以下为个人学习笔记整理。参考书籍《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 机制让编译器能够在编译阶段计算出结果为常量的表达式。