c++11 unordered_map自定义key

一、unordered_map简介

unordered_map 是用哈希函数组织的map,采用哈希表存储数据,元素的位置取决于计算的散列值,是关联容器的一种。unordered_map使用一个哈希函数和关键字类型的==运算符(==运算符用来当发生碰撞时比较关键字key)来组织元素。

unordered_map的元素是关键字 - 值(key-value)对,无序保存元素,查询效率较高。在无序容器中,元素没有明确的排列次序,我们只关心的是,某个特定元素是否位于容器内。

元素值或其insert顺序,都不影响元素的位置,而且元素的位置有可能在容器生命中被改变。比如你放n个元素到这种集合内,它们的次序不明确,并且可能随时间而改变。

哈希表是一种以空间换时间的一种思想。

哈希函数又叫散列函数,哈希表又叫散列表。

二、自定义key

默认情况下,unordered_map使用关键字类型的 == 运算符来比较元素,因此默认情况下我们可以直接定义关键字是内置类型,比如string ,int ,指针等,标准库为这些内置类型提供了一个hash模板,hash<key_type>来组织其元素。

但是不能直接自定义关键字类型,比如结构体,因为我们不能使用标准库提供的默认hash模板,我们必须自己提供hash模板。

unordered_map是一个模板类,有5个参数:

  /**
   *  @brief A standard container composed of unique keys (containing
   *  at most one of each key value) that associates values of another type
   *  with the keys.
   *
   *  @ingroup unordered_associative_containers
   *
   *  @tparam  _Key    Type of key objects.
   *  @tparam  _Tp     Type of mapped objects.
   *  @tparam  _Hash   Hashing function object type, defaults to hash<_Value>.
   *  @tparam  _Pred   Predicate function object type, defaults
   *                   to equal_to<_Value>.
   *  @tparam  _Alloc  Allocator type, defaults to 
   *                   std::allocator<std::pair<const _Key, _Tp>>.
   *
   *
   * The resulting value type of the container is std::pair<const _Key, _Tp>.
   *
   *  Base is _Hashtable, dispatched at compile time via template
   *  alias __umap_hashtable.
   */

template<typename _Key, typename _Tp,
	   typename _Hash = hash<_Key>,
	   typename _Pred = equal_to<_Key>,
	   typename _Alloc = allocator<std::pair<const _Key, _Tp>>>
    class unordered_map
    {
		......
	}

unordered_map模板类的第一参数就是key,对 key 的要求反而比map的要求更严格。它要求 key 具备两个条件,一是可以计算 hash 值,二是能够执行相等比较操作。第一个是因为散列表的要求,只有计算 hash 值才能放入散列表,第二个则是因为 hash 值可能会冲突,所以当 hash 值相同时,就要比较真正的 key 值

因此我们要自定义key,主要修改是第三个参数和第四个参数。
第三个参数是:哈希函数对象类型,用来确定key在哈希表中的位置,默认是hash<_Key>模板。
可以使用类模板特例化hash<_Key>模板,或者用函数重载调用operator()。

第四个参数是:发生碰撞时用来比较key值,因为我们自定义key值,所以我们要重载 key 类型 == 运算符。
默认的 std::equal_to 会去调用 key 类型的 operator() 函数,比较内置类型的key值。

typename _Pred = equal_to<_Key>,
// One of the @link comparison_functors comparison functors@endlink.
  template<typename _Tp>
    struct equal_to : public binary_function<_Tp, _Tp, bool>
    {
      _GLIBCXX14_CONSTEXPR
      bool
      operator()(const _Tp& __x, const _Tp& __y) const
      { return __x == __y; }
    };

当我们自定义key时,重载equal_to函数时,由于equal_to的比较函数参数和函数体都有const修饰,因此我们重载的比较函数参数和函数体也要有const修饰。

小结:
如果要自定义key,那么要修改两个地方:unordered_map模板类的第三个参数和第四个参数。
(1)Hashing function object type。
(2)Predicate(equal_to) function object type。
(3)重载比较函数时函数参数和函数体const修饰保持不变。

对于typename _Hash = hash<_Key>模板,可以自定义hash模板,也可以特例化原模板所在命名空间的hash模板,即 std::hash。

三、代码演示API

3.1 自定义哈希模板

#include <string>
#include <iostream>
#include <unordered_map>

 
struct student_id_info
{
    std::string name;
    int arge;
    std::string grade;
 
    student_id_info(std::string a, int b, std::string c) : name(a), arge(b), grade(c){}
    student_id_info(){}
 
 	//相等比较函数
 	//自己提供函数来替代==运算符:operator重载==运算符,当哈希需要处理碰撞时,用来比较结构体student_id_info,比较真正的key值是否相等
 	//简单点说就是多个key的value值在一个桶里(发生哈希碰撞),用来比较其key是否相等
 	//这里重载时参数和函数体的const修饰要与默认比较函数保持不变
    bool operator==(const student_id_info& id) const {
        return name == id.name && arge == id.arge && grade == id.grade;
    }

};
 
 //自己提供的哈希模板
struct hash_fn
{
	//哈希函数
	//重载函数调用符:operator(),函数调用运算符必须是成员函数
    std::size_t operator() (const student_id_info & id) const {
    	//生成name的哈希值
        std::size_t h1 = std::hash<std::string>()(id.name);
        生成arge的哈希值
        std::size_t h2 = std::hash<int>()(id.arge);
        //生成grade的哈希值
        std::size_t h3 = std::hash<std::string>()(id.grade);
        //h1 ^ h2 ^ h3进行异或运算,形成给定的完整的student_id_info哈希值
        //使用异或运算来降低哈希碰撞
        return h1 ^ h2 ^ h3;
    }
};

struct student_score
{
    int math;
    int chinese;
    int english;
 
    student_score(int a, int b, int c) : math(a), chinese(b), english(c){}
    student_score(){}

};

int main()
{
 	//umap多了我们自定义的 哈希模板参数:hash_fn
    std::unordered_map< student_id_info, student_score, hash_fn > umap =
    {
        {{"xiaoming", 18, "freshman"}, {100, 90, 80}},
        {{"xiaohong", 19, "sophomore"}, {95, 90, 85}},
    };
    
    umap.emplace(student_id_info("xiaoli", 20, "Junior year"), student_score(90, 80, 95));

    for (const auto &student: umap)
    {
        std::cout << "{" << student.first.name<< "," << student.first.arge << ","<< student.first.grade <<"}: "
                  << "{" << student.second.math<< "," << student.second.chinese<< ","<< student.second.english <<"}" 
                  << std::endl;
   
    }

    student_id_info s_id = {"xiaohong", 19, "sophomore"};


    if(umap.find(s_id) == umap.end()){
        printf("dont't find the student\n");
    }else{
        printf("student math= %d, chinese = %d, english = %d\n", umap[s_id].math, umap[s_id].chinese, umap[s_id].english);
    }
 
    return 0;
}

结果展示:
在这里插入图片描述

3.2 类模板特例化

#include <string>
#include <iostream>
#include <unordered_map>

struct student_id_info
{
    std::string name;
    int arge;
    std::string grade;
 
    student_id_info(std::string a, int b, std::string c) : name(a), arge(b), grade(c){}
    student_id_info(){}
 
    bool operator==(const student_id_info& id) const {
        return name == id.name && arge == id.arge && grade == id.grade;
    }

};

//打开std命名空间,特例化std::hash
namespace std {
	//定义一个特例化版本
    template<>
    //模板参数为student_id_info
    struct hash<student_id_info>
    {
        std::size_t operator() (const student_id_info & id) const {
            std::size_t h1 = std::hash<std::string>()(id.name);
            std::size_t h2 = std::hash<int>()(id.arge);
            std::size_t h3 = std::hash<std::string>()(id.grade);
            return h1 ^ h2 ^ h3;
        }
    };
}//关闭std命名空间

struct student_score
{
    int math;
    int chinese;
    int english;
 
    student_score(int a, int b, int c) : math(a), chinese(b), english(c){}
    student_score(){}

};

int main()
{
 	//umap没有hash函数参数
    std::unordered_map< student_id_info, student_score > umap =
    {
        {{"xiaoming", 18, "freshman"}, {100, 90, 80}},
        {{"xiaohong", 19, "sophomore"}, {95, 90, 85}},
    };
    
    umap.emplace(student_id_info("xiaoli", 20, "Junior year"), student_score(90, 80, 95));

    for (const auto &student: umap)
    {
        std::cout << "{" << student.first.name<< "," << student.first.arge << ","<< student.first.grade <<"}: "
                  << "{" << student.second.math<< "," << student.second.chinese<< ","<< student.second.english <<"}" 
                  << std::endl;
   
    }

    student_id_info s_id = {"xiaohong", 19, "sophomore"};


    if(umap.find(s_id) == umap.end()){
        printf("dont't find the student\n");
    }else{
        printf("student math= %d, chinese = %d, english = %d\n", umap[s_id].math, umap[s_id].chinese, umap[s_id].english);
    }
 
    return 0;
}

在这里插入图片描述

其中 for (const auto &student: umap) 和 for (const auto student: umap)相比较,前者是引用umap的元素,后者是拷贝umap的元素,前者可以避免更多拷贝。

如果不加const修饰,即for (auto &student: umap) 和 for ( auto student: umap),前者由于是引用,那么修改student就相当于修改了umap中的元素,而后者仅仅只是拷贝,修改student不会修改umap中的元素。

拷贝的开销远远大于引用。

总结

以上就是unordered_map自定义key结构体方法。

参考资料

c++ primer

https://finixlei.blog.csdn.net/article/details/110267430
https://blog.csdn.net/Histranger_/article/details/120199102

  • 13
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值