unordered_map自定义key类型-C++

介绍

对于unordered_map来讲,我们使用过或者了解过的人来讲,大致都会知道他的底层数据结构是哈希表。不同于map的底层数据结构是红黑树,本文只针对unordered_map来进行介绍。

如果我们使用过unordered_map,我们也会知道unordered_map是一个key-value形式的结构,并且是一个类模板,我们在使用时通常只传入连个类型,一个是key,一个是value,接下来我们从源码先看一下unorder_map的具体结构。

 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
    {
      ...
    }

从上面的结构我们大致可以看出以下几点:

1 该类模板存在五个模板类型,对应的分别是:_Key,_Tp,_Hash,_Pred,_Alloc
2 倒数三个参数都存在默认值,且默认值分别是:hash<_Key>,equal_to<_Key>,allocator<str::pair<const _Key, _Tp>>

各个参数代表的意思
_Key:key的类型
_Tp:value值的类型
_Hash:哈希函数
_Pred:key相等的比较类模板
_Alloc:分配器

我们现在大概清楚了以上各个参数的含义,那么我们再来深入的了解一下unordered_map,为什么我们在使用基础数据类型,或者一些其他复合类型时可以直接使用,只需指定两个类型,自定义的类型传入两个却不行。

我们看上图可以知道该类模板提供了三个默认的模板类型,当我们不传递时,会使用这三个默认的模板类型。因此我们在指定类型时确实指定两个就ok了,但是这仅仅针对一些基础类型或者某些复合类型,想要知道原因我们就得扒开他的外衣,看看他的源码实现,因为已经提供了默认了参数,我们深入默认的参数看一下:

对于hash<_Key>

template<>						\
    struct hash<_Tp> : public __hash_base<size_t, _Tp>  \
    {                                                   \
      size_t                                            \
      operator()(_Tp __val) const noexcept              \
      { return static_cast<size_t>(__val); }            \
    };

看上面的代码,我们只是需要了解一下该默认参数的功能作用。我们知道,对于一个哈希的数据结构,那么必然他会有一个特定的哈希函数来计算特定的key的位置,从上面我们可以看出该哈希类模板重载了()运算符,并且返回值类型为size_t。即利用了仿函数的思想,然后计算到了特定的位置。我们暂时不深究他是如何计算key的位置的,我们目前只需要知道hash默认模板参数是通过仿函数,来计算出特定的key的位置。

对于equal_to<_Key>

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

我们看这个源码,可以看到该源码里传了连个变量,一个是__x,一个是__y,同样是利用了仿函数的思想,并且返回值是bool类型,且直接利用了 __x == __y来比较这两个类型是否相等。

对于最后一个_Alloc默认参数,就是分配资源的,我们使用默认的就好,对我们来讲基本用不到,也无用,因此就不看这个默认参数啦。

unordered_map基础使用

我们看了上面几个参数的源码,不知道大家能否自行分析处我们不需要添加任何东西就能使用这个unordered_map具有什么样的特征?即我们只需要定义unordered_map<keyType, valueType>就可以使用的?
我相信你们肯定是能分析出来的,不过我也大致讲一下,来看看咱们分析的是否一样~

1 key可以是基础数据类型
2 两个key之间可以进行比较(==)

那么满足这两点,就可以直接进行使用,来讲一下原因哈:不能使用的原因就是因为计算机不知道怎么进行处理,如果计算机知道了怎么处理那么自然而然的就可以使用。显然,对于基础数据类型,一定是可以直接进行使用的,因为计算机知道如何处理基础数据类型,所有的复杂类型都是有基础数据类型组合而成的。
那么string类可以当做key来使用吗?我们再重复一下,不能使用的根本原因就是计算机不知道如何处理,只要计算机知道了如何处理,那就可以使用。因为string类内部重载了许多方法,而其中有用的是其重载了 == 这个运算符,因此将两个string类型的key进行直接比较是合理的。对于hash默认模板类,这个我认为在内部其实是实现了针对string的哈希方法,因为是标准库提供给我们使用的类,我没有具体的去看这部分的源码,具体细节的实现不是太了解。因此,要实现一个可用的哈希,那么必然就得使得这两个方法可用,那么我们现在应该就清楚了,我们如果想自定义key方法,那么就需要使得这两个方法可用。知道了这个,那么实现自定义key类型就比较容易了。


unordered_map自定义key类型

设我们的自定义类型如下(两个整形元素组成):

struct myDefine {
    myDefine(int a = 1, int b = 2) :m_a(a), m_b(b) {}
    int m_a;
    int m_b;
};

第一步
对于自定义类型,我们需要实现自己的哈希方法,我们可以仿照标准库给定的默认hash原型来实现,即我们同样实现一个返回值类型为size_t的仿函数。

template<class T>
struct hashh {
    size_t operator()(const T& key) const{
        return key.m_a + key.m_b;
    }
};

如上图,我们实现了一个简单的仿函数哈希,哈希算法就是返回两个属性的和为哈希位置。(这只是随便定义的,你们可以根据自己需求进行定义)

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

第二步
我们观察上面equal_to的这个源码我们就会知道,该仿函数里面就是直接比较这两个key是否相等。因此,我们有两种做法,第一种就是我们使得这两个key能够直接进行相等比较,即我们可以在自己的自定义key里面重载 ==运算符,使得这个key类型可以直接进行相等比较,那么我们就不需要再实现额外的仿函数了。第二种就是我们自己自定义一个仿函数,然后仿照给定的仿函数实现。两种方法如下:
第一种方法

struct myDefine {
    myDefine(int a = 1, int b = 2) :m_a(a), m_b(b) {}
    bool operator==(myDefine& key2) {
        return m_a == key2.m_a;
    }
    int m_a;
    int m_b;
};

第二种方法

template<class T>
struct myEqual {
    bool operator()(const T& key1, const T& key2) const {
        return key1.m_a == key2.m_a && key1.m_b == key2.m_b;
    }
};

经过这两步,我们的自定义key就已经实现完成了,下面我们测试一下


#include <unordered_map>
#include <iostream>

using namespace std;

template<class T>
struct hashh {
    size_t operator()(const T& key) const{
        return key.m_a + key.m_b;
    }
};
struct myDefine {
    myDefine(int a = 1, int b = 2) :m_a(a), m_b(b) {}
    bool operator==(const myDefine& key2) const{
        return m_a == key2.m_a && m_b == key2.m_b;
    }
    int m_a;
    int m_b;
};

template<class T>
struct myEqual {
    bool operator()(const T& key1, const T& key2) const {
        return key1.m_a == key2.m_a && key1.m_b == key2.m_b;
    }
};



int main() {
    //自定义key中重载 == 运算符的实现(即第二步的方法一)
    unordered_map<myDefine, int, hashh<myDefine>> um1;

    um1.insert(make_pair(myDefine(), 6));
    um1.insert(make_pair(myDefine(3, 4), 23));
    um1.insert(make_pair(myDefine(5, 6), 39));

    cout << um1[myDefine()] << endl;
    cout << um1[myDefine(3, 4)] << endl;
    cout << um1[myDefine(5, 6)] << endl;

    cout << "========================================================" << endl;

    //自己实现仿函数 即第二步的方法二
    unordered_map<myDefine, int, hashh<myDefine>, myEqual<myDefine>> um2;
    um2.insert(make_pair(myDefine(), 1));
    um2.insert(make_pair(myDefine(3,4), 2));
    um2.insert(make_pair(myDefine(5, 6), 3));

    cout << um2[myDefine()] << endl;
    cout << um2[myDefine(3,4)] << endl;
    cout << um2[myDefine(5, 6)] << endl;



    return 0;
}

执行结果

在这里插入图片描述

注意点:尤其需要注意的就是,自己实现的方法原型一定要和默认提供的方法原型保持一致!!!否则就会报错=====就是传参为const类型,方法最后同样是const类型!!!

  • 8
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Bug.Remove()

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值