关于STL的话题已经探讨了好几个篇了,今天来讨论一下剩下的一些内容吧
一个万用的hash function
使用以Hash Table为底层的容器,比如unordered_map(hash_map),在使用个过程中,需要有计算hash code的方法来计算出元素在hashtable中的具体位置。
那么对于自定义类型来说,需要指定对应的Hash Function,只有这样才能在使用的过程,系统找到对应元素应该插入的位置,以便系统自动管理我们需要插入到容器中的各个元素。关于这个函数该如何定义,对于不同个自定义类型来说并不相同,也没有办法找到一个通用的方式,毕竟石头类和动物类,他们各自的hash function多少会有些区别吧,具体的定义,还需要依靠开发这自己了。
那么,为什么后还会有关于个万用的hash function呢?关于这个问题,先放下不说,咱们先来看看关于指定hash function的方式吧。
- 指定自定义的hash function的方法一
#include<functional>
class Customer{
//........
};
//这是一个自定义的类,如果使用他作为hashtable为底层的容器时
//系统需要知道如何把他插入到hashtable中的具体位置去
//通过一个类,重载了操作符()的方式,形成了一个仿函数(函数对象)
class CustomerHash
{
public:
std::size_t operator()(const Customer& c) const{
return /*........*/;
}
};
//使用
unordered_set<Customer, CustomerHash> customers;
//声明容器变量时,指定的第二参数,就是对应的Hash Function
//实际在第二参数中指定的是一个函数对象
- 指定自定义的hash function的方法二
size_t customer_hash_func(const Customer& c)
{
return /*......*/;
}
//使用
unorder_set<Customer, size(*) (const Cunstomer&)> customers(20, customer_hash_func);
//这个方法实际是通过指定了一个函数指针的方式来通知容器,对于这个自定义类到底应该使用哪个hash function
- 指定自定义的hash function的方法三
//以struct hash 偏特化形式实现hash function
class MyString
{
private:
char* _data;
size_t _len;
};
namespace std;
{
template<>
struct hash<MyStrinng>
{
size_t operatoe()(const MyString& s) const noexcept{
return hash<string>()(string(s.get()));
}
}
}
那么通过这三种方式可以指定需要的hash function,但是到底能不能有一个万能的hash function呢?不论自定义的的class,内涵的成员是由什么组成,总归都是有基本数据类型所组成(比如int、char、double等),而之前我们也提过,基本数据类型,STL是提供了一套Hash Function的实现代码的。那么,能否依靠之前我们提到的方法将hash code相加来获取该对象的hash code呢?(例如下方代码)
class CustomerHash
{
public:
std::size_t operator()(const Customer& c) const{
return std::hash<std::string>()(c.fname)
+ std::hash<std::string>()(c.Iname)
+ std::hash<long><c.no);
}
}
//这个方法可以实现出计算Hash code的功能,但是,不得不提出的就是
//这个方法其实对于hash code的重复概率比较高,因为只是简单的相累加嘛。而重复的hash code 会导致“bucket”中的元素过多,影响查询的效率
其实在 C++ TR1 的版本后,STL为我么提供了一套万用的Hash Function。那么应该如何使用呢?咱们就先来看看吧。
//使用万能的Hash Function
class CustomerHash
{
public:
std::size_t operator()(const Cunstomer& c) const {
return hash_val(c.fname, c,Iname, c.no);
//在此,只需要调用hash_val函数,并把每个成员的值传递给他就行了
}
}
咱们看过了这个万用Hash Functon的使用,那么他其中的实现原理到底是什么呢?咱们来看看他的代码吧。
#include<functional>
template<typename T>
inline void hash_combine(size_t& seed, const T& val){
seed = std::hash<T>(val) + 0x9e3779b9
+ (seed << 6) + (seed >> 2);
}
template<typename T>
inline void hash_val(size_t& seed, const T& val){
hash_combine(seed, val);