什么是C++11中的lambda表达式?
什么是C++11中的lambda表达式?
在C++11中,什么是lambda表达式?我何时使用它?他们解决了哪类问题,这些问题在引入它们之前是不可能解决的?
一些示例和用例会很有用。
lambda函数是什么?
C++中的lambda函数的概念源于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只有一个返回语句,则可以省略返回类型,隐式类型为decltype(return_statement)
。
可变的
如果一个lambda被标记为可变(例如[]() 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(10); // See below for std::make_unique auto lambda = [ptr = std::move(ptr)] {return *ptr;};
通用Lambda
现在lambda可以是通用的(如果T
在周围范围内的某个地方是类型模板参数,则auto
相当于T
):
auto lambda = [](auto x, auto y) {return x + y;};
改进的返回类型推导
C++14允许为每个函数推导返回类型,并且不限制于形式为return expression;
的函数。这也适用于lambda表达式。
问题
C++ 包含有用的通用函数,比如 std::for_each
和 std::transform
,它们非常方便。不幸的是,它们使用起来也可能非常繁琐,特别是如果你想应用的函数对象对于特定函数是唯一的。
#include#include namespace { struct f { void operator()(int) { // do something } }; } void func(std::vector & v) { f f; std::for_each(v.begin(), v.end(), f); }
如果你只想在特定的地方使用 f
一次,那么为了完成一些微不足道的事情,编写一个完整的类可能过于浪费。
在 C++03 中,你可能会尝试编写以下代码以使函数对象局部化:
void func2(std::vector& 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& v) { std::for_each(v.begin(), v.end(), [](int) { /* do something here*/ }); }
Lambda 函数只是匿名函数对象的语法糖。
返回类型
在简单的情况下,lambda 的返回类型可以被编译器自动推断,例如:
void func4(std::vector& v) { std::transform(v.begin(), v.end(), v.begin(), [](double d) { return d < 0.00001 ? 0 : d; } ); }
然而,当你开始编写更复杂的 lambda 时,你很快就会遇到编译器无法推断返回类型的情况,例如:
void func4(std::vector& 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& 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& 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 标记为 可变的,以请求不产生 const
的 operator()
。