• C++ - 封装 unordered_set 和 unordered_map - 哈希桶的迭代器实现


    前言

     unordered_set 和  unordered_map  两个容器的底层是哈希表实现的,此处的封装使用的 上篇博客当中的哈希桶来进行封装,相当于是在 哈希桶之上在套上了 unordered_set 和  unordered_map 。

    哈希桶的逻辑实现:

    C++ - 开散列的拉链法(哈希桶) 介绍 和 实现-CSDN博客

     在本篇博客当中的思路只是大体介绍,这个封装过程哟点复杂,如果有问题的可以参考下述 博客 对 map 和 set 两个容器的封装,这两个容器是底层实现是 红黑树,在这篇博客当中介绍更为详细,是按照源代码当中的封装逻辑进行的模拟实现:
    C++ - map 和 set 的模拟实现上篇 - 红黑树当中的仿函数 - 红黑树的迭代器实现-CSDN博客

    C++ - set 和 map 的实现(下篇)- set 和 map 的迭代器实现_chihiro1122的博客-CSDN博客

    基础封装 unordered_set 和  unordered_map 

     unordered_set 基础结构:

    1. namespace unordered
    2. {
    3. template<class K>
    4. class unordered_set
    5. {
    6. // set 当中从 T 当中取出 key 的仿函数
    7. struct SetKeyOfT
    8. {
    9. const K& operator()(const K& key)
    10. {
    11. return key;
    12. }
    13. };
    14. public:
    15. bool insert(const K& key)
    16. {
    17. return _ht.Insert(key);
    18. }
    19. private:
    20. hash_bucket::hash _ht;
    21. };
    22. }

    unordered_map  基础结构:

    1. namespace unordered
    2. {
    3. template<class K, class V>
    4. class unordered_map
    5. {
    6. // map 当中从 T 当中取出key 的仿函数
    7. struct MapKeyOfT
    8. {
    9. const K& operator()(const pair& kv)
    10. {
    11. return kv.first;
    12. }
    13. };
    14. public:
    15. bool insert(const pair& kv)
    16. {
    17. return _ht.Insert(kv);
    18. }
    19. private:
    20. hash_bucket::hash, MapKeyOfT> _ht;
    21. };
    22. }

    上述两个框架的实现逻辑在这里大体说明一下:
    在经过  unordered_set 和  unordered_map  包裹直线,原本的 哈希桶在使用之上已经非常麻烦了,所以一般是直接使用 在 哈希桶之上的  unordered_set 和  unordered_map 。

    在  unordered_set 和  unordered_map  当中的 insert()函数是直接复用 哈希桶当中的 Insert()函数。

    其中的 SetKeyOfT 和 MapKeyOfT 两个内部类是用来实现 在 两个容器当中的 不同取 key 逻辑的。其实 在 set 当中只有key ,是不可以不写的,但是在 map 当中就需要从 pair 当中的first 拿出,所以,为了在 哈希桶当中key 值的实现,实现一份代码在 set 和 map 当中都可以使用的话,就要 让 set 做出牺牲,和 map 一样实现 从 T 当中 取 key 的仿函数。所以你才会看到 在 set 当中创建的 哈希桶,要传入两个 K 作为哈希桶的模版参数。

     而在哈希桶当中,对 需要用 key 值的地方都用 set 和 map 当中实现的 仿函数来调用,对 key 值的取出进行判断,我们用 哈系统当中 Insert()函数来做演示:
     

    1. bool Insert(const T& data)
    2. {
    3. HashFunc hf;
    4. KeyOfT kot;
    5. if (find(data))
    6. {
    7. return false;
    8. }
    9. // 负载因子 到 1 就扩容(每一个桶当中都有数据)
    10. if (_n == _table.size())
    11. {
    12. size_t newsize = _table.size() * 2;
    13. vector newTable;
    14. newTable.resize(newsize, nullptr);
    15. for (int i = 0; i < _table.size(); i++)
    16. {
    17. Node* cur = _table[i];
    18. while (cur)
    19. {
    20. Node* next = cur->_next; // 保存链表的下一个结点
    21. // 头插到新表当中
    22. size_t hashi = hf(kot(cur->_data)) % newTable.size();
    23. cur->_next = newTable[hashi];
    24. newTable[hashi] = cur;
    25. // 向链表后迭代
    26. cur = next;
    27. }
    28. }
    29. // 交换 两个表在 对象当中的指向,让编译器 帮我们释放旧表的空间
    30. _table.swap(newTable);
    31. }

    红黑树的参数模版:
     

    1. template<class K, class T,class KeyOfT , class HashFunc = DefaultHashFunc>
    2. class hash
    3. {
    4. ·········
    5. ········
    6. ········
    7. ········

    其中的 T 这模版参数,在 set 当中传入的是 K,而在 map 当中传入的是一个 pair,这个pair 当中 存储的是一个结点的 key-value 键值对。而 KeyOfT 模版参数就是上述所说的,在 set 和 map 当中实现不同 取 key 逻辑的 仿函数的类的类型。这里就是要和 set 和 map 都可以使用一个 哈希桶的代码,实现泛型编程。

    而 HashFunc 这个模版参数是为了 在哈希当中能以多种 类型 作为 key 值来实现的仿函数。(在上一篇博客对哈希表的介绍当中有具体说明:C++ - 开放地址法的哈希介绍 - 哈希表的仿函数例子_chihiro1122的博客-CSDN博客

     哈希桶的迭代器实现

     哈希桶的遍历非常的简单,直接按照指针数组的顺序来遍历其中的 哈希桶就行了:
     

     但是,遍历简单,但是对于迭代器当中的  operator++()函数 和 operator--()函数,这两个函数的实现就要推敲一下。

    比如 ,当it 迭代器遍历到其中某一个 结点,那么 operator--()如何找到上一个结点;当 it 迭代器遍历到 某一个哈希桶的最后一个结点的时候,operator++()函数如何找到下一个哈希桶的位置。

    在 迭代器当中用 key 计算 hash 值,也是需要用 set 和 map 当中的仿函数来调用不同的 key 值取出的方法的。

    key 取出来了,还有哈希桶当中的 不同类型的 key 值的计算方式,也需要仿函数去计算。

    首先,每一个结点当中的值,都是按照哈希桶的规则插入进去的,我们可以计算出当前这个结点的key值,计算出当前结点是在哪一个桶;这样的话就可以直接从头开始遍历找到当前结点的上一个结点了,也可以找到下一个桶和上一个桶。

     operator++()函数

     在迭代器类当中存储的有当前结点的指针_node,那么当 _node->_next  不为空的时候,就继续遍历 这个哈希桶;为空说明已经遍历到这个哈希桶的最后一个结点了,就要找下一个哈希桶。

    怎么找,在上述已经说明了,就是计算当前结点的 key 值,计算当前哈希桶在指针数组的位置,找到下一个 哈希桶的位置。

    当找不到下一个桶,

    但是,想找到下一个哈希桶的位置,就要有 指针数组,但是指着数组在 哈希桶类 内当中,迭代器是另一个专门的类,如果在迭代器当中取到 这个 指针数组呢?

    我们就在 迭代器类当中多一个 成员来存储一个 哈希桶对象的指针。这样就可以找到指针数组了。

    1. Self& operator++()
    2. {
    3. if (_node->_next)
    4. {
    5. // 当前桶还没完
    6. // 就继续遍历当前桶
    7. _node = _node->_next;
    8. }
    9. else
    10. {
    11. // 两个仿函数类的实例化
    12. KeyOfT kot;
    13. HashFunc hf;
    14. // 计算当前结点的 hash值
    15. size_t hashi = hf(kot(_node->_data)) % _pht->_table.size();
    16. // 从下一个位置查找查找下一个不为空的桶
    17. ++hashi;
    18. while (hashi < _pht->_table.size())
    19. {
    20. // 如果遍历到的桶不为空
    21. if (_pht->_table[hashi])
    22. {
    23. // 把桶的第一个元素赋值给 _node
    24. _node = _pht->_table[hashi];
    25. return *this;
    26. }
    27. else
    28. {
    29. // 如果桶为空 继续寻找下一个桶
    30. ++hashi;
    31. }
    32. }
    33. // 走到这说明 后面的桶都为空
    34. // 或者当前桶就是最后一个桶了
    35. _node = nullptr;
    36. }
    37. return *this;
    38. }

    类模板的有元

      但是 哈希桶当中的 _table 这个 vector 容器是 private 的,在 迭代器类当中不能访问,所以此时我们就要把 迭代器 作为 哈希表的有元出现,届时迭代器才能访问到 哈希表当中的 私有成员。

    但是此处不是友元函数,是一个类模板的 有元,类模板的有元要在之前把模版参数加上:

    1. template<class K, class T,class KeyOfT , class HashFunc = DefaultHashFunc>
    2. class hash
    3. {
    4. // 有元声明
    5. template<class K, class T, class KeyOfT, class HashFunc>
    6. friend struct HTIterator;
    7. ··················
    8. ··················
    9. };

    哈希桶类当中的 begin()和 end()

    找到第一个桶也很简单,和上述 operator++()找下一个桶一样;end ()的话是最后一个结点的下一个位置,也就是说 nullptr,所以说,直接构造一个空的迭代器返回就行了:
     

    1. iterator begin()
    2. {
    3. // 找第一个桶
    4. for (size_t i = 0; i < _table.size(); i++)
    5. {
    6. Node* cur = _table[i];
    7. // 当前桶的不为空
    8. if (cur)
    9. {
    10. // 返回需要构造一个迭代器返回
    11. return iterator(cur, this);
    12. }
    13. }
    14. // 没找到就返回一个 空的迭代器
    15. return iterator(nullptr, this);
    16. }
    17. iterator end()
    18. {
    19. // 构造一个空的迭代器返回
    20. return iterator(nullptr, this);
    21. }

    在 set 和 map 当中的 begin()和 end()也都是套用 哈希桶当中的 begin()和end()了:
     

    1. //unordered_set.h
    2. typedef typename hash_bucket::HashTable::iterator iterator;
    3. iterator begin()
    4. {
    5. return _ht.begin();
    6. }
    7. iterator end()
    8. {
    9. return _ht.end();
    10. }
    11. // unordered_map.h
    12. typedef typename hash_bucket::HashTable, MapKeyOfT>::iterator iterator;
    13. iterator begin()
    14. {
    15. return _ht.begin();
    16. }
    17. iterator end()
    18. {
    19. return _ht.end();
    20. }

    哈希桶的迭代器类 和 哈希桶类的相互依赖问题

    之前实现的迭代器,都是 本类 当中有一个 迭代器 的 typedef,那么 在本类当中就可以直接按照typedef 当中的模版参数,在需要构造迭代器的地方,按照这个模版参数来构造。也就是说,要想用迭代器,那么 本类就要在前面,这样迭代器才能按照 本类来进行 定义。

    但是,我们本次实现的哈希表的 迭代器当中,有哈希表的指针;在哈希表当中还有 迭代器,这是一个相互使用的场景。

    当哈希表当中要用迭代器,所以迭代器在 哈希表当中 最前处声明,没问题。但是在 迭代器当中还有哈希表,那么此时,在迭代器当中的哈希表的类型,编译器不认识。

     所以,此时就要把 哈希桶类,在 迭代器上声明一下,因为编译器在遇到类型的时候,只会向上寻找定义,那么我们在迭代器上声明一下,高速编译器哈希表在下面定义了。

     我们把这种方式称为 前置声明

    1. // 前置声明
    2. template<class K, class T, class KeyOfT, class HashFunc>
    3. class HashTable;
    4. template<class K, class T, class KeyOfT, class HashFunc>
    5. struct HTIterator
    6. {
    7. ·············
    8. ·············
    9. ·············
    10. }
    11. template<class K, class T,class KeyOfT , class HashFunc = DefaultHashFunc>
    12. class hash
    13. {
    14. ·············
    15. ·············
    16. ·············
    17. }

    前置声明当中不用给模版的缺省参数。

    const迭代器

     上述实现之后,虽然迭代器能够跑了,但是还有一些问题,在上述取出的迭代器,可以修改 哈希桶的当中的key值。我们可以用 const 迭代器来解决这个问题,在 以 红黑树为底层实现的 map 和 set 也使用了这样方法,具体可以参考在前言当中给出的两篇博客。

    const 迭代器 和  普通迭代器共用的是一个 迭代器的模版。而之所以 const 的迭代器在外部不能修改,实际上也就是在 operator*() 和 operator->()这两个函数在返回值上做了处理,返回一个 const 的 引用 或者 指针,这个引用的对象 或者 指针指向的对象就不能修改了。

    1. template<class K, class T, class Ptr, class Ref, class KeyOfT, class HashFunc>
    2. struct HTIterator
    3. {
    4. typedef hash_bucket::HashNode Node;
    5. // 方便下述书写 迭代器的模版参数
    6. typedef HTIterator Self;
    7. Node* _node;
    8. hashtable* _pht;
    9. HTIterator(Node* node, hashtable* pht)
    10. :_node(node)
    11. , _pht(pht)
    12. {}
    13. Ref operator*()
    14. {
    15. return _node->_data;
    16. }
    17. Ptr operator->()
    18. {
    19. return &_node->_data;
    20. }
    21. ····································
    22. ····································
    23. ····································
    24. }

    所以,我们只需要把 T*  T& 作为 普通迭代器operator*() 和 operator->()的返回值;把 const T* ,const  T& 作为 普通迭代器operator*() 和 operator->()的返回值;

    因为 我们在 哈希表当中对 iterator 类 的模版参数进行了 typedef ,所以,我们只需要再在哈希表当中 typedef 出一个 const_iterator ,而 iterator 和 const_iterator 的不同就在于 传入给迭代器模版类的 模版参数不同。

    1. template<class K, class T,class KeyOfT , class HashFunc = DefaultHashFunc>
    2. class hashtable
    3. {
    4. typedef HashNode Node;
    5. // 有元声明
    6. template<class K, class T, class Ptr, class Ref, class KeyOfT, class HashFunc>
    7. friend struct HTIterator;
    8. public:
    9. typedef HTIterator iterator;
    10. typedef HTIteratorconst T*, const T&, KeyOfT, HashFunc> const_iterator;
    11. ·····················
    12. ·····················
    13. ·····················
    14. iterator begin()
    15. {
    16. // 找第一个桶
    17. for (size_t i = 0; i < _table.size(); i++)
    18. {
    19. Node* cur = _table[i];
    20. // 当前桶的不为空
    21. if (cur)
    22. {
    23. // 返回需要构造一个迭代器返回
    24. return iterator(cur, this);
    25. }
    26. }
    27. // 没找到就返回一个 空的迭代器
    28. return iterator(nullptr, this);
    29. }
    30. iterator end()
    31. {
    32. // 构造一个空的迭代器返回
    33. return iterator(nullptr, this);
    34. }
    35. // const 的 begin()和 end()
    36. const_iterator begin() const
    37. {
    38. // 找第一个桶
    39. for (size_t i = 0; i < _table.size(); i++)
    40. {
    41. Node* cur = _table[i];
    42. // 当前桶的不为空
    43. if (cur)
    44. {
    45. // 返回需要构造一个迭代器返回
    46. return const_iterator(cur, this);
    47. }
    48. }
    49. // 没找到就返回一个 空的迭代器
    50. return const_iterator(nullptr, this);
    51. }
    52. const_iterator end() const
    53. {
    54. // 构造一个空的迭代器返回
    55. return const_iterator(nullptr, this);
    56. }
    57. };

     哈希桶迭代器的 const *this 问题

     在哈希桶当中的 const 版本的 begin()和 end()当中,返回的是一个 迭代器 ,此时我们调用了一个 迭代器的构造函数,这个构造函数当中还传入了 当前哈希桶对象 的 this 指针。但是这个指针在 const 版本的begin()和 end()函数当中是被 const 修饰的,但是 在构造函数当中接受 this 指针的 形参不是 const 的,此时就会发生权限的放大,就会报错。

     当然,最简单的方式就是 在构造函数的当中用一个 const 的形参去接受,然后 构造函数对应初始化的成员也应该是 const 的,这样才能正确接受 const 的 this 指针。

    虽然在我们之前对 哈希桶当中的实现来看,在迭代器当中我们并没有在迭代器当中使用哈希桶的指针来修改过 哈希桶当中的 _table 这个 vector 等等成员什么的,只是从 _table 当中读数据,所以是对于 const 的指针是没有问题的。

    也就是说在 当前实现的 迭代器当中是不会通过 哈希表 指针修改到哈希表的,在迭代器当中是使用 _node 当前迭代器指向的 结点指针来修改 到 哈希表的,所以在当前是没有问题的。

     而且,构造函数只用写一个 const this指针的版本就足够了,因为普通迭代器传过来的 普通 this 指针 转给 const 的形参是完全没有问题的,是权限的缩小,然后初始化给 const 的指针也是没有问题

     修改之后的代码:

    1. template<class K, class T, class Ptr, class Ref, class KeyOfT, class HashFunc>
    2. struct HTIterator
    3. {
    4. ·········
    5. const hashtable* _pht;
    6. HTIterator(Node* node, const hashtable* pht)
    7. :_node(node)
    8. , _pht(pht)
    9. {}
    10. ·········
    11. }

     库当中实现不是按照上述方式实现的,他是 把 iteator 和 const_iterator 两个迭代器都实现了一遍
     

     而且,在const_iterator 当中,不仅仅是 哈希桶指针是 const 的,结点的指针也是 const 的。

    哈希桶迭代器完整代码

     

    1. // 前置声明
    2. template<class K, class T, class KeyOfT, class HashFunc>
    3. class hashtable;
    4. // 在哈希表 的 iterator template
    5. // 在哈希表 的 const_iterator template
    6. template<class K, class T, class Ptr, class Ref, class KeyOfT, class HashFunc>
    7. struct HTIterator
    8. {
    9. typedef hash_bucket::HashNode Node;
    10. // 方便下述书写 迭代器的模版参数
    11. typedef HTIterator Self;
    12. Node* _node;
    13. hashtable* _pht;
    14. HTIterator(Node* node, hashtable* pht)
    15. :_node(node)
    16. , _pht(pht)
    17. {}
    18. Ref operator*()
    19. {
    20. return _node->_data;
    21. }
    22. Ptr operator->()
    23. {
    24. return &_node->_data;
    25. }
    26. Self& operator++()
    27. {
    28. if (_node->_next)
    29. {
    30. // 当前桶还没完
    31. // 就继续遍历当前桶
    32. _node = _node->_next;
    33. }
    34. else
    35. {
    36. // 两个仿函数类的实例化
    37. KeyOfT kot;
    38. HashFunc hf;
    39. // 计算当前结点的 hash值
    40. size_t hashi = hf(kot(_node->_data)) % _pht->_table.size();
    41. // 从下一个位置查找查找下一个不为空的桶
    42. ++hashi;
    43. while (hashi < _pht->_table.size())
    44. {
    45. // 如果遍历到的桶不为空
    46. if (_pht->_table[hashi])
    47. {
    48. // 把桶的第一个元素赋值给 _node
    49. _node = _pht->_table[hashi];
    50. return *this;
    51. }
    52. else
    53. {
    54. // 如果桶为空 继续寻找下一个桶
    55. ++hashi;
    56. }
    57. }
    58. // 走到这说明 后面的桶都为空
    59. // 或者当前桶就是最后一个桶了
    60. _node = nullptr;
    61. }
    62. return *this;
    63. }
    64. bool operator!=(const Self& s)
    65. {
    66. return _node != s._node;
    67. }
    68. };
    69. // 哈希桶当中 begin 和 end 代码部分演示
    70. template<class K, class T,class KeyOfT , class HashFunc = DefaultHashFunc>
    71. class hashtable
    72. {
    73. typedef HashNode Node;
    74. // 有元声明
    75. template<class K, class T, class Ptr, class Ref, class KeyOfT, class HashFunc>
    76. friend struct HTIterator;
    77. public:
    78. typedef HTIterator iterator;
    79. typedef HTIteratorconst T*, const T&, KeyOfT, HashFunc> const_iterator;
    80. hashtable()
    81. {
    82. _table.resize(10, nullptr);
    83. }
    84. iterator begin()
    85. {
    86. // 找第一个桶
    87. for (size_t i = 0; i < _table.size(); i++)
    88. {
    89. Node* cur = _table[i];
    90. // 当前桶的不为空
    91. if (cur)
    92. {
    93. // 返回需要构造一个迭代器返回
    94. return iterator(cur, this);
    95. }
    96. }
    97. // 没找到就返回一个 空的迭代器
    98. return iterator(nullptr, this);
    99. }
    100. iterator end()
    101. {
    102. // 构造一个空的迭代器返回
    103. return iterator(nullptr, this);
    104. }
    105. // const 的 begin()和 end()
    106. const_iterator begin() const
    107. {
    108. // 找第一个桶
    109. for (size_t i = 0; i < _table.size(); i++)
    110. {
    111. Node* cur = _table[i];
    112. // 当前桶的不为空
    113. if (cur)
    114. {
    115. // 返回需要构造一个迭代器返回
    116. return const_iterator(cur, this);
    117. }
    118. }
    119. // 没找到就返回一个 空的迭代器
    120. return const_iterator(nullptr, this);
    121. }
    122. const_iterator end() const
    123. {
    124. // 构造一个空的迭代器返回
    125. return const_iterator(nullptr, this);
    126. }
    127. ·······················
    128. ·······················
    129. ·······················
    130. };

    unordered_set 和 unordered_map 当中复用 哈希桶的迭代器

     unordered_set

     set 当中只有 key ,用户是不能对 这个 key 进行修改的,所以,在 unordered_set 当中 ,不管是 iterator 还是 const_iteartor 都是 const_iteartor。想实现这样的功能,直接把 const_iteartor  typedef 出 iterator 和 const_iteartor 就可以实现。

    unordered_set 当中就只有 const 版本的 begin()和 end()函数,实现:

    1. public:
    2. typedef typename hash_bucket::hashtable::const_iterator iterator;
    3. typedef typename hash_bucket::hashtable::const_iterator const_iterator;
    4. // 返回值是 iterator 还是 const_iterator 都一样,都是 const_iterator
    5. iterator begin() const
    6. {
    7. return _ht.begin();
    8. }
    9. iterator end() const
    10. {
    11. return _ht.end();
    12. }

     如果只提供const 版本的迭代器的话,不管是 const 对象还是 普通对象都可以调用它,普通对象调用就是 权限的缩小,const 调用就是 权限的平移。

    1. #pragma once
    2. #include"hash.h"
    3. namespace unordered
    4. {
    5. template<class K>
    6. class unordered_set
    7. {
    8. // set 当中从 T 当中取出 key 的仿函数
    9. struct SetKeyOfT
    10. {
    11. const K& operator()(const K& key)
    12. {
    13. return key;
    14. }
    15. };
    16. public:
    17. typedef typename hash_bucket::hashtable::const_iterator iterator;
    18. typedef typename hash_bucket::hashtable::const_iterator const_iterator;
    19. iterator begin() const
    20. {
    21. return _ht.begin();
    22. }
    23. iterator end() const
    24. {
    25. return _ht.end();
    26. }
    27. pairbool> insert(const K& key)
    28. {
    29. pair<typename hash_bucket::hashtable::iterator, bool> pit = _ht.Insert(key);
    30. return pairbool>(pit.first, pit.second);
    31. }
    32. private:
    33. hash_bucket::hashtable _ht;
    34. };
    35. }

     

    unordered_map

    unordered_map  当中,按照 map 和 set 当中一样的性质进行套用和封装,在 unordered_map  当中的哈希桶构造的时候,对 pair 当中的 key 就使用 const 的方式,这样就可以修改到 value 而不修改到 key 了。
     当然,insert()也不能再像直线一样返回一个 bool 值了,得返回一个 迭代器和 bool 值,pair

    而且 find()函数也要返回一个 迭代器 ,修改如下:
     

    1. pairbool> Insert(const T& data)
    2. {
    3. HashFunc hf;
    4. KeyOfT kot;
    5. iterator it = find(kot(data));
    6. if (it != end())
    7. {
    8. return make_pair(iterator, false);
    9. }
    10. // 负载因子 到 1 就扩容(每一个桶当中都有数据)
    11. if (_n == _table.size())
    12. {
    13. size_t newsize = _table.size() * 2;
    14. vector newTable;
    15. newTable.resize(newsize, nullptr);
    16. for (int i = 0; i < _table.size(); i++)
    17. {
    18. Node* cur = _table[i];
    19. while (cur)
    20. {
    21. Node* next = cur->_next; // 保存链表的下一个结点
    22. // 头插到新表当中
    23. size_t hashi = hf(kot(cur->_data)) % newTable.size();
    24. cur->_next = newTable[hashi];
    25. newTable[hashi] = cur;
    26. // 向链表后迭代
    27. cur = next;
    28. }
    29. }
    30. // 交换 两个表在 对象当中的指向,让编译器 帮我们释放旧表的空间
    31. _table.swap(newTable);
    32. }
    33. // 计算hash值
    34. size_t hashi = hf(data) % _table.size();
    35. Node* newnode = new Node(data);
    36. newnode->_next = _table[hashi];
    37. _table[hashi] = newnode;
    38. ++_n;
    39. return make_pair(iterator(newnode, this), true);
    40. }

     

    1. iterator find(const K& key)
    2. {
    3. HashFunc hf;
    4. KeyOfT kot;
    5. size_t hash = hf(kot(key)) % _table.size();
    6. Node* cur = _table[hash];
    7. while (cur)
    8. {
    9. // 如果找到就返回 迭代器,不在返回结点的指针
    10. if (kot(cur->_data) == key)
    11. return iterator(cur, this);
    12. cur = cur->_next;
    13. }
    14. return nullptr;
    15. }

     在上述修改之后,在 unordered_set 和 unordered_map 当中的 insert()函数也要进行修改,它的返回值也应该是一个 pair。但是在上述修改之后就会引发另一个问题,如下所示:

    在 set 当中的insert()函数的 pair 的iterator 是 const_iterator ; 哈希桶当中的pair 的 iterator 就是 iterator。就类似于 vector 和 vector 的关系,是两个模版实例化出的类型,已经不是权限的放大和缩小了,根本就不是一个类型了。vector 和 vector 两个也是不同的类型。

    而 map 中不会,因为map 当中的 iteartor 就是 iterator,const_iterator 就是  const_iterator。

    但是前提是 实现了 传入 iterator 就构造 const_iteartor 的const 的构造函数,我们在 map 和 set 当中也就行了说明,他是 一份函数代码,在 iterator 和 const_iteartor 当中可以 实例化出两种函数,在 iteartor 就是 传入 iterator 的拷贝构造函数,在 const_iteartor 就是 传入 iterator 就构造 一个const_iteartor 的构造函数,具体可以参考 map 和 set 的博客。

    修改就是 增加一个 拷贝构造函数/const构造函数。

     像上述的修改方式在 list 当中也支持,如果用一个 iterator 取 构造 const_iterator 在 list 当中是支持的:
     

     我们可以看到it2 迭代器是 const 迭代器,但是 it 是 普通 list 对象,那么调用的迭代器的就是 普通迭代器,像上述的方式是支持的。库当中是这样实现的:

     在以前的迭代器实现当中,我们没有写这样的,类似拷贝构造函数一样的 函数,因为以前的迭代器的拷贝就是一个浅拷贝,只需要拷贝迭代器当中的指针就行了,而编译器自动生成的 浅拷贝的拷贝构造函数就已经够用了,不需要我们在写了。而上述写了之后,相当于是把 iterator 的不需要写的浅拷贝的拷贝构造函数写了,把 const_iterator 的构造函数写了,但是在  iterator 当中本来是不用写的,编译器自己写的就够用了,但是需要写一个 传入 iterator 构造 const_iterator 的构造函数,写了这个函数也就相当于是把 iterator 的浅拷贝函数给写了。

    而且这个函数的参数类型没有用 self ,而是用的 iterator,如果用 self 那么这个函数不管在哪都是一个拷贝构造函数;但是用的是 iterator,T* 和 T& 是写死的,此处就是绝妙之处。 


     还需要注意的是, 不同的对象但是类模版类型相同,是可以访问到对方的private 对象的,如果是不同累模版类型就不能了:

    比如 A  和 A 的两个对象是可以访问的,但是如果是 A 和 A 两个对象就不行了。在库当中可以实现是因为 人家不是类模板,是 struct的。因为类有类域

     

    1. #pragma once
    2. #include"hash.h"
    3. namespace unordered
    4. {
    5. template<class K, class V>
    6. class unordered_map
    7. {
    8. // map 当中从 T 当中取出key 的仿函数
    9. struct MapKeyOfT
    10. {
    11. const K& operator()(const pair& kv)
    12. {
    13. return kv.first;
    14. }
    15. };
    16. public:
    17. typedef typename hash_bucket::hashtableconst K, V>, MapKeyOfT>::iterator iterator;
    18. typedef typename hash_bucket::hashtableconst K, V>, MapKeyOfT>::const_iterator const_iterator;
    19. iterator begin()
    20. {
    21. return _ht.begin();
    22. }
    23. iterator end()
    24. {
    25. return _ht.end();
    26. }
    27. const_iterator begin() const
    28. {
    29. return _ht.begin();
    30. }
    31. const_iterator end() const
    32. {
    33. return _ht.end();
    34. }
    35. pairbool> insert(const pair& kv)
    36. {
    37. return _ht.Insert(kv);
    38. }
    39. private:
    40. hash_bucket::hashtableconst K, V>, MapKeyOfT> _ht;
    41. };
    42. }

     

     

     

  • 相关阅读:
    云IDE 使用体验,git项目快速启动实践
    【Designing ML Systems】第 3 章 :数据工程基础
    overleaf 写论文Latex语法记录
    爬虫学习(05): 数据解析_bs4篇
    手机通用便签APP哪个比较好用?
    【linux内核中的双向链表-02】list_for_each_safe
    【Linux】:Kafka基础命令
    大模型LLM相关面试题整理-PEFT
    【Linux进程篇】进程终章:POSIX信号量&&线程池&&线程安全的单例模式&&自旋锁&&读者写者问题
    MogDB逻辑解码与pg_recvlogical
  • 原文地址:https://blog.csdn.net/chihiro1122/article/details/133464983