引入:lambda表达式的意义是什么?
Lambda表达式是C++11引入的匿名函数对象,旨在简化代码、提升开发效率,尤其在需要短小临时函数的场景中替代传统仿函数。
一:繁琐的场景
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
struct Goods
{
string _name; // 名字
double _price; // 价格
int _evaluate; // 评价
Goods(const char* str, double price, int evaluate)
: _name(str)
, _price(price)
, _evaluate(evaluate)
{}
};
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.1, 4 }, { "橙子", 2.2,3 }, { "菠萝", 1.5, 4 } };
sort(v.begin(), v.end(), ComparePriceLess());
for (auto& e : v)
{
cout << e._name << " " << e._price << " " << e._evaluate << endl;
}
cout << "------------------" << endl;
sort(v.begin(), v.end(), ComparePriceGreater());
for (auto& e : v)
{
cout << e._name << " " << e._price << " " << e._evaluate << endl;
}
}
运行结果:
解释:想要比较什么,就自己去写一个仿函数
二:lambda表达式的格式
看这些规则,总会觉得很繁杂,举个例子就好了
int main()
{
// 局部的匿名函数对象
auto add = [](int a, int b)->int {return a + b; };
cout << add(1, 2) << endl;
return 0;
}

//可以去掉箭头->
auto add = [](int a, int b) {return a + b; };
理解:[ ] + () + {}
[ ]作用后面会讲
()里面写参数,就像函数的参数部分一样
{}里面写函数体的内容
多举几个lambda例子:
例子1:
int main()
{
auto swap1 = [](int& a, int& b)->void
{
int tmp = a;
a = b;
b = tmp;
};
int x = 1, y = 2;
swap1(x, y);
cout << x << " " << y << endl;
return 0;
}
运行结果:
例子2:
int main()
{
auto func1 = []
{
cout << "hello world" << endl;
};
func1();
return 0;
}
运行结果:
解释:没参数的时候 ()都不用写了
总结:通过上述例子可以看出,lambda表达式实际上可以理解为无名函数,该函数无法直接调 用,如果想要直接调用,可借助auto将其赋值给一个变量。
三:用lambda改造繁杂场景
用lambda改造一中的繁琐场景后:
int main()
{
vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,3 }, { "菠萝", 1.5, 4 } };
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;//价格降序
});
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
return g1._evaluate < g2._evaluate;//评价升序
});
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
return g1._evaluate > g2._evaluate;//评价降序
});
return 0;
}
打印一下:
解释:不用在单独搞一个类写比较逻辑了,直接lambda表达式即可
四:捕捉列表的用途
叫捕捉也可以 叫捕获也可以
哎,看起来真的复杂,直接举例子吧,理解了例子就够了
int main()
{
auto swap1 = [](int& a, int& b)->void
{
int tmp = a;
a = b;
b = tmp;
};
int x = 1, y = 2;
swap1(x, y);
cout << x << " " << y << endl;
return 0;
}
我们还可以通过捕获列表 [&]
隐式捕获外部变量 a
和 b
(引用捕获):
①:swap函数的引用捕获
int main()
{
int a = 1, b = 2;
auto Swap = [&]
{
int tmp = a;
a = b;
b = tmp;
};
Swap(); //交换a和b
cout<<a<<b<<endl;
return 0;
}

②:swap的传值方式捕获
int main()
{
int a = 1, b = 2;
auto Swap = [a, b]()mutable
{
int tmp = a;
a = b;
b = tmp;
};
Swap(); //交换a和b?
cout<<a<<""<<b<<endl;
return 0;
}
运行结果:
发现没变,因为这里是传值捕捉,lambda函数中对a和b的修改不会影响外面的a、b变量,与函数的传值传参是一个道理,因此这种方法无法完成两个数的交换。
③:捕捉列表的风险
a:引用捕获的风险
int main() {
int a = 10, b = 20;
int c = 30, d = 40; // 其他变量
auto Swap = [&] { // [&] 会隐式捕获所有可见变量(a、b、c、d)
int tmp = a;
a = b;
b = tmp;
// 这里也可以访问 c 和 d(但实际未使用)
};
Swap();
}
所有局部变量(a、b、c、d)都会被引用捕获,即使 lambda 内部未使用它们。
潜在风险:如果 lambda 被传递到其他作用域(如异步任务),可能导致悬垂引用(访问已销毁的变量)。
b:传值捕获的风险
auto Swap = [=] { // 错误!a 和 b 被值捕获,无法修改(默认值捕获是 const)
a = b; // 编译错误:a 是只读的
};
正确写法:显式指定捕获的变量(推荐做法)
auto Swap = [&a, &b] { // 只捕获 a 和 b,其他变量(c、d)不受影响
int tmp = a;
a = b;
b = tmp;
};
总结:
关键结论
[&]
和[=]
会捕获所有可见变量(即使未使用),可能引发性能或安全问题。推荐显式列出需要捕获的变量(如
[&a, &b]
),避免隐式捕获的副作用。值捕获 (
[a, b]
) 无法修改原变量,除非用mutable
(但通常引用捕获更符合交换操作的需求)。
五:汇编下的lambda
lambda看起来这么的神奇,但其实底层和仿函数一模一样!就好像范围for好牛,其实本质就是迭代器......
class Add
{
public:
Add(int base)
:_base(base)
{}
int operator()(int num)
{
return _base + num;
}
private:
int _base;
};
int main()
{
int base = 1;
//函数对象
Add add1(base);
add1(1000);
//lambda表达式
auto add2 = [base](int num)->int
{
return base + num;
};
add2(1000);
return 0;
}
调试代码并转到反汇编,可以看到:
- 在创建函数对象add1时,会调用Add类的构造函数。
- 在使用函数对象add1时,会调用Add类的
()
运算符重载函数。
如下图:
观察lambda表达式时,也能看到类似的代码:
- 借助auto将lambda表达式赋值给add2对象时,会调用
<lambda_uuid>
类的构造函数。 - 在使用add2对象时,会调用
<lambda_uuid>
类的()
运算符重载函数。
如下图:
本质就是因为lambda表达式在底层被转换成了仿函数。
- 当我们定义一个lambda表达式后,编译器会自动生成一个类,在该类中对
()
运算符进行重载,实际lambda函数体的实现就是这个仿函数的operator()
的实现。 - 在调用lambda表达式时,参数列表和捕获列表的参数,最终都传递给了仿函数的
operator()
。
lambda表达式和范围for是类似的,它们在语法层面上看起来都很神奇,但实际范围for底层就是通过迭代器实现的,lambda表达式底层的处理方式和函数对象是一样的。
所以,由上面也可以佐证一个结论:
lambda表达式之间不能相互赋值
lambda表达式之间不能相互赋值,就算是两个一模一样的lambda表达式。
解释:
a:因为lambda表达式底层的处理方式和仿函数是一样的,在VS下,lambda表达式在底层会被处理为函数对象,该函数对象对应的类名叫做<lambda_uuid>。
b:类名中的uuid叫做通用唯一识别码(Universally Unique Identifier),简单来说,uuid就是通过算法生成一串字符串,保证在当前程序当中每次生成的uuid都不会重复。
c:lambda表达式底层的类名包含uuid,这样就能保证每个lambda表达式底层类名都是唯一的。
例子(一模一样的lambda表达式,它们的类型都是不同):
int main()
{
int a = 10, b = 20;
auto Swap1 = [](int& x, int& y)->void
{
int tmp = x;
x = y;
y = tmp;
};
auto Swap2 = [](int& x, int& y)->void
{
int tmp = x;
x = y;
y = tmp;
};
cout << typeid(Swap1).name() << endl; //class <lambda_797a0f7342ee38a60521450c0863d41f>
cout << typeid(Swap2).name() << endl; //class <lambda_f7574cd5b805c37a13a7dc214d824b1f>
return 0;
}
可以看到,就算是两个一模一样的lambda表达式,它们的类型都是不同的。
说明: 编译器只需要保证每个lambda表达式底层对应类的类名不同即可,并不是每个编译器都会将lambda表达式底层对应类的类名处理成<lambda_uuid>
,这里只是以VS为例。
六:lambda的特性及其影响场景
①:lambda的特性
1:禁止默认构造
Lambda 表达式对应的类型没有默认构造函数,因此不能直接声明未初始化的 Lambda 对象。
示例:
auto lambda = []{ return 42; };
decltype(lambda) l; // 错误:无法默认构造
2:允许拷贝构造
Lambda 对应的类型允许拷贝构造,允许通过已有的 Lambda 对象初始化新对象。
示例:
auto lambda1 = []{ return 42; };
auto lambda2 = lambda1; // 正确:调用拷贝构造
lambda叫作闭包类型
而由于lambda有这两个特性,在priority_queue的场景中,写法和仿函数会有所差距
②:特性影响的场景
场景:将 Lambda 作为自定义比较器传递给 STL 容器priority_queue
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{
cout << "Date(int year, int month, int day)" << endl;
}
Date(const Date& d)
:_year(d._year)
, _month(d._month)
, _day(d._day)
{
cout << "Date(const Date& d)" << endl;
}
// 1在 Date 类中重载 < 运算符
bool operator<(const Date& d) const {
if (_year != d._year) return _year < d._year;
if (_month != d._month) return _month < d._month;
return _day < d._day;
}
private:
int _year = 1;
int _month = 1;
int _day = 1;
};
#include<queue>
int main()
{
auto DateLess = [](const Date* p1, const Date* p2)
{
return *p1 < *p2; // 比较对象内容而非指针地址
};
priority_queue<Date*, vector<Date*>, decltype(DateLess)> p1(DateLess);
return 0;
}
代码意义:演示如何将 Lambda 作为自定义比较器传递给 STL 容器(需显式指定类型并传递实例
重点就在这一行代码:
priority_queue<Date*, vector<Date*>, decltype(DateLess)> p1(DateLess);
理解点1:第三个参数:
decltype(DateLess)
如果是普通的仿函数的话,这里直接传这个仿函数类的类名就好了,但是我们用的lambda表达式,由于 Lambda 的类型是匿名且编译器生成的,不知道名字啊,所以只能decltype(DateLess)了
理解点2:
p1(DateLess)
Q:为什么需要显式传递 DateLess
对象,而不仅仅是声明类型
A: priority_queue
内部实际需要一个 该类型的实例对象 来调用比较逻辑。
所以如果你是仿函数,这里只用p1即可,因为仿函数可以默认构造,priority_queue
内部会自己创建一个实例;而lambda没有默认构造,所以priority_queue
内部无法通过参数3这个类型来实例化出对象,所以我们只能必须显示的调用拷贝构造,DataLess就是lambda表达式的一个对象,其通过拷贝构造给p1
一段又有趣的话: