C++ Lambda 表达式中的 [ ] —— 捕获列表详解
C++ Lambda 表达式中的 [] —— 捕获列表详解
一、先看全貌:Lambda 的完整语法
[捕获列表](参数列表) -> 返回类型 { 函数体 }
↑ ↑ ↑ ↑
① ② ③ ④
| 部分 | 作用 | 是否必须 |
|---|---|---|
① [] 捕获列表 |
决定 lambda 能"记住"外部哪些变量 | ✅ 必须 |
② () 参数列表 |
调用 lambda 时传入的参数 | 可省略 |
③ -> 返回类型 |
指定返回值类型 | 可省略(自动推导) |
④ {} 函数体 |
具体的代码逻辑 | ✅ 必须 |
[]就是 C++11 引入 Lambda 表达式时的专属语法,叫做"捕获列表"(Capture List)。
它是 lambda 的"身份标志"——编译器看到[]就知道"这是一个 lambda"。
二、为什么需要捕获列表?
Lambda 本质上是一个匿名函数,它定义在某个作用域内部。问题来了:
int x = 10;
int y = 20;
// 我想在 lambda 里用 x 和 y,怎么办?
auto fn = [???](int z) {
return x + y + z; // x 和 y 不是参数,是外部变量!
};
普通函数做不到这一点:
// ❌ 普通函数无法直接访问定义它时周围的局部变量
int x = 10;
void fn(int z) {
std::cout << x + z; // 如果 x 是局部变量,这里编译不过
}
[] 就是告诉编译器:我要把外部的哪些变量"抓进来"带在身上。
三、捕获列表的所有写法 📋
3.1 [] —— 什么都不捕获
int x = 10;
auto fn = []() {
// std::cout << x; // ❌ 编译错误!没有捕获 x
std::cout << "我不需要外部变量" << std::endl;
};
fn(); // ✅
相当于一个完全独立的普通函数,和外界没有任何关系。
3.2 [x] —— 按值捕获指定变量
int x = 10;
int y = 20;
auto fn = [x]() {
std::cout << x << std::endl; // ✅ 可以用 x
// std::cout << y; // ❌ 没有捕获 y
};
x = 999; // 修改原始的 x
fn(); // 输出 10,不是 999!
// 因为捕获的是当时 x 的"副本"
比喻:拍了一张 x 的照片📸,之后原来的 x 怎么变,照片不会变。
注意:按值捕获的变量默认是 const 的,不能修改:
int x = 10;
auto fn = [x]() {
// x++; // ❌ 编译错误!按值捕获的变量默认是 const
std::cout << x;
};
想修改副本?加 mutable:
int x = 10;
auto fn = [x]() mutable { // 加了 mutable
x++; // ✅ 可以修改副本了
std::cout << x; // 11
};
fn();
std::cout << x; // 10 ← 原始的 x 不受影响,改的只是副本
3.3 [&x] —— 按引用捕获指定变量
int x = 10;
auto fn = [&x]() {
x++; // ✅ 直接修改原始的 x
std::cout << x;
};
fn(); // 输出 11
std::cout << x; // 11 ← 原始的 x 也变了!
比喻:不是拍照,而是拿了一根绳子🪢绑着 x。你拉绳子,x 就跟着动。
3.4 [=] —— 按值捕获所有外部变量
int x = 10;
int y = 20;
int z = 30;
auto fn = [=]() {
std::cout << x + y + z; // ✅ 全都能用,全是副本
};
fn(); // 60
相当于:把周围所有变量都拍了一张照片。
3.5 [&] —— 按引用捕获所有外部变量
int x = 10;
int y = 20;
auto fn = [&]() {
x++;
y++; // ✅ 全都能改,全是引用
};
fn();
std::cout << x << " " << y; // 11 21
⚠️ JavaScript 的闭包行为就等价于这个
[&]! 这就是前面博客里说的。
3.6 混合捕获 —— 灵活组合
int a = 1, b = 2, c = 3;
// a 按值捕获,b 按引用捕获,c 不捕获
auto fn1 = [a, &b]() {
std::cout << a << b;
// std::cout << c; // ❌ 没捕获 c
};
// 默认按值捕获所有,但 b 按引用
auto fn2 = [=, &b]() {
std::cout << a << b << c; // ✅ a, c 是副本,b 是引用
};
// 默认按引用捕获所有,但 a 按值
auto fn3 = [&, a]() {
std::cout << a << b << c; // ✅ a 是副本,b, c 是引用
};
四、完整对照表 📊
| 写法 | 含义 | 能否修改原始变量 |
|---|---|---|
[] |
什么都不捕获 | — |
[x] |
按值捕获 x(拷贝) | ❌ |
[x]() mutable |
按值捕获 x(可改副本) | ❌(改的是副本) |
[&x] |
按引用捕获 x | ✅ |
[=] |
按值捕获所有 | ❌ |
[&] |
按引用捕获所有 | ✅ |
[=, &x] |
默认按值,x 按引用 | 只有 x ✅ |
[&, x] |
默认按引用,x 按值 | 除了 x 都 ✅ |
五、回到你问的代码 🔍
std::function<int(int)> createAdder(int x) {
return [x](int y) { // ← 这里
return x + y;
};
}
逐层拆解:
[x] → 按值捕获外部变量 x(拷贝一份 x 带走)
(int y) → 调用这个 lambda 时传入一个 int 参数 y
{ return x + y; } → 函数体:返回 x + y
执行流程:
auto add10 = createAdder(10);
// 1. 调用 createAdder,参数 x = 10
// 2. 创建 lambda,按值捕获 x,lambda 内部保存了 x 的副本 = 10
// 3. createAdder 返回这个 lambda
// 4. 此时 createAdder 已经结束了,参数 x 被销毁
// 5. 但没关系!lambda 里保存的是副本,不受影响
add10(5); // lambda 执行:副本 x(10) + y(5) = 15 ✅
add10(20); // lambda 执行:副本 x(10) + y(20) = 30 ✅
这里为什么用 [x](值捕获)而不是 [&x](引用捕获)?
// ❌ 如果用引用捕获:
std::function<int(int)> createAdder(int x) {
return [&x](int y) { // 引用捕获 x
return x + y;
};
} // ← 函数结束,x 被销毁了!
auto add10 = createAdder(10);
add10(5); // 💥 未定义行为!x 已经不存在了,引用指向了一块废弃的内存
⚠️ 如果 lambda 要"活过"外部函数的生命周期(被 return 出去),必须用值捕获
[x],不能用引用捕获[&x]! 这是 C++ 程序员必须时刻注意的。而 JavaScript 不需要担心这个问题——垃圾回收器会保证被闭包引用的变量不被销毁。
六、编译器在背后做了什么? 🛠️
当你写下:
int x = 10;
auto fn = [x](int y) { return x + y; };
编译器实际上帮你生成了一个匿名类:
// 编译器在背后自动生成的(大致等价):
class __lambda_at_line_2 {
private:
int x; // 值捕获 → 成员变量(副本)
public:
__lambda_at_line_2(int _x) : x(_x) {} // 构造时保存 x
int operator()(int y) const { // 重载 () 运算符
return x + y;
}
};
// 你写的 auto fn = [x](int y) {...}; 等价于:
__lambda_at_line_2 fn(x); // 构造对象,把 x 拷贝进去
fn(5); // 调用 fn.operator()(5) → 10 + 5 = 15
如果是引用捕获 [&x]:
class __lambda_ref {
private:
int& x; // 引用捕获 → 引用成员
public:
__lambda_ref(int& _x) : x(_x) {}
int operator()(int y) const {
return x + y;
}
};
所以
[]捕获列表本质上就是告诉编译器:给自动生成的匿名类添加什么样的成员变量。
七、一句话总结 💡
[]是 C++ lambda 表达式的"捕获列表",它决定了 lambda 能"记住"外部的哪些变量、以什么方式记住(拷贝还是引用)。这就是 C++ 版的"闭包"机制。
后记
2026年4月16日14点53分于上海,在opus 4.6辅助下完成。
更多推荐



所有评论(0)