什么是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引入lambda允许你编写一个内联的匿名函数来替换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]通过引用捕获[&epsilon]
  • [&]通过引用捕获lambda中使用的所有变量
  • [=]通过值捕获lambda中使用的所有变量
  • [&, epsilon]用[& [&, 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的外部应该可用的内容以及如何实现。 它可以是:

  • 值:[x]
  • 参考文献[&x]
  • 目前在参考范围内的任何变量[&]
  • 与3相同,但按值[=]
  • 你可以用逗号分隔的列表[x, &y]混合上述任何一个。

    参数列表

    参数列表与其他C ++函数中的参数列表相同。

    功能体

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

    返回类型扣除

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

    易变的

    如果一个lambda被标记为mutable(例如[]() mutable { } ),那么它允许突变已被值捕获的值。

    用例

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

    C ++ 14

    在C ++中,14个lambda通过各种提议得到了扩展。

    初始化的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;};
    

    通用Lambdas

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

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

    改进的退货类型扣除

    C ++ 14允许为每个函数推导返回类型,并且不会将其限制为形式return expression;函数return expression; 。 这也延伸到lambda。


    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在眼睛上也更容易一些(与匿名内联lambdas相比)。 注意:不要忘记关闭大括号后的分号。

    auto algorithm = [&]( double x, double m, double b ) -> double
       {
       return m*x+b;
       };
    
    int a=algorithm(1,2,3), b=algorithm(4,5,6);
    

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

    链接地址: http://www.djcxy.com/p/6883.html

    上一篇: What is a lambda expression in C++11?

    下一篇: What are move semantics?