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

参考文章: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 则默认开启。c++

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

下面来看一个例子:express

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

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

咱们从另一个例子开始:函数

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

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

C++ 11 的这种语法,其实就是匿名函数声明以后立刻调用(不然的话,若是这个匿名函数既不调用,又不做为闭包传递给其它函数,那么这个匿名函数就没有什么用处)。若是你以为奇怪,那么来看看 JavaScript 的这种写法:spa

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

C++ 11 的写法彻底相似 JavaScript 的语法。指针

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

  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: 
  3: std::cout << [=](float f) { return f0 + std::abs(f); } (-3.5);

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

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

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

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

上面的例子是,全部的变量要么传值,要么传引用。那么,是否是有混合机制呢?固然也有!好比下面的例子:

  1: float f0 = 1.0f;
  2: 
  3: float f1 = 10.0f;
  4: 
  5: std::cout << [=, &f0](float a) { return f0 += f1 + std::abs(a); } (-3.5);
  6: 
  7: 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(); }();

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

相关文章
相关标签/搜索