目录
1. 复习C++11功能
1.1 新类型
C++11新增类型:
- 整型
long long
unsigned long long
- 字符表示
char16_t
char32_t
1.2 统一的初始化
C++11 扩大了用大括号起的列表(初始化列表)的适用范围。使其可用于所有内置类型
和用户定义类型(即类对象)
。列表初始化的三种方式:
使用初始化列表时,可添加等号(=),也可不添加。
int x = {5};
short quar[5] {4,5,6,2,1};
可以用于new表达式中
int *ar = new int [4] {2,4,6,7}; C++11
创建对象时,可使用大括号(而不是圆括号)括起来的列表来调用构造函数。
class Stump
{
private:
int roots;
double weight;
public:
Stump(int r,rouble w) : roots(r), weight(w) {}
};
Stump s1(3,15.6); // old style
Stump s2{5,43.4}; // C++11
Stump s3{4,32.1}; // C++11
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关键字:将变量的类型声明为表达式指定的类型。
template <typename T, typename U> void ef(T t, U u) { decltype(T*U) tu; ... }
1.3.3 返回类型后置
-
C++11新增函数声明语法:在函数名和参数列表后面指定返回类型。
double f1(double,int); // 传统方式 auto f2(double,int)->double; // C++11中的新语法
-
使用 decltype来指定模板函数的返回类型:
template<typename T, typename U> auto eff(T t, U u) -> decltype(T*U) { // 当编译器遇到 ef的参数列表时,T和 U不再作用域内,因此必须在参数列表后使用decltype。 .... }
1.3.4 模板别名:using
解决标识符冗长或复杂的问题。
- 早期使用
typedef
- 不能用于模板部分具体化
typedef std::vector<std::string>::iterator itType;
- C++11提供
using=
- 可用于模板部分具体化
template <typename T> using arr = std::array<T,int>; // 模板部分具体化
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
:
void f875(short, short) noexpect; // 不会抛出异常
1.6 作用域内枚举
在C++11中新增一种枚举。使用 class
或者 struc
t来定义。
enum old1 {yes,no,maybe}; // 传统方式
enum class New1 {never,sometimes,often,always}; // 新方式
enum struct New2 {never,lever,sever}; // 新方式
新枚举要求进行显式限定
,以避免名称冲突
。所以在引用枚举时,则使用
New1::never; New2::never;
1.7 对类的修改
-
显式转换运算符
- 早期的自动类型转换会导致意外问题,所以在C++11中引入关键字
explicit
,以禁止单参数构造函数导致的自动转换
。
- 早期的自动类型转换会导致意外问题,所以在C++11中引入关键字
-
类内成员初始化
- 可使用等号或者大括号版本的初始化,但不能使用圆括号版本的初始化。
- 类内成员初始化,可避免在构造函数中编写重复的代码。
1.8 模板和STL方面的修改
-
基于范围的for循环
double price[5] = {4.99,10.99,6.87,7.99,8.49}; for (auto x : price) std::out << x << std::endl;
- 如果要在循环中修改数组或容器中的每个元素,可使用引用类型。
-
新的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使得声明的标识符,即不能给它赋值,但可获取其地址。
int n; int * pt = new int; const int b = 101; // 不能赋值, int & rn = n; int & rt = *pt; const int &rb = b; // b标识符const引用地址
-
右值引用
- C++11新增
右值引用
,使用&&
表示。右值引用可以关联到右值,即可出现在赋值表达式右边,但不能对其应用地址运算符的值
。 右值
包括:字面常量
、表达式
和返回值的函数
(函数返回的不能是引用)。- 引入右值引用的主要目的之一:实现
移动语义
。
int x = 10; int y = 23; int && r1 = 13; // 字面常量 int && r2 = x + y; // 表达式 double && r3 = std::sqrt(2.0); // 函数返回值
- C++11新增
2. 移动语义和右值引用
2.1 需要移动语义的原因
-
常规复制构造函数
- 使用
const左值引用
作为参数,使得引用关联到左值实参
。 - 可执行深复制
- 使用
-
移动构造函数
- 使用
右值引用
作为参数,将引用关联到右值实参
。 - 只调整记录
- 在将所有权转移给新对象的过程中,移动构造函数可能修改其实参,意味着
右值引用
参数不应是const
。
- 使用
2.2 移动构造函数
2.3 赋值
- 适用于构造函数的移动语义考虑也适用于赋值运算符。
3. 新的类功能
3.1 特殊的成员函数
- 原有的4个特殊成员函数
- 默认构造函数
- 复制构造函数
- 复制赋值运算符
- 析构函数
- C++新增的2个
- 移动构造函数
- 移动赋值运算符
这些成员函数是编译器在各种情况下自动提供的。
- 如果提供了
析构函数、复制构造函数或者复制赋值运算符
,编译器将不会自动
提供移动构造函数
和移动赋值运算符
。 - 如果提供了
移动构造函数
或移动赋值运算符
,编译器将不会自动
提供复制构造函数
和复制赋值运算符
。
3.2 默认的方法和禁用的方法
-
C++11中如果提供了
移动构造函数
,所以编译器不会自动创建默认的构造函数、复制构造函数和复制赋值构造函数。可使用关键字 default
显式声明
方法的默认版本。class Someclass { public: Someclass(Someclass &&); Someclass() = default; Someclass(const Someclass &) = default; Someclass & operator=(const Someclass &) = default; ... };
-
关键字
delete
可用于禁止编译器使用特定方法。- 也可禁止特定的转换
-
关键字
default
只能用于6个特殊成员函数
,但delete
可用于任何成员函数
。
3.3 委托构造函数
- C++11 允许在一个构造函数的定义中使用另一个构造函数,这种方式称为
委托
。
Notes::Notes(int kk,double xx) : Notes(kk,xx,"Uh") {/**/}
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
。
[] (int x) {return x % 3 == 0;}
当且仅当, lambda表达式完全由一条返回语句组成时,自动类型推断才管用。否则,需要使用新增的返回类型后置语法:
[] (double x)-> double{int y = x; return x - y;} // 返回类型为double
4.2 使用Lambda原因
四个角度来分析
- 距离
- 函数定义离使用地点近。
- 简洁
- 函数符代码比函数和lambda代码更繁琐。
- 效率
- 三种方式的相对效率取决于编译器内联。
- 功能
- lambda可访问
作用域内
的任何动态变量
- 要捕获使用的变量,可将其名称放在中括号内。
- 如果
只指定变量名
,如[z]
,则将值访问
变量 - 如果在
名称前加上 &
,如[&count],则按引用访问
变量
- 如果
[&]
按引用访问
所有动态变量[=]
按值访问
所有动态变量。
- lambda可访问
函数指针方法阻止内联
,因为编译器传统上不会内联其他地址被获取的函数,因为函数地址的概念意味着非内联函数。而函数符
和lambda
通常不会阻止内联
。
可以给lambda指定一个名称,然后使用名称来替代。
auto mod3 = [] (int x) {return x % 3 == 0;} // mod3 a name for the lambda count1 = std::count_if(n1.begin(),n1,end(),mod3); count2 = std::count_if(n2.begin(),n2.end(),mod3);
C++中引入lambda的主要目的:能将类似于函数的表达式用做接受函数指针或函数符的函数的参数。
5. 包装器
C++提供多个包装器
(wrapper
,也叫做适配器(adapter)
)。
C++11中提供了其他的包装器:
bind
- 可替代
bind1st
和bind2nd
- 可替代
mem_fn
- 可将成员函数作为常规函数进行传递
reference_wrapper
- 能创建行为像引用但可被复制的对象
function
- 以统一的方式处理多种类似于函数的形式
5.1 包装器function及模板的低效性
模板function 是在头文件functional 中声明,从调用特征标的角度定义了一个对象,可用于包装调用特征相同的函数指针、函数对象或者lambda表达式。
// 接受一个char参数和一个int参数,并返回一个double值的任何函数指针、函数对象或者lambda表达式赋值给它 std::function<double>(char, int) > fdci;
6. 可变参数模板
要创建可变参数模板,需要理解以下要点:
- 模板参数包(parameter pack)
- 函数参数包
- 展开参数包
- 递归
6.1 模板和函数参数包
C++11中提供省略号表示的元运算符(meta-operator)。
模板参数包
:可以声明表示模板参数包
的标识符,模板参数包是一个类型列表
。函数参数包
:可以声明表示函数参数包
的标识符,函数参数包是一个值列表
。
// Args可以与任意数量的类型匹配
template <typename... Args> // Args 是模板参数包
void show_list(Args...args) // args 是函数参数包
{
...
}
6.2 展开参数包中使用递归
将函数参数包展开,对列表中的第一项进行处理,再将余下的内容传递给递归调用,以此类推,直到列表为空。
template <typename T ,typename...Args>
void show_list(T value, Args..args)
{
show_list(args...);
}
// 对于按值传递来说,效率低,所以可更改为;
void show_list1(const T& value, const Args&... args)
{
show_list1(args...);
}
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)
的要求。 - 允许
共用体的成员
有构造函数
和析构函数
。