📔 第 18 章 探讨C++新标准 学习笔记
1. 复习C++11功能¶
1.1 新类型¶
C++11新增类型:
- 整型
long long
unsigned long long
- 字符表示
char16_t
char32_t
1.2 统一的初始化¶
C++11 扩大了用大括号起的列表(初始化列表)的适用范围。使其可用于所有内置类型
和用户定义类型(即类对象)
。列表初始化的三种方式:
- 使用初始化列表时,可添加
等号(=)
,也可不添加。
- 可以用于
new表达式
中
- 创建对象时,可使用
大括号(而不是圆括号)
括起来的列表来调用构造函数。
1.2.1 缩窄¶
-
初始化列表语法可防止缩窄,即禁止将数值赋给无法存储它的数值变量,即将值存储到比它“窄”的变量中。
-
编译器会禁止变窄类型的转换,但允许在范围内变宽的转换。
1.2.2 std::initializer_list
¶
-
C++11中提供
initializer_list模板类
,可用作构造函数的参数。 -
如果类有接受
initializer_list
作为参数的构造函数,则初始化列表语法
就只能用于该构造函数。 - 列表中的元素必须是
同一种类型
或者可转换为同一种类型
。
1.3 声明¶
1.3.1 auto
¶
C++11
使用auto关键字
实现自动类型推断
。-
要求进行显式初始化,让编译器能够将变量的类型设置为初始值的类型。
-
auto关键字
也可简化模板声明
。
1.3.2 decltype
¶
- decltype关键字:将变量的类型声明为表达式指定的类型。
1.3.3 返回类型后置¶
- C++11新增函数声明语法:在函数名和参数列表后面指定返回类型。
- 使用 decltype来指定模板函数的返回类型:
1.3.4 模板别名:using
¶
解决标识符冗长或复杂的问题。
-
早期使用
typedef
- 不能用于模板部分具体化
-
C++11提供
using=
- 可用于模板部分具体化
1.3.5 nullptr
¶
C++11 新增关键字 nullptr
来表示空指针
。它是指针类型
,不能转换为整型类型。
为向后兼容,C++11仍允许使用 0
来表示空指针,因此表达式 nullptr==0
为 true
。
1.4 智能指针¶
因为在程序中使用 new 分配内存,未将其及时 delete释放。引入智能指针auto_ptr
。
C++11抛弃auto_ptr
,并新增了三种智能指针:unique_ptr
、shared_ptr
和 weak_ptr
。
1.5 异常规范的修改¶
指出函数不会引发异常有一定的价值,所以C++11添加了关键字noexpect
:
1.6 作用域内枚举¶
在C++11中新增一种枚举。使用 class
或者 struc
t来定义。
新枚举要求进行显式限定
,以避免名称冲突
。所以在引用枚举时,则使用
1.7 对类的修改¶
-
显式转换运算符
- 早期的自动类型转换会导致意外问题,所以在C++11中引入关键字
explicit
,以禁止单参数构造函数导致的自动转换
。
- 早期的自动类型转换会导致意外问题,所以在C++11中引入关键字
-
类内成员初始化
- 可使用等号或者大括号版本的初始化,但不能使用圆括号版本的初始化。
- 类内成员初始化,可避免在构造函数中编写重复的代码。
1.8 模板和STL方面的修改¶
-
基于范围的for循环
- 如果要在循环中修改数组或容器中的每个元素,可使用引用类型。
-
新的STL容器
- C++11新增STL容器:
forward_list
,unordered_map
,unordered_multimap
,unordered_set
,unordered_multiset
。 - C++11新增模板array,实例化时,可指定元素类型和固定的元素数。
- C++11新增STL容器:
-
新的STL方法
- C++11新增STL方法
cbegin()
cend()
crbegin()
crend()
- 其中
crbegin()
、crend()
是rbegin()
、rend()
的const
版本。 - 除传统的
复制构造函数
和常规赋值运算符
外,STL容器还有移动构造函数
和移动赋值运算符
。
- C++11新增STL方法
-
valarray升级
- C++11中添加了两个函数 begin() 和 end()。都接受 valarray 作为参数,并返回迭代器。
-
抛弃export
- C++98新增
- C++终止用法,但为了兼容还保留关键字 export。
-
尖括号
- C++在声明嵌套模板时不再需要使用空格将尖括号分开。
1.9 右值引用¶
-
左值引用
- 左值:一个表示数据的表达式,程序可获取其地址。
- 左值出现在赋值语句的左边,但修饰符的出现const使得声明的标识符,即不能给它赋值,但可获取其地址。
-
右值引用
- C++11新增
右值引用
,使用&&
表示。右值引用可以关联到右值,即可出现在赋值表达式右边,但不能对其应用地址运算符的值
。 右值
包括:字面常量
、表达式
和返回值的函数
(函数返回的不能是引用)。- 引入右值引用的主要目的之一:实现
移动语义
。
- C++11新增
2. 移动语义和右值引用¶
2.1 需要移动语义的原因¶
-
常规复制构造函数
- 使用
const左值引用
作为参数,使得引用关联到左值实参
。 - 可执行深复制
- 使用
-
移动构造函数
- 使用
右值引用
作为参数,将引用关联到右值实参
。 - 只调整记录
- 在将所有权转移给新对象的过程中,移动构造函数可能修改其实参,意味着
右值引用
参数不应是const
。
- 使用
2.2 移动构造函数¶
2.3 赋值¶
- 适用于构造函数的移动语义考虑也适用于赋值运算符。
3. 新的类功能¶
3.1 特殊的成员函数¶
-
原有的4个特殊成员函数
- 默认构造函数
- 复制构造函数
- 复制赋值运算符
- 析构函数
-
C++新增的2个
- 移动构造函数
- 移动赋值运算符
这些成员函数是编译器在各种情况下自动提供的。
- 如果提供了
析构函数、复制构造函数或者复制赋值运算符
,编译器将不会自动
提供移动构造函数
和移动赋值运算符
。 - 如果提供了
移动构造函数
或移动赋值运算符
,编译器将不会自动
提供复制构造函数
和复制赋值运算符
。
3.2 默认的方法和禁用的方法¶
- C++11中如果提供了
移动构造函数
,所以编译器不会自动创建默认的构造函数、复制构造函数和复制赋值构造函数。可使用关键字 default
显式声明
方法的默认版本。
delete
可用于禁止编译器使用特定方法。
- 也可禁止特定的转换
- 关键字
default
只能用于6个特殊成员函数
,但delete
可用于任何成员函数
。
3.3 委托构造函数¶
- C++11 允许在一个构造函数的定义中使用另一个构造函数,这种方式称为
委托
。
3.4 继承构造函数¶
-
C++98 使用
名称空间中函数可用
的方式。 -
C++11 中使用
派生类
继承基类
的所有构造函数(默认构造函数、复制构造函数和移动构造函数除外),但不会使用与派生类构造函数的特征标匹配的构造函数
。
3.5 管理虚方法:override
和 final
¶
-
在C++11中,可使用
虚说明符override
指出要覆盖
一个函数,将其放在参数列表后面。如果声明与基类方法不匹配,编译器将视为错误。 -
final说明符
:禁止
派生类覆盖
特定的虚方法,所以在参数列表后面加上final
。 -
override
和final
并非关键字
,而是具有特殊含义的标识符
。
4. Lambda函数¶
Lambda 函数,也叫做Lambda表达式。
匿名函数无需给出函数命名。
4.1 比较函数指针、函数符和Lambda函数¶
在C++11中,对于接受函数指针或者函数符的函数,可使用匿名函数定义作为其参数。
匿名函数与函数的区别:
- 使用
[]
替代函数名
没有声明返回类型
(返回类型相当于使用decltype根据返回值推断得到)- 如果
lambda不包含返回语句
,推断出的返回类型将为void
。
当且仅当, lambda表达式完全由一条返回语句组成时,自动类型推断才管用。否则,需要使用新增的返回类型后置语法:
4.2 使用Lambda原因¶
四个角度来分析
- 距离
- 函数定义离使用地点近。
- 简洁
- 函数符代码比函数和lambda代码更繁琐。
- 效率
- 三种方式的相对效率取决于编译器内联。
- 功能
- lambda可访问
作用域内
的任何动态变量
- 要捕获使用的变量,可将其名称放在中括号内。
- 如果
只指定变量名
,如[z]
,则将值访问
变量 - 如果在
名称前加上 &
,如[&count],则按引用访问
变量
- 如果
[&]
按引用访问
所有动态变量[=]
按值访问
所有动态变量。
- lambda可访问
函数指针方法阻止内联
,因为编译器传统上不会内联其他地址被获取的函数,因为函数地址的概念意味着非内联函数。而函数符
和lambda
通常不会阻止内联
。
可以给lambda指定一个名称,然后使用名称来替代。
C++中引入lambda的主要目的:能将类似于函数的表达式用做接受函数指针或函数符的函数的参数。
5. 包装器¶
C++提供多个包装器
(wrapper
,也叫做适配器(adapter)
)。
C++11中提供了其他的包装器:
bind
- 可替代
bind1st
和bind2nd
- 可替代
mem_fn
- 可将成员函数作为常规函数进行传递
reference_wrapper
- 能创建行为像引用但可被复制的对象
function
- 以统一的方式处理多种类似于函数的形式
5.1 包装器function及模板的低效性¶
模板function 是在头文件functional 中声明,从调用特征标的角度定义了一个对象,可用于包装调用特征相同的函数指针、函数对象或者lambda表达式。
6. 可变参数模板¶
要创建可变参数模板,需要理解以下要点:
- 模板参数包(parameter pack)
- 函数参数包
- 展开参数包
- 递归
6.1 模板和函数参数包¶
C++11中提供省略号表示的元运算符(meta-operator)。
模板参数包
:可以声明表示模板参数包
的标识符,模板参数包是一个类型列表
。函数参数包
:可以声明表示函数参数包
的标识符,函数参数包是一个值列表
。
6.2 展开参数包中使用递归¶
将函数参数包展开,对列表中的第一项进行处理,再将余下的内容传递给递归调用,以此类推,直到列表为空。
7. C++11新增的功能¶
7.1 并行编程¶
为解决并行性
的问题,C++定义了一个支持线程化执行
的内存模型,添加了关键字 thread_local
,提供了相关库支持。
关键字 thread_local
将变量声明为静态存储
,其持续性和特定线程相关,即定义变量的线程过期
时,变量也过期
。
库支持由原子操作(automic、operation)库
和线程支持库
组成。原子操作库提供的头文件:
thread
mutex
condition
variable
future
7.2 新增的库¶
在C++11中新增专用的头文件库
- random:随机数扩展工具
- chrono:处理时间间隔的途径
- tuple:支持模板tuple。
- ratio:编译阶段的有理数算术库
- regex:正则表达式库。用于指定与文本字符串的内容匹配。
7.3 低级编程¶
低级编程中的“低级”指的是抽象程度,而不是编程质量。
C++11给低级编程人员提供的两个便利:
- 放松了
POD(Plain Old Data)
的要求。 - 允许
共用体的成员
有构造函数
和析构函数
。