【C++】编程必备:深入解析仿函数与std::sort

一、仿函数介绍
假设我们要写一个加法函数,但是这个加法函数有点特别,它不仅需要给我们做加法运算,还需要记录这个函数总共运算了多少次,这个时候普通函数就不好实现了,需要用到仿函数,接下来我们先学习一下仿函数,最后我们再来解决这个问题
仿函数的本质是 “穿了函数外衣的类”,因为在C++中,类可以重载运算符,我们把一个类的“()”运算符重载一下,然后在这个重载函数中实现特定的功能,比如做加法运算,那么我们是不是就可以把这个类的对象当函数一样使用
这样说大家可能有点懵,我们还是从实际出发,给大家简单写一个仿函数,我们参照这个仿函数来学习,如下:
//一个仿函数(本质是一个重载了operator()的类)
struct AddFunctor
{
//这个类重载了operator()之后
//这个类的对象就可以当做函数使用,所以叫“仿”函数
int operator()(int x, int y)
{
return x + y;
}
};
上面给出的就是一个简单的仿函数类了,它的对象可以调用operator()来实现加法运算,就像一个函数一样,仿函数一般的用法有两种,一种是实例化一个对象出来使用,一种方法就是直接使用匿名对象使用,如下:
#include <iostream>
//一个仿函数(本质是一个重载了operator()的类)
struct AddFunctor
{
//这个类重载了operator()之后
//这个类的对象就可以当做函数使用,所以叫“仿”函数
int operator()(int x, int y)
{
return x + y;
}
};
int main()
{
//用法一:实例化一个对象来使用
AddFunctor add;
//由于重载了operator(),所以对象可以像函数那样使用
std::cout << "用法一:" << add(1, 2) << std::endl;
//用法二:使用匿名对象调用
std::cout << "用法二:" << AddFunctor()(1, 2) << std::endl;
return 0;
}
在上面的示例中我们分别采用了两种方法来使用仿函数,我们先来看看代码运行结果如何:
可以看到两种使用方式都能成功得到正确答案,那么这种仿函数写起来也没有函数简单呀,它的好处在哪里呢?这下我们就要回归上面我们最开始举的例子了
函数不能携带状态,比如上面的加法使用了多少次这种状态,而仿函数可以携带函数,因为它的本质还是一个类,所以它可以有自己的成员变量,可以使用成员变量实现,如下:
#include <iostream>
//一个仿函数(本质是一个重载了operator()的类)
struct AddFunctor
{
//这个类重载了operator()之后
//这个类的对象就可以当做函数使用,所以叫“仿”函数
int operator()(int x, int y)
{
//在返回之前让count++,表示执行了一次加法运算
count++;
return x + y;
}
//成员变量
static int count;
};
int AddFunctor::count = 0;
int main()
{
//用法一:实例化一个对象来使用
AddFunctor add;
//由于重载了operator(),所以对象可以像函数那样使用
std::cout << "用法一:" << add(1, 2) << std::endl;
//用法二:使用匿名对象调用
std::cout << "用法二:" << AddFunctor()(1, 2) << std::endl;
std::cout << "进行了" << add.count << "次加法运算" << std::endl;
return 0;
}
在上面的例子中我们感觉到了仿函数功能更加强大, 并且仿函数还可以轻松传做模板参数,用来实现特定功能
二、 常见仿函数(greator与less)
在std中有两个非常常见的仿函数,也就是greater与less,它们常常用来控制某些需要比大小的场景,我们来看看它们的原型,如下:

它们都属于类模板,可以根据传参去比较内置类型数据的大小,至于自定义类型如日期类Date则需要自己实现比大小的仿函数,不能使用greater和less,我们等下以sort为例进行学习
三、sort
sort是C++算法库中的一个排序算法,底层采用了自省排序的逻辑,因为我们知道虽然快排的综合性能很好,但是在某些特定的场景却很慢,比如数据原本就有序或者有大量重复数据的时候就很影响排序
而自省排序就很好地解决了这一点,它默认选择快排对数据进行排序,但是它的内部会以某种方式记录进行递归的深度检测,如果递归的层次太深,那么就会立即停止快排,转而使用堆排
然后当数据量较小时,就会采用插入排序优化排序效率,总之自省排序适用于各种需要对数据进行排序的通用场景,尤其在数据规模较大且对最坏情况性能有要求时表现出色,这也是它被用于 C++ 标准库 std::sort 的原因
接下来我们开始正式介绍它的使用,同时穿插greater和less的讲解,greater和less可以决定sort是排降序还是排升序,如果传greator给sort就是降序,传less就是升序,我们来看看sort函数原型,如下:
sort有两个重载,第一个重载不需要第三个参数就是默认升序,而第二个重载我们需要传第三个参数,也就是仿函数对象过去控制
这样做的好处有两点,首先就是用户可以自己控制升降序了,其次就是如果碰到一些特殊的比较方式,用户就可以自己写一个仿函数,然后把它的对象传过去
大家可能有点懵,接下来我们来点实战演示一下,就以sort为例,首先是sort按照默认排序方式排序:
#include <iostream>
#include <vector>
#include <algorithm>
int main()
{
std::vector<int> v = { 2, 1, 6,3,4,7,4 };
//使用sort需要包含头文件algorithm
sort(v.begin(), v.end());
for (auto e : v)
{
std::cout << e << " ";
}
std::cout << std::endl;
return 0;
}
我们来看看代码运行结果:
可以看到sort默认是升序排序,如果我们想要它实现降序排序呢?那么我们可以给它传第三个参数,也就是一个仿函数对象给它,可以自己实现,也可以直接用库里面的greator,如下:
#include <iostream>
#include <vector>
#include <algorithm>
int main()
{
std::vector<int> v = { 2, 1, 6,3,4,7,4 };
//使用sort需要包含头文件algorithm
//sort(v.begin(), v.end());
sort(v.begin(), v.end(), std::greater<int>());
for (auto e : v)
{
std::cout << e << " ";
}
std::cout << std::endl;
return 0;
}
我们来看看代码运行结果:
可以看到数据确实按照降序进行排列了,当然如果把greater换成less,那么跟默认的情况就差不多了,所以如果是升序我们一般不显示地传递less
那么关于sort和greater、less的基础知识就讲解到这里,接下来我们会介绍需要我们手动写仿函数的排序场景
四、较为复杂的sort场景
在一些较为复杂的排序场景中,往往不是内置类型的变量进行排序,而是一堆自定义类型进行排序,比如我们对一堆日期进行排序,那么std的greater和less就做不到了,这个时候就需要我们手动去写一个仿函数来实现对应的要求
接下来我给大家提供一个重载了operator<和operator>的日期类,大家可以直接拷贝,方便后序我们学习,这个日期类也是我们之前类和对象写过的,如果想了解完整的日期类的实现可以看如下博客:【C++】类与对象初级应用篇:打造自定义日期类与日期计算器(2w5k字长文附源码)
#include <iostream>
#include <vector>
#include <algorithm>
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
,_month(month)
,_day(day)
{}
bool operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
bool operator<(const Date& d)
{
if (_year < d._year)
return true;
else if (_year == d._year && _month < d._month)
return true;
else if (_year == d._year && _month == d._month)
return _day < d._day;
return false;
}
bool operator>(const Date& d)
{
return !(*this < d) && !(*this == d);
}
void Print()
{
std::cout << _year << "年" << _month << "月" << _day << "日" << " ";
}
private:
int _year;
int _month;
int _day;
};
同时假设我们有如下4个日期,我们该如何让sort帮我们进行排序呢?
std::vector<Date> v = { {2025, 6, 1}, {2014,2, 12}, {2014, 9, 10}, {2025, 5, 1} };
此时我们就不能依赖库里面的greater和less了,需要我们自己分别实现两个仿函数,实现方法也很简单,因为日期类内部已经帮我们实现好了比大小的逻辑,我们直接使用就好了,如下:
struct DateGreater
{
bool operator()(Date& d1, Date& d2)
{
return d1 > d2;
}
};
struct DateLess
{
bool operator()(Date& d1, Date& d2)
{
return d1 < d2;
}
};
接下来我们就分别来测试一下我们写的这两个仿函数:
#include <iostream>
#include <vector>
#include <algorithm>
int main()
{
std::vector<Date> v = { {2025, 6, 1}, {2014,2, 12}, {2014, 9, 10}, {2025, 5, 1} };
sort(v.begin(), v.end(), DateGreater());
for (auto& e : v)
{
e.Print();
}
std::cout << std::endl;
return 0;
}
我们来看看代码运行结果是否是一个升序排列,如下:
可以看到没有问题,接下来我们测试降序:
#include <iostream>
#include <vector>
#include <algorithm>
int main()
{
std::vector<Date> v = { {2025, 6, 1}, {2014,2, 12}, {2014, 9, 10}, {2025, 5, 1} };
//sort(v.begin(), v.end(), DateGreater());
sort(v.begin(), v.end(), DateLess());
for (auto& e : v)
{
e.Print();
}
std::cout << std::endl;
return 0;
}
我们来看看运行结果:
可以看到代码运行结果如我们所料,就是将日期升序排列,那么我们就以日期类为例给大家讲解了sort如何解决一些复杂的比较场景,方法就是自己写一个仿函数传给sort
那么今天关于仿函数和sort的知识就讲到这里,有什么不懂欢迎私信问我,我会及时做出解答,下一篇文章开始我们学习priority_queue了,敬请期待吧!
bye~
更多推荐

所有评论(0)