Lambda 表达式
约 1108 字大约 4 分钟
C++11Lambda回调
2026-05-17
Lambda 是 C++11 最有代表性的特性之一。它让我们可以在需要的地方直接定义一个可调用对象,减少为短小逻辑单独声明函数或仿函数的样板代码。
为什么需要 Lambda
在 C++11 之前,如果想把一小段逻辑传给算法或者回调,通常要么写普通函数,要么写函数对象。
struct GreaterThanFive {
bool operator()(int value) const {
return value > 5;
}
};如果这段逻辑只会用一次,这样的写法就显得很重。Lambda 的价值,就是把“定义可调用逻辑”和“使用它的地方”放在一起。
基本语法
Lambda 的基本形式如下:
[capture](params) -> return_type {
// function body
}一个最简单的例子:
auto add = [](int left, int right) {
return left + right;
};这里的含义是:
[]是捕获列表(int left, int right)是参数列表- 花括号内是函数体
- 返回类型通常可以由编译器推导
最常见的使用场景
Lambda 经常和标准算法一起出现,比如 std::find_if、std::sort、std::for_each。
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector<int> values {1, 3, 5, 8, 13};
auto iter = std::find_if(values.begin(), values.end(), [](int value) {
return value % 2 == 0;
});
if (iter != values.end()) {
std::cout << *iter << std::endl;
}
}这段代码的优势很明显:判断条件就在调用点旁边,阅读时不需要再跳转到别的函数定义。
捕获列表
Lambda 最关键的部分不是参数,而是捕获。捕获决定了 Lambda 能否访问外部作用域中的变量,以及用什么方式访问。
值捕获
int base = 10;
auto add_base = [base](int value) {
return base + value;
};这里的 base 会在 Lambda 创建时拷贝一份进去。之后即使外部的 base 变化,Lambda 内部用的仍然是旧值。
引用捕获
int total = 0;
auto accumulate = [&total](int value) {
total += value;
};这里捕获的是引用,Lambda 内部修改的就是外部变量本身。
默认捕获
int a = 1;
int b = 2;
auto by_value = [=]() {
return a + b;
};
auto by_reference = [&]() {
return a + b;
};[=]表示默认按值捕获外部变量[&]表示默认按引用捕获外部变量
默认捕获写起来方便,但工程里不建议滥用。变量一多,读代码时很难快速判断 Lambda 依赖了哪些外部状态。
混合捕获
int threshold = 10;
int count = 0;
auto check = [threshold, &count](int value) {
if (value > threshold) {
++count;
return true;
}
return false;
};这种写法更适合真实项目,因为依赖关系是显式的。
mutable 的作用
按值捕获时,Lambda 默认不能修改捕获进来的副本。
int seed = 42;
auto next = [seed]() mutable {
++seed;
return seed;
};mutable 的意思不是修改外部变量,而是允许修改 Lambda 内部那份按值捕获的副本。
返回类型
大多数场景下,返回类型可以自动推导:
auto is_even = [](int value) {
return value % 2 == 0;
};如果分支比较复杂,或者你希望代码意图更明确,也可以显式写出返回类型:
auto compare = [](int left, int right) -> bool {
return left < right;
};一个更接近实际工程的例子
下面这个例子使用 Lambda 给 std::sort 提供排序规则:
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
struct User {
std::string name;
int score;
};
int main() {
std::vector<User> users {
{"alice", 82},
{"bob", 95},
{"carol", 88},
};
std::sort(users.begin(), users.end(), [](const User& left, const User& right) {
return left.score > right.score;
});
for (const auto& user : users) {
std::cout << user.name << ": " << user.score << std::endl;
}
}如果不用 Lambda,你通常需要额外写一个比较器类型。对于只使用一次的排序规则,Lambda 明显更直接。
使用 Lambda 时要注意什么
不要随手使用 [&]
按引用默认捕获虽然省事,但很容易把局部变量生命周期问题带进来。尤其当 Lambda 被保存起来、异步执行,或者返回到外部时,悬空引用是非常常见的 bug 来源。
优先显式捕获真正需要的变量
比起 [=] 或 [&],像 [threshold, &count] 这样的写法更利于维护。代码评审时,也更容易判断这个 Lambda 是否依赖了不该依赖的状态。
Lambda 适合短逻辑,不适合承载整段业务
如果 Lambda 已经长到几十行,通常意味着这段逻辑应该提炼成具名函数。Lambda 的核心优势是“就近表达意图”,不是替代所有函数。
小结
Lambda 把 C++ 的很多局部逻辑写法从“笨重”变成了“自然”。
