• C++ Lambda表达式


    什么是Lambda表达式呢?λ演算(λ-calculus)是一个形式化的数学逻辑系统,它基于函数的抽象和应用,使用变量绑定和替换来表示计算。具体请参考:λ-calculus
    从C++11开始可以使用Lambda表达式创建匿名函数对象(闭包),它可以作为一个参数传递给另一个函数。语法如下:
    lambdaexpsyntax3.png

    1.捕获从句

    1.[ ]是捕获从句,lambda可以访问或捕获周围作用域的变量。在c++ 14中,还可以在其函数体中引入新变量。lambda表达式必须以[]开始。用它来指定捕获哪些变量并指明捕获是通过值还是通过引用。有&前缀的变量会以引用的方式被访问,否则就是以值的方式来访问。

    • [ ],表示lambda表达式的函数体内不访问任何外部变量。如果此时在函数体内访问了lambda表达式的函数体作用域外的变量就会报错。 如:
    int main(int argc, char *argv[])
    {
        ...
        Car car("BYD",90000);
        int m = 0;
        int n = 0;
        [] (int a) mutable {
            m = ++n + a + car.getPrice(); }(4); // lambda函数体不能捕获m和n变量,因此会报错
    
    	...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    -[arg1,arg2,…],我们也可以指定要捕获的变量和方式(引用或值,若是引用方式就是在变量前加&,函数体对引用变量的改变会影响原值),如

    int main(int argc, char *argv[])
    {
        ...
        Car car("BYD",90000);
        int m = 0;
        int n = 0;
        [&m,n,car] (int a) mutable {// 以引用方式引用外部变量m,所以m的改动会影响原值,
    // n和car都是以值的形式,所以它们的改动不会影响原值。
            m = ++n + a + car.getPrice(); }(4);
    
    	...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • [&]和[=],我们指定一个默认模式去捕获外部变量。[&]表示以引用方式捕获所有外部变量,在函数体中对变量做的操作会影响原值,[=]就是以值方式捕获所有外部变量,在函数体中对变量做的操作不会影响原值。
    int main(int argc, char *argv[])
    {
        ...
        Car car("BYD",90000);
        int m = 0;
        int n = 0;
        [&] (int a) mutable {// 以引用方式引用外部变量,改动变量会影响原值,
            m = ++n + a + car.getPrice(); }(4);
    	[=] (int a) mutable {// 以值方式引用外部变量,改动变量不会影响原值,
            m = ++n + a + car.getPrice(); }(6);
    	...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    当指定了默认捕获模式后,也可以用相反模式指定某个变量的捕获,如:

    int main(int argc, char *argv[])
    {
        ...
        Car car("BYD",90000);
        int m = 0;
        int n = 0;
        [=,&m,&car] (int a) mutable {// 默认以值模式捕获外部变量,对于外部变量m和car则使用引用模式捕获。
            m = ++n + a + car.getPrice(); }(4);
    
    	...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    这里还要注意一点,当指定了默认捕获模式,只有在lambda函数体用到的外部变量才会被捕捉。

    如果捕获从句(即[ ])中已指定了默认捕获模式为&,即引用,那么[ ]中就不能再指定任何& identifier,因为指定的默认模式&中,已包含此。例子:

    [&, &i]{}; // ERROR: i preceded by & when & is the default
    
    • 1

    同理,如果指定了默认捕获模式为=,那么也不能在[]指定任何= identifier

    [=, this]{};   // ERROR: this when = is the default
    
    • 1

    一个标识符或this指针在[]捕获从句中只能出现一次,例子:

    [=, *this]{ }; // OK: captures this by value. See below.
    [i, i]{};      // ERROR: i repeated
    
    • 1
    • 2

    对于可变参数的捕获:一个捕获加上一个省略号是一个包的扩展:

    template
    void f(Args... args) {
        auto x = [args...] { return g(args...); };
        x();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    要在类成员函数体中使用lambda表达式,就要将this指针传递给capture子句,以提供对封闭类的成员函数和数据成员的访问。

    int Car::getPrice(){
        [this] (int a) mutable {
            int aw = price;
        }(4);
        return price;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在c++17之后,this 指针可以通过值的方式被捕获,即[*this]。通过值方式的捕获会拷贝整个闭包到lambda调用处。 所谓的闭包就是匿名函数对象,这个对象封装了lambda表达式。当lambda表达式并行执行或异步操作时,通过值方式的捕获非常有用。

    在多线程使用lambda表达式时,要注意:

    1. 引用型捕获可以改变外部变量,但是值捕获不会。mutable关键字表明允许修改副本,但不允许修改原始副本
    2. 引用型捕获会引入生命周期依赖,但是值方式的捕获没有生命周期依赖问题。特别重要的一点是当lambda表达式异步执行,如果在异步lambda中通过引用捕获局部变量, 那么那个局部变量随着lambda的运行很容易就被销毁了。我们的代码可能就会在运行时导致访问冲突。

    在c++ 14中,可以在capture子句中引入和初始化新变量,而不需要让这些变量存在于lambda函数的封闭作用域中。初始化可以表示为任意表达式,新变量的类型由表达式产生的类型推导而来。该特性允许从周围的作用域捕获仅需移动的变量,并在lambda中使用它们。

    QString *ptr;
    auto a = [ptr=new QString()]()
            {
               // use ptr
            };
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2.参数列表

    Lambdas不仅可以捕获变量,还可以接受输入参数. 参数列表对于lambda表达式来说是可选,就是说不一定要有。

    auto y = [] (int first, int second)
    {
        return first + second;
    };
    
    • 1
    • 2
    • 3
    • 4

    在c++ 14中,如果参数类型是泛型的,可以使用auto关键字作为类型说明符。该关键字告诉编译器将函数调用操作符创建为模板。参数列表中auto的每个实例等效于一个不同的类型参数。

    auto y = [] (auto first, auto second)
    {
        return first + second;
    };
    
    • 1
    • 2
    • 3
    • 4

    另外,lambda表达式也可以将另一个lambda表达式作为它的参数。因为参数列表是可选的,所以如果lambda表达式不用传参数,并且lambda表达式的声明符中不包含异常声明、返回类型或mutable关键字,那么括号可以省略。

    3.mutable

    lambda以值方式捕获的变量,lambda函数体内默认是const的,即不能修改它们。mutable关键字就是取消这一点。加上mutable关键字,能够在函数体内修改以值方式捕获到的变量的副本。

    int m = 0;
    int n = 0;
    [&m,n] (int a)  {
         m = ++n + a;// 没有mutable关键字,所以++n会报错,因为n是const的。
    }(5);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    加上mutable关键字就可以修改n的副本的值了。

    int m = 0;
    int n = 0;
    [&m,n] (int a) mutable  {
         m = ++n + a;// 没有mutable关键字,所以++n会报错,因为n是const的。
    }(5);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    4.exception

    我们以用noexcept表明lambda表达式不会抛出任何异常。

    5.Return type

    指定lambda表达式的返回类型。lambda表达式的返回类型是自动推导的。如果函数体只有一条return语句,那么会自动推导出返回类型。

    auto x1 = [](int i){ return i; }; // OK: return type is int
    
    • 1

    6.Lambda body

    lambda函数体实现。函数体可以访问以下这些变量:

    1. 从封闭区域捕获到的变量
    2. 参数
    3. lambda函数内部本地定义的变量。
    4. 类成员,当在一个类里定义了lambda表达式,且表达式里捕获了this。
    5. 任何有静态存储期的变量,如全局变量
  • 相关阅读:
    systemd 服务脚本编写与管理
    何登骥获“两优一先”荣誉 谋定·国稻种芯: 湖南农业科学院表彰
    按照前序输入创建二叉树并以前序遍历二叉树
    迁移学习怎么用
    Viva Workplace Analytics & Employee Feedback SU Viva Insight部署方案
    Node学习笔记之Express框架
    快 50 岁了,怎么还在写代码?
    论文阅读:CenterFormer: Center-based Transformer for 3D Object Detection
    华为云分布式数据库GaussDB,做金融数字化的坚实数据底座
    【NOTE】【深入浅出nodejs】内存控制(一)
  • 原文地址:https://blog.csdn.net/weixin_40763897/article/details/126458133