前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C++11——lambda表达式

C++11——lambda表达式

作者头像
恋喵大鲤鱼
发布2018-08-03 11:17:40
1.3K0
发布2018-08-03 11:17:40
举报
文章被收录于专栏:C/C++基础C/C++基础C/C++基础

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016年11月23日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.简介
  • 2.lambda的捕获列表
  • 3.lambda的常见用法
  • 参考文献
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档