第6.13章: Lambda
概述
lambda 是一种直接在代码中书写小函数的简短方式。 当你需要为一个一次性用途(例如回调或小计算)快速写一个函数时, lambda 非常有用。
在本章中,你将学到:
- 如何编写 lambda
- 如何调用 lambda
- 捕获如何工作
- 如何使用 lambda 作为回调
- 如何编写递归 lambda
- 为什么看起来相同的 lambda 仍然会生成各自独立的代码
1. lambda 的基本语法
最简单的 lambda 看起来像这样:
auto f = [] {
::fast_io::io::println("Hello from a lambda!");
};
f(); // call it like a normal function
[] 部分被称为捕获列表。
2. 带参数的 lambda
lambda 可以像普通函数一样接受参数。
auto add = [](int a, int b) {
return a + b;
};
::fast_io::io::println(add(3, 4)); // prints 7
3. 捕获
lambda 可以使用外围作用域的变量。要做到这一点,你必须在 捕获列表中列出它们。
3.1 按值捕获
int x = 10;
auto f = [x] {
::fast_io::io::println(x);
};
f(); // prints 10
该 lambda 存储了 x 自己的副本。
3.2 按引用捕获
int x = 10;
auto f = [&x] {
x += 1;
};
f();
::fast_io::io::println(x); // prints 11
该 lambda 修改的是原变量。
3.3 捕获一切
int a = 1;
int b = 2;
auto f = [=] { return a + b; }; // capture all used variables by value
auto g = [&] { a += b; }; // capture all used variables by reference
4. lambda 与函数指针
没有捕获的 lambda 可以用在任何期望函数指针的地方。
int operate(int a, int b, int (*op)(int, int))
{
return op(a, b);
}
auto add = [](int a, int b) { return a + b; };
::fast_io::io::println(operate(3, 4, add)); // OK: no captures
带捕获的 lambda 不能用作函数指针。
int x = 10;
auto f = [x](int y) { return x + y; };
operate(3, 4, f); // ERROR: f has captures
5. 就地使用 lambda
你不必把 lambda 存入变量,可以立刻使用它。
::fast_io::io::println(
[](int a, int b) { return a * b; }(3, 4)
); // prints 12
这对快速的一次性操作很有用。
6. 把 lambda 用作回调
lambda 常被用作回调。例如:
void repeat(int n, void (*f)())
{
for(int i{}; i != n; ++i)
f();
}
repeat(3, [] { ::fast_io::io::println("tick"); });
这会打印三次 tick。
7. 编写递归 lambda
在 C++23 之前,lambda 只能通过一个变量来引用自身(因此必须给它一个名字,才能在函数体里引用它)。 从 C++23 起,lambda 可以用显式的 "this" 参数引用自身,于是你可以把递归 lambda 作为参数传递,或者立刻调用它。
示例:阶乘
auto fact = [](this auto& self, int n) {
if(n < 0)
::fast_io::fast_terminate();
if(n == 0)
return 1;
return n * self(n - 1); // calls itself
};
::fast_io::io::println(fact(5)); // prints 120
示例:倒计时
auto countdown = [](this auto& self, int n) {
if(n < 0)
return;
::fast_io::io::println(n);
self(n - 1);
};
countdown(5);
8. 看起来相同的 lambda 仍然各不相同
即便两个 lambda 看起来完全一样,编译器也会把每个 lambda 表达式视为独立的函数。例如:
auto f1 = [] { return 1; };
auto f2 = [] { return 1; };
auto f3 = [] { return 1; };
这三个 lambda 看起来完全相同,但它们是三个不同的函数。 编译器会为每一个生成独立的代码。
如果你在经常调用的函数里写很多 lambda,这可能会 增大程序体积。为避免不必要的代码膨胀:
- 复用同一个 lambda,而不是创建许多相同的 lambda
- 当逻辑简单且重复时,改用普通函数
- 除非必要,避免在热点循环内写 lambda
要点
- lambda 是书写小函数的一种简短方式。
- lambda 可以接受参数并返回值。
- 捕获让 lambda 可以使用外围变量。
- 无捕获的 lambda 可以用作函数指针。
- 递归 lambda 使用显式的 "this" 参数进行自引用。
- 看起来相同的 lambda 仍会生成各自的代码。
- lambda 非常适合回调与一次性操作。