- 背景
Lambda表达式在C++11及之后的版本中是一个非常强大的特性,它允许你在代码中定义匿名函数对象。一个lambda表达式基本上就是一个函数,但它没有名字。这使得lambda表达式非常适合用作短期使用的小型操作,尤其是作为算法库函数的参数。
- 语法
lambda表达式形式:
[capture](parameters)[mutable] [-> return_type] {
function_body
}
- capture(捕获列表):定义了lambda表达式可以从封闭作用域(通常是定义它的局部作用域)中捕获哪些变量,以及如何捕获(通过值或引用)。如果不需要捕获任何变量,捕获列表可以为空。
[] 什么也不捕获,无法lambda函数体使用任何
[=] 按值的方式捕获所有变量
[&] 按引用的方式捕获所有变量
[=, &a] 除了变量a之外,按值的方式捕获所有局部变量,变量a使用引用的方式来捕获。这里可以按引用捕获多个,例如 [=, &a, &b,&c]。这里注意,如果前面加了=,后面加的具体的参数必须以引用的方式来捕获,否则会报错。
[&, a] 除了变量a之外,按引用的方式捕获所有局部变量,变量a使用值的方式来捕获。这里后面的参数也可以多个,例如 [&, a, b, c]。这里注意,如果前面加了&,后面加的具体的参数必须以值的方式来捕获。
[a, &b] 以值的方式捕获a,引用的方式捕获b,也可以捕获多个。
[this] 在成员函数中,也可以直接捕获this指针,其实在成员函数中,[=]和[&]也会捕获this指针。
- parameters(参数列表):与普通函数的参数列表类似,如果lambda不接受任何参数,则可以省略参数列表或保留空的括号()。
- return_type(返回类型):lambda表达式的返回类型。如果lambda体包含单一的return语句或不返回任何值,则可以省略返回类型和箭头->,编译器会自动推导返回类型。 mutable关键字只有在捕获列表中是值捕获时才有用,下面会进行详细介绍。
- function_body(函数体):包含lambda表达式逻辑的代码块
- 编译器在遇到lambda表达式时做了哪些事情
我们把lambda表达式看成一个函数,那编译器怎么看待我们协的lambda呢?其实,编译器会把我们写的lambda表达式翻译成一个类,并重载 operator()来实现。比如我们写一个lambda表达式为:
auto plus = [] (int a, int b) -> int { return a + b; }
int c = plus(1, 2);
编译器会翻译为:
class ClassName
{
public:
int operator () (int a, int b) const
{
return a + b;
}
};
ClassName plus;
int c = plus(1, 2);
调用的时候编译器会生成一个Lambda的对象,并调用opeartor ()函数。(备注:这里的编译的翻译结果并不和真正的结果完全一致,只是把最主要的部分体现出来,其他的像类到函数指针的转换函数均省略)
上面是一种调用方式,那么如果我们写一个复杂一点的lambda表达式,表达式中的成分会如何与类的成分对应呢?我们再看一个 值捕获 例子。
int x = 1; int y = 2;
auto plus = [=] (int a, int b) -> int { return x + y + a + b; };
int c = plus(1, 2);
// 编译器的翻译结果为
class ClassName
{
public:
ClassName(int xx, int yy)
: x(xx), y(yy) {}
int operator () (int a, int b) const
{
return x + y + a + b;
}
private:
int x;
int y;
}
int x = 1; int y = 2;
ClassName plus(x, y);
int c = plus(1, 2);
其实这里就可以看出,值捕获时,编译器会把捕获到的值作为类的成员变量,并且变量是以值的方式传递的。需要注意的时,如果所有的参数都是值捕获的方式,那么生成的operator()函数是const函数的,是无法修改捕获的值的,哪怕这个修改不会改变lambda表达式外部的变量,如果想要在函数内修改捕获的值,需要加上关键字 mutable。向下面这样的形式。
int x = 1; int y = 2;
auto plus = [=] (int a, int b) mutable -> int { x++; return x + y + a + b; };
int c = plus(1, 2);
最后是一个用引用捕获的例子:
int x = 1; int y = 2;
auto plus = [&] (int a, int b) -> int { x++; return x + y + a + b;};
int c = plus(1, 2);
// 编译器的翻译结果为
class ClassName
{
public:
ClassName(int& xx, int& yy)
: x(xx), y(yy) {}
int operator () (int a, int b)
{
x++;
return x + y + a + b;
}
private:
int &x;
int &y;
};
我们可以看到以引用的方式捕获变量,和值捕获的方式有3个不同的地方:1. 参数引用的方式进行传递; 2. 引用捕获在函数体修改变量,会直接修改lambda表达式外部的变量;3. opeartor()函数不是const的。
最后,我们把lambda的各个成分和类的各个成分对应起来就是如下的关系:
- 捕获列表,对应ClassName类的private成员。
- 参数列表,对应ClassName类的成员函数的operator()的形参列表
- mutable,对应 ClassName类成员函数 operator() 的const属性 ,加了它编译器生成的operator()不带const,只有捕获列表中只有值捕获的时候才有意义,因为如果捕获列表中有引用捕获,那么operator()天然不带const
- 返回类型,对应 ClassName类成员函数 operator() 的返回类型
- 函数体,对应 ClassName类成员函数 operator() 的函数体。
- 引用捕获和值捕获不同的一点就是,对应的成员是否为引用类型。