c++中std::hash以及万能hash的使用方式
作者:米安r
c++ std::hash以及万能hash的使用
首先是标准库中的std::hash函数,对于内置的类型,标准库中是已经提供了的(包括std::string),但是若是自己自定义的类型想要求其哈希值的话,就需要自己定义其哈希值的求值方式。
下面是简单示范
#include <map> #include <unordered_map> #include <unordered_set> #include <iostream> using std::cout; using std::endl; class MyClass { public: MyClass():str("hello"), data(0) {} bool operator==(const MyClass& rhs) const{return (data == rhs.data) && (str == rhs.str); } //注意要重载这个==, //因为unordered_set或者unordered_map //中需要对元素是否相同进行判断 public: // int data; std::string str; }; //注意这里是将自己写的偏特化也同样加入到std中,因为他的模板是在std里面的, //具体形式可以自己简单查看一下源码中的实现形式 //然后照着写一个自己的版本就行了。 namespace std { template<> struct hash<MyClass>: public __hash_base<size_t, MyClass> //标准库中有这个继承,查看一下其实只是继承两个typedef而已, //所以不写这个继承在这个例子中也是可以运行的 //但为了更好的使用这个hash,写上去会比较好 { size_t operator()(const MyClass& rhs) const noexcept //这个const noexpect一定要写上去 { return (std::hash<int>()(rhs.data)) ^ (std::hash<std::string>()(rhs.str) << 1); //当然,可以使用其他的方式来组合这个哈希值, //这里是cppreference里面的例子,产生的数够乱就行。 } }; } int main() { MyClass c; std::hash<MyClass> myHash; //创建一个函数对象 std::cout << myHash(c) << std::endl; //注意这第三个参数是typename _Hash = hash < _Value >, 是可写可不写的,因为他是有默认形式的,写出来就是这样 std::unordered_map<MyClass, char, std::hash<MyClass>> m; //这第三个参数 std::unordered_set<MyClass> s; //和上面的是一个意思,第二个参数是typename _Hash = hash < _Value >,可写可不写, 这里我是没写的。 s.insert(c); s.insert(c); std::cin.get(); }
另外一种方式是使用侯捷老师在讲的“万能哈希函数”原理只要明白可变模板参数的使用方法就不会太难,下面是他课上使用的代码
#include <string> using std::string; class Customer { public: string mFirstName; string mLastName; string mAge; Customer(string firstName, string lastName, string age):mFirstName(firstName),mLastName(lastName),mAge(age){} bool operator ==(const Customer& c) const { return (mFirstName == c.mFirstName && mLastName == c.mLastName && mAge == c.mAge); } }; class CustomerHash { public: std::size_t operator()(const Customer& c) const { return hash_val(c.mFirstName, c.mLastName, c.mAge); } template <typename... Types> size_t hash_val(const Types&... args)const { size_t seed = 0; hash_value(seed, args...); return seed; } template <typename T, typename... Types> void hash_value(size_t& seed, const T& firstArg, const Types&... args) const { hash_combine(seed, firstArg); hash_value(seed, args...); } template <typename T> void hash_value(size_t& seed, const T& val) const { hash_combine(seed, val); } template<typename T> void hash_combine(size_t& seed, const T& val) const { seed ^= std::hash<T>()(val) + 0x9e3779b9 + (seed << 6) + (seed >> 2); } }; int main() { std::unordered_multiset<Customer, CustomerHash> set; }
c++11中std::hash用法
哈希的定义
Hash,一般翻译做散列、杂凑,或音译为哈希,是把任意长度的输入(又叫做预映射pre-image)通过散列算法变换成固定长度的输出,该输出就是散列值。
这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来确定唯一的输入值。
简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。
哈希简介
- Hash算法可以将一个数据转换为一个标志,这个标志和源数据的每一个字节都有十分紧密的关系。
- Hash算法还具有一个特点,就是很难找到逆向规律。基本不可能从结果推算出输入,所以又称为不可逆的算法
- Hash算法是一个广义的算法,也可以认为是一种思想,使用Hash算法可以提高存储空间的利用率,可以提高数据的查询效率,也可以做数字签名来保障数据传递的安全性。所以Hash算法被广泛地应用在互联网应用中。
- Hash算法也被称为散列算法,Hash算法虽然被称为算法,但实际上它更像是一种思想。Hash算法没有一个固定的公式,只要符合散列思想的算法都可以被称为是Hash算法
常见的哈希算法
- MD4
- MD5
- SHA-1及其他
哈希的用途
- 文件校验
- 数字签名
- 鉴权协议
std::hash的测试示例
对于内置的类型,C++标准库中已经提供了std::hash函数计算哈希值,但如果是自己自定义的类型想求哈希值的话,则需要自己定义哈希值的求值方式。
#include <iostream> #include <functional> #include <string> #include <iomanip> int main() { std::string strInput = "Peaceful in the present world, quiet in the years"; std::hash<std::string> szHash; size_t hashVal = szHash(strInput); std::cout << std::quoted(strInput) << "'s hash=" << hashVal << "\n"; // char buffer1[] = "Everything is fine"; char buffer2[] = "Everything is fine"; std::string strBuffer1(buffer1); std::string strBuffer2(buffer2); std::hash<char*> ptrHash; std::hash<std::string> strHash; //C++14引入std::quoted用于给字符串添加双引号 std::cout << std::quoted(buffer1) << "'s hash<char*>=" << ptrHash(buffer1) << std::endl; std::cout << std::quoted(buffer2) << "'s hash<char*>=" << ptrHash(buffer2) << std::endl; std::cout << std::quoted(strBuffer1) << "'s hash<std::string>=" << strHash(strBuffer1) << std::endl; std::cout << std::quoted(strBuffer2) << "'s hash<std::string>=" << strHash(strBuffer2) << std::endl; //boolalpha是使bool型变量按照false、true的格式输出,如不使用该标识符,则会按照1、0的格式输出 std::cout << "same hashes:\n" << std::boolalpha; std::cout << "buffer1 and buffer2: " << (ptrHash(buffer1) == ptrHash(buffer2)) << '\n';//false std::cout << "strBuffer1 and strBuffer2: " << (strHash(strBuffer1) == strHash(strBuffer2)) << '\n';//true return 0; }
输出结果:
"Peaceful in the present world, quiet in the years"'s hash=1772844349
"Everything is fine"'s hash<char*>=3806338771
"Everything is fine"'s hash<char*>=1493957927
"Everything is fine"'s hash<std::string>=1559491232
"Everything is fine"'s hash<std::string>=1559491232
same hashes:
buffer1 and buffer2: false
strBuffer1 and strBuffer2: true
C++ 官方提供的 demo
链接地址:https://en.cppreference.com/w/cpp/utility/hash
#include <iostream> #include <iomanip> #include <functional> #include <string> #include <unordered_set> struct S { std::string first_name; std::string last_name; }; bool operator==(const S& lhs, const S& rhs) { return lhs.first_name == rhs.first_name && lhs.last_name == rhs.last_name; } // custom hash can be a standalone function object: struct MyHash { std::size_t operator()(S const& s) const noexcept { std::size_t h1 = std::hash<std::string>{}(s.first_name); std::size_t h2 = std::hash<std::string>{}(s.last_name); return h1 ^ (h2 << 1); // or use boost::hash_combine } }; // custom specialization of std::hash can be injected in namespace std template<> struct std::hash<S> { std::size_t operator()(S const& s) const noexcept { std::size_t h1 = std::hash<std::string>{}(s.first_name); std::size_t h2 = std::hash<std::string>{}(s.last_name); return h1 ^ (h2 << 1); // or use boost::hash_combine } }; int main() { std::string str = "Meet the new boss..."; std::size_t str_hash = std::hash<std::string>{}(str); std::cout << "hash(" << std::quoted(str) << ") = " << str_hash << '\n'; S obj = { "Hubert", "Farnsworth" }; // using the standalone function object std::cout << "hash(" << std::quoted(obj.first_name) << ", " << std::quoted(obj.last_name) << ") = " << MyHash{}(obj) << " (using MyHash)\n" << std::setw(31) << "or " << std::hash<S>{}(obj) << " (using injected std::hash<S> specialization)\n"; // custom hash makes it possible to use custom types in unordered containers // The example will use the injected std::hash<S> specialization above, // to use MyHash instead, pass it as a second template argument std::unordered_set<S> names = {obj, {"Bender", "Rodriguez"}, {"Turanga", "Leela"} }; for(auto& s: names) std::cout << std::quoted(s.first_name) << ' ' << std::quoted(s.last_name) << '\n'; }
输出结果:
hash("Meet the new boss...") = 1861821886482076440
hash("Hubert", "Farnsworth") = 17622465712001802105 (using MyHash)
or 17622465712001802105 (using injected std::hash<S> specialization)
"Turanga" "Leela"
"Bender" "Rodriguez"
"Hubert" "Farnsworth"
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。