目录
[1. Lambda 基础知识](#1. Lambda 基础知识)
[1.1 基本语法](#1.1 基本语法)
[1.2 捕获列表](#1.2 捕获列表)
[1.3 基础示例](#1.3 基础示例)
[2. Lambda 的原理](#2. Lambda 的原理)
[2.1 编译器背后生成](#2.1 编译器背后生成)
[2.2 编译器生成细节](#2.2 编译器生成细节)
[2.3 lambda调用与执行流程](#2.3 lambda调用与执行流程)
[2.4 小结](#2.4 小结)
[3. Lambda 的典型应用场景](#3. Lambda 的典型应用场景)
[3.1 传统写法:定义独立仿函数结构体](#3.1 传统写法:定义独立仿函数结构体)
[3.2 Lambda 写法:就地写逻辑,极简高效](#3.2 Lambda 写法:就地写逻辑,极简高效)
1. Lambda 基础知识
Lambda 表达式是 C++11 引入的核心特性之一,本质是匿名函数对象,可以在代码中就地定义、就地使用,无需像普通函数那样单独声明 / 定义,极大简化了回调函数、临时逻辑的编写。
1.1 基本语法
Lambda 的完整语法格式如下(方括号 [] 和花括号 {} 是必须的,其余部分可选):
[capture](parameters) mutable noexcept -> return_type {
// 函数体(执行逻辑)
}
各部分含义:
| 部分 | 作用 |
|---|---|
[capture] |
捕获列表:指定从外部作用域捕获哪些变量(值 / 引用)到 Lambda 内部使用 |
(parameters) |
参数列表:和普通函数的参数列表一致(可省略,无参数时可直接写 () 或省略) |
mutable |
可选:允许修改按值捕获的变量(默认按值捕获的变量是 const 的) |
noexcept |
可选:声明 Lambda 不会抛出异常 |
-> return_type |
返回值类型:可省略(C++11 起编译器会自动推导返回值类型) |
{} |
函数体:Lambda 要执行的逻辑 |
1.2 捕获列表
Lambda 表达式中,默认只能直接使用函数体内部定义的变量 、参数列表中的参数 、**全局变量 / 静态变量,**若要使用外层局部作用域的非静态变量则必须通过捕获列表捕获。
捕获列表决定了 Lambda 能否访问外部变量,以及访问方式(值 / 引用),常见用法:
| 捕获方式 | 语法 | 含义 |
|---|---|---|
| 空捕获 | [] |
不捕获任何外部变量 |
| 显式值捕获 | [x,y] |
拷贝x、y到Lambda内部,默认是const,需加mutable才能修改,修改仅影响副本 |
| 显式引用捕获 | [&x,&y] |
引用x、y,指向原变量,修改会影响外部 |
| 隐式值捕获 | [=] |
捕获所有用到的外部变量(Lambda 内是副本,默认不可修改,若修改加mutable) |
| 隐式引用捕获 | [&] |
捕获所有用到的外部变量(Lambda 内操作的是原变量,修改会影响外部) |
| 混合捕获 | [=, &x] |
默认值捕获,除 x 按引用捕获,其余变量按值捕获 |
| 混合捕获 | [&, x] |
默认引用捕获,除 x 按值捕获,其余变量按引用捕获 |
| 捕获this | [this] |
捕获当前类的 this 指针(成员函数内的 Lambda 可访问类的成员变量 / 函数) |
1.3 基础示例
示例1
最简单的 Lambda(无捕获、无参数、无返回值):
int main()
{
//lambda表达式本身(定义一个匿名类对象)后面的 () → 立刻调用这个对象的 operator()
[]() {
cout << "hello" << endl;
}();
//简化写法(无参数时可省略 ())
[] {
cout << "hello" << endl;
}();
return 0;
}
示例2
带参数和返回值的lambda:
Lambda 表达式被编译器处理后,会生成一个匿名的、独一无二的函数对象类型,这个类型没有名字,无法手动写出它的类型名,所以一般用auto或模版参数接收lambda对象。
int main()
{
// 显式指定返回值类型(-> int)
auto add = [](int x, int y) -> int {
return x + y;
};
cout << add(3, 5) << endl; // 输出:8
// 省略返回值类型(编译器自动推导)
auto multiply = [](int x, int y) {
return x * y; // 推导返回值为 int
};
cout << multiply(4, 6) << endl; // 输出:24
return 0;
}
示例3
捕获外部变量:
int main()
{
int a = 10, b = 20;
// 1. 按值捕获 a,按引用捕获 b
auto lambda1 = [a, &b]() {
// a = 100; 错误!按值捕获的变量默认不可修改(加mutable才可修改)
b = 200; // 正确!按引用捕获可修改原变量
cout << "a = " << a << ", b = " << b << endl; // 输出:a=10, b=200
};
lambda1();
cout << "外部 b = " << b << endl; // 输出:外部 b=200(引用捕获修改了原变量)
// 2. 按值捕获 + mutable(允许修改捕获的副本)
auto lambda2 = [a]() mutable {
a = 100; // 正确!mutable 解除 const 限制
cout << "lambda2 内 a = " << a << endl; // 输出:100
};
lambda2();
cout << "外部 a = " << a << endl; // 输出:10(值捕获仅修改副本,不影响原变量)
return 0;
}
int main()
{
int a = 30, b = 40, c = 50;
// 1. 隐式值捕获([=]) 捕获所有用到的
auto lambda3 = [=]() mutable {
a = 3000;
b = 4000;
return a + b; //编译器不会捕获c,只会捕获a、b
};
cout << lambda3() << endl; // 7000
cout << "外部 a=" << a << ", b=" << b << endl; // 输出:30,40(值捕获不影响原变量)
// 2. 隐式引用捕获([&])
auto lambda4 = [&]() mutable {
a = 3000;
b = 4000;
return a + b; //编译器不会捕获c,只会捕获a、b
};
cout << lambda4() << endl; // 7000
cout << "外部 a=" << a << ", b=" << b << endl; // 输出:3000,4000(引用捕获修改原变量)
return 0;
}
int main()
{
int a = 3, b = 4, c = 5, d = 6;
//混合捕捉:默认按引用捕捉,a、b按值捕捉
auto func4 = [&, a, b]
{
//a++; error
//b++;
c++;
d++;
return a + b + c + d;
};
cout << func4() << endl; // 20
cout << a << " " << b << " " << c << " " << d << endl; // 3 4 6 7
//混合捕捉:默认按值捕捉,a、b按引用捕捉
auto func5 = [=, &a, &b]
{
a++;
b++;
//c++; error
//d++;
return a + b + c + d;
};
cout << func5() << endl; // 22
cout << a << " " << b << " " << c << " " << d << endl; // 4 5 6 7
return 0;
}
示例4
全局作用域的 Lambda:
全局 / 静态变量无需捕获,Lambda 无论定义在何处,都可以直接访问全局 / 静态变量,因此全局 Lambda 的捕获列表必须为空,因为全局作用域没有 "局部变量" 可捕获。
// 全局变量(全局作用域)
int global_var = 10;
// 全局静态变量(仅当前文件可见)
static int static_global_var = 20;
// 1. 全局Lambda(定义在全局作用域)
// 全局Lambda的捕获列表必须为空([]),但可直接访问全局/静态变量
auto global_lambda = []() {
global_var = 100;// 直接访问并修改全局变量(无需捕获)
static_global_var = 200;// 直接访问并修改全局静态变量(无需捕获)
};
// 2. 局部Lambda(定义在函数内)
void test()
{
// 函数内静态变量
static int static_local_var = 30;
// 函数内局部变量(需要捕获才能访问)
int local_var = 40;
// 局部Lambda:捕获局部变量local_var,直接访问全局/静态变量
auto local_lambda = [local_var]() mutable {
global_var = 1000; // 直接修改全局变量(无需捕获)
static_global_var = 2000; // 直接修改全局静态变量(无需捕获)
static_local_var = 3000;// 直接修改函数内静态变量(无需捕获)
local_var = 4000; // 修改捕获的局部变量(需要mutable)
};
local_lambda();
}
int main()
{
global_lambda(); // 执行全局Lambda
test(); // 执行局部Lambda
return 0;
}
示例5
类成员函数中的 Lambda(捕获 this):
class MyClass
{
private:
int num = 100;
public:
void test()
{
// 捕获this指针,访问类的成员变量
auto lambda = [this]() {
num = 200; // 等价于 this->num = 200
cout << num << endl; // 输出:200
};
lambda();
}
};
int main()
{
MyClass obj;
obj.test();
return 0;
}
2. Lambda 的原理
Lambda 和范围 for一样,都是 C++ 语法糖!范围 for 底层是迭代器遍历, lambda 底层是仿函数对象, 编译之后,它们都不复存在。
lambda = 编译器自动生成的匿名类 + 自动创建的匿名对象
示例代码:
int main()
{
double rate = 0.49;
auto r = [rate](double money, int year)
{
return money * rate * year;
};
r(10000, 2);
return 0;
}
2.1 编译器背后生成
编译器识别 [...] 结构,确定这是一个 Lambda 表达式后在背后偷偷生成如下匿名类与对象:
//创建匿名仿函数类(闭包类)
class __lambda_abc123 //编译器自动生成唯一类名
{
// 捕获的变量 → 变成【成员变量】值捕获->拷贝成员 引用捕获->引用成员
double _rate;
public:
// 捕获变量 → 变成【构造函数参数】构造函数执行时,会将外部变量rate的值,赋值给类的私有成员变量_rate
__lambda_abc123 (double rate)
: _rate(rate)
{}
// lambda 函数体 → 变成 operator()
double operator()(double money, int year) const
{
// 用的是【成员变量 _rate】
return money * _rate * year;
}
};
//创建匿名仿函数类对象
__lambda_abc123 r(rate);
2.2 编译器生成细节
生成匿名类:编译器会自动生成一个独一无二的匿名仿函数类( 类名是随机且唯一的,如__lambda_123**)。**
捕获列表处理 :捕获的变量会转化为类的私有成员变量 :
- 引用捕获 → 引用成员变量
- 值捕获 → 拷贝成员变量
构造函数生成:生成带参数的构造函数,用外部捕获的变量初始化类成员。
函数体映射 :Lambda 函数体直接作为
operator()的实现。默认情况下operator()会自动添加const修饰,若要修改捕获变量,需添加mutable关键字。
auto r 接收匿名对象,用这个匿名对象初始化 r ,得到可调用变量 r 。__lambda_abc123 r = __lambda_abc123(rate);
//或者编译器优化为__lambda_abc123 r(rate);auto自动推导出编译器生成的匿名仿函数类类型(__lambda_abc123),因为这个类型名我们无法手动书写,只能靠auto接收。
让原本"匿名、无法直接使用"的仿函数对象,有了一个可用的变量名r ,后续就可以通过r(...)调用函数。
2.3 lambda调用与执行流程
当代码执行 r(10000, 2) 时,编译器会将其翻译为对operator()的显式调用:
r.operator()(10000, 2);执行流程:
- 调用
r对象的operator()方法,传入参数money = 10000、year = 2。- 方法内部使用已保存的成员变量
_rate = 0.49参与计算。- 返回结果:
10000 * 0.49 * 2 = 9800。
2.4 小结
- 编译器会将 Lambda 表达式自动生成一个独一无二的匿名类。
- 捕获的变量会成为该匿名类的成员变量 ,函数体成为
operator()的实现。auto推导为该匿名类类型,调用r(...)本质是调用r.operator()。- 整个过程是纯编译期行为,运行时无额外开销,和手写类效率完全一致。
3. Lambda 的典型应用场景
STL 算法的回调函数是 Lambda 表达式最经典、最常用的应用场景。在 C++ 标准库提供的排序、查找、遍历、统计等算法中,往往需要用户自定义比较规则、筛选条件或处理逻辑,而 Lambda 凭借语法简洁、逻辑内聚、可就地定义等优势,能够完美替代传统的函数指针与仿函数,让算法的使用更加灵活高效,成为开发中最主流的选择。
在 C++ 开发中,我们经常需要对自定义类型(如商品、学生等)进行排序,std::sort算法允许传入一个比较函数 / 仿函数来定义排序规则。
自定义类型示例:
struct Goods
{
string _name; // 名字
double _price; // 价格
int _evaluate; // 评价
// ...
Goods(const char* str, double price, int evaluate)
:_name(str)
, _price(price)
, _evaluate(evaluate)
{}
};
3.1 传统写法:定义独立仿函数结构体
在 C++11 之前,要给sort传自定义比较规则,必须单独定义仿函数结构体 (重载
operator())。
struct ComparePriceLess
{
bool operator()(const Goods& gl, const Goods& gr)
{
return gl._price < gr._price;
}
};
struct ComparePriceGreater
{
bool operator()(const Goods& gl, const Goods& gr)
{
return gl._price > gr._price;
}
};
int main()
{
vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 3}, { "菠萝", 1.5, 4 } };
sort(v.begin(), v.end(), ComparePriceLess());
sort(v.begin(), v.end(), ComparePriceGreater());
return 0;
}
传统写法的缺点:
- 代码冗余: 为了一个小小的比较逻辑,必须单独写结构体。
- 可读性差: 逻辑定义和使用位置分离,需要跳转查看比较规则。
- 维护成本高:比较规则越多(如按评价升序 / 降序),需要定义的结构体数量越多,容易造成类爆炸。
3.2 Lambda 写法:就地写逻辑,极简高效
C++11 引入 Lambda 后,我们可以在sort调用处直接写比较逻辑,无需额外定义结构体。
int main()
{
vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 3}, { "菠萝", 1.5, 4 } };
// 直接在sort里面写比较规则,随用随写
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
return g1._price < g2._price;
});
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
return g1._price > g2._price;
});
return 0;
}
Lambda 写法的优点:
- 无额外定义:不需要写任何仿函数结构体,代码更简洁。
- 逻辑内聚:比较逻辑直接写在sort调用处,一眼就能看懂排序规则。
- 灵活高效:新增 / 修改排序规则只需改一行 Lambda,不会产生大量无用结构体。
- 代码量更少:对比传统写法,行数大幅减少,可读性和可维护性显著提升。
Lambda 不仅适合sort,在所有需要自定义谓词 / 回调的 STL 算法中都有广泛应用:
std::find_if:查找满足条件的元素std::count_if:统计满足条件的元素个数std::for_each:遍历容器并执行自定义操作std::transform:对容器元素做转换
Lambda 是 STL 算法自定义回调逻辑的最佳实践,它完美解决了传统仿函数写法的冗余、可读性差、维护成本高的问题,让代码更简洁、更直观、更灵活。