以下为个人学习笔记整理。参考书籍《C++ Primer Plus》
# 十八、C++11 新功能
# 新类型
- long long
- unsigned long long
- char16_t
- char32_t
- string
# 新的统一初始化规则
支持大括号初始化列表。
int x = {5}; | |
double y {2.1}; | |
short lst[6] = {1,2,3,4,5,6}; |
# 缩窄
初始化列表可以防止缩窄,即禁止将数值赋值给无法存储它的数值变量。
# std::initializer_list
如果类的构造函数参数中带有 std::initializer_list
,那么初始化列表语言就只能用于构造函数。
# 声明
auto
:通过 auto 实现自动类型推断的声明,被声明对象必须要显示的初始化。decltype
:将变量类型声明为表达式指定的类型。
# 返回值类型后置
支持了一种新的函数声明方式:
// 两者等效 | |
double f(double, int); | |
auto f(double, int) -> double; |
# 模板别名
有些模板类型命名很长,现在支持重命名,功能和 typedef 类型,但支持模板具体化操作。
// 两者等效 | |
typedef std::vector<std::string>::iterator itType; | |
using itType = typedef std::vector<std::string>::iterator; | |
// 支持模板具体化 | |
template<typename T> | |
using array_12 = std::array<T, 12> |
# nullptr
引入空指针概念
# 智能指针
支持三种智能指针
- unique_ptr
- shared_ptr
- weak_ptr
# 异常处理相关
支持函数显示声明可能产生的异常
void f() throw(ExceptionName); |
# 作用域内枚举
传统枚举的作用域为当前定义的作用域。同一个作用域出现重名枚举将会有问题,另外由于枚举的底层实现不是完全可移植,所以引入了新的枚举定义方式:
enum COLOR_TYPE {RED, GREEN}; // old | |
enum class COLOR_TYPE {RED, GREEN}; // new | |
enum struct COLOR_TYPE {RED, GREEN}; // new |
# 对类的修改
# 显示转换运算符
explicit
可以限制对象自动转换,只能手动调用才会被正确执行。
class A{ | |
A(int); // automatic | |
explicit A(double) // assign type | |
} | |
A a_1,a_2,a_3; | |
a_1 = 1; // ok | |
a_2 = 1.0; // fail | |
a_3 = A(1.0); // ok |
此外还支持 explicit
应用于类型转换函数
class A{ | |
operator int() const; | |
explicit operator double() const; | |
} | |
A a_1,a_2,a_3; | |
int m = a_1; // ok | |
double n_1 = a_2; // fail | |
double n_2 = double(a_3); // ok |
# 类内成员初始化
现在在类的定义中可以允许初始化类成员。
class A{ | |
int m = 10; | |
double n = 10.0; | |
short k = 1; | |
} |
# 对于模板和 STL 方面的修改
# 基于范围的 for 循环
double prices[5] = {1,2,3,4,5}; | |
for (double x: prices) | |
cout << x << endl; | |
double prices[5] = {1,2,3,4,5}; | |
for (auto x: prices) | |
cout << x << endl; | |
double prices[5] = {1,2,3,4,5}; | |
for (auto& x: prices) | |
x = 1.0; |
# 新增 STL 容器
forward_list:单向链表
unordered_map:底层哈希表
unordered_multimap:底层哈希表
unordered_set:底层哈希表
unordered_multiset:底层哈希表
新增模板 array
# 新增 STL 方法
- cbegin ():和 begin 类似,返回值视作 const。
- cend ():和 end 类似,返回值视作 const。
# valarray 升级
支持基于范围的迭代 valarray 对象。
# 摒弃 export
# 尖括号问题
C++ 11 之前,为避免 >> 运算符 和 > 之前混淆,所以中间需要加上空格隔开,但现在不需要了
std::vector<std::list<int> > vl; // C++98 | |
std::vector<std::list<int>> vl; // C++11 |
# 右值引用
左值引用操作相当于给对象取别名,前提是对象必须是能够获取地址的,即:对象存储在内存而非寄存器中。
int b=a;// 此时 a 在内存中 | |
int b=a+1;// 此时 a+1 在寄存器中 | |
int& b_left = b; // 声明一个左值引用 b_left, b 和 b_left 地址一样 | |
int b_copy = b; // 声明一个同数值的 b_copy, b_copy 和 b 地址不一样 | |
int& b_left = 10; // fail |
由于特定情况下需要获取寄存器的值或者匿名对象的值,这时候右值引用诞生了
int && b_left = 10; //ok | |
int && b_left = x + y; // ok | |
double && r = std::sqrt(2.0); // ok |
# 移动语义和右值引用
# 什么是移动语义以及为啥要它
移动语义可以在函数传递的时候不进行值拷贝而是传递地址,这里的传递地址并非函数里面传递引用的概念。
因为传递引用相当于创建一个临时变量用于存放对象地址,虽然对象本身不需要完全拷贝一份,但是地址本身是需要拷贝一次的。
而移动语义的作用则是能够让地址拷贝本身也变得不需要,尽可能的减少拷贝和创建临时变量的消耗。
class A{ | |
private: | |
char *pc; | |
public: | |
A(A&& f); // 移动语义 | |
} | |
A::A(&&f){ | |
pc = f.pc; | |
f.pc = nullptr; // 避免对象 f 销毁后调用析构函数清楚 pc 值,所以修改 pc 的指向。 | |
} | |
A('a'); // 调用移动语义进行拷贝 |
# 赋值
除去构造函数,赋值语句同样适用。
A& A::operator=(A &&f){ | |
if(this == &f) | |
return *this; | |
delete []pc; | |
pc = f.pc; | |
f.pc = nullptr; | |
return *this; | |
} |
# 强制移动
如果某些情况下,希望能够在移动语义中使用左值作为参数(有地址的非匿名对象),可以通过 move
函数。
move
函数的返回值是一个右值,因此 two = std::move(one)
先当与把一个右值传递给 two
,这会默认调用「移动赋值语句」。
如果没有定义移动赋值语句,那么会执行「赋值运算符」。但是如果两者都没定义,那么操作将失败。
#include <utility> | |
A one{'o'}; | |
A two; | |
two = std::move(one); |
# 类的新功能
# 特殊成员函数
新增了两个特殊成员函数:
- 默认构造函数
- 复制构造函数
- 复制赋值运算符
- 析构函数
- 移动构造函数「new」
- 移动赋值运算符「new」
# 默认的方法和禁用的方法
如果定义了移动构造函数,那么编译器不会再创建默认的构造函数,但如果希望默认构造函数被创建,可以使用 default
关键字:
class A{ | |
public: | |
A(A&&); | |
A()=default; // 显示声明默认构造函数 | |
} |
此外,可以通过 delete
声明某些方法不可被调用
class A{ | |
public: | |
A(const A&) = delete; // 禁止复制构造函数 | |
A& operator=(const A&) = delete; // 禁止复制赋值运算符 | |
} |
# 委托构造函数
委托构造函数可以再构造一个对象的时候,把其中一部分对象的构造操作「委托」给其他函数执行
class A{ | |
int k; | |
string str; | |
public: | |
A(int, string); | |
} | |
A::A(int kk, string s):k(kk),str(s){} |
# 继承构造函数
C++11 提供了可以让派生类能够继承基类构造函数的机制,该方法不仅可以用于构造函数,其他非特殊成员函数都🉑。
class A{ | |
public: | |
A(); | |
} | |
class B:public A{ | |
public: | |
using A::A; | |
} |
# 管理虚方法:override 和 final
C++11 提供了一个显示覆盖虚函数定义的标识符 override
, 避免由于特征匹配把一些不期望覆盖的虚函数被隐藏,导致调用错误。
class A{ | |
public: | |
virtual void f(char) const; | |
} | |
class B:public A{ | |
public: | |
virtual void f(char*) const; // 将把父类的 f (char) const 隐藏,从而导致调用 f (char) 版本报错。 | |
virtual void f(char*) const override; // 加上 override 显示声明需要覆盖父类虚函数,由于两者不匹配,因此可以在编译时出错,及时发现问题。 | |
} |
final
标识符则是限制某些特定的虚函数不能够被子类所覆盖
class A{ | |
public: | |
virtual void f(char) const final; | |
} | |
class B:public A{ | |
public: | |
virtual void f(char) const override; //fail,不允许覆盖父类虚函数 | |
} |
# Lambda 函数
Lambda 又可以理解为匿名函数。
[](int x){return x % 3 == 0;} // 功能上等效于 bool f3 (int x){return x % 3 == 0} | |
// 如果表达式内不仅仅只有 return 一条语句,则还需要带上返回值 | |
[](int x)->double{int y = x; return y - x;} |
# 包装器
可以封装一个函数,并且指定其中的某几个参数,返回值则是一个包装后的函数。或是通过统一的方式调用相同类型的函数:
#include <functional> | |
double dub(double x) {return 2.0 * x;} | |
double square(double x){return x*x;} | |
function<double(double)> f_1 = dub; | |
function<double(double)> f_2 = square; | |
function<double(double)> f_3 = [](double x){return x*2.5}; | |
use_f(1.0, f_1); | |
use_f(2.0, f_2); | |
use_f(3.0, f_3); |
# 可变参数模板
- 模板参数包(parameter pack)
- 函数参数包
- 展开(unpack)参数包
- 递归
# 模板和函数参数包
C++11 提供了一个元运算符(meta-operator)「...」来声明模板参数包,表示一个类型列表:
template<typename... Args> // Args:模板参数包 | |
void show_list(Args... args){ //args:函数参数包 | |
} |
# 展开参数包
如何展开参数包呢?在 args 右边加上「...」。
template<typename... Args> // Args:模板参数包 | |
void show_list(Args... args){ //args:函数参数包 | |
show_list(args...); //same as show_list (1,2,3) 然而这将导致无限的递归调用 | |
} | |
show_list(1,2,3) |
# 在可变参数模板函数中使用递归
每次确定模板参数包内的第一项参数,递归拆解,直到确定所有的参数,从而解决无限递归的情况:
void show_list(){} | |
template<typename T, typename... Args> | |
void show_list(T value, Args... args){ | |
cout << value; | |
show_list(args...); | |
} | |
show_list(1,2,3); |
# C++11 新增的其他功能
# 并行编程
添加了关键字 thread_local。
支持了原子操作库和线程支持库。
- atomic:原子操作库
- thread:线程支持库
- mutex:线程支持库
- condition_variable:线程支持库
- future:线程支持库
# 新增库
- random:提供大量随机工具
- chrono:提供处理时间间隔
- tuple:支持模板 tuple(元组)
- ratio:编译阶段有理数算术库
- regex:正则表达式库,支持模式匹配
# 低级编程
- 放松了 POD(Plain Old Data)要求,让一些新的数据类型满足 POD。
- 允许共用体的成员有构造函数和析构函数。
- 解决了内存对齐问题。某些系统需要 double 值的内存地址为「偶数」或是「8 的倍数」,详见 alignof () 和 alignas 关键字。
- consexpr 机制让编译器能够在编译阶段计算出结果为常量的表达式。