在C++98中,如果想要对一个数据集合中的元素进行排序,可以使用std::sort方法
#include <algorithm>
#include <functional>
int main()
{
int array[] = { 4,1,8,5,3,7,0,9,2,6 };
// 默认按照小于比较,排出来结果是升序
std::sort(array, array + sizeof(array) / sizeof(array[0]));
// 如果需要降序,需要改变元素的比较规则
std::sort(array, array + sizeof(array) / sizeof(array[0]), greater<int>());
return 0;
}
如果待排序元素为自定义类型,需要用户定义排序时的比较规则:
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, 4 }, { "橙子", 2.2,
3 }, { "菠萝", 1.5, 4 } };
sort(v.begin(), v.end(), ComparePriceLess());
sort(v.begin(), v.end(), ComparePriceGreater());
}
随着C++语法的发展,人们开始觉得上面的写法太复杂了,每次为了实现一algorithm算法,都要重新去写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,这些都给编程者带来了极大的不便。因此,在C++11语法中出现了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; });
}
[capture-list] (parameters) mutable -> return-type { statement }
[capture-list]
: 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。(parameters)
:参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略mutable
:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。->returntype
:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。{statement}
:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空。因此C++11中最简单的lambda函数为:[]{}
; 该lambda函数不能做任何事情
int main()
{
// 最简单的lambda表达式, 该lambda表达式没有任何意义
[] {};
// 省略参数列表和返回值类型,返回值类型由编译器推导为int
int a = 3, b = 4;
[=] {return a + 3; };
// 省略了返回值类型,无返回值类型
auto fun1 = [&](int c) {b = a + c; };
fun1(10);
cout << a << " " << b << endl;
// 各部分都很完善的lambda函数
auto fun2 = [=, &b](int c)->int {return b += a + c; };
cout << fun2(10) << endl;
// 复制捕捉x
int x = 10;
auto add_x = [x](int a) mutable { x *= 2; return a + x; };
cout << add_x(10) << endl;
return 0;
}
通过上述例子可以看出,lambda表达式实际上可以理解为无名函数,该函数无法直接调用,如果想要直接调用,可借助auto将其赋值给一个变量
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(), ComparePriceGreater());
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
return g1._price < g2._price; });
}
Lambda 的代码:
[](const Goods& g1, const Goods& g2) {
return g1._price < g2._price;
}
编译器实际上会生成一个类似于以下的匿名类:
class LambdaGeneratedClass {
public:
bool operator()(const Goods& g1, const Goods& g2) const {
return g1._price < g2._price;
}
};
这表明,Lambda 是由编译器生成的一个仿函数对象,但它不是手动定义的仿函数。
特性 | 仿函数 | Lambda |
---|---|---|
定义方式 | 明确定义类,手动实现 operator() | 使用匿名语法,编译器生成类 |
状态捕获 | 静态状态(类的成员变量) | 支持捕获外部变量,动态状态捕获 |
语法简洁性 | 需要额外定义类,使用略繁琐 | 直接内联编写,简洁明了 |
使用场景 | 可在多处复用,逻辑复杂的情况适用 | 适用于简短的、局部的逻辑 |
从语法上,Lambda 和仿函数都是通过实现 operator()
的方式实现可调用性。因此,Lambda 在功能上可以视为仿函数的一个特例。但从本质上看,Lambda 是编译器生成的匿名类的实例,而仿函数是用户手动定义的类。可以认为 Lambda 是一种更灵活、更轻量的仿函数形式。
捕获列表位于 Lambda 表达式的 []
部分,用于指定捕获的变量以及捕获的方式
捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用。
注意:
int x = 10;
auto lambda = [x]() {
// 捕获 x 的副本,x 的值是 10
std::cout << x << std::endl;
};
x = 20; // 外部修改 x
lambda(); // 输出 10,不受外部 x 变化影响
如果希望按值捕获的变量可修改,可以使用 mutable
:
int x = 10;
auto lambda = [x]() mutable {
x++; // 允许修改捕获的副本
std::cout << x << std::endl;
};
lambda(); // 输出 11
语法:[&var]
示例:
int x = 10;
auto lambda = [&x]() {
x++; // 修改外部变量 x
std::cout << x << std::endl;
};
lambda(); // 输出 11
std::cout << x << std::endl; // 输出 11
语法:[=]
捕获外部作用域中所有变量的副本(按值)。
特点:所有捕获的变量对 Lambda 来说是只读的,除非使用 mutable
修饰符。
int x = 10, y = 20;
auto lambda = [=]() {
std::cout << x + y << std::endl;
};
lambda(); // 输出 30
按引用捕获所有变量
语法:[&]
捕获外部作用域中所有变量的引用。 特点:Lambda 内部可以修改外部变量。
int x = 10, y = 20;
auto lambda = [&]() {
x++;
y++;
std::cout << x + y << std::endl;
};
lambda(); // 输出 32
std::cout << x << ", " << y << std::endl; // 输出 11, 21
混合捕获
[var1, &var2]
示例:
int x = 10, y = 20;
auto lambda = [x, &y]() {
// x 是按值捕获,y 是按引用捕获
std::cout << "x = " << x << ", y = " << y << std::endl;
y++;
};
lambda(); // 输出 x = 10, y = 20
std::cout << y << std::endl; // 输出 21
默认捕获和显式捕获
[=]
或 [&]
)已经定义,还可以显式指定部分变量的捕获方式。int x = 10, y = 20, z = 30;
auto lambda = [=, &z]() {
// 默认按值捕获,但 z 是按引用捕获
std::cout << x << ", " << y << ", " << z << std::endl;
z++;
};
lambda(); // 输出 10, 20, 30
std::cout << z << std::endl; // 输出 31
无法捕获临时变量 捕获列表中只能捕获作用域中的变量,不能捕获临时对象或表达式结果。例如:
auto lambda = [x + 1]() {}; // 错误:不能直接捕获表达式
解决方法:通过初始化捕获:
auto lambda = [val = x + 1]() { std::cout << val << std::endl; };
初始化捕获(C++14 引入)
捕获列表中可以使用初始化捕获,为捕获的变量指定初值。语法为 [var = init_value]
:
int x = 10;
auto lambda = [val = x + 5]() { std::cout << val << std::endl; };
lambda(); // 输出 15
无法捕获静态变量 Lambda 不需要捕获静态变量,因为静态变量的生命周期贯穿整个程序。