# Lambda 表达式
面试官问:"Lambda 的本质是什么?捕获列表是怎么实现的?"
很多人能写出 lambda 的语法,但问到编译器怎么实现的、捕获列表在底层是什么、mutable 为什么需要、无捕获 lambda 为什么能转函数指针,就答不上来了。关键是理解 lambda 的编译器转换原理。
# 简要回答
Lambda 本质是编译器生成的匿名仿函数类。语法 [捕获列表](参数) { 函数体 } 会被编译器转换成一个结构体:捕获的变量变成成员变量,函数体变成 operator() 的实现。
值捕获 [x]:x 变成结构体的成员(拷贝一份)。引用捕获 [&x]:x 变成结构体的引用成员。无捕获 []:结构体没有成员,等价于普通函数,所以能隐式转为函数指针。
# 详细回答
编译器转换过程
[threshold](int x) { return x > threshold; } 编译器实际生成:
struct __lambda_1 {
int threshold; // 值捕获 -> 成员变量
bool operator()(int x) const { return x > threshold; }
};
2
3
4
然后用当前作用域的 threshold 值初始化这个结构体。所以 lambda 就是一个有 operator() 的对象——和手写仿函数完全等价。
捕获列表的底层机制
[x]值捕获:x 被拷贝到 lambda 对象内部,之后外部 x 的变化不影响 lambda 内的副本[&x]引用捕获:lambda 内部持有 x 的引用,通过 lambda 修改会影响外部的 x[=]全部值捕获:所有用到的外部变量都拷贝一份[&]全部引用捕获:所有用到的外部变量都按引用访问[x, &y]混合捕获:x 按值,y 按引用
mutable 的作用
默认情况下,lambda 的 operator() 是 const 的——值捕获的变量不能修改(因为是 const 成员函数里的成员变量)。加 mutable 去掉 const 限定,允许修改值捕获的副本(但不影响外部原变量)。
无捕获 lambda 与函数指针
无捕获的 lambda 没有成员变量,等价于一个普通函数。C++ 标准规定它可以隐式转换为对应签名的函数指针。这让 lambda 能直接传给 C 风格的回调接口。有捕获的 lambda 不能转函数指针——因为它有状态(成员变量),需要对象来承载。

# 知识拓展
面试官可能追问:
Q1: lambda 和函数指针有什么区别?
lambda 是对象(编译器生成的类的实例),函数指针是纯地址。lambda 可以捕获外部变量(有状态),函数指针不能。lambda 能被编译器内联优化(因为类型唯一,编译器知道调用目标),函数指针是间接调用,通常不能内联。无捕获 lambda 可以退化为函数指针,但有捕获的不行。
Q2: 值捕获和引用捕获怎么选?
值捕获安全但有拷贝开销——lambda 比被捕获变量活得久也没问题(因为是副本)。引用捕获零开销但有悬垂风险——如果 lambda 比被捕获变量活得久(比如异步回调),引用就悬空了。原则:短生命周期的 lambda(同步使用、不出作用域)用引用捕获;长生命周期的 lambda(存起来以后用、传给异步任务)用值捕获。
Q3: C++14/17/20 对 lambda 有什么增强?
C++14:泛型 lambda(参数用 auto)、初始化捕获([x = std::move(obj)] 移动捕获)。C++17:constexpr lambda(编译期求值)。C++20:模板 lambda([]<typename T>(T x))、lambda 可以出现在未求值上下文中。每个版本都在让 lambda 更强大、更接近"万能可调用对象"。
Q4: lambda 什么时候不该用?
三种情况:函数体超过 5~10 行(可读性差,应该提取为命名函数);需要在多处复用同一逻辑(应该定义普通函数或仿函数);需要递归(lambda 不能直接递归调用自己,需要用 std::function 包装,有性能开销)。
评论
验证登录状态...