最重要的事:一定要先从官方文档阅读
网上大部分教程都是某一个版本、或者是以自己的理解来讲解,但是工具是变化的,C++在不同的时期会有不同的特性,所以紧跟时代是重要的。
- C++ 库标头
#include <iostream>
现代 C++
资源和智能指针
- C 样式编程的一个主要 bug 类型是内存泄漏。
泄漏通常是由于为分配的内存的调用失败引起的 delete new 。 现代 C++ 强调“资源获取即初始化”(RAII) 原则。
请尽可能使用智能指针管理堆内存。
std::string 和 std::string_view
- C 样式字符串是 bug 的另一个主要来源。
通过使用 std::string 和 std::wstring,,几乎可以消除与 C 样式字符串关联的所有错误。
std::vector 和其他标准库容器
- 标准库容器都遵循 RAII 原则。 它们为安全遍历元素提供迭代器。
使用 vector 替代原始数组,来作为 C++ 中的序列容器。
使用 map(而不是 unordered_map),作为默认关联容器。
对于退化和多案例,使用 set、multimap 和 multiset。
vector<string> apples;
apples.push_back("Granny Smith");
map<string, string> apple_color;
// ...
apple_color["Granny Smith"] = "Green";
标准库算法
- 在假设需要为程序编写自定义算法之前,请先查看 C++ 标准库算法。
标准库包含许多常见操作(如搜索、排序、筛选和随机化)的算法分类,这些分类在不断增长。
for_each,默认遍历算法(以及基于范围的 for 循环)。
transform,用于对容器元素进行非就地修改
find_if,默认搜索算法。
sort、lower_bound 和其他默认的排序和搜索算法。
auto comp = [](const widget& w1, const widget& w2)
{ return w1.weight() < w2.weight(); }
sort( v.begin(), v.end(), comp );
auto i = lower_bound( v.begin(), v.end(), comp );
用 auto 替代显式类型名称
- C++11 引入了 auto 关键字,以便可将其用于变量、函数和模板声明中。
auto 会指示编译器推导对象的类型,这样你就无需显式键入类型。
当推导出的类型是嵌套模板时,auto 尤其有用
map<int,list<string>>::iterator i = m.begin(); // C-style
auto i = m.begin(); // modern C++
基于范围的 for 循环
- 使用基于范围的 for 循环,此循环包含标准库容器和原始数组。
#include <iostream>
#include <vector>
int main()
{
std::vector<int> v {1,2,3};
// C-style
for(int i = 0; i < v.size(); ++i)
{
std::cout << v[i];
}
// Modern C++:
for(auto& num : v)
{
std::cout << num;
}
}
用 constexpr 表达式替代宏
- C 样式编程通常使用宏来定义编译时常量值。 但宏容易出错且难以调试。
在现代 C++ 中,应优先使用 constexpr 变量定义编译时常量。
#define SIZE 10 // C-style
constexpr int size = 10; // modern C++
const和define的区别以及const的优点
统一初始化
- 在现代 C++ 中,可以使用任何类型的括号初始化。
在初始化数组、矢量或其他容器时,这种初始化形式会非常方便。
#include <vector>
struct S
{
std::string name;
float num;
S(std::string s, float f) : name(s), num(f) {}
};
int main()
{
// C-style initialization
std::vector<S> v;
S s1("Norah", 2.7);
S s2("Frank", 3.5);
S s3("Jeri", 85.9);
v.push_back(s1);
v.push_back(s2);
v.push_back(s3);
// Modern C++:
std::vector<S> v2 {s1, s2, s3};
// or...
std::vector<S> v3{ {"Norah", 2.7}, {"Frank", 3.5}, {"Jeri", 85.9} };
}
移动语义
- 现代 C++ 提供了移动语义,此功能可以避免进行不必要的内存复制。
在此语言的早期版本中,在某些情况下无法避免复制。
移动操作会将资源的所有权从一个对象转移到下一个对象,而不必再进行复制。
一些类拥有堆内存、文件句柄等资源。 实现资源所属的类时,可以定义此类的移动构造函数和移动赋值运算符。 在解析重载期间,如果不需要进行复制,编译器会选择这些特殊成员。 如果定义了移动构造函数,则标准库容器类型会在对象中调用此函数。
// string_concatenation.cpp
// compile with: /EHsc
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s = string("h") + "e" + "ll" + "o";
cout << s << endl;
}
Lambda 表达式
- 在 C 样式编程中,可以通过使用函数指针将函数传递到另一个函数。
函数指针不便于维护和理解。
它们引用的函数可能是在源代码的其他位置中定义的,而不是从调用它的位置定义的。
此外,它们不是类型安全的。
现代 c + + 提供 函数对象、重写 运算符的类,从而使它们可以像函数一样进行调用。 创建函数对象的最简便方法是使用内联 lambda 表达式。
std::vector<int> v {1,2,3,4,5};
int x = 2;
int y = 4;
auto result = find_if(begin(v), end(v), [=](int i) { return i > x && i < y; });
现代 C++ 处理异常和错误的最佳做法
- 现代 c + + 强调异常,而不是错误代码,作为报告和处理错误条件的最佳方式。
线程间通信机制
- 对线程间通信机制使用 C++ 标准库 std::atomic 结构和相关类型。
C++17 管理可变类型的内存位置 variant
- C 样式编程通常通过并集使不同类型的成员可以占用同一个内存位置,从而节省内存。
但是,并集不是类型安全的,并且容易导致编程错误。
C++17 引入了更加安全可靠的 std::variant 类,来作为并集的替代项。
可以使用 std::visit 函数以类型安全的方式访问 variant 类型的成员。
语法
引用
左值引用声明符 Lvalue & (C 风格 取地址运算符)
-
不要将引用声明与使用 地址运算符混淆。
&当标识符前面有类型(例如int或char)标识符声明为对类型的引用时。
如果 &标识符 前面没有类型,则用法是地址运算符的用法。 -
引用类型函数自变量
向函数传递引用而非大型对象的效率通常更高。 这使编译器能够在保持已用于访问对象的语法的同时传递对象的地址。 -
引用类型函数返回值
返回的信息是一个返回引用比返回副本更有效的足够大的对象。
函数的类型必须为左值。
引用的对象在函数返回时不会超出范围。 -
指针的引用
BTree* //指针
BTree& //引用
BTree** //二重指针
BTree*& //指针的引用
右值引用声明符 Rvalue &&
- 移动语义
管理内存缓冲区的 C++ 类 MemoryBlock 就是用移动语义编写出来的。
若要实现移动语义,通常为类提供 移动构造函数( 可选)和移动赋值运算符 (operator=) 。
其源是右值的复制和赋值操作随后会自动利用移动语义。
与默认复制构造函数不同,编译器不提供默认移动构造函数。
为什么使用移动语义?
若要更好地了解移动语义,请考虑将元素插入 vector 对象的示例。 如果超出 vector 对象的容量,则 vector 对象必须为其元素重新分配内存,然后将所有元素复制到其他内存位置,以便为插入的元素腾出空间。 当插入操作复制元素时,它首先创建一个新元素。 然后,它会调用复制构造函数将数据从上一个元素复制到新元素。 最后,它会销毁上一个元素。 使用移动语义可以直接移动对象,而无需进行昂贵的内存分配和复制操作。