一、起始
众所周知,map是STL库中常用的关联式容器,底层实现就不多提了是平衡二叉树,今天主要关注的是map的KEY值,观看std::map源码如下:
template<class _Kty, class _Ty, class _Pr = less<_Kty>, class _Alloc = allocator<pair<const _Kty, _Ty>>> class map : public _Tree<_Tmap_traits<_Kty, _Ty, _Pr, _Alloc, false>> { // ordered red-black tree of {key, mapped} values, unique keys public:map有四个参数,第一个为_Kty就是key,第二个_Ty就是value,第三、四都有默认值,所以在一定的条件下可以不填。
二、问题
可以看到map的定义中 _Kty和_Pr都可以为任意类型,对与基本类型的map数组定义如下:
std::map<int, double> int_to_double; std::map<char, int> char_to_int;不需要任何多余的操作,就能直接使用,当KEY为自定义时,情况就不一样了,代码如下:
#include <map> struct customize { int _id; int _sum; }; int main() { std::map<customize, int> customize_to_int; system("pause"); return 0; }编译发现能够通过,运行也没有问题,这是为什么呢?
这是因为map是泛型编程实现的,所以未使用的函数实际上不会编译(根据数据类型生成函数代码段),所以使用stl库时有时候一些错误的用法,不是非常明显,只有使用了具体的函数才会显现出来,例如我们使用如下:
customize_to_int.insert(std::pair<customize, int>(customize(), 0));编译结果如下:
这就是map中第三个参数的作用,提供一个less函数,比较key值间的大小,从而构建二叉树,有人问了为什么基本类型就不需要呢,这是因为基本类型可以直接进行大小比较,我们看出错的源码如下:
constexpr bool operator()(const _Ty& _Left, const _Ty& _Right) const { // apply operator< to operands return (_Left < _Right); // 报错地方 } };显然让两个int型进行大小比较不会有任何问题,但是如果是自定义类型就不行了。
三、解决方法
这就是map第三个参数的作用了
需要我们提供一个比较大小的仿函数,仿函数就是类似于函数的类,不过大都是重载了一些操作符如'()'号、'<'号等
完成仿函数如下:
struct hash_function { bool operator ()(const customize &c1, const customize &c2) const { if (c1._id < c2._id) { return true; } if (c1._sum < c2._sum) { return true; } return false; } }; int main() { std::map<customize, int, hash_function> customize_to_int; }好了万事大吉了,仿函数也提供了,博客也水完了,应该可以关机去追番剧了吧,等等等.......
测试代码如下:
int main() { std::map<customize, int, hash_function> customize_to_int; for (int i = 0; i < 3; i++) { customize cus; cus._id = 0; cus._sum = i; customize_to_int.insert(std::pair<customize, int>(cus, i)); } customize cus; cus._sum = 0; cus._id = 1; customize_to_int.insert(std::pair<customize, int>(cus, 3)); for (int i = 0; i < 3; i++) { customize cus; cus._sum = i; cus._id = 0; auto iter = customize_to_int.find(cus); if (iter != customize_to_int.end()) { std::cout << iter->second << std::endl; } } system("pause"); return 0; }运行结果如下:
WFK!!!这是什么情况,崩溃了!!!只是插入几个数随便输出一下数据就崩溃了,好吧,老老实实调试一下:
可以看到现在map中已经有了3个数据 KEY(id-sum)为0-0,0-1,0-2,待插入数据KEY(id-sum)1-0时崩溃。
这是为什么呢,std::map内部会进行反向比较。因为我们写的比较函数STL怎么知道对不对呢,万一错了,不是会造成我们把锅甩给STL这多不好啊。所以STL比较两个KEY大小时,会是这样先输入key1(0-1)和key2(1-0) 经过我们的less仿函数取得结果,再传入key1(1-0)和key2(0-1)取得结果,进行比较。这时我们发现带入值会得出传入数据key1、key2顺序时仿函数得到结果是true,传入key2、key1顺序时仿函数得到结果也是true。看到这里就明白了,不一致,key1既大于key2又小于key2,这显然不对,所以在debug模式下STL报错崩溃了(debug慢也是有道理的),成功拯救了一次难调的异常,感动到想哭。
既然是仿函数矛盾,那么就解决吧:
struct hash_function { bool operator ()(const customize &c1, const customize &c2) const { if (c1._id != c2._id) { return c1._id < c2._id; } if (c1._sum != c2._sum) { return c1._sum < c2._sum; } return false; } };经过修改的仿函数如上:
运行结果如下:
终于出现了0,1,2,不容易啊,终于没有矛盾了。
李四君终于可以愉快的去追番剧了,下次水博客时再见。
