协慌网

登录 贡献 社区

什么是 C ++ 11 中的 lambda 表达式?

什么是 C ++ 11 中的 lambda 表达式?我什么时候用?他们解决了哪些问题在引入之前是不可能的?

一些示例和用例将是有用的。

答案

问题

C ++ 包括有用的泛型函数,如std::for_eachstd::transform ,它们非常方便。不幸的是,他们也可以是相当繁琐的使用,特别是如果函子 ,你想申请是唯一的特定功能。

#include <algorithm>
#include <vector>

namespace {
  struct f {
    void operator()(int) {
      // do something
    }
  };
}

void func(std::vector<int>& v) {
  f f;
  std::for_each(v.begin(), v.end(), f);
}

如果你只使用f一次,并且在那个特定的地方,写一个全班只是做一些微不足道的事情似乎有点过分。

在 C ++ 03 中,您可能想要编写类似下面的内容,以保持函数本地:

void func2(std::vector<int>& v) {
  struct {
    void operator()(int) {
       // do something
    }
  } f;
  std::for_each(v.begin(), v.end(), f);
}

但是这是不允许的, f不能传递给 C ++ 03 中的模板函数。

新的解决方案

C ++ 11 引入了 lambdas,允许你编写一个内联的匿名函子来替换struct f 。对于小的简单示例,这可以更清晰地阅读(它将所有内容保存在一个地方)并且可能更简单地维护,例如以最简单的形式:

void func3(std::vector<int>& v) {
  std::for_each(v.begin(), v.end(), [](int) { /* do something here*/ });
}

Lambda 函数只是匿名函子的语法糖。

返回类型

在简单的情况下,lambda 的返回类型是为您推导出来的,例如:

void func4(std::vector<double>& v) {
  std::transform(v.begin(), v.end(), v.begin(),
                 [](double d) { return d < 0.00001 ? 0 : d; }
                 );
}

但是当你开始编写更复杂的 lambda 时,很快就会遇到编译器无法推断出返回类型的情况,例如:

void func4(std::vector<double>& v) {
    std::transform(v.begin(), v.end(), v.begin(),
        [](double d) {
            if (d < 0.0001) {
                return 0;
            } else {
                return d;
            }
        });
}

要解决此问题,您可以使用-> T显式指定 lambda 函数的返回类型:

void func4(std::vector<double>& v) {
    std::transform(v.begin(), v.end(), v.begin(),
        [](double d) -> double {
            if (d < 0.0001) {
                return 0;
            } else {
                return d;
            }
        });
}

“捕获” 变量

到目前为止,我们还没有使用除了传递给 lambda 之外的任何东西,但我们也可以在 lambda 中使用其他变量。如果要访问其他变量,可以使用 capture 子句(表达式的[] ),这些子句在这些示例中到目前为止尚未使用,例如:

void func5(std::vector<double>& v, const double& epsilon) {
    std::transform(v.begin(), v.end(), v.begin(),
        [epsilon](double d) -> double {
            if (d < epsilon) {
                return 0;
            } else {
                return d;
            }
        });
}

您可以通过引用和值进行捕获,您可以使用&=分别指定:

  • [&epsilon]通过引用捕获
  • [&]通过引用捕获 lambda 中使用的所有变量
  • [=]按值捕获 lambda 中使用的所有变量
  • [&, epsilon]捕获变量,如 [&],但 epsilon 值
  • [=, &epsilon]捕获与 [=] 相似的变量,但通过引用捕获 epsilon

默认情况下,生成的operator()const ,这意味着默认情况下访问时它们将是const 。这样的结果是每个具有相同输入的调用都会产生相同的结果,但是您可以将 lambda 标记为mutable以请求生成的operator()不是const

什么是 lambda 函数?

lambda 函数的 C ++ 概念起源于 lambda 演算和函数编程。 lambda 是一个未命名的函数,对于不可重用且不值得命名的短代码片段(在实际编程中,而不是理论上)很有用。

在 C ++ 中,lambda 函数定义如下

[]() { } // barebone lambda

或者尽其所能

[]() mutable -> T { } // T is the return type, still lacking throw()

[]是捕获列表, ()参数列表和{}函数体。

捕获列表

捕获列表定义了 lambda 外部应该在函数体内可用的内容以及如何使用。它可以是:

  1. 一个值:[x]
  2. 参考文献 [&x]
  3. 目前在参考范围内的任何变量 [&]
  4. 与 3 相同,但按值 [=]

您可以在逗号分隔列表[x, &y]混合上述任何内容。

参数列表

参数列表与任何其他 C ++ 函数相同。

功能体

实际调用 lambda 时将执行的代码。

退货类型扣除

如果 lambda 只有一个 return 语句,则返回类型可以省略,并且具有隐式类型decltype(return_statement)

易变的

如果 lambda 被标记为可变(例如[]() mutable { } ),则允许改变已经通过值捕获的值。

用例

ISO 标准定义的库很大程度上受益于 lambda,并提高了几个条形码的可用性,因为现在用户不必在一些可访问的范围内使用小仿函数来混淆代码。

C ++ 14

在 C ++ 14 中,lambdas 已被各种提议扩展。

初始化的 Lambda 捕获

现在可以使用=初始化捕获列表的元素。这允许重命名变量并通过移动捕获。从标准中取得的一个例子:

int x = 4;
auto y = [&r = x, x = x+1]()->int {
            r += 2;
            return x+2;
         }();  // Updates ::x to 6, and initializes y to 7.

一个从维基百科中获取,显示如何使用std::move捕获:

auto ptr = std::make_unique<int>(10); // See below for std::make_unique
auto lambda = [ptr = std::move(ptr)] {return *ptr;};

通用 Lambda

Lambdas 现在可以是通用的(如果T是周围范围内某处的类型模板参数,则auto将等效于T ):

auto lambda = [](auto x, auto y) {return x + y;};

改进的返回类型扣除

C ++ 14 允许为每个函数推导出返回类型,并且不将其限制为表单return expression;函数return expression; 。这也扩展到了 lambdas。

Lambda 表达式通常用于封装算法,以便将它们传递给另一个函数。但是, 可以在定义时立即执行 lambda

[&](){ ...your code... }(); // immediately executed lambda expression

在功能上等同于

{ ...your code... } // simple code block

这使得 lambda 表达式成为重构复杂函数的强大工具 。首先将代码段包装在 lambda 函数中,如上所示。然后可以在每个步骤之后通过中间测试逐渐执行显式参数化的过程。一旦完全参数化了代码块(如删除& ),您可以将代码移动到外部位置并使其成为正常功能。

同样,您可以使用 lambda 表达式根据算法的结果初始化变量 ...

int a = []( int b ){ int r=1; while (b>0) r*=b--; return r; }(5); // 5!

作为一种分区程序逻辑的方法 ,你甚至可能会发现将 lambda 表达式作为参数传递给另一个 lambda 表达式很有用......

[&]( std::function<void()> algorithm ) // wrapper section
   {
   ...your wrapper code...
   algorithm();
   ...your wrapper code...
   }
([&]() // algorithm section
   {
   ...your algorithm code...
   });

Lambda 表达式还允许您创建命名嵌套函数 ,这可以是避免重复逻辑的便捷方法。当将非平凡函数作为参数传递给另一个函数时,使用命名的 lambdas 在眼睛上也会更容易(与匿名内联 lambda 相比)。 注意:关闭大括号后不要忘记分号。

auto algorithm = [&]( double x, double m, double b ) -> double
   {
   return m*x+b;
   };

int a=algorithm(1,2,3), b=algorithm(4,5,6);

如果后续分析显示函数对象的显着初始化开销,您可以选择将其重写为普通函数。