C++ -- 学习系列 无序关联式容器 unordered_set 与 unordered_map(未完待续)

一    Hash Table 是什么?

   哈希表(Hash Table)也叫做散列表,是一种通过将关键字与存储位置映射起来,利用关键字直接访问存储位置上的 value 的数据结构,使得元素查找的时间复杂度达到 O(1)。

映射函数被称为散列函数(hash 函数),存储数据的数组叫做散列表,即 Hash Table。

为什么使用哈希表呢?

答:哈希表可以为我们的查找带来便利,由于底层是基于数组的,所有优缺点与数组类似:

优点:查找速度快,时间复杂度 O(1);

缺点:插入数据与删除一般情况下 时间复杂度 O(1),最坏的情况下可能需要移动其他元素,时间复杂度O(n)。

合适的 hash 函数可以使关键字的寻址快速迅捷

一般的 hash 函数有:

hash算法原理详解_哈希算法-CSDN博客

1. 直接定址法(常用)

    直接取关键字的线性函数得到的值作为地址:

     Hash(Key) = a * Key + b

2. 除留取余法(常用)

     关键字除以小于散列表长度的素数得到的值作为地址:

Hash(Key) = Key % mod

3. 平均取中法(了解)

     对关键字取平方,将得到结果的中间几位作为存储地址

     假设关键字为 5644,对它平方就是 31854736,抽取中间的 3 位 547 作为哈希地址。

4. 折叠法(了解)

        折叠法是将关键字从左到右分割成位数相等的几部分(最后一部分位数可以短些),然后将这几部分叠加求和,并按哈希表表长,取后几位作为哈希地址

5.随机数法(了解)

        选择一个随机函数,取关键字的随机函数值为它的哈希地址,即 Hash(Key) = random(Key),其中 random 为随机数函数。

二  C++ HashTable 结构、底层源码与使用?

C++那些事之彻底搞懂STL HashTable - 知乎 (zhihu.com)

 1. 结构图

C++ -- 学习系列 无序关联式容器 unordered_set 与 unordered_map(未完待续)_第1张图片

    底层是一个数组,数组中每个索引代表一个 bucket,元素实际存储在链表上,若是出现 哈希冲突,新元素定位在一个 已有元素的 bucket 上,则将新元素 append 到bucket 的链表上。

2. 源码

   这里只列出了_Hashtable 的模板参数部分

 template
    class _Hashtable

     模板参数含义:

  参数 含义
_Key 关联容器的键类型
_Value 关联容器的值类型
_Alloc 用于内存分配的分配类型
_ExtractKey 从值中提取键的函数对象类型
_Equal 判断键是否相等的函数对象类型
_H1 第一个 Hash 的函数对象类型,用于计算关键字的 hashcode 
_H2 第二个 Hash 的函数对象类型,用于计算 hashcode 对应的bucket 索引
_Hash Hash 函数对象类型
_RehashPolicy 重新哈希函数策略类型
_Traits 特定容器特定类型

            常见函数解析:

1.1 find

 template
    auto
    _Hashtable<_Key, _Value, _Alloc, _ExtractKey, _Equal,
	       _H1, _H2, _Hash, _RehashPolicy, _Traits>::
    find(const key_type& __k) const
    -> const_iterator
    {
      __hash_code __code = this->_M_hash_code(__k); // 计算关键字 key 的 hash code
      std::size_t __n = _M_bucket_index(__k, __code); // 通过 hash code 获取桶的索引 index
      __node_type* __p = _M_find_node(__n, __k, __code); // 通过桶的index、key 、hash code获取到 链表上的 node ,若是返回为 nullptr 说明没找到,否则便找到了相应的 node ,并将 node 塞进 const_iterator 中,作为函数的返回参数
      return __p ? const_iterator(__p) : end();
    }

find 函数分为如下几步:

1)  计算关键字 key 得 hash code 值

2)  依据 关键字与 1) 中计算出的 hash code 值,获取桶的索引 index

3) 通过桶的 index、key 与 hash code 获取实际存储数据的 node 节点

4)   返回 包含有 node 节点的 迭代器

1.2  模板参数中_H1 与 _H2 的区别

   _H1 是 是计算 hash code 时用的函数对象,_H2 是 通过 hashcode 计算 桶的索引的函数对象。

 源代码:

     奥秘在于 struct _Hash_code_base 

template
    struct _Hash_code_base<_Key, _Value, _ExtractKey, _H1, _H2,
			   _Default_ranged_hash, true>
...
{
...
   __hash_code
      _M_hash_code(const _Key& __k) const
      { return _M_h1()(__k); }

      std::size_t
      _M_bucket_index(const _Key&, __hash_code __c,
		      std::size_t __n) const
      { return _M_h2()(__c, __n); }
...
 const _H1&
      _M_h1() const { return __ebo_h1::_S_cget(*this); }

      _H1&
      _M_h1() { return __ebo_h1::_S_get(*this); }

      const _H2&
      _M_h2() const { return __ebo_h2::_S_cget(*this); }

      _H2&
      _M_h2() { return __ebo_h2::_S_get(*this); }
};

       

 template
  struct _Hashtable_base
  : public _Hash_code_base<_Key, _Value, _ExtractKey, _H1, _H2, _Hash,
			   _Traits::__hash_cached::value>,
    ....
{
  .....
};

 template
 class _Hashtable
    : public __detail::_Hashtable_base<_Key, _Value, _ExtractKey, _Equal,
				       _H1, _H2, _Hash, _Traits>,
  ...
{
...
};

       _Hashtable 继承于 __detail::_Hashtable_base, 而  __detail::_Hashtable_base 继承于 struct _Hash_code_base 。

 struct _Hash_code_base  中

1. 函数 hash_code = _M_hash_code(key)  计算关键字 的 hash_code 使用的就是 _H1 函数对象

2. 函数 _M_bucket_index(key, hash_code) 计算 hash_code 对应桶的索引,这里使用的就是 _H2 函数对象

1.3  _Hash_node 结构

 _Hash_table 中使用的链表节点是 下面的 __node_type ,其来源于 __detail::_Hash_node, 若是

__hash_cashed::value 为 true ,则会存储 hash_code ,为 false,则不会存储 hash_code ,如下代码有两个分别针对 true 于false 的偏特化版本

using __node_type = __detail::_Hash_node<_Value, __hash_cached::value>;
 /**
   *  Primary template struct _Hash_node.
   */
  template
    struct _Hash_node;

  /**
   *  Specialization for nodes with caches, struct _Hash_node.
   *
   *  Base class is __detail::_Hash_node_value_base.
   */
  template
    struct _Hash_node<_Value, true> : _Hash_node_value_base<_Value>
    {
      std::size_t  _M_hash_code;

      _Hash_node*
      _M_next() const noexcept
      { return static_cast<_Hash_node*>(this->_M_nxt); }
    };

  /**
   *  Specialization for nodes without caches, struct _Hash_node.
   *
   *  Base class is __detail::_Hash_node_value_base.
   */
  template
    struct _Hash_node<_Value, false> : _Hash_node_value_base<_Value>
    {
      _Hash_node*
      _M_next() const noexcept
      { return static_cast<_Hash_node*>(this->_M_nxt); }
    };

2.  使用

  2.1  hashtable 当作set 使用

#include
#include 

int main()
{
    std::_Hashtable, std::_Identity,
             std::equal_to, std::hash, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy,
             std::__detail::_Hashtable_traits>::value, true, true>>  hash_table;


     for(int i = 0; i < 10; i++)
     {
         hash_table.insert(i+1);
         std::cout << "element num: " << hash_table.size() << ", bucket count: " << hash_table.bucket_count() << std::endl;
     }

     hash_table.insert(1);
     hash_table.insert(1);
     hash_table.insert(1);

     for(auto iter = hash_table.begin(); iter != hash_table.end(); iter++)
     {
        std::cout << *iter << " ";
     }
     std::cout << "" << std::endl;

     std::cout << "-----------------------------" << std::endl;

     std::_Hashtable, std::_Identity,
             std::equal_to, std::hash, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy,
             std::__detail::_Hashtable_traits>::value, true, false>>  multi_hash_table;


     for(int i = 0; i < 10; i++)
     {
         multi_hash_table.insert(i+1);
         std::cout << "element num: " << multi_hash_table.size() << ", bucket count: " << multi_hash_table.bucket_count() << std::endl;
     }

     multi_hash_table.insert(1);
     multi_hash_table.insert(1);
     multi_hash_table.insert(1);

     for(auto iter = multi_hash_table.begin(); iter != multi_hash_table.end(); iter++)
     {
        std::cout << *iter << " ";
     }
     std::cout << "" << std::endl;
   
   return 0;
}

   输出:

C++ -- 学习系列 无序关联式容器 unordered_set 与 unordered_map(未完待续)_第2张图片

  示例中第一个 hash_table 中的元素不可重复,第二个 multi_hash_table 中的元素是可以重复的。

  原因在于 模板参数中的最后一个参数 _Traits ,示例中用的 _Traits 对应的函数对象为 _Hashtable_traits ,源码如下:_unique_keys 为 true 时,hash table 的元素不可重复,若是为 false,则 hash table 的元素可以重复

template
    struct _Hashtable_traits
    {
      using __hash_cached = __bool_constant<_Cache_hash_code>;
      using __constant_iterators = __bool_constant<_Constant_iterators>;
      using __unique_keys = __bool_constant<_Unique_keys>;
    };

 2.2 hashtable 当作 map 使用

#include
#include
#include 

int main()
{
   // 1. key 唯一
    std::_Hashtable,  std::allocator, std::__detail::_Select1st,
            std::equal_to, std::hash, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy,
            std::__detail::_Hashtable_traits>::value, true, true>>  hash_table_map;
    std::string s1 = "aaa";
    hash_table_map.insert(std::make_pair(1, s1));

    std::string s2 = "bbb";
    hash_table_map.insert(std::make_pair(2, s2));

    std::string s3 = "ccc";
    hash_table_map.insert(std::make_pair(3, s3));

    std::string s4 = "ddd";
    hash_table_map.insert(std::make_pair(4, s4));

    std::string s5 = "eee";
    hash_table_map.insert(std::make_pair(5, s5));


    for(auto map_iter = hash_table_map.begin(); map_iter != hash_table_map.end(); map_iter++)
    {
        std::cout << "first: " << map_iter->first << ", second: " << map_iter->second << std::endl;
    }


    // 2. key 可重复
    std::_Hashtable,  std::allocator, std::__detail::_Select1st,
            std::equal_to, std::hash, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy,
            std::__detail::_Hashtable_traits>::value, true, false>>  hash_table_multimap;


    std::string ss1 = "aaa";
    hash_table_multimap.insert(std::make_pair(1, ss1));

    std::string ss2 = "bbb";
    hash_table_multimap.insert(std::make_pair(2, ss2));

    std::string ss3 = "ccc";
    hash_table_multimap.insert(std::make_pair(3, ss3));
    hash_table_multimap.insert(std::make_pair(3, ss1));
    hash_table_multimap.insert(std::make_pair(3, ss2));

    std::string ss4 = "ddd";
    hash_table_multimap.insert(std::make_pair(4, ss4));

    std::string ss5 = "eee";
    hash_table_multimap.insert(std::make_pair(5, ss5));

    std::cout << "-------------------------------" << std::endl;
    for(auto map_iter = hash_table_multimap.begin(); map_iter != hash_table_multimap.end(); map_iter++)
    {
        std::cout << "first: " << map_iter->first << ", second: " << map_iter->second << std::endl;
    }

    return 0;
}

输出:

C++ -- 学习系列 无序关联式容器 unordered_set 与 unordered_map(未完待续)_第3张图片

三  无序关联式容器的底层原理

1. set

 1.1  unordered_set 源码


/// Base types for unordered_set.
  template
    using __uset_traits = __detail::_Hashtable_traits<_Cache, true, true>;

  template,
	   typename _Pred = std::equal_to<_Value>,
  	   typename _Alloc = std::allocator<_Value>,
	   typename _Tr = __uset_traits<__cache_default<_Value, _Hash>::value>>
    using __uset_hashtable = _Hashtable<_Value, _Value, _Alloc,
					__detail::_Identity, _Pred, _Hash,
					__detail::_Mod_range_hashing,
					__detail::_Default_ranged_hash,
					__detail::_Prime_rehash_policy, _Tr>;

template,
	   class _Pred = std::equal_to<_Value>,
	   class _Alloc = std::allocator<_Value> >
    class unordered_set
    {
      typedef __uset_hashtable<_Value, _Hash, _Pred, _Alloc>  _Hashtable;
      _Hashtable _M_h;
     ......

};

   class  unordered_set 类模板参数解析

参数 说明
_Value 容器键类型,对于 set而言,其 key 与value是相同的
_Hash hash 函数,与 hashtable 的_H1 对应,用于将键转为 hashcode,默认是 hash
_Pred 与 hashtable 的 _Equal 对应,该函数对象类型用于判断两个键之间是否相等, 默认是 equal_to
_Alloc 用于内存分配的分配器类型, 默认是 allocator<_Value>

1.2  unordered_multiset 源码

/// Base types for unordered_multiset.
  template
    using __umset_traits = __detail::_Hashtable_traits<_Cache, true, false>;

  template,
	   typename _Pred = std::equal_to<_Value>,
	   typename _Alloc = std::allocator<_Value>,
	   typename _Tr = __umset_traits<__cache_default<_Value, _Hash>::value>>
    using __umset_hashtable = _Hashtable<_Value, _Value, _Alloc,
					 __detail::_Identity,
					 _Pred, _Hash,
					 __detail::_Mod_range_hashing,
					 __detail::_Default_ranged_hash,
					 __detail::_Prime_rehash_policy, _Tr>;

 template,
	   class _Pred = std::equal_to<_Value>,
	   class _Alloc = std::allocator<_Value> >
    class unordered_multiset
    {
      typedef __umset_hashtable<_Value, _Hash, _Pred, _Alloc>  _Hashtable;
      _Hashtable _M_h;
      ......

};

class  unordered_multiset 类模板参数解析

参数 说明
_Value 容器键类型,对于 set而言,其 key 与value是相同的
_Hash hash 函数,与 hashtable 的_H1 对应,用于将键转为 hashcode,默认是 hash
_Pred 与 hashtable 的 _Equal 对应,该函数对象类型用于判断两个键之间是否相等, 默认是 equal_to
_Alloc 用于内存分配的分配器类型, 默认是 allocator<_Value>

unordered_set 与 unordered_multiset 最大的区别在于 使用的 _Hashtable 类的模板参数的最后一个参数_Tr,前者使用的是 __detail::_Hashtable_traits<_Cache, true, true> ,最后一个模板参数为 true时,表示 _Hashtable 中的 key 唯一;后者使用的是  __detail::_Hashtable_traits<_Cache, true, false> ,最后一个模板参数为 false 时,表示 _Hashtable 中的 key 是可重复的

通过以上代码我们可以分析出,set 的 使用的 hashtable 底层的 key 与 value 的类型是同一种,

2. map

 2.1  unordered_map 源码

/// Base types for unordered_map.
  template
    using __umap_traits = __detail::_Hashtable_traits<_Cache, false, true>;

  template,
	   typename _Pred = std::equal_to<_Key>,
	   typename _Alloc = std::allocator >,
	   typename _Tr = __umap_traits<__cache_default<_Key, _Hash>::value>>
    using __umap_hashtable = _Hashtable<_Key, std::pair,
                                        _Alloc, __detail::_Select1st,
				        _Pred, _Hash,
				        __detail::_Mod_range_hashing,
				        __detail::_Default_ranged_hash,
				        __detail::_Prime_rehash_policy, _Tr>;

 template,
	   class _Pred = std::equal_to<_Key>,
	   class _Alloc = std::allocator > >
    class unordered_map
    {
      typedef __umap_hashtable<_Key, _Tp, _Hash, _Pred, _Alloc>  _Hashtable;
      _Hashtable _M_h;
    ......
};

       class unordered_map 类模板参数解析:

参数名 说明
_Key 容器键类型
_Tp 容器值类型
_Hash hash 函数,与 hashtable 的_H1 对应,用于将键转为 hashcode,默认是 hash
_Pred 与 hashtable 的 _Equal 对应,该函数对象类型用于判断两个键之间是否相等, 默认是 equal_to
_Alloc 用于内存分配的分配器类型, 默认是 allocator>

2.2 unoredred_multimap 源码

/// Base types for unordered_multimap.
  template
    using __ummap_traits = __detail::_Hashtable_traits<_Cache, false, false>;

  template,
	   typename _Pred = std::equal_to<_Key>,
	   typename _Alloc = std::allocator >,
	   typename _Tr = __ummap_traits<__cache_default<_Key, _Hash>::value>>
    using __ummap_hashtable = _Hashtable<_Key, std::pair,
					 _Alloc, __detail::_Select1st,
					 _Pred, _Hash,
					 __detail::_Mod_range_hashing,
					 __detail::_Default_ranged_hash,
					 __detail::_Prime_rehash_policy, _Tr>;

template,
	   class _Pred = std::equal_to<_Key>,
	   class _Alloc = std::allocator > >
    class unordered_multimap
    {
      typedef __ummap_hashtable<_Key, _Tp, _Hash, _Pred, _Alloc>  _Hashtable;
      _Hashtable _M_h;
   ......
};

class unordered_multimap 类模板参数解析:

参数名 说明
_Key 容器键类型
_Tp 容器值类型
_Hash hash 函数,与 hashtable 的_H1 对应,用于将键转为 hashcode,默认是 hash
_Pred 与 hashtable 的 _Equal 对应,该函数对象类型用于判断两个键之间是否相等, 默认是 equal_to
_Alloc 用于内存分配的分配器类型, 默认是 allocator>

四  无序关联式容器的使用

1. set

 1.1  unordered_set 常见函数与使用

std::unordered_set - cppreference.com

   1.1.1  构造函数
函数 说明
unordered_set 空构造函数
template< class InputIt >

unordered_set( InputIt first, InputIt last)

构造拥有范围 [first, last) 的内容的容器
unordered_set( size_type bucket_count) 指定 bucket 数量的构造函数
unordered_set(initializer_list __l)
通过 
initializer_list构造 set的构造函数

1.1.2  迭代器
函数 说明
begin  返回 容器中第一个元素的 iterator
end 返回 容器中最后一个元素的下一个 iterator
1.1.3  容器容量
函数 说明
empty 判断容器是否为空
size 返回容器存储的元素数量
1.1.4  容器修改
函数 说明
clear 清空容器
insert 向容器插入元素
emplace 向容器插入元素,该函数与 insert的区别在于,该函数可以只传入元素类的构造参数实现元素的原地构造
erase 移除指定元素或者指定 位置的元素
1.1.5  容器查询
函数 说明
count 返回指定元素的数量
find 返回指定元素的 iterator
contains 返回容器中是否存在指定元素 (c++ 20 支持)

示例代码:

#include
#include

int main()
{
    std::vector vec = {1, 2, 3, 4, 5, 6};

    // vec.begin()
    // 1. 初始化构造函数
    std::unordered_set  set1;
    std::unordered_set  set2(vec.begin(), vec.end(), 7);
    std::unordered_set  set3 = {1, 2, 3, 4, 5, 6};
    std::unordered_set  set4(13);

    // 2. 迭代器
    for(auto iter = set2.begin(); iter != set2.end(); iter++)
    {
        std::cout << *iter << " "; // 6 5 4 3 2 1
    }

    std::cout << "" << std::endl;
    std::cout << "------------------------" << std::endl;

    for(auto iter = set2.cbegin(); iter != set2.cend(); iter++)
    {
        std::cout << *iter << " "; // 6 5 4 3 2 1
    }
    std::cout << "" << std::endl;
    std::cout << "------------------------" << std::endl;
    // 3. 容器容量
    std::cout << "empty: " << set1.empty() << std::endl;
    std::cout << "size: " << set3.size() << std::endl;
    // std::cout << "contanis 1: " << set3.contains(1) << std::endl; c++20 支持

    // 4. 容器修改
    set2.insert(666);
    set2.insert(888);
    set2.erase(4);

    for(auto iter = set2.begin(); iter != set2.end(); iter++)
    {
        std::cout << *iter << " "; // 1 2 3 4 5 6
    }
    set2.clear();
    std::cout << "" << std::endl;

    std::cout << "empty: " << set2.empty() << std::endl;
    std::cout << "size: " << set2.size() << std::endl;
    std::cout << "" << std::endl;
    std::cout << "------------------------" << std::endl;

    // 5. 容器查找
    std::cout << "count: " << set3.count(2) << std::endl;
    std::cout << "find: " << *set3.find(2) << std::endl;
    set3.erase(3);
    for(auto iter = set3.begin(); iter != set3.end(); iter++)
    {
        std::cout << *iter << " "; // 1 2 3 4 5 6
    }
    std::cout << "" << std::endl;

    return 0;
}

输出:

C++ -- 学习系列 无序关联式容器 unordered_set 与 unordered_map(未完待续)_第4张图片

 1.2  unordered_multiset 常见函数与使用

 1.2.1  构造函数
函数 说明
unordered_set 空构造函数
template< class InputIt >

unordered_set( InputIt first, InputIt last)

构造拥有范围 [first, last) 的内容的容器
unordered_set( size_type bucket_count) 指定 bucket 数量的构造函数
unordered_set(initializer_list __l)
通过 
initializer_list构造 set的构造函数
  1.2.2  迭代器
函数 说明
begin  返回 容器中第一个元素的 iterator
end 返回 容器中最后一个元素的下一个 iterator
1.2.3  容器容量
函数 说明
empty 判断容器是否为空
size 返回容器存储的元素数量
1.2.4  容器修改
函数 说明
clear 清空容器
insert 向容器插入元素
emplace 向容器插入元素,该函数与 insert的区别在于,该函数可以只传入元素类的构造参数实现元素的原地构造
erase 移除指定元素或者指定 位置的元素
1.2.5  容器查询
函数 说明
count 返回指定元素的数量
find 返回指定元素的 iterator
contains 返回容器中是否存在指定元素 (c++ 20 支持)

示例代码:

#include
#include

int  main()
{
    std::vector vec = {1, 2, 3, 3, 3, 4, 5, 6};

    // vec.begin()
    // 1. 初始化构造函数
    std::unordered_multiset  set1;
    std::unordered_multiset  set2(vec.begin(), vec.end(), 7);
    std::unordered_multiset  set3 = {1, 2, 3, 3, 3, 4, 5, 6};
    std::unordered_multiset  set4(13);


    // 2. 迭代器
    for(auto iter = set2.begin(); iter != set2.end(); iter++)
    {
        std::cout << *iter << " ";
    }

    std::cout << "" << std::endl;
    std::cout << "------------------------" << std::endl;

    for(auto iter = set2.cbegin(); iter != set2.cend(); iter++)
    {
        std::cout << *iter << " ";
    }
    std::cout << "" << std::endl;
    std::cout << "------------------------" << std::endl;
    // 3. 容器容量
    std::cout << "empty: " << set1.empty() << std::endl;
    std::cout << "size: " << set3.size() << std::endl;
    // std::cout << "contanis 1: " << set3.contains(1) << std::endl; c++20 支持

    // 4. 容器修改
    set2.insert(666);
    set2.insert(888);
    set2.erase(4);

    for(auto iter = set2.begin(); iter != set2.end(); iter++)
    {
        std::cout << *iter << " ";
    }
    set2.clear();
    std::cout << "" << std::endl;

    std::cout << "empty: " << set2.empty() << std::endl;
    std::cout << "size: " << set2.size() << std::endl;
    std::cout << "" << std::endl;
    std::cout << "------------------------" << std::endl;

    // 5. 容器查找
    std::cout << "count: " << set3.count(2) << std::endl;
    std::cout << "find: " << *set3.find(2) << std::endl;
    set3.erase(3);
    for(auto iter = set3.begin(); iter != set3.end(); iter++)
    {
        std::cout << *iter << " ";
    }
    std::cout << "" << std::endl;

    return 0;
}

输出:

C++ -- 学习系列 无序关联式容器 unordered_set 与 unordered_map(未完待续)_第5张图片

2. map

 2.1  unordered_map 常见函数与使用

 2.1.1  构造函数
函数 说明
unordered_map 空构造函数
template< class InputIt >

unordered_map( InputIt first, InputIt last)

构造拥有范围 [first, last) 的内容的容器
unordered_map( std::initializer_list init) 通过 list 来构造 map 的gou'z
  2.1.2  迭代器
函数 说明
begin  返回 容器中第一个元素的 iterator
end 返回 容器中最后一个元素的下一个 iterator
2.1.3  容器容量
函数 说明
empty 判断容器是否为空
size 返回容器存储的元素数量
2.1.4  容器修改
函数 说明
clear 清空容器
insert 向容器插入元素
emplace 向容器插入元素,该函数与 insert的区别在于,该函数可以只传入元素类的构造参数实现元素的原地构造
erase 移除指定元素或者指定 位置的元素
2.1.5  容器查询
函数 说明
count 返回指定 key 的数量
find 返回指定 key 的 iterator
contains 返回容器中是否存在指定元素 (c++ 20 支持)
at 返回指定 key 的引用
operator[] 返回指定 key 的引用,若是不存在该 key, 则将 key 与对应的值插入容器

示例代码:

#include
#include

int main()
{
 // 1. 初始化构造函数
    std::unordered_map  map1;
    std::unordered_map  map2 = {{1, "a"}, {2, "b"}, {3, "c"}, {4, "d"}, {5, "e"}};
    std::unordered_map  map3(map2.begin(), map2.end());

    // 2. 迭代器
    for(auto iter = map2.begin(); iter != map2.end(); iter++)
    {
        std::cout << iter->first << ", " << iter->second << " ";
    }

    std::cout << "" << std::endl;
    std::cout << "------------------------" << std::endl;

    for(auto iter = map2.cbegin(); iter != map2.cend(); iter++)
    {
        std::cout << iter->first << ", " << iter->second << " ";
    }
    std::cout << "" << std::endl;
    std::cout << "------------------------" << std::endl;
    // 3. 容器容量
    std::cout << "empty: " << map1.empty() << std::endl;
    std::cout << "size: " << map3.size() << std::endl;
    // std::cout << "contanis 1: " << map3.contains(1) << std::endl; c++20 支持

    // 4. 容器修改
    map2.insert({666, "fff"});
    map2.emplace(888, "hhh");
    map2.erase(4);

    for(auto iter = map2.begin(); iter != map2.end(); iter++)
    {
        std::cout << iter->first << ", " << iter->second  << " ";
    }
    map2.clear();
    std::cout << "" << std::endl;

    std::cout << "empty: " << map2.empty() << std::endl;
    std::cout << "size: " << map2.size() << std::endl;
    std::cout << "" << std::endl;
    std::cout << "------------------------" << std::endl;

    // 5. 容器查找
    std::cout << "count: " << map3.count(2) << std::endl;
    std::cout << "find: " << map3.find(2)->first << ", " << map3.find(2)->second << std::endl;
    map3.erase(3);

    for(auto iter = map3.begin(); iter != map3.end(); iter++)
    {
        std::cout << iter->first << ", " << iter->second << " ";
    }
    std::cout << "" << std::endl;
    std::cout << "operator[]: " << map3[1] << std::endl;
    std::cout << "at: "<< map3.at(1) <

输出:

C++ -- 学习系列 无序关联式容器 unordered_set 与 unordered_map(未完待续)_第6张图片

2.2 unoredred_multimap 常见函数与使用

 2.2.1  构造函数
函数 说明
unordered_map 空构造函数
template< class InputIt >

unordered_map( InputIt first, InputIt last)

构造拥有范围 [first, last) 的内容的容器
unordered_map( std::initializer_list init) 通过 list 来构造 map 的gou'z
  2.2.2  迭代器
函数 说明
begin  返回 容器中第一个元素的 iterator
end 返回 容器中最后一个元素的下一个 iterator
2.2.3  容器容量
函数 说明
empty 判断容器是否为空
size 返回容器存储的元素数量
2.2.4  容器修改
函数 说明
clear 清空容器
insert 向容器插入元素
emplace 向容器插入元素,该函数与 insert的区别在于,该函数可以只传入元素类的构造参数实现元素的原地构造
erase 移除指定元素或者指定 位置的元素
2.2.5  容器查询
函数 说明
count 返回指定 key 的数量
find 返回指定 key 的 iterator
contains 返回容器中是否存在指定元素 (c++ 20 支持)

注意:

没有看错,unordered_multimap 不支持 at 与 operator[] 函数访问元素,因为这个容器是允许重复 key存在的,所以你打算调用 at 与 operator[] 是访问重复key 的哪个值呢?

基于此,c++ 标准针对 multimap 就不提供 at 与 operator[] 函数 了

示例代码:

#include
#include

int main()
{
     // 1. 初始化构造函数
    std::unordered_multimap  map1;
    std::unordered_multimap  map2 = {{1, "a"}, {2, "b"}, {3, "c1"}, {3, "c2"},{3, "c3"},{4, "d"}, {5, "e"}};
    std::unordered_multimap  map3(map2.begin(), map2.end());

    // 2. 迭代器
    for(auto iter = map2.begin(); iter != map2.end(); iter++)
    {
        std::cout << iter->first << ", " << iter->second << " ";
    }

    std::cout << "" << std::endl;
    std::cout << "------------------------" << std::endl;

    for(auto iter = map2.cbegin(); iter != map2.cend(); iter++)
    {
        std::cout << iter->first << ", " << iter->second << " ";
    }
    std::cout << "" << std::endl;
    std::cout << "------------------------" << std::endl;
    // 3. 容器容量
    std::cout << "empty: " << map1.empty() << std::endl;
    std::cout << "size: " << map3.size() << std::endl;
    // std::cout << "contanis 1: " << map3.contains(1) << std::endl; c++20 支持

    // 4. 容器修改
    map2.insert({666, "fff1"});
    map2.insert({666, "fff2"});

    map2.emplace(888, "hhh");
    map2.erase(4);

    for(auto iter = map2.begin(); iter != map2.end(); iter++)
    {
        std::cout << iter->first << ", " << iter->second << " ";
    }
    map2.clear();
    std::cout << "" << std::endl;

    std::cout << "empty: " << map2.empty() << std::endl;
    std::cout << "size: " << map2.size() << std::endl;
    std::cout << "" << std::endl;
    std::cout << "------------------------" << std::endl;

    // 5. 容器查找
    std::cout << "count: " << map3.count(2) << std::endl;
    std::cout << "find: " << map3.find(2)->first << ", " << map3.find(2)->second << std::endl;
    map3.erase(3);

    for(auto iter = map3.begin(); iter != map3.end(); iter++)
    {
        std::cout << iter->first << ", " << iter->second << " ";
    }
    std::cout << "" << std::endl;
    return 0;
}

输出:

C++ -- 学习系列 无序关联式容器 unordered_set 与 unordered_map(未完待续)_第7张图片

五 简单实现

1. set

 1.1  unordered_set 

// my_unordered_set.h

#include 
#include
#include
#include

template
class my_unordered_set
{
public:
    template
      using __cache_default
        =  std::__not_,
                 // Mandatory to have erase not throwing.
                 std::__detail::__is_noexcept_hash<_Tp, _Hash>>>;
    using __traits = std::__detail::_Hashtable_traits<__cache_default<_Value,
    std::hash<_Value>>::value, true, true>;
    using hashtable =
        std::_Hashtable<_Value, _Value,
                    std::allocator<_Value>,
                    std::_Identity<_Value>,
                    std::equal_to<_Value>, std::hash<_Value>,
                    std::__detail::_Mod_range_hashing,
                    std::__detail::_Default_ranged_hash,
                    std::__detail::_Prime_rehash_policy,
                    __traits>;

    // using iterator = typename hashtable::iterator;
    using const_iterator = typename hashtable::const_iterator;
    typedef typename  hashtable::hasher my_hasher;
    typedef typename  hashtable::allocator_type  my_allocator_type;
    typedef typename  hashtable::key_equal my_key_equal;

public:
    my_unordered_set()
    {
    }

    const_iterator begin() const
    {
        return hash_container.begin();
    }

    const_iterator end() const
    {
        return hash_container.end();
    }

    std::size_t size()
    {
        return hash_container.size();
    }

    bool empty()
    {
        return hash_container.empty();
    }

    void clear()
    {
        hash_container.clear();
    }

    void insert(_Value& val)
    {
        hash_container.insert(val);
    }

    void insert(_Value&& val)
    {
        hash_container.insert(val);
    }

    template
    void emplace(_Args& ...args)
    {
        hash_container.emplace(std::forward<_Args>(args)...);
    }

    template
    void emplace(_Args&& ...args)
    {
        hash_container.emplace(std::forward<_Args>(args)...);
    }

    void erase(_Value& val)
    {
        hash_container.erase(val);
    }

    void erase(_Value&& val)
    {
        hash_container.erase(val);
    }

    std::size_t count(_Value& val)
    {
        return hash_container.count(val);
    }

    std::size_t count(_Value&& val)
    {
        return hash_container.count(val);
    }

    const_iterator find(_Value& val)
    {
        return hash_container.find(val);
    }

    const_iterator find(_Value&& val)
    {
        return hash_container.find(val);
    }

private:

    hashtable hash_container;

};


// main.cpp

#include
#include"my_unordered_set.h"

int main()
{
        // 1. 初始化构造函数
    my_unordered_set  set1;
    set1.insert(1);
    set1.insert(2);
    set1.insert(3);
    set1.insert(4);
    set1.insert(5);
    set1.insert(6);

    // 2. 迭代器
    for(auto iter = set1.begin(); iter != set1.end(); iter++)
    {
        std::cout << *iter << " "; // 6 5 4 3 2 1
    }

    std::cout << "" << std::endl;
    std::cout << "------------------------" << std::endl;

    // 3. 容器容量
    std::cout << "empty: " << set1.empty() << std::endl;
    std::cout << "size: " << set1.size() << std::endl;
    // std::cout << "contanis 1: " << set3.contains(1) << std::endl; c++20 支持

    // 4. 容器修改
    set1.insert(666);
    set1.insert(888);
    set1.erase(4);

    for(auto iter = set1.begin(); iter != set1.end(); iter++)
    {
        std::cout << *iter << " "; // 1 2 3 4 5 6
    }
    std::cout << "" << std::endl;

    std::cout << "empty: " << set1.empty() << std::endl;
    std::cout << "size: " << set1.size() << std::endl;
    std::cout << "" << std::endl;
    std::cout << "------------------------" << std::endl;

    // 5. 容器查找
    std::cout << "count: " << set1.count(1) << std::endl;
    std::cout << "find: " << *set1.find(1) << std::endl;
    set1.erase(3);
    for(auto iter = set1.begin(); iter != set1.end(); iter++)
    {
        std::cout << *iter << " "; // 1 2 3 4 5 6
    }
    std::cout << "" << std::endl;
    set1.clear();

    std::cout << "empty: " << set1.empty() << std::endl;
    std::cout << "size: " << set1.size() << std::endl;


    return 0;
}

输出:

C++ -- 学习系列 无序关联式容器 unordered_set 与 unordered_map(未完待续)_第8张图片

 1.2  unordered_multiset

// my_unordered_multiset.h

#include 
#include
#include
#include

template
class my_unordered_multiset
{
public:
    template
      using __cache_default
        =  std::__not_,
                 // Mandatory to have erase not throwing.
                 std::__detail::__is_noexcept_hash<_Tp, _Hash>>>;
    using _traits = std::__detail::_Hashtable_traits<__cache_default<_Value, std::hash<_Value>>::value, true, false>;
    using hashtable =
        std::_Hashtable<_Value, _Value,  std::allocator<_Value>, std::_Identity<_Value>,
                    std::equal_to<_Value>, std::hash<_Value>,
                    std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy,
                    _traits>;

    // using iterator = typename hashtable::iterator;
    using const_iterator = typename hashtable::const_iterator;

public:
    my_unordered_multiset()
    {

    }

    const_iterator begin() const
    {
        return hash_container.begin();
    }

    const_iterator end() const
    {
        return hash_container.end();
    }

    std::size_t size()
    {
        return hash_container.size();
    }

    bool empty()
    {
        return hash_container.empty();
    }

    void clear()
    {
        hash_container.clear();
    }

    void insert(_Value& val){
        hash_container.insert(val);
    }

    void insert(_Value&& val){
        hash_container.insert(val);
    }

    template
    void emplace(_Args& ...args)
    {
        hash_container.emplace(std::forward<_Args>(args)...);
    }

    template
    void emplace(_Args&& ...args)
    {
        hash_container.emplace(std::forward<_Args>(args)...);
    }

    void erase(_Value& val)
    {
        hash_container.erase(val);
    }

    void erase(_Value&& val)
    {
        hash_container.erase(val);
    }

    std::size_t count(_Value& val)
    {
        return hash_container.count(val);
    }

    std::size_t count(_Value&& val)
    {
        return hash_container.count(val);
    }

    const_iterator find(_Value& val)
    {
        return hash_container.find(val);
    }

    const_iterator find(_Value&& val)
    {
        return hash_container.find(val);
    }

private:

    hashtable hash_container;

};

// main.cpp

#include
#include"my_unordered_multiset.h"

int main()
{

  // 1. 初始化构造函数
    my_unordered_multiset  set1;

    set1.insert(1);
    set1.insert(2);
    set1.insert(3);
    set1.insert(4);
    set1.insert(5);
    set1.insert(6);
    set1.insert(6);
    set1.insert(6);


    // 2. 迭代器
    for(auto iter = set1.begin(); iter != set1.end(); iter++)
    {
        std::cout << *iter << " "; // 6 5 4 3 2 1
    }

    std::cout << "" << std::endl;
    std::cout << "------------------------" << std::endl;

    // 3. 容器容量
    std::cout << "empty: " << set1.empty() << std::endl;
    std::cout << "size: " << set1.size() << std::endl;
    // std::cout << "contanis 1: " << set3.contains(1) << std::endl; c++20 支持

    // 4. 容器修改
    set1.insert(666);
    set1.insert(888);
    set1.erase(4);

    for(auto iter = set1.begin(); iter != set1.end(); iter++)
    {
        std::cout << *iter << " "; // 1 2 3 4 5 6
    }
    std::cout << "" << std::endl;

    std::cout << "empty: " << set1.empty() << std::endl;
    std::cout << "size: " << set1.size() << std::endl;
    std::cout << "" << std::endl;
    std::cout << "------------------------" << std::endl;

    // 5. 容器查找
    std::cout << "count: " << set1.count(2) << std::endl;
    std::cout << "find: " << *set1.find(2) << std::endl;
    set1.erase(3);
    for(auto iter = set1.begin(); iter != set1.end(); iter++)
    {
        std::cout << *iter << " "; // 1 2 3 4 5 6
    }
    std::cout << "" << std::endl;
    set1.clear();
    std::cout << "empty: " << set1.empty() << std::endl;
    std::cout << "size: " << set1.size() << std::endl;

    return 0;
}

输出:

C++ -- 学习系列 无序关联式容器 unordered_set 与 unordered_map(未完待续)_第9张图片

2. map

 2.1  unordered_map

// my_unordered_map.h
#include 
#include
#include
#include

template
class my_unordered_map
{
public:
    template
      using __cache_default
        =  std::__not_,
                 // Mandatory to have erase not throwing.
                 std::__detail::__is_noexcept_hash<_Tp, _Hash>>>;
    using  _traits = std::__detail::_Hashtable_traits<__cache_default>::value, false, true>;
    using _HashTable =  std::_Hashtable,  std::allocator, std::__detail::_Select1st,
            std::equal_to, std::hash, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy,
            _traits>;

   // using const_iterator = typename _HashTable::const_iterator;
    using iterator = typename _HashTable::iterator;

    typedef typename _HashTable::value_type  value_type;


    my_unordered_map()
    {

    }

    iterator begin()
    {
        return _hash_container.begin();
    }

    iterator end()
    {
        return _hash_container.end();
    }

    bool empty()
    {
        return _hash_container.empty();
    }

    std::size_t size()
    {
        return _hash_container.size();
    }

    void insert(const Key& key, Value& value)
    {
        iterator iter = _hash_container.find(key);
        if(iter != _hash_container.end())
        {
            iter->second = value;
        }
        else
        {
            _hash_container.insert(std::make_pair(key, value));
        }
    }

    void insert(const Key&& key, Value&& value)
    {
        insert(key, value);
    }

    void insert(std::initializer_list list)
    {
        _hash_container.insert(list);
    }

    void insert(value_type&& value)
    {
        _hash_container.insert(value);
    }

    template
    void emplace(_Args ...args)
    {
        _hash_container.emplace(std::forward<_Args>(args)...);
    }

    void clear()
    {
        _hash_container.clear();
    }

    void erase(const Key& key)
    {
        _hash_container.erase(key);
    }

    void erase(const Key&& key)
    {
        _hash_container.erase(key);
    }

    std::size_t count(const Key& key)
    {
        return _hash_container.count(key);
    }

    std::size_t count(const Key&& key)
    {
        return _hash_container.count(key);
    }

    iterator find(const Key& key)
    {
        return _hash_container.find(key);
    }

    iterator find(const Key&& key)
    {
        return _hash_container.find(key);
    }

    Value& at(const Key& key)
    {
        return _hash_container.at(key);
    }

    Value& at(const Key&& key)
    {
        return _hash_container.at(key);
    }

    Value& operator[](const Key& key)
    {
        return _hash_container[key];
    }

    Value& operator[](const Key&& key)
    {
        return _hash_container[key];
    }
private:
    _HashTable _hash_container;
};

// main.cpp

#include
#include"my_unordered_map.h"

int main()
{
       // 1. 初始化构造函数
    my_unordered_map  map1;

    map1[1] = "a";
    map1[2] = "b";
    map1[3] = "c";
    map1[4] = "d";
    map1[5] = "e";


    // 2. 迭代器
    for(auto iter = map1.begin(); iter != map1.end(); iter++)
    {
        std::cout << iter->first << ", " << iter->second << " | ";
    }

    std::cout << "" << std::endl;
    std::cout << "------------------------" << std::endl;


    // 3. 容器容量
    std::cout << "empty: " << map1.empty() << std::endl;
    std::cout << "size: " << map1.size() << std::endl;
    // std::cout << "contanis 1: " << map1.contains(1) << std::endl; c++20 支持

    // 4. 容器修改
    map1.insert({566, "fff"});
    map1.insert({{666, "fff"}});
    map1.insert({std::make_pair(666, "fff")});
    map1.emplace(888, "hhh");
    map1.erase(4);

    for(auto iter = map1.begin(); iter != map1.end(); iter++)
    {
        std::cout << iter->first << ", " << iter->second << " | ";
    }
    std::cout << "" << std::endl;

    std::cout << "empty: " << map1.empty() << std::endl;
    std::cout << "size: " << map1.size() << std::endl;
    std::cout << "" << std::endl;
    std::cout << "------------------------" << std::endl;

    // 5. 容器查找
    std::cout << "count: " << map1.count(2) << std::endl;
    std::cout << "find: " << map1.find(2)->first << ", " << map1.find(2)->second << std::endl;
    map1.erase(3);

    for(auto iter = map1.begin(); iter != map1.end(); iter++)
    {
        std::cout << iter->first << ", " << iter->second << " | ";
    }
    std::cout << "" << std::endl;
    std::cout << "operator[]: " << map1[1] << std::endl;
    std::cout << "at: "<< map1.at(1) <

输出:

C++ -- 学习系列 无序关联式容器 unordered_set 与 unordered_map(未完待续)_第10张图片

2.2 unoredred_multimap

// my_unordered_multimap.h

#include 
#include
#include
#include

template
class my_unordered_multimap
{
public:
    template
      using __cache_default
        =  std::__not_,
                 // Mandatory to have erase not throwing.
                 std::__detail::__is_noexcept_hash<_Tp, _Hash>>>;
    using  _traits = std::__detail::_Hashtable_traits<__cache_default>::value, false, false>;
    using _HashTable =  std::_Hashtable,  std::allocator, std::__detail::_Select1st,
            std::equal_to, std::hash, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy,
            _traits>;

   // using const_iterator = typename _HashTable::const_iterator;
    using iterator = typename _HashTable::iterator;

    typedef typename _HashTable::value_type  value_type;


    my_unordered_multimap()
    {

    }

    iterator begin()
    {
        return _hash_container.begin();
    }

    iterator end()
    {
        return _hash_container.end();
    }

    bool empty()
    {
        return _hash_container.empty();
    }

    std::size_t size()
    {
        return _hash_container.size();
    }

    void insert(const Key& key, Value& value)
    {
        iterator iter = _hash_container.find(key);
        if(iter != _hash_container.end())
        {
            iter->second = value;
        }
        else
        {
            _hash_container.insert(std::make_pair(key, value));
        }
    }

    void insert(const Key&& key, Value&& value)
    {
        insert(key, value);
    }

    void insert(std::initializer_list list)
    {
        _hash_container.insert(list);
    }

    void insert(value_type&& value)
    {
        _hash_container.insert(value);
    }

    template
    void emplace(_Args ...args)
    {
        _hash_container.emplace(std::forward<_Args>(args)...);
    }

    void clear()
    {
        _hash_container.clear();
    }

    void erase(const Key& key)
    {
        _hash_container.erase(key);
    }

    void erase(const Key&& key)
    {
        _hash_container.erase(key);
    }

    std::size_t count(const Key& key)
    {
        return _hash_container.count(key);
    }

    std::size_t count(const Key&& key)
    {
        return _hash_container.count(key);
    }

    iterator find(const Key& key)
    {
        return _hash_container.find(key);
    }

    iterator find(const Key&& key)
    {
        return _hash_container.find(key);
    }

private:
    _HashTable _hash_container;
};

// main.cpp
#include
#include"my_unordered_multimap.h"

int main()
{
     // 1. 初始化构造函数
    my_unordered_multimap  map1;

    map1.insert({1, "a"});
    map1.insert({1, "b"});
    map1.insert({2, "b"});
    map1.insert({3, "c"});


    // 2. 迭代器
    for(auto iter = map1.begin(); iter != map1.end(); iter++)
    {
        std::cout << iter->first << ", " << iter->second << " | ";
    }

    std::cout << "" << std::endl;
    std::cout << "------------------------" << std::endl;


    // 3. 容器容量
    std::cout << "empty: " << map1.empty() << std::endl;
    std::cout << "size: " << map1.size() << std::endl;
    // std::cout << "contanis 1: " << map3.contains(1) << std::endl; c++20 支持

    // 4. 容器修改
    map1.insert({666, "fff1"});
    map1.insert({666, "fff2"});

    map1.emplace(888, "hhh");
    map1.erase(4);

    for(auto iter = map1.begin(); iter != map1.end(); iter++)
    {
        std::cout << iter->first << ", " << iter->second << " | ";
    }
    std::cout << "" << std::endl;

    std::cout << "empty: " << map1.empty() << std::endl;
    std::cout << "size: " << map1.size() << std::endl;
    std::cout << "" << std::endl;
    std::cout << "------------------------" << std::endl;

    // 5. 容器查找
    std::cout << "count: " << map1.count(2) << std::endl;
    std::cout << "find: " << map1.find(2)->first << ", " << map1.find(2)->second << std::endl;
    map1.erase(3);

    for(auto iter = map1.begin(); iter != map1.end(); iter++)
    {
        std::cout << iter->first << ", " << iter->second << " | ";
    }
    std::cout << "" << std::endl;
    map1.clear();
    std::cout << "empty: " << map1.empty() << std::endl;
    std::cout << "size: " << map1.size() << std::endl;
    std::cout << "" << std::endl;
    
    return 0;
}

输出:

C++ -- 学习系列 无序关联式容器 unordered_set 与 unordered_map(未完待续)_第11张图片

你可能感兴趣的:(c++,学习,开发语言)