C++中的lambda表达式简单介绍
作者:落羽的落羽
一、 什么是lambda表达式
C++中有“可调用对象”的概念,主要包括函数指针、仿函数、lambda表达式。lambda表达式本质是一个匿名函数对象,跟普通函数不同的是,他可以定义在函数内部。
lambda表达式在语法使用层面上“没有类型”,简单来说:lambda表达式的类型是编译器即时生成的、唯一的、匿名的,程序员无法在代码中直接写出它的类型名称。lambda表达式使用时本质是被转换成一个仿函数再使用的,每个lambda表达式都会被编译器转换成一个独一无二的、匿名的类(仿函数)。即使两个lambda看起来完全一样,编译器也会为它们生成两个不同的类。所以,我们一般都是用auto或者模板参数定义的对象去接收lambda对象。
二、 表达式语法
lambda表达式的语法格式是:[capture-list](parameters)->return-type {function body}
(parameters)
:参数列表。与普通的函数参数列表功能类似,如果不需要传参,则可以连同()
一起省略。->return-type
:return type是lambda函数的返回值类型。如果没有返回值,那么->return type
这部分就可以省略,或者返回值类型明确的情况下,能由编译器自动推导返回类型出来,也能省略这部分。{function body}
:函数体。函数体的实现与普通函数完全一样,函数体内除了能使用参数列表外,还可以使用捕捉列表内的变量。函数体为空也必须写{}
。[capture-list]
:捕捉列表。该列表写在lambda表达式的开始位置,编译器根据[]
来判断接下来的代码是否为lambda函数。捕捉列表能够捕捉上下文的变量,以供lambda函数内部使用,捕捉列表可以传值或传引用,捕捉列表就算为空也必须写[]
。
举几个栗子:
auto add = [](int x, int y){ return x + y; };
auto func = [] { cout << "hello world" << endl; };
auto swap = [](int& a, int& b) { int tmp = a; a = b; b = tmp; };
非常好理解吧!
关于捕捉列表,还有一些使用的细节:
lambda表达式中默认只能使用参数列表中的变量或lambda内定义的,如果想使用外层作用域中的变量就需要进行捕捉。
- 第一种方式是显式捕捉,在捕捉列表中可以显式传值捕捉和传引用捕捉,捕捉的多个变量用逗号分隔,比如
[x, y, &z]
表示x和y进行传值捕捉,lambda内部改变x和y不会改变原变量,而z是传引用捕捉,对z的改变会改变原变量。 - 第二种捕捉方式是隐式捕捉,我们在[ ]中写一个 = 表示隐式值捕捉,写一个 & 表示隐式引用捕捉。这样我们的lambda的表达式中使用了哪些外部变量,编译器就会自动捕捉那些变量。
- 第三种捕捉列表是混合捕获,可以组合使用隐式捕获和显式捕获,实现更精细的控制。[=, &x] 表示x进行引用捕捉,其余变量都为值捕捉;[&, x, y] 表示x和y进行值捕捉,其余变量进行引用捕捉。使用混合捕捉时,第一个元素必须是=或&,第一个是=时,后面的捕捉变量必须是引用捕捉;第一个是&时,后面的捕捉变量必须是值捕捉。
lambda表达式中如果在局部域时,可以捕捉到它之前定义的变量,不能捕捉静态局部变量和全局变量,因为这两种变量本来就不需要捕捉也能在内部直接使用,lambda表达式内部可以直接使用。lambda表达式如果定义在全局位置,捕捉列表必须为空。
默认情况下,lambda捕捉列表是const属性的,也就是说传值捕捉来的对象不能被修改,在参数列表后加上修饰符mutable
就可以取消其常性,但是修改还是改变的形参对象,不会影响实参。使用该修饰符后参数列表不能省略。
int x = 0; // 捕捉列表必须为空,因为全局变量不⽤捕捉就可以⽤,没有可被捕捉的变量 auto func1 = []() { x++; }; int main() { // 只能⽤当前lambda局部域和捕捉的对象和全局对象 int a = 0, b = 1, c = 2, d = 3; auto func1 = [a, &b] { // 值捕捉的变量不能修改,引⽤捕捉的变量可以修改 //a++; b++; int ret = a + b; return ret; }; cout << func1() << endl; // 隐式值捕捉 // ⽤了哪些变量就捕捉哪些变量 auto func2 = [=] { int ret = a + b + c; return ret; }; cout << func2() << endl; // 隐式引⽤捕捉 // ⽤了哪些变量就捕捉哪些变量 auto func3 = [&] { a++; c++; d++; }; func3(); cout << a << " " << b << " " << c << " " << d << endl; // 混合捕捉1 auto func4 = [&, a, b] { //a++; //b++; c++; d++; return a + b + c + d; }; func4(); cout << a << " " << b << " " << c << " " << d << endl; // 混合捕捉1 auto func5 = [=, &a, &b] { a++; b++; /*c++; d++;*/ return a + b + c + d; }; func5(); cout << a << " " << b << " " << c << " " << d << endl; // 局部的静态和全局变量不能捕捉,也不需要捕捉 static int m = 0; auto func6 = [] { int ret = x + m; return ret; }; // 传值捕捉本质是⼀种拷⻉,并且被const修饰了 // mutable相当于去掉const属性,可以修改了 // 但是修改了不会影响外⾯被捕捉的值,因为是⼀种拷⻉ auto func7 = [=]()mutable { a++; b++; c++; d++; return a + b + c + d; }; cout << func7() << endl; cout << a << " " << b << " " << c << " " << d << endl; return 0; }
三、lambda的应用
学习lambda表达式之前,我们使用的可调用对象只有函数指针和仿函数,它们的定义都相对麻烦,使用lambda表达式去定义可调用对象,就十分简单方便了。
vector<pair<int, int>> v = { {1,2}, {3,4}, {8,3}, {5,1} }; //遇到这样的场景,假如我们想要用多种排序规则进行排序,以前是需要写不同的仿函数传给sort /*struct Compare_first { bool operator()(const pair<int, int>& p1, const pair<int, int>& p2) { return p1.first < p2.first; } }; struct Compare_second { bool operator()(const pair<int, int>& p1, const pair<int, int>& p2) { return p1.second < p2.second; } }; sort(v.begin(), v.end(), Compare_first()); sort(v.begin(), v.end(), Compare_second());*/ //有了lambda表达式后,就方便许多了 sort(v.begin(), v.end(), [](const pair<int, int>& p1, const pair<int, int>& p2) {return p1.first < p2.first; }); for (auto pa : v) { cout << pa.first << ":" << pa.second << " "; } cout << endl; sort(v.begin(), v.end(), [](const pair<int, int>& p1, const pair<int, int>& p2) {return p1.second < p2.second; }); for (auto pa : v) { cout << pa.first << ":" << pa.second << " "; } cout << endl;
到此这篇关于C++简单介绍lambda表达式的文章就介绍到这了,更多相关C++ lambda表达式内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!