【c++】c++ 11之lamba表达式
C++ lambda表达式与函数对象
lambda
表达式是C++11
中引入的一项新技术,利用lambda
表达式可以编写内嵌的匿名函数,用以替换独立函数或者函数对象,并且使代码更可读。但是从本质上来讲,lambda
表达式只是一种语法糖,因为所有其能完成的工作都可以用其它稍微复杂的代码来实现。但是它简便的语法却给C++
带来了深远的影响。如果从广义上说,lamdba
表达式产生的是函数对象。在类中,可以重载函数调用运算符()
,此时类的对象可以将具有类似函数的行为,我们称这些对象为函数对象(Function Object)或者仿函数(Functor)。相比lambda
表达式,函数对象有自己独特的优势。下面我们开始具体讲解这两项黑科技。
lambda表达式
我们先从简答的例子开始,我们定义一个可以输出字符串的lambda
表达式,表达式一般都是从方括号[]
开始,然后结束于花括号{}
,花括号里面就像定义函数那样,包含了lamdba
表达式体:
// 定义简单的lambda表达式
auto basicLambda = [] { cout << "Hello, world!" << endl; };
// 调用
basicLambda(); // 输出:Hello, world!
上面是最简单的lambda
表达式,没有参数。如果需要参数,那么就要像函数那样,放在圆括号里面,如果有返回值,返回类型要放在->
后面,即拖尾返回类型,当然你也可以忽略返回类型,lambda
会帮你自动推断出返回类型:
// 指明返回类型
auto add = [](int a, int b) -> int { return a + b; };
// 自动推断返回类型
auto multiply = [](int a, int b) { return a * b; }; int sum = add(2, 5); // 输出:7
int product = multiply(2, 5); // 输出:10
大家可能会想lambda
表达式最前面的方括号的意义何在?其实这是lambda
表达式一个很要的功能,就是闭包。这里我们先讲一下lambda
表达式的大致原理:每当你定义一个lambda
表达式后,编译器会自动生成一个匿名类(这个类当然重载了()
运算符),我们称为闭包类型(closure type)。那么在运行时,这个lambda
表达式就会返回一个匿名的闭包实例,就是一个右值。所以,我们上面的lambda
表达式的结果就是一个个闭包。闭包的一个强大之处是其可以通过传值或者引用的方式捕捉其封装作用域内的变量,前面的方括号就是用来定义捕捉模式以及变量,我们又将其称为lambda
捕捉块。看下面的例子:
int main()
{
int x = 10; auto add_x = [x](int a) { return a + x; }; // 复制捕捉x
auto multiply_x = [&x](int a) { return a * x; }; // 引用捕捉x cout << add_x(10) << " " << multiply_x(10) << endl;
// 输出:20 100
return 0;
}
lambda
捕捉块为空时,表示没有捕捉任何变量。但是上面的add_x
是以复制的形式捕捉变量x
,而multiply
是以引用的方式捕捉x
。前面讲过,lambda
表达式是产生一个闭包类,那么捕捉是回事?对于复制传值捕捉方式,类中会相应添加对应类型的非静态数据成员。在运行时,会用复制的值初始化这些成员变量,从而生成闭包。前面说过,闭包类也实现了函数调用运算符的重载,一般情况是:class ClosureType
{
public:
// ...
ReturnType operator(params) const { body };
}
这意味着lambda
表达式无法修改通过复制形式捕捉的变量,因为函数调用运算符的重载方法是const
属性的。有时候,你想改动传值方式捕获的值,那么就要使用mutable
,例子如下:
int main()
{
int x = 10; auto add_x = [x](int a) mutable { x *= 2; return a + x; }; // 复制捕捉x cout << add_x(10) << endl; // 输出 30
return 0;
}
这是为什么呢?因为你一旦将lambda
表达式标记为mutable
,那么实现的了函数调用运算符是非const属性的:
class ClosureType
{
public:
// ...
ReturnType operator(params) { body };
}
对于引用捕获方式,无论是否标记mutable
,都可以在lambda
表达式中修改捕获的值。至于闭包类中是否有对应成员,C++
标准中给出的答案是:不清楚的,看来与具体实现有关。既然说到了深处,还有一点要注意:lambda
表达式是不能被赋值的:
auto a = [] { cout << "A" << endl; };
auto b = [] { cout << "B" << endl; }; a = b; // 非法,lambda无法赋值
auto c = a; // 合法,生成一个副本
你可能会想a
与b
对应的函数类型是一致的(编译器也显示是相同类型:lambda [] void () -> void),为什么不能相互赋值呢?因为禁用了赋值操作符:
ClosureType& operator=(const ClosureType&) = delete;
但是没有禁用复制构造函数,所以你仍然可以用一个lambda
表达式去初始化另外一个lambda
表达式而产生副本。并且lambda
表达式也可以赋值给相对应的函数指针,这也使得你完全可以把lambda
表达式看成对应函数类型的指针。
闲话少说,归入正题,捕获的方式可以是引用也可以是复制,但是具体说来会有以下几种情况来捕获其所在作用域中的变量:
- []:默认不捕获任何变量;
- [=]:默认以值捕获所有变量;
- [&]:默认以引用捕获所有变量;
- [x]:仅以值捕获x,其它变量不捕获;
- [&x]:仅以引用捕获x,其它变量不捕获;
- [=, &x]:默认以值捕获所有变量,但是x是例外,通过引用捕获;
- [&, x]:默认以引用捕获所有变量,但是x是例外,通过值捕获;
- [this]:通过引用捕获当前对象(其实是复制指针);
- [*this]:通过传值方式捕获当前对象;
在上面的捕获方式中,注意最好不要使用[=]
和[&]
默认捕获所有变量。首先说默认引用捕获所有变量,你有很大可能会出现悬挂引用(Dangling references),因为引用捕获不会延长引用的变量的声明周期:
后续补充完整,链接:
https://www.jianshu.com/p/d686ad9de817
【c++】c++ 11之lamba表达式的更多相关文章
- Java8中list.sort的lamba表达式
最近写代码,需要对list集合排序,IDEA总是黄色警告: Reports calls to Collections.sort(list, comparator) which could be rep ...
- [C/C++11语法]_[0基础]_[lamba 表达式介绍]
场景 lambda 表达式在非常多语言里都有一席之地,由于它的原因,能够在函数里高速定义一个便携的函数,或者在函数參数里直接高速构造和传递. 它能够说是匿名函数对象,一般仅仅适用于某个函数内,仅仅做暂 ...
- 「C++11」Lambda 表达式
维基百科上面对于 lambda 的引入是如下描述的: 在标准 C++,特别是当使用 C++ 标准程序库算法函数诸如 sort 和 find.用户经常希望能够在算法函数调用的附近定义一个临时的述部函数( ...
- C++11之lambda表达式
lambda表达式源于函数式编程的概念,它可以就地匿名定义目标函数或函数对象,不需要额外写一个命名函数或者函数对象.lambda表达式的类型在C++11中被称为"闭包类型",也可以 ...
- 11 JSP/EL表达式/EL函数
JSP * 概述: JSP(Java Server Pages)与Java Servlet一样,是在服务器端执行的不同的是先由服务器编译部署成Servlet执行 * JSP的运行原 ...
- C++11 constexpr常量表达式
常量表达式函数 要求: 函数体内只有单一的return返回语句 例如: constexpr int data() { const int i=1; //含有除了return以外的语句 return i ...
- STL - C++ 11的Lambda表达式(上)
Lambda始自C++ 11,是一种在表达式或语句内指定函数行为的定义式. 你可以定义函数行为作为对象,以inline实参的形式传给算法作为predicate(判断式). eg: std:transf ...
- linq 和lamba表达式
一.什么是Linq(what)二.Linq的优点(why)三.Linq查询的步骤(how)四.查询基本操作五.結合實例代碼(具體聯繫用linqtosql來寫的增刪改查)一.什么是Linq(what). ...
- C++11 里lambda表达式的学习
最近看到很多关于C++11的文档,有些是我不怎么用到,所以就略过去了,但是lambda表达式还是比较常用的,其实最开始学习python的时候就觉得lambda这个比较高级,为什么C++这么弱.果然C+ ...
随机推荐
- 一种比较low的linux的hung分析
在调试一个功能的时候,发现了两种hung,以前认为的hung肯定是softlock导致的,后来才发现不一定要有lock这种结构,但是有类似于锁的功能的时候,也可能触发hung,为了避免大家走弯路,故记 ...
- C# 图像处理: 获取当前活动窗口句柄,获取窗口大小及位置
需调用API函数 需在开头引入命名空间 using System.Runtime.InteropServices; 获取当前窗口句柄:GetForegroundWindow() [DllImport( ...
- 在使用 #import <objc/message.h>时 xcode 报 :Too many arguments to function call, expected 0 , have * 解决方法
选中项目 - Project - Build Settings -
- EF AutoMaper
Mapper.CreateMap<Source,Dest>(); 该方法已弃用,使用下面这个 Mapper.Initialize(x=>x.CreateMap<Source,D ...
- MongoDB分布式集群搭建
最近在做一个关于车险的项目,由于数据量较大,实验室的Boss决定采用HBase+ES/MongoDB这两种方案,并做性能对比,本人负责MongoDB方案.为了满足海量数据的存储要求,需要搭建一个分布式 ...
- 学JS的心路历程-JS支持面向对象?(二)
昨天讲了面向对象的继承,今天我们来谈谈多态和封装吧! 多态polymorphism 抽象讲法解释,就是使用单一界面操作多种型态的物件 继承父类别,定义与父类别中相同的方法,但实作内容不同,称为复写(o ...
- 学JS的心路历程-函式(六)其余参数及预设参数
今天我们要来介绍ES6新增的其余参数及预设参数! 其余参数rest parameter …numbers可以让我们表示不确定数量的参数,并将其视为一个数组: function getVal(…numb ...
- CSS 盒子大小
盒子的宽和高 盒子的大小通过宽和高来指定. 默认情况下,盒子的大小刚好容纳其中的内容. 两个属性设置盒子的宽和高 width 设置宽 height 设置高 示例: 1 2 3 4 5 6 7 8 9 ...
- webpack 中使用 vue-router 注意
//render 会把el指定的容器中所有的内容都清空把#app也会去掉 都在c(app)其中的app组件中展示 所有router-link router-view要写在app这个组件里面 //A ...
- vuex this.$store.state.属性和mapState的属性中的一点点区别
做泰康公众号的项目时候有一个需求创建公众号的时候后台有一个社区id提供给后台展现人员和部门,在群发消息时候也要给后台一个社区id只不过获取社区的id接口和上一个不是一样的,本来在页面中写了两个sele ...