提问人:Nawaz 提问时间:10/2/2011 最后编辑:Jan SchultkeNawaz 更新时间:9/27/2023 访问量:658546
什么是 lambda 表达式,何时应该使用?
What is a lambda expression, and when should I use one?
答:
问题
C++ 包含有用的泛型函数,如 和 ,这非常方便。不幸的是,它们使用起来也相当麻烦,特别是如果您要应用的函子对于特定函数是唯一的。std::for_each
std::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);
}
但是,这是不允许的,不能传递给 C++03 中的模板函数。f
新的解决方案
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;
}
});
}
要解决此问题,您可以使用以下命令显式指定 lambda 函数的返回类型:-> T
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
默认情况下,生成的文件意味着捕获将在您默认访问它们时进行。这样做的效果是,具有相同输入的每个调用将产生相同的结果,但是您可以将 lambda 标记为可变
,以请求生成的 不是 .operator()
const
const
operator()
const
评论
const
std::function<double(int, bool)> f = [](int a, bool b) -> double { ... };
auto f = [](int a, bool b) -> double { ... };
#include <functional>
)
return d < 0.00001 ? 0 : d;
0.0 : d
std::array
什么是 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 被标记为可变(例如 ),则允许它改变已由 value 捕获的值。[]() 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;};
通用 Lambda
Lambda 现在可以是通用的(如果是周围作用域中某处的类型模板参数,则相当于这里):auto
T
T
auto lambda = [](auto x, auto y) {return x + y;};
改进的退货类型扣除
C++14 允许为每个函数推导返回类型,并且不将其限制为 形式的函数。这也扩展到 lambdas。return expression;
评论
r = &x; r += 2;
any variable currently in scope
x
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 表达式还允许您创建命名嵌套函数,这是避免重复逻辑的便捷方法。在将一个非平凡的函数作为参数传递给另一个函数时,使用命名 lambda 也往往更容易一些(与匿名内联 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);
如果后续分析显示函数对象的初始化开销很大,则可以选择将其重写为普通函数。
评论
if
if ([i]{ for (char j : i) if (!isspace(j)) return false ; return true ; }()) // i is all whitespace
i
std::string
[](){}();
main() {{{{((([](){{}}())));}}}}
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');
});
}
评论
apply
答案
问:C++11 中的 lambda 表达式是什么?
答:在后台,它是具有重载 operator() const 的自动生成类的对象。这样的对象称为闭包,由编译器创建。 这个“闭包”概念与 C++11 中的绑定概念很接近。 但 lambda 通常会生成更好的代码。通过闭包的调用允许完全内联。
问:我什么时候会使用?
答:定义“简单和小的逻辑”,并要求编译器从上一个问题执行生成。你给编译器一些你想在 operator() 中存在的表达式。编译器将为您生成所有其他内容。
问:他们解决了哪一类问题,而这些问题在引入之前是不可能的?
答:它是某种语法糖,如运算符重载,而不是用于自定义添加、subrtact 操作的函数......但它节省了更多不需要的代码行,将 1-3 行真实逻辑包装到某些类,等等!一些工程师认为,如果行数越少,那么出错的机会就越小(我也这么认为)
使用示例
auto x = [=](int arg1){printf("%i", arg1); };
void(*f)(int) = x;
f(1);
x(1);
关于lambda的额外内容,不在问题中。如果您不感兴趣,请忽略此部分
1. 捕获的值。您可以捕获的内容
1.1. 您可以引用具有静态存储持续时间的变量(以 lambda 为单位)。他们都被俘虏了。
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. 转换
4.1 !!Lambda 不是函数指针,也不是匿名函数,但无捕获 lambda 可以隐式转换为函数指针。
附言
有关 lambda 语法的更多信息,请参见编程语言 C++ 的工作草案 #337, 2012-01-16, 5.1.2。Lambda 表达式,第 88 页
在 C++14 中,添加了名为“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);};
评论
[&,=Param2](int arg1){}
[&,Param2](int arg1){}
它解决了一个问题:对于使用输出参数函数初始化 const 成员的构造函数调用,代码比 lambda 更简单
您可以初始化类的 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,您可能需要针对不同的情况执行一些操作。当然,你可以创建一个函数,但是如果你想将使用限制在灵魂用户函数的范围内怎么办?lambda 的性质满足了这一要求,我将其用于这种情况。bsize
评论
C++ Bjarne Stroustrup的作者在他的书第11章(ISBN-13:978-0321563842)中给出了最好的解释之一:lambda expression
***The C++ Programming Language***
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 表达式:这是优化代码的方式,也是使其更具吸引力的一种方式。正如斯特劳斯图普所悲伤的那样:
有效的优化方法
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 排序x - 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]:通过引用隐式捕获列表中未提及名称的所有局部变量。捕获列表可以包含此内容。列出的名称前面不能以 & 开头。在 捕获列表按值捕获。
• [=, capture-list]:按值隐式捕获列表中未提及名称的所有局部变量。捕获列表不能包含此内容。列出的名称必须以 & 开头。捕获列表中命名的可变变量通过引用捕获。
请注意,以 & 开头的本地名称始终由 引用和不以 & 开头的本地名称始终由 价值。只有通过引用捕获才允许修改 调用环境。
Additional
Lambda expression
格式
其他参考资料:
- 维基
- open-std.org,第 5.1.2 章
评论
for (int x : v) { if (x % m == 0) os << x << '\n';}
c++ 中的 lambda 被视为“移动可用函数”。 是的,它实际上是在旅途中,你定义它;使用它;随着父函数作用域的完成,lambda 函数也消失了。
C++ 在 C++ 11 中引入了它,每个人都开始在各个可能的地方使用它。 示例和 lambda 可以在此处找到 https://en.cppreference.com/w/cpp/language/lambda
我将描述哪些不存在,但对于每个 C++ 程序员来说,哪些是必不可少的
Lambda 并不打算在任何地方使用,并且每个函数都不能替换为 lambda。与正常功能相比,它也不是最快的。因为它有一些开销,需要由 Lambda 处理。
在某些情况下,它肯定会有助于减少行数。 它基本上可以用于代码部分,该代码部分在同一函数中被调用一次或多次,并且该代码段在其他任何地方都不需要,因此您可以为其创建独立函数。
以下是 lambda 的基本示例以及后台发生的情况。
用户代码:
int main()
{
// Lambda & auto
int 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 vector
void printVector(vector<int> v)
{
// lambda expression to print vector
for_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 true
vector<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 explanation
sort(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 5
int 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 number
v.resize(distance(v.begin(), p));
printVector(v);
// accumulate function accumulate the container on the basis of
// function provided as third argument
int 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 variable
auto square = [](int i)
{
return i * i;
};
cout << "Square of 5 is : " << square(5) << endl;
}
输出
4 1 3 5 2 3 1 7
First number greater than 4 is : 5
7 5 4 3 3 2 1 1
The number of elements greater than or equal to 5 is : 2
7 5 4 3 2 1
Factorial of 10 is : 3628800
Square 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 reference
auto pushinto = [&] (int m)
{
v1.push_back(m);
v2.push_back(m);
};
// it pushes 20 in both v1 and v2
pushinto(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 value
vector<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 variable
int 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 : 7
The number of elements greater than or equal to 5 is : 3
评论