首页 文章

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

提问于
浏览
1268

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

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

8 回答

  • 1

    lambda函数是您在线创建的匿名函数 . 它可以像某些人所解释的那样捕获变量(例如http://www.stroustrup.com/C++11FAQ.html#lambda)但存在一些局限性 . 例如,如果有这样的回调接口,

    void apply(void (*f)(int)) {
        f(10);
        f(20);
        f(30);
    }
    

    你可以在现场编写一个函数来使用它,如下所示:

    int col=0;
    void output() {
        apply([](int data) {
            cout << data << ((++col % 10) ? ' ' : '\n');
        });
    }
    

    但你不能这样做:

    void output(int n) {
        int col=0;
        apply([&col,n](int data) {
            cout << data << ((++col % 10) ? ' ' : '\n');
        });
    }
    

    因为C 11标准的限制 . 如果你想使用捕获,你必须依赖于库和

    #include <functional>
    

    (或其他一些STL库,如算法间接获取它)然后使用std :: function而不是将普通函数作为参数传递,如下所示:

    #include <functional>
    void apply(std::function<void(int)> f) {
        f(10);
        f(20);
        f(30);
    }
    void output(int width) {
        int col;
        apply([width,&col](int data) {
            cout << data << ((++col % width) ? ' ' : '\n');
        });
    }
    
  • 753

    Lambda表达式通常用于封装算法,以便将它们传递给另一个函数 . 但是, it is possible to execute a lambda immediately upon definition

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

    在功能上等同于

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

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

    同样,你可以使用lambda表达式 initialize variables based on the result of an algorithm ...

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

    作为 a way of partitioning your program logic ,您甚至可能会发现将lambda表达式作为参数传递给另一个lambda表达式很有用...

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

    Lambda表达式还允许您创建名为nested functions,这可以是避免重复逻辑的便捷方法 . 当将非平凡函数作为参数传递给另一个函数时,使用命名的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);
    

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

  • 36

    嗯,我发现的一个实际用途是减少锅炉板代码 . 例如:

    void process_z_vec(vector<int>& vec)
    {
      auto print_2d = [](const vector<int>& board, int bsize)
      {
        for(int i = 0; i<bsize; i++)
        {
          for(int j=0; j<bsize; j++)
          {
            cout << board[bsize*i+j] << " ";
          }
          cout << "\n";
        }
      };
      // Do sth with the vec.
      print_2d(vec,x_size);
      // Do sth else with the vec.
      print_2d(vec,y_size);
      //... 
    }
    

    没有lambda,你可能需要为不同的情况做一些事情 . 当然你可以创建一个函数但是如果你想限制灵魂用户函数范围内的用法怎么办? lambda的性质满足了这个要求,我在那个案例中使用它 .

  • 2

    问题

    C包括有用的通用函数,如 std::for_eachstd::transform ,它们非常方便 . 不幸的是,它们使用起来也很麻烦,特别是如果您想要应用的functor对于特定功能是唯一的 .

    #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中的template函数 .

    新的解决方案

    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; }
                     );
    }
    

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

    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中使用其他变量 . 如果要访问其他变量,可以使用捕获子句(表达式的 [] ),这在本例中尚未使用,例如:

    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 . 这样的结果是每个具有相同输入的调用都会产生相同的结果,但是你可以mark the lambda as mutable请求生成的 operator() 不是 const .

  • 159

    它解决的一个问题是:Code simpler than lambda for a call in constructor that uses an output parameter function for initializing a const member

    您可以通过调用函数来初始化类的const成员,该函数通过将其输出作为输出参数返回来设置其值 .

  • 8

    Answers

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

    答:在引擎盖下,它是一个重载 operator() const 的自动生成类的对象 . 这种对象称为闭包,由编译器创建 . 这个'closure'概念接近来自C 11的绑定概念 . 但是lambdas通常会生成更好的代码 . 通过闭包调用允许完全内联 .

    问:我什么时候使用?

    答:定义“简单和小逻辑”并要求编译器执行上一个问题的生成 . 你给编译器一些你想要在operator()里面的表达式 . 所有其他东西编译器都会生成给你 .

    问:他们解决了哪些问题在引入之前是不可能的?

    答:这是某种语法糖,比如运算符重载而不是自定义添加,subrtact操作的函数......但它保存了更多不需要的代码行,将1-3行真实逻辑包装到某些类等等!一些工程师认为,如果线的数量较小,那么产生错误的机会就会减少(我也这么认为)

    Example of usage

    auto x = [=](int arg1){printf("%i", arg1); };
    void(*f)(int) = x;
    f(1);
    x(1);
    

    Extras about lambdas, not covered by question. Ignore this section if you're not interest

    1.捕获的 Value . 你可以捕获什么

    1.1 . 您可以在lambdas中引用具有静态存储持续时间的变量 . 他们都被抓获了 .

    1.2 . 您可以将lambda用于“按值”捕获值 . 在这种情况下,捕获的变量将被复制到函数对象(闭包) .

    [captureVar1,captureVar2](int arg1){}
    

    1.3 . 你可以捕获参考 . & - 在这种情况下意味着参考,而不是指针 .

    [&captureVar1,&captureVar2](int arg1){}
    

    1.4 . 它存在通过值或引用捕获所有非静态变量的符号

    [=](int arg1){} // capture all not-static vars by value
    
      [&](int arg1){} // capture all not-static vars by reference
    

    1.5 . 它存在通过值或通过引用捕获所有非静态变量并指定smth的符号 . 更多 . 示例:按值捕获所有非静态变量,但通过引用捕获Param2

    [=,&Param2](int arg1){}
    

    通过引用捕获所有非静态变量,但通过值捕获Param2

    [&,Param2](int arg1){}
    

    2.退货类型扣除

    2.1 . 如果lambda是一个表达式,则可以推导出Lambda返回类型 . 或者您可以明确指定它 .

    [=](int arg1)->trailing_return_type{return trailing_return_type();}
    

    如果lambda有多个表达式,则必须通过尾随返回类型指定返回类型 . 此外,类似的语法可以应用于自动函数和成员函数

    3.捕获的值 . 什么你不能捕捉

    3.1 . 您只能捕获本地变量,而不能捕获对象的成员变量 .

    4.Сonversions

    4.1 !! Lambda不是函数指针,它不是匿名函数,但 capture-less lambdas可以隐式转换为函数指针 .

    p.s.

    • 有关lambda语法信息的更多信息,请参阅编程语言C#337,2012-01-16,5.1.2的工作草案 . Lambda表达式,第88页

    • 在C14中,添加了名为“init capture”的额外功能 . 它允许对闭包数据成员进行任意声明:

    auto toFloat = [](int value) { return float(value);};
    auto interpolate = [min = toFloat(0), max = toFloat(255)](int value)->float { return (value - min) / (max - min);};
    
  • 13

    什么是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 { } ),则允许改变已经通过值捕获的值 .

    用例

    由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.
    

    和一个从维基百科中显示如何使用_439150捕获:

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

    GenericLambda表达式

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

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

    改进的返回类型扣除

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

  • 1288

    lambda expression 的最佳解释之一是C Bjarne Stroustrup 的作者在他的书 ***The C++ Programming Language*** 第11章(ISBN-13: 978-0321563842)中给出的:

    What is a lambda expression?

    lambda表达式,有时也称为lambda函数或(严格来说不正确,但通俗地说)作为lambda,是用于定义和使用匿名函数对象的简化表示法 . 我们可以使用简写,而不是使用operator()定义命名类,稍后创建该类的对象,最后调用它 .

    When would I use one?

    当我们想要将操作作为参数传递给算法时,这尤其有用 . 在图形用户界面(以及其他地方)的上下文中,这种操作通常被称为回调 .

    What class of problem do they solve that wasn't possible prior to their introduction?

    在这里,我想用lambda表达式完成的每个动作都可以在没有它们的情况下解决,但代码更多,复杂度更高 . Lambda表达式这是对代码进行优化的一种方式,也是一种使其更具吸引力的方法 . 令Stroustup感到难过:

    有效的优化方式

    Some examples

    通过lambda表达式

    void print_modulo(const vector<int>& v, ostream& os, int m) // output v[i] to os if v[i]%m==0
    {
        for_each(begin(v),end(v),
            [&os,m](int x) { 
               if (x%m==0) os << x << '\n';
             });
    }
    

    或通过功能

    class Modulo_print {
             ostream& os; // members to hold the capture list int m;
         public:
             Modulo_print(ostream& s, int mm) :os(s), m(mm) {} 
             void operator()(int x) const
               { 
                 if (x%m==0) os << x << '\n'; 
               }
    };
    

    甚至

    void print_modulo(const vector<int>& v, ostream& os, int m) 
         // output v[i] to os if v[i]%m==0
    {
        class Modulo_print {
            ostream& os; // members to hold the capture list
            int m; 
            public:
               Modulo_print (ostream& s, int mm) :os(s), m(mm) {}
               void operator()(int x) const
               { 
                   if (x%m==0) os << x << '\n';
               }
         };
         for_each(begin(v),end(v),Modulo_print{os,m}); 
    }
    

    如果你需要你可以命名 lambda expression 如下:

    void print_modulo(const vector<int>& v, ostream& os, int m)
        // output v[i] to os if v[i]%m==0
    {
          auto Modulo_print = [&os,m] (int x) { if (x%m==0) os << x << '\n'; };
          for_each(begin(v),end(v),Modulo_print);
     }
    

    或者假设另一个简单的样本

    void TestFunctions::simpleLambda() {
        bool sensitive = true;
        std::vector<int> v = std::vector<int>({1,33,3,4,5,6,7});
    
        sort(v.begin(),v.end(),
             [sensitive](int x, int y) {
                 printf("\n%i\n",  x < y);
                 return sensitive ? x < y : abs(x) < abs(y);
             });
    
    
        printf("sorted");
        for_each(v.begin(), v.end(),
                 [](int x) {
                     printf("x - %i;", x);
                 }
                 );
    }
    

    将产生下一个

    0 1 0 1 0 1 0 1 0 1 0 sortedx - 1; x - 3; x - 4; x - 5; x - 6; x - 7; x - 33;

    [] - 这是捕获列表或 lambda introducer :如果 lambdas 无法访问其本地环境,我们可以使用它 .

    从书中引用:

    lambda表达式的第一个字符始终为[ . lambda介绍者可以采用各种形式:•[]:空捕获列表 . 这意味着在lambda体中不能使用周围上下文中的本地名称 . 对于这样的lambda表达式,数据是从参数或非局部变量获得的 . •[&]:通过引用隐式捕获 . 可以使用所有本地名称 . 通过引用访问所有局部变量 . •[=]:按值隐式捕获 . 可以使用所有本地名称 . 所有名称都是指在lambda表达式的调用点处获取的局部变量的副本 . •[capture-list]:显式捕获;捕获列表是要通过引用或按值捕获(即,存储在对象中)的局部变量的名称列表 . 名称前面带有&的变量通过引用捕获 . 其他变量按值捕获 . 捕获列表也可以包含此名称,后跟名称作为元素 . •[&,capture-list]:通过引用隐式捕获列表中未提及名称的所有局部变量 . 捕获列表可以包含此内容 . 列出的名称不能以&开头 . 捕获列表中命名的变量按值捕获 . •[=,capture-list]:按值隐式捕获列表中未提及名称的所有局部变量 . 捕获列表不能包含此内容 . 列出的名称必须以&开头 . 捕获列表中命名的变量通过引用捕获 . 请注意,以&开头的本地名称始终通过引用捕获,而未由&表示的本地名称始终按值捕获 . 仅通过引用捕获允许修改调用环境中的变量 .

    Additional

    Lambda expression 格式

    enter image description here

    其他参考:

相关问题