首先向侯捷大佬致敬。
本文记录一个自定义的通用hash函数的写法,涉及了变参模板的用法
template<typename T> void hash_combine(size_t& seed, const T& val) { /*使得seed足够乱序而已*/ seed ^= std::hash<T>()(val)+0x9e3779b9 + (seed << 6) + (seed >> 2); } template<typename T> void hash_val(size_t& seed, const T& val){ hash_combine(seed, val); } template<typename T,typename... Types> void hash_val(size_t& seed, const T& val, const Types&... args){ hash_combine(seed, val); hash_val(seed, args...); } template<typename... Types> size_t hash_val(const Types... args){ size_t seed = 0; hash_val(seed, args...); return seed; } /*自定义类*/ class Customer { public: Customer(string f, string l, long num):fname(f),lname(l),no(num){}
bool operator==(const Customer& tmp)const { return this->no == tmp.no; } string fname; string lname; long no; }; /*hash仿函数*/ class CustomerHash_func { public: std::size_t operator()(const Customer& c)const { return hash_val(c.fname, c.lname, c.no); } };
测程序如下:
/* *unordered_set需要四个模板参数 *一般使用只需要填第一个,它表示容器中元素的类型 *当容器存放的类型是自定义类型,第二个参数就被填入,一个求hash值的hash函数 *第三个参数表示判断元素相等的准则,默认使用的是std::equal_to,里边就是==符号比较 *所以自定义类型需要重载这个符号 *最后一个参数是分配器,这里就不讨论了 */ unordered_set<Customer, CustomerHash_func> m_hashset; m_hashset.insert(Customer("c++", "program", 996L)); int bucket_cout = m_hashset.bucket_count(); /*预判Customer("c++", "program", 996L)将位于哪一个位置*/ CustomerHash_func test_hf; cout << "now bucket_cout is:" << bucket_cout << endl;; cout << "Customer("c++", "program", 996L) will be in bucket #" << test_hf(Customer("c++", "program", 996L)) % bucket_cout << endl; /*打印容器此时内部存储情况*/ for (unsigned int i = 0; i < bucket_cout; ++i) { cout << "bucket #" << i << " is:"; for (auto it = m_hashset.begin(i); it != m_hashset.end(i); ++it) { cout << "fname:" << it->fname << " " << "lname:" << it->lname << " " << " no:" << it->no; } cout << " "; }
控制台显示:
可以看出计算出来的hash值是5,bucket_cout方法可得出当前哈希表(unordered_set的内部容器)的桶长度,插一句嘴:哈希表又叫散列表,其结构没记错应该是vector加list,这个vector常常称为桶;也就是用vector的每个节点去挂上一条链表,链表就是hash冲突形成的,此时如果再次插入一个Customer且其算出来的hash值等于5,那它就会继续被挂在“五号桶”上,和之前挂的Customer("c++","program",996L)形成一条两个节点的链表;当这个链表太长也就是所谓的hash冲突过多就会导致rehash,rehash就是分两步,先扩充vector,然后将当前容器里所以的节点重新算hash值,然后挂到该挂的桶;我感觉自己已经说的挺直白了哈哈哈;
更多关于hash表的知识可参阅stl源码剖析,关于这本书网上有一些言论说太老不值得看,怎么说呢还是看人,技术大佬们肯定另说啦;当前时间为2020/11/19,算了一下18年有余,哈哈哈,刚刚了解到有此书时也稍受言论影响,过了一段时间后还是遵从本心去买来看看(技术较菜,时间也有,且不是很贵哈哈),一个原因也是当前市面上暂未发现有此类书籍,个人感觉对于学习理解stl有较大用处。
打印:
补充另外一种方法:
通过偏特化hash来实现,实现方法于上一种略有不同,熟悉偏特化的同学应该挺清楚的,效果与前边一致,有兴趣的同学可以自己调试下
template<> struct hash < Customer > { size_t operator()(const Customer& c)const { return hash_val(c.fname, c.lname, c.no); } }; /* *不需要自定义仿函数,还是使用默认的hash参数,但是通过偏特化自定义类型来起作用 *通过vs里f12跳进去看看原型就一目了然了 *效果和上边使用仿函数的方法一模一样 */ unordered_set<Customer> m_hashset_ex; m_hashset_ex.insert(Customer("c++", "program", 996L));