第6.13章: Lambda

English

概述

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,这可能会 增大程序体积。为避免不必要的代码膨胀:

要点