语法:
[捕获](形参)约束(可选)->返回值类型(可选){函数体}
约束包括 说明符如异常说明如
mutable
throw()
捕获
捕获形式
说明 | |
---|---|
[] | 不捕获任何外部变量 |
[变量名 , …] | 默认以值的形式捕获指定的多个外部变量并以逗号分隔 |
[this] | 以值的形式捕获this指针 |
[=] | 以值的形式捕获所有外部变量(需要拷贝) |
[&] | 以引用形式捕获所有外部变量 |
[= , &x] | 变量x以引用形式捕获,其余变量以传值形式捕获 |
[& , x] | 变量x以值的形式不会,其余变量以引用形式捕获 |
捕获示例
值捕获
值捕获和函数传参中的值传递类似,被捕获的变量的值在lambda表达式创建时通过值拷贝的方式传入,故随后在外部对该变量的修改不会对lambda表达式中的值产生影响。
int a = 10, b = 100, c = 1000;
auto f = [a, b](){
cout << a << " " << b << endl;
cout << c << endl;//error 变量c没有被捕获 不能在lambda表达式中使用
};
f();//调用lambda表达式
但该方法在lambda表达式内部不能对拷贝的变量的值进行修改,即:
int a = 10, b = 100, c = 1000;
auto f = [a, b](){
a = 20;//error
cout << a << " " << b << endl;
};
f();
为了能在lambda表达式中修改捕获的值的拷贝,需要加上关键字
mutable
,实例如下:
int a = 10, b = 100, c = 1000;
auto f = [a, b]()mutable{
a = 20;
cout << a << " " << b << endl;
};
f();
/**
输出:
20 100
*/
引用捕获
如果想在lambda表达式中改变外部变量的值,我们需要把变量的引用传入,类似于函数传参中的引用传递。
int a = 10;
auto f = [&a](){
cout << "改变前: " << a << endl;
a = 30;
cout <<"改变后:" << a << endl;
};
f();
a = 40;
f();
/**
输出:
改变前: 10
改变后:30
改变前: 40
改变后:30
*/
隐式捕获
上面的值捕获和引用捕获都需要我们在捕获列表中显示列出Lambda表达式中使用的外部变量。除此之外,我们还可以让编译器根据函数体中的代码来推断需要捕获哪些变量,这种方式称之为隐式捕获。隐式捕获有两种方式,分别是[=]和[&]。[=]表示以值捕获的方式捕获所有外部变量,[&]表示以引用捕获的方式捕获所有外部变量。
隐式值捕获
int a = 10, b = 100, c = 1000;
auto f = [=]() {
a = 20;//error
cout << a << " " << b << " " << c << endl;
};
f();
/**
输出:10 100 1000
*/
想要修改捕获到的值,同样需要添加关键字
mutable
.
int a = 10, b = 100, c = 1000;
auto f = [=]()mutable {
a = 20;//correct
cout << a << " " << b << " " << c << endl;
};
f();
/**
输出:20 100 1000
*/
隐式引用捕获
int a = 10, b = 100, c = 1000;
auto f = [&](){
a = 20;//correct
cout << a << " " << b << " " << c << endl;
};
f();
/**
输出:20 100 1000
*/
混合捕获
在上述的例子中只使用了单一的形式,lambda表达式支持混合的方式捕获变量。实例:
int a = 10, b = 100, c = 1000;
auto f = [& , a](){
a = 20;//error
cout << a << " " << b << " " << c << endl;
};
f();
上述示例中,变量a以值的形式捕获其余变量以引用的形式捕获,由于没加
mutable
关键字故对a的拷贝进行修改非法。
int a = 10, b = 100, c = 1000;
auto f = [& , a]()mutable{
a = 20;//correct
cout << a << " " << b << " " << c << endl;
};
f();
/**
输出:20 100 1000
*/
参数
lambda表达式的参数与普通函数的参数几乎相同,仅有如下限制:
- 参数列表中不能有默认参数
- 不支持可变参数
- 所有参数必须有参数名
约束
说明符
mutable
,允许函数体修改各个捕获对象的拷贝
constexpr
(
C++17
起),显示指定函数调用符为
constexpr
,当函数体满足
constexpr
函数要求时,即使未显式指定,也会是
constexpr
。
异常说明
提供
throw
和
noexpect
字句
示例:
int a = 10, b = 100, c = 1000;
auto f = [&](int a, int b) mutable noexcept->int {
return a + b;
};
cout << f(a, b) + f(b, c) << endl;
/**
输出:1210
*/
返回值类型
此处可以省略,让
auto
自动推导。
泛型lambda
从
C++14
开始,可以使用
auto x
作为参数。则可以写出如下代码:
auto f = [](auto x) {
cout << x << endl;
};
f(10);
f(10.11);
f("hello world!");
//输出
10
10.11
hello world!
该特性的引入可以方便我们实现递归函数,相关示例将在后面提及。
生命周期
lambda表达式相关对象的生命周期如下:
- 全局,更外层作用域的生命周期不受影响。
- 使用值捕获的情况,先于lambda表达式函数体构造对象,后于函数体执行完析构
- 在lambda表达式函数体内的对象,在函数体执行时创建,在闭包析构函数内析构
- lambda对象的生命周期为所在作用域结束,析构的顺序为声明的逆序析构
lambda表达式的实际使用
C++ 优先队列
在
C++
优先队列中的使用参考 priority_queue优先队列
C++ 排序函数sort
#include<bits/stdc++.h>
using namespace std;
struct Point {
int x, y;
Point(int _x = 0 , int _y = 0):x(_x) , y(_y){}
};
int main() {
std::vector<Point> points;
for (int i = 0; i <= 10; ++i) {
points.push_back({ rand() % 10 , rand() % 10 });
}
for (auto [x, y] : points) cout << x << " " << y << endl;
cout << "After sort" << endl;
//写法一
auto cmp = [&](const Point& a, const Point& b) {
//按照x为第一优先级 y为第二优先级从小到大排序
return a.x < b.x || (a.x == b.x && a.y < b.y);
};
std::sort(points.begin(), points.end(), cmp);
//写法二
std::sort(points.begin(), points.end(), [&](const Point& a, const Point& b) {
//按照x为第一优先级 y为第二优先级从小到大排序
return a.x < b.x || (a.x == b.x && a.y < b.y);
});
//上述两种写法等价
for (auto [x, y] : points) cout << x << " " << y << endl;
return 0;
}
1 7
4 0
9 4
8 8
2 4
5 5
1 1
5 2
7 6
1 4
After sort
递归函数
#include<bits/stdc++.h>
#include<functional>
using namespace std;
int main() {
//该部分的一些代码来自cpp 参考手册 lambda表达式部分 链接已经在文末给出
std::cout << "模仿递归 lambda 调用:\n斐波那契数:";
auto nth_fibonacci1 = [](int n) {
std::function<int(int, int, int)> fib = [&](int a, int b, int n) {
return n ? fib(a + b, a, n - 1) : b;
};
return fib(1, 0, n);
};
for (int i{ 1 }; i != 9; ++i) { std::cout << nth_fibonacci1(i) << ", "; }
//传递 lambda 给泛型
std::cout << "\n另一种 lambda 递归方案:\n斐波那契数:";
auto nth_fibonacci2 = [](int n) {
auto fib = [](auto self, int a, int b, int n) -> int {
return n ? self(self, a + b, a, n - 1) : b;
};
return fib(fib, 1, 0, n);
};
for (int i{ 1 }; i != 9; ++i) { std::cout << nth_fibonacci2(i) << ", "; }
//对于上述第二种递归方案也可以写成如下形式
std::cout << "\n第二种 lambda 递归方案另一种形式:\n斐波那契数:";
auto nth_fibonacci = [](auto nth_fibonacci, int a, int b, int n)-> int {
return n ? nth_fibonacci(nth_fibonacci, a + b, a, n - 1) : b;
};
for (int i{ 1 }; i != 9; ++i) { std::cout << nth_fibonacci(nth_fibonacci , 1 , 0 , i) << ", "; }
return 0;
}
模仿递归 lambda 调用:
斐波那契数:1, 1, 2, 3, 5, 8, 13, 21,
另一种 lambda 递归方案:
第二种 lambda 递归方案另一种形式:
参考资料
C++ LAMBDA的演化
C++ lambda 分析
cpp 参考手册 Lambda 表达式