C11简洁之道:lambda表达式
1、 定义
lambda表达式是C++11非常重要也是很常用的特性之一,来源于函数式编程的概念,也是现代编程语言的一个特点。它有如下特点:
- 声明式编程风格:就地匿名定义目标函数或者函数,不需要额外写一个命名函数或者函数对象,以更直接的方式写程序。
- 简洁:不需要额外再写一个函数或者函数对象,避免了代码膨胀和功能分散。
- 在需要的时间和地点实现功能闭包,使程序更加灵活。
lambda表达式定义一个匿名函数,并且可以捕获一定范围内的变量,其定义如下:
[captrue] (params) opt -> ret {body};
其中,capture是捕获列表;params是参数列表;opt是函数选项;ret是返回值类型;body是函数体。
我们来做一个简单的例子,定义一个简单的封包,用来将输入的结果+1并返回。
auto f = [](int a) -> int {return a + ; };
std::cout << f() << std::endl;
lambda表达式的返回值通过返回值后置语法来定义,所以很多时候可以省略返回值类型,编译器根据return语句自动推导返回值类型。
auto f = [] (int a) {return a + ;};
但是初始化列表不能作为返回值的自动推导,需要显示给出具体的返回值类型。
auto f1 = [] (int a) {return a + ;}; //ok,return type is int
auto f2 = [] () {return {, }}; //error:无法推导出返回值类型
lambda表达式在没有参数列表的时候,参数列表可以省略。
auto f1 = [] () {return ;};
auto f2 = [] {return ;} //省略空参数表
2、 捕捉
lambda表达式可以通过捕获列表捕获一定范围内的变量:
- []不捕获任何变量;
- [&]捕获外部作用域所有变量,并作为引用在函数体使用(按引用捕获);
- [=]捕获外部作用域作用变量,并作为副本在函数体使用(按值捕获);
- [=,&foo]按值捕获外部作用域所有变量,并按引用捕获foo变量;
- [bar]按值捕获bar变量,同时不捕获其他变量;
- [this]捕获当前类中的this指针,让lambda拥有和当前类成员函数同样的访问权限,如果已经使用了&或者=,就默认添加此选项。捕获this的目的是可以在lambda中使用当前类的成员函数和成员变量。
class A
{
public:
int mi = ; void func(int x, int y)
{
auto x1 = []{return mi;}; //error,没有捕获外部变量
auto x2 = [=] {return mi + x + y;}; //ok,按值捕获所有外部变量
auto x3 = [&] {return mi + x + y;}; //ok,按引用捕获所有外部变量
auto x4 = [this] {return mi;}; //ok,捕获this指针
auto x5 = [this] {return mi + x + y;}; //error,没有捕获x,y
auto x6 = [this,x,y] {return mi + x + y;}; //ok,捕获this,x,y
auto x7 = [this] {return mi++;}; //ok,捕获this指针,并修改成员的值
}
}; int a = , b = ;
auto f1 = [] {return a;} ; //error,没有捕获外部变量
auto f2 = [&] {return a++;}; //ok,按引用捕获所有外部变量,并对a执行自加运算
auto f3 = [=] {return a;}; //ok,按值捕获所有外部变量,并返回a
auto f4 = [=] {return a++;}; //error,按值引用不能改变值
auto f5 = [a] {return a + b;}; //error,没有捕获b
auto f6 = [a, &b] {return a + (b++);}; //ok,捕获a和b的值,并对b做自加运算
auto f7 = [=, &b] {return a + (b++);}; //ok,捕获所有外部变量和b的引用,并对b做自加运算
从例子中可以看到,lambda表达式的捕获列表精细的控制表达式能够访问的外部变量,以及如何访问这些变量。需要注意的是,默认状态下的lambda表达式无法修改通过复制方式捕获的外部变量,如果希望修改这些变量,需要用引用的方式进行修改。但是按值引用的话,如果延迟调用,那么在调用该lambda表达式的时候,其捕获的变量值还是在lambda定义的时候的值。
int a = ;
auto f = [=] {return a;}; //按值捕获外部变量
a += ; //修改值
std::cout << f() << std::endl; //输出0
这个例子中,lambda表达式按值捕获了所有外部变量,在捕获的一瞬间a的值就已经被赋值在其中,之后a值修改并不会改变lambda表达中存的a的值,因此最终结果输出还是0。如果希望lambda表达式在调用的时候能即时访问外部变量,应当使用引用的方式捕获。
如果希望去修改按值捕获的外部变量,需要显示指明lambda表达式为mutable,但是被mutable修饰的lambda表达式有一个特点,就是无论有没有参数都要写明参数列表。
int a = ;
auto f1 = [=] {return a++;}; //error,不能修改按值捕获的变量
auto f2 = [=] mutable {return a++; }; //ok,mutable
lambda表达式其实是一个带有operator()的类,即仿函数,因此我们可以使用std::bind和std::function来存储和操作lambda表达式:
std::function<int(int)> f1 = [] (int a){ return a;};
std::function<int(void)> f2 = std::bind([](int a) {return a}, );
对于没有捕获任何变量的lambda表达式,还可以被转换成一个普通的函数指针:
using func_t = int(*)(int);
func_t f = [](int a){return a;};
f();
lambda表达式可以说是定义仿函数闭包的语法糖,它捕获的任何外部变量都会转换为闭包类型的成员变量。而使用成员变量的类的operator(),如果能直接转换为普通的函数指针,那lambda表达式本身的this指针会丢失,没有捕获任何外部变量的lambda表达式则不存在这个问题,所以按值捕获的外部变量无法修改。因为lambda表达式中的operator()默认是const的,一个const成员函数无法修改成员变量的值,而mutable则是取消operator()的const。
所以,没有捕获变量的lambda表达式可以直接转换为函数指针,而捕获变量的lambda表达式则不能转换为函数指针。
typedef void(*Ptr)(int *);
Ptr p1 = [](int *p) {delete p;}; //ok,没有捕获的lambda表达式可以转换为函数指针
Ptr p2 = [&](int *p){delete p;}; //error,有捕获的lambda表达式不能直接转换为函数指针,不能通过编译
3、 简洁代码
就地定义匿名函数,不再需要定义函数对象,大大简化了标准库的调用。比如我们使用for_each将vector中的偶数打印出来。
class CountEven
{
private:
int &micount; public:
CountEven(int &count) : micount(count) {} void operator()(int val)
{
if(!(val & ))
{
++micount;
}
}
}; std::vector<int> v = {, , , , , };
int count = ;
for_each(v.begin(), v.end(), CountEven(count));
std::cout << "The number:" << count << std::endl;
这样写比较繁琐,如果用lambda表达式,使用闭包概念来替换这里的仿函数。
std::vector<int> v = {, , , , , };
int count = ;
for_each(v.begin(), v.end(), [&count] (int val)
{
if(!(val & ))
{
++count;
}
});
在之前的例子中,使用std::bind组合多个函数,实现了计算集合中大于5小于10的元素个数。
using std::placeholders::_1;
auto f = std::bind(std::logical_and<bool>(),
std::bind(std::greater<int>(), std::placeholders::_1, ),
std::bind(std::less_equal<int>(), std::placeholders::_1, ));
int count = std::count_if(coll.begin(), coll.end(), f);
通过lambda表达式可以轻松的实现类似的功能:
//查找大于5小于10的元素个数
int count = std::count_if(coll.begin(), coll.end(), [](int x) {return x > && x < ;})
lambda表达式比std::bind更加灵活和简洁,如果简单的逻辑处理,用lambda表达式来代替function,提升开发效率,效果会更好。
C11简洁之道:lambda表达式的更多相关文章
- Java8之——简洁优雅的Lambda表达式
Java8发布之后,Lambda表达式,Stream等等之类的字眼边慢慢出现在我们字眼.就像是Java7出现了之后,大家看到了“钻石语法”,看到了try-with-resource等等.面对这些新东西 ...
- C11简洁之道:类型推导
1. 概述 C++11里面引入了auto和decltype关键字来实现类型推导,通过这两个关键字不仅能方便的获取复杂的类型,还能简化书写,提高编码效率. 2. auto 2.1 auto关键字的新 ...
- C11简洁之道:循环的改善
1. for循环的新用法 在C++98/03中,通过for循环对一个容器进行遍历,一般有两种方法,常规的for循环,或者使用<algorithm>中的for_each方法. for循环遍 ...
- C11简洁之道:tupe元祖
tuple元组是一个固定大小不同类型的值的集合,是泛化的std::pair.我们也可以把它当作一个通用的结构体来使用,不需要创建结构体有获取结构体特征,在某些情况可以取代结构体,使程序更简洁.直观. ...
- C11简洁之道:初始化改进
1. C++98/03初始化 我们先来总结一下C++98/03的各种不同的初始化情况: //普通数组 ] = {, , }; //POD(plain old data) struct A { int ...
- C11简洁之道:模板改进
1. 右尖括号 我们在C++98/03中使用泛型编程的时候,经常遇到“>>”被当作右移操作符,而不是模板参数的结尾.假如我们有如下代码: template <typename T& ...
- C11简洁之道:函数绑定
1. 可调用对象 在C++中,有“可调用对象”这么个概念,那么什么是调用对象呢?有哪些情况?我们来看看: 函数指针: 具有operator()成员函数的类对象(仿函数): 可以被转换为函数指针的类对 ...
- Java 8 Lambda表达式,让你的代码更简洁
Lambda表达式是Java 8一个非常重要的新特性.它像方法一样,利用很简单的语法来定义参数列表和方法体.目前Lambda表达式已经成为高级编程语言的标配,像Python,Swift等都已经支持La ...
- JDK新特性-Lambda表达式的神操作
一.Lambda表达式的介绍 Lambda表达式是 Java8 中最重要的新功能之一.使用 Lambda 表达 式可以替代只有一个抽象函数的接口实现,告别匿名内部类,代码看 起来更简洁易懂.Lambd ...
随机推荐
- 第十八次ScrumMeeting会议
第十八次Scrum Meeting 时间:2017/12/8 地点:线上+SPR咖啡馆 人员:蔡帜 王子铭 游心 解小锐 王辰昱 李金奇 杨森 陈鑫 赵晓宇 照片: 目前工作进展 名字 今日 明天的工 ...
- Android开发技巧--Application, ListView排列,格式化浮点数,string.xml占位符,动态引用图片
一. Application用途 1. Application用途 创建Application时机 : Application在启动的时候会调用Application无参的构造方法创建实例; Appl ...
- JavaScript初探系列之日期对象
时间对象是一个我们经常要用到的对象,无论是做时间输出.时间判断等操作时都与这个对象离不开.它是一个内置对象——而不是其它对象的属性,允许用户执行各种使用日期和时间的过程. 一 Date 日期对象 ...
- Ansys Workbench热流固耦合仿真配置
1.Fluent-Thermal-Structural瞬态分析 此模块连接在fluent已实现流体和固体的热流耦合,传递至thermal实际上只是将流体表面温度作为热载荷施加在固体的液体通道表面,极大 ...
- sql sever误删数据库
在sql sever 2008 r2中,我想把一个数据库添加到DATA中,结果发现被占用,我就打算解除占用后再进行复制,本来应该先是让数据库脱离,再复制,结果,我自作聪明地右键数据库,选择了删除,结果 ...
- vue 开发多页应用
vue 开发多页应用 https://www.cnblogs.com/fengyuqing/p/vue_cli_webpack.html https://segmentfault.com/a/1190 ...
- JavaScript-序列化及转义
1. for循环: while循环: 2. 条件语句: 类似于if else的功能. name='1'; switch(name){ case:'1': console.log(123); brea ...
- 【转】PowerDesigner中Table视图同时显示Code和Name
为避免图片失效,文字描述, Tools-Display Preference-->左侧Table-->右下角Advanced-->左侧树Columns-->右侧上面第一个放大镜 ...
- A表数据插入到B表(表结构不一致)
D_A 有E\F\H 3字段 D_B 有 A\B\C\D\E\ID 字段 将 D_B 个别字段插入到D_A 表 INSERT INTO D_A(E,F,H) select B,A,ID from ...
- 【题解】CF#855 G-Harry Vs Voldemort
个人感觉挺有意思的,然而被颜神D无聊惹(- ̄▽ ̄)- 这题我们可以首先试图去统计以每一个点作为 w 点所能对答案造成的贡献是多少.不难发现,当且仅当 u 和 v 都在 w 所在边双的一侧的时候不能构成 ...