Lambda 表达式

匿名函数 —— Lambda 表达式

1. 什么是匿名函数(Lambda 表达式)?

在 C++中,匿名函数通常指的是没有名称的函数,也叫做“lambda 表达式”。匿名函数可以在代码中定义并立即使用,常见于临时的、一次性使用的操作。对于类和对象来说,匿名函数常常用于定义类内部的行为或操作。Lambda 表达式的基本形式如下:

1
[捕获列表](参数列表) -> 返回类型 { 函数体 }
  • 捕获列表:决定了 lambda 函数是否可以访问外部变量。
  • 参数列表:与普通函数一样,lambda 函数也可以接收参数。
  • 返回类型:指定 lambda 函数返回的类型(如果不指定,编译器会自动推断)。
  • 函数体:包含函数的实际操作。

示例:

1
2
3
4
auto add = [](int a, int b) -> int {
return a + b;
};
std::cout << add(3, 4); // 输出 7

这里,[] 是捕获列表,表示 lambda 函数没有捕获外部变量,int a, int b 是参数列表,-> int 表示返回类型,return a + b; 是函数体。

2. 参数列表 (...) —— 和普通函数的参数完全一致

() 里的参数就是传给 Lambda 的输入,和普通函数的参数规则 完全一样

  • 传值、传引用(&)、传指针、自定义类型、甚至函数指针 / 类对象,都和普通函数一样。
  • 目的也一样:想修改外部变量本身(不拷贝副本)就传引用(&);想高效传递大对象也用引用;普通小变量传值即可。
  1. 传值(和普通函数一样,内部修改不影响外部):

    1
    auto add = [](int a, int b) { return a + b; };
  2. 传引用(内部修改会影响外部,速度快,不拷贝):

    1
    2
    3
    4
    5
    auto increment = [](int& x) { x++; };  // 注意参数是 int&
    // 测试:
    int x = 5;
    increment(x); // x 变成 6
    std::cout << x << std::endl; // 输出 6
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    #include <iostream>
    using namespace std;

    int main()
    {
    int x = 10, y = 20;

    auto lambda = [x, &y]() {
    cout << "x = " << x << ", y = " << y << endl;
    };

    x = 50; // x 变了,lambda 内部不会受影响
    y = 60; // y 变了,lambda 内部会看到最新的 y 值

    lambda(); // 输出 x = 10, y = 60

    return 0;
    }
  3. 传递一些特殊类型(指针/类对象等):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    #include <iostream>
    #include <string>

    struct Person
    {
    std::string name;
    int age;
    };

    int main()
    {
    // 1. 传指针(和普通指针用法一样)
    auto print_ptr = [](Person* p) {
    std::cout << p->name << " " << p->age << std::endl;
    };

    // 2. 传自定义类对象(可以传值或引用)
    auto get_age = [](const Person& p) { return p.age; }; // 传 const 引用更高效

    Person p = { "Alice", 20 };
    print_ptr(&p); // 输出 Alice 20
    std::cout << get_age(p) << std::endl; // 输出 20
    return 0;
    }

简单说:() 里的参数怎么写,完全参照普通函数,不用额外记新规则。在上面的示例中,相信大家也发现了:代码中并没有明确指明返回类型,这是为什么呢?

3. 为什么可以省略 -> 返回类型

日常使用中,90% 以上的 Lambda 表达式都可以省略 -> 返回类型,这也是大家更常用简化写法的原因。

C++11 及以上标准中,Lambda 表达式的返回类型可以 自动推导,规则如下:当函数体中只有一条 return 语句时,编译器会根据返回值的类型自动推断 Lambda 的返回类型。此时,-> 返回类型 可以完全省略。 所以就有了:

1
2
3
4
5
// 省略返回类型,编译器自动推导为 int
auto add = [](int a, int b) {
return a + b; // 只有一条 return,返回 int,推导成功。功能完全一致,且更简洁,因此更常用。
};
std::cout << add(3, 4); // 仍输出 7

什么时候必须显式写 -> 返回类型

当 Lambda 函数体中存在 多个 return 语句,且返回值类型可能不一致时,必须显式指定返回类型。

1
2
3
4
5
6
7
8
9
10
11
// 错误:多个 return 语句,类型可能不同,编译器无法自动推导
auto func = [](bool flag) {
if (flag) return 1; // 返回 int
else return 3.14; // 返回 double
};

// 正确:显式指定返回类型为 double
auto func = [](bool flag) -> double {
if (flag) return 1; // 自动转换为 double
else return 3.14;
};

4. 捕获列表 [] —— 控制 Lambda 能否访问外部变量,以及怎么访问

这是 Lambda 最特殊的部分。[] 里的内容决定了 Lambda 能否使用 定义它的作用域中的变量(比如 main 函数里的局部变量),以及是 “拷贝用” 还是 “直接操作原变量”。

1. 常见捕获方式

捕获列表含义通俗理解
[]不捕获任何外部变量Lambda 里用不了外面的变量
[x]传值 捕获 x拷贝一份 x 到 Lambda 里,内部改的是副本,不影响外面的 x
[&x]传引用 捕获 x直接用外面的 x,内部修改会影响外部(速度快,不拷贝)
[=]传值 捕获 所有 用到的外部变量只要 Lambda 里用了某个外部变量,就自动拷贝一份(省心但可能低效)
[&]传引用 捕获 所有 用到的外部变量只要 Lambda 里用了某个外部变量,就直接用原变量(高效,适合修改外部变量)
[=, &x]大部分变量传值,但 x 传引用混合模式:默认传值,单独指定 x 传引用
[&, x]大部分变量传引用,但 x 传值混合模式:默认传引用,单独指定 x 传值

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>

int main()
{
int a = 10, b = 20;

// 1. [=]:传值捕获所有用到的变量(a 和 b)
auto func1 = [=]() {
std::cout << "func1: " << a + b << std::endl; // 这里用的是 a 和 b 的副本,修改不影响外部,输出 30
// a++; // 错误!传值捕获的变量默认是 const,不能改(想改可以加 mutable,后面说)
};

// 2. [&]:传引用捕获所有用到的变量(a 和 b)
auto func2 = [&]() {
a++; // 直接改外部的 a,此时 a = 11
b++; // 直接改外部的 b,此时 b = 21
std::cout << "func2: " << a + b << std::endl; // 输出 32(11+21)
};

// 3. [x = 5]:C++14 起支持初始化捕获(临时变量,和外部无关)
auto func3 = [x = 5]() {
std::cout << "func3: " << x << std::endl; // 输出 5(这个 x 和外面的 a/b 无关)
};

func1();
func2();
func3();
std::cout << "外部 a=" << a << ", b=" << b << std::endl;// 输出 a = 11, b = 21

return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>

class MyClass
{
public:
void process()
{
int x = 10, y = 20;

// 按值捕获 x,按引用捕获 y
auto lambda1 = [x, &y]() {
std::cout << "x: " << x << ", y: " << y << std::endl;
y = 30; // 修改 y(引用捕获)
};

lambda1(); // 输出:x: 10, y: 20
std::cout << "lambda1 调用后, y: " << y << std::endl; // 输出:y: 30

// 重新使用 lambda,捕获 x 和 y,x 和 y 都按值捕获
auto lambda2 = [=]() {
std::cout << "x: " << x << ", y: " << y << std::endl;
};

lambda2(); // 输出:x: 10, y: 30
}
};

int main()
{
MyClass obj;
obj.process();
return 0;
}

2. mutable 关键字

1
2
3
4
5
6
7
int x = 10;
auto func = [x]() mutable { // 加了 mutable
x++; // 可以修改副本了
std::cout << x << std::endl; // 输出 11
};
func();
std::cout << x << std::endl; // 外部 x 还是 10(因为改的是副本)

5. 什么时候用 Lambda?

简单说:临时需要一个小函数,不想费劲定义普通函数时,比如:

1
2
3
4
5
6
7
8
9
10
#include <vector>
#include <algorithm>

int main()
{
std::vector<int> v = {3, 1, 4, 1, 5};
// 用 Lambda 作为排序的比较函数(按从大到小排)
std::sort(v.begin(), v.end(), [](int a, int b) { return a > b; });
return 0;
}