Lambda 表达式因数学中的 \(\lambda\) 演算得名, 直接对应于其中的 lambda 抽象. Lambda 表达式能够捕获作用域中的变量的无名函数对象, 我们可以将其理解为一个匿名的内联函数, 可以用来替换独立函数或者函数对象, 从而使代码更可读. 但是从本质上来讲, Lambda 表达式只是一种语法糖, 因为它能完成的工作也可以用其他复杂的 C++ 语法来实现.
下面是 Lambda 表达式的语法结构.
[capture] (parameters) mutable -> return-type {statement}
capture
: 捕获;
parameters
: 参数;
mutable
: 可变规范;
return-type
: 返回类型;
statement
: Lambda 表达式的主体;
下面是更高版本的 C++ 的有关 Lambda 表达式的语法. [1]
[capture] (parameters) lambda说明符 约束(可选) {statement}
[capture] {statement} // (C++23 前)
[capture] lambda说明符 {statement} // (C++23 起)
[capture] < 模板形参 > 约束(可选) // (C++20 起)
(parameters) lambda说明符 约束(可选) {statement} // (C++20 起)
[capture] < 模板形参 > 约束(可选) {statement} // (C++20 起 C++23 前)
[capture] < 模板形参 > 约束(可选) lambda说明符 {statement} //(C++23 起)
capture 捕获
捕获 是一个含有零或更多个捕获符的逗号分隔列表, 可以 默认捕获符 开始. 默认捕获符只有两种:
&
(以引用隐式捕获被使用的自动变量).=
(以复制隐式捕获被使用的自动变量).
当出现任一默认捕获符时, 都能隐式捕获当前对象(*this). 如果隐式捕获它, 那么会始终以引用捕获,即使默认捕获符是 =
. 当默认捕获符为 =
时, *this 的隐式捕获被弃用. (C++20 起)
当默认捕获符是 &
时, 后继的简单捕获符不能以 &
开始.
struct S2 { void f(int i); };
void S2::f(int i)
{
[&]{}; // OK:默认以引用捕获
[&, i]{}; // OK:以引用捕获,但 i 以值捕获
[&, &i] {}; // 错误:以引用捕获为默认时的以引用捕获
[&, this] {}; // OK:等价于 [&]
[&, this, i]{}; // OK:等价于 [&, i]
}
当默认捕获符是 =
时, 后继的简单捕获符必须以 &
开始, 或者为 *this (C++17 起) 或 this (C++20 起).
struct S2 { void f(int i); };
void S2::f(int i)
{
[=]{}; // OK:默认以复制捕获
[=, &i]{}; // OK:以复制捕获,但 i 以引用捕获
[=, *this]{}; // C++17 前:错误:无效语法
// C++17 起:OK:以复制捕获外围的 S2
[=, this] {}; // C++20 前:错误:= 为默认时的 this
// C++20 起:OK:同 [=]
}
任何捕获符只可以出现一次, 并且名字不能与形参相同:
struct S2 { void f(int i); };
void S2::f(int i)
{
[i, i] {}; // 错误:i 重复
[this, *this] {}; // 错误:"this" 重复(C++17)
[i] (int i) {}; // 错误:形参和捕获的名字相同
}
parameters 参数
大多数情况下类似于函数的参数列表.
C++14 中, 若参数类型是泛型, 则可以使用 auto
声明类型:
int x[] = {5, 1, 7, 6, 1, 4, 2};
std::sort(x, x + 7, [](int a, int b) { return (a > b); });
for (auto i : x) std::cout << i << " ";
这份代码将打印出 x
数组从大到小排序后的结果.
mutable 可变规范
利用可变规范, Lambda 表达式的主体可以修改通过值捕获的变量. 若使用此关键字, 则 parameters 不可省略 (即使为空).
一个例子, 使用 capture 捕获字句 中的例子, 来观察 \(a\) 的值的变化:
int a = 0;
auto func = [a]() mutable { ++a; };
此时 lambda 中的 \(a\) 的值改变为 \(1\), lambda 外的 \(a\) 保持不变.
return-type 返回类型
用于指定 Lambda 表达式的返回类型. 若没有指定返回类型, 则返回类型将被自动推断 (行为与用 auto
声明返回值的普通函数一致). 具体的, 如果函数体中没有 return
语句, 返回类型将被推导为 void
, 否则根据返回值推导. 若有多个 return
语句且返回值类型不同, 将产生编译错误.
auto lam = [](int a, int b) -> int
statement Lambda 主体
Lambda 主体可包含任何函数可包含的部分。普通函数和 Lambda 表达式主体均可访问以下变量类型:
- 从封闭范围捕获变量
- 参数
- 本地声明的变量
- 在一个
class
中声明时,若捕获this
,则可以访问该对象的成员 - 具有静态存储时间的任何变量,如全局变量
大部分资料来自 \(\texttt{OI-Wiki}\).