C++11 ——— lambda表达式
1 lambda表达式语法
lambda表达式是一个匿名函数,恰当使用lambda表达式可以让代码变得简洁,并且可以提高代码的可读性。
用单独函数,代码分散,阅读时要跳来跳去
#include <vector>
#include <algorithm>
#include <iostream>
// 为了筛选偶数,要单独定义一个函数
bool isEven(int x) {
return x % 2 == 0;
}
int main() {
std::vector<int> v = {1,2,3,4,5,6};
// 找第一个偶数
auto it = std::find_if(v.begin(), v.end(), isEven);
if (it != v.end()) std::cout << *it << std::endl;
return 0;
}
用 lambda 就地写逻辑,代码 “所见即所得”
#include <vector>
#include <algorithm>
#include <iostream>
int main() {
std::vector<int> v = {1,2,3,4,5,6};
// 筛选偶数的逻辑直接写在调用处,一眼看懂意图
auto it = std::find_if(v.begin(), v.end(), [](int x) {
return x % 2 == 0;
});
if (it != v.end()) std::cout << *it << std::endl;
return 0;
}
• lambda 表达式本质是⼀个匿名函数对象,跟普通函数不同的是他可以定义在函数内部。lambda 表达式语法使⽤层⽽⾔没有类型,所以我们⼀般是⽤auto或者模板参数定义的对象去接收 lambda 对象。
• lambda表达式的格式: [capture-list] (parameters)-> return type { function boby }
• [capture-list] :捕捉列表,该列表总是出现在 lambda 函数的开始位置,编译器根据[]来判断接下来的代码是否为 lambda 函数,捕捉列表能够捕捉上下⽂中的变量供 lambda 函数使⽤,捕捉列表可以传值和传引⽤捕捉,具体细节1.2中我们再细讲。捕捉列表为空也不能省略。
• (parameters) :参数列表,与普通函数的参数列表功能类似,如果不需要参数传递,则可以连同()⼀起省略
• ->return type :返回值类型,⽤追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。⼀般返回值类型明确情况下,也可省略,由编译器对返回类型进⾏推导。
• {function boby} :函数体,函数体内的实现跟普通函数完全类似,在该函数体内,除了可以使⽤其参数外,还可以使⽤所有捕获到的变量,函数体为空也不能省略。
int main()
{
// ⼀个简单的lambda表达式
auto add1 = [](int x, int y)->int {return x + y; };
cout << add1(1, 2) << endl;
// 1、捕捉为空也不能省略
// 2、参数为空可以省略
// 3、返回值可以省略,可以通过返回对象⾃动推导
// 4、函数题不能省略
auto func1 = []
{
cout << "hello bit" << endl;
return 0;
};
func1();
//交换两个数
int a = 0, b = 1;
auto swap1 = [](int& x, int& y)
{
int tmp = x;
x = y;
y = tmp;
};
swap1(a, b);
cout << a << ":" << b << endl;
return 0;
}
1.2捕捉列表
一、捕获核心作用
lambda 默认不能直接用外层变量,必须通过捕获列表 [] 才能使用。
二、3 种捕获方式
1.显式捕获
[x, y]:x、y 值捕获
[&x, &y]:x、y 引用捕获
[x, &y]:混合显式
2.隐式捕获
[=]:所有用到的外层变量 自动值捕获
[&]:所有用到的外层变量 自动引用捕获
3.混合隐式 + 显式(最常用)
[=, &x]:其余值捕获,x 引用捕获
[&, x, y]:其余引用捕获,x、y 值捕获
规则:第一个必须是 = / &,且后面必须和它相反。
三、捕获范围限制
只能捕获 定义位置之前 的局部变量
全局变量、静态局部变量 不用捕获,直接用
全局 lambda:捕获列表必须为空 []
四、mutable 关键字
默认:值捕获的变量是 const,不能修改
加 mutable:可以修改拷贝出来的值,不影响原变量
规则:加了 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;
}
1.3lambda的原理
• lambda 的原理和范围for很像,编译后从汇编指令层的⻆度看,压根就没有 lambda 和范围for这样的东西。范围for底层是迭代器,⽽lambda底层是仿函数对象,也就说我们写了⼀个lambda 以后,编译器会⽣成⼀个对应的仿函数的类。
• 仿函数的类名是编译按⼀定规则⽣成的,保证不同的 lambda ⽣成的类名不同,lambda参数/返回类型/函数体就是仿函数operator()的参数/返回类型/函数体, lambda 的捕捉列表本质是⽣成的仿函数类的成员变量,也就是说捕捉列表的变量都是 lambda 类构造函数的实参,当然隐式捕捉,编译器要看使⽤哪些就传那些对象。
• 上⾯的原理,我们可以透过汇编层了解⼀下,下⾯第⼆段汇编层代码印证了上⾯的原理。
class Rate
{
public:
Rate(double rate)
: _rate(rate)
{}
double operator()(double money, int year)
{
return money * _rate * year;
}
private:
double _rate;
};
int main()
{
double rate = 0.49;
// 函数对象
Rate r1(rate);
r1(10000, 2);
// lambda
auto r2 = [rate](double money, int year) {
return money * rate * year;
};
r2(10000, 2);
调试代码并转到反汇编,可以看到:
在创建函数对象rate r1时,会调用rate类的构造函数。
在使用函数对象rate r2时,会调用rate类的()运算符重载函数。

观察lambda表达式时,也能看到类似的代码:
借助auto将lambda表达式赋值给r2对象时,会调用<lambda_uuid>类的构造函数。
在使用r2对象时,会调用<lambda_uuid>类的()运算符重载函数。

总结
Lambda 表达式 = 编译器自动生成的匿名类 + 自动生成的 operator () 重载
1.生成一个匿名类(没有名字,编译器自己知道)
2.把捕获列表变成类的成员变量
3.重载 operator() 运算符
4.把 lambda 函数体放进 operator() 里
注意
Lambda 底层是编译器自动生成的匿名类(仿函数),每个表达式对应唯一独立类,与手写仿函数原理完全一致。
VS 编译器实现:
类名格式:<lambda_uuid>
uuid:通用唯一识别码,算法生成不重复字符串
作用:确保每个 lambda 底层类名绝对唯一
通用规则:
不同编译器类名格式不统一
核心要求:每个 lambda 必须对应独一无二的类,类名形式由编译器自行决定
开发者无需关心具体类名,仅需使用 lambda 本身
更多推荐

所有评论(0)