C++11 lambda(一)

转自:http://www.cnblogs.com/haippy/archive/2013/05/31/3111560.html

很多语言都提供了 lambda 表达式,如 Python,Java 8。lambda 表达式可以方便地构造匿名函数,如果你的代码里面存在大量的小函数,而这些函数一般只被调用一次,那么不妨将他们重构成 lambda 表达式。

C++11 的 lambda 表达式规范如下:

[ capture ] ( params ) mutable exception attribute -> ret { body } (1)  
[ capture ] ( params ) -> ret { body } (2)  
[ capture ] ( params ) { body } (3)  
[ capture ] { body } (4)  

其中

  • (1) 是完整的 lambda 表达式形式,
  • (2) const 类型的 lambda 表达式,该类型的表达式不能改捕获("capture")列表中的值。
  • (3)省略了返回值类型的 lambda 表达式,但是该 lambda 表达式的返回类型可以按照下列规则推演出来:
    • 如果 lambda 代码块中包含了 return 语句,则该 lambda 表达式的返回类型由 return 语句的返回类型确定。
    • 如果没有 return 语句,则类似 void f(...) 函数。
  • 省略了参数列表,类似于无参函数 f()。

mutable 修饰符说明 lambda 表达式体内的代码可以修改被捕获的变量,并且可以访问被捕获对象的 non-const 方法。

exception 说明 lambda 表达式是否抛出异常(noexcept),以及抛出何种异常,类似于void f() throw(X, Y)。

attribute 用来声明属性。

另外,capture 指定了在可见域范围内 lambda 表达式的代码内可见得外部变量的列表,具体解释如下:

  • [a,&b] a变量以值的方式呗捕获,b以引用的方式被捕获。
  • [this] 以值的方式捕获 this 指针。
  • [&] 以引用的方式捕获所有的外部自动变量。
  • [=] 以值的方式捕获所有的外部自动变量。
  • [] 不捕获外部的任何变量。

此外,params 指定 lambda 表达式的参数。

 

转自:http://www.devbean.net/2012/05/cpp11-lambda/

为什么说 lambda 表达式如此激动人心呢?举一个例子。标准 C++ 库中有一个常用算法的库,其中提供了很多算法函数,比如 sort() 和 find()。这些函数通常需要提供一个“谓词函数 predicate function”。所谓谓词函数,就是进行一个操作用的临时函数。比如 find() 需要一个谓词,用于查找元素满足的条件;能够满足谓词函数的元素才会被查找出来。这样的谓词函数,使用临时的匿名函数,既可以减少函数数量,又会让代码变得清晰易读。

下面来看一个例子:

1 #include <algorithm>
2 #include <cmath>
3 
4 void abssort(float *x, unsigned N)
5 {
6   std::sort(x,
7             x + N,
8             [](float a, float b) { return std::abs(a) < std::abs(b); });
9 }

从上面的例子来看,尽管支持 lambda 表达式,但 C++ 的语法看起来却很“神奇”。lambda 表达式使用一对方括号作为开始的标识,类似于声明一个函数,只不过这个函数没有名字,也就是一个匿名函数。这个匿名函数接受两个参数,a和b;其返回值是一个 bool 类型的值,注意,返回值是自动推断的,不需要显式声明,不过这是有条件的!条件就是,lambda 表达式的语句只有一个 return。函数的作用是比较 a、b 的绝对值的大小。然后,在此例中,这个 lambda 表达式作为一个闭包被传递给 std::sort() 函数。

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

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

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

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

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

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

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

引入 lambda 表达式的前导符是一对方括号,称为 lambda 引入符(lambda-introducer)。lambda 引入符是有其自己的作用的,不仅仅是表明一个 lambda 表达式的开始那么简单。lambda 表达式可以使用与其相同范围 scope 内的变量。这个引入符的作用就是表明,其后的 lambda 表达式以何种方式使用(正式的术语是“捕获”)这些变量(这些变量能够在 lambda 表达式中被捕获,其实就是构成了一个闭包)。目前为止,我们看到的仅仅是一个空的方括号,其实,这个引入符是相当灵活的。例如:

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

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

1 float f0 = 1.0;
2 std::cout << [&](float f) { return f0 += std::abs(f); } (-3.5);
3 std::cout << '\n' << f0 << '\n';

输出值是 4.5 和 4.5。[&] 表明,lambda 表达式以传引用的方式捕获外部变量。那么,下一个例子:

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

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

1 float f0 = 1.0f;
2 float f1 = 10.0f;
3 std::cout << [=, &f0](float a) { return f0 += f1 + std::abs(a); } (-3.5);
4 std::cout << '\n' << f0 << '\n';

这个例子的输出是 14.5 和 14.5。在这个例子中,f0 通过引用被捕获,而其它变量,比如 f1 则是通过值被捕获。

下面我们来总结下所有出现的 lambda 引入符:

  • []        // 不捕获任何外部变量
  • [=]      // 以值的形式捕获所有外部变量
  • [&]      // 以引用形式捕获所有外部变量
  • [x, &y] // x 以传值形式捕获,y 以引用形式捕获
  • [=, &z]// z 以引用形式捕获,其余变量以传值形式捕获
  • [&, x]  // x 以值的形式捕获,其余变量以引用形式捕获

另外有一点需要注意。对于[=]或[&]的形式,lambda 表达式可以直接使用 this 指针。但是,对于[]的形式,如果要使用 this 指针,必须显式传入:

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

 

posted @ 2015-06-01 15:20  felove  阅读(155)  评论(0)    收藏  举报