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);
}
但是这是不允许的, 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 函数的 C ++ 概念起源于 lambda 演算和函数编程。 lambda 是一个未命名的函数,对于不可重用且不值得命名的短代码片段(在实际编程中,而不是理论上)很有用。
在 C ++ 中,lambda 函数定义如下
[]() { } // barebone lambda
或者尽其所能
[]() mutable -> T { } // T is the return type, still lacking throw()
[]
是捕获列表, ()
参数列表和{}
函数体。
捕获列表定义了 lambda 外部应该在函数体内可用的内容以及如何使用。它可以是:
您可以在逗号分隔列表[x, &y]
混合上述任何内容。
参数列表与任何其他 C ++ 函数相同。
实际调用 lambda 时将执行的代码。
如果 lambda 只有一个 return 语句,则返回类型可以省略,并且具有隐式类型decltype(return_statement)
。
如果 lambda 被标记为可变(例如[]() mutable { }
),则允许改变已经通过值捕获的值。
ISO 标准定义的库很大程度上受益于 lambda,并提高了几个条形码的可用性,因为现在用户不必在一些可访问的范围内使用小仿函数来混淆代码。
在 C ++ 14 中,lambdas 已被各种提议扩展。
现在可以使用=
初始化捕获列表的元素。这允许重命名变量并通过移动捕获。从标准中取得的一个例子:
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 现在可以是通用的(如果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);
如果后续分析显示函数对象的显着初始化开销,您可以选择将其重写为普通函数。