C++STL:无序关联容器

文章目录

  • 1. 无序关联容器
    • 1.1 概述
    • 1.2 无序容器种类
  • 2. unordered_map
    • 2.1 概述
    • 2.2 成员方法
    • 2.3 创建C++ unordered_map容器的方法
    • 2.4 迭代器
    • 2.5 C++ STL unordered_map获取元素的几种方法
    • 2.6 C++ unordered_map insert()方法
    • 2.7 C++ unordered_map emplace()和emplace_hint()方法
      • 2.7.1 unordered_map emplace()方法
      • 2.7.2 unordered_map emplace_hint()方法
    • 2.8 C++ STL unordered_map删除元素:erase()和clear()
      • 2.8.1 unordered_map erase()方法
      • 2.8.2 unordered_map clear()方法
  • 3. unordered_multimap
    • 3.1 概述
    • 3.2 成员方法
    • 3.3 创建C++ unordered_multimap容器
  • 4. unordered_set
    • 4.1 概述
    • 4.2 成员方法
    • 4.3 创建C++ unordered_set容器
  • 5. unordered_multiset
    • 5.1 概述
    • 5.2 成员函数
    • 5.3 创建C++ unordered_multiset容器

1. 无序关联容器

1.1 概述

继 map、multimap、set、multiset 关联容器之后,本节介绍一类“特殊”的关联容器,它们常被称为“无序容器”、“哈希容器”或者“无序关联容器”。

注意,无序容器是 C++ 11 标准才正式引入到 STL 标准库中的,这意味着如果要使用该类容器,则必须选择支持 C++ 11 标准的编译器。

和关联容器一样,无序容器也使用键值对(pair 类型)的方式存储数据。不过,它们有本质上的不同:

  • 关联式容器的底层实现采用的树存储结构,更确切的说是红黑树结构;
  • 无序容器的底层实现采用的是哈希表的存储结构。

C++ STL 底层采用哈希表实现无序容器时,会将所有数据存储到一整块连续的内存空间中,并且当数据存储位置发生冲突时,解决方法选用的是“链地址法”(又称“开链法”)。

基于底层实现采用了不同的数据结构,因此和关联容器相比,无序容器具有以下 2 个特点:

  1. 无序容器内部存储的键值对是无序的,各键值对的存储位置取决于该键值对中的键,
  2. 和关联容器相比,无序容器擅长通过指定键查找对应的值(平均时间复杂度为 O(1));但对于使用迭代器遍历容器中存储的元素,无序容器的执行效率则不如关联容器。

1.2 无序容器种类

和关联容器一样,无序容器只是一类容器的统称,其包含有 4 个具体容器,分别为 unordered_map、unordered_multimap、unordered_set 以及 unordered_multiset。

下表对这 4 种无序容器的功能做了详细的介绍:

无序容器功能
unordered_map存储键值对 <key, value> 类型的元素,其中各个键值对键的值不允许重复,且该容器中存储的键值对是无序的。
unordered_multimap和 unordered_map 唯一的区别在于,该容器允许存储多个键相同的键值对。
unordered_set不再以键值对的形式存储数据,而是直接存储数据元素本身(当然也可以理解为,该容器存储的全部都是键 key 和值 value 相等的键值对,正因为它们相等,因此只存储 value 即可)。另外,该容器存储的元素不能重复,且容器内部存储的元素也是无序的。
unordered_multiset和 unordered_set 唯一的区别在于,该容器允许存储值相同的元素。

以上 4 种无序容器的名称,仅是在前面所学的 4 种关联容器名称的基础上,添加了 “unordered_”,其实它们仅有一个区别,即 map 容器内存会对存储的键值对进行排序,而 unordered_map 不会。

也就是说,C++ 11 标准的 STL 中,在已提供有 4 种关联式容器的基础上,又新增了各自的“unordered”版本(无序版本、哈希版本),提高了查找指定元素的效率。既然无序容器和之前所学的关联容器类似,那么在实际使用中应该选哪种容器呢?总的来说,实际场景中如果涉及大量遍历容器的操作,建议首选关联容器;反之,如果更多的操作是通过键获取对应的值,则应首选无序容器。

这里以 unordered_map 容器为例,举个例子(不必深究该容器的具体用法):

#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;int main()
{// 创建并初始化一个 unordered_map 容器,其存储的 <string,string> 类型的键值对std::unordered_map<std::string, std::string> my_uMap{{"C语言教程","http://c.biancheng.net/c/"},{"Python教程","http://c.biancheng.net/python/"},{"Java教程","http://c.biancheng.net/java/"} };// 查找指定键对应的值,效率比关联式容器高string str = my_uMap.at("C语言教程");cout << "str = " << str << endl;// 使用迭代器遍历哈希容器,效率不如关联式容器for (auto iter = my_uMap.begin(); iter != my_uMap.end(); ++iter){// pair 类型键值对分为 2 部分cout << iter->first << " " << iter->second << endl;}return 0;
}

程序执行结果为:

str = http://c.biancheng.net/c/
C语言教程 http://c.biancheng.net/c/
Python教程 http://c.biancheng.net/python/
Java教程 http://c.biancheng.net/java/

2. unordered_map

2.1 概述

unordered_map 容器,直译过来就是"无序 map 容器"的意思。所谓“无序”,指的是 unordered_map 容器不会像 map 容器那样对存储的数据进行排序。换句话说,unordered_map 容器和 map 容器仅有一点不同,即 map 容器中存储的数据是有序的,而 unordered_map 容器中是无序的。

可以将 unordered_map 容器等价为无序的 map 容器。

具体来讲,unordered_map 容器和 map 容器一样,以键值对(pair类型)的形式存储数据,存储的各个键值对的键互不相同且不允许被修改。但由于 unordered_map 容器底层采用的是哈希表存储结构,该结构本身不具有对数据的排序功能,所以此容器内部不会自行对存储的键值对进行排序。

unordered_map 容器在<unordered_map>头文件中,并位于 std 命名空间中。因此,如果想使用该容器,代码中应包含如下语句:

#include <unordered_map>
using namespace std;

注意,第二行代码不是必需的,但如果不用,则后续程序中在使用此容器时,需手动注明 std 命名空间(建议初学者使用)。

unordered_map 容器模板的定义如下所示:

template < class Key,                        // 键值对中键的类型class T,                          // 键值对中值的类型class Hash = hash<Key>,           // 容器内部存储键值对所用的哈希函数class Pred = equal_to<Key>,       // 判断各个键值对键相同的规则class Alloc = allocator< pair<const Key,T> >  // 指定分配器对象的类型> class unordered_map;

以上 5 个参数中,必须显式给前 2 个参数传值,并且除特殊情况外,最多只需要使用前 4 个参数,各自的含义和功能如下表所示:

参数含义
<key,T>前 2 个参数分别用于确定键值对中键和值的类型,也就是存储键值对的类型。
Hash = hash<Key>用于指明容器在存储各个键值对时要使用的哈希函数,默认使用 STL 标准库提供的 hash<key> 哈希函数。注意,默认哈希函数只适用于基本数据类型(包括 string 类型),而不适用于自定义的结构体或者类。
Pred = equal_to<Key>要知道,unordered_map 容器中存储的各个键值对的键是不能相等的,而判断是否相等的规则,就由此参数指定。默认情况下,使用 STL 标准库中提供的 equal_to<key> 规则,该规则仅支持可直接用 == 运算符做比较的数据类型。

总的来说,当无序容器中存储键值对的键为自定义类型时,默认的哈希函数 hash 以及比较函数 equal_to 将不再适用,只能自己设计适用该类型的哈希函数和比较函数,并显式传递给 Hash 参数和 Pred 参数。

2.2 成员方法

unordered_map 既可以看做是关联容器,更属于自成一脉的无序容器。因此在该容器模板类中,既包含一些在学习关联容器时常见的成员方法,还有一些属于无序容器特有的成员方法。下表列出了 unordered_map 类模板提供的所有常用的成员方法以及各自的功能:

成员方法功能
begin()返回指向容器中第一个键值对的正向迭代器。
end()返回指向容器中最后一个键值对之后位置的正向迭代器。
cbegin()和 begin() 功能相同,只不过在其基础上增加了 const 属性,即该方法返回的迭代器不能用于修改容器内存储的键值对。
cend()和 end() 功能相同,只不过在其基础上,增加了 const 属性,即该方法返回的迭代器不能用于修改容器内存储的键值对。
empty()若容器为空,则返回 true;否则 false。
size()返回当前容器中存有键值对的个数。
max_size()返回容器所能容纳键值对的最大个数,不同的操作系统,其返回值亦不相同。
operator[key]该模板类中重载了 [] 运算符,其功能是可以向访问数组中元素那样,只要给定某个键值对的键 key,就可以获取该键对应的值。注意,如果当前容器中没有以 key 为键的键值对,则其会使用该键向当前容器中插入一个新键值对。
at(key)返回容器中存储的键 key 对应的值,如果 key 不存在,则会抛出 out_of_range 异常。
find(key)查找以 key 为键的键值对,如果找到,则返回一个指向该键值对的正向迭代器;反之,则返回一个指向容器中最后一个键值对之后位置的迭代器(如果 end() 方法返回的迭代器)。
count(key)在容器中查找以 key 键的键值对的个数。
equal_range(key)返回一个 pair 对象,其包含 2 个迭代器,用于表明当前容器中键为 key 的键值对所在的范围。
emplace()向容器中添加新键值对,效率比 insert() 方法高。
emplace_hint()向容器中添加新键值对,效率比 insert() 方法高。
insert()向容器中添加新键值对。
erase()删除指定键值对。
clear()清空容器,即删除容器中存储的所有键值对。
swap()交换 2 个 unordered_map 容器存储的键值对,前提是必须保证这 2 个容器的类型完全相等。
bucket_count()返回当前容器底层存储键值对时,使用桶(一个线性链表代表一个桶)的数量。
max_bucket_count()返回当前系统中,unordered_map 容器底层最多可以使用多少桶。
bucket_size(n)返回第 n 个桶中存储键值对的数量。
bucket(key)返回以 key 为键的键值对所在桶的编号。
load_factor()返回 unordered_map 容器中当前的负载因子。负载因子,指的是的当前容器中存储键值对的数量(size())和使用桶数(bucket_count())的比值,即 load_factor() = size() / bucket_count()。
max_load_factor()返回或者设置当前 unordered_map 容器的负载因子。
rehash(n)将当前容器底层使用桶的数量设置为 n。
reserve()将存储桶的数量(也就是 bucket_count() 方法的返回值)设置为至少容纳count个元(不超过最大负载因子)所需的数量,并重新整理容器。
hash_function()返回当前容器使用的哈希函数对象。

注意,对于实现互换 2 个相同类型 unordered_map 容器的键值对,除了可以调用该容器模板类中提供的 swap() 成员方法外,STL 标准库还提供了同名的 swap() 非成员函数。

2.3 创建C++ unordered_map容器的方法

常见的创建 unordered_map 容器的方法有以下几种。

  1. 通过调用 unordered_map 模板类的默认构造函数,可以创建空的 unordered_map 容器。比如:
std::unordered_map<std::string, std::string> umap;

由此,就创建好了一个可存储 <string,string> 类型键值对的 unordered_map 容器。

  1. 当然,在创建 unordered_map 容器的同时,可以完成初始化操作。比如:
std::unordered_map<std::string, std::string> umap{{"Python教程","http://c.biancheng.net/python/"},{"Java教程","http://c.biancheng.net/java/"},{"Linux教程","http://c.biancheng.net/linux/"} };

通过此方法创建的 umap 容器中,就包含有 3 个键值对元素。

  1. 另外,还可以调用 unordered_map 模板中提供的复制(拷贝)构造函数,将现有 unordered_map 容器中存储的键值对,复制给新建 unordered_map 容器。

例如,在第二种方式创建好 umap 容器的基础上,再创建并初始化一个 umap2 容器:

std::unordered_map<std::string, std::string> umap2(umap);

由此,umap2 容器中就包含有 umap 容器中所有的键值对。

除此之外,C++ 11 标准中还向 unordered_map 模板类增加了移动构造函数,即以右值引用的方式将临时 unordered_map 容器中存储的所有键值对,全部复制给新建容器。例如:

// 返回临时 unordered_map 容器的函数
std::unordered_map <std::string, std::string > retUmap(){std::unordered_map<std::string, std::string>tempUmap{{"Python教程","http://c.biancheng.net/python/"},{"Java教程","http://c.biancheng.net/java/"},{"Linux教程","http://c.biancheng.net/linux/"} };return tempUmap;
}
// 调用移动构造函数,创建 umap2 容器
std::unordered_map<std::string, std::string> umap2(retUmap());

注意,无论是调用复制构造函数还是拷贝构造函数,必须保证 2 个容器的类型完全相同。

  1. 当然,如果不想全部拷贝,可以使用 unordered_map 类模板提供的迭代器,在现有 unordered_map 容器中选择部分区域内的键值对,为新建 unordered_map 容器初始化。例如:
// 传入 2 个迭代器
std::unordered_map<std::string, std::string> umap2(++umap.begin(),umap.end());

通过此方式创建的 umap2 容器,其内部就包含 umap 容器中除第 1 个键值对外的所有其它键值对。

2.4 迭代器

C++ STL 标准库中,unordered_map 容器迭代器的类型为前向迭代器(又称正向迭代器)。这意味着,假设 p 是一个前向迭代器,则其只能进行 *p、p++、++p 操作,且 2 个前向迭代器之间只能用 ==!= 运算符做比较。

在 unordered_map 容器模板中,提供了下表所示的成员方法,可用来获取指向指定位置的前向迭代器。

成员方法功能
begin()返回指向容器中第一个键值对的正向迭代器。
end()返回指向容器中最后一个键值对之后位置的正向迭代器。
cbegin()和 begin() 功能相同,只不过在其基础上增加了 const 属性,即该方法返回的迭代器不能用于修改容器内存储的键值对。
cend()和 end() 功能相同,只不过在其基础上,增加了 const 属性,即该方法返回的迭代器不能用于修改容器内存储的键值对。
find(key)查找以 key 为键的键值对,如果找到,则返回一个指向该键值对的正向迭代器;反之,则返回一个指向容器中最后一个键值对之后位置的迭代器(如果 end() 方法返回的迭代器)。
equal_range(key)返回一个 pair 对象,其包含 2 个迭代器,用于表明当前容器中键为 key 的键值对所在的范围。

值得一提的是,equal_range(key) 很少用于 unordered_map 容器,因为该容器中存储的都是键不相等的键值对,即便调用该成员方法,得到的 2 个迭代器所表示的范围中,最多只包含 1 个键值对。事实上,该成员方法更适用于 unordered_multimap 容器。

下面的程序演示了上表中部分成员方法的用法:

#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;int main()
{// 创建 umap 容器unordered_map<string, string> umap{{"Python教程","http://c.biancheng.net/python/"},{"Java教程","http://c.biancheng.net/java/"},{"Linux教程","http://c.biancheng.net/linux/"} };cout << "umap 存储的键值对包括:" << endl;// 遍历输出 umap 容器中所有的键值对for (auto iter = umap.begin(); iter != umap.end(); ++iter) {cout << "<" << iter->first << ", " << iter->second << ">" << endl;}// 获取指向指定键值对的前向迭代器unordered_map<string, string>::iterator iter = umap.find("Java教程");cout <<"umap.find(\"Java教程\") = " << "<" << iter->first << ", " << iter->second << ">" << endl;return 0;
}

程序执行结果为:

umap 存储的键值对包括:
<Python教程, http://c.biancheng.net/python/>
<Linux教程, http://c.biancheng.net/linux/>
<Java教程, http://c.biancheng.net/java/>
umap.find("Java教程") = <Java教程, http://c.biancheng.net/java/>

需要注意的是,在操作 unordered_map 容器过程(尤其是向容器中添加新键值对)中,一旦当前容器的负载因子超过最大负载因子(默认值为 1.0),该容器就会适当增加桶的数量(通常是翻一倍),并自动执行 rehash() 成员方法,重新调整各个键值对的存储位置(此过程又称“重哈希”),此过程很可能导致之前创建的迭代器失效。

所谓迭代器失效,针对的是那些用于表示容器内某个范围的迭代器,由于重哈希会重新调整每个键值对的存储位置,所以容器重哈希之后,之前表示特定范围的迭代器很可能无法再正确表示该范围。但是,重哈希并不会影响那些指向单个键值对元素的迭代器。

举个例子:

#include <iostream>
#include <unordered_map>
using namespace std;int main()
{// 创建 umap 容器unordered_map<int, int> umap;// 向 umap 容器添加 50 个键值对for (int i = 1; i <= 50; i++) {umap.emplace(i, i);}// 获取键为 49 的键值对所在的范围auto pair = umap.equal_range(49);// 输出 pair 范围内的每个键值对的键的值for (auto iter = pair.first; iter != pair.second; ++iter) {cout << iter->first <<" ";}cout << endl;// 手动调整最大负载因子数umap.max_load_factor(3.0);// 手动调用 rehash() 函数重哈希umap.rehash(10);// 重哈希之后,pair 的范围可能会发生变化for (auto iter = pair.first; iter != pair.second; ++iter) {cout << iter->first << " ";}return 0;
}

程序执行结果为:

49
49 17

观察输出结果不难发现,之前用于表示键为 49 的键值对所在范围的 2 个迭代器,重哈希之后表示的范围发生了改变。

经测试,用于遍历整个容器的 begin()/end() 和 cbegin()/cend() 迭代器对,重哈希只会影响遍历容器内键值对的顺序,整个遍历的操作仍然可以顺利完成。

2.5 C++ STL unordered_map获取元素的几种方法

为了方便用户快速地从该类型容器提取出目标元素(也就是某个键值对的值),unordered_map 容器类模板中提供了以下几种方法。

  1. unordered_map 容器类模板中,实现了对 [ ] 运算符的重载,使得我们可以像“利用下标访问普通数组中元素”那样,通过目标键值对的键获取到该键对应的值。

举个例子:

#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;int main()
{// 创建 umap 容器unordered_map<string, string> umap{{"Python教程","http://c.biancheng.net/python/"},{"Java教程","http://c.biancheng.net/java/"},{"Linux教程","http://c.biancheng.net/linux/"} };// 获取 "Java教程" 对应的值string str = umap["Java教程"];cout << str << endl;return 0;
}

程序输出结果为:

http://c.biancheng.net/java/

需要注意的是,如果当前容器中并没有存储以 [ ] 运算符内指定的元素作为键的键值对,则此时 [ ] 运算符的功能将转变为:向当前容器中添加以目标元素为键的键值对。举个例子:

#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;int main()
{// 创建空 umap 容器unordered_map<string, string> umap;// [] 运算符在 = 右侧string str = umap["STL教程"];// [] 运算符在 = 左侧umap["C教程"] = "http://c.biancheng.net/c/";for (auto iter = umap.begin(); iter != umap.end(); ++iter) {cout << iter->first << " " << iter->second << endl;}return 0;
}

程序执行结果为:

C教程 http://c.biancheng.net/c/
STL教程

可以看到,当使用 [ ] 运算符向 unordered_map 容器中添加键值对时,分为 2 种情况:

  1. 当 [ ] 运算符位于赋值号(=)右侧时,则新添加键值对的键为 [ ] 运算符内的元素,其值为键值对要求的值类型的默认值(string 类型默认值为空字符串);
  2. 当 [ ] 运算符位于赋值号(=)左侧时,则新添加键值对的键为 [ ] 运算符内的元素,其值为赋值号右侧的元素。
  1. unordered_map 类模板中,还提供有 at() 成员方法,和使用 [ ] 运算符一样,at() 成员方法也需要根据指定的键,才能从容器中找到该键对应的值;不同之处在于,如果在当前容器中查找失败,该方法不会向容器中添加新的键值对,而是直接抛出out_of_range异常。

举个例子:

#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;int main()
{// 创建 umap 容器unordered_map<string, string> umap{{"Python教程","http://c.biancheng.net/python/"},{"Java教程","http://c.biancheng.net/java/"},{"Linux教程","http://c.biancheng.net/linux/"} };// 获取指定键对应的值string str = umap.at("Python教程");cout << str << endl;// 执行此语句会抛出 out_of_range 异常//cout << umap.at("GO教程");return 0;
}

程序执行结果为:

http://c.biancheng.net/python/

此程序中,第 14 行代码用于获取 umap 容器中键为“Python教程”对应的值,由于 umap 容器确实有符合条件的键值对,因此可以成功执行;而第 17 行代码,由于当前 umap 容器没有存储以“Go教程”为键的键值对,因此执行此语句会抛出 out_of_range 异常。

  1. 运算符和 at() 成员方法基本能满足大多数场景的需要。除此之外,还可以借助 unordered_map 模板中提供的 find() 成员方法。

和前面方法不同的是,通过 find() 方法得到的是一个正向迭代器,该迭代器的指向分以下 2 种情况:

  1. 当 find() 方法成功找到以指定元素作为键的键值对时,其返回的迭代器就指向该键值对;
  2. 当 find() 方法查找失败时,其返回的迭代器和 end() 方法返回的迭代器一样,指向容器中最后一个键值对之后的位置。

举个例子:

#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;int main()
{// 创建 umap 容器unordered_map<string, string> umap{{"Python教程","http://c.biancheng.net/python/"},{"Java教程","http://c.biancheng.net/java/"},{"Linux教程","http://c.biancheng.net/linux/"} };// 查找成功unordered_map<string, string>::iterator iter = umap.find("Python教程");cout << iter->first << " " << iter->second << endl;// 查找失败unordered_map<string, string>::iterator iter2 = umap.find("GO教程");if (iter2 == umap.end()) {cout << "当前容器中没有以\"GO教程\"为键的键值对";}return 0;
}

程序执行结果为:

Python教程 http://c.biancheng.net/python/
当前容器中没有以"GO教程"为键的键值对
  1. 除了 find() 成员方法之外,甚至可以借助 begin()/end() 或者 cbegin()/cend(),通过遍历整个容器中的键值对来找到目标键值对。

举个例子:

#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;int main()
{// 创建 umap 容器unordered_map<string, string> umap{{"Python教程","http://c.biancheng.net/python/"},{"Java教程","http://c.biancheng.net/java/"},{"Linux教程","http://c.biancheng.net/linux/"} };// 遍历整个容器中存储的键值对for (auto iter = umap.begin(); iter != umap.end(); ++iter) {//判断当前的键值对是否就是要找的if (!iter->first.compare("Java教程")) {cout << iter->second << endl;break;}}return 0;
}

程序执行结果为:

http://c.biancheng.net/java/

以上 4 种方法中,前 2 种方法基本能满足多数场景的需要,建议初学者首选 at() 成员方法!

2.6 C++ unordered_map insert()方法

为了方便向已建 unordered_map 容器中添加新的键值对,该容器模板中提供了 insert() 方法,unordered_map 模板类中,提供了多种语法格式的 insert() 方法,根据功能的不同,可划分为以下几种用法。

  1. insert() 方法可以将 pair 类型的键值对元素添加到 unordered_map 容器中,其语法格式有 2 种:
// 以普通方式传递参数
pair<iterator,bool> insert ( const value_type& val );
// 以右值引用的方式传递参数
template <class P>pair<iterator,bool> insert ( P&& val );

以上 2 种格式中,参数 val 表示要添加到容器中的目标键值对元素;该方法的返回值为 pair类型值,内部包含一个 iterator 迭代器和 bool 变量:

  • 当 insert() 将 val 成功添加到容器中时,返回的迭代器指向新添加的键值对,bool 值为 True;
  • 当 insert() 添加键值对失败时,意味着当前容器中本就存储有和要添加键值对的键相等的键值对,这种情况下,返回的迭代器将指向这个导致插入操作失败的迭代器,bool 值为 False。

举个例子:

#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;int main()
{// 创建空 umap 容器unordered_map<string, string> umap;// 构建要添加的键值对std::pair<string, string>mypair("STL教程", "http://c.biancheng.net/stl/");// 创建接收 insert() 方法返回值的pair类型变量std::pair<unordered_map<string, string>::iterator, bool> ret;// 调用 insert() 方法的第一种语法格式ret = umap.insert(mypair);cout << "bool = " << ret.second << endl;cout << "iter -> " << ret.first->first <<" " << ret.first->second << endl;// 调用 insert() 方法的第二种语法格式ret = umap.insert(std::make_pair("Python教程","http://c.biancheng.net/python/"));cout << "bool = " << ret.second << endl;cout << "iter -> " << ret.first->first << " " << ret.first->second << endl;return 0;
}

程序执行结果为:

bool = 1
iter -> STL教程 http://c.biancheng.net/stl/
bool = 1
iter -> Python教程 http://c.biancheng.net/python/

从输出结果很容易看出,两次添加键值对的操作,insert() 方法返回值中的 bool 变量都为 1,表示添加成功,此时返回的迭代器指向的是添加成功的键值对。

  1. 除此之外,insert() 方法还可以指定新键值对要添加到容器中的位置,其语法格式如下:
// 以普通方式传递 val 参数
iterator insert ( const_iterator hint, const value_type& val );
// 以右值引用方法传递 val 参数
template <class P>iterator insert ( const_iterator hint, P&& val );

以上 2 种语法格式中,hint 参数为迭代器,用于指定新键值对要添加到容器中的位置;val 参数指的是要添加容器中的键值对;方法的返回值为迭代器:

  • 如果 insert() 方法成功添加键值对,该迭代器指向新添加的键值对;
  • 如果 insert() 方法添加键值对失败,则表示容器中本就包含有相同键的键值对,该方法返回的迭代器就指向容器中键相同的键值对;

注意,以上 2 种语法格式中,虽然通过 hint 参数指定了新键值对添加到容器中的位置,但该键值对真正存储的位置,并不是 hint 参数说了算,最终的存储位置仍取决于该键值对的键的值。

举个例子:

#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;int main()
{// 创建空 umap 容器unordered_map<string, string> umap;// 构建要添加的键值对std::pair<string, string>mypair("STL教程", "http://c.biancheng.net/stl/");// 创建接收 insert() 方法返回值的迭代器类型变量unordered_map<string, string>::iterator iter;// 调用第一种语法格式iter = umap.insert(umap.begin(), mypair);cout << "iter -> " << iter->first <<" " << iter->second << endl;// 调用第二种语法格式iter = umap.insert(umap.begin(),std::make_pair("Python教程", "http://c.biancheng.net/python/"));cout << "iter -> " << iter->first << " " << iter->second << endl;return 0;
}

程序输出结果为:

iter -> STL教程 http://c.biancheng.net/stl/
iter -> Python教程 http://c.biancheng.net/python/
  1. insert() 方法还支持将某一个 unordered_map 容器中指定区域内的所有键值对,复制到另一个 unordered_map 容器中,其语法格式如下:
template <class InputIterator>void insert ( InputIterator first, InputIterator last );

其中 first 和 last 都为迭代器,[first, last)表示复制其它 unordered_map 容器中键值对的区域。

举个例子:

#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;int main()
{// 创建并初始化 umap 容器unordered_map<string, string> umap{ {"STL教程","http://c.biancheng.net/stl/"},{"Python教程","http://c.biancheng.net/python/"},{"Java教程","http://c.biancheng.net/java/"} };// 创建一个空的 unordered_map 容器unordered_map<string, string> otherumap;// 指定要拷贝 umap 容器中键值对的范围unordered_map<string, string>::iterator first = ++umap.begin();unordered_map<string, string>::iterator last = umap.end();// 将指定 umap 容器中 [first,last) 区域内的键值对复制给 otherumap 容器otherumap.insert(first, last);// 遍历 otherumap 容器中存储的键值对for (auto iter = otherumap.begin(); iter != otherumap.end(); ++iter){cout << iter->first << " " << iter->second << endl;}return 0;
}

程序输出结果为:

Python教程 http://c.biancheng.net/python/
Java教程 http://c.biancheng.net/java/
  1. 除了以上 3 种方式,insert() 方法还支持一次向 unordered_map 容器添加多个键值对,其语法格式如下:
void insert ( initializer_list<value_type> il );

其中,il 参数指的是可以用初始化列表的形式指定多个键值对元素。举个例子:

#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;int main()
{// 创建空的 umap 容器unordered_map<string, string> umap;// 向 umap 容器同时添加多个键值对umap.insert({ {"STL教程","http://c.biancheng.net/stl/"},{"Python教程","http://c.biancheng.net/python/"},{"Java教程","http://c.biancheng.net/java/"} });// 遍历输出 umap 容器中存储的键值对for (auto iter = umap.begin(); iter != umap.end(); ++iter){cout << iter->first << " " << iter->second << endl;}return 0;
}

程序输出结果为:

STL教程 http://c.biancheng.net/stl/
Python教程 http://c.biancheng.net/python/
Java教程 http://c.biancheng.net/java/

总的来说,unordered_map 模板类提供的 insert() 方法有以上 4 种,我们可以根据实际场景的需要自行选择使用哪一种。

2.7 C++ unordered_map emplace()和emplace_hint()方法

和前面学的 map、set 等容器一样,C++ 11 标准也为 unordered_map 容器新增了 emplace() 和 emplace_hint() 成员方法。我们知道,实现向已有 unordered_map 容器中添加新键值对,可以通过调用 insert() 方法,但其实还有更好的方法,即使用 emplace() 或者 emplace_hint() 方法,它们完成“向容器中添加新键值对”的效率,要比 insert() 方法高。

2.7.1 unordered_map emplace()方法

emplace() 方法的用法很简单,其语法格式如下:

template <class... Args>pair<iterator, bool> emplace ( Args&&... args );

其中,参数 args 表示可直接向该方法传递创建新键值对所需要的 2 个元素的值,其中第一个元素将作为键值对的键,另一个作为键值对的值。也就是说,该方法无需我们手动创建键值对,其内部会自行完成此工作。

另外需要注意的是,该方法的返回值为 pair 类型值,其包含一个迭代器和一个 bool 类型值:

  • 当 emplace() 成功添加新键值对时,返回的迭代器指向新添加的键值对,bool 值为 True;
  • 当 emplace() 添加新键值对失败时,说明容器中本就包含一个键相等的键值对,此时返回的迭代器指向的就是容器中键相同的这个键值对,bool 值为 False。

举个例子:

#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;int main()
{// 创建 umap 容器unordered_map<string, string> umap;// 定义一个接受 emplace() 方法的 pair 类型变量pair<unordered_map<string, string>::iterator, bool> ret;// 调用 emplace() 方法ret = umap.emplace("STL教程", "http://c.biancheng.net/stl/");// 输出 ret 中包含的 2 个元素的值cout << "bool =" << ret.second << endl;cout << "iter ->" << ret.first->first << " " << ret.first->second << endl;return 0;
}

程序执行结果为:

bool =1
iter ->STL教程 http://c.biancheng.net/stl/

通过执行结果中 bool 变量的值为 1 可以得知,emplace() 方法成功将新键值对添加到了 umap 容器中。

2.7.2 unordered_map emplace_hint()方法

emplace_hint() 方法的语法格式如下:

template <class... Args>iterator emplace_hint ( const_iterator position, Args&&... args );

和 emplace() 方法相同,emplace_hint() 方法内部会自行构造新键值对,因此我们只需向其传递构建该键值对所需的 2 个元素(第一个作为键,另一个作为值)即可。不同之处在于:

  • emplace_hint() 方法的返回值仅是一个迭代器,而不再是 pair 类型变量。当该方法将新键值对成功添加到容器中时,返回的迭代器指向新添加的键值对;反之,如果添加失败,该迭代器指向的是容器中和要添加键值对键相同的那个键值对。
  • emplace_hint() 方法还需要传递一个迭代器作为第一个参数,该迭代器表明将新键值对添加到容器中的位置。需要注意的是,新键值对添加到容器中的位置,并不是此迭代器说了算,最终仍取决于该键值对的键的值。

可以这样理解,emplace_hint() 方法中传入的迭代器,仅是给 unordered_map 容器提供一个建议,并不一定会被容器采纳。

举个例子:

#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;int main()
{// 创建 umap 容器unordered_map<string, string> umap;// 定义一个接受 emplace_hint() 方法的迭代器unordered_map<string,string>::iterator iter;// 调用 empalce_hint() 方法iter = umap.emplace_hint(umap.begin(),"STL教程", "http://c.biancheng.net/stl/");// 输出 emplace_hint() 返回迭代器 iter 指向的键值对的内容cout << "iter ->" << iter->first << " " << iter->second << endl;return 0;
}

程序执行结果为:

iter ->STL教程 http://c.biancheng.net/stl/

2.8 C++ STL unordered_map删除元素:erase()和clear()

C++ STL 标准库为了方便用户可以随时删除 unordered_map 容器中存储的键值对,unordered_map 容器类模板中提供了以下 2 个成员方法:

  • erase():删除 unordered_map 容器中指定的键值对;
  • clear():删除 unordered_map 容器中所有的键值对,即清空容器。

2.8.1 unordered_map erase()方法

为了满足不同场景删除 unordered_map 容器中键值对的需要,此容器的类模板中提供了 3 种语法格式的 erase() 方法。

  1. erase() 方法可以接受一个正向迭代器,并删除该迭代器指向的键值对。该方法的语法格式如下:
iterator erase ( const_iterator position );

其中 position 为指向容器中某个键值对的迭代器,该方法会返回一个指向被删除键值对之后位置的迭代器。举个例子:

#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;int main()
{// 创建 umap 容器unordered_map<string, string> umap{{"STL教程", "http://c.biancheng.net/stl/"},{"Python教程", "http://c.biancheng.net/python/"},{"Java教程", "http://c.biancheng.net/java/"} };// 输出 umap 容器中存储的键值对for (auto iter = umap.begin(); iter != umap.end(); ++iter) {cout << iter->first << " " << iter->second << endl;}cout << "erase:" << endl;// 定义一个接收 erase() 方法的迭代器unordered_map<string,string>::iterator ret;// 删除容器中第一个键值对ret = umap.erase(umap.begin());// 输出 umap 容器中存储的键值对for (auto iter = umap.begin(); iter != umap.end(); ++iter) {cout << iter->first << " " << iter->second << endl;}cout << "ret = " << ret->first << " " << ret->second << endl;return 0;
}

程序执行结果为:

STL教程 http://c.biancheng.net/stl/
Python教程 http://c.biancheng.net/python/
Java教程 http://c.biancheng.net/java/
erase:
Python教程 http://c.biancheng.net/python/
Java教程 http://c.biancheng.net/java/
ret = Python教程 http://c.biancheng.net/python/

可以看到,通过给 erase() 方法传入指向容器中第一个键值对的迭代器,该方法可以将容器中第一个键值对删除,同时返回一个指向被删除键值对之后位置的迭代器。

注意,如果erase()方法删除的是容器存储的最后一个键值对,则该方法返回的迭代器,将指向容器中最后一个键值对之后的位置(等同于 end() 方法返回的迭代器)。

  1. 我们还可以直接将要删除键值对的键作为参数直接传给 erase() 方法,该方法会自行去 unordered_map 容器中找和给定键相同的键值对,将其删除。erase() 方法的语法格式如下:
size_type erase ( const key_type& k );

其中,k 表示目标键值对的键的值;该方法会返回一个整数,其表示成功删除的键值对的数量。举个例子:

#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;int main()
{// 创建 umap 容器unordered_map<string, string> umap{{"STL教程", "http://c.biancheng.net/stl/"},{"Python教程", "http://c.biancheng.net/python/"},{"Java教程", "http://c.biancheng.net/java/"} }; // 输出 umap 容器中存储的键值对for (auto iter = umap.begin(); iter != umap.end(); ++iter) {cout << iter->first << " " << iter->second << endl;}int delNum = umap.erase("Python教程");cout << "delNum = " << delNum << endl;// 再次输出 umap 容器中存储的键值对for (auto iter = umap.begin(); iter != umap.end(); ++iter) {cout << iter->first << " " << iter->second << endl;}return 0;
}

程序执行结果为:

STL教程 http://c.biancheng.net/stl/
Python教程 http://c.biancheng.net/python/
Java教程 http://c.biancheng.net/java/
delNum = 1
STL教程 http://c.biancheng.net/stl/
Java教程 http://c.biancheng.net/java/

通过输出结果可以看到,通过将 “Python教程” 传给 erase() 方法,就成功删除了 umap 容器中键为 “Python教程” 的键值对。

  1. 除了支持删除 unordered_map 容器中指定的某个键值对,erase() 方法还支持一次删除指定范围内的所有键值对,其语法格式如下:
iterator erase ( const_iterator first, const_iterator last );

其中 first 和 last 都是正向迭代器,[first, last) 范围内的所有键值对都会被 erase() 方法删除;同时,该方法会返回一个指向被删除的最后一个键值对之后一个位置的迭代器。举个例子:

#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;int main()
{// 创建 umap 容器unordered_map<string, string> umap{{"STL教程", "http://c.biancheng.net/stl/"},{"Python教程", "http://c.biancheng.net/python/"},{"Java教程", "http://c.biancheng.net/java/"} };// first 指向第一个键值对unordered_map<string, string>::iterator first = umap.begin();// last 指向最后一个键值对unordered_map<string, string>::iterator last = umap.end();// 删除[fist,last)范围内的键值对auto ret = umap.erase(first, last);// 输出 umap 容器中存储的键值对for (auto iter = umap.begin(); iter != umap.end(); ++iter) {cout << iter->first << " " << iter->second << endl;}return 0;
}

执行程序会发现,没有输出任何数据,因为 erase() 方法删除了 umap 容器中 [begin(), end()) 范围内所有的元素。

2.8.2 unordered_map clear()方法

在个别场景中,可能需要一次性删除 unordered_map 容器中存储的所有键值对,可以使用 clear() 方法,其语法格式如下:

void clear()

举个例子:

#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;int main()
{// 创建 umap 容器unordered_map<string, string> umap{{"STL教程", "http://c.biancheng.net/stl/"},{"Python教程", "http://c.biancheng.net/python/"},{"Java教程", "http://c.biancheng.net/java/"} };// 输出 umap 容器中存储的键值对for (auto iter = umap.begin(); iter != umap.end(); ++iter) {cout << iter->first << " " << iter->second << endl;}// 删除容器内所有键值对umap.clear();cout << "umap size = " << umap.size() << endl;return 0;
}

程序执行结果为:

STL教程 http://c.biancheng.net/stl/
Python教程 http://c.biancheng.net/python/
Java教程 http://c.biancheng.net/java/
umap size = 0

显然,通过调用 clear() 方法,原本包含 3 个键值对的 umap 容器,变成了空容器。

注意,虽然使用 erase() 方法的第 3 种语法格式,可能实现删除 unordered_map 容器内所有的键值对,但更推荐使用 clear() 方法。


3. unordered_multimap

3.1 概述

C++ STL 标准库中,除了提供有 unordered_map 无序关联容器,还提供有和 unordered_map 容器非常相似的 unordered_multimap 无序关联容器。

和 unordered_map 容器一样,unordered_multimap 容器也以键值对的形式存储数据,且底层也采用哈希表结构存储各个键值对。两者唯一的不同之处在于,unordered_multimap 容器可以存储多个键相等的键值对,而 unordered_map 容器不行。

无序容器中存储的各个键值对,都会哈希存到各个桶(本质为链表)中。而对于 unordered_multimap 容器来说,其存储的所有键值对中,键相等的键值对会被哈希到同一个桶中存储。

另外值得一提得是,STL 标准库中实现 unordered_multimap 容器的模板类并没有定义在以自己名称命名的头文件中,而是和 unordered_map 容器一样,定义在<unordered_map>头文件,且位于 std 命名空间中。因此,在使用 unordered_multimap 容器之前,程序中应包含如下 2 行代码:

#include <unordered_map>
using namespace std;

注意,第二行代码不是必需的,但如果不用,则后续程序中在使用此容器时,需手动注明 std 命名空间(建议初学者使用)。

unordered_multimap 容器模板的定义如下所示:

template < class Key,      // 键(key)的类型class T,        // 值(value)的类型class Hash = hash<Key>,  // 底层存储键值对时采用的哈希函数class Pred = equal_to<Key>,  // 判断各个键值对的键相等的规则class Alloc = allocator< pair<const Key,T> > // 指定分配器对象的类型> class unordered_multimap;

以上 5 个参数中,必须显式给前 2 个参数传值,且除极个别的情况外,最多只使用前 4 个参数,它们各自的含义和功能如下表所示。

参数含义
<key,T>前 2 个参数分别用于确定键值对中键和值的类型,也就是存储键值对的类型。
Hash = hash<Key>用于指明容器在存储各个键值对时要使用的哈希函数,默认使用 STL 标准库提供的 hash<key> 哈希函数。注意,默认哈希函数只适用于基本数据类型(包括 string 类型),而不适用于自定义的结构体或者类。
Pred = equal_to<Key>unordered_multimap 容器可以存储多个键相等的键值对,而判断是否相等的规则,由此参数指定。默认情况下,使用 STL 标准库中提供的 equal_to<key> 规则,该规则仅支持可直接用 == 运算符做比较的数据类型。

注意,当 unordered_multimap 容器中存储键值对的键为自定义类型时,默认的哈希函数 hash<key> 以及比较函数 equal_to<key> 将不再适用,这种情况下,需要我们自定义适用的哈希函数和比较函数,并分别显式传递给 Hash 参数和 Pred 参数。

3.2 成员方法

和 unordered_map 容器相比,unordered_multimap 容器的类模板中没有重载 [ ] 运算符,也没有提供 at() 成员方法,除此之外它们完全一致。

没有提供 [ ] 运算符和 at() 成员方法,意味着 unordered_multimap 容器无法通过指定键获取该键对应的值,因为该容器允许存储多个键相等的键值对,每个指定的键可能对应多个不同的值。

unordered_multimap 类模板提供的成员方法如下表所示:

成员方法功能
begin()返回指向容器中第一个键值对的正向迭代器。
end()返回指向容器中最后一个键值对之后位置的正向迭代器。
cbegin()和 begin() 功能相同,只不过在其基础上增加了 const 属性,即该方法返回的迭代器不能用于修改容器内存储的键值对。
cend()和 end() 功能相同,只不过在其基础上,增加了 const 属性,即该方法返回的迭代器不能用于修改容器内存储的键值对。
empty()若容器为空,则返回 true;否则 false。
size()返回当前容器中存有键值对的个数。
max_size()返回容器所能容纳键值对的最大个数,不同的操作系统,其返回值亦不相同。
find(key)查找以 key 为键的键值对,如果找到,则返回一个指向该键值对的正向迭代器;反之,则返回一个指向容器中最后一个键值对之后位置的迭代器(如果 end() 方法返回的迭代器)。
count(key)在容器中查找以 key 键的键值对的个数。
equal_range(key)返回一个 pair 对象,其包含 2 个迭代器,用于表明当前容器中键为 key 的键值对所在的范围。
emplace()向容器中添加新键值对,效率比 insert() 方法高。
emplace_hint()向容器中添加新键值对,效率比 insert() 方法高。
insert()向容器中添加新键值对。
erase()删除指定键值对。
clear()清空容器,即删除容器中存储的所有键值对。
swap()交换 2 个 unordered_multimap 容器存储的键值对,前提是必须保证这 2 个容器的类型完全相等。
bucket_count()返回当前容器底层存储键值对时,使用桶(一个线性链表代表一个桶)的数量。
max_bucket_count()返回当前系统中,unordered_multimap 容器底层最多可以使用多少桶。
bucket_size(n)返回第 n 个桶中存储键值对的数量。
bucket(key)返回以 key 为键的键值对所在桶的编号。
load_factor()返回 unordered_multimap 容器中当前的负载因子。负载因子,指的是的当前容器中存储键值对的数量(size())和使用桶数(bucket_count())的比值,即 load_factor() = size() / bucket_count()。
max_load_factor()返回或者设置当前 unordered_multimap 容器的负载因子。
rehash(n)将当前容器底层使用桶的数量设置为 n。
reserve()将存储桶的数量(也就是 bucket_count() 方法的返回值)设置为至少容纳count个元(不超过最大负载因子)所需的数量,并重新整理容器。
hash_function()返回当前容器使用的哈希函数对象。

注意,对于实现互换 2 个相同类型 unordered_multimap 容器的键值对,除了可以调用该容器模板类中提供的 swap() 成员方法外,STL 标准库还提供了同名的 swap() 非成员函数。

3.3 创建C++ unordered_multimap容器

常见的创建 unordered_map 容器的方法有以下几种。

  1. 利用 unordered_multimap 容器类模板中的默认构造函数,可以创建空的 unordered_multimap 容器。比如:
std::unordered_multimap<std::string, std::string> myummap;

由此,就创建好了一个可存储 <string, string> 类型键值对的 unordered_multimap 容器,只不过当前容器是空的,即没有存储任何键值对。

  1. 当然,在创建空 unordered_multimap 容器的基础上,可以完成初始化操作。比如:
unordered_multimap<string, string> myummap{{"Python教程","http://c.biancheng.net/python/"},{"Java教程","http://c.biancheng.net/java/"},{"Linux教程","http://c.biancheng.net/linux/"} };

通过此方法创建的 myummap 容器中,就包含有 3 个键值对。

  1. 另外,unordered_multimap 模板中还提供有复制(拷贝)构造函数,可以实现在创建 unordered_multimap 容器的基础上,用另一 unordered_multimap 容器中的键值对为其初始化。

例如,在第二种方式创建好 myummap 容器的基础上,再创建并初始化一个 myummap2 容器:

unordered_multimap<string, string> myummap2(myummap);

由此,刚刚创建好的 myummap2 容器中,就包含有 myummap 容器中所有的键值对。

除此之外,C++ 11 标准中还向 unordered_multimap 模板类增加了移动构造函数,即以右值引用的方式将临时 unordered_multimap 容器中存储的所有键值对,全部复制给新建容器。例如:

// 返回临时 unordered_multimap 容器的函数
std::unordered_multimap <std::string, std::string > retUmmap() {std::unordered_multimap<std::string, std::string>tempummap{{"Python教程","http://c.biancheng.net/python/"},{"Java教程","http://c.biancheng.net/java/"},{"Linux教程","http://c.biancheng.net/linux/"} };return tempummap;
}
// 创建并初始化 myummap 容器
std::unordered_multimap<std::string, std::string> myummap(retummap());

注意,无论是调用复制构造函数还是拷贝构造函数,必须保证 2 个容器的类型完全相同。

  1. 当然,如果不想全部拷贝,可以使用 unordered_multimap 类模板提供的迭代器,在现有 unordered_multimap 容器中选择部分区域内的键值对,为新建 unordered_multimap 容器初始化。例如:
// 传入 2 个迭代器
std::unordered_multimap<std::string, std::string> myummap2(++myummap.begin(), myummap.end());

通过此方式创建的 myummap2 容器,其内部就包含 myummap 容器中除第 1 个键值对外的所有其它键值对。


4. unordered_set

4.1 概述

unordered_set 容器,可直译为“无序 set 容器”,即 unordered_set 容器和 set 容器很像,唯一的区别就在于 set 容器会自行对存储的数据进行排序,而 unordered_set 容器不会。

总的来说,unordered_set 容器具有以下几个特性:

  1. 不再以键值对的形式存储数据,而是直接存储数据的值;
  2. 容器内部存储的各个元素的值都互不相等,且不能被修改;
  3. 不会对内部存储的数据进行排序(这和该容器底层采用哈希表结构存储数据有关)。

对于 unordered_set 容器不以键值对的形式存储数据,也可以这样认为,即 unordered_set 存储的都是键和值相等的键值对,为了节省存储空间,该类容器在实际存储时选择只存储每个键值对的值。

另外,实现 unordered_set 容器的模板类定义在<unordered_set>头文件,并位于 std 命名空间中。这意味着,如果程序中需要使用该类型容器,则首先应该包含如下代码:

#include <unordered_set>
using namespace std;

注意,第二行代码不是必需的,但如果不用,则程序中只要用到该容器时,必须手动注明 std 命名空间(建议初学者使用)。

unordered_set 容器的类模板定义如下:

template < class Key,            // 容器中存储元素的类型class Hash = hash<Key>,    // 确定元素存储位置所用的哈希函数class Pred = equal_to<Key>,   // 判断各个元素是否相等所用的函数class Alloc = allocator<Key>   // 指定分配器对象的类型> class unordered_set;

可以看到,以上 4 个参数中,只有第一个参数没有默认值,这意味着如果我们想创建一个 unordered_set 容器,至少需要手动传递 1 个参数。事实上,在 99% 的实际场景中最多只需要使用前 3 个参数(各自含义如下表所示),最后一个参数保持默认值即可。

参数含义
Key确定容器存储元素的类型,如果读者将 unordered_set 看做是存储键和值相同的键值对的容器,则此参数则用于确定各个键值对的键和值的类型,因为它们是完全相同的,因此一定是同一数据类型的数据。
Hash = hash<Key>指定 unordered_set 容器底层存储各个元素时,所使用的哈希函数。需要注意的是,默认哈希函数 hash<Key> 只适用于基本数据类型(包括 string 类型),而不适用于自定义的结构体或者类。
Pred = equal_to<Key>unordered_set 容器内部不能存储相等的元素,而衡量 2 个元素是否相等的标准,取决于该参数指定的函数。 默认情况下,使用 STL 标准库中提供的 equal_to<key> 规则,该规则仅支持可直接用 == 运算符做比较的数据类型。

注意,如果 unordered_set 容器中存储的元素为自定义的数据类型,则默认的哈希函数 hash<key> 以及比较函数 equal_to<key> 将不再适用,只能自己设计适用该类型的哈希函数和比较函数,并显式传递给 Hash 参数和 Pred 参数。

4.2 成员方法

成员方法功能
begin()返回指向容器中第一个元素的正向迭代器。
end();返回指向容器中最后一个元素之后位置的正向迭代器。
cbegin()和 begin() 功能相同,只不过其返回的是 const 类型的正向迭代器。
cend()和 end() 功能相同,只不过其返回的是 const 类型的正向迭代器。
empty()若容器为空,则返回 true;否则 false。
size()返回当前容器中存有元素的个数。
max_size()返回容器所能容纳元素的最大个数,不同的操作系统,其返回值亦不相同。
find(key)查找以值为 key 的元素,如果找到,则返回一个指向该元素的正向迭代器;反之,则返回一个指向容器中最后一个元素之后位置的迭代器(如果 end() 方法返回的迭代器)。
count(key)在容器中查找值为 key 的元素的个数。
equal_range(key)返回一个 pair 对象,其包含 2 个迭代器,用于表明当前容器中值为 key 的元素所在的范围。
emplace()向容器中添加新元素,效率比 insert() 方法高。
emplace_hint()向容器中添加新元素,效率比 insert() 方法高。
insert()向容器中添加新元素。
erase()删除指定元素。
clear()清空容器,即删除容器中存储的所有元素。
swap()交换 2 个 unordered_map 容器存储的元素,前提是必须保证这 2 个容器的类型完全相等。
bucket_count()返回当前容器底层存储元素时,使用桶(一个线性链表代表一个桶)的数量。
max_bucket_count()返回当前系统中,unordered_map 容器底层最多可以使用多少桶。
bucket_size(n)返回第 n 个桶中存储元素的数量。
bucket(key)返回值为 key 的元素所在桶的编号。
load_factor()返回 unordered_map 容器中当前的负载因子。负载因子,指的是的当前容器中存储元素的数量(size())和使用桶数(bucket_count())的比值,即 load_factor() = size() / bucket_count()。
max_load_factor()返回或者设置当前 unordered_map 容器的负载因子。
rehash(n)将当前容器底层使用桶的数量设置为 n。
reserve()将存储桶的数量(也就是 bucket_count() 方法的返回值)设置为至少容纳count个元(不超过最大负载因子)所需的数量,并重新整理容器。
hash_function()返回当前容器使用的哈希函数对象。

注意,此容器模板类中没有重载 [ ] 运算符,也没有提供 at() 成员方法。不仅如此,由于 unordered_set 容器内部存储的元素值不能被修改,因此无论使用那个迭代器方法获得的迭代器,都不能用于修改容器中元素的值。

另外,对于实现互换 2 个相同类型 unordered_set 容器的所有元素,除了调用上表中的 swap() 成员方法外,还可以使用 STL 标准库提供的 swap() 非成员函数,它们具有相同的名称,用法也相同(都只需要传入 2 个参数即可),仅是调用方式上有差别。

4.3 创建C++ unordered_set容器

前面介绍了如何创建 unordered_map 和 unordered_multimap 容器,值得一提的是,创建它们的所有方式完全适用于 unordereded_set 容器。

  1. 通过调用 unordered_set 模板类的默认构造函数,可以创建空的 unordered_set 容器。比如:
std::unordered_set<std::string> uset;

由此,就创建好了一个可存储 string 类型值的 unordered_set 容器,该容器底层采用默认的哈希函数 hash<Key> 和比较函数 equal_to<Key>。

  1. 当然,在创建 unordered_set 容器的同时,可以完成初始化操作。比如:
std::unordered_set<std::string> uset{ "http://c.biancheng.net/c/","http://c.biancheng.net/java/","http://c.biancheng.net/linux/" };

通过此方法创建的 uset 容器中,就包含有 3 个 string 类型元素。

  1. 还可以调用 unordered_set 模板中提供的复制(拷贝)构造函数,将现有 unordered_set 容器中存储的元素全部用于为新建 unordered_set 容器初始化。

例如,在第二种方式创建好 uset 容器的基础上,再创建并初始化一个 uset2 容器:

std::unordered_set<std::string> uset2(uset);

由此,umap2 容器中就包含有 umap 容器中所有的元素。

除此之外,C++ 11 标准中还向 unordered_set 模板类增加了移动构造函数,即以右值引用的方式,利用临时 unordered_set 容器中存储的所有元素,给新建容器初始化。例如:

// 返回临时 unordered_set 容器的函数
std::unordered_set <std::string> retuset() {std::unordered_set<std::string> tempuset{ "http://c.biancheng.net/c/","http://c.biancheng.net/java/","http://c.biancheng.net/linux/" };return tempuset;
}
// 调用移动构造函数,创建 uset 容器
std::unordered_set<std::string> uset(retuset());

注意,无论是调用复制构造函数还是拷贝构造函数,必须保证 2 个容器的类型完全相同。

  1. 当然,如果不想全部拷贝,可以使用 unordered_set 类模板提供的迭代器,在现有 unordered_set 容器中选择部分区域内的元素,为新建 unordered_set 容器初始化。例如:
// 传入 2 个迭代器
std::unordered_set<std::string> uset2(++uset.begin(),uset.end());

通过此方式创建的 uset2 容器,其内部就包含 uset 容器中除第 1 个元素外的所有其它元素。


5. unordered_multiset

5.1 概述

前面章节介绍了 unordered_set 容器的特定和用法,在此基础上,本节再介绍一个类似的 C++ STL 无序容器,即 unordered_multiset 容器。

所谓“类似”,指的是 unordered_multiset 容器大部分的特性都和 unordered_set 容器相同,包括:

  1. unordered_multiset 不以键值对的形式存储数据,而是直接存储数据的值;
  2. 该类型容器底层采用的也是哈希表存储结构,它不会对内部存储的数据进行排序;
  3. unordered_multiset 容器内部存储的元素,其值不能被修改。

和 unordered_set 容器不同的是,unordered_multiset 容器可以同时存储多个值相同的元素,且这些元素会存储到哈希表中同一个桶(本质就是链表)上。

可以这样认为,unordered_multiset 除了能存储相同值的元素外,它和 unordered_set 容器完全相同。

另外值得一提的是,实现 unordered_multiset 容器的模板类并没有定义在以该容器名命名的文件中,而是和 unordered_set 容器共用同一个<unordered_set>头文件,并且也位于 std 命名空间。因此,如果程序中需要使用该类型容器,应包含如下代码:

#include <unordered_set>
using namespace std;

注意,第二行代码不是必需的,但如果不用,则程序中只要用到该容器时,必须手动注明 std 命名空间(建议初学者使用)。

unordered_multiset 容器类模板的定义如下:

template < class Key,            // 容器中存储元素的类型class Hash = hash<Key>,    // 确定元素存储位置所用的哈希函数class Pred = equal_to<Key>,   // 判断各个元素是否相等所用的函数class Alloc = allocator<Key>   // 指定分配器对象的类型> class unordered_multiset;

需要说明的是,在 99% 的实际场景中,最多只需要使用前 3 个参数(各自含义如下表所示),最后一个参数保持默认值即可。

参数含义
Key确定容器存储元素的类型,如果读者将 unordered_multiset 看做是存储键和值相同的键值对的容器,则此参数则用于确定各个键值对的键和值的类型,因为它们是完全相同的,因此一定是同一数据类型的数据。
Hash = hash<Key>指定 unordered_multiset 容器底层存储各个元素时所使用的哈希函数。需要注意的是,默认哈希函数 hash<Key> 只适用于基本数据类型(包括 string 类型),而不适用于自定义的结构体或者类。
Pred = equal_to<Key>用于指定 unordered_multiset 容器判断元素值相等的规则。默认情况下,使用 STL 标准库中提供的 equal_to<key> 规则,该规则仅支持可直接用 == 运算符做比较的数据类型。

总之,如果 unordered_multiset 容器中存储的元素为自定义的数据类型,则默认的哈希函数 hash<key> 以及比较函数 equal_to<key> 将不再适用,只能自己设计适用该类型的哈希函数和比较函数,并显式传递给 Hash 参数和 Pred 参数。

5.2 成员函数

unordered_multiset 模板类中提供的成员方法,无论是种类还是数量,都和 unordered_set 类模板一样,如下表所示:

成员方法功能
begin()返回指向容器中第一个元素的正向迭代器。
end();返回指向容器中最后一个元素之后位置的正向迭代器。
cbegin()和 begin() 功能相同,只不过其返回的是 const 类型的正向迭代器。
cend()和 end() 功能相同,只不过其返回的是 const 类型的正向迭代器。
empty()若容器为空,则返回 true;否则 false。
size()返回当前容器中存有元素的个数。
max_size()返回容器所能容纳元素的最大个数,不同的操作系统,其返回值亦不相同。
find(key)查找以值为 key 的元素,如果找到,则返回一个指向该元素的正向迭代器;反之,则返回一个指向容器中最后一个元素之后位置的迭代器(如果 end() 方法返回的迭代器)。
count(key)在容器中查找值为 key 的元素的个数。
equal_range(key)返回一个 pair 对象,其包含 2 个迭代器,用于表明当前容器中值为 key 的元素所在的范围。
emplace()向容器中添加新元素,效率比 insert() 方法高。
emplace_hint()向容器中添加新元素,效率比 insert() 方法高。
insert()向容器中添加新元素。
erase()删除指定元素。
clear()清空容器,即删除容器中存储的所有元素。
swap()交换 2 个 unordered_multimap 容器存储的元素,前提是必须保证这 2 个容器的类型完全相等。
bucket_count()返回当前容器底层存储元素时,使用桶(一个线性链表代表一个桶)的数量。
max_bucket_count()返回当前系统中,容器底层最多可以使用多少桶。
bucket_size(n)返回第 n 个桶中存储元素的数量。
bucket(key)返回值为 key 的元素所在桶的编号。
load_factor()返回容器当前的负载因子。所谓负载因子,指的是的当前容器中存储元素的数量(size())和使用桶数(bucket_count())的比值,即 load_factor() = size() / bucket_count()。
max_load_factor()返回或者设置当前 unordered_map 容器的负载因子。
rehash(n)将当前容器底层使用桶的数量设置为 n。
reserve()将存储桶的数量(也就是 bucket_count() 方法的返回值)设置为至少容纳count个元(不超过最大负载因子)所需的数量,并重新整理容器。
hash_function()返回当前容器使用的哈希函数对象。

注意,和 unordered_set 容器一样,unordered_multiset 模板类也没有重载 [ ] 运算符,没有提供 at() 成员方法。不仅如此,无论是由哪个成员方法返回的迭代器,都不能用于修改容器中元素的值。

另外,对于互换 2 个相同类型 unordered_multiset 容器存储的所有元素,除了调用上表中的 swap() 成员方法外,STL 标准库也提供了 swap() 非成员函数。

5.3 创建C++ unordered_multiset容器

考虑到不同场景的需要,unordered_multiset 容器模板类共提供了以下 4 种创建 unordered_multiset 容器的方式。

  1. 调用 unordered_multiset 模板类的默认构造函数,可以创建空的 unordered_multiset 容器。比如:
std::unordered_multiset<std::string> umset;

由此,就创建好了一个可存储 string 类型值的 unordered_multiset 容器,该容器底层采用默认的哈希函数 hash<Key> 和比较函数 equal_to<Key>。

  1. 当然,在创建 unordered_multiset 容器的同时,可以进行初始化操作。比如:
std::unordered_multiset<std::string> umset{ "http://c.biancheng.net/c/","http://c.biancheng.net/java/","http://c.biancheng.net/linux/" };

通过此方法创建的 umset 容器中,内部存有 3 个 string 类型元素。

  1. 还可以调用 unordered_multiset 模板中提供的复制(拷贝)构造函数,将现有 unordered_multiset 容器中存储的元素全部用于为新建 unordered_multiset 容器初始化。

例如,在第二种方式创建好 umset 容器的基础上,再创建并初始化一个 umset2 容器:

std::unordered_multiset<std::string> umset2(umset);

由此,umap2 容器中就包含有 umap 容器中所有的元素。

除此之外,C++ 11 标准中还向 unordered_multiset 模板类增加了移动构造函数,即以右值引用的方式,利用临时 unordered_multiset 容器中存储的所有元素,给新建容器初始化。例如:

// 返回临时 unordered_multiset 容器的函数
std::unordered_multiset <std::string> retumset() {std::unordered_multiset<std::string> tempumset{ "http://c.biancheng.net/c/","http://c.biancheng.net/java/","http://c.biancheng.net/linux/" };return tempumset;
}
// 调用移动构造函数,创建 umset 容器
std::unordered_multiset<std::string> umset(retumset());

注意,无论是调用复制构造函数还是拷贝构造函数,必须保证 2 个容器的类型完全相同。

  1. 当然,如果不想全部拷贝,可以使用 unordered_multiset 类模板提供的迭代器,在现有 unordered_multiset 容器中选择部分区域内的元素,为新建的 unordered_multiset 容器初始化。例如:
// 传入 2 个迭代器
std::unordered_multiset<std::string> umset2(++umset.begin(), umset.end());

通过此方式创建的 umset2 容器,其内部就包含 umset 容器中除第 1 个元素外的所有其它元素。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1136.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【稳定性验证】视频流mesh环境下稳定性验证

目录 正常保持上线状态 延时丢包 丢包&#xff08;很稳&#xff09; 延时 丢包 乱序 &#xff08;也很稳&#xff09; webGL lost 正常保持上线状态 延时丢包 丢包&#xff08;很稳&#xff09; 延时 丢包 乱序 &#xff08;也很稳&#xff09; webGL lost

Flink SQL之常用函数

官方函数查询地址&#xff1a;https://nightlies.apache.org/flink/flink-docs-release-1.12/dev/table/functions/systemFunctions.html 可根据使用的版本查找&#xff0c;该链接为1.12版本。 1.比较函数 <> > > < < 注意&#xff1a;select nullnull…

手写Spring框架---MVC实现

目录 预备 自研框架MVC的实现 MVC架构草图&#xff1a; 大致流程 实现思路 自定义注解 JavaBean 请求的拦截-建立DispatcherServlet 责任链处理请求 RequestProcessor矩阵 Render矩阵 预备 在DispatcherServlet&#xff1a; 解析请求路径和请求方法依赖容器&#xf…

PostgreSQL 笔记

PostgreSQL 笔记 一、简介 这里主要是记录学习 PostgreSQL 常用操作命令&#xff0c;方便今后查阅&#xff01;&#xff01;&#xff01; PostgreSQL 是一个免费的对象-关系数据库服务器(ORDBMS)&#xff0c;在灵活的BSD许可证下发行。 PostgreSQL 开发者把它念作 post-gress…

水库大坝安全监测系统是由什么组成的?

水库大坝是防洪抗灾的重要设施&#xff0c;它们的安全性直接关系到人民群众的生命财产安全。因此&#xff0c;水库大坝的安全监测必不可少。水库大坝安全监测系统是一种集成了数据采集、传输、处理和分析的技术平台&#xff0c;能够实时、准确地监测大坝的状态&#xff0c;及时…

一.《某三国》人物属性及其相关属性

人物属性 1.找一个可以操控变化的属性来找 比如血量.坐标或者五铢(绑定金币),这里我们用五铢找 五铢只要打一个怪就会加一点 2.我们直接搜变化即可搜到 五铢地址0AD64EAC 3.我们CE给地址下访问 4.这里我们最后找第一条访问 因为他是被改变的 或者你CE给地址下写入 5.然后我…

APSIM作物生长模拟模型:农田管理、土壤碳氮平衡、土壤水平衡、作物产量、物候发育光合生产、作物产量等

查看原文>>>基于R语言APSIM模型高级应用及批量模拟实践技术 目录 专题一、APSIM模型应用与R语言数据清洗 专题二、APSIM气象文件准备与R语言融合应用 专题三、APSIM模型的物候发育和光合生产模块 专题四、APSIM物质分配与产量模拟 专题五、APSIM土壤水平衡模块 …

前端浏览器缓存的好处和弊端以及如何处理弊端

浏览器缓存 好处&#xff1a; 减少冗余的数据传输&#xff0c;节省带宽。减轻服务器的请求压力&#xff0c;因为有缓存可以减少向服务器发送请求&#xff0c;资源从缓存中读取&#xff0c;加快客户端的访问速度。因为无需从服务器请求等待响应 缺点&#xff1a; 系统更新时…

使用docker-file 将springboot项目打成镜像,发布成容器服务

一 docker-file将jar包发布成容器服务 1.1 docker的安装 [rootlocalhost ~]# uname -r 3.10.0-862.el7.x86_64 [rootlocalhost ~]# yum install docker [rootlocalhost export]# systemctl start docker [rootlocalhost export]# docker -v Docker version 1.13.1, build…

CANoe测试车载以太网 Link up/Link down时间

系列文章目录 文章目录 系列文章目录前言一、CANoe配置Link up/Link down1、配置端口2、打开端口二、CANoe测试Link up /Link down时间1、打开Trace监听窗口2、CANoe查看Link up时间3、CANoe查看Link down时间前言 随着智能电动汽车的行业的发展,车载以太网的应用越来越广泛,…

学习react,复制一个civitai(C站)-更新3

更新内容 优化了一下加载速度 图片列表 初步更新了199张图片&#xff0c;大部分都有stable diffusion 的prompts。 可以直接复制到AI绘画里面使用。 先来看看效果图吧&#xff1a; 我还是挺喜欢这种砌砖流布局 技术点 同样使用了砌墙瀑布流布局:masonry js 安装方法 npm …

修复Google翻译小工具

修复Google翻译小工具 前言自己改的&#xff0c;小点声用 sudo vim /etc/hosts加上以下内容即可 nslookup google.cn |grep Address: |head -n 2|tail -n 1 |awk {print $2} translate.google.com

Golang 面向对象编程 概念

在Golang里面没有类的概念&#xff0c;它是使用结构体的东西来替代这个类的概念。 //张老太养了2只猫猫&#xff1a;一只名字叫小白&#xff0c;今年3岁&#xff0c;白色。还有一只叫小花&#xff0c;今年8岁&#xff0c;花色。 请编写一个程序&#xff0c;当用户输入小猫的名…

Redis第一天

Redis第一天 Redis基本数据结构数据结构字符串Redis链表字典跳跃表压缩列表 对象字符串对象列表对象哈希对象集合对象有序集合对象类型检查键回收 Redis&#xff08;Remote Dictionary Server )&#xff0c;即远程字典服务&#xff0c;是一个开源的使用ANSI C语言编写、支持网络…

ios 启动页storyboard 使用记录

本文简单记录ios启动页storyboard 如何使用和注意事项。 xcode窗口简介 以xcode14为例&#xff0c;新建项目如下图&#xff0c;左边文件栏中的LaunchScreen.storyboard 为默认启动页布局。窗口中间部分是storyboard中的组件列表&#xff0c;右侧为预览&#xff0c;可以看到渲…

摩尔投票算法(Moore‘s Voting Algorithm)及例题

摩尔投票算法&#xff08;Moores Voting Algorithm&#xff09;及例题 摩尔投票算法简介摩尔投票算法算法思想摩尔投票算法经典题目169. 多数元素229. 多数元素 II6927. 合法分割的最小下标 上午打力扣第 354 场周赛最后十五分钟用摩尔投票算法直接秒了第三题。 摩尔投票算法简…

matlab数组中元素位置

%判断某个元素的位置 find(a2)就得到2所在的位置Matlab判断某数组中是否包含某元素及其位置

torch.nn.BCEWithLogitsLoss与torch.nn.BCELoss

torch.nn.BCEWithLogitsLoss相当于sigmoidtorch.nn.BCELoss。代码示例如下&#xff0c; import torch import torch.nn as nnBCEWithLogitsLoss nn.BCEWithLogitsLoss() BCELoss nn.BCELoss()x torch.randn((1,)) y torch.FloatTensor([1])Loss_BCEWithLogits BCEWithLog…

【图像处理】基于双目视觉的物体体积测量算法研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

【QT/OpenCV】QT实现张正友相机标定

相机标定 01、相机标定02、OpenCV函数及其张正友标定法2.1、相机标定步骤2.2、相机标定相关函数2.2.1 提取角点--- findChessboardCorners2.2.2 亚像素角点提取1--- find4QuadCornerSubpix2.2.3 亚像素角点提取2--- cornerSubPix2.2.4 绘制内角点 --- drawChessboardCorners2.2…