Cao Lin's Blog.

了解一下Cpp11特性

Word count: 2.1kReading time: 9 min
2020/04/09 Share

关于CPP的更多资料可以访问:nemoTyrant/manong/blob/master/category/C_C++.md

这里只是记录【C++11】30分钟了解C++11新特性 这篇文章的一些重要点和代码。

C++11包括大量的新特性:包括lambda表达式,类型推导关键字auto、decltype,和模板的大量改进。

auto

auto实际上实在编译时对变量进行了类型推导,所以不会对程序的运行效率造成不良影响。

这是一个关于在哪些地方使用auto的讨论:

https://stackoverflow.com/questions/6434971/how-much-is-too-much-with-c11-auto-keyword

1
2
3
4
5
6
7
8
9
10
11
auto foo = std::make_shared<Foo>();   // obvious
auto foo = bla(); // unclear. don't know which type `foo` has

const size_t max_size = 100;
for ( auto x = max_size; x > 0; --x ) // unclear. could lead to the errors
// since max_size is unsigned

std::vector<some_class> v;
for ( auto it = v.begin(); it != v.end(); ++it )
// ok, since I know that `it` has an iterator type
// (don't really care which one in this context)

除了进行类型推导外,auto也可以用于模板中,以简化麻烦的模板参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 不使用cuto, 必须添加Product参数
template <typename Product, typename Creator>
void processProduct(const Creator& creator) {
Product* val = creator.makeObject();
// do somthing with val
}

// 使用auto, 省略Product参数
template <typename Creator>
void processProduct(const Creator& creator) {
auto val = creator.makeObject();
// do somthing with val
}

decltype

decltype与auto相对应, 可以从一个变量或表达式中得到类型:

1
2
int x = 3;
decltype(x) y = x;

当上文中的模板需要返回值的时候,可以结合auto和deltype简洁实现:

1
2
3
4
5
6
// 使用auto,  省略Product参数
template <typename Creator>
void processProduct(const Creator& creator) -> decltype(creator.makeObject()) {
auto val = creator.makeObject();
// do somthing with val
}

nullptr

nullptr用于解决原来C++中NULL的二义性而引入的一种新的类型,因为NULL实际上代表的是0。

1
2
3
4
5
6
7
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>
#include <assert.h>
using namespace std;

void F(int a){
cout<<"a: " << a<<endl;
}

void F(int *p){
assert(p != NULL);

cout<< "p: "<< p <<endl;
}

int main(){

int *p = nullptr;
int *q = NULL;
bool equal = ( p == q ); // equal的值为true,说明p和q都是空指针
cout << "equal: "<< equal << endl;
// int a = nullptr; // 编译失败,nullptr不能转型为int
F(0); // 在C++98中编译失败,有二义性;在C++11中调用F(int)
F(nullptr);

return 0;
}


//OUTPUT:
/*
equal: 1
a: 0
Assertion failed: p != NULL
*/

序列for循环

在C++中for循环可以使用简化的for循环,可以用于遍历数组,容器,string以及由begin和end函数定义的序列(即有Iterator),示例代码如下:

1
2
3
4
map<string, int> m{{"a", 1}, {"b", 2}, {"c", 3}};
for (auto p : m){
cout<<p.first<<" : "<<p.second<<endl;
}

Lambda表达式

lambda表达式与python的lambda相似,它可以用于创建并定义匿名的函数对象,以简化编程工作。

Lambda的简单语法如下:

以下内容基本和这篇文章相同https://www.jianshu.com/p/d686ad9de817

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/* 定义简单的lambda表达式 */
auto basicLambda = [] { cout << "Hello, world!" << endl; };
// 调用
basicLambda(); // 输出:Hello, world!


/* 携带参数 参数放在( )内*/
// 指明返回类型
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


/* 中括号[] 实现闭包,用于捕获变量*/
int main()
{
int x = 10;

auto add_x = [x](int a) { return a + x; }; // 复制捕捉x
auto multiply_x = [&x](int a) { return a * x; }; // 引用捕捉x

cout << add_x(10) << " " << multiply_x(10) << endl;
// 输出:20 100
return 0;
}

/* lambda 表达式无法改变复制捕获的变量,需要使用 mutable 改动传值方式捕获的值*/
//对于引用捕获方式,无论是否标记mutable,都可以在lambda表达式中修改捕获的值。

int main()
{
int x = 10;

auto add_x = [x](int a) mutable { x *= 2; return a + x; }; // 复制捕捉x

cout << add_x(10) << endl; // 输出 30
return 0;
}

/* lambda 表达式禁用了赋值操作符,因此无法进行赋值操作;但是可以进行复制构造,可以利用一个 -*/
// lambda 表达式去初始化另一个lambda 表达式,产生副本。
auto a = [] { cout << "A" << endl; };
auto b = [] { cout << "B" << endl; };

a = b; // 非法,lambda无法赋值
auto c = a; // 合法,生成一个副本

捕获的方式具体有以下几类:

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

采用默认值捕获所有变量仍然有风险:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 无法捕获divisor变量,数据成员divisor对lambda表达式并不可见
class Filter
{
public:
Filter(int divisorVal):
divisor{divisorVal}
{}

std::function<bool(int)> getFilter()
{
return [=](int value) {return value % divisor == 0; };
}

private:
int divisor;
};

// 【验证】类的方法,下面无法编译,因为divisor并不在lambda捕捉的范围
std::function<bool(int)> getFilter()
{
return [divisor](int value) {return value % divisor == 0; };
}
// 使用this指针变量,捕获this指针的副本,此时采用默认值捕捉所有变量仍然是不安全的,主要是由于指针变量的复制,实际上还是按引用传值。
std::function<bool(int)> getFilter()
{
return [this](int value) {return value % this->divisor == 0; };
}

相关应用, 大部分STL算法,可以非常灵活地搭配lambda表达式来实现想要的效果:

  • 用于函数的参数,通过这种方式可以实现回调函数,比如你要统计一个数组中满足特定条件的元素数量,通过lambda表达式给出条件,传递给count_if函数:
1
2
3
4
int value = 3;
vector<int> v {1, 3, 5, 2, 6, 10};

int count = std::count_if(v.beigin(), v.end(), [value](int x) { return x > value; });
  • 再比如你想生成斐波那契数列,然后保存在数组中,此时你可以使用generate函数,并辅助lambda表达式:
1
2
3
4
5
vector<int> v(10);
int a = 0;
int b = 1;
std::generate(v.begin(), v.end(), [&a, &b] { int value = b; b = b + a; a = value; return value; });
// 此时v {1, 1, 2, 3, 5, 8, 13, 21, 34, 55}
  • 对象排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Person
{
public:
Person(const string& first, const string& last):
firstName{first}, lastName{last}
{}

Person() = default;

string first() const { return firstName; }
string last() const { return lastName; }
private:
string firstName;
string lastName;
};

int main()
{
vector<Person> vp;
// ... 添加Person信息

// 按照姓名排序
std::sort(vp.begin(), vp.end(), [](const Person& p1, const Person& p2)
{ return p1.last() < p2.last() || (p1.last() == p2.last() && p1.first() < p2.first()); });
// ...
return 0;
}

lambda的完整语法

1
2
3
4
5
6
7
// 完整语法
[ 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:函数执行体。

https://en.cppreference.com/w/cpp/language/lambda

变长参数的模板

1
2
3
4
5
6
auto p = make_pair(1, "C++ 11");

auto t1 = make_tuple(1, 2.0, "C++ 11");
auto t2 = make_tuple(1, 2.0, "C++ 11", {1, 0, 2});

Print(1, 1.0, "C++11");

更加优雅的初始化方法

1
2
3
4
5
6
7
8
9
// c++11 以前
int arr[3] = {1, 2, 3}
vector<int> v(arr, arr + 3);

// c++11 以后
int arr[3]{1, 2, 3};
vector<int> iv{1, 2, 3};
map<int, string>{{1, "a"}, {2, "b"}};
string str{"Hello World"};
CATALOG
  1. 1. auto
  2. 2. decltype
  3. 3. nullptr
  4. 4. 序列for循环
  5. 5. Lambda表达式
    1. 5.1. Lambda的简单语法如下:
    2. 5.2. 捕获的方式具体有以下几类:
    3. 5.3. 相关应用, 大部分STL算法,可以非常灵活地搭配lambda表达式来实现想要的效果:
    4. 5.4. lambda的完整语法
  6. 6. 变长参数的模板
  7. 7. 更加优雅的初始化方法