Skip to content

lambda表达式

基本语法特性

cpp
[capture](params) opt -> retureType {
    body;
};
  • capture 捕获列表
  • params 参数列表
  • opt 函数选项
  • retureType 返回值类型
  • body 函数体

捕获列表capture

不能省略。捕获一定范围内的变量。

  • [] 不捕捉任何变量
  • [&] 捕获外部作用域中所有变量, 并作为引用在函数体内使用 (按引用捕获)
  • [=] 捕获外部作用域中所有变量, 并作为副本在函数体内使用 (按值捕获),拷贝的副本在匿名函数 体内部是只读的
  • [=, &bar] 按值捕获外部作用域中所有变量, 并按照引用捕获外部变量bar
  • [bar] 按值捕获bar变量, 同时不捕获其他变量
  • [&bar] 按引用捕获bar变量,同时不捕获其他变量
  • [&,bar] 按引用捕获其他变量,同时按值捕获bar变量
  • [this] 捕获当前类中的this指针。让lambda表达式拥有和当前类成员函数同样的访问权限;可以 修改类的成员变量,使用类的成员函数。如果已经使用了 & 或者 =, 默认添加此选项

注意

如果是全局变量,可以不用捕获,直接使用。

下面看两个例子,看看lambda的使用方法。

cpp
class Example {
public:
    void print(int x, int y) {
        auto x1 = [] { return _number; };                   // error
        auto x2 = [this] { return _number; };               // ok
        auto x3 = [this] { return _number + x + y; };       // error
        auto x4 = [this, x, y] { return _number + x + y; }; // ok
        auto x5 = [this] { return _number++; };             // ok
        auto x6 = [=] { return _number + x + y; };          // ok
        auto x7 = [&] { return _number + x + y; };          // ok
    }

    int _number = 100;
};

void test() {
    int a = 10, b = 20;
    auto f1 = [] { return a; };              // error
    auto f2 = [&] { return a++; };           // ok
    auto f3 = [=] { return a; };             // ok
    auto f4 = [=] { return a++; };           // error
    auto f5 = [a] { return a + b; };         // error
    auto f6 = [a, &b] { return a + (b++); }; // ok
    auto f7 = [=, &b] { return a + (b++); }; // ok
}

在匿名函数内部,需要通过lambda表达式的捕获列表控制如何捕获外部变量,以及访问哪些变量。默 认状态下lambda表达式无法修改通过复制方式捕获外部变量,如果希望修改这些外部变量,需要通过 引用的方式进行捕获。

参数列表params

和普通函数的参数列表一样,如果没有参数,参数列表可以省略不写。

cpp
auto f = []() { return 10; }; // 没有参数, 参数列表为空
auto f = [] { return 10; };   // 没有参数, 参数列表省略不写

选项opt

可以省略。

  • mutable: 可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)
  • exception: 指定函数抛出的异常,如抛出整数类型的异常,可以使用throw();

返回类型retureType

可以省略。通过返回值后置语法来定义的。一般情况下,不指定lambda表达式的返回值,编译器会根 据return语句自动推导返回值的类型,但需要注意的是labmda表达式不能通过列表初始化自动推导出 返回值类型。

cpp
// ok,可以自动推导出返回值类型
auto f = [](int i) { return i; };

// error,不能推导出返回值类型
auto f1 = []() {
    return {1, 2}; // 基于列表初始化推导返回值,错误
};

函数体body

不能省略。但函数体可以为空

函数本质

使用lambda表达式捕获列表捕获外部变量,如果希望去按值捕获的外部变量,那么应该如何处理呢? 这就需要使用mutable选项,被mutable修改是lambda表达式就算没有参数也要写明参数列表,并且可 以去掉按值捕获的外部变量的只读(const)属性。

cpp
int a = 0;
auto f1 = [=] { return a++; };           // error, 按值捕获外部变量, a是只读的
auto f2 = [=]() mutable { return a++; }; // ok

最后再剖析一下为什么通过值拷贝的方式捕获的外部变量是只读的。

lambda表达式的类型在C++11中会被看做是一个带operator()的类,即仿函数。按照C++标准, lambda表达式的operator()默认是const的,一个const成员函数是无法修改成员变量值的。mutable选 项的作用就在于取消operator()的const属性。

因为lambda表达式在C++中会被看做是一个仿函数,因此可以使用std::function和std::bind来存储和操 作lambda表达式。

cpp
void test() {
    // 包装可调用函数
    function<int(int)> f1 = [](int a) { return a; };

    // 绑定可调用函数
    function<int(int)> f2 = bind([](int a) { return a; }, placeholders::_1);

    // 函数调用
    cout << f1(100) << endl;
    cout << f2(200) << endl;
}

基本使用形式

cpp
void test() {
    // 匿名函数
    [](const std::string &name) { std::cout << "[] name = " << name << std::endl; }("lili");

    auto func2 = [](const std::string &name) -> std::string {
        std::cout << "func2 name = " << name << std::endl;
        return name;
    };
    func2("lucy");

    auto func3 = [](const std::string &name) { std::cout << "func3 name = " << name << std::endl; };
    func3("welcome");

    std::function<void(const std::string &)> func4 = [](const std::string &name) { std::cout << "func4 name = " << name << std::endl; };
    func4("wangdao");
}

因为Lambda表达式是一个匿名函数, 因此是没有函数声明的, 直接在程序中进行代码的定义即可, 但是如果只定义匿名函数在程序执行过程中是不会被调用的。

cpp
// 匿名函数的定义, 程序执行这个匿名函数是不会被调用的
[](){
    std::cout << "hello, 我是一个lambda表达式..." << std::endl;
};

// 匿名函数的定义+调用:
int ret = [](int a) -> int
{
    return a+1;
}(100);  // 100是传递给匿名函数的参数

Lambda表达式的捕获列表中也就是 []内部添加不同的关键字, 就可以在函数体中使用外部变量了。

cpp
// 在匿名函数外部定义变量
int a=100, b=200, c=300;
// 调用匿名函数
[](){
    // 打印外部变量的值
    std::cout << "a:" << a << ", b: " << b << ", c:" << c << std::endl;  // error, 不能使用任何外部变量
}

[&](){
    std::cout << "hello, 我是一个lambda表达式..." << std::endl;
    std::cout << "使用引用的方式传递数据: " << std::endl;
    std::cout << "a+1:" << a++ << ", b+c= " << b+c << std::endl;
}();

// 值拷贝的方式使用外部数据
[=](int m, int n)mutable{
    std::cout << "hello, 我是一个lambda表达式..." << std::endl;
    std::cout << "使用拷贝的方式传递数据: " << std::endl;
    // 拷贝的外部数据在函数体内部是只读的, 如果不添加 mutable 关键字是不能修改这些只读数据的值的
    // 添加 mutable 允许修改的数据是拷贝到函数内部的副本, 对外部数据没有影响
    std::cout << "a+1:" << a++ << ", b+c= " << b+c << std::endl;
    std::cout << "m+1: " << ++m << ", n: " << n << std::endl;
}(1, 2);

捕获列表细究

cpp
void test() {
    int num = 10;
    int age = 100;
    std::string name("wangdao");

    // 匿名函数
    [](std::string value) {
        /* cout << "num = " << num << endl; */
        /* cout << "name = " << name << endl; */
        std::cout << "value = " << value << std::endl;
    }("lili");

    // 值捕获
    [num, name](std::string value) {
        std::cout << "num = " << num << std::endl;
        std::cout << "name = " << name << std::endl;
        std::cout << "value = " << value << std::endl;
    }("lili");

    std::cout << std::endl;

    // 引用捕获
    [&num, &name](std::string value) {
        num = 100;
        name = "nice";
        std::cout << "num = " << num << std::endl;
        std::cout << "name = " << name << std::endl;
        std::cout << "value = " << value << std::endl;
    }("wangdao");

    std::cout << "num = " << num << ", name = " << name << std::endl;
    std::cout << std::endl;

    // 全部用值捕获,name用引用捕获
    [=, &name]() {
        // num = 100; // error
        name = "good";
        std::cout << "num = " << num << std::endl;
        std::cout << "age = " << age << std::endl;
        std::cout << "name = " << name << std::endl;
    }();

    std::cout << std::endl;

    // 全部用引用捕获,name用值捕获
    [&, name]() {
        num = 1000;    // ok
        age = 200;     // ok
        // name = "good"; // error
        std::cout << "num = " << num << std::endl;
        std::cout << "age = " << age << std::endl;
        std::cout << "name = " << name << std::endl;
    }();
}

使用误区

不要捕获局部变量的引用

cpp
std::vector<std::function<void(const std::string &)>> vec;

void test() {
    int num = 100;
    std::string name("wangdao");
    vec.push_back([&num, &name](const std::string &value) { 
        std::cout << "num = " << num << std::endl; 
        std::cout << "name = " << name << std::endl; 
        std::cout << "value = " << value << std::endl; 
    });
}

void test2() {
    for (auto func : vec) {
        func("wuhan");
    }
}

异步体系中的使用

现有以下代码片段

cpp
void function1() {
    int data = 10;
    function2([data]() {
        std::cout << data << "\n";
    });
}

function2 函数需要一个回调函数,我们提供了一个 lamada 表达式,此处的捕获列表尽量用值捕获,因为离开 function1 函数的作用域后 再访问data的引用或它指向的地址就是非法访问了,是一种不安全的做法。