问题分析
对于unordered_map而言,当我们插入<key, value>的时候,需要哈希函数的函数对象对key进行hash,又要利用等比函数的函数对象确保插入的键值对没有重复。然而,当我们自定义类型时,c++标准库并没有对应的哈希函数和等比函数的函数对象。因此需要分别对它们进行定义。
STL unordered_map 官方定义
template<class Key,
class Ty,
class Hash = std::hash<Key>,
class Pred = std::equal_to<Key>,
class Alloc = std::allocator<std::pair<const Key, Ty> > >
class unordered_map;
> class unordered_map
- 第一个参数 key 值
- 第二个参数 mapped value
- 第三个参数 存储哈希函数的函数对象,并利用函数对象中的哈希函数返回类型为size_t的唯一哈希值
- 第四个参数 等比函数的函数对象。它内部通过等比操作符’=='来判断两个key是否相等,返回值为bool类型
重载方法
- 构建函数实例
#include <iostream>
#include <unordered_map>
#include <string>
#include <functional>
using namespace std;
class Person{
public:
string name;
int age;
Person(string n, int a){
name = n;
age = a;
}
bool operator==(const Person & p) const
{
return name == p.name && age == p.age;
}
};
size_t person_hash( const Person & p )
{
return hash<string>()(p.name) ^ hash<int>()(p.age);
}
int main(int argc, char* argv[])
{
//ERRO: unordered_map<Person,int,decltype(&person_hash)> ids;
//ERRO: unordered_map<Person,int,person_hash> ids(100, person_hash );
unordered_map<Person, int, decltype(&person_hash)> ids(100, person_hash); ////需要把person_hash传入构造函数
// C++11新增的关键字decltype。它可以直接获取自定义哈希函数的类型,并把它作为参数传送。
ids[Person("Mark", 17)] = 40561;
ids[Person("Andrew",16)] = 40562;
for ( auto ii = ids.begin() ; ii != ids.end() ; ii++ )
cout << ii->first.name
<< " " << ii->first.age
<< " : " << ii->second
<< endl;
return 0;
}
- 重载operator() 类
利用重载operator()的类,将哈希函数打包成可以直接调用的类
#include <iostream>
#include <string>
#include <unordered_map>
#include <functional>
using namespace std;
class Person{
public:
string name;
int age;
Person(string n, int a){
name = n;
age = a;
}
bool operator==(const Person & p) const
{
return name == p.name && age == p.age;
}
};
struct hash_name{
size_t operator()(const Person & p) const{
return hash<string>()(p.name) ^ hash<int>()(p.age);
}
};
int main(int argc, char* argv[]){
unordered_map<Person, int, hash_name> ids; //不需要把哈希函数传入构造器
ids[Person("Mark", 17)] = 40561;
ids[Person("Andrew",16)] = 40562;
for ( auto ii = ids.begin() ; ii != ids.end() ; ii++ )
cout << ii->first.name
<< " " << ii->first.age
<< " : " << ii->second
<< endl;
return 0;
}
- 模板定制
#include <iostream>
#include <unordered_map>
#include <string>
#include <functional>
using namespace std;
typedef pair<string,string> Name;
namespace std {
template <> //function-template-specialization
class hash<Name>{
public :
size_t operator()(const Name &name ) const
{
return hash<string>()(name.first) ^ hash<string>()(name.second);
}
};
};
int main(int argc, char* argv[])
{
unordered_map<Name,int> ids;
ids[Name("Mark", "Nelson")] = 40561;
ids[Name("Andrew","Binstock")] = 40562;
for ( auto ii = ids.begin() ; ii != ids.end() ; ii++ )
cout << ii->first.first
<< " " << ii->first.second
<< " : " << ii->second
<< endl;
return 0;
}
当我们将模板订制包含在定义类的头文件中时,其他人无需额外工作,就可以直接用我们的类作为任何无序容器的键。这对于要使用我们自定义类的人来说,绝对是最方便的。如果你想要在多个地方用这个类,方法3是最好的选择。当然,你要确保自己的hash function不会影响std空间里的其他类。
#include <iostream>
#include <string>
#include <unordered_map>
#include <functional>
using namespace std;
class Person{
public:
string name;
int age;
Person(string n, int a){
name = n;
age = a;
}
};
namespace std{
template<>
struct hash<Person>{//哈希的模板定制
public:
size_t operator()(const Person &p) const
{
return hash<string>()(p.name) ^ hash<int>()(p.age);
}
};
template<>
struct equal_to<Person>{//等比的模板定制
public:
bool operator()(const Person &p1, const Person &p2) const
{
return p1.name == p2.name && p1.age == p2.age;
}
};
}
int main(int argc, char* argv[]){
unordered_map<Person, int> ids;
ids[Person("Mark", 17)] = 40561;
ids[Person("Andrew",16)] = 40562;
for ( auto ii = ids.begin() ; ii != ids.end() ; ii++ )
cout << ii->first.name
<< " " << ii->first.age
<< " : " << ii->second
<< endl;
return 0;
}