C++ STL: Unordered associative containers

1. Unordered associative containers

1.1 Overview

Following the map, multimap, set, multiset associative containers, this section introduces a class of "special" associative containers, which are often called "unordered containers", "hash containers" or "unordered associative containers".

Note that the unordered container was officially introduced into the STL standard library only after the C++11 standard, which means that if you want to use this type of container, you must choose a compiler that supports the C++11 standard.

Like associative containers, unordered containers also use key-value pairs (pair type) to store data. However, they are fundamentally different:

  • The tree storage structure adopted by the underlying implementation of the associative container, more precisely, the red-black tree structure;
  • The underlying implementation of the unordered container uses the storage structure of the hash table.

When the bottom layer of C++ STL uses a hash table to implement unordered containers, all data will be stored in a whole continuous memory space, and when the data storage location conflicts, the solution is to use the "chain address method" (also known as "Open Chain Method").

Different data structures are adopted based on the underlying implementation. Therefore, compared with associative containers, unordered containers have the following two characteristics:

  1. The key-value pairs stored in the unordered container are unordered, and the storage location of each key-value pair depends on the key in the key-value pair.
  2. Compared with associative containers, unordered containers are good at finding the corresponding value by specifying a key (the average time complexity is O(1)); but for using iterators to traverse the elements stored in the container, the execution efficiency of unordered containers is not as good as that of associative container.

1.2 Types of unordered containers

Like associative containers, unordered containers are just a general term for a class of containers, which include four specific containers, namely unordered_map, unordered_multimap, unordered_set, and unordered_multiset.

The following table gives a detailed introduction to the functions of these four unordered containers:

unordered container Function
unordered_map Store elements of the key-value pair <key, value> type, where the values ​​of each key-value pair key are not allowed to be repeated, and the key-value pairs stored in the container are unordered.
unordered_multimap The only difference from unordered_map is that this container allows storing multiple key-value pairs with the same key.
unordered_set Data is no longer stored in the form of key-value pairs, but directly stores the data elements themselves (of course, it can also be understood that all the containers store are key-value pairs with the same key and value. Because they are equal, only Just store the value). In addition, the elements stored in the container cannot be repeated, and the elements stored in the container are also out of order.
unordered_multiset The only difference from unordered_set is that this container allows elements with the same value to be stored.

The names of the above 4 kinds of unordered containers are only based on the names of the 4 kinds of associated containers learned earlier, adding "unordered_". In fact, there is only one difference between them, that is, the memory of the map container will store the key-value pairs sorted, while unordered_map does not.

That is to say, in the STL of the C++11 standard, on the basis of the four types of associative containers already provided, they have added their own "unordered" versions (unordered version, hash version), which improves the speed of searching for specified elements. efficiency. Since the unordered container is similar to the previously learned associative container, which container should we choose in actual use? In general, if a large number of operations involving traversing containers are involved in actual scenarios, it is recommended to prefer associative containers; conversely, if more operations are to obtain corresponding values ​​through keys, unordered containers should be preferred.

Here we take the unordered_map container as an example (it is not necessary to delve into the specific usage of the container):

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

The program execution result is:

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 Overview

The unordered_map container literally translates to "unordered map container". The so-called "unordered" means that the unordered_map container does not sort the stored data like the map container. In other words, the unordered_map container is only different from the map container, that is, the data stored in the map container is ordered, while the unordered_map container is unordered.

The unordered_map container can be equivalent to an unordered map container.

Specifically, the unordered_map container, like the map container, stores data in the form of key-value pairs (pair type), and the keys of each stored key-value pair are different from each other and cannot be modified. However, since the bottom layer of the unordered_map container uses a hash table storage structure, the structure itself does not have the function of sorting data, so the key-value pairs stored in this container will not be sorted by itself.

The unordered_map container is <unordered_map>in the header file and is in the std namespace. Therefore, if you want to use this container, your code should include the following statement:

#include <unordered_map>
using namespace std;

Note that the second line of code is not necessary, but if not, you need to manually indicate the std namespace when using this container in subsequent programs (recommended for beginners).

The unordered_map container template is defined as follows:

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

Among the above 5 parameters, you must explicitly pass values ​​to the first 2 parameters, and except for special cases, only the first 4 parameters need to be used at most, and the respective meanings and functions are shown in the following table:

parameter meaning
<key,T> The first two parameters are used to determine the type of the key and value in the key-value pair, that is, the type of storing the key-value pair.
Hash = hash<Key> It is used to indicate the hash function to be used by the container when storing each key-value pair. By default, the hash<key> hash function provided by the STL standard library is used. Note that the default hash function is only applicable to basic data types (including string types), not to custom structures or classes.
Pred = equal_to<Key> You should know that the keys of each key-value pair stored in the unordered_map container cannot be equal, and the rules for judging whether they are equal are specified by this parameter. By default, the equal_to<key> rule provided in the STL standard library is used, which only supports data types that can be compared directly with the == operator.

In general, when the key that stores the key-value pair in the unordered container is a custom type, the default hash function hash and the comparison function equal_to will no longer be applicable, and you can only design a hash function and comparison suitable for this type function, and explicitly passed to the Hash parameter and the Pred parameter.

2.2 Member methods

unordered_map can be regarded as not only an associative container, but also a self-contained unordered container. Therefore, the container template class contains not only some common member methods when learning associative containers, but also some member methods unique to unordered containers. The following table lists all common member methods provided by the unordered_map class template and their respective functions:

member method Function
begin() Returns a forward iterator pointing to the first key-value pair in the container.
end() Returns a forward iterator pointing to the position past the last key-value pair in the container.
cbegin() It has the same function as begin(), except that the const attribute is added on top of it, that is, the iterator returned by this method cannot be used to modify the key-value pairs stored in the container.
a few() It has the same function as end(), except that the const attribute is added on top of it, that is, the iterator returned by this method cannot be used to modify the key-value pairs stored in the container.
empty() Returns true if the container is empty; otherwise, false.
size() Returns the number of key-value pairs stored in the current container.
max_size() Returns the maximum number of key-value pairs that the container can hold. Different operating systems have different return values.
operator[key] The [] operator is overloaded in the template class, and its function is to access the elements in the array, as long as the key of a key-value pair is given, the value corresponding to the key can be obtained. Note that if there is no key-value pair with key as the key in the current container, it will use this key to insert a new key-value pair into the current container.
at(key) Returns the value corresponding to the key key stored in the container. If the key does not exist, an out_of_range exception will be thrown.
find(key) Find the key-value pair with key as the key, if found, return a forward iterator pointing to the key-value pair; otherwise, return an iterator pointing to the position after the last key-value pair in the container (if end() method returns an iterator).
count(key) Find the number of key-value pairs with key in the container.
equal_range(key) Returns a pair object, which contains 2 iterators, which are used to indicate the range of key-value pairs whose key is key in the current container.
place() Add new key-value pairs to the container, which is more efficient than the insert() method.
emplace_hint() Add new key-value pairs to the container, which is more efficient than the insert() method.
insert() Adds a new key-value pair to the container.
erase() Delete the specified key-value pair.
clear() Empty the container, that is, delete all key-value pairs stored in the container.
swap() To exchange the key-value pairs stored in two unordered_map containers, the premise is that the types of the two containers must be completely equal.
bucket_count() Returns the number of buckets (a linear linked list represents a bucket) used to store key-value pairs at the bottom of the current container.
max_bucket_count() Returns the maximum number of buckets that can be used at the bottom of the unordered_map container in the current system.
bucket_size(n) Returns the number of key-value pairs stored in the nth bucket.
bucket(key) Returns the number of the bucket where the key-value pair with key is the 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 个元素外的所有其它元素。

Guess you like

Origin blog.csdn.net/crossoverpptx/article/details/131749583