c++新特性之匿名函数lambda

本文深入讲解C++中的Lambda表达式,包括基本语法、捕获列表的不同类型及其应用场景,并介绍了泛型Lambda与可变Lambda等高级特性。

目录

前言

1.匿名函数的基本语法

2.捕获列表

2.1 值捕获

2.2 引用捕获

2.3 隐式捕获

2.4 空捕获列表

2.6 泛型 Lambda

2.7 可变lambda  

总结


前言

        使用  STL 时,往往会大量用到函数对象,为此要编写很多函数对象类。有的函数对象类只用来定义了一个对象,而且这个对象也只使用了一次,编写这样的函数对象类就有点浪费。而且,定义函数对象类的地方和使用函数对象的地方可能相隔较远,看到函数对象,想要查看其 operator() 成员函数到底是做什么的也会比较麻烦。对于只使用一次的函数对象类,能否直接在使用它的地方定义呢?Lambda 表达式能够解决这个问题。使用 Lambda 表达式可以减少程序中函数对象类的数量,使得程序更加优雅。

1.匿名函数的基本语法

[ 捕获列表 ]( 参数列表 ) mutable( 可选 ) 异常属性 -> 返回类型 {
        // 函数体
}

代码如下(示例):

//[ 捕获列表 ]( 参数列表 )-> 返回类型 { 函数体 }
int main ()
{
        auto Add = []( int a , int b ) -> int {
                return a + b ;
        };
        std::cout << Add ( 1 , 2 ) << std::endl ; // 输出 3
        return 0 ;
}
一般情况下,编译器可以自动推断出 lambda 表达式的返回类型,所以我们可以不指定返回类型
//[ 捕获列表 ]( 参数列表 ){ 函数体 }
int main ()
{
        auto Add = []( int a , int b ) {
                return a + b ;
        };
        std::cout << Add ( 1 , 2 ) << std::endl ; // 输出 3
        return 0 ;
}

但是如果函数体内有多个return语句时,编译器无法自动推断出返回类型,此时必须指定返回类型。 

2.捕获列表

        有时候,需要在匿名函数内使用外部变量,所以用捕获列表来传递参数。根据传递参数的行为,捕获列表可分为以下几种:

2.1 值捕获

        与参数传值类似,值捕获的前提是变量可以拷贝,不同之处则在于,被捕获的变量在 lambda 表达式被 创建时拷贝 ,而非调用时才拷贝:
void test3 ()
{
        cout << "test3" << endl ;
        int c = 12 ;
        int d = 30 ;
        auto Add = [ c , d ]( int a , int b ) -> int {
                cout << "d = " << d << endl ;
                return c ;
        };
        d = 20 ;
        std::cout << Add ( 1 , 2 ) << std::endl ;
}

2.2 引用捕获

与引用传参类似,引用捕获保存的是引用,值会发生变化。
如果Add中加入一句:
c = a;

void test5 ()
{
        cout << "test5" << endl ;
        int c = 12 ;
        int d = 30 ;
        auto Add = [ & c , & d ]( int a , int b ) -> int {
                c = a ; // 编译对的
                cout << "d = " << d << endl ;
                return c ;
        };
        d = 20 ;
        std::cout << Add ( 1 , 2 ) << std::endl ;
}

2.3 隐式捕获

        手动书写捕获列表有时候是非常复杂的,这种机械性的工作可以交给编译器来处理,这时候可以在捕获列表中写一个 & 或 = 向编译器声明采用引用捕获或者值捕获。

void test7 ()
{
        cout << "test7" << endl ;
        int c = 12 ;
        int d = 30 ;
        // 把捕获列表的 & 改成 = 再测试
        auto Add = [ & ]( int a , int b ) -> int {
                c = a ; // 编译对的
                cout << "d = " << d << endl ;
                return c ;
        };
        d = 20 ;
        std::cout << Add ( 1 , 2 ) << std::endl ;
        std::cout << "c:" << c << std::endl ;
}

2.4 空捕获列表

捕获列表'[]'中为空,表示Lambda不能使用所在函数中的变量。

void test8 ()
{
        cout << "test7" << endl ;
        int c = 12 ;
        int d = 30 ;
        // 把捕获列表的 & 改成 = 再测试
        auto Add = []( int a , int b ) -> int {
                cout << "d = " << d << endl ; // 编译报错
                return c ; // 编译报错
        };
        d = 20 ;
        std::cout << Add ( 1 , 2 ) << std::endl ;
        std::cout << "c:" << c << std::endl ;
}
2.5 表达式捕获
上面提到的值捕获、引用捕获都是已经在外层作用域声明的变量,因此这些捕获方式捕获的均为左值,而不能捕获右值
C++14 之后支持捕获右值,允许捕获的成员用任意的表达式进行初始化,被声明的捕获变量类型会根据表达式进行判断,判断方式与使用 auto 本质上是相同的:
void test9 ()
{
        cout << "test9" << endl ;
        auto important = std::make_unique < int > ( 1 );
        auto add = [ v1 = 1 , v2 = std::move ( important )]( int x , int y ) -> int {
                return x + y + v1 + ( * v2 );
        };
        std::cout << add ( 3 , 4 ) << std::endl ;
}

2.6 泛型 Lambda

C++14 之前, lambda 表示的形参只能指定具体的类型,没法泛型化。从 C++14 开始, Lambda 函数的形式参数可以使用 auto 关键字来产生意义上的泛型:
// 泛型 Lambda C++14
void test10 ()
{
        cout << "test10" << endl ;
        auto add = []( auto x , auto y ) {
                return x + y ;
        };
        std::cout << add ( 1 , 2 ) << std::endl ;
        std::cout << add ( 1.1 , 1.2 ) << std::endl ;
}

2.7 可变lambda  

  • 采用值捕获的方式lambda不能修改其值,如果想要修改,使用mutable修饰
  • 采用引用捕获的方式,lambda可以直接修改其值
void test12() {
        cout << "test12" << endl;
        int v = 5;
        // 值捕获方式,使用 mutable 修饰,可以改变捕获的变量值
        auto ff = [v]() mutable {return ++v;};
        v = 0;
        auto j = ff(); // j为 6
}
void test13() {
        cout << "test13" << endl;
        int v = 5;
        // 采用引用捕获方式,可以直接修改变量值
        auto ff = [&v] {return ++v;};
        v = 0;
        auto j = ff(); // v引用已修改, j 1
}


总结

1. 如果捕获列表为 [&] ,则表示所有的外部变量都按引用传递给 lambda 使用;
2. 如果捕获列表为 [=] ,则表示所有的外部变量都按值传递给 lambda 使用;
3. 匿名函数构建的时候对于按值传递的捕获列表, 会立即将当前可以取到的值拷贝一份作为常数 ,然后将该常数作为参数传递。

### C++ Lambda 表达式的使用方法和特性 #### 定义与基本语法 Lambda表达式是C++11标准引入的一种匿名函数,允许在需要函数的地方直接编代码块,而无需定义一个完整的函数。其主要用途在于简化回调函数以及STL算法的应用场景[^2]。 #### 语法结构 典型的lambda表达式由以下几个部分组成: - **捕获列表**:决定哪些局部变量可以在lambda体内被访问。 - **参数列表**:类似于常规函数的参数说明。 - **可选返回类型**:对于复杂情况下的显式指定(默认自动推导)。 - **函数体**:执行的具体逻辑。 例如,最简单的形式如下所示: ```cpp [] () { /* 函数体 */ } ``` 当涉及到具体应用时,则可以根据需求调整各个组成部分的内容。比如,在上述例子中创建了一个接受整数并返回该数值加一的操作: ```cpp [](int x) { return x + 1; } ``` #### 捕获机制 为了使lambda能够操作所在作用域内的变量,可以通过捕获列表来实现这一点。常见的几种方式有按值传递(`=`, `&`)或者混合模式([`this`]表示当前类实例)[^3]。 - `[=]`: 复制所有外部变量到闭包环境; - `[&]`: 引用所有外部变量至闭包内部; - `[var, &refVar]`: 显式指明要复制或引用特定变量; #### 实际案例展示 下面的例子展示了如何利用lambda作为函数对象存放在容器当中,并通过迭代器遍历调用这些函数对象来进行计算处理[^1]: ```cpp #include <iostream> #include <vector> #include <functional> int main() { // 存储Lambda表达式的向量 std::vector<std::function<int(int)>> v; // 使用Lambda表达式推入函数对象 v.push_back([](int x) { return x + 1; }); v.push_back([](int x) { return x * 2; }); // 输出Lambda表达式值 for (auto f : v) { std::cout << f(2) << " "; } return 0; } ``` 此程序会依次打印出两个不同的运算结果:“3 4”。 #### 特性和优势 - 提高代码简洁度,减少不必要的命名函数开销。 - 方便地嵌入到其他语句之中,增强表达力。 - 支持复杂的控制流设计,特别是在涉及多线程编程或是异步任务调度的情况下显得尤为有用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值