C++初阶-仿函数
仿函数是C++的一种特殊的对象它可以像函数一样被调用。本质上,仿函数是一个重载了 operator()
的类或结构体。
仿函数并不是一个函数,而是一个类,只是类似函数一样可以被调用而已,但是这个类或结构体里面只有一个函数:重载函数调用操作符operator(),这种说法并不是很全面,因为仿函数如果只有一个函数的话可能不是那么稳定,可以在这个类里面定义一些成员变量使得该类保持状态。也可以用模板来延伸它更多的用法。仿函数可以:
//(1)
template<class T>
class big
{
public:T operator()(T x, T y) const{return x > y ? x : y;}
};
//(2)
template<class T>
class Less
{
public:bool operator()(T x, T y) const{return x < y;}
};
我们也可以不把它写成模板,直接写成普通类也是可以的!
对于上述代码,我们可以测试一下:
#include<iostream>
using namespace std;
int main()
{big<int> B;cout << B(5, 10) << endl;cout << B.operator()(39, 45) << endl;Less<int> l;if (l(4, 5)){cout << "4<5" << endl;}else{cout << "4>5" << endl;}return 0;
}
那么运行结果就是:
这个仿函数调用如big B;那么B(3,4)相当于B.operator()(3,4);在堆这种可以排序的容器也会用到仿函数,仿函数在我们现在看了用处确实不是很大,比如:我们比较大小直接用x<y这种就可以了,为什么还要用一个类,其实这个仿函数相当于函数优势可大多了。
最常用的一个方面就是在容器中作为模板参数,比如我们之前学过的堆:
其中的Compare就是用来接收仿函数的,如果学到后期的map和set这种高阶的容器:
都会用到仿函数,到我们学到map和set的时候,仿函数就是我们写的比较多的一个部分了,现阶段我们仅仅只是通过堆来简单了解了这个仿函数,比如我之前写的一篇博客:
C++初阶-priority_queue(优先级队列)(堆)-CSDN博客文章浏览阅读274次,点赞5次,收藏6次。在C++官网中,链接为:搜索queue,会出现:在左下角可以看到priority_queue这个容器,它和queue放在同一个头文件,只要在使用它时#include 即可使用这个容器。我们知道queue是队列,在数据结构中,队列是有先进先出的性质的,而在priority_queue(优先级队列)中也有同样的性质,但是它是优先级高的先出,默认情况下是大的优先级高,不过我们可以通过仿函数让小的优先级高。通过这些性质我们发现这个容器很像我们之前学过的数据结构-堆,实际上它的底层就是堆。https://blog.csdn.net/2401_86446710/article/details/149242986?spm=1011.2415.3001.10575&sharefrom=mp_manage_link这个就是接触仿函数的一个比较重要的部分。
此外,仿函数是对象,可以存储内部状态,而普通函数是无状态的(static 变量除外)。
struct Counter
{int count = 0;void operator()() { ++count; std::cout << "调用次数: " << count << std::endl;}
};Counter c;
c(); // 输出 "调用次数: 1"
c(); // 输出 "调用次数: 2"(普通函数无法实现这种计数)
这个仿函数可以通过存储成员变量来得到调用仿函数的次数,这个在普通函数时只能用static静态变量来实现,这表明仿函数:可携带状态。
仿函数可以为类型,故可以作为模板参数:STL 算法和容器(如 std::sort
、std::priority_queue
)要求比较函数必须是类型,而仿函数是类型,普通函数不行:
//可以
std::priority_queue<int, std::vector<int>, std::greater<int>> min_heap;
//不可以
bool compare(int a, int b) { return a > b; }
// std::priority_queue<int, std::vector<int>, compare> heap;
所以表明仿函数:可作为模板参数。
此外,我们在C++进阶会学到:多态,在多态中用仿函数的次数是比较多的,可以说多态中没有仿函数就不会产生多态,比如:
struct Base
{virtual void operator()() = 0;
};
struct Derived : Base
{void operator()() override { std::cout << "Derived\n"; }
};Base* f = new Derived();
(*f)();
那么这个代码的运行结果就是:Derived
所以仿函数:支持多态。
STL 算法直接支持仿函数,而函数指针需要适配,如:
std::sort(v.begin(), v.end(), std::greater<int>()); // 仿函数
std::sort(v.begin(), v.end(), [](int a, int b) { return a > b; }); // Lambda(本质是仿函数)bool compare(int a, int b) { return a > b; }
std::sort(v.begin(), v.end(), compare); // 函数指针(需退化为指针)
在C++11将进行讲解:Lambda,这个也是仿函数,可以说仿函数比普通函数好太多,以后的很多地方都要用到仿函数!
那么哪些情况要用到仿函数还是用普通函数?
-
用仿函数:需要状态、作为模板参数、高性能场景
-
用普通函数:简单无状态操作、C风格API兼容
仿函数是C++泛型编程的核心工具之一,理解其优势能显著提升代码质量和性能。
但在C++11之后引入了Lambda表达式,可以用Lambda表达式替代一些仿函数,在一些简单的场景下我们可以用之后学的Lambda表达式来解决一部分可以用仿函数解决的问题,但是这个Lambda部分可能要放到C++进阶最后才能进行讲解了,不过在学完C++之后可以将仿函数与 Lambda 表达式互补使用。Lambda并不是现阶段能讲解的,如果感兴趣的可以到其他博客去了解哦。
仿函数的更多知识我将在C++进阶部分讲解,因为之后的C++进阶部分用仿函数更多。
好了,这一讲就到这里,下一讲将讲解:C++初阶-模板进阶。喜欢的可以一键三连哦,下讲再见!