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

发布于:2022-11-08 ⋅ 阅读:(497) ⋅ 点赞:(0)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

目录

文章目录

前言

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. 匿名函数构建的时候对于按值传递的捕获列表, 会立即将当前可以取到的值拷贝一份作为常数 ,然后将该常数作为参数传递。


网站公告

今日签到

点亮在社区的每一天
去签到