undefined

自动类型推导

1. auto

自动类型推导,C++14开始,还有函数的返回类型。auto 并没有改变C++是静态语言这一事实,使用auto 的变量(或函数返回值)的类型仍然是编译时就确定了,只不过编译器能自动帮你填充而已。

auto 实际使用的规则类似于函数模板参数的推导规则。当你写了一个含有auto的表达式时,相当于把 auto 替换为模板参数的结果。如下:

  1. auto a = expr; 意味着用 expr 去匹配一个假想的 template<typename T> f(T) 函数模板,结果为值类型
  2. const auto& a = expr; 意味着用 expr 去匹配一个假想的 template<typename T> f(const T&) 函数模板,结果为常左值引用类型
  3. auto&& a = expr; 意味着用 expr 去匹配一个假想的 template<typename T> f(T&&) 函数模板,根据转发引用和引用塌缩规则,结果是一个跟 expr 值类别相同的引用类型

2. decltype

  1. decltype(变量名) 可以获得变量的精确类型
  2. decltype(表达式) : (表达式不是变量名,但包括 decltype((变量名)) 的情况)可以获得表达式的引用类型;除非表达式的结果是个纯右值,此时结果仍然是值类型

如下说明: 如果我们有 int a; 那么:

1
2
3
1. decltype(a) 会获得 int ,因为 a 是 int
2. decltype((a)) 会获得 int& ,因为 a 是 lvalue
3. decltype(a+a) 会获得 int ,因为 a+a 是纯右值

3. decltype(auto)

使用 auto 不能通用的根据表达式类型来决定返回值的类型。因为 auto 是值类型,auto& 是左值引用类型,auto&& 是转发引用(左值或右值)。而 decltype(expr) 既可以是值类型,也可以是引用类型。如 decltype(expr) a = expr

这种写法对于表达式很长的情况不让我们满足,因为 C++14 引入 decltype(auto) a = expr 。这种代码主要用在通用的转发函数模板中,因为不知道调用的函数是不是会返回一个引用。

4. 其他

1
2
3
4
5
6
7
8
9
10
1. 函数返回值类型推断
2. 类模板的模板参数推导
3. 结构化绑定
4. 列表初始化
5. 统一初始化
如果一个类既有使用初始化列表的构造函数,又有不使用初始化列表的构造函数,那编译器会千方百计地试图调用使用初始化列表的构造函数,导致各种意外。所以,如果给一个推荐的话,那就是:
- 如果一个类没有使用初始化列表的构造函数时,初始化该类对象可全部使用统一初始化语法
- 如果一个类有使用初始化列表的构造函数时,则只应用在初始化列表构造的情况
6. 类数据成员的默认初始化
C++11允许在声明数据成员时直接给予一个初始化表达式

5. 字面量

1
2
3
4
5
6
7
8
9
10
11
12
13
1. 自定义字面量
2. 二进制字面量
可以使用 bitset 来间接指定二进制
3. 数字分隔符。从 C++14 开始,允许在数字型字面量中任意添加 ' 来使其可读。常见情况如下:
- 十进制数字使用三位的分隔,对应英文习惯的 thousand、million 等单位
- 十进制数字使用四位的分隔,对应中文习惯的万、亿等单位。
- 十六进制数字使用两位或四位的分隔,对应字节或双字节。
- 二进制数字使用三位的分隔,对应文件系统的权限分组。

unsigned mask = 0b111'000'000;
long r_earth_equatorial = 6'378'137;
double pi = 3.14159'26535'89793;
const unsigned magic = 0x44'42'47'4E;

6. 静态断言

C++98 的 assert 允许在运行时检查一个函数的前置条件是否成立。没有一种方法允许开发人员在编译的时候检查假设是否成立。C++11直接从语言层面提供了静态断言机制,不仅能输出更好的信息,而且适用性也好,可以直接放在类的定义中。

静态断言语法:static_assert(编译期条件表达式, 可选输出信息);

7. default 和 delete 成员函数

特殊的成员函数:默认构造函数、析构函数、拷贝构造函数、拷贝赋值函数、移动构造函数、移动赋值函数

特殊成员函数的状态:隐式声明还是用户声明、默认提供还是用户提供、正常状态还是删除状态

8. override 和 final 说明符

override:显式声明了成员函数是一个虚函数且覆盖了基类中的该函数。如果有 override 声明的函数不是虚函数,或基类中不存在这个虚函数,编译器会报告错误。这个说明符的主要作用有两个:

  1. 给开发人员更明确的提示,这个函数覆写了基类的成员函数
  2. 让编译器进行额外的检查,防止程序猿由于拼写错误或者代码改动没有让基类和派生类中的成员函数名称完全一致

final:声明成员函数是一个虚函数,且该虚函数不可在派生类中被覆盖。如果有一点没有满足,编译器就会报错。final 还有一个作用是标志某个类或结构不可被派生。同样,这时应将其放在被定义的类或结构名后面。

9. constexpr

一个 constexpr 变量是一个编译时完全确定的常数。一个 constexpr 函数至少对于某一组实参可以在编译期间产生一个编译期常数。

注意一个 constexpr 函数不保证在所有情况下都会产生一个编译期常数(因而也是可以作为普通函数来使用的)。编译器也没法通用地检查这点。编译器唯一强制的是:

  • constexpr 变量必须立即初始化
  • 初始化只能使用字面量或常量表达式,后者不允许调用任何非 constexpr 函数

可以用于编译器计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
constexpr int factorial(int n)
{
if (n == 0) {
return 1;
} else {
return n * factorial(n - 1);
}
}

int main()
{
constexpr int n = factorial(10);
printf("%d\n", n);
}

使用 objdump -d xxx ,可以看到在编译时已经帮我们计算好了函数的值

const 和 constexpr 的区别

  1. const char* 是指向常字符的指针,指针指向的内容不可更改。char * const 代表指向字符的常指针。本质上 const 用于表示一个运行时常量
  2. conexpr 编译器常数