专栏首页C/C++基础C++11——lambda表达式

C++11——lambda表达式

1.简介

定义: C++11新增了很多特性,lambda表达式(lambda expression)就是其中之一,很多语言都提供了 lambda 表达式,如 Python,Java ,C#等。本质上, lambda 表达式就是一个可调用的代码单元[1]^{[1]}。实际上是一个闭包(closure),类似于一个匿名的函数,拥有捕获所在作用域中变量的能力;能够将函数做为对象一样使用。通常用用来实现回调函数、代理等功能。lambda表达式是函数式编程的基础,C++11引入了lambda则弥补了C++在函数式编程方面的空缺。

关于闭包的理解,请参见web前端开发初学者十问集锦(4)

作用: 以往C++需要传入一个函数的时候,必须事先进行声明,视情况可以声明为一个普通函数然后传入函数指针,或者声明一个仿函数(functor,函数对象),然后传入一个对象。比如C++的STL中很多算法函数模板需要传入谓词(predicate)来作为判断条件,如排序算法sort。谓词就是一个可调用的表达式,其返回结果是一个能用作条件的值。标准库算法所使用的谓词分为两类:一元谓词(unary predicate,只接受单一参数)和二元谓词(binary predicate,接受两个参数)。接受谓词的算法对输入序列中的元素调用谓词,因此元素类型必须能转换为谓词的参数类型。如下面使用sort()传入比较函数shorter()(这里的比较函数shorter()就是谓词)将字符串按长度由短至长排列。

//谓词:比较函数,用来按长度排列字符串
bool shorter(const string& s1,const string& s2){
    return s1.size()<s2.size();
}

//按长度由短至长排列words
std::sort(words.begin(),words.end(),shorter);

lambda表达式可以像函数指针、仿函数一样,作为一个可调用对象(callable object)被使用,比如作为谓词传入标准库算法。

也许有人会问,有了函数指针、函数对象为何还要引入lambda呢?函数对象能维护状态,但语法开销大,而函数指针语法开销小,却没法保存函数体内的状态。如果你觉得鱼和熊掌不可兼得,那你可错了。lambda函数结合了两者的优点,让你写出优雅简洁的代码。

语法格式: lambda 表达式就是一个可调用的代码单元,我们可以将其理解为一个未命名的内联函数。与任何函数类似,一个lambda具有一个返回类型、一个参数列表和一个函数体。但与函数不同,lambda可以定义在函数内部,其语法格式如下:

[capture list](parameter list) mutable(可选) 异常属性->return type{function body}

capture list(捕获列表)是一个lambda所在函数中定义的局部变量的列表,通常为空,表示lambda不使用它所在函数中的任何局部变量。parameter list(参数列表)、return type(返回类型)、function body(函数体)与任何普通函数基本一致,但是lambda的参数列表不能有默认参数,且必须使用尾置返回类型。 mutable表示lambda能够修改捕获的变量,省略了mutable,则不能修改。异常属性则指定lambda可能会抛出的异常类型。

其中lambda表达式必须的部分只有capture list和function body。在lambda忽略参数列表时表示指定一个空参数列表,忽略返回类型时,lambda可根据函数体中的代码推断出返回类型。例如:

auto f=[]{return 42;}

我们定义了一个可调用对象f,它不接受任何参数,返回42。auto关键字实际会将 lambda 表达式转换成一种类似于std::function的内部类型(但并不是std::function类型,虽然与std::function“兼容”)。所以,我们也可以这么写:

std::function<int()> lambda = [] () -> int { return val * 100; };

如果你对std::function<int()>这种写法感到很神奇,可以查看 C++ 11 的有关std::function的用法。简单来说,std::function<int()>就是一个可调用对象模板类,代表一个可调用对象,接受 0 个参数,返回值是int。所以,当我们需要一个接受一个double作为参数,返回int的对象时,就可以写作:std::function<int(double)>[3]^{[3]}。

调用方式: lambda的调用方式与普通函数的调用方式相同,上面的lambda示例调用如下:

cout<<f()<<endl;  // 打印42

//或者直接调用
cout<<[]{return 42;}()<<endl;

我们还可以定义一个单参数的lambda,实现上面字符串排序的shorter()比较函数的功能:

auto f=[](cosnt string& a,const string& b){
    return a.size()<b.size();
}

//将lambda传入排序算法sort中
sort(words.begin(),word2.end(),[](cosnt string& a,const string& b){
    return a.size()<b.size();
});

//或者
sort(words.begin(),word2.end(),f);

2.lambda的捕获列表

lambda可以获取(捕获)它所在作用域中的变量值,由捕获列表(capture list)指定在lambda 表达式的代码内可使用的外部变量。比如虽然一个lambda可以出现在一个函数中,使用其局部变量,但它只能使用那些在捕获列表中明确指明的变量。lambda在捕获所需的外部变量有两种方式:引用和值。我们可以在捕获列表中设置各变量的捕获方式。如果没有设置捕获列表,lambda默认不能捕获任何的变量。捕获方式具体有如下几种:

[] 不截取任何变量
[&} 截取外部作用域中所有变量,并作为引用在函数体中使用
[=] 截取外部作用域中所有变量,并拷贝一份在函数体中使用
[=,&valist]   截取外部作用域中所有变量,并拷贝一份在函数体中使用,但是对以逗号分隔valist使用引用
[&,valist] 以引用的方式捕获外部作用域中所有变量,对以逗号分隔的变量列表valist使用值的方式捕获
[valist] 对以逗号分隔的变量列表valist使用值的方式捕获
[&valist] 对以逗号分隔的变量列表valist使用引用的方式捕获
[this] 截取当前类中的this指针。如果已经使用了&或者=就默认添加此选项。

在[]中设置捕获列表,就可以在lambda中使用变量a了,这里使用按值(=, by value)捕获。

#include <iostream>

int main(){
    int a = 123;
    auto lambda = [=]()->void{
        std::cout << "In lambda: " << a << std::endl;
    };
    lambda();
    return 0;
}

编译运行:

$ g++ main.cpp -std=c++11
$ ./a.out
In lambda: 123

可变类型(mutable): 按值传递到lambda中的变量,默认是不可变的(immutable),如果需要在lambda中进行修改的话,需要在形参列表后添加mutable关键字(按值传递无法改变lambda外变量的值)。

#include <iostream>
int main(){
    int a = 123;
    std::cout << a << std::endl;
    auto lambda = [=]() mutable ->void{
        a = 234;
        std::cout << "In lambda: " << a << std::endl;
    };
    lambda();
    std::cout << a << std::endl;
    return 0;
}

编译运行结果为:

$ g++ main.cpp -std=c++11
lishan:c_study apple$ ./a.out 
123
In lambda: 234  //可以修改
123             //注意这里的值,并没有改变

如果没有添加mutable,则编译出错:

$ g++ main.cpp -std=c++11
main.cpp:9:5: error: cannot assign to a variable captured by copy in a non-mutable lambda 
                a = 234;
                ~ ^
1 error generated.

看到这,不禁要问,这魔法般的变量捕获是怎么实现的呢?原来,lambda是通过创建个类来实现的。这个类重载了操作符(),一个lambda函数是该类的一个实例。当该类被构造时,周围的变量就传递给构造函数并以成员变量保存起来,看起来跟函数对象(仿函数)很相似,但是C++11标准建议使用lambda表达式,而不是函数对象,lambda表达式更加轻量高效,易于使用和理解[4]^{[4]}。

3.lambda的常见用法

(1)lambda函数和STL lambda函数的引入为STL的使用提供了极大的方便。比如下面这个例子,当你想遍历一个vector的时候,原来你得这么写:

vector<int> v;  
v.push_back( 1 );  
v.push_back( 2 );  
//...  
for ( auto itr = v.begin(), end = v.end(); itr != end; itr++ ){  
    cout << *itr;  
} 

现在有了lambda函数你就可以这么写:

vector<int> v;  
v.push_back( 1 );  
v.push_back( 2 );  
//...  
for_each(v.begin(),v.end(),[](int val){  
    cout << val;
});

而且这么写了之后执行效率反而提高了。因为编译器有可能使用”循环展开“来加速执行过程。


参考文献

[1]Stanley B. Lippman著,王刚 杨巨峰译.C++ Primer中文版第五版.2013:346-346 [2]C++教程之lambda表达式一 [3]C++11 新特性:Lambda 表达式 [4] 初窥c++11:lambda函数及其用法

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • C++11 Lambda表达式

    C++11新增了很多特性,Lambda表达式(Lambda expression)就是其中之一,很多语言都提供了 Lambda 表达式,如 Python,Jav...

    Dabelv
  • C++为什么要引入异常处理机制

    在程序设计中,错误时不可避免的。及时有效的发现错误,并作出适当的处理,无论是在软件的开发阶段还是在维护阶段都是至关重要的。错误修复技术是提高代码健壮性的最有效的...

    Dabelv
  • C++函数指针简介

    (1)定义函数指针的语法形式比较复杂,常借助于typedef类型定义符来简化函数指针的定义。

    Dabelv
  • Python lambda介绍

    在学习python的过程中,lambda的语法时常会使人感到困惑,lambda是什么,为什么要使用lambda,是不是必须使用lambda?

    战神伽罗
  • 医学图像处理案例(六)——生成血管三维模型

    在前面的文章中,已经分享了人体肋骨和肺组织分割生成三维模型的例子。今天将继续分享人体脑部血管分割并生成三维模型的案例。

    用户7498388
  • python 学习笔记(1)——python中的lambda函数用法

    匿名函数lambda:是指一类无需定义标识符(函数名)的函数或子程序。 lambda 函数可以接收任意多个参数 (包括可选参数) 并且返回单个表达式的值。

    my_sunshine
  • 匿名函数

    老七Linux
  • 细说Python的lambda函数用法,建议收藏

    在Python中有两种函数,一种是def定义的函数,另一种是lambda函数,也就是大家常说的匿名函数。今天我就和大家聊聊lambda函数,在Python编程中...

    stormwen
  • Python中lambda表达式的常见用法

    非常抱歉,昨天发的代码中有一处小错误,已通过留言的方式进行了纠正,详情请见【详解Python列表推导式】 lambda表达式常用来声明匿名函数,即没有函数名字的...

    Python小屋屋主
  • Python:lambda表达式的两种应用场景

    python书写简单,功能强大, 迅速发展成为 AI ,深度学习的主要语言。介绍Python中的lambda表达式,注意到,它只是一个表达式,不是语句啊。

    double

扫码关注云+社区

领取腾讯云代金券