C++11 新特性:Lambda 表达式

豆子 2012年5月15日 C++ 10条评论

参考文章:https://blogs.oracle.com/pcarlini/entry/c_1x_tidbits_lambda_expressions

或许,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() 需要一个谓词,用于查找元素满足的条件;能够满足谓词函数的元素才会被查找出来。这样的谓词函数,使用临时的匿名函数,既可以减少函数数量,又会让代码变得清晰易读。

下面来看一个例子:

C++
#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); });
}

1
2
3
4
5
6
7
8
9
#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 表达式使用一对方括号作为开始的标识,类似于声明一个函数,只不过这个函数没有名字,也就是一个匿名函数。这个匿名函数接受两个参数,ab;其返回值是一个 bool 类型的值,注意,返回值是自动推断的,不需要显式声明,不过这是有条件的!条件就是,lambda 表达式的语句只有一个 return。函数的作用是比较 a、b 的绝对值的大小。然后,在此例中,这个 lambda 表达式作为一个闭包被传递给 std::sort() 函数。

下面,我们来详细解释下这个神奇的语法到底代表着什么。

我们从另外一个例子开始:

C++
std::cout << [](float f) { return std::abs(f); } (-3.5);
1
std::cout << [](float f) { return std::abs(f); } (-3.5);

输出值是什么?3.5!注意,这是一个函数对象(由 lambda 表达式生成),其实参是 -3.5,返回值是参数的绝对值。lambda 表达式的返回值类型是语言自动推断的,因为std::abs()的返回值就是 float。注意,前面我们也提到了,只有当 lambda 表达式中的语句“足够简单”,才能自动推断返回值类型。

C++ 11 的这种语法,其实就是匿名函数声明之后马上调用(否则的话,如果这个匿名函数既不调用,又不作为闭包传递给其它函数,那么这个匿名函数就没有什么用处)。如果你觉得奇怪,那么来看看 JavaScript 的这种写法:

JavaScript
function() {} ();

function(a) {} (-3.5);

1
2
3
function() {} ();
 
function(a) {} (-3.5);

C++ 11 的写法完全类似 JavaScript 的语法。

如果我不想让 lambda 表达式自动推断类型,或者是 lambda 表达式的内容很复杂,不能自动推断怎么办?比如,std::abs(float)的返回值是 float,我想把它强制转型为 int。那么,此时,我们就必须显式指定 lambda 表达式返回值的类型:

C++
std::cout << [](float f) -> int { return std::abs(f); } (-3.5);
1
std::cout << [](float f) -> int { return std::abs(f); } (-3.5);

这个语句与前面的不同之处在于,lambda 表达式的返回时不是 float 而是 int。也就是说,上面语句的输出值是 3。返回值类型的概念同普通的函数返回值类型是完全一样的。

当我们想引用一个 lambda 表达式时,我们可以使用auto关键字,例如:

C++
auto lambda = [] () -> int { return val * 100; };
1
auto lambda = [] () -> int { return val * 100; };

auto关键字实际会将 lambda 表达式转换成一种类似于std::function的内部类型(但并不是std::function类型,虽然与std::function“兼容”)。所以,我们也可以这么写:

C++
std::function<int()> lambda = [] () -> int { return val * 100; };
1
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 表达式中被捕获,其实就是构成了一个闭包)。目前为止,我们看到的仅仅是一个空的方括号,其实,这个引入符是相当灵活的。例如:

C++
float f0 = 1.0;
std::cout << [=](float f) { return f0 + std::abs(f); } (-3.5);
1
2
float f0 = 1.0;
std::cout << [=](float f) { return f0 + std::abs(f); } (-3.5);

其输出值是 4.5。[=] 意味着,lambda 表达式以传值的形式捕获同范围内的变量。另外一个例子:

C++
float f0 = 1.0;
std::cout << [&](float f) { return f0 += std::abs(f); } (-3.5);
std::cout << '\n' << f0 << '\n';
1
2
3
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 表达式以传引用的方式捕获外部变量。那么,下一个例子:

C++
float f0 = 1.0;
std::cout << [=](float f) mutable { return f0 += std::abs(f); } (-3.5);
std::cout << '\n' << f0 << '\n';
1
2
3
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 依然不会被修改。

上面的例子是,所有的变量要么传值,要么传引用。那么,是不是有混合机制呢?当然也有!比如下面的例子:

C++
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';
1
2
3
4
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 指针,必须显式传入:

C++
[this]() { this->someFunc(); }();
1
[this]() { this->someFunc(); }();

至此,我们已经大致了解了 C++ 11 提供的 lambda 表达式的概念。建议通过结合 lambda 表达式与std::sort()std::for_each()这样的标准函数来尝试使用一下吧!

lambda函数、lambda表达式的更多相关文章

  1. 深入理解Lambda函数及其用法

    Lambda函数又称匿名函数,匿名函数就是没有名字的函数,函数没有名字也行?当然可以啦.有些函数如果只是临时一用,而且它的业务逻辑也很简单时,就没必要非给它取个名字不可. 先来看个简单lambda函数 ...

  2. Python 特殊函数解析(lambda 函数,map 函数,filter 函数,reduce 函数)

    写在之前 今天给大家介绍几个比较特殊的函数,他们具有函数式编程的特点,有人将它们视为 Python 可进行 「函数式编程」 的见证,至于什么是函数式编程,不是本篇文章的重点,感兴趣的可以去了解一下.老 ...

  3. Python——lambda函数

    Lambda 函数又称匿名函数,匿名函数就是没有名字的函数,函数没有名字也行?当然可以啦.有些函数如果只是临时一用,而且它的业务逻辑也很简单时,就没必要非给它取个名字不可. 好比电影里面的群众演员,往 ...

  4. Python 拓展之特殊函数(lambda 函数,map 函数,filter 函数,reduce 函数)

    写在之前 今天给大家介绍几个比较特殊的函数,他们具有函数式编程的特点,有人将它们视为 Python 可进行 "函数式编程" 的见证,至于什么是函数式编程,不是本篇文章的重点,感兴趣 ...

  5. Lambda函数及其用法

    Lambda函数又称匿名函数,匿名函数就是没有名字的函数,函数没有名字也行? 当然可以啦.有些函数如果只是临时一用,而且它的业务逻辑也很简单时,就没必要非给它取个名字不可. 先来看个简单lambda函 ...

  6. 匿名函数lambda和map函数

    一.map函数,实现迭代操作 map(f1,x) f1为函数的名称(不加括号),x为map的参数,示例如下: def add(a): return a+10 print map(add,[1,2,3] ...

  7. Lambda函数到底是个什么

    1 什么是Lambda函数 lambda函数是指简单的代码片段,通常认为是不值得命名的函数,它不能重复使用,能方便程序员使用,增强代码可读性,降低代码出错概率. [ 捕获列表 ] (参数) -> ...

  8. lambda函数小结

    C++中的lambda函数 lambda函数是函数式编程中的概念,由C++11引入,成为现代C++中重要的特性. 所谓lambda函数就是匿名函数,语法结构: [capture list] (para ...

  9. python学习笔记12(函数三): 参数类型、递归、lambda函数

    一.函数参数的类型 之前我们接触到的那种函数参数定义和传递方式叫做位置参数,即参数是通过位置进行匹配的,从左到右,依次进行匹配,这个对参数的位置和个数都有严格的要求.而在Python中还有一种是通过参 ...

  10. 匿名函数 lambda表达式(lambda expression)

    阅读g2log时,发现有两行代码居然看不懂. 1. auto bg_call =  [this, log_directory]() {return pimpl_->backgroundChang ...

随机推荐

  1. POST和GET区别

    1. get是从服务器上获取数据,post是向服务器传送数据.2. get是把参数数据队列加到提交表单的ACTION属性所指的URL中,值和表单内各个字段一一对应,在URL中可以看到.post是通过H ...

  2. Makefile内嵌函数

    subst字符串替换函数 $(subst <from>, <to>, <text>),把<text>中的<from>字符串替换成<to ...

  3. ubuntu下eclipse scala开发插件(Scala IDE for Eclipse)安装

    1. 环境介绍 系统:ubuntu16.04(不过和系统版本关系不大) elipse:Neon.1aRelease (4.6.1) 2. 插件介绍 Scala IDE for eclipse是elip ...

  4. 设置SecureCRT配色和解决乱码问题

    SecureCRT是一款支持SSH(SSH1和SSH2)的终端仿真程序,简单的说是Windows下登录UNIX或Linux服务器主机的软件. 一.配色 第一步: Options => Sessi ...

  5. ANSI Common Lisp Learn

    It has been a long time that I haven't dealt with my blog. On one hand I was preparing the exams.On ...

  6. POJ3260The Fewest Coins[背包]

    The Fewest Coins Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 6299   Accepted: 1922 ...

  7. python反弹tty shell

    linux环境下进行内外渗透需要反弹shell连接内网ssh,Telnet等,就需要tty层的shell了.python代码转过来的,备用. #!/usr/bin/python import sys ...

  8. 修复垂直滑动RecyclerView嵌套水平滑动RecyclerView水平滑动不灵敏问题

    在 Android 应用中,大部分情况下都会使用一个垂直滚动的 View 来显示内容(比如 ListView.RecyclerView 等).但是有时候你还希望垂直滚动的View 里面的内容可以水平滚 ...

  9. 直接拿来用!最火的Android开源项目(完结篇)

    直接拿来用!最火的Android开源项目(完结篇) 2014-01-06 19:59 4785人阅读 评论(1) 收藏 举报 分类: android 高手进阶教程(100) 摘要:截至目前,在GitH ...

  10. javascript一些小问题

    1.async 类型:Boolean 默认值: true.默认设置下,所有请求均为异步请求.如果需要发送同步请求,请将此选项设置为 false. 注意,同步请求将锁住浏览器,用户其它操作必须等待请求完 ...