在这里插入图片描述

一、仿函数介绍

假设我们要写一个加法函数,但是这个加法函数有点特别,它不仅需要给我们做加法运算,还需要记录这个函数总共运算了多少次,这个时候普通函数就不好实现了,需要用到仿函数,接下来我们先学习一下仿函数,最后我们再来解决这个问题

仿函数的本质是 “穿了函数外衣的类”,因为在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~

更多推荐