1. 匿名函数概念2. Lambda 表达式的表示3. Lambda 表达式各部分3.1 Capture 子句3.1.1 引用捕获3.1.2 值捕获3.1.3 不捕获3.1.4 捕获方式总结3.2 参数列表3.3 可变规范3.4 异常规范3.5 返回类型3.6 函数体4. 嵌套 Lambda 表达式
在计算机编程中,匿名函数(英语:anonymous function)是指一类无需定义标识符(函数名)的函数或子程序,普遍存在于多种编程语言中。
在 C++11 和更高的版本中,lambda 表达式通常称为 lambda —— 是一种在调用它或作为参数传递给函数时定义匿名函数对象(闭包)的简便方法。Lambda 通常用于封装传递给算法或异步方法的少量代码。
注:** 本文只讨论 C++11 中的 lambda特性。
ISO C++ 标准展示了作为第三个参数传递给 std::sort()
函数的简单 lambda:
#include <algorithm>
#include <cmath>
void abssort(float* x, unsigned n) {
std::sort(x, x + n,
// Lambda expression begins
[](float a, float b) {
return (std::abs(a) < std::abs(b));
} // end of lambda expression
);
}
lambda 表达式的完整语法如下:
[captures] (params) specifiers(可选) exception -> ret { body }
下图通过实例展示了 lambda 的组成部分:
01_constituent_part_of_lambda
解释:
constexpr
且为可平凡复制构造。auto
为参数类型,则该 lambda 为泛型 lambda 。 (C++14 起)mutable
:允许 body 修改以复制捕获的参数,及调用其非 const 成员函数constexpr
:显式指定函数调用运算符为 constexpr 函数。此指定符不存在时,若函数调用运算符恰好满足所有 constexpr 函数要求,则它也会是 constexpr
(C++17 起)consteval
:指定函数调用运算符为立即函数。不能同时使用 consteval
和 constexpr
。(C++20 起)operator()
提供异常规定或 noexcept 子句。Lambda 表达式是纯右值表达式,其类型是独有的无名非联合非聚合类类型,被称为闭包类型,它声明于含有该 lambda 表达式的最小块作用域、类作用域或命名空间作用域。
Lambda 以 capture 子句开头,指定哪些变量被捕获,以及是通过值还是引用捕获。Lambda 通过在最前面的方括号 []
来明确指明其内部可以访问的外部变量,这一过程也称为 Lambda 表达式“捕获”了外部变量。
使用引用捕获一个外部变量,只需要在捕获列表变量前面加引用说明符 & 即可,如果捕获列表只有一个 引用说明符但没有变量名称,则表示可以引用访问所有其可以访问到的变量。
示例 3.1.1:
void CaptureByRef()
{
int total = 20;
auto func = [&]() {
cout << "The total num of sutdents is: " << total << endl;
cout << "There comes a new student. " << endl;
total++;
};
func();
cout << "Now, the total number of sutdents is: " << total << endl;
}
上述示例在 func
匿名函数中对 total
采用引用访问,并在该函数中对 total
进行加一操作,输出如下:
The total num of sutdents is: 20
There comes a new student.
Now, the total number of sutdents is: 21
使用值捕获一个外部变量,只需要在捕获列表变量前面加一个等号 = 即可,如果捕获列表只有一个等号但没有变量名称,则表示可以使用值捕获的方式访问所有其可以访问到的变量。如果我们仅将上述示例的引用访问改为值访问,会怎样?
示例 3.1.2:
void CaptureByValue()
{
int total = 20;
auto func = [=]() {
cout << "The total num of sutdents is: " << total << endl;
cout << "There comes a new student. " << endl;
total++;
};
func();
cout << "Now, the total number of sutdents is: " << total << endl;
}
Visual Studio 会提示如下错误,也就是值传递的变量不可以被修改
,所以需要将total++;
删掉或移出 lambda 函数。
02_capture_by_value_error
空 capture 子句 []
指示 lambda 表达式的主体不访问封闭范围内的变量。
如果将上述代码中代表值传递的=
去掉,就是空 capture 子句:
示例 3.1.3:
void CaptureDefault()
{
int total = 20;
auto func = []() {
cout << "The total num of sutdents is: " << total << endl;
};
func();
cout << "Now, the total number of sutdents is: " << total << endl;
}
此代码会报如下错误封闭函数局部变量不能在lambda体内引用,除非其位于捕获列表中
。
03_capture_default_error1
假如我们把 total
放入参数列表中,情况如何?
示例 3.1.4:
void CaptureDefault()
{
int total = 20;
auto func = [](int total) {
cout << "The total num of sutdents is: " << total << endl;
cout << "There comes a new student. " << endl;
total++;
cout << "Now, the total number of sutdents is: " << total << endl;
};
func(20);
cout << "Actually, the total number of sutdents is: " << total << endl;
}
运行结果为:
=========== CaptureDefault test =============
The total num of sutdents is: 20
There comes a new student.
Now, the total number of sutdents is: 21
Actually, the total number of sutdents is: 20
可以看到:虽然参数列表和外部的 total
同名,但是匿名函数并未改变外部 total
的值,这和全局变量与局部变量的差别类似。
通过以上示例,基本的捕获方式已经介绍完毕,我们来总结一下:
捕获方式 | 说明 |
---|---|
[] | 不捕获任何外部变量 |
[x1, x2, …] | 默认以值捕获的方式捕获指定外部变量 x1, x2, … |
[&x1, &x2, …] | 以引用捕获的方式捕获指定外部变量x1, x2, … |
[this] | 以值捕获的方式捕获this指针 |
[=] | 以值捕获的方式捕获所有外部变量 |
[&] | 以引用捕获的方式捕获所有外部变量 |
[=, &x] | 外部变量x以引用捕获方式捕获,其余变量以值捕获的方式捕获 |
[&, x] | 外部变量x以值捕获方式捕获,其余变量以引用捕获的方式捕获 |
Lambda表达式的参数和普通函数的参数类似,但是在 Lambda 表达式中传递参数还有一些限制,主要有以下几点:
虽然参数列表中不支持默认参数,但是可以通过 lambda 函数体 后面加一个小括号,在小括号中指定默认值。
void ParamList()
{
// 指定参数值
cout << "The total number of sutdents is: " << [](int total) -> int {return total; } (20) << endl;
}
执行结果为:
The total number of sutdents is: 20
通常情况下,lambda 的函数调用运算符是常量的值,但使用的 mutable
关键字就可以改变其值。它不会生成可变的数据成员。 利用可变规范,lambda 表达式的主体可以修改通过值捕获的变量。
示例3.1.2 中在 lambda 函数体内对值捕获的外部变量进行修改会提示错误,我们可以通过加入 mutable
关键字来解决。
示例 3.3.1
void MutableSpecifier()
{
int total = 20;
auto func = [=]() mutable {
cout << "The total number of sutdents is: " << total << endl;
cout << "There comes a new student. " << endl;
total++;
cout << "Now, the total number of sutdents is: " << total << endl;
};
func();
cout << "Actually, the total number of sutdents is: " << total << endl;
}
执行结果:
The total number of sutdents is: 20
There comes a new student.
Now, the total number of sutdents is: 21
Actually, the total number of sutdents is: 20
从输出结果可以看出,加上 mutable
关键字之后,在 lambda 函数体内可以对外部变量进行修改,但是其修改的有效作用域限制于函数体内,在函数体外部该变量并没有修改。
为闭包类型的 operator()
提供异常规定(https://zh.cppreference.com/w/cpp/language/except_spec)或 noexcept 子句(https://zh.cppreference.com/w/cpp/language/noexcept_spec)。
Lambda 表达式的返回类型是自动推导的。 如果不指定返回类型,可以使用 auto 关键字 。 trailing-return-type 类似于普通方法或函数的返回类型部分。 但是,返回类型必须跟在参数列表的后面,你必须在返回类型前面包含 trailing-return-type 关键字 ->
。
如果 lambda 函数体仅包含一个返回语句或其表达式不返回值,则可以省略 lambda 表达式的返回类型部分。 如果 lambda 函数体包含单个返回语句,编译器将从返回表达式的类型推导返回类型。 否则,编译器会推导返回类型为void。
Lambda 函数体可以包含普通方法或函数的主体可以包含的任何内容。 普通函数和 lambda 表达式的主体均可访问以下变量类型:
this
可以被捕获Lambda 表达式可以嵌套以实现复杂的应用场景。
示例 4.1:
void NestedLambda()
{
auto result = [](int x) {
return [](int y) {
return y * 2;
}(x)+3;
}(5);
cout << "result: " << result << endl;
}
该表达式用来计算 5*2+3
的值,其输出结果为:
result: 13
参考链接: