Lambda 表达式是C++11 引入的核心特性,本质是一个匿名函数对象(闭包),允许你在代码中就地定义临时函数,无需单独命名函数或类。它极大简化了 STL 算法、回调函数、并发编程等场景的代码编写,是现代 C++ 最常用的语法之一。

一、核心语法结构

Lambda 表达式的完整语法如下:

[捕获列表] (参数列表) mutable noexcept -> 返回值类型 { 函数体 }

其中只有捕获列表和函数体是必须的,其余部分可根据情况省略。

1. 各部分详解

部分 说明 可省略情况
[捕获列表] 定义 lambda 能访问的外部变量及访问方式 不可省略(空捕获写[]
(参数列表) 与普通函数的参数列表一致 无参数时可省略(C++11 需写(),C++14 及以后可完全省略)
mutable 允许修改值捕获的变量(默认 lambda 是 const 函数) 不需要修改值捕获变量时省略
noexcept 声明 lambda 不会抛出异常 可能抛出异常时省略
-> 返回值类型 显式指定返回值类型 编译器可自动推导时省略(如单条 return 语句)
{函数体} 函数的具体实现逻辑 不可省略

2. 最简与完整示例

// 最简 lambda:无参数、无返回值、空捕获
auto hello = [] { std::cout << "Hello Lambda!" << std::endl; };
hello(); // 调用方式与普通函数一致

// 完整 lambda:有参数、有返回值、值捕获、mutable
int x = 10;
auto add = [x](int a, int b) mutable -> int {
    x++; // 有 mutable 才能修改值捕获的 x
    return a + b + x;
};
std::cout << add(1, 2) << std::endl; // 输出 14(1+2+11)
std::cout << x << std::endl; // 输出 10(值捕获不影响原变量)

二、捕获列表:最关键的部分

捕获列表决定了 lambda 可以访问哪些外部变量,以及访问方式。这是 lambda 与普通函数最大的区别 —— 它能捕获上下文环境中的变量

1. 捕获方式分类

捕获语法 含义 注意事项
[] 空捕获:不捕获任何外部变量 可转换为普通函数指针
[var] 值捕获:复制变量 var 的值到 lambda 内部 默认不可修改,需加 mutable
[&var] 引用捕获:引用外部变量 var 需保证 lambda 执行时 var 未被销毁(避免悬垂引用)
[=] 隐式值捕获:自动捕获所有用到的外部变量,均按值捕获 捕获所有用到的变量,代码更简洁
[&] 隐式引用捕获:自动捕获所有用到的外部变量,均按引用捕获 风险较高,易出现悬垂引用
[=, &var] 混合捕获:默认值捕获,var 按引用捕获 不能重复捕获同一变量
[&, var] 混合捕获:默认引用捕获,var 按值捕获 同上

2. 捕获示例

int a = 1, b = 2, c = 3;

// 显式值捕获 a 和 b
auto f1 = [a, b] { return a + b; };
std::cout << f1() << std::endl; // 3

// 显式引用捕获 c
auto f2 = [&c] { c++; };
f2();
std::cout << c << std::endl; // 4

// 隐式值捕获所有用到的变量(a 和 c)
auto f3 = [=] { return a + c; };
std::cout << f3() << std::endl; // 5(a=1, c=4)

// 混合捕获:默认值捕获,b 按引用捕获
auto f4 = [=, &b] { b = a + c; };
f4();
std::cout << b << std::endl; // 5(1+4)

3. 重要注意事项

  • 值捕获的是变量的副本:lambda 内部修改值捕获的变量不会影响原变量(除非加 mutable)。
  • 引用捕获的是变量本身:lambda 内部修改会直接影响原变量,但必须保证 lambda 执行时变量仍存在。
  • 悬垂引用风险:如果 lambda 被延迟执行(如放入线程池、回调队列),引用捕获的局部变量可能已被销毁,导致未定义行为。
  • 不能捕获全局变量和静态变量:它们本身在全局作用域,可直接访问,无需捕获。

三、返回值类型推导

C++11 及以后,lambda 的返回值类型可以由编译器自动推导,但有以下规则:

  1. 如果函数体只有单条 return 语句,编译器会自动推导返回值类型。
  2. 如果函数体有多条 return 语句且返回值类型不一致,编译器无法推导,必须显式指定返回值类型。
// 自动推导为 int
auto f1 = [](int x) { return x * 2; };

// 多条 return 语句,类型一致,C++14 及以后可自动推导
auto f2 = [](int x) {
    if (x > 0) return x;
    else return -x;
};

// 多条 return 语句,类型不一致,必须显式指定返回值类型
auto f3 = [](int x) -> double {
    if (x > 0) return x; // int 转换为 double
    else return 0.5; // double
};

四、Lambda 的本质:闭包类型

编译器会为每个 lambda 表达式生成一个唯一的匿名类(闭包类型),该类重载了operator(),因此 lambda 本质是一个函数对象。

例如,下面的 lambda:

int x = 10;
auto add = [x](int a, int b) { return a + b + x; };

编译器会生成类似这样的代码:

class __lambda_1 {
private:
    int x; // 捕获的变量作为成员变量
public:
    __lambda_1(int x) : x(x) {}
    // 重载 operator(),默认是 const 成员函数
    int operator()(int a, int b) const {
        return a + b + x;
    }
};
__lambda_1 add(x);

这解释了为什么:

  • 值捕获的变量默认不可修改:因为operator()是 const 成员函数。
  • mutable后可以修改:mutable会去掉operator()的 const 修饰。
  • 每个 lambda 类型唯一:即使两个 lambda 代码完全相同,它们的类型也不同。

更多推荐