变参模板之万用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来实现,实现方法于上一种略有不同,熟悉偏特化的同学应该挺清楚的,效果与前边一致,有兴趣的同学可以自己调试下

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));

  

 

posted @ 2020-11-19 15:24  乐swap火  阅读(379)  评论(0编辑  收藏  举报