变参模板之万用hash函数
首先向侯捷大佬致敬。
本文记录一个自定义的通用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 << "\n"; }
控制台显示:
可以看出计算出来的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来实现,实现方法于上一种略有不同,熟悉偏特化的同学应该挺清楚的,效果与前边一致,有兴趣的同学可以自己调试下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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)); |
纯属记录程序人生,如有差错,欢迎指正,轻喷
标签:
c++
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了