在 C++ 11 中,lambda 表达式是一种在调用处或函数参数处,定义匿名函数对象的简便方法。它与普通函数不同的是,lambda必须使用尾置返回来指定返回类型。
-
Capture 子句
Capture 子句可以访问周边范围内的变量,它指定要捕获的变量以及是通过值还是引用进行捕获。有与号 (&) 前缀的变量通过引用访问,没有该前缀的变量通过值访问。
可以使用默认捕获模式来指示如何捕获 lambda 中引用的任何外部变量:[&] 表示通过引用捕获所有引用变量,而 [=] 表示通过值捕获它们。可以使用默认捕获模式,然后为特定变量显式指定相反的模式。
例如,如果 lambda 体通过访问外部引用变量 total 和 值变量 factor,则以下 capture 子句等效:
[&total, factor]
[factor, &total]
[&, factor]
[factor, &]
[=, &total]
[&total, =]
如果 capture 子句不包含任何标识符,则不接受任何外部变量。
如果 capture 子句使用默认捕获模式并捕获所有引用变量&,则该 capture 字句中不能再包含任何形似&标识符 的形式。
如果 capture 子句使用默认捕获模式并捕获所有值变量 =,则该 capture 子句不能再包含任何形似 = 标识符 的形式。
变量 或 this 在 capture 子句中出现的次数最多只能是一次。 以下代码片段给出了一些示例。
struct S { void f(int i); };
void S::f(int i) {
[&, i]{}; // OK
[&, &i]{}; // ERROR: i preceded by & when & is the default
[=, this]{}; // ERROR: this when = is the default
[i, i]{}; // ERROR: i repeated
}
capture 后跟省略号是包扩展,如以下示例中所示:
template<class... Args>
void f(Args... args) {
auto x = [args...] { return g(args...); };
x();
}
要在类方法的正文中使用 lambda 表达式,请将 this 指针传递给 Capture 子句,以提供对封闭类的方法和数据成员的访问权限。
引用捕获存在生存期依赖,而值捕获却没有生存期依赖。 当 lambda 以异步方式运行时,这一点尤其重要。如果在异步 lambda 中通过引用捕获值变量,当lambda 运行时该值变量将很可能已经消失,从而导致运行时访问冲突。
2 . 参数列表(可选)(也称 lambda 声明符)
除了捕获变量,lambda 还可接受输入参数。 参数列表(在标准语法中称为 lambda 声明符)是可选的,它在大多数方面类似于函数的参数列表。
int y = [] (int first, int second)
{
return first + second;
};
由于参数列表是可选的,因此如果不传递参数且表达式中不包含关键字mutable 、异常处理和返回值,则可以省略空括号。
3. 可变规范(可选)
通常,lambda 的函数调用操作是 const-by-value的,不会生成可变的数据成员,但使用 mutable 关键字可以改变这个情况。使用 mutable 关键字可以在lambda 表达式的主体内修改值变量。
4. 异常规范(可选)
你可以使用 throw() 来定义异常,这表示表达式不会抛出任何异常。 与普通函数一样,如果 lambda 表达式定义了 throw() 异常并在lambda体内抛出异常,那Visual C++ 编译器将生成警告C4297。
5. 尾随返回类型(可选)
lambda 表达式自动推导返回类型,而无需使用 auto 关键字,除非你指定了返回类型。 返回类型必须跟在参数列表的后面,你必须在返回类型前面使用关键字 -> 。
如果 lambda 体仅包含一个返回语句或其表达式不返回值,则可以省略 lambda 表达式的返回类型部分。 如果 lambda 体包含单个返回语句,编译器将从返回表达式的类型推导返回类型。否则,编译器会将返回类型推导为 void。 下面的代码示例片段说明了这一原则。
auto x1 = [](int i){ return i; }; // OK: return type is int
auto x2 = []{ return{ 1, 2 }; }; // ERROR: return type is void, deducing
// return type from braced-init-list is not valid
6. “lambda 体”
lambda 表达式的 lambda 体可包含普通方法或函数的主体可包含的任何内容。
下面是测试实例:
class CPPTest
{
private:
int m_Data;
public:
CPPTest () : m_Data(20) {}
void LambdaTest()
{
vector<int> vctTemp;
vctTemp.push_back(1);
vctTemp.push_back(2);
// 空的Lambda表达式
{
[](){}(); []{}();
}
// 无函数对象参数,输出:1 2
{
for_each(vctTemp.begin(), vctTemp.end(), [](int v){ cout << v << endl; });
}
// 操作符重载函数参数按引用传递,输出:2 3
{
for_each(vctTemp.begin(), vctTemp.end(), [](int &v){ v++; });
for_each(vctTemp.begin(), vctTemp.end(), [](int v){ cout << v << endl; });
}
// 以值方式传递局部变量a,输出:11 13 10
{
int a = 10;
for_each(vctTemp.begin(), vctTemp.end(), [a](int v)mutable{ cout << v+a << endl; a++; });
cout << a << endl;
}
// 以引用方式传递局部变量a,输出:11 13 12
{
int a = 10;
for_each(vctTemp.begin(), vctTemp.end(), [&a](int v){ cout << v+a << endl; a++; });
cout << a << endl;
}
// 以引用方式传递作用域内所有可见的局部变量(包括this),输出:11 13 12
{
int a = 10;
for_each(vctTemp.begin(), vctTemp.end(), [&](int v)mutable{ cout << v+a << endl; a++; });
cout << a << endl;
}
// 以值方式传递作用域内所有可见的局部变量(包括this),输出:11 12
{
int a = 10;
for_each(vctTemp.begin(), vctTemp.end(), [=](int v){ cout << v+a << endl; });
}
// 除b按引用传递外,其他均按值传递,输出:11 12 17
{
int a = 10;
int b = 15;
for_each(vctTemp.begin(), vctTemp.end(), [=, &b](int v){ cout << v+a << endl; b++; });
cout << b << endl;
}
// 传递this,输出:21 22
{
for_each(vctTemp.begin(), vctTemp.end(), [this](int v){ cout << v+m_nData << endl; });
}
}
};