自从 C++11 引入 Lambda 表达式以来,它已经成为现代 C++ 编程的常用工具。无论是算法调用、回调函数,还是线程池和异步任务中,Lambda 都能大大简化代码。本文将系统介绍 Lambda 表达式的语法、捕获列表、参数传递区别,以及一些常见用法与注意事项。
1. Lambda 表达式的语法
Lambda 的完整语法为:
cpp
[capture-list](parameters) mutable(optional) exception(optional) -> return_type(optional) {
// 函数体
}
各部分含义:
-
capture-list
:捕获外部作用域中的变量。 -
parameters
:形参列表,和普通函数一样。 -
mutable
:允许修改按值捕获的副本。 -
exception
:异常说明(如noexcept
)。 -
return_type
:返回类型,可省略(编译器会自动推断)。 -
函数体
{}
:Lambda 的实际逻辑。
2. 捕获列表([]
)详解
捕获列表决定 Lambda 如何使用外部作用域的变量。
2.1 空捕获
cpp
auto f = [] { return 42; };
std::cout << f() << std::endl; // 输出 42
👉 不捕获任何外部变量。
2.2 按值捕获
cpp
int x = 10;
auto f = [x]() { return x + 1; };
-
捕获时复制一份
x
。 -
后续调用使用副本,不影响外部变量。
若想修改捕获的副本,需要加 mutable
:
cpp
auto f = [x]() mutable { return ++x; };
2.3 按引用捕获
cpp
int x = 10;
auto f = [&x]() { return ++x; };
f(); // 修改外部 x,x = 11
2.4 捕获所有变量
cpp
int a = 1, b = 2;
auto f1 = [=]() { return a + b; }; // 按值捕获所有局部变量
auto f2 = [&]() { return a + b; }; // 按引用捕获所有局部变量
2.5 混合捕获
cpp
int a = 1, b = 2;
auto f = [=, &b]() { return a + (b++); };
2.6 捕获 this
在类成员函数中,常常需要访问对象的成员,可以用 [this]
捕获:
cpp
class Test {
int value = 42;
public:
void run() {
auto f = [this]() { return value; };
std::cout << f() << std::endl; // 输出 42
}
};
👉 捕获 this
指针后,Lambda 内部可以访问成员变量。
C++17 新增了 [=, this]
的写法,允许同时按值捕获局部变量并捕获 this
。
3. 捕获 vs 参数传递
很多同学会疑惑:通过参数传递,不也能把变量传进 Lambda 吗?为什么还要捕获?
来看对比:
参数传递
cpp
auto f = [](int v) { return v + 1; };
std::cout << f(10) << std::endl; // 输出 11
-
值由调用者传入。
-
灵活,每次调用可传不同的值。
捕获
cpp
int base = 100;
auto f = [base](int v) { return base + v; };
std::cout << f(5) << std::endl; // 输出 105
-
值在定义时绑定。
-
简洁,不需要每次调用都显式传入
区别总结

4. 其他语法细节
4.1 mutable
按值捕获的变量默认是只读的,若想修改副本需加 mutable
:
cpp
int x = 10;
auto f = [x]() mutable { return ++x; };
std::cout << f() << std::endl; // 输出 11
4.2 返回类型推断
cpp
auto f = [](int x, int y) { return x + y; };
// 返回类型自动推断为 int
如需显式指定:
cpp
auto f = [](int x, int y) -> double { return x + y + 0.5; };
4.3 Lambda 在 STL 中的应用
Lambda 最常见的用途就是配合标准库算法:
cpp
std::vector<int> v = {1, 2, 3, 4, 5};
// 自定义条件排序
std::sort(v.begin(), v.end(), [](int a, int b) { return a > b; });
// 遍历输出
std::for_each(v.begin(), v.end(), [](int x) { std::cout << x << " "; });
5. 捕获的生命周期陷阱 ⚠️
注意:按引用捕获的变量必须保证在 Lambda 使用时仍然有效。
错误示例:
cpp
auto f = []() {
int x = 10;
return [&x]() { return x; }; // 危险!x 会悬空
}();
这里返回的 Lambda 内部引用了 x
,但 x
在函数结束后已被销毁,调用 Lambda 会导致未定义行为。
6.显示捕获与隐式捕获
显式捕获 (Explicit Capture)
显式捕获就是在 []
中明确列出要捕获的变量,并指定是按值还是按引用。
示例
cpp
int a = 10, b = 20;
auto f1 = [a]() { return a; }; // 显式按值捕获 a
auto f2 = [&b]() { return b; }; // 显式按引用捕获 b
auto f3 = [a, &b]() { return a + b; }; // 混合捕获
优点:
-
精确控制,代码可读性强。
-
明确知道 Lambda 使用了哪些变量。
隐式捕获 (Implicit Capture)
隐式捕获就是使用 =
或 &
,一次性捕获所有在 Lambda 内部使用的外部变量。
示例
cpp
int a = 10, b = 20;
// 按值隐式捕获(只会捕获 Lambda 内部用到的变量)
auto f1 = [=]() { return a + b; };
// 按引用隐式捕获
auto f2 = [&]() { return a + b; };
特点:
-
代码简洁,省去一个个列出变量的麻烦。
-
只会捕获实际在 Lambda 内部用到的变量(编译器自动推断)。
缺点:
-
可读性差,不容易看出 Lambda 实际依赖了哪些变量。
-
容易引入不必要的捕获,尤其是在大型函数里。
隐式与显式混合捕获
C++ 允许在隐式捕获的基础上,对某些变量单独指定捕获方式。
示例
cpp
int a = 10, b = 20, c = 30;
// 默认按值捕获,但 b 显式按引用捕获
auto f1 = [=, &b]() { return a + b + c; };
// 默认按引用捕获,但 a 显式按值捕获
auto f2 = [&, a]() { return a + b + c; };
规则:
-
=
表示默认按值捕获,个别变量可用&var
显式按引用。 -
&
表示默认按引用捕获,个别变量可用var
显式按值。 -
不能同时出现
[=, &]
或[&, =]
,这是语法错误
小结

7. 总结
-
Lambda 捕获列表:决定如何引入外部作用域变量(值 / 引用 / this)。
-
参数传递:调用时显式传值,更灵活。
-
捕获与参数结合使用:是 Lambda 强大的原因之一。
-
注意生命周期:尤其是按引用捕获。
Lambda 让 C++ 代码更简洁、更现代,是函数式编程思想在 C++ 的体现。掌握 Lambda,是写出现代 C++ 的必经之路。