在C++11及更新的版本中引入Lambda表达式的支持。在C++中可以这么理解Lambda:它是用于在C++中定义匿名函数方式。它的功能类似于C++的函数对象(仿函数),但是相较于函数对象,Lambda定义起来语法更简单。本文将对其语法以及在不同版本的C++中的支持情况做介绍。
## 语法结构
{.alignnone}
1. 参数捕获列表(必选):用于设置Lambda表达式用到的外部需要以何种方式被捕捉。
2. 参数列表(可选):此处是Lambda自身的参数列表,语法与函数的参数列表定义方式一样。
3. mutable规范(可选):用于修改按值捕获的变量的方式(默认捕获为 const)。
4. throw()异常类型(可选):用于表明是否会抛异常以及抛异常的类型。
5. -> return type(可选): 用于设置Lambda返回的数据类型(类似函数的返回值类型定义)。
6. Lambda主体(必选):等价于函数体的实现。
## 参数捕捕获列表([])
Lambda定义是以“参数捕获列表”子句([])开头的,它用于定义Lambda以何种方式捕获其所在作用域内被Lambda体用到的变量。
捕获变量的方式总体可以分以下两种(值或引用):
– [&] : 表示通过引用的方式捕获当前作用域下被Lambda体使用到的所有变量。
– [=] : 表示通过值的方式捕获当前作用域下被Lambda体使用到的所有变量。
### 参数捕获列表归纳有以下几种用法
1. 空捕获
– 语法:[]
– 含义:不捕获任何外部变量,Lambda体内部无法访问外部作用域的变量。
– 示例:
“` cpp
auto func = [] { cout << "不捕获任何变量"; }; ``` 2. 按值捕获 捕获变量的副本,lambda 内部修改不会影响外部原变量。 - 按值捕获部分变量 - 语法:[var, var1](var, var1为外部变量名,只写需要被捕获的变量名) - 含义:只有在捕获列表内的变量才会被Lambda捕获,且只有被捕获的变量才可以在Lambda体内访问。 - 示例: ``` cpp int x = 10; int y = 0; int z = 0; // 仅捕获x和y的副本,z不可以在Lambda体内被访问。 auto func = [x, y] { cout << x << y; }; ``` - 按值捕获所有变量 - 语法:[=] - 含义:按值捕获 lambda 内部使用的所有外部变量(隐含捕获,无需显式列出)。 - 示例: ``` cpp int a = 1; int b = 2; // 按值捕获 a 和 b auto sum = [=] { return a + b; }; ``` 3. 按引用捕获 捕获变量的引用,lambda 内部修改会直接影响外部原变量(需确保变量生命周期长于 lambda)。 - 按引用捕获部分变量 - 语法:[&var1, &var2](var1,var2 为外部变量名,只写需要被捕获的变量名) - 含义:只有在捕获列表内的变量才会被Lambda捕获,且只有被捕获的变量才可以在Lambda体内访问。 - 示例: ``` cpp int x = 10; // 捕获 x 的引用,修改会影响外部 auto func = [&x] { x++; }; // 外部 x 变为 11 func(); ``` - 按引用捕获所有变量 - 语法:[&] - 含义:按引用捕获Lambda内部使用的所有外部变量(隐含捕获)。 - 示例: ``` cpp int a = 1; int b = 2; // 按引用捕获 a 和 b auto increment = [&] { a++; b++; }; ``` 4. 混合捕获(按值 + 按引用) 结合 = 或 & 作为默认捕获方式,再显式指定个别变量的捕获方式(覆盖默认)。 - 默认按值捕获,个别变量按引用捕获。 - 语法:[=, &var1, &var2] - 含义:默认按值捕获所有变量,但 var1、var2 等按引用捕获。 - 示例: ``` cpp int a = 1; int b = 2; int c = 3; // a、c 按值,b 按引用 auto func = [=, &b] { a++; b++; c++; }; ``` - 默认按引用捕获,个别变量按值捕获. - 语法:[&, var1, var2] - 含义:默认按引用捕获所有变量,但 var1、var2 等按值捕获。 - 示例: ``` cpp int a = 1; int b = 2; int c = 3; // a、c 按引用,b 按值 auto func = [&, b] { a++; b++; c++; }; ``` 5. 类成员函数中的捕获(this 指针) 在类的非静态成员函数中,Lambda 可捕获当前对象的 this 指针,从而访问类的成员变量和成员函数。 - 捕获 this 指针(按引用访问对象) - 语法:[this] - 含义:通过 this 指针访问当前对象的成员(本质是按引用捕获对象,对象生命周期需注意)。 - 示例: ``` cpp class MyClass { public: void func() { // 通过 this 访问 x auto lambda = [this] { x++; }; // 类成员 x 变为 11 lambda(); } private: int x = 10; }; ``` - 按值捕获当前对象(C++17 起) - 语法:[*this] - 含义:复制当前对象的副本(而非引用),Lambda 内部操作的是副本,不影响原对象。 - 示例: ``` cpp class MyClass { public: void func() { // 捕获对象副本,x 为 10 auto lambda = [*this] { cout << x; }; x = 20; // 修改原对象 // 输出 10(副本的值未变) lambda(); } private: int x = 10; }; ``` 6. 捕获表达式(C++14 起) 允许捕获时对变量进行初始化或表达式计算,生成一个新的变量供 Lambda 内部使用(类似 “捕获并重命名”)。 - 语法:[identifier = expression] - 含义:expression 可以是外部变量、表达式或函数调用,结果存储在 identifier 中(按值捕获)。 - 示例: ``` cpp int x = 10; // 捕获 x+5 的结果,存储为 y(值为 15) auto func = [y = x + 5] { cout << y; }; ``` - 示例:也可结合引用: ``` cpp int x = 10; // 引用捕获 x,并重命名为 y auto func = [&y = x] { y++; }; ``` #### 捕获变量时需要注意以下几点 1. 捕获列表不能重复捕获同一变量(如 [x, &x] 是错误的)。 2. 默认捕获(= 或 &)与显式捕获的变量不能冲突(如 [=, x] 错误,因 = 已隐含按值捕获 x)。 3. 局部变量不能被隐式捕获(需显式列出或用默认捕获),全局变量无需捕获即可直接访问。 4. 捕获的变量生命周期需与 lambda 匹配(尤其按引用捕获时,避免变量销毁后 lambda 仍使用其引用)。 ## 参数列表 Lambda 既可以捕获变量,也可以接受输入参数。 参数列表(在标准语法中称为 Lambda 声明符)是可选的,它在大多数方面类似于函数的参数列表。 - 示例: ``` cpp auto y = [] (int first, int second) { return first + second; }; ``` 在 C++14 中,如果参数类型是泛型,则可以使用 auto 关键字作为类型说明符。 此关键字将告知编译器将函数调用运算符创建为模板。 参数列表中的每个 auto 实例等效于一个不同的类型参数。 - 示例: ``` cpp auto y = [] (auto first, auto second) { return first + second; }; ``` ## mutable 规范 通常,Lambda 的函数调用运算符是 const-by-value,但对 mutable 关键字的使用可将其取消。它不产生 mutable 数据成员。 利用 mutable 规范,Lambda 表达式的主体可以修改通过值捕获的变量。 这个特性知道有就好了,不推荐使用(后文有用法示例)。 ## 异常规范 你可以使用 noexcept 异常规范来指示 Lambda 表达式不会引发任何异常。 与普通函数一样,如果 Lambda 表达式声明 noexcept 异常规范且 Lambda 体引发异常。 - 示例: ``` cpp int main() // C4297 expected { []() noexcept { throw 5; }(); } ``` ## 返回类型 将自动推导 Lambda 表达式的返回类型。 无需使用 auto 关键字,除非指定了 trailing-return-type。 trailing-return-type 类似于普通函数或成员函数的 return-type 部分。 但是,返回类型必须跟在参数列表的后面,你必须在返回类型前面包含 trailing-return-type 关键字 ->。
如果 Lambda 体仅包含一个返回语句,则可以省略 Lambda 表达式的 return-type 部分.
或者,在表达式未返回值的情况下。 如果 lambda 体包含单个返回语句,编译器将从返回表达式的类型推导返回类型。 否则,编译器会将返回类型推导为 void。
– 示例:
“` cpp
auto x1 = [](int i){ return i; }; // OK: return int
auto x2 = []{ return{ 1, 2 }; }; // ERROR: return type is void, deducing
// return type from braced-init-list isn’t valid
“`
## Lambda 体
Lambda表达式的Lambda体是一个复合语句;它可以包含普通函数或成员函数体中允许的任何内容。
## 总结
到此关于C++中Lambda语法及用法的内容就介绍完毕了,最后以一个非常典型的例子来结束本文的内容。
“` cpp
#include
using namespace std;
int main()
{
int m = 0;
int n = 0;
[&, n] (int a) mutable { m = ++n + a; }(4);
// 可以猜测以下这里会输出什么?
cout << m << endl << n << endl;
}
```

