前言
最近在刷 LeetCode 时,被官方题解里的 Lambda 表达式卡住了,查了一些资料彻底搞懂。于是把学习心得整理成一篇系统性文章,希望能帮助到同样在入门现代 C++ 的同学们。
一、Lambda表达式是什么?
用一句话定义就是Lambda 表达式是 C++11 引入的一种"就地定义匿名函数"的语法。
它本质是一个表达式,但其功能和行为完全等同于一个没有名字的函数。
传统函数:必须先在某个地方完整定义,才能调用。
Lambda:可以直接在需要的地方"现场写一个函数",写完立刻就能用。
最直观的比喻:
普通函数 = 提前在厨房做好一道菜再端上桌。
Lambda = 直接在餐桌上现场做一道简单的小菜,吃完就走,不用给它起名字。
二、Lambda的基本语法
cpp
[捕获列表](参数列表) -> 返回类型 { 函数体 };
各部分含义:
-
捕获列表\]:\[ \] ------ 决定能否使用外部变量(核心)
- -> 返回类型:可选,编译器大多能自动推断
- { 函数体 }:真正的代码逻辑
- 结尾的分号 ;:必须加!因为它是一个表达式/赋值语句
最简版本的Lambda表达式(无捕获、无参数):
cpp
[] {cout<<"Hello Lambda!"<<endl;};
三、Lambda的使用方式
1.基本用法:赋值给变量后调用
cpp
#include<iostream>
using namespace std;
int main()
{
auto greet = [](){
cout<<"你好,我是一个Lambda!"<<endl;
};
greet(); //调用
return 0;
}
2.带参数+带返回值
cpp
auto add = [](int a, int b) -> int { // 可以省略 -> int,编译器会推断
return a + b;
};
cout<<add(10, 20)<<endl; // 输出 30
3.捕获外部变量(这是Lambda的灵魂)
按值捕获(复制一份):
cpp
int base = 100;
auto multiply = [base](int x){ //[base]只捕获base
return base * x;
};
cout<<multiply(5)<<endl; //500
按引用捕获(可以修改外部变量):
cpp
int counter = 0;
auto increment = [&counter]() { // [&] 按引用捕获所有外部变量
++counter;
};
increment();
increment();
cout <<counter<<endl; // 输出 2
混合捕获:
cpp
int a = 1, b = 2;
auto func = [a, &b](int x) {
b += x; // 可以修改 b
return a + b;
};
4.在STL算法中使用
cpp
#include <algorithm>
#include <vector>
using namespace std;
vector<int> v = {3, 1, 4, 1, 5, 9};
// 降序排序
sort(v.begin(), v.end(), [](int x, int y) {
return x > y;
});
// for_each 遍历并打印
for_each(v.begin(), v.end(), [](int x) {
cout << x << " ";
});
// 输出:9 5 4 3 1 1
5.捕获this(类成员函数中使用)
cpp
class Calculator {
int value = 42;
public:
auto getMultiplier() {
return [this](int x) { // 捕获 this
return value * x;
};
}
};
int main() {
Calculator c;
auto mul = c.getMultiplier();
cout << mul(10) << endl; // 420
}
6.mutable关键字
cpp
#include <iostream>
using namespace std;
int main() {
int x = 0; // 原变量 x,初始值 0
auto func = mutable { // 按值捕获 x,生成一个拷贝(我们叫它 x_copy)
x++; // 修改的是 x_copy,不是外面的原 x
return x;
};
cout << func() << " " << func() << endl; // 输出 1 2
cout << "原变量 x 的值:" << x << endl; // 原 x 还是 0!
return 0;
}
只有按值捕获,且需要修改Lambda内部的拷贝时,才需要加 mutable (若按值捕获,且需要修改Lambda内部的拷贝时,没有加 mutable,编译器会报错)。
其他场景(按引用捕获、只读取不修改、按值捕获不修改)都不需要加。
四、Lambda解决了什么问题?
传统 C++ 的痛点:
代码分散:一个小功能也要单独写一个函数或函数对象,逻辑不集中。
命名污染:要给很多只用一次的小函数起名字。
无法方便地捕获上下文:普通函数无法直接使用外部局部变量。
Lambda 完美解决:
就地定义,就地使用,代码可读性大幅提升。
无需命名,减少全局/命名空间污染。
方便捕获上下文,特别适合 STL 算法、回调函数、线程、事件处理等。
就用排序为例:
cpp
//C++98写法
bool cmp(int a, int b) { return a > b; }
std::sort(v.begin(), v.end(), cmp);
//C++11Lambda写法(简洁)
std::sort(v.begin(), v.end(), [](int a, int b){ return a > b; });
简单的介绍一下Lambda表达式的底层原理:
编译器在编译时,会把 Lambda 表达式自动转换成一个匿名的类(称为闭包类型 ),这个类重载了 operator()。
cpp
// 写的 Lambda
auto add = [](int a, int b){ return a + b; };
// 编译器实际生成的类似代码(简化版)
class __lambda_12345 {
public:
int operator()(int a, int b) const {
return a + b;
}
};
__lambda_12345 add; // 创建对象
add(3, 5); // 实际调用 operator()
捕获的变量会变成这个类的成员变量:
=\] → 值拷贝成员 \[\&\] → 引用成员 这就是为什么 Lambda 既是"表达式",又能"像函数一样被调用"。 ### 五、注意点 1.必须加分号 ; 因为 Lambda 是一个表达式,不是函数定义。忘记加分号是初学者最常见的编译错误。 2.捕获方式选错导致 Bug 按引用捕获 \[\&\] 时,Lambda 生命周期不能超过被捕获变量(否则悬空引用)。 按值捕获 \[=\] 时,外部变量修改后 Lambda 不会同步(因为是拷贝)。 3.不要在类成员函数里直接用 \[\&\] 捕获所有 容易导致 this 悬空。推荐显式写 \[this\] 或 \[\&\] + 注意生命周期。 4.mutable 的使用场景 只有按值捕获的变量才需要 mutable,否则编译报错。 5.返回值类型推断 如果函数体有多条 return 语句,返回类型不同,编译器会报错,必须手动写 -\> 类型。 6.性能 Lambda 本身几乎零开销(编译器会内联),但大量捕获大对象时要注意拷贝成本。 7。C++14 及以上增强 泛型 Lambda:\[\](auto x, auto y){ return x + y; } 捕获初始化:[\[x = std::move(obj)\]](){ ... } Lambda表达式总结就是:就地写函数,\[\] 捕获上下文,mutable 改拷贝,生命周期要注意