c++ Lambda函数学习
或许,Lambda 表达式算得上是 C++ 11 新增特性中最激动人心的一个。这个全新的特性听起来很深奥,但却是很多其他语言早已提供(比如 C#)或者即将提供(比如 Java)的。简而言之,Lambda 表达式就是用于创建匿名函数的。GCC 4.5.x 和 Microsoft Visual Studio 早已提供了对 lambda 表达式的支持。在 GCC 4.7 中,默认是不开启 C++ 11 特性的,需要添加 -std=c++11 编译参数。而 VS2010 则默认开启。
为什么说 lambda 表达式如此激动人心呢?举一个例子。标准 C++ 库中有一个常用算法的库,其中提供了很多算法函数,比如 sort() 和 find()。这些函数通常需要提供一个“谓词函数 predicate function”。所谓谓词函数,就是进行一个操作用的临时函数。比如 find() 需要一个谓词,用于查找元素满足的条件;能够满足谓词函数的元素才会被查找出来。这样的谓词函数,使用临时的匿名函数,既可以减少函数数量,又会让代码变得清晰易读。
下面来看一个例子:
#include <algorithm>
#include <cmath>
void abssort(float *x, unsigned N)
{
std::sort(x,
x + N,
[](float a, float b) { return std::abs(a) < std::abs(b); });
}
从上面的例子来看,尽管支持 lambda 表达式,但 C++ 的语法看起来却很“神奇”。lambda 表达式使用一对方括号作为开始的标识,类似于声明一个函数,只不过这个函数没有名字,也就是一个匿名函数。这个匿名函数接受两个参数,a和b;其返回值是一个 bool 类型的值,注意,返回值是自动推断的,不需要显式声明,不过这是有条件的!条件就是,lambda 表达式的语句只有一个 return。函数的作用是比较 a、b 的绝对值的大小。然后,在此例中,这个 lambda 表达式作为一个闭包被传递给 std::sort() 函数。
下面,我们来详细解释下这个神奇的语法到底代表着什么。
我们从另外一个例子开始:
std::cout << [](float f) { return std::abs(f); } (-3.5);
输出值是什么?3.5!注意,这是一个函数对象(由 lambda 表达式生成),其实参是 -3.5,返回值是参数的绝对值。lambda 表达式的返回值类型是语言自动推断的,因为std::abs()的返回值就是 float。注意,前面我们也提到了,只有当 lambda 表达式中的语句“足够简单”,才能自动推断返回值类型。
C++ 11 的这种语法,其实就是匿名函数声明之后马上调用(否则的话,如果这个匿名函数既不调用,又不作为闭包传递给其它函数,那么这个匿名函数就没有什么用处)。如果你觉得奇怪,那么来看看 JavaScript 的这种写法:
function() {} ();
function(a) {} (-3.5);
C++ 11 的写法完全类似 JavaScript 的语法。
如果我不想让 lambda 表达式自动推断类型,或者是 lambda 表达式的内容很复杂,不能自动推断怎么办?比如,std::abs(float)的返回值是 float,我想把它强制转型为 int。那么,此时,我们就必须显式指定 lambda 表达式返回值的类型:
std::cout << [](float f) -> int { return std::abs(f); } (-3.5);
这个语句与前面的不同之处在于,lambda 表达式的返回时不是 float 而是 int。也就是说,上面语句的输出值是 3。返回值类型的概念同普通的函数返回值类型是完全一样的。
当我们想引用一个 lambda 表达式时,我们可以使用auto关键字,例如:
auto lambda = [] () -> int { return val * 100; };
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)>。
引入 lambda 表达式的前导符是一对方括号,称为 lambda 引入符(lambda-introducer)。lambda 引入符是有其自己的作用的,不仅仅是表明一个 lambda 表达式的开始那么简单。lambda 表达式可以使用与其相同范围 scope 内的变量。这个引入符的作用就是表明,其后的 lambda 表达式以何种方式使用(正式的术语是“捕获”)这些变量(这些变量能够在 lambda 表达式中被捕获,其实就是构成了一个闭包)。目前为止,我们看到的仅仅是一个空的方括号,其实,这个引入符是相当灵活的。例如:
float f0 = 1.0;
std::cout << [=](float f) { return f0 + std::abs(f); } (-3.5);
其输出值是 4.5。[=] 意味着,lambda 表达式以传值的形式捕获同范围内的变量。另外一个例子:
float f0 = 1.0;
std::cout << [&](float f) { return f0 += std::abs(f); } (-3.5);
std::cout << '\n' << f0 << '\n';
输出值是 4.5 和 4.5。[&] 表明,lambda 表达式以传引用的方式捕获外部变量。那么,下一个例子:
float f0 = 1.0;
std::cout << [=](float f) mutable { return f0 += std::abs(f); } (-3.5);
std::cout << '\n' << f0 << '\n';
这个例子很有趣。首先,[=]意味着,lambda 表达式以传值的形式捕获外部变量。C++ 11 标准说,如果以传值的形式捕获外部变量,那么,lambda 体不允许修改外部变量,对 f0 的任何修改都会引发编译错误。但是,注意,我们在 lambda 表达式前声明了mutable关键字,这就允许了 lambda 表达式体修改 f0 的值。因此,我们的例子本应报错,但是由于有 mutable 关键字,则不会报错。那么,你会觉得输出值是什么呢?答案是,4.5 和 1.0。为什么 f0 还是 1.0?因为我们是传值的,虽然在 lambda 表达式中对 f0 有了修改,但由于是传值的,外部的 f0 依然不会被修改。
上面的例子是,所有的变量要么传值,要么传引用。那么,是不是有混合机制呢?当然也有!比如下面的例子:
float f0 = 1.0f;
float f1 = 10.0f;
std::cout << [=, &f0](float a) { return f0 += f1 + std::abs(a); } (-3.5);
std::cout << '\n' << f0 << '\n';
这个例子的输出是 14.5 和 14.5。在这个例子中,f0 通过引用被捕获,而其它变量,比如 f1 则是通过值被捕获。
下面我们来总结下所有出现的 lambda 引入符:
[] // 不捕获任何外部变量
[=] // 以值的形式捕获所有外部变量
[&] // 以引用形式捕获所有外部变量
[x, &y] // x 以传值形式捕获,y 以引用形式捕获
[=, &z]// z 以引用形式捕获,其余变量以传值形式捕获
[&, x] // x 以值的形式捕获,其余变量以引用形式捕获
另外有一点需要注意。对于[=]或[&]的形式,lambda 表达式可以直接使用 this 指针。但是,对于[]的形式,如果要使用 this 指针,必须显式传入:
[this]() { this->someFunc(); }();
至此,我们已经大致了解了 C++ 11 提供的 lambda 表达式的概念。建议通过结合 lambda 表达式与std::sort()或std::for_each()这样的标准函数来尝试使用一下吧!
c++ Lambda函数学习的更多相关文章
- Python新手学习基础之函数-lambda函数
lambda函数 在Python里除了用def定义函数外,还有一种匿名函数,也就是标题所示的lambda函数,它是指一类无需定义标识符(函数名)的函数或子程序. lambda函数的使用语法如下: la ...
- python学习笔记12(函数三): 参数类型、递归、lambda函数
一.函数参数的类型 之前我们接触到的那种函数参数定义和传递方式叫做位置参数,即参数是通过位置进行匹配的,从左到右,依次进行匹配,这个对参数的位置和个数都有严格的要求.而在Python中还有一种是通过参 ...
- C++11—lambda函数
[1]lambda表达式语法定义 lambda表达式的语法定义如下: [capture] (parameters) mutable ->return-type {statement}; (1) ...
- pythone函数基础(8)内置函数学习
内置函数学习# sorted# map# filter# max# sum# round# chr# ord# dir# bool# eval# exec# zipimport mathres = m ...
- 第三课 创建函数 - 从EXCEL读取 - 导出到EXCEL - 异常值 - Lambda函数 - 切片和骰子数据
第 3 课 获取数据 - 我们的数据集将包含一个Excel文件,其中包含每天的客户数量.我们将学习如何对 excel 文件进行处理.准备数据 - 数据是有重复日期的不规则时间序列.我们将挑战数 ...
- Python基础总结之认识lambda函数、map函数、filter() 函数。第十二天开始(新手可相互督促)
今天周日,白天在学习,晚上更新一些笔记,希望对大家能更好的理解.学习python~ lambda函数,也就是大家说的匿名函数.它没有具体的名称,也可以叫做一句话函数,我觉得也不过分,大家看下代码,来体 ...
- Python分布式进程报错:pickle模块不能序列化lambda函数
今天在学习到廖老师Python教程的分布式进程时,遇到了一个错误:_pickle.PicklingError: Can't pickle <function <lambda> at ...
- C++11 Lambda函数
Lambda函数 C++11新增了lambda函数,其基本格式如下 [捕捉列表] (参数) mutable -> 返回值类型 {函数体} 说明 []是lambda的引出符,捕捉列表能够捕捉上下文 ...
- Python3中的字符串函数学习总结
这篇文章主要介绍了Python3中的字符串函数学习总结,本文讲解了格式化类方法.查找 & 替换类方法.拆分 & 组合类方法等内容,需要的朋友可以参考下. Sequence Types ...
随机推荐
- golang 之验证码api
知识一:如何返回一个json数据? 先定义一个结构体ResponseData,2个参数,并返回的是json数据,key就是json后定义的名称 type ResponseData struct { S ...
- IDE eclipse PyDev插件安装
Python安装成功后,即要配置开发环境,这里选用Eclipse, 在Eclipse中安装PyDev插件,有多种方法,这里介绍最最常用的两种. 1)使用Eclipse安装插件,打开eclipse,进入 ...
- [机器学习] Apriori算法
适用场合 Apriori算法包含两部分内容:1,发现频繁项集 2,挖掘关联规则. 通俗地解释一下,就是这个意思:1.发现哪些项目常常同时出现 2.挖掘这些常常出现的项目是否存在“如果A那么B”的关系. ...
- RecyclerView 配合 DiffUtil,好用到飞起
版权声明: 本账号发布文章均来自公众号,承香墨影(cxmyDev),版权归承香墨影所有. 每周会统一更新到这里,如果喜欢,可关注公众号获取最新文章. 未经允许,不得转载. 一.前言 DiffUtils ...
- mysql常用的hint
--mysql常用的hint -------------------2014/06/26 对于经常使用oracle的朋友可能知道,oracle的hint功能种类很多,对于优化sql语句提供了很多方法. ...
- Linux/Unix监控其他用户和信号
--Linux/Unix监控其他用户和信号 ------------------------------------------------------2013/10/27 查看有哪些用户登录 w ...
- QWebEngineView拦截Url请求设置
QWebEngineView拦截Url请求设置 QWebEngineUrlRequestInterceptor类提供了一个拦截URL请求抽象基类,可通过实现QWebEngineUrlRequestIn ...
- Mac实用操作技巧(六)
Night Shift,保护你夜间的眼睛 之前在Mac上有一款很著名的软件叫f.lux,这款软件会根据Mac的时间和地理位置,来自动切换屏幕亮度,如果进入晚上则调成暖色,白天就切换成普通颜色.现在Ma ...
- ASP.NET MVC5写.php路由匹配时的问题 ASP.NET MVC 4 在 .NET 4.0 与.NET 4.5 的專案範本差異
由于外包公司结束合作,所以考虑把其APP服务替换过来,因原后台是用php写的,在不影响员客户端使用的情况下在MVC下重写路由配置实现处理原php链接地址的请求,但实现时发现怎么也匹配不到自己写的路由, ...
- 【渗透课程】第三篇-体验http协议的应用
之前我们都了解了,访问网页时,客户端与服务端之间的请求与响应数据交互.本篇就浅谈它的应用. 利用HTTP拦截突破前段验证 比方说,我们在某个网页提交某些数据(例如留言.上传.插入xss等),发生错误( ...