一篇文章了解lambda表达式的本质

本文详细介绍了C++11中的Lambda表达式,包括其语法、捕获变量的方式(值捕获和引用捕获)、以及编译器如何将其转化为类并实现operator()函数。讨论了值捕获和引用捕获的区别,以及mutable关键字的作用。
摘要由CSDN通过智能技术生成
  1. 背景

Lambda表达式在C++11及之后的版本中是一个非常强大的特性,它允许你在代码中定义匿名函数对象。一个lambda表达式基本上就是一个函数,但它没有名字。这使得lambda表达式非常适合用作短期使用的小型操作,尤其是作为算法库函数的参数。

  1. 语法
    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表达式逻辑的代码块
  1. 编译器在遇到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() 的函数体。
  • 引用捕获和值捕获不同的一点就是,对应的成员是否为引用类型。
  • 23
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值