科技改变生活 · 科技引领未来
继 map、multimap、set、multiset 关联式容器之后,从本节开始,再讲解一类“特殊”的关联式容器,它们常被称为“无序容器”、“哈希容器”或者“无序关联容器”。
和关联式容器一样,无序容器也使用键值对(pair 类型)的方式存储数据。不过,本教程将二者分开进行讲解,因为它们有本质上的不同:
C++ STL 底层采用哈希表实现无序容器时,会将所有数据存储到一整块连续的内存空间中,并且当数据存储位置发生冲突时,解决方法选用的是“链地址法”(又称“开链法”)。有关哈希表存储结构,读者可阅读《哈希表(散列表)详解》一文做详细了解。
基于底层实现采用了不同的数据结构,因此和关联式容器相比,无序容器具有以下 2 个特点:
和关联式容器一样,无序容器只是一类容器的统称,其包含有 4 个具体容器,分别为 unordered_map、unordered_multimap、unordered_set 以及 unordered_multiset。
表 1 对这 4 种无序容器的功能做了详细的介绍。
可能读者已经发现,以上 4 种无序容器的名称,仅是在前面所学的 4 种关联式容器名称的基础上,添加了 &34;unordered_&34;。如果读者已经学完了 map、multimap、set 和 multiset 容器不难发现,以 map 和 unordered_map 为例,其实它们仅有一个区别,即 map 容器内存会对存储的键值对进行排序,而 unordered_map 不会。
也就是说,C++ 11 标准的 STL 中,在已提供有 4 种关联式容器的基础上,又新增了各自的“unordered”版本(无序版本、哈希版本),提高了查找指定元素的效率。
有读者可能会问,既然无序容器和之前所学的关联式容器类似,那么在实际使用中应该选哪种容器呢?总的来说,实际场景中如果涉及大量遍历容器的操作,建议首选关联式容器;反之,如果更多的操作是通过键获取对应的值,则应首选无序容器。
为了加深读者对无序容器的认识,这里以 unordered_map 容器为例,举个例子(不必深究该容器的具体用法):
include
include
include
using namespace std;
int main()
{
//创建并初始化一个 unordered_map 容器,其存储的 类型的键值对
std::unordered_map my_uMap{
{&34;C语言教程&34;,&34;http://c.biancheng.net/c/&34;},
{&34;Python教程&34;,&34;http://c.biancheng.net/python/&34;},
{&34;Java教程&34;,&34;http://c.biancheng.net/java/&34;} };
//查找指定键对应的值,效率比关联式容器高
string str = my_uMap.at(&34;C语言教程&34;);
cout << &34;str = &34; << str << endl;
//使用迭代器遍历哈希容器,效率不如关联式容器
for (auto iter = my_uMap.begin(); iter != my_uMap.end(); ++iter)
{
//pair 类型键值对分为 2 部分
cout << iter->first << &34; &34; << 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/
C++ STL 标准库中提供有 4 种无序关联式容器,本节先讲解 unordered_map 容器。
unordered_map 容器,直译过来就是&34;无序 map 容器&34;的意思。所谓“无序”,指的是 unordered_map 容器不会像 map 容器那样对存储的数据进行排序。换句话说,unordered_map 容器和 map 容器仅有一点不同,即 map 容器中存储的数据是有序的,而 unordered_map 容器中是无序的。
对于已经学过 map 容器的读者,可以将 unordered_map 容器等价为无序的 map 容器。
具体来讲,unordered_map 容器和 map 容器一样,以键值对(pair类型)的形式存储数据,存储的各个键值对的键互不相同且不允许被修改。但由于 unordered_map 容器底层采用的是哈希表存储结构,该结构本身不具有对数据的排序功能,所以此容器内部不会自行对存储的键值对进行排序。
值得一提的是,unordered_map 容器在
include using namespace std;
注意,第二行代码不是必需的,但如果不用,则后续程序中在使用此容器时,需手动注明 std 命名空间(强烈建议初学者使用)。
unordered_map 容器模板的定义如下所示:
template < class Key, //键值对中键的类型
class T, //键值对中值的类型
class Hash = hash, //容器内部存储键值对所用的哈希函数
class Pred = equal_to, //判断各个键值对键相同的规则
class Alloc = allocator< pairnst Key,T> > // 指定分配器对象的类型
> class unordered_map;
以上 5 个参数中,必须显式给前 2 个参数传值,并且除特殊情况外,最多只需要使用前 4 个参数,各自的含义和功能如表 1 所示。
表 1 unordered_map 容器模板类的常用参数
总的来说,当无序容器中存储键值对的键为自定义类型时,默认的哈希函数 hash 以及比较函数 equal_to 将不再适用,只能自己设计适用该类型的哈希函数和比较函数,并显式传递给 Hash 参数和 Pred 参数。至于如何实现自定义,后续章节会做详细讲解。
常见的创建 unordered_map 容器的方法有以下几种。
1) 通过调用 unordered_map 模板类的默认构造函数,可以创建空的 unordered_map 容器。比如:
std::unordered_map umap;
由此,就创建好了一个可存储
2) 当然,在创建 unordered_map 容器的同时,可以完成初始化操作。比如:
std::unordered_map umap{
{&34;Python教程&34;,&34;http://c.biancheng.net/python/&34;},
{&34;Java教程&34;,&34;http://c.biancheng.net/java/&34;},
{&34;Linux教程&34;,&34;http://c.biancheng.net/linux/&34;} };
通过此方法创建的 umap 容器中,就包含有 3 个键值对元素。
3) 另外,还可以调用 unordered_map 模板中提供的复制(拷贝)构造函数,将现有 unordered_map 容器中存储的键值对,复制给新建 unordered_map 容器。
例如,在第二种方式创建好 umap 容器的基础上,再创建并初始化一个 umap2 容器:
std::unordered_map umap2(umap);
由此,umap2 容器中就包含有 umap 容器中所有的键值对。
除此之外,C++ 11 标准中还向 unordered_map 模板类增加了移动构造函数,即以右值引用的方式将临时 unordered_map 容器中存储的所有键值对,全部复制给新建容器。例如:
//返回临时 unordered_map 容器的函数
std::unordered_map retUmap(){
std::unordered_maptempUmap{
{&34;Python教程&34;,&34;http://c.biancheng.net/python/&34;},
{&34;Java教程&34;,&34;http://c.biancheng.net/java/&34;},
{&34;Linux教程&34;,&34;http://c.biancheng.net/linux/&34;} };
return tempUmap;}
//调用移动构造函数,创建 umap2 容器
std::unordered_map umap2(retUmap());
注意,无论是调用复制构造函数还是拷贝构造函数,必须保证 2 个容器的类型完全相同。
4) 当然,如果不想全部拷贝,可以使用 unordered_map 类模板提供的迭代器,在现有 unordered_map 容器中选择部分区域内的键值对,为新建 unordered_map 容器初始化。例如:
//传入 2 个迭代器,
std::unordered_map
umap2(++umap.begin(),umap.end());
通过此方式创建的 umap2 容器,其内部就包含 umap 容器中除第 1 个键值对外的所有其它键值对。
unordered_map 既可以看做是关联式容器,更属于自成一脉的无序容器。因此在该容器模板类中,既包含一些在学习关联式容器时常见的成员方法,还有一些属于无序容器特有的成员方法。
表 2 列出了 unordered_map 类模板提供的所有常用的成员方法以及各自的功能。
注意,对于实现互换 2 个相同类型 unordered_map 容器的键值对,除了可以调用该容器模板类中提供的 swap() 成员方法外,STL 标准库还提供了同名的 swap() 非成员函数。
下面的样例演示了表 2 中部分成员方法的用法:
include
include
include
using namespace std;
int main()
{
//创建空 umap 容器
unordered_map umap;
//向 umap 容器添加新键值对
umap.emplace(&34;Python教程&34;, &34;http://c.biancheng.net/python/&34;);
umap.emplace(&34;Java教程&34;, &34;http://c.biancheng.net/java/&34;);
umap.emplace(&34;Linux教程&34;, &34;http://c.biancheng.net/linux/&34;);
//输出 umap 存储键值对的数量
cout << &34;umap size = &34; << umap.size() << endl;
//使用迭代器输出 umap 容器存储的所有键值对
for (auto iter = umap.begin(); iter != umap.end(); ++iter) {
cout << iter->first << &34; &34; << iter->second << endl;
}
return 0;
}
程序执行结果为:
umap size = 3
Python教程 http://c.biancheng.net/python/
Linux教程 http://c.biancheng.net/linux/
Java教程 http://c.biancheng.net/java/
在了解哈希表存储结构的基础上,本节将具体分析 C++ STL 无序容器(哈希容器)底层的实现原理。
C++ STL 标准库中,不仅是 unordered_map 容器,所有无序容器的底层实现都采用的是哈希表存储结构。更准确地说,是用“链地址法”(又称“开链法”)解决数据存储位置发生冲突的哈希表,整个存储结构如图 1 所示。
其中,Pi 表示存储的各个键值对。
可以看到,当使用无序容器存储键值对时,会先申请一整块连续的存储空间,但此空间并不用来直接存储键值对,而是存储各个链表的头指针,各键值对真正的存储位置是各个链表的节点。
注意,STL 标准库通常选用 vector 容器存储各个链表的头指针。
不仅如此,在 C++ STL 标准库中,将图 1 中的各个链表称为桶(bucket),每个桶都有自己的编号(从 0 开始)。当有新键值对存储到无序容器中时,整个存储过程分为如下几步:
另外值得一提的是,哈希表存储结构还有一个重要的属性,称为负载因子(load factor)。该属性同样适用于无序容器,用于衡量容器存储键值对的空/满程序,即负载因子越大,意味着容器越满,即各链表中挂载着越多的键值对,这无疑会降低容器查找目标键值对的效率;反之,负载因子越小,容器肯定越空,但并不一定各个链表中挂载的键值对就越少。
举个例子,如果设计的哈希函数不合理,使得各个键值对的键带入该函数得到的哈希值始终相同(所有键值对始终存储在同一链表上)。这种情况下,即便增加桶数是的负载因子减小,该容器的查找效率依旧很差。
无序容器中,负载因子的计算方法为:
负载因子 = 容器存储的总键值对 / 桶数
默认情况下,无序容器的最大负载因子为 1.0。如果操作无序容器过程中,使得最大复杂因子超过了默认值,则容器会自动增加桶数,并重新进行哈希,以此来减小负载因子的值。需要注意的是,此过程会导致容器迭代器失效,但指向单个键值对的引用或者指针仍然有效。
这也就解释了,为什么我们在操作无序容器过程中,键值对的存储顺序有时会“莫名”的发生变动。
C++ STL 标准库为了方便用户更好地管控无序容器底层使用的哈希表存储结构,各个无序容器的模板类中都提供表 2 所示的成员方法。
表 2 无序容器管理哈希表的成员方法
下面的程序以学过的 unordered_map 容器为例,演示了表 2 中部分成员方法的用法:
include
include
include
using namespace std;
int main()
{
//创建空 umap 容器
unordered_map umap;
cout << &34;umap 初始桶数: &34; << umap.bucket_count() << endl;
cout << &34;umap 初始负载因子: &34; << umap.load_factor() << endl;
cout << &34;umap 最大负载因子: &34; << umap.max_load_factor() << endl;
//设置 umap 使用最适合存储 9 个键值对的桶数
umap.reserve(9);
cout << &34;*********************&34; << endl;
cout << &34;umap 新桶数: &34; << umap.bucket_count() << endl;
cout << &34;umap 新负载因子: &34; << umap.load_factor() << endl;
//向 umap 容器添加 3 个键值对
umap[&34;Python教程&34;] = &34;http://c.biancheng.net/python/&34;;
umap[&34;Java教程&34;] = &34;http://c.biancheng.net/java/&34;;
umap[&34;Linux教程&34;] = &34;http://c.biancheng.net/linux/&34;;
//调用 bucket() 获取指定键值对位于桶的编号
cout << &34;以&34;Python教程&34;为键的键值对,位于桶的编号为:&34; << umap.bucket(&34;Python教程&34;) << endl;
//自行计算某键值对位于哪个桶
auto fn = umap.hash_function();
cout << &34;计算以&34;Python教程&34;为键的键值对,位于桶的编号为:&34; << fn(&34;Python教程&34;) % (umap.bucket_count()) << endl;
return 0;
}
程序执行结果为:
umap 初始桶数: 8
umap 初始负载因子: 0
umap 最大负载因子: 1
*********************
umap 新桶数: 16
umap 新负载因子: 0
以&34;Python教程&34;为键的键值对,位于桶的编号为:9
计算以&34;Python教程&34;为键的键值对,位于桶的编号为:9
从输出结果可以看出,对于空的 umap 容器,初始状态下会分配 8 个桶,并且默认最大负载因子为 1.0,但由于其为存储任何键值对,因此负载因子值为 0。
与此同时,程序中调用 reverse() 成员方法,是 umap 容器的桶数改为了 16,其最适合存储 9 个键值对。从侧面可以看出,一旦负载因子大于 1.0(9/8 > 1.0),则容器所使用的桶数就会翻倍式(8、16、32、...)的增加。
程序最后还演示了如何手动计算出指定键值对存储的桶的编号,其计算结果和使用 bucket() 成员方法得到的结果是一致的。
C++ STL 标准库中,unordered_map 容器迭代器的类型为前向迭代器(又称正向迭代器)。这意味着,假设 p 是一个前向迭代器,则其只能进行 *p、p++、++p 操作,且 2 个前向迭代器之间只能用 == 和 != 运算符做比较。
在 unordered_map 容器模板中,提供了表 1 所示的成员方法,可用来获取指向指定位置的前向迭代器。
表 1 C++ unordered_map迭代器相关成员方法
值得一提的是,equal_range(key) 很少用于 unordered_map 容器,因为该容器中存储的都是键不相等的键值对,即便调用该成员方法,得到的 2 个迭代器所表示的范围中,最多只包含 1 个键值对。事实上,该成员方法更适用于 unordered_multimap 容器(该容器后续章节会做详细讲解)。
下面的程序演示了表 1 中部分成员方法的用法。
include
include
include
using namespace std;
int main()
{
//创建 umap 容器
unordered_map umap{
{&34;Python教程&34;,&34;http://c.biancheng.net/python/&34;},
{&34;Java教程&34;,&34;http://c.biancheng.net/java/&34;},
{&34;Linux教程&34;,&34;http://c.biancheng.net/linux/&34;} };
cout << &34;umap 存储的键值对包括:&34; << endl;
//遍历输出 umap 容器中所有的键值对
for (auto iter = umap.begin(); iter != umap.end(); ++iter) {
cout << &34;<&34; << iter->first << &34;, &34; << iter->second << &34;>&34; << endl;
}
//获取指向指定键值对的前向迭代器
unordered_map::iterator iter = umap.find(&34;Java教程&34;);
cout <<&34;umap.find(&34;Java教程&34;) = &34; << &34;<&34; << iter->first << &34;, &34; << iter->second << &34;>&34; << endl;
return 0;
}
程序执行结果为:
umap 存储的键值对包括:
umap.find(&34;Java教程&34;) =
需要注意的是,在操作 unordered_map 容器过程(尤其是向容器中添加新键值对)中,一旦当前容器的负载因子超过最大负载因子(默认值为 1.0),该容器就会适当增加桶的数量(通常是翻一倍),并自动执行 rehash() 成员方法,重新调整各个键值对的存储位置(此过程又称“重哈希”),此过程很可能导致之前创建的迭代器失效。
所谓迭代器失效,针对的是那些用于表示容器内某个范围的迭代器,由于重哈希会重新调整每个键值对的存储位置,所以容器重哈希之后,之前表示特定范围的迭代器很可能无法再正确表示该范围。但是,重哈希并不会影响那些指向单个键值对元素的迭代器。
举个例子:
include
include
using namespace std;
int main()
{
//创建 umap 容器
unordered_map 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 <<&34; &34;;
}
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 << &34; &34;;
}
return 0;
}
程序执行结果为:
49
49 17
观察输出结果不难发现,之前用于表示键为 49 的键值对所在范围的 2 个迭代器,重哈希之后表示的范围发生了改变。
经测试,用于遍历整个容器的 begin()/end() 和 cbegin()/cend() 迭代器对,重哈希只会影响遍历容器内键值对的顺序,整个遍历的操作仍然可以顺利完成。
通过前面的学习我们知道,unordered_map 容器以键值对的方式存储数据。为了方便用户快速地从该类型容器提取出目标元素(也就是某个键值对的值),unordered_map 容器类模板中提供了以下几种方法。
1) unordered_map 容器类模板中,实现了对 [ ] 运算符的重载,使得我们可以像“利用下标访问普通数组中元素”那样,通过目标键值对的键获取到该键对应的值。
举个例子:
include
include
include
using namespace std;
int main()
{
//创建 umap 容器
unordered_map umap{
{&34;Python教程&34;,&34;http://c.biancheng.net/python/&34;},
{&34;Java教程&34;,&34;http://c.biancheng.net/java/&34;},
{&34;Linux教程&34;,&34;http://c.biancheng.net/linux/&34;} };
//获取 &34;Java教程&34; 对应的值
string str = umap[&34;Java教程&34;];
cout << str << endl;
return 0;
}
程序输出结果为:
http://c.biancheng.net/java/
需要注意的是,如果当前容器中并没有存储以 [ ] 运算符内指定的元素作为键的键值对,则此时 [ ] 运算符的功能将转变为:向当前容器中添加以目标元素为键的键值对。举个例子:
include
include
include
using namespace std;
int main()
{
//创建空 umap 容器
unordered_map umap;
//[] 运算符在 = 右侧
string str = umap[&34;STL教程&34;];
//[] 运算符在 = 左侧
umap[&34;C教程&34;] = &34;http://c.biancheng.net/c/&34;;
for (auto iter = umap.begin(); iter != umap.end(); ++iter) {
cout << iter->first << &34; &34; << iter->second << endl;
}
return 0;
}
程序执行结果为:
C教程 http://c.biancheng.net/c/
STL教程
可以看到,当使用 [ ] 运算符向 unordered_map 容器中添加键值对时,分为 2 种情况:
2) unordered_map 类模板中,还提供有 at() 成员方法,和使用 [ ] 运算符一样,at() 成员方法也需要根据指定的键,才能从容器中找到该键对应的值;不同之处在于,如果在当前容器中查找失败,该方法不会向容器中添加新的键值对,而是直接抛出out_of_range异常。
举个例子:
include
include
include
using namespace std;
int main(){
//创建 umap 容器
unordered_map umap{
{&34;Python教程&34;,&34;http://c.biancheng.net/python/&34;},
{&34;Java教程&34;,&34;http://c.biancheng.net/java/&34;},
{&34;Linux教程&34;,&34;http://c.biancheng.net/linux/&34;} };
//获取指定键对应的值
string str = umap.at(&34;Python教程&34;);
cout << str << endl;
//执行此语句会抛出 out_of_range 异常
//cout << umap.at(&34;GO教程&34;);
return 0;}
程序执行结果为:
http://c.biancheng.net/python/
此程序中,第 13 行代码用于获取 umap 容器中键为“Python教程”对应的值,由于 umap 容器确实有符合条件的键值对,因此可以成功执行;而第 17 行代码,由于当前 umap 容器没有存储以“Go教程”为键的键值对,因此执行此语句会抛出 out_of_range 异常。
3) [ ] 运算符和 at() 成员方法基本能满足大多数场景的需要。除此之外,还可以借助 unordered_map 模板中提供的 find() 成员方法。
和前面方法不同的是,通过 find() 方法得到的是一个正向迭代器,该迭代器的指向分以下 2 种情况:
举个例子:
include
include
include
using namespace std;
int main(){
//创建 umap 容器
unordered_map umap{
{&34;Python教程&34;,&34;http://c.biancheng.net/python/&34;},
{&34;Java教程&34;,&34;http://c.biancheng.net/java/&34;},
{&34;Linux教程&34;,&34;http://c.biancheng.net/linux/&34;} };
//查找成功
unordered_map::iterator iter = umap.find(&34;Python教程&34;);
cout << iter->first << &34; &34; << iter->second << endl;
//查找失败
unordered_map::iterator iter2 = umap.find(&34;GO教程&34;);
if (iter2 == umap.end()) {
cout << &34;当前容器中没有以&34;GO教程&34;为键的键值对&34;;
}
return 0;}
程序执行结果为:
Python教程 http://c.biancheng.net/python/
当前容器中没有以&34;GO教程&34;为键的键值对
4) 除了 find() 成员方法之外,甚至可以借助 begin()/end() 或者 cbegin()/cend(),通过遍历整个容器中的键值对来找到目标键值对。
举个例子:
include
include
include
using namespace std;
int main()
{
//创建 umap 容器
unordered_map umap{
{&34;Python教程&34;,&34;http://c.biancheng.net/python/&34;},
{&34;Java教程&34;,&34;http://c.biancheng.net/java/&34;},
{&34;Linux教程&34;,&34;http://c.biancheng.net/linux/&34;} };
//遍历整个容器中存储的键值对
for (auto iter = umap.begin(); iter != umap.end(); ++iter) {
//判断当前的键值对是否就是要找的
if (!iter->first.compare(&34;Java教程&34;)) {
cout << iter->second << endl;
break;
}
}
return 0;
}
程序执行结果为:
http://c.biancheng.net/java/
以上 4 种方法中,前 2 种方法基本能满足多数场景的需要,建议初学者首选 at() 成员方法!
为了方便用户向已建 unordered_map 容器中添加新的键值对,该容器模板中提供了 insert() 方法,本节就对此方法的用法做详细的讲解。
unordered_map 模板类中,提供了多种语法格式的 insert() 方法,根据功能的不同,可划分为以下几种用法。
1) insert() 方法可以将 pair 类型的键值对元素添加到 unordered_map 容器中,其语法格式有 2 种:
//以普通方式传递参数
pair
//以右值引用的方式传递参数
template
pair
为了方便用户向已建 unordered_map 容器中添加新的键值对,该容器模板中提供了 insert() 方法,本节就对此方法的用法做详细的讲解。
unordered_map 模板类中,提供了多种语法格式的 insert() 方法,根据功能的不同,可划分为以下几种用法。
1) insert() 方法可以将 pair 类型的键值对元素添加到 unordered_map 容器中,其语法格式有 2 种:
//以普通方式传递参数
pair
//以右值引用的方式传递参数
template
pair
以上 2 种格式中,参数 val 表示要添加到容器中的目标键值对元素;该方法的返回值为 pair类型值,内部包含一个 iterator 迭代器和 bool 变量:
include
include
include
using namespace std;
int main()
{
//创建空 umap 容器
unordered_map umap;
//构建要添加的键值对
std::pairmypair(&34;STL教程&34;, &34;http://c.biancheng.net/stl/&34;);
//创建接收 insert() 方法返回值的pair类型变量
std::pair::iterator, bool> ret;
//调用 insert() 方法的第一种语法格式
ret = umap.insert(mypair);
cout << &34;bool = &34; << ret.second << endl;
cout << &34;iter -> &34; << ret.first->first <<&34; &34; << ret.first->second << endl;
//调用 insert() 方法的第二种语法格式
ret = umap.insert(std::make_pair(&34;Python教程&34;,&34;http://c.biancheng.net/python/&34;));
cout << &34;bool = &34; << ret.second << endl;
cout << &34;iter -> &34; << ret.first->first << &34; &34; << 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,表示添加成功,此时返回的迭代器指向的是添加成功的键值对。
2) 除此之外,insert() 方法还可以指定新键值对要添加到容器中的位置,其语法格式如下:
//以普通方式传递 val 参数
iterator insert ( const_iterator hint, const value_type& val );
//以右值引用方法传递 val 参数
template
iterator insert ( const_iterator hint, P&& val );
以上 2 种语法格式中,hint 参数为迭代器,用于指定新键值对要添加到容器中的位置;val 参数指的是要添加容器中的键值对;方法的返回值为迭代器:
注意,以上 2 种语法格式中,虽然通过 hint 参数指定了新键值对添加到容器中的位置,但该键值对真正存储的位置,并不是 hint 参数说了算,最终的存储位置仍取决于该键值对的键的值。
include
include
include
using namespace std;
int main()
{
//创建空 umap 容器
unordered_map umap;
//构建要添加的键值对
std::pairmypair(&34;STL教程&34;, &34;http://c.biancheng.net/stl/&34;);
//创建接收 insert() 方法返回值的迭代器类型变量
unordered_map::iterator iter;
//调用第一种语法格式
iter = umap.insert(umap.begin(), mypair);
cout << &34;iter -> &34; << iter->first <<&34; &34; << iter->second << endl;
//调用第二种语法格式
iter = umap.insert(umap.begin(),std::make_pair(&34;Python教程&34;, &34;http://c.biancheng.net/python/&34;));
cout << &34;iter -> &34; << iter->first << &34; &34; << iter->second << endl;
return 0;
}
程序输出结果为:
iter -> STL教程 http://c.biancheng.net/stl/
iter -> Python教程 http://c.biancheng.net/python/
3) insert() 方法还支持将某一个 unordered_map 容器中指定区域内的所有键值对,复制到另一个 unordered_map 容器中,其语法格式如下:
template
void insert ( InputIterator first, InputIterator last );
其中 first 和 last 都为迭代器,[first, last)表示复制其它 unordered_map 容器中键值对的区域。
include
include
include
using namespace std;
int main()
{
//创建并初始化 umap 容器
unordered_map umap{ {&34;STL教程&34;,&34;http://c.biancheng.net/stl/&34;},
{&34;Python教程&34;,&34;http://c.biancheng.net/python/&34;},
{&34;Java教程&34;,&34;http://c.biancheng.net/java/&34;} };
//创建一个空的 unordered_map 容器
unordered_map otherumap;
//指定要拷贝 umap 容器中键值对的范围
unordered_map::iterator first = ++umap.begin();
unordered_map::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 << &34; &34; << iter->second << endl;
}
return 0;
}
程序输出结果为:
Python教程 http://c.biancheng.net/python/
Java教程 http://c.biancheng.net/java/
4) 除了以上 3 种方式,insert() 方法还支持一次向 unordered_map 容器添加多个键值对,其语法格式如下:
void insert ( initializer_list
其中,il 参数指的是可以用初始化列表的形式指定多个键值对元素。
include
include
include
using namespace std;
int main()
{
//创建空的 umap 容器
unordered_map umap;
//向 umap 容器同时添加多个键值对
umap.insert({ {&34;STL教程&34;,&34;http://c.biancheng.net/stl/&34;},
{&34;Python教程&34;,&34;http://c.biancheng.net/python/&34;},
{&34;Java教程&34;,&34;http://c.biancheng.net/java/&34;} });
//遍历输出 umap 容器中存储的键值对
for (auto iter = umap.begin(); iter != umap.end(); ++iter){
cout << iter->first << &34; &34; << iter->second << endl;
}
return 0;
}
程序输出结果为:
STL教程 http://c.biancheng.net/stl/
Python教程 http://c.biancheng.net/python/
Java教程 http://c.biancheng.net/java/
和前面学的 map、set 等容器一样,C++ 11 标准也为 unordered_map 容器新增了 emplace() 和 emplace_hint() 成员方法,本节将对它们的用法做详细的介绍。
我们知道,实现向已有 unordered_map 容器中添加新键值对,可以通过调用 insert() 方法,但其实还有更好的方法,即使用 emplace() 或者 emplace_hint() 方法,它们完成“向容器中添加新键值对”的效率,要比 insert() 方法高。
emplace() 方法的用法很简单,其语法格式如下:
template
pair
其中,参数 args 表示可直接向该方法传递创建新键值对所需要的 2 个元素的值,其中第一个元素将作为键值对的键,另一个作为键值对的值。也就是说,该方法无需我们手动创建键值对,其内部会自行完成此工作。
另外需要注意的是,该方法的返回值为 pair 类型值,其包含一个迭代器和一个 bool 类型值:
include
include
include
using namespace std;
int main()
{
//创建 umap 容器
unordered_map umap;
//定义一个接受 emplace() 方法的 pair 类型变量
pair::iterator, bool> ret;
//调用 emplace() 方法
ret = umap.emplace(&34;STL教程&34;, &34;http://c.biancheng.net/stl/&34;);
//输出 ret 中包含的 2 个元素的值
cout << &34;bool =&34; << ret.second << endl;
cout << &34;iter ->&34; << ret.first->first << &34; &34; << ret.first->second << endl;
return 0;
}
程序执行结果为:
bool =1
iter ->STL教程 http://c.biancheng.net/stl/
通过执行结果中 bool 变量的值为 1 可以得知,emplace() 方法成功将新键值对添加到了 umap 容器中。
emplace_hint() 方法的语法格式如下:
template
iterator emplace_hint ( const_iterator position, Args&&... args );
和 emplace() 方法相同,emplace_hint() 方法内部会自行构造新键值对,因此我们只需向其传递构建该键值对所需的 2 个元素(第一个作为键,另一个作为值)即可。不同之处在于:
可以这样理解,emplace_hint() 方法中传入的迭代器,仅是给 unordered_map 容器提供一个建议,并不一定会被容器采纳。
举个例子:
include
include
include
using namespace std;
int main(){
//创建 umap 容器
unordered_map umap;
//定义一个接受 emplace_hint() 方法的迭代器
unordered_map::iterator iter;
//调用 empalce_hint() 方法
iter = umap.emplace_hint(umap.begin(),&34;STL教程&34;, &34;http://c.biancheng.net/stl/&34;);
//输出 emplace_hint() 返回迭代器 iter 指向的键值对的内容
cout << &34;iter ->&34; << iter->first << &34; &34; << iter->second << endl;
return 0;
}
程序执行结果为:
iter ->STL教程 http://c.biancheng.net/stl/
高悦明