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

C++11中的lambda表达式是什么?我什么时候会使用一个?他们解决了什么类型的问题,在引入之前是不可能的?

一些例子和用例会很有用。

636382 次浏览

问题

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中使用其他变量。如果你想访问其他变量,你可以使用捕获子句(表达式的[]),到目前为止,它在这些示例中没有使用,例如:

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, zeta]通过引用捕获epsilon,通过值捕获zeta
  • [&]通过引用捕获lambda中使用的所有变量
  • [=]按值捕获lambda中使用的所有变量
  • [&, epsilon]通过引用捕获lambda中使用的所有变量,但通过值捕获epsilon
  • [=, &epsilon]通过值捕获lambda中使用的所有变量,但通过引用捕获epsilon

生成的operator()默认为const,这意味着默认访问捕获时捕获将为const。这意味着具有相同输入的每个调用都会产生相同的结果,但是您可以将lambda标记为#3请求生成的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只有一个返回语句,则可以省略返回类型并具有decltype(return_statement)的隐式类型。

可变

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

用例

由ISO标准定义的库在很大程度上受益于lambdas,并将可用性提高了几条,因为现在用户不必在某些可访问的范围内使用小函数来杂乱他们的代码。

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_uniqueauto lambda = [ptr = std::move(ptr)] {return *ptr;};

通用Lambdas

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

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

改进的返回类型扣除

C++14允许推导每个函数的返回类型,并且不将其限制为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);

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

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

答案

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

A:在引擎盖下,它是重载()const运算符常量的自动生成类的对象。这样的对象称为关闭并由编译器创建。这个“闭包”概念与C++11中的绑定概念很接近。但是lambda通常会生成更好的代码。通过闭包的调用允许完全内联。

问:我什么时候会使用一个?

答:为了定义“简单而小的逻辑”并要求编译器执行上一个问题的生成。你给编译器一些你想在操作符()中的表达式。所有其他的东西编译器都会生成给你。

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

A:这是某种语法糖,比如用于自定义加减操作的操作符重载而不是函数……但是它可以节省更多不需要的代码行,为一些类包装1-3行真正的逻辑等等!有些工程师认为如果行数少,出错的机会就少(我也这么认为)

用法示例

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

关于lambdas的额外内容,问题未涵盖。如果您不感兴趣,请忽略本节

1.捕获的值。您可以捕获的内容

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

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

[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.版本

4.1!!Lambda不是函数指针,也不是匿名函数,但无捕获 lambda可以隐式转换为函数指针。

p. s.

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

  2. 在C++14中,添加了名为“初始化捕获”的额外功能。它允许执行闭包数据成员的任意声明:

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

它解决了一个问题:比lambda更简单的代码,用于在构造函数中调用,该构造函数使用输出参数函数来初始化const成员

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

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

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,您可能需要为不同的bsize情况做一些事情。当然,您可以创建一个函数,但是如果您想将使用限制在灵魂用户函数的范围内怎么办?lambda的性质满足了这一要求,我将其用于这种情况。

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

#0

lambda表达式,有时也称为lambda功能或(严格地说不正确,但通俗地说)作为lambda,是定义和使用匿名函数对象的简化符号。不是用运算符()定义命名类,而是稍后制作该类的对象,最后

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

将产生下

1

1

1

1

1

0分类x-1;x-3;x-4;x-5;x-6;x-7;x-33;

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

引用本书:

lambda表达式的第一个字符总是[。一个lambda介绍者可以采取各种形式:

[]:一个空的捕获列表意味着不能使用来自周围上下文的本地名称对于此类lambda表达式,数据是从参数或非局部变量。

[&]:隐式捕获引用。可以使用所有局部名称。所有局部变量都是通过引用访问。

[=]:按值隐式捕获。所有本地可以使用名称。所有名称都引用局部变量的副本在lambda表达式的调用点获取。

【捕获列表】:显式捕获;捕获列表是要通过引用或值捕获的局部变量的名称列表(即存储在对象中)。名称以&开头的变量由引用。其他变量由值捕获。捕获列表可以还包含this和后跟…的名称作为元素。

[&,捕获列表]:通过引用隐式捕获列表中未提及名称的所有局部变量。捕获列表可以包含此内容。列出的名称不能在&之前。在捕获列表按值捕获。

[=,捕获列表]:隐式按值捕获列表中未提及名称的所有局部变量。捕获列表不能包含这一点。列出的名称必须在&之前。捕获列表中命名的变量是通过引用捕获的。

请注意,前面有&的本地名称总是被捕获引用和未由&预先指定的本地名称总是由&捕获值。仅通过引用捕获允许修改调用环境。

Additional

Lambda expression格式

在此处输入图片描述

其他参考资料:

c++中的lambda被视为“随时随地可用的函数”。是的,它的字面意思是在旅途中,你定义它;使用它;随着父函数范围的完成,lambda函数消失了。

c++在c++11中引入了它,每个人都开始在任何可能的地方使用它。示例和lambda可以在这里找到https://en.cppreference.com/w/cpp/language/lambda

我将描述哪些是不存在的,但对每个c++程序员来说是必不可少的

lambda并不意味着在任何地方都可以使用,每个函数都不能用lambda代替。与普通函数相比,它也不是最快的。因为它有一些开销需要由lambda处理。

在某些情况下,它肯定会有助于减少行数。它基本上可以用于代码部分,它在同一函数中被调用一次或多次,并且该代码段在其他任何地方都不需要,因此您可以为其创建独立函数。

下面是lambda的基本示例以及背景中发生的事情。

用户代码:

int main(){// Lambda & autoint member=10;auto endGame = [=](int a, int b){ return a+b+member;};
endGame(4,5);
return 0;
}

编译如何扩展它:

int main(){int member = 10;
class __lambda_6_18{int member;public:inline /*constexpr */ int operator()(int a, int b) const{return a + b + member;}
public: __lambda_6_18(int _member): member{_member}{}
};
__lambda_6_18 endGame = __lambda_6_18{member};endGame.operator()(4, 5);
return 0;}

正如你所看到的,当你使用它时,它会增加什么样的开销。所以在任何地方使用它们都不是个好主意。它可以在适用的地方使用。

C++11引入了lambda表达式,允许我们编写一个内联函数,可用于简短的代码片段

[ capture clause ] (parameters) -> return-type{definition of method}

通常,lambda表达式中的返回类型由编译器自己计算,我们不需要明确指定可以忽略->返回类型部分,但在一些复杂的情况下,如在条件语句中,编译器无法确定返回类型,我们需要指定它。

// C++ program to demonstrate lambda expression in C++#include <bits/stdc++.h>using namespace std;
// Function to print vectorvoid printVector(vector<int> v){// lambda expression to print vectorfor_each(v.begin(), v.end(), [](int i){std::cout << i << " ";});cout << endl;}
int main(){vector<int> v {4, 1, 3, 5, 2, 3, 1, 7};
printVector(v);
// below snippet find first number greater than 4// find_if searches for an element for which// function(third argument) returns truevector<int>:: iterator p = find_if(v.begin(), v.end(), [](int i){return i > 4;});cout << "First number greater than 4 is : " << *p << endl;

// function to sort vector, lambda expression is for sorting in// non-decreasing order Compiler can make out return type as// bool, but shown here just for explanationsort(v.begin(), v.end(), [](const int& a, const int& b) -> bool{return a > b;});
printVector(v);
// function to count numbers greater than or equal to 5int count_5 = count_if(v.begin(), v.end(), [](int a){return (a >= 5);});cout << "The number of elements greater than or equal to 5 is : "<< count_5 << endl;
// function for removing duplicate element (after sorting all// duplicate comes together)p = unique(v.begin(), v.end(), [](int a, int b){return a == b;});
// resizing vector to make size equal to total different numberv.resize(distance(v.begin(), p));printVector(v);
// accumulate function accumulate the container on the basis of// function provided as third argumentint arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};int f = accumulate(arr, arr + 10, 1, [](int i, int j){return i * j;});
cout << "Factorial of 10 is : " << f << endl;
//   We can also access function by storing this into variableauto square = [](int i){return i * i;};
cout << "Square of 5 is : " << square(5) << endl;}

产出

4 1 3 5 2 3 1 7First number greater than 4 is : 57 5 4 3 3 2 1 1The number of elements greater than or equal to 5 is : 27 5 4 3 2 1Factorial of 10 is : 3628800Square of 5 is : 25

通过从封闭范围访问变量,lambda表达式可以比普通函数拥有更多的功能。我们可以通过三种方式从封闭范围中捕获外部变量:

  • 引用捕获
  • 按价值捕获
  • 双方捕获(混合捕获)

用于捕获变量的语法:

  • [&]:通过引用捕获所有外部变量
  • [=]:按值捕获所有外部变量
  • [a,&b]:按值捕获a,按引用捕获b带有空捕获子句[]的lambda只能访问它的局部变量。
    #include <bits/stdc++.h>using namespace std;    
int main(){vector<int> v1 = {3, 1, 7, 9};vector<int> v2 = {10, 2, 7, 16, 9};    
// access v1 and v2 by referenceauto pushinto = [&] (int m){v1.push_back(m);v2.push_back(m);};    
// it pushes 20 in both v1 and v2pushinto(20);    
// access v1 by copy[v1](){for (auto p = v1.begin(); p != v1.end(); p++){cout << *p << " ";}};    
int N = 5;    
// below snippet find first number greater than N// [N] denotes, can access only N by valuevector<int>:: iterator p = find_if(v1.begin(), v1.end(), [N](int i){return i > N;});    
cout << "First number greater than 5 is : " << *p << endl;    
// function to count numbers greater than or equal to N// [=] denotes, can access all variableint count_N = count_if(v1.begin(), v1.end(), [=](int a){return (a >= N);});    
cout << "The number of elements greater than or equal to 5 is : "<< count_N << endl;}

输出:

   First number greater than 5 is : 7The number of elements greater than or equal to 5 is : 3