到目前为止,我们已经把有序关联式容器全部学习完了,读者是否能够完整地枚举它们并清晰地区分它们呢?它们分别是:map容器、multimap容器、set容器和multiset容器。它们都有一个共同的特点,就是能够在元素插入时按照默认的排序规则将键(key)排序(默认都是升序排序),那么如何更改有序关联式容器的排序规则呢?对于这个问题,我推荐使用仿函数来实现。什么是仿函数,读者心里可能一头雾水。简单来说,仿函数就是把对象当函数使用,其实也没有什么深奥的,基本知识就是充分利用对象和operator'()'重载这个概念。下面我将通过代码的方式为读者详细介绍该方法:
1. 什么是仿函数?(基础)
把对象当函数使用,本质是重载对象内‘()’函数调用运算符,调用方式与调用函数无异,看起来就像对象在模仿函数”行为“,故称作”仿函数“。
#include <iostream> using namespace std; /*仿函数*/ /*充分利用struct的public特性*/ struct bigger { bool operator()(int a,int b)const { return a>b; } }; void test() { bigger bgr; if(bgr(1,0))cout << "我们通过仿函数实现1>0的比较\n"; } int main() { test(); return 0; }
编译结果如下:
通过对象bigger我们调用'()'实现比较功能,成功实现1>0这个判断。
2. 在学会仿函数的基础下,读者要知道这么一个库——<functional>,它被称为STL库里面的”函数对象头文件“,通过调用里面的模板对象实现仿函数的功能,我们也可以把它视为”模板化的仿函数“。(扩展)
#include <iostream> #include<functional> using namespace std; /*学习函数对象库*/ void test() { greater<int> gtr; less<int> les; if(gtr(1,0))cout << "通过对象greater实现比较行为:" << "\t1>0\n"; if(les(0,1))cout << "通过对象less实现比较行为:"<< "\t0<1\n" ; } int main() { test(); return 0; }
编译结果如下:
学习使用这个库能够提高编程效率,读者需要走出舒适圈,适应这个函数对象库。
3. 有了仿函数这个概念,我们就可以通过仿函数更改有序关联式容器的默认排序规则。值得注意的是,自定义排序是容器的一部分属性,需要在容器创建之初就指定排序规则,如果容器创建后再”制定规则“则无法成功更改。(实操)
#include <iostream> #include<functional> #include<map> #include<set> #include<utility> using namespace std; /*通过仿函数更改有序关联式容器的默认排序规则*/ struct cmp { bool operator()(int a,int b) { return a>b; } }; void test() { /*创建之初就要指定排序规则*/ /*既可以通过仿函数又可以通过functional库*/ /*map*/ map<int,int,cmp> mp{{1,1},{2,2},{3,3}}; cout << "遍历map容器: " ; for(auto it = mp.begin();it!=mp.end();++it) cout << "key="<< it->first << "&&value=" << it->second << " "; cout << '\n'; /*multimap*/ multimap<int,int,cmp> mmp{{1,1},{2,2},{3,3}}; cout << "遍历multimap容器: " ; for(auto it = mmp.begin();it!=mmp.end();++it) cout << "key="<< it->first << "&&value=" << it->second << " "; cout << '\n'; /*set*/ set<int,greater<int>> s{1,2,3}; cout << "遍历set容器: " ; for(auto it = s.begin();it!=s.end();++it) cout << *it << " "; cout << '\n'; /*multiset*/ multiset<int,greater<int>> ms{1,2,3}; cout << "遍历multiset容器: " ; for(auto it = ms.begin();it!=ms.end();++it) cout << *it << " "; } int main() { test(); return 0; }
编译结果如下:
我们分别通过cmp和greater实现有序关联式容器逆序排序,建议读者使用通用的<functional>对象,提高代码的可读性。
4. 当然,在使用有序关联式容器时,我们也可能遇到键值(key)为自定义数据类型,这时候需要我们重载比较运算符'<'实现自定义排序规则,切记有序关联式容器内默认是重载'<'而非'>',不要重载其他比较运算符。(进阶)
#include <iostream> #include<functional> #include<map> #include<set> #include<utility> using namespace std; /*如果是自定义数据类型,则可通过重载'<'更改有序关联式容器默认的排序规则*/ /*假如自定义数据类型是一个坐标,有坐标dire和步数step*/ struct node//public特性 { bool operator<(const node&n)const { return m_step<n.m_step; } node(int step,pair<int,int> dire): m_step(step),m_dire(dire){}; int m_step;//步数 pair<int,int> m_dire;//坐标 }; void test() { /*以map为例,其他multimap、set、multiset都是千篇一律的 */ map<node,int> mp{{{1,{1,1}},1},{{2,{2,2}},2},{{3,{3,3}},3}} ; cout << "遍历容器为:\n"; for(auto it = mp.begin();it!=mp.end();++it) { cout <<"key: " << "step=" << it->first.m_step << ",&&《" << it->first.m_dire.first<< "," << it->first.m_dire.second << "》 "<< "|value: " << it->second <<'\n'; } cout << '\n' ; } int main() { test(); return 0; }
编译结果如下:
当你想要逆序排序时,仅需在node里面更改排序规则即可!具体操作为将“return m_step<n.m_step;”改为“return m_step>n.m_step;”。
至此,你就能随心所欲地更改有序关联式容器的排序规则。
总结:本节主要讨论了如何更改有序关联式容器的排序规则,主要是通过仿函数进行操作,如果是自定义数据类型,则需重载对象内的operator'<'。
C语言网提供由在职研发工程师或ACM蓝桥杯竞赛优秀选手录制的视频教程,并配有习题和答疑,点击了解:
一点编程也不会写的:零基础C语言学练课程
解决困扰你多年的C语言疑难杂症特性的C语言进阶课程
从零到写出一个爬虫的Python编程课程
只会语法写不出代码?手把手带你写100个编程真题的编程百练课程
信息学奥赛或C++选手的 必学C++课程
蓝桥杯ACM、信息学奥赛的必学课程:算法竞赛课入门课程
手把手讲解近五年真题的蓝桥杯辅导课程