C++11——lambda表达式

来源:互联网 发布:黑马程序员贴吧 编辑:程序博客网 时间:2024/06/08 06:28

1.简介

定义:
C++11新增了很多特性,lambda表达式(lambda expression)就是其中之一,很多语言都提供了 lambda 表达式,如 Python,Java ,C#等。本质上, lambda 表达式就是一个可调用的代码单元[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();}//按长度由短至长排列wordsstd::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]

调用方式:
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.outIn 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++11lishan:c_study apple$ ./a.out 123In lambda: 234  //可以修改123             //注意这里的值,并没有改变

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

$ g++ main.cpp -std=c++11main.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]

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函数及其用法

1 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 b2驾照扣了1分怎么办 红绿色盲驾考怎么办 驾驶证忘带被交警查了怎么办 车子被扣45分怎么办 驾照被扣在外省交警支队怎么办 从渭南把驾照转到西安怎么办 a1a2驾驶证扣3分怎么办 车辆累计扣12分怎么办 驾照扣了40分怎么办 驾驶证扣了30分怎么办 b2驾照逾期未审怎么办 c1实习期扣6分怎么办 车子累计扣30分怎么办 实习期间扣满12分怎么办 新手驾照扣6分怎么办 a2驾驶证逾期未审验怎么办 c1驾照扣了6分怎么办 b1驾照被扣12分怎么办 b2驾驶本扣分了怎么办 驾驶本扣9分后怎么办 b1照扣12分怎么办 b2扣了15分怎么办 b2有扣分未年审怎么办 b2驾驶证扣4分怎么办 b2驾驶证扣10分怎么办 刚发驾驶证照片太丑想换怎么办! 考驾照时户口变更怎么办 驾照年审色盲未过怎么办 考驾驶证互联网注册号码怎么办 驾驶证体检视力不过关怎么办 六年驾照满了怎么办 驾照扣了40多分怎么办 一个驾照扣24分怎么办 南昌电动车牌照丢了怎么办 上海餐饮工作人员怎么办健康证 房产过户没有遗嘱公证怎么办 在外地被扣12分怎么办 公务员体检视力不过关怎么办弱视 身份证被盗用注册公司怎么办 驾照分卖了12分怎么办 一年12分扣完了怎么办