第 18 章 探讨 C++ 新标准
18.1 复习前面介绍过的 C++11 功能
新增 long long
和 unsigned long long
类型。
使用大括号进行内置数据或对象的初始化,可以不添加等号;也提供了使用 initializer_list
的初始化方法。
auto
关键字自动推导对象类型,decltype
关键字可以通过表达式推导类型,在函数声明末尾推导返回类型,常用的方法为 auto function(T t, U u) -> decltype(t + u)
。
可以使用 typedef std::vector<double>::iterator itType
或 using itType = std::vector<double>::iterator
为标识符创建别名。
引入空指针 nullptr
和三种智能指针 unique_ptr
,shared_ptr
和 weak_ptr
。
在函数声明后加上关键字 noexcept
表示函数将不会引发异常。
允许作用域内枚举。允许类内成员初始化。
引入关键字 explicit
以阻止单参数函数通过构造函数进行自动转换。
新增基于范围的 for
循环,新增 STL 容器、方法。
左值是一个表示数据的表达式,包括变量名或解除引用的指针,程序可以获取它的地址。右值包括字面常量,包括 类似 x + y
的表达式等,不能对它应用地址运算。
18.2 移动语义和右值引用
在执行语句 vector<string> vstr_cpy(allcaps(vstr));
时首先调用 allcaps()
函数生成了一个临时向量,再通过复制构造函数将临时向量复制到新向量 vstr_cpy
中,最后销毁临时向量。移动语义可以避免移动数据,而只修改数据的所有权。
复制构造函数需要深度复制对象中的所有元素,而移动构造函数则只需要转让数据的所有权,然后将原来的指针设置为空指针以防止调用析构函数时对同一个地址调用两次 delete
。这种方法同样适用于赋值运算符,它们的参数类型不能是 const
引用。
需要正确地使用右值引用使编译器知道什么时候使用复制构造函数,什么时候使用移动构造函数。
18.3 新的类功能
C++ 默认提供默认构造函数、复制构造函数、复制赋值运算符、析构函数、移动构造函数、移动赋值运算符。
如果提供了复制构造函数或复制赋值运算符,编译器不会自动提供移动构造函数和移动赋值运算符。
如果提供了移动构造函数或移动赋值运算符,编译器不会自动提供复制构造函数和复制赋值运算符。
如果需要某个函数的默认函数,则在函数声明末尾加上 = default
,不需要的默认函数需要加上 = delete
。
构造函数允许委托给另一个构造函数,Notes::Notes(int kk, double xx) : Notes(10, 2.2, "that") { ... }
可行。构造函数可继承,但调用时仅初始化基类的成员。
在虚函数末尾声明 override
或 final
表示该虚函数应该覆盖基类虚函数或不能被重新定义。
18.4 Lambda 函数
当使用函数变量作为参数时,可以考虑使用 lambda 函数,表示方法为 [](argumens list){operation;}
,这将生成一个匿名函数,返回类型相当于使用 decltype
自动推导。当 lambda 表达式仅由一条语句组成时才使用自动推断,否则应使用返回类型后置语法 [](double x)->double{int y = x; return y - x;}
。
当需要多次使用 lambda 函数时可以给它指定名称 auto mod = [](int x){return -x;};
。
方括号内表示 lambda 函数可以捕获的变量,[=]
表示所有动态变量,[&]
表示所有引用变量。
18.5 包装器
使用包装器可以减少针对同一种模板生成多个同类型的实例化而造成的效率低下问题。
18.6 可变参数模板
使用 template<typename... Args> void show(Args... args);
声明函数参数可以为任意数量的任意参数类型匹配。
递归调用自己即可依次使用每一个参数,实现方法为 template<typename T, typename... Args> void show(T t, Args... args) { cout << t << ","; show(args...); }
。为了防止最后一个输出后面有逗号,可以单独再设计函数 template<typename T> void show(T t) { cout << t << endl; }
,当 args
包只剩下最后一个参数时会调用这个新函数。可以将参数改为 const Args... &args
使用引用。