C++11特性

  • 提高运行效率的语言特性:右值引用、泛化常量表达式
  • 原有语法的增强:初始化列表、统⼀的初始化语法、类型推导、范围for循环、Lambda表达式、final和override、构造函数委托
  • 语言能力的提升:空指针nullptr、default和delete、⻓整数、静态assert
  • c++标准库的更新:智能指针、正则表达式、哈希表等

提高运行效率

右值引用

C++03及之前的标准中,右值是不允许被改变的,实践中也通常使⽤const T&的⽅式传递右值。然⽽这是效率低下的做法,例如:

1
2
3
4
5
Person get(){
Person p;
return p;
}
Person p = get();

上述获取右值并初始化p的过程包含了Person的3个构造过程和2个析构过程,这是C++⼴受诟病的⼀点,但C++11 的右值引⽤特性允许我们对右值进⾏修改。借此可以实现move语义,即从右值中直接拿数据过来初始化或修改左值,⽽不需要重新构造左值后再析构右值。⼀个move构造函数是这样声明的:

1
2
3
4
5
class Person{
public:
Person(Person&& rhs){...}
...
};

泛化常量表达式

原有语法的增强

初始化列表

统一的初始化语法

类型推导

范围for循环

lambda表达式

lambda表达式实际上就是提供了⼀个类似匿名函数的特性,⽽匿名函数则是在需要⼀个函数,但是⼜不想费⼒去命名⼀个函数的情况下去使⽤的。利⽤lambda表达式可以编写内嵌的匿名函数,⽤以替换独⽴函数或者函数对象,并且使代码更可读。

从本质上来讲,lambda表达式只是⼀种语法糖,因为所有其能完成的⼯作都可以⽤其它稍微复杂的代码来实现,但是它简便的语法却给C++带来了深远的影响。从⼴义上说,lamdba表达式产⽣的是函数对象。在类中,可以重载函数调⽤运算符(),此时类的对象可以将具有类似函数的⾏为,我们称这些对象为函数对象(Function Object)或者仿函数(Functor)。相⽐ lambda表达式,函数对象有⾃⼰独特的优势。

lambda表达式⼀般都是从⽅括号[]开始,结束于花括号{},花括号⾥⾯就像定义函数那样,包含了lamdba表达式体,⼀个最简单的例⼦如下:

1
2
3
// 定义简单的lambda表达式
auto basicLambda = [] { cout << "Hello, world!" << endl; };
basicLambda(); // 输出:Hello, world!

(hello world百用不厌嗷兄弟们

上⾯是最简单的lambda表达式,没有参数。如果需要参数,那么就要像函数那样,放在圆括号⾥⾯,如果有返回值,返回类型要放在->后⾯,即拖尾返回类型,当然你也可以忽略返回类型,lambda会帮你⾃动推断出返回类型:

1
2
3
4
5
6
// 指明返回类型,托尾返回类型
auto add = [](int a, int b) -> int { return a + b; };
// ⾃动推断返回类型
auto multiply = [](int a, int b) { return a * b; };
int sum = add(2, 5); // 输出:7
int product = multiply(2, 5); // 输出:10

最前边的[]是lambda表达式的⼀个很重要的功能,就是闭包。先说明⼀下lambda表达式的⼤致原理:每当你定义⼀个lambda表达式后,编译器会⾃动⽣成⼀个匿名类(这个类重载了()运算符),我们称为闭包类型(closure type)。那么在运⾏时,这个lambda表达式就会返回⼀个匿名的闭包实例,其是⼀个右值。所以,上⾯lambda表达式的结果就是⼀个个闭包实例。闭包的⼀个强⼤之处是可以通过传值或者引⽤的⽅式捕捉其封装作⽤域内的变量,前⾯的⽅括号就是⽤来定义捕捉模式以及变量,⼜将其称为 lambda 捕捉块。例⼦如下:

1
2
3
4
5
6
7
8
9
10
int main() {
int x = 10;
auto add_x = [x](int a) { return a + x; };
// 复制捕捉x,lambda表达式⽆法修改此变量
auto multiply_x = [&x](int a) { return a * x; };
// 引⽤捕捉x,lambda表达式可以修改此变量
cout << add_x(10) << " " << multiply_x(10) << endl;
// 输出:20 100
return 0;
}

捕获的⽅式可以是引⽤也可以是复制,但是具体说来会有以下⼏种情况来捕获其所在作⽤域中的变量:

  • []:默认不捕获任何变量;
  • [=]:默认以值捕获所有变量;
  • [&]:默认以引⽤捕获所有变量;
  • [a]:仅以值捕获a,其它变量不捕获;
  • [&a]:仅以引⽤捕获a,其它变量不捕获;
  • [=, &a]:默认以值捕获所有变量,但a是例外,通过引⽤捕获;
  • [&, a]:默认以引⽤捕获所有变量,但是a是例外,通过值捕获;
  • [this]:通过引⽤捕获当前对象(其实是复制指针);
  • [*this]:通过传值⽅式捕获当前对象;

⽽**lambda表达式⼀个更重要的应⽤是其可以⽤于函数的参数,通过这种⽅式可以实现回调函数。**其实最常⽤的是在STL算法中,⽐如要统计⼀个数组中满⾜特定条件的元素数量,通过lambda表达式给出条件,传递给count_if函数:

1
2
3
4
int val = 3;
vector<int> v {1, 8, 5, 3, 6, 10};
int count = std::count_if(v.beigin(), v.end(), [val](int x) { return x > val; });
// v中⼤于3的元素数量

最后给出lambda表达式的完整语法:

1
2
3
4
5
[capture-list] (params) mutable(optional) constexpr(optional)(c++17) exception attribute -> ret { body }
// 可选的简化语法
[capture-list] (params) -> ret {body}
[capture-list] (params) {body}
[capture-list] {body}
  • capture-list:捕捉列表,前⾯已经讲过,不能省略;
  • params:参数列表,可以省略(但是后⾯必须紧跟函数体);
  • mutable:可选,将lambda表达式标记为mutable后,函数体就可以修改传值⽅式捕获的变量;
  • constexpr:可选,C++17,可以指定lambda表达式是⼀个常量函数;
  • exception:可选,指定lambda表达式可以抛出的异常;
  • attribute:可选,指定lambda表达式的特性;
  • ret:可选,返回值类型;
  • body:函数执⾏体。

final和override

构造函数委托

语言能力提升

空指针nullptr

nullptr的出现是为了替代原有的NULL。在某种意义上来说,传统C++会把NULL和0视为相同,取决于编译器如何定义NULL:有些编译器会将NULL定义为((void*)0),有些则直接定义为0。C++不允许将void*隐式转换到其他类型,但如果NULL被定义为((void*)0),那么当编译char *ch = NULL时,NULL只能被定义为0。但是依然会产生问题,将导致C++中重载特性发生混乱。考虑如下代码:

1
2
void func(int);
void func(char *);

对于这两个函数来说,如果NULL为0那么func(NULL)将会去调用func(int),导致代码违反直观。

为了解决该问题,C++11引入关键字nullptr专门用于区分空指针和0。nullptr的类型为nullptr_t,能够隐式地转换为任何指针或成员指针的类型,也能和他们进行相等或不等的比较。
(所以养成用nullptr取代NULL的好习惯噢

default和delete

长整数

静态assert

c++标准库更新

智能指针

正则表达式

哈希表

Contents
  1. 1. 提高运行效率
    1. 1.1. 右值引用
    2. 1.2. 泛化常量表达式
  2. 2. 原有语法的增强
    1. 2.1. 初始化列表
    2. 2.2. 统一的初始化语法
    3. 2.3. 类型推导
    4. 2.4. 范围for循环
    5. 2.5. lambda表达式
    6. 2.6. final和override
    7. 2.7. 构造函数委托
  3. 3. 语言能力提升
    1. 3.1. 空指针nullptr
    2. 3.2. default和delete
    3. 3.3. 长整数
    4. 3.4. 静态assert
  4. 4. c++标准库更新
    1. 4.1. 智能指针
    2. 4.2. 正则表达式
    3. 4.3. 哈希表
|