一文读懂C++11的Lambda表达式的用法与原理

1、Lambda表达式简介

Lambda表达式(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数。Lambda表达式可以表示闭包(注意和数学传统意义上的不同)。

2、Lambda表达式使用方法介绍

C++11中增加了对Lambda表达式的支持,其格式如下:
[捕获列表](参数列表)mutable->类型{函数体}
其中各个参数的说明见表2-1

表2-1
参数说明
捕获列表允许访问当前作用域下的某一个变量,可以为空(方括号不能省略)
参数列表Lambda表达式的参数,可以省略(包括括号),不能有不定参数,不能有默认参数,参数必须有名称
mutable捕获列表中的变量默认以const方式传递,不能修改,添加此关键字能指定变量可以被修改
类型指定返回值类型(如果返回类型比较明显,可以省略,让编译器自动推断,省略时要连同->符号一起省略),包括void类型
函数体需执行的代码,如果没有return语句且没指定类型,默认返回类型为void

从上面的介绍中可以得出最短的Lambda表达式的形式如下:

[]{}

表2-1介绍的的捕获列表还需要进一步扩展,其有多种形式,详见表2-2

表2-2
捕获形式捕获类型说明
[]不捕获不捕获外部变量
[变量名, …]显式捕获默认以值得形式捕获指定的多个外部变量(用逗号分隔),如果引用捕获,需要显示声明(使用&说明符)
[this]显式捕获以值的形式捕获this指针
[=]隐式捕获以值的形式捕获所有外部变量
[&]隐式捕获以引用形式捕获所有外部变量
[=, &x]混合捕获变量x以引用形式捕获,其余变量以传值形式捕获(注意顺序不能变,=符号一定要在前面)
[&, x]混合捕获变量x以值的形式捕获,其余变量以引用形式捕获(注意顺序不能变,&符号一定要在前面)

下面给出一些例子帮助大家更好地理解Lambda表达式

#include <iostream>
#include <string>
#include <functional>

int main(int argc, char* argv[])
{
    int a = 10, b = -1;

    // 显式的值捕获,报错,因为显示值捕获默认是以const方式传递
    //auto f1 = [a, b]{ a = 100; b = -10; };

    // 显式的值捕获,正常,注意此时()一定要加,不然依旧给你报错
    auto f2 = [a, b]()mutable{ a = 100; b = -10; };

    // 显式的引用捕获
    auto f3 = [&a, &b]{ a = 100; b = -10; };

    // 隐式的值捕获,报错,理由同上
    //auto f4 = [=]{ a = 100; b = -10; };

    // 隐式的值捕获,正常
    auto f5 = [=]()mutable{ a = 100; b = -10; };

    // 隐式的引用捕获,正常
    auto f6 = [&]{ a = 100; b = -10; };

    // 混合捕获
    auto f7 = [=, &a]{ a = 100; };

    // 混合捕获,出错,因为a是值传递,需要加mutable
    //auto f8 = [&, a]{ a = 100; };

    // 混合捕获,正常
    auto f9 = [&, a]()mutable{ a = 100; };
}

3、Lambda表达式实现原理解析

前面讲了Lambda表达式的一些基本用法,现在到例行解析原理环节了。C++有一个术语叫做可调用对象,我们知道C语言中是没有这个说法,因为C语言可调用的无非就是函数或者说是函数指针而已,而C++不同,有许多种方法可以模拟函数的行为,例子如下:

#include <iostream>
#include <string>
#include <functional>

typedef void (*FUNC)(const std::string);

void fun1(const std::string name)
{
    std::cout << name << std::endl;
}

class f_class
{
public:
    static void fun4(const std::string name)
    {
        std::cout << name << std::endl;
    }

    void operator()(const std::string name)
    {
        std::cout << name << std::endl;
    }

    void fun6(const std::string name)
    {
        std::cout << name << std::endl;
    }
};

int main(int argc, char* argv[])
{
    FUNC fun2 = fun1;
    auto fun3 = [](const std::string name){ std::cout << name << std::endl; };
    f_class fun5;

    fun1("函数");

    fun2("函数指针");

    fun3("Lambda表达式");

    f_class::fun4("类静态成员函数");

    fun5("类重载()运算符");

    // 这么用肯定是不行的啦,没有权限
    //f_class::fun6("类成员函数");
    // 需要这么用
    f_class* a = nullptr;
    std::mem_fn(&f_class::fun6)(a, "类成员函数");
}

上面写的这些看起来好像每一种都各不相同,但其实只分为两类。第一种、第二种还有第四种根本就是一个东西,都是普通的函数指针,而其余三种本质上也是一样的,尽管他们长得一点都不像。关于std::mem_fn的相关内容大家可以看这篇文章《C++11的std::mem_fn源码解析》,看完大家就知道std::mem_fn最终也是通过类重载()运算符来实现函数调用的,但这些都不是本文重点,接下来我们就看一下Lambda表达式被编译器解释为什么样的形式。

先写一段简单的测试代码

#include <iostream>
#include <string>
#include <functional>

int main(int argc, char* argv[])
{
    int a = 20, b = -1;
    auto f = [&](int x, int y)->bool{ return a + b + x + y; };
    f(-123, 9999);
}

然后编译一下,记得加上-g选项

g++ -g -std=c++11 main.cpp -o all

使用gdb调试代码

gdb ./all

接下来就进入gdb调试界面了,为了完整呈现整个调试过程,下面我将完整贴出控制台输出信息,在指令处我做了红色的标记,重要输出信息处做了加粗标记

GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-120.el7
Copyright © 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later http://gnu.org/licenses/gpl.html
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type “show copying”
and “show warranty” for details.
This GDB was configured as “x86_64-redhat-linux-gnu”.
For bug reporting instructions, please see:
http://www.gnu.org/software/gdb/bugs/
Reading symbols from /home/bifang/src/tmp/all…done.
(gdb) b main
Breakpoint 1 at 0x400691: file main.cpp, line 7.
(gdb) r
Starting program: /home/bifang/src/tmp/./all
Breakpoint 1, main (argc=1, argv=0x7fffffffe4f8) at main.cpp:7
7 int a = 20, b = -1;
(gdb) ptype f
type = struct __lambda0 {
int &__a;
int &__b;
}

(gdb) s
8 auto f = [&](int x, int y)->bool{ return a + b + x + y; };
(gdb) s
9 f(-123, 9999);
(gdb) s
__lambda0::operator() (__closure=0x7fffffffe3f0, x=-123, y=9999) at main.cpp:8
8 auto f = [&](int x, int y)->bool{ return a + b + x + y; };
(gdb) ptype __closure
type = const struct __lambda0 {
int &__a;
int &__b;
} * const

从调试信息中可以看到,由于Lambda表达式f引用了局部变量ab,编译器在编译时动态地生成了这样一个结构

struct __lambda0 {
int &__a;
int &__b;
bool operator()(int x, int y)
{
return x + y+ __a + __b;
}
}

这个东西看起来好熟悉的样子,这不就是前面讲的类重载()运算符的那种形式吗。Lambda虽然叫做匿名函数,但是它是对我们使用者匿名,不是对编译器匿名,编译器会根据表达式的内容动态地生成一个类,这个类实现了operator(),使得其具有成为一个可调用对象的能力,至于引用了其它参数,则是在类里面创建相应的类成员来实现这个功能。至此Lambda的实现原理也就全部解析完毕了。

4、总结

本文先是简单地介绍了Lambda表达式的定义,然后对C++11的Lambda表达式的使用方法进行了详细介绍,并给出了相关示例,最后就是对Lambda表达式的实现原理进行了详细解析,其中用到了一点简单的GDB调试技巧,大家可以顺便学一下GDB的使用,对理解代码还是有点帮助的。

最后,如果大家觉得本文写得好的话麻烦点赞收藏关注一下谢谢,也可以关注该专栏,以后会有更多优质文章输出的。

  • 172
    点赞
  • 137
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 184
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

彼 方

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值