在C++之前,如果想对一个数据集合中的元素进行排序,可以用std::sort
方法:
排序规则定义方法:
void(*ptr)(int x)
,用起来复杂难读,能不用就不用operator()
,使得对象可以像函数一样使用仿函数定义排序规则如:,
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; });
}
上述代码就是使用C++11中的lambda表达式来解决,可以看出lambda表达式实际是一个匿名函数,在函数内部直接定义使用。
对比仿函数,使用lambda表达式可以使得代码更简洁,可读性更好。
lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement }
lambda表达式各部分说明:
[capture-list]
:捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]
来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。(parameters)
:参数列表,与普通函数的参数列表一致,如果不需要参数传递,则可以连同()
一起省略 mutable
:默认情况下,lambda函数总是一个const函数,mutable
可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。int x = 10;
auto lambda = [x]() mutable { x++; cout << x; };
lambda(); // 输出11
cout << x; // 输出10(外部x未改变)
->returntype
:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。{statement}
:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空。
因而最简单的lambda函数为:[]{}
该lambda函数不能做任何事情。
捕捉列表描述了上下文中哪些数据可以被lambda使用?以及使用的方式传值还是传引用?
传值:拷贝一份变量的副本到lambda中,该副本具有常性,在lambda函数体中不能修改。若要修改,在捕捉列表后声明关键字mutable,就可以修改了,但不会影响被捕捉的变量。 传引用:对该变量取别名,lambda函数体中对其修改,会影响被捕捉的变量。
【注意】
int main()
{
auto f1 = [](int x, int y) {return x + y; };
auto f2 = [](int x, int y) {return x + y; };
//f1 = f2;
//打印f1和f2的类型
cout << typeid(f1).name() << endl;
cout << typeid(f2).name() << endl;
return 0;
}
其实,lambada和范围for是类似的:用起来方便舒服,其实底层原理很简单。意义都是:方便人们使用,人性化处理。
实际上,编译器在底层对于lambda表达式的处理方式,完全就是按照仿函数的方式处理的,就是在类中对()
运算符进行了重载的类对象。
下面我们来验证一下:
class Rate
{
public:
Rate(double rate) : _rate(rate)
{}
double operator()(double money, int year)
{
return money * _rate * year;
}
private:
double _rate;
};
int main()
{
// 仿函数
double rate = 0.49;
Rate r1(rate);
r1(10000, 2);
// lambda
auto r2 = [=](double monty, int year)->double {return monty * rate * year; };
r2(10000, 2);
return 0;
}
调试代码并转到反汇编,可以看到:
本质上就是因为lambda表达式在底层被转换成了仿函数。