目录:点我
一、复习
1. 新类型
C++ 11 新增了类型 long long 和 unsigned long long ,以支持 64 位(或更宽)的整型;新增了类型 char16_t 和 char32_t ,以支持 16 位和 32 位的字符表示;还新增了“原始字符串”。
2. 统一的初始化
C++ 11 扩大了用大括号括起的列表(初始化列表)的适用范围,使其可用于所有内置类型和用户定义的类型(即类对象)。使用初始化列表时,可添加等号,也可不添加:
3. 声明
C++ 11 提供了多种简化声明的功能:
4. 智能指针
如果在程序中使用 new 从堆分配内存,等到不再需要时,应使用 delete 释放,新增智能指针帮助自动化完成该过程。C++ 11 摒弃了 auto_ptr ,新增了 unique_ptr 、shared_ptr 、weak_ptr 。
5. 异常规范
用于指出函数可能引发哪些异常:
6. 作用域内枚举
传统枚举:
- 提供了一种创建名称常量的方法,但其类型检查相当低级。
- 枚举名的作用域为枚举定义所属的作用域,这意味着如果在同一个作用域内定义两个枚举,它们的枚举成员不能同名。
- 枚举可能不是可完全移植的,因为不同的实现可能选择不同的底层类型。
为解决这些问题,新增枚举:
7. 对类的修改
禁止隐式转换运算符:
类内成员初始化:
可以使用等号和大括号初始化,但不能使用圆括号。通过类内初始化可以避免在构造函数内编写重复代码。
8. 模板和 STL
基于范围的 for 循环:
新增 STL 容器:
容器 | 作用 |
forward_list | 单向链表 |
unordered_map | 无序 map |
unordered_multimap | 无序、可重复 map |
unordered_set | 无序 set |
unordered_multiset | 无需、可重复 set |
array | 数组 |
新增 STL 方法:
方法 | 说明 |
cbegin() | 指向容器第一个元素,并将元素视为 const |
cend() | 指向容器最后一个元素的后面,并将元素视为 const |
对 valarray 进行升级,添加了 begin() 和 end() ,使之可以被迭代器访问,从而能够将基于范围的 STL 算法用于该容器。
摒弃 export ,原本作用为让程序员能够将模板定义放在接口文件和实现文件中,其中前者包含原型和模板声明,后者包含模板函数和方法的定义。
新增尖括号的识别,避免了与 >> 运算符混淆。
9. 右值引用
左值是一个表示数据的表达式(变量名、解除引用的指针),最初只能出现在赋值语句左边,但是随着 const 引入,也可以出现在赋值语句右边:
右值则是一个表示运算的表达式(不能对其赋值),不能应用取地址符,这种方式更像是创建并保存了一个表达式结果的副本,并引用了这个副本:
二、移动语义和右值引用
1. 移动语义
vector 和 string 类都使用动态内存分配,因此它们必须定义使用某种 new 版本的拷贝构造函数。为初始化对象 vstr_copy1 ,拷贝构造函数 vector<string> 将使用 new 给 2 万个 string 对象分配内存,而每个 string 对象又将调用 string 的拷贝构造函数,该构造函数使用 new 为 1 千个字符分配内存。接下来全部的字符都将从 vstr 控制的内存种复制到 vstr_copy1 控制的内存中。这样的工作量巨大,并且存在问题:解决这个问题的方法为不删除 tmp 变量,而是转移所有权,该操作称为移动语义(move semantics),这样避免了删除和拷贝原数据。要实现它,需要让编译器直到何时需要复制,何时不需要,这就是右值发挥作用的地方。可以定义两个构造函数,一个是常规拷贝构造函数,使用 const 左值引用作为参数,这个引用关联到左值实参,如语句 1 的 vstr ;另一个是移动构造函数,使用右值引用作为参数,该引用关联到右值实参,如语句 2 种 allcaps 函数的返回值 tmp 。拷贝构造函数可执行深复制,而移动构造函数只负责调整记录(转换所有权)。由于移动构造函数可能修改其实参,因此右值引用不应是 const 。
虽然右值引用可支持移动语义,但是需要执行两个步骤来执行。
- 首先,让编译器直到何时可使用移动语义:
- 对象 one 是左值,因此与左值引用匹配,调用拷贝构造函数;one + three 是右值,与右值引用匹配,调用移动构造函数。
- 然后,编写移动构造函数,提供所需的方法。
2. 赋值
适用于构造函数的移动语义考虑也适用于赋值运算符,即移动赋值运算符:
与上一种情况相同,由于修改了源对象,因此不能使用 const 引用。
3. 强制移动
上述两个例子使用了右值,如果要使用左值,就需要强制移动:
由于
choices[pick] 是左值,因此上述赋值语句将使用复制赋值运算符,而不是移动赋值运算符,但如果能让它看起来像右值,便将使用移动赋值运算符。为此可使用运算符 static_cast<> 将对象的类型强制转换为 Useless && ,但 C++ 11 在 utility 头文件中提供了一种更简单的方式 std::move :如果定义了移动赋值运算符,则调用移动赋值运算符;如果没有定义则调用复制赋值运算符;如果复制赋值运算符也没有定义,则不允许上述赋值。
三、新的类功能
1. 特殊成员函数
C++ 11 在原有的 4 个特殊成员函数(默认构造函数、复制构造函数、复制赋值运算符和析构函数)的基础上,新增了移动构造函数和移动赋值运算符。这些成员函数是编译器在各种情况下自动提供的。但是存在一些例外:
- 如果显式的提供了析构函数、复制构造函数或复制赋值运算符,编译器将不会自动提供移动构造函数和移动赋值运算符;
- 如果显式的提供了移动构造函数或移动赋值运算符,编译器将不会自动提供复制构造函数和复制赋值运算符;
- 默认的移动构造函数和移动赋值运算符的工作方式与复制版本类似,将会采取复制的策略处理;
- 如果由于显式的提供了移动构造函数,导致系统没有自动创建默认的构造函数、拷贝构造函数和复制赋值运算符,此时可以使用 default 关键字显式地生命这些方法为默认版本,该方法只能用于这 6 个特殊的成员函数:
- 相对的,关键字 delete 可用于禁止使用特定方法,可以用于任何成员函数:
- 如果要启动移动方法的同时禁用复制方法,则会导致左值的操作失效:
2. 委托构造函数
如果需要给类提供多个构造函数,其中部分构造函数的代码需要重用,C++ 11 允许在一个构造函数的定义中使用另一个构造函数,这被称为委托构造函数:
3. 继承构造函数
为了进一步简化编码工作,C++ 11 还提供了一种让派生类能够继承基类构造函数的机制。C++ 98 提供了一种让名称空间中函数可用的语法:
也可以使用这种方法让基类所有非特殊成员函数对派生类可用:
C++ 11 将这种方法用于构造函数,这让派生类继承基类的所有构造函数(除默认构造函数、复制构造函数和移动构造函数),但不会使用与派生类构造函数的特征标匹配的构造函数(理解为重写,因此不调用父类的方法而是使用派生类自己的)。
4. 管理虚方法
假设基类声明了一个虚方法,而要在派生类中提供不同的版本,这将覆盖旧版本。但是由之前的讨论可知,如果特征标不匹配,将隐藏而不是覆盖旧版本:
此时由于派生类定义的虚函数参数为指针,而不是 char ,因此导致程序不能执行:
C++ 11 可使用虚说明符 override 在派生类中指出要覆盖一个虚函数:
如果想要禁止派生类覆盖特定方法,可以在基类中使用 final 虚说明符:
由于虚说明符不是关键字,因此编译器将根据上下文来确定其是否具有特殊含义,在其他场景中可以作为常规标识符使用。
四、Lambda 函数
Lambda 函数是 λ 演算——一种定义和应用函数的数学系统,可以使用匿名函数替代函数指针和函数符。首先使用函数指针、函数符和 Lambda 函数来实现一个函数:判断有多少个元素能够被 3 整除。
- 函数指针:
- 函数符:
- Lambda 函数:
需要注意的是,自动推断只有在一条返回语句组成时才有效,否则需要显式给出具体类型:
需要注意的是,自动推断只有在一条返回语句组成时才有效,否则需要显式给出具体类型:
如果需要多次使用同一个 Lambda 函数,可以给它指定一个名称,并且可以作为正常函数使用:
Lambda 函数可访问作用域内的任何动态变量,需将其放入 [] 内:
- 按值使用:[z] ;
- 引用:[&z] ;
- 访问所有动态变量:[=] 、[&];
- 混合使用:[&, z] ,表示 z 按值使用,其他变量按引用使用。
五、包装器
包装器(wrapper,也叫适配器 adapter),用于给其他编程接口提供更一致或更合适的接口。
ef 可以是函数名、函数指针、函数对象或有名称的 lambda 表达式,这些都为可调用的类型(callable type),由于可调用的类型丰富,因此可能导致模板的效率降低,下面是一个例子:
可以发现,在每次调用中,模板参数 T 都被设置为类型 double ,但是 F 的类型却各不相同,下面进行分析:
use_f(y, dub);:dub 是一个函数指针,因此 F 的类型为double(*)(double);
use_f(y, square);:与上一个相同;
use_f(y, Fp(5.0));:F 的类型为 Fp ;
use_f(y, Fq(5.0));:F 的类型为 Fq;
use_f(y, [](double u){return u * u;});:F 为 lambda 表达式使用的类型;
use_f(y, [](double u){return u + u / 2.0;});:F 为 lambda 表达式使用的类型;
因此可以发现同样的模板代码,生成了 5 个不同的实例,而由于这 6 个调用中的调用特征标均为 double(double) ,导致造成了浪费。模板 function 是在头文件 functional 中声明的,他从调用特征标的角度定义了一个对象,可用于包装调用特征标相同的函数指针、函数对象或 labmda 表达式:
下面改写上一个例子:
此时只创建了一个实例,节省了资源,还可以进一步简化:
另外可以对源函数进行修改:
由于函数本身的类型不是
function<double(double)> ,因此在 use_f 后面使用了 <double> 来指出所需的具体化。六、可变参数模板
如果想要对一个函数传递任意的参数列表,根据之前所学的知识是对其进行重载,但是这样很难满足“任意”的需求,因此需要可变参数模板。
1. 模板和函数参数包
上述代码中有两个参数列表,模板参数列表中只包含 T ,函数参数列表只包含 value 。C++ 11 提供了一个用省略号表示的元运算符(mera-operator),可以声明表示模板参数包的标识符(类型列表),还能够声明表示函数参数包的标识符(值列表):
其中,Args 是一个模板参数包,而 args 是一个函数参数包:
2. 展开参数包
上面的代码存在缺陷,会导致无限循环,正确的使用方法如下:
考虑到参数结束的情况,还可以对其进行改进,重新定义一个只接受一项参数的版本:
考虑到直接传值效率很低,因此可以使用引用:
原创存证 · 本地哈希 · 4c91b283e121...
算法:SHA-256
时间:2026-10-15
哈希:
4c91b283e121634cb072a77e4010797cd4e726f8f9e5c78153e8a1fef2d14d48该信息用于辅助证明当前内容版本,不等同于法律公证或版权登记。
- 作者:BeZer0
- 链接:https://blog.bezer0.cn/%E8%80%83%E7%A0%94/3fa0bc13-b7ac-804a-a8ee-d590de9f2e23
- 声明:本文为原创文章,采用 CC BY-NC-SA 4.0 协议,转载请注明作者和原文链接。

