C++STL: map and multimap of associative containers

1. map

overview

As a kind of associative container, the map container stores pair objects, that is, key-value pairs created with the pair class template. Among them, the key and value of each key-value pair can be any data type, including C++ basic data types (int, double, etc.), structures or class-defined types.

Usually, each key-value pair stored in the map container uses string string as the key type.

At the same time, when using the map container to store multiple key-value pairs, the container will automatically sort according to the established rules according to the size of the key of each key-value pair. By default, the map container uses std::less<T>a collation (where T represents the data type of the key), which sorts all key-value pairs in ascending order according to the size of the key. Of course, according to the needs of the actual situation, we can manually specify the sorting rules of the map container. We can choose other sorting rules (for example) provided in the STL standard library, or customize the sorting std::greater<T>rules.

In addition, it should be noted that for each key-value pair stored in the map container, the value of the key can neither be repeated nor modified. In other words, each key-value pair stored in the map container is not only unique, but also the type of the key is modified with const, which means that as long as the key-value pair is stored in the map container, the value of the key will no longer be any modification.

As mentioned earlier, the map container stores the key-value pair elements of the pair type, more precisely, the container stores the pair<const K, T> type (where K and T represent the data types of the key and value respectively ) key-value pair elements.

The map container is defined in the <map> header file and resides in the std namespace. Therefore, if you want to use the map container, the code should contain the following statement:

#include <map>
using namespace std;

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

The template definition for the map container is as follows:

template < class Key,                                     // 指定键(key)的类型
           class T,                                       // 指定值(value)的类型
           class Compare = less<Key>,                     // 指定排序规则
           class Alloc = allocator<pair<const Key,T> >    // 指定分配器对象的类型
           > class map;

It can be seen that the map container template has 4 parameters, and the last 2 parameters have default values. In most scenarios, we only need to set the value of the first 2 parameters, some scenarios may use the third parameter, but the last parameter is almost never used.

member function

The following table lists the common member methods provided by the map container and their respective functions:

member function Function
begin() Returns a bidirectional iterator pointing to the first (note, the sorted first) key-value pair in the container. If the map container is const-qualified, this method returns a bidirectional iterator of const type.
end() Returns a bidirectional iterator pointing to the position after the last element of the container (note that it is the last one that has been sorted), usually used in conjunction with begin(). If the map container is const-qualified, this method returns a bidirectional iterator of const type.
rbegin() Returns a reverse bidirectional iterator pointing to the last (note, the last sorted) element. If the map container is const-qualified, this method returns a reverse bidirectional iterator of const type.
rend() Returns a reverse bidirectional iterator pointing to the position preceding the position of the first (note, the first sorted) element. If the map container is const-qualified, this method returns a reverse bidirectional iterator of const type.
cbegin() It has the same function as begin(), but on the basis of it, a const attribute is added, which cannot be used to modify the key-value pairs stored in the container.
a few() It has the same function as end(), except that a const attribute is added on top of it, which cannot be used to modify the key-value pairs stored in the container.
crbegin() It has the same function as rbegin(), but on the basis of it, a const attribute is added, which cannot be used to modify the key-value pairs stored in the container.
crend() It has the same function as rend(), except that on its basis, a const attribute is added, which cannot be used to modify the key-value pairs stored in the container.
find(key) Look up the key-value pair whose key is key in the map container. If found successfully, return a bidirectional iterator pointing to the key-value pair; otherwise, return the same iterator as the end() method. In addition, if the map container is const-qualified, this method returns a bidirectional iterator of const type.
lower_bound(key) Returns a bidirectional iterator pointing to the first key-value pair greater than or equal to key in the current map container. If the map container is const-qualified, this method returns a bidirectional iterator of const type.
upper_bound(key) Returns an iterator pointing to the first key-value pair greater than key in the current map container. If the map container is const-qualified, this method returns a bidirectional iterator of const type.
equal_range(key) This method returns a pair object (contains two bidirectional iterators), where the return values ​​of pair.first and lower_bound() methods are equivalent, and the return values ​​of pair.second and upper_bound() methods are equivalent. That is to say, this method will return a range that contains the key-value pair whose key is key (the map container key-value pair is unique, so the range contains at most one key-value pair).
empty() Returns true if the container is empty; otherwise, false.
size() Returns the number of key-value pairs stored in the current map container.
max_size() Returns the maximum number of key-value pairs that the map container can hold. Different operating systems have different return values.
operator[] The map container overloads the [] operator. As long as you know the value of the key of a key-value pair in the map container, you can directly obtain the corresponding value through the key just like obtaining the elements in the array.
at(key) Find the value corresponding to the key key in the map container, if not found, the function will raise an out_of_range exception.
insert() Insert key-value pairs into the map container.
erase() Delete the specified location, specified key (key) value, or key-value pair in the specified area of ​​the map container. Subsequent chapters will focus on this method.
swap() Exchange the key-value pairs stored in the two map containers, which means that the types of the two key-value pairs for the operation must be the same.
clear() Clear all key-value pairs in the map container, even if the size() of the map container is 0.
place() Constructs a new key-value pair at the specified position in the current map container. Its effect is the same as inserting key-value pairs, but more efficient.
emplace_hint() It is essentially the same as how emplace() constructs a new key-value pair in a map container. The difference is that the user must provide this method with an iterator indicating the location where the key-value pair is generated, and use it as the method's first parameter.
count(key) In the current map container, find the number of key-value pairs whose key is key and return it. Note that since the key value of each key-value pair in the map container is unique, the maximum return value of this function is 1.

Several methods of creating C++ map container

The template class of the map container contains multiple constructors, so there are many ways to create a map container. The following introduces several commonly used methods for creating a map container.

  1. An empty map container can be created by calling the default constructor of the map container class, for example:
std::map<std::string, int> myMap;

The myMap container created in this way is initially empty, that is, no key-value pairs are stored. Since an empty map container can add new key-value pairs at any time as needed, it is more common to create an empty map container.

  1. Of course, while creating the map container, it can also be initialized, for example:
std::map<std::string, int> myMap{ {"C语言教程",10},{"STL教程",20} };

Thus, the myMap container contains 2 key-value pairs in its initial state.

Again, the key-value pairs stored in the map container are essentially pair objects created by the pair class template. Therefore, the following program can also create exactly the same myMap container:

std::map<std::string, int> myMap{std::make_pair("C语言教程",10),std::make_pair("STL教程",20)};
  1. In addition, in some scenarios, a previously created map container can be used to create a new map container. For example:
std::map<std::string, int> newMap(myMap);

Thus, by calling the copy (replication) constructor of the map container, a newMap container exactly the same as myMap can be successfully created.

In the C++11 standard, a move constructor is also added to the map container. The move constructor is called when a temporary map object is passed as a parameter to the map container to be initialized. for example:

// 创建一个会返回临时 map 对象的函数
std::map<std::string,int> disMap() {
    std::map<std::string, int>tempMap{ {"C语言教程",10},{"STL教程",20} };
    return tempMap;
}
// 调用 map 类模板的移动构造函数创建 newMap 容器
std::map<std::string, int> newMap(disMap());

Note that whether calling the copy constructor or calling the copy constructor, you must ensure that the types of the two containers are exactly the same.

  1. The map class template also supports taking the key-value pairs in the specified area of ​​the established map container, creating and initializing a new map container. For example:
std::map<std::string, int> myMap{ {"C语言教程",10},{"STL教程",20} };
std::map<std::string, int> newMap(++myMap.begin(), myMap.end());

Here, by calling the bidirectional iterator of the map container, it is realized that while creating the newMap container, it is initialized as a container containing a {"STL Tutorial",20} key-value pair.

  1. Of course, on the basis of the above creation of map containers, we can manually modify the sorting rules of the map container. By default, the map container invokes the std::less<T> rule to sort all key-value pairs in ascending order according to the key size of each key-value pair in the container. Therefore, the following two lines of creating a map container are actually equivalent:
std::map<std::string, int> myMap{ {"C语言教程",10},{"STL教程",20} };
std::map<std::string, int, std::less<std::string> > myMap{ {"C语言教程",10},{"STL教程",20} };

For the myMap container generated in the above 2 creation methods, the order of the internal key-value pairs is as follows:

<"C语言教程", 10>
<"STL教程", 20>

The following program manually modifies the sorting rules of the myMap container to sort in descending order:

std::map<std::string, int, std::greater<std::string> > myMap{ {"C语言教程",10},{"STL教程",20} };

At this point, the order of the key-value pairs inside the myMap container is:

<"STL教程", 20>
<"C语言教程", 10>

In some specific scenarios, we also need to customize the sorting rules for the map container.

iterator

Whether it is the sequential container learned earlier or the associative container, in order to implement the traversal operation, it is necessary to use the iterator of this type of container. Of course, map containers are no exception.

The C++ STL standard library equips the map container with a bidirectional iterator. This means that map container iterators can only be ++p、p++、--p、p--、*poperated on, and iterators can only be compared using ==the or operator.!=

It is worth mentioning that, compared with sequential containers, map containers provide more member methods (as shown in the table below), and by calling them, we can easily obtain iterators with specified meanings.

member method Function
begin() Returns a bidirectional iterator pointing to the first (note, the sorted first) key-value pair in the container. If the map container is const-qualified, this method returns a bidirectional iterator of const type.
end() Returns a bidirectional iterator pointing to the position after the last element of the container (note that it is the last one that has been sorted), usually used in conjunction with begin(). If the map container is const-qualified, this method returns a bidirectional iterator of const type.
rbegin() Returns a reverse bidirectional iterator pointing to the last (note, the last sorted) element. If the map container is const-qualified, this method returns a reverse bidirectional iterator of const type.
rend() Returns a reverse bidirectional iterator pointing to the position preceding the position of the first (note, the first sorted) element. If the map container is const-qualified, this method returns a reverse bidirectional iterator of const type.
cbegin() It has the same function as begin(), but on the basis of it, a const attribute is added, which cannot be used to modify the key-value pairs stored in the container.
a few() It has the same function as end(), except that a const attribute is added on top of it, which cannot be used to modify the key-value pairs stored in the container.
crbegin() It has the same function as rbegin(), but on the basis of it, a const attribute is added, which cannot be used to modify the key-value pairs stored in the container.
crend() It has the same function as rend(), except that on its basis, a const attribute is added, which cannot be used to modify the key-value pairs stored in the container.
find(key) Look up the key-value pair whose key is key in the map container. If found successfully, return a bidirectional iterator pointing to the key-value pair; otherwise, return the same iterator as the end() method. In addition, if the map container is const-qualified, this method returns a bidirectional iterator of const type.
lower_bound(key) Returns a bidirectional iterator pointing to the first key-value pair greater than or equal to key in the current map container. If the map container is const-qualified, this method returns a bidirectional iterator of const type.
upper_bound(key) Returns an iterator pointing to the first key-value pair greater than key in the current map container. If the map container is const-qualified, this method returns a bidirectional iterator of const type.
equal_range(key) 该方法返回一个 pair 对象(包含 2 个双向迭代器),其中 pair.first 和 lower_bound() 方法的返回值等价,pair.second 和 upper_bound() 方法的返回值等价。也就是说,该方法将返回一个范围,该范围中包含的键为 key 的键值对(map 容器键值对唯一,因此该范围最多包含一个键值对)。

上表中多数的成员方法,诸如 begin()、end() 等,在学习顺序容器时已经多次使用过,它们的功能如下图所示:

在这里插入图片描述

注意,图中 Ei 表示的是 pair 类对象,即键值对。对于 map 容器来说,每个键值对的键的值都必须保证是唯一的。

  1. 下面程序以 begin()/end() 组合为例,演示如何遍历 map 容器:
#include <iostream>
#include <map>      // pair
#include <string>       // string
using namespace std;

int main() {
    // 创建并初始化 map 容器
    std::map<std::string, std::string>myMap{ {"STL教程","http://c.biancheng.net/stl/"},{"C语言教程","http://c.biancheng.net/c/"} };
    // 调用 begin()/end() 组合,遍历 map 容器
    for (auto iter = myMap.begin(); iter != myMap.end(); ++iter) {
        cout << iter->first << " " << iter->second << endl;
    }
    return 0;
}

程序执行结果为:

C语言教程 http://c.biancheng.net/c/
STL教程 http://c.biancheng.net/stl/
  1. 除此之外,map 类模板中还提供了 find() 成员方法,它能帮我们查找指定 key 值的键值对,如果成功找到,则返回一个指向该键值对的双向迭代器;反之,其功能和 end() 方法相同。举个例子:
#include <iostream>
#include <map>      // pair
#include <string>       // string
using namespace std;

int main() {
    // 创建并初始化 map 容器
    std::map<std::string, std::string>myMap{ {"STL教程","http://c.biancheng.net/stl/"},
                                             {"C语言教程","http://c.biancheng.net/c/"},
                                             {"Java教程","http://c.biancheng.net/java/"} };
    // 查找键为 "Java教程" 的键值对
    auto iter = myMap.find("Java教程");
    // 从 iter 开始,遍历 map 容器
    for (; iter != myMap.end(); ++iter) {
        cout << iter->first << " " << iter->second << endl;
    }
    return 0;
}

程序执行结果为:

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

此程序中,创建并初始化的 myMap 容器,默认会根据各键值对中键的值,对各键值对做升序排序,其排序的结果为:

<"C语言教程","http://c.biancheng.net/c/">
<"Java教程","http://c.biancheng.net/java/">
<"STL教程","http://c.biancheng.net/stl/">

在此基础上,通过调用 find() 方法,我们可以得到一个指向键为 “Java教程” 的键值对的迭代器,由此当使用 for 循环从该迭代器出开始遍历时,就只会遍历到最后 2 个键值对。

  1. 同时,map 类模板中还提供有 lower_bound(key) 和 upper_bound(key) 成员方法,它们的功能是类似的,唯一的区别在于:
  • lower_bound(key) 返回的是指向第一个键不小于 key 的键值对的迭代器;
  • upper_bound(key) 返回的是指向第一个键大于 key 的键值对的迭代器;

下面程序演示了它们的功能:

#include <iostream>
#include <map>      // pair
#include <string>       // string
using namespace std;

int main() {
    // 创建并初始化 map 容器
    std::map<std::string, std::string>myMap{ {"STL教程","http://c.biancheng.net/stl/"},
                                             {"C语言教程","http://c.biancheng.net/c/"},
                                             {"Java教程","http://c.biancheng.net/java/"} };
    // 找到第一个键的值不小于 "Java教程" 的键值对
    auto iter = myMap.lower_bound("Java教程");
    cout << "lower:" << iter->first << " " << iter->second << endl;
   
    // 找到第一个键的值大于 "Java教程" 的键值对
    iter = myMap.upper_bound("Java教程");
    cout <<"upper:" << iter->first << " " << iter->second << endl;
    return 0;
}

程序执行结果为:

lower:Java教程 http://c.biancheng.net/java/
upper:STL教程 http://c.biancheng.net/stl/

lower_bound(key) 和 upper_bound(key) 更多用于 multimap 容器,在 map 容器中很少用到。

  1. equal_range(key) 成员方法可以看做是 lower_bound(key) 和 upper_bound(key) 的结合体,该方法会返回一个 pair 对象,其中的 2 个元素都是迭代器类型,其中 pair.first 实际上就是 lower_bound(key) 的返回值,而 pair.second 则等同于 upper_bound(key) 的返回值。

显然,equal_range(key) 成员方法表示的是一个范围,位于此范围中的键值对,其键的值都为 key。举个例子:

#include <iostream>
#include <utility>  // pair
#include <map>      // map
#include <string>       // string
using namespace std;

int main() {
    // 创建并初始化 map 容器
    std::map<string, string>myMap{ {"STL教程","http://c.biancheng.net/stl/"},
                                   {"C语言教程","http://c.biancheng.net/c/"},
                                   {"Java教程","http://c.biancheng.net/java/"} };
    // 创建一个 pair 对象,来接收 equal_range() 的返回值
    pair <std::map<string, string>::iterator, std::map<string, string>::iterator> myPair = myMap.equal_range("C语言教程");
    // 通过遍历,输出 myPair 指定范围内的键值对
    for (auto iter = myPair.first; iter != myPair.second; ++iter) {
        cout << iter->first << " " << iter->second << endl;
    }
    return 0;
}

程序执行结果为:

C语言教程 http://c.biancheng.net/c/

和 lower_bound(key)、upper_bound(key) 一样,该方法也更常用于 multimap 容器,因为 map 容器中各键值对的键的值都是唯一的,因此通过 map 容器调用此方法,其返回的范围内最多也只有 1 个键值对。

map获取键对应值的几种方法

我们知道,map 容器中存储的都是 pair 类型的键值对,但几乎在所有使用 map 容器的场景中,经常要做的不是找到指定的 pair 对象(键值对),而是从该容器中找到某个键对应的值。

注意,使用 map 容器存储的各个键值对,其键的值都是唯一的,因此指定键对应的值最多有 1 个。

庆幸的是,map 容器的类模板中提供了以下 2 种方法,可直接获取 map 容器指定键对应的值。

  1. map 类模板中对[]运算符进行了重载,这意味着,类似于借助数组下标可以直接访问数组中元素,通过指定的键,我们可以轻松获取 map 容器中该键对应的值。举个例子:
#include <iostream>
#include <map>      // map
#include <string>   // string
using namespace std;

int main() {
    // 创建并初始化 map 容器
    std::map<std::string, std::string>myMap{ {"STL教程","http://c.biancheng.net/stl/"},
                                             {"C语言教程","http://c.biancheng.net/c/"},
                                             {"Java教程","http://c.biancheng.net/java/"} };
    string cValue = myMap["C语言教程"];
    cout << cValue << endl;
    return 0;
}

程序执行结果为:

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

可以看到,在第 11 行代码中,通过指定键的值为 “C语言教程”,借助重载的 [ ] 运算符,就可以在 myMap 容器中直接找到该键对应的值。

注意,只有当 map 容器中确实存有包含该指定键的键值对,借助重载的 [ ] 运算符才能成功获取该键对应的值;反之,若当前 map 容器中没有包含该指定键的键值对,则此时使用 [ ] 运算符将不再是访问容器中的元素,而变成了向该 map 容器中增添一个键值对。其中,该键值对的键用 [ ] 运算符中指定的键,其对应的值取决于 map 容器规定键值对中值的数据类型,如果是基本数据类型,则值为 0;如果是 string 类型,其值为 “”,即空字符串(即使用该类型的默认值作为键值对的值)。举个例子:

#include <iostream>
#include <map>      // map
#include <string>   // string
using namespace std;

int main() {
    // 创建空 map 容器
    std::map<std::string, int>myMap;
    int cValue = myMap["C语言教程"];
    for (auto i = myMap.begin(); i != myMap.end(); ++i) {
        cout << i->first << " "<< i->second << endl;
    }
    return 0;
}

程序执行结果为:

C语言教程 0

显然,对于空的 myMap 容器来说,其内部没有以 “C语言教程” 为键的键值对,这种情况下如果使用 [ ] 运算符获取该键对应的值,其功能就转变成了向该 myMap 容器中添加一个<"C语言教程",0>键值对(由于 myMap 容器规定各个键值对的值的类型为 int,该类型的默认值为 0)。

实际上,[ ] 运算符确实有“为 map 容器添加新键值对”的功能,但前提是要保证新添加键值对的键和当前 map 容器中已存储的键值对的键都不一样。例如:

#include <iostream>
#include <map>      // map
#include <string>   // string
using namespace std;

int main() {
    // 创建空 map 容器
    std::map<string, string>myMap;
    myMap["STL教程"]="http://c.biancheng.net/java/";
    myMap["Python教程"] = "http://c.biancheng.net/python/";
    myMap["STL教程"] = "http://c.biancheng.net/stl/";
    for (auto i = myMap.begin(); i != myMap.end(); ++i) {
        cout << i->first << " " << i->second << endl;
    }
    return 0;
}

程序执行结果为:

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

注意,程序中第 9 行代码已经为 map 容器添加了一个以 “STL教程” 作为键的键值对,则第 11 行代码的作用就变成了修改该键对应的值,而不再是为 map 容器添加新键值对。

  1. 除了借助 [ ] 运算符获取 map 容器中指定键对应的值,还可以使用 at() 成员方法。和前一种方法相比,at() 成员方法也需要根据指定的键,才能从容器中找到该键对应的值;不同之处在于,如果在当前容器中查找失败,该方法不会向容器中添加新的键值对,而是直接抛出 out_of_range 异常。举个例子:
#include <iostream>
#include <map>      // map
#include <string>   // string
using namespace std;

int main() {
    // 创建并初始化 map 容器
    std::map<std::string, std::string>myMap{ {"STL教程","http://c.biancheng.net/stl/"},
                                             {"C语言教程","http://c.biancheng.net/c/"},
                                             {"Java教程","http://c.biancheng.net/java/"} };
    cout << myMap.at("C语言教程") << endl;
    // 下面一行代码会引发 out_of_range 异常
    // cout << myMap.at("Python教程") << endl;
    return 0;
}

程序执行结果为:

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

程序第 11 行代码处,通过 myMap 容器调用 at() 成员方法,可以成功找到键为 “C语言教程” 的键值对,并返回该键对应的值;而第 13 行代码,由于当前 myMap 容器中没有以 “Python教程” 为键的键值对,会导致 at() 成员方法查找失败,并抛出 out_of_range 异常。

  1. 除了可以直接获取指定键对应的值之外,还可以借助 find() 成员方法间接实现此目的。和以上 2 种方式不同的是,该方法返回的是一个迭代器,即如果查找成功,该迭代器指向查找到的键值对;反之,则指向 map 容器最后一个键值对之后的位置(和 end() 成功方法返回的迭代器一样)。举个例子:
#include <iostream>
#include <map>      // map
#include <string>   // string
using namespace std;

int main() {
    // 创建并初始化 map 容器
    std::map<std::string, std::string>myMap{ {"STL教程","http://c.biancheng.net/stl/"},
                                             {"C语言教程","http://c.biancheng.net/c/"},
                                             {"Java教程","http://c.biancheng.net/java/"} };
    map< std::string, std::string >::iterator myIter = myMap.find("C语言教程");
    cout << myIter->first << " " << myIter->second << endl;
    return 0;
}

程序执行结果为:

C语言教程 http://c.biancheng.net/c/

注意,此程序中如果 find() 查找失败,会导致第 12 行代码运行出错。因为当 find() 方法查找失败时,其返回的迭代器指向的是容器中最后一个键值对之后的位置,即不指向任何有意义的键值对,也就没有所谓的 first 和 second 成员了。

  1. 如果以上方法都不适用,我们还可以遍历整个 map 容器,找到包含指定键的键值对,进而获取该键对应的值。比如:
#include <iostream>
#include <map>      // map
#include <string>   // string
using namespace std;

int main() {
    // 创建并初始化 map 容器
    std::map<std::string, std::string>myMap{ {"STL教程","http://c.biancheng.net/stl/"},
                                             {"C语言教程","http://c.biancheng.net/c/"},
                                             {"Java教程","http://c.biancheng.net/java/"} };
    for (auto iter = myMap.begin(); iter != myMap.end(); ++iter) {
        // 调用 string 类的 compare() 方法,找到一个键和指定字符串相同的键值对
        if (!iter->first.compare("C语言教程")) {
            cout << iter->first << " " << iter->second << endl;
        }
    }
    return 0;
}

程序执行结果为:

C语言教程 http://c.biancheng.net/c/

本节所介绍的几种方法中,仅从“在 map 容器存储的键值对中,获取指定键对应的值”的角度出发,更推荐使用 at() 成员方法,因为该方法既简单又安全。

map insert()插入数据的4种方式

前面讲过,C++ STL map 类模板中对[]运算符进行了重载,即根据使用场景的不同,借助[]运算符可以实现不同的操作。举个例子:

#include <iostream>
#include <map>  //map
#include <string> //string
using namespace std;

int main()
{
    std::map<string, string> mymap{ {"STL教程","http://c.biancheng.net/java/"} };
    // 获取已存储键值对中,指定键对应的值
    cout << mymap["STL教程"] << endl;
    // 向 map 容器添加新键值对
    mymap["Python教程"] = "http://c.biancheng.net/python/";
    // 修改 map 容器已存储键值对中,指定键对应的值
    mymap["STL教程"] = "http://c.biancheng.net/stl/";
    for (auto iter = mymap.begin(); iter != mymap.end(); ++iter) {
        cout << iter->first << " " << iter->second << endl;
    }
   
    return 0;
}

程序执行结果为:

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

可以看到,当操作对象为 map 容器中已存储的键值对时,则借助 [ ] 运算符,既可以获取指定键对应的值,还能对指定键对应的值进行修改;反之,若 map 容器内部没有存储以 [ ] 运算符内指定数据为键的键值对,则使用 [ ] 运算符会向当前 map 容器中添加一个新的键值对。

实际上,除了使用 [ ] 运算符实现向 map 容器中添加新键值对外,map 类模板中还提供有 insert() 成员方法,该方法专门用来向 map 容器中插入新的键值对。

注意,这里所谓的“插入”,指的是 insert() 方法可以将新的键值对插入到 map 容器中的指定位置,但这与 map 容器会自动对存储的键值对进行排序并不冲突。当使用 insert() 方法向 map 容器的指定位置插入新键值对时,其底层会先将新键值对插入到容器的指定位置,如果其破坏了 map 容器的有序性,该容器会对新键值对的位置进行调整。

自 C++ 11 标准后,insert() 成员方法的用法大致有以下 4 种。

  1. 无需指定插入位置,直接将键值对添加到 map 容器中。insert() 方法的语法格式有以下 2 种:
// 1、引用传递一个键值对
pair<iterator,bool> insert (const value_type& val);
// 2、以右值引用的方式传递键值对
template <class P>
  pair<iterator,bool> insert (P&& val);

其中,val 参数表示键值对变量,同时该方法会返回一个 pair 对象,其中 pair.first 表示一个迭代器,pair.second 为一个 bool 类型变量:

  • 如果成功插入 val,则该迭代器指向新插入的 val,bool 值为 true;
  • 如果插入 val 失败,则表明当前 map 容器中存有和 val 的键相同的键值对(用 p 表示),此时返回的迭代器指向 p,bool 值为 false。

以上 2 种语法格式的区别在于传递参数的方式不同,即无论是局部定义的键值对变量还是全局定义的键值对变量,都采用普通引用传递的方式;而对于临时的键值对变量,则以右值引用的方式传参。

举个例子:

#include <iostream>
#include <map>  // map
#include <string> // string
using namespace std;

int main()
{
    // 创建一个空 map 容器
    std::map<string, string> mymap;
   
    // 创建一个真实存在的键值对变量
    std::pair<string, string> STL = { "STL教程","http://c.biancheng.net/stl/" };
   
    // 创建一个接收 insert() 方法返回值的 pair 对象
    std::pair<std::map<string, string>::iterator, bool> ret;
   
    // 插入 STL,由于 STL 并不是临时变量,因此会以第一种方式传参
    ret = mymap.insert(STL);
    cout << "ret.iter = <{" << ret.first->first << ", " << ret.first->second << "}, " << ret.second << ">" << endl;
    
    // 以右值引用的方式传递临时的键值对变量
    ret = mymap.insert({ "C语言教程","http://c.biancheng.net/c/" });
    cout << "ret.iter = <{" << ret.first->first << ", " << ret.first->second << "}, " << ret.second << ">" << endl;
    
    // 插入失败样例
    ret = mymap.insert({ "STL教程","http://c.biancheng.net/java/" });
    cout << "ret.iter = <{" << ret.first->first << ", " << ret.first->second << "}, " << ret.second << ">" << endl;
    return 0;
}

程序执行结果为:

ret.iter = <{
    
    STL教程, http://c.biancheng.net/stl/}, 1>
ret.iter = <{
    
    C语言教程, http://c.biancheng.net/c/}, 1>
ret.iter = <{
    
    STL教程, http://c.biancheng.net/stl/}, 0>

从执行结果中不难看出,程序中共执行了 3 次插入操作,其中成功了 2 次,失败了 1 次:

  • 对于插入成功的 insert() 方法,其返回的 pair 对象中包含一个指向新插入键值对的迭代器和值为 1 的 bool 变量。
  • 对于插入失败的 insert() 方法,同样会返回一个 pair 对象,其中包含一个指向 map 容器中键为 “STL教程” 的键值对和值为 0 的 bool 变量。

另外,在程序中的第 22 行代码,还可以使用如下 2 种方式创建临时的键值对变量,它们是等价的:

// 调用 pair 类模板的构造函数
ret = mymap.insert(pair<string,string>{ "C语言教程","http://c.biancheng.net/c/" });
// 调用 make_pair() 函数
ret = mymap.insert(make_pair("C语言教程", "http://c.biancheng.net/c/"));
  1. 除此之外,insert() 方法还支持向 map 容器的指定位置插入新键值对,该方法的语法格式如下:
// 以普通引用的方式传递 val 参数
iterator insert (const_iterator position, const value_type& val);
// 以右值引用的方式传递 val 键值对参数
template <class P>
  iterator insert (const_iterator position, P&& val);

其中 val 为要插入的键值对变量。注意,和第 1 种方式的语法格式不同,这里 insert() 方法返回的是迭代器,而不再是 pair 对象:

  • 如果插入成功,insert() 方法会返回一个指向 map 容器中已插入键值对的迭代器;
  • 如果插入失败,insert() 方法同样会返回一个迭代器,该迭代器指向 map 容器中和 val 具有相同键的那个键值对。

举个例子:

#include <iostream>
#include <map>  // map
#include <string> // string
using namespace std;

int main()
{
    // 创建一个空 map 容器
    std::map<string, string> mymap;
   
    // 创建一个真实存在的键值对变量
    std::pair<string, string> STL = { "STL教程","http://c.biancheng.net/stl/" };
    // 指定要插入的位置
    std::map<string, string>::iterator it = mymap.begin();
    // 向 it 位置以普通引用的方式插入 STL
    auto iter1 = mymap.insert(it, STL);
    cout << iter1->first << " " << iter1->second << endl;
    
    // 向 it 位置以右值引用的方式插入临时键值对
    auto iter2 = mymap.insert(it, std::pair<string, string>("C语言教程", "http://c.biancheng.net/c/"));
    cout << iter2->first << " " << iter2->second << endl;
    
    // 插入失败样例
    auto iter3 = mymap.insert(it, std::pair<string, string>("STL教程", "http://c.biancheng.net/java/"));
    cout << iter3->first << " " << iter3->second << endl;
    return 0;
}

程序执行结果为:

STL教程 http://c.biancheng.net/stl/
C语言教程 http://c.biancheng.net/c/
STL教程 http://c.biancheng.net/stl/

再次强调,即便指定了新键值对的插入位置,map 容器仍会对存储的键值对进行排序。也可以说,决定新插入键值对位于 map 容器中位置的,不是 insert() 方法中传入的迭代器,而是新键值对中键的值。

  1. insert() 方法还支持向当前 map 容器中插入其它 map 容器指定区域内的所有键值对,该方法的语法格式如下:
template <class InputIterator>
 void insert (InputIterator first, InputIterator last);

其中 first 和 last 都是迭代器,它们的组合<first,last>可以表示某 map 容器中的指定区域。

举个例子:

#include <iostream>
#include <map>  // map
#include <string> // string
using namespace std;

int main()
{
    // 创建并初始化 map 容器
    std::map<std::string, std::string>mymap{ {"STL教程","http://c.biancheng.net/stl/"},
                                                {"C语言教程","http://c.biancheng.net/c/"},
                                                {"Java教程","http://c.biancheng.net/java/"} };
    // 创建一个空 map 容器
    std::map<std::string, std::string>copymap;
    // 指定插入区域
    std::map<string, string>::iterator first = ++mymap.begin();
    std::map<string, string>::iterator last = mymap.end();
    // 将<first,last>区域内的键值对插入到 copymap 中
    copymap.insert(first, last);
    // 遍历输出 copymap 容器中的键值对
    for (auto iter = copymap.begin(); iter != copymap.end(); ++iter) {
        cout << iter->first << " " << iter->second << endl;
    }
    return 0;
}

程序执行结果为:

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

此程序中,<first,last> 指定的区域是从 mumap 容器第 2 个键值对开始,之后所有的键值对,所以 copymap 容器中包含有 2 个键值对。

  1. 除了以上一种格式外,insert() 方法还允许一次向 map 容器中插入多个键值对,其语法格式为:
void insert ({val1, val2, ...});

其中,vali 都表示的是键值对变量。

举个例子:

#include <iostream>
#include <map>  // map
#include <string> // string
using namespace std;

int main()
{
    // 创建空的 map 容器
    std::map<std::string, std::string>mymap;
    // 向 mymap 容器中添加 3 个键值对
    mymap.insert({ {"STL教程", "http://c.biancheng.net/stl/"},
                   { "C语言教程","http://c.biancheng.net/c/" },
                   { "Java教程","http://c.biancheng.net/java/" } });
    for (auto iter = mymap.begin(); iter != mymap.end(); ++iter) {
        cout << iter->first << " " << iter->second << endl;
    }
    return 0;
}

程序执行结果为:

C语言教程 http://c.biancheng.net/c/
Java教程 http://c.biancheng.net/java/
STL教程 http://c.biancheng.net/stl/

map emplace()和emplace_hint()方法

学习 map insert() 方法时提到,C++ STL map 类模板中还提供了 emplace() 和 emplace_hint() 成员函数,也可以实现向 map 容器中插入新的键值对。

值得一提的是,实现相同的插入操作,无论是用 emplace() 还是 emplace_hont(),都比 insert() 方法的效率高。

  1. 和 insert() 方法相比,emplace() 和 emplace_hint() 方法的使用要简单很多,因为它们各自只有一种语法格式。其中,emplace() 方法的语法格式如下:
template <class... Args>
 pair<iterator,bool> emplace (Args&&... args);

参数 (Args&&… args) 指的是,这里只需要将创建新键值对所需的数据作为参数直接传入即可,此方法可以自行利用这些数据构建出指定的键值对。另外,该方法的返回值也是一个 pair 对象,其中 pair.first 为一个迭代器,pair.second 为一个 bool 类型变量:

  • 当该方法将键值对成功插入到 map 容器中时,其返回的迭代器指向该新插入的键值对,同时 bool 变量的值为 true;
  • 当插入失败时,则表明 map 容器中存在具有相同键的键值对,此时返回的迭代器指向此具有相同键的键值对,同时 bool 变量的值为 false。

下面程序演示 emplace() 方法的具体用法:

#include <iostream>
#include <map>  // map
#include <string> // string
using namespace std;

int main()
{
    // 创建并初始化 map 容器
    std::map<string, string>mymap;
    // 插入键值对
    pair<map<string, string>::iterator, bool> ret = mymap.emplace("STL教程", "http://c.biancheng.net/stl/");
    cout << "1、ret.iter = <{" << ret.first->first << ", " << ret.first->second << "}, " << ret.second << ">" << endl;
    // 插入新键值对
    ret = mymap.emplace("C语言教程", "http://c.biancheng.net/c/");
    cout << "2、ret.iter = <{" << ret.first->first << ", " << ret.first->second << "}, " << ret.second << ">" << endl;
    // 失败插入的样例
    ret = mymap.emplace("STL教程", "http://c.biancheng.net/java/");
    cout << "3、ret.iter = <{" << ret.first->first << ", " << ret.first->second << "}, " << ret.second << ">" << endl;
    return 0;
}

程序执行结果为:

1、ret.iter = <{
    
    STL教程, http://c.biancheng.net/stl/}, 1>
2、ret.iter = <{
    
    C语言教程, http://c.biancheng.net/c/}, 1>
3、ret.iter = <{
    
    STL教程, http://c.biancheng.net/stl/}, 0>

可以看到,程序中共执行了 3 次向 map 容器插入键值对的操作,其中前 2 次都成功了,第 3 次由于要插入的键值对的键和 map 容器中已存在的键值对的键相同,因此插入失败。

  1. emplace_hint() 方法的功能和 emplace() 类似,其语法格式如下:
template <class... Args>
 iterator emplace_hint (const_iterator position, Args&&... args);

显然和 emplace() 语法格式相比,有以下 2 点不同:

  1. 该方法不仅要传入创建键值对所需要的数据,还需要传入一个迭代器作为第一个参数,指明要插入的位置(新键值对键会插入到该迭代器指向的键值对的前面);
  2. 该方法的返回值是一个迭代器,而不再是 pair 对象。当成功插入新键值对时,返回的迭代器指向新插入的键值对;反之,如果插入失败,则表明 map 容器中存有相同键的键值对,返回的迭代器就指向这个键值对。

下面程序演示 emplace_hint() 方法的用法:

#include <iostream>
#include <map>  // map
#include <string> // string
using namespace std;

int main()
{
    // 创建并初始化 map 容器
    std::map<string, string>mymap;
    // 指定在 map 容器插入键值对
    map<string, string>::iterator iter = mymap.emplace_hint(mymap.begin(),"STL教程", "http://c.biancheng.net/stl/");
    cout << iter->first << " " << iter->second << endl;
    iter = mymap.emplace_hint(mymap.begin(), "C语言教程", "http://c.biancheng.net/c/");
    cout << iter->first << " " << iter->second << endl;
    // 插入失败样例
    iter = mymap.emplace_hint(mymap.begin(), "STL教程", "http://c.biancheng.net/java/");
    cout << iter->first << " " << iter->second << endl;
    return 0;
}

程序执行结果为:

STL教程 http://c.biancheng.net/stl/
C语言教程 http://c.biancheng.net/c/
STL教程 http://c.biancheng.net/stl/

注意,和 insert() 方法一样,虽然 emplace_hint() 方法指定了插入键值对的位置,但 map 容器为了保持存储键值对的有序状态,可能会移动其位置。


2. multimap

概述

在掌握 C++ STL map 容器的基础上,本节介绍一个和 map 相似的关联容器,即 multimap 容器。

所谓“相似”,指的是 multimap 容器具有和 map 相同的特性,即 multimap 容器也用于存储 pair<const K, T> 类型的键值对(其中 K 表示键的类型,T 表示值的类型),其中各个键值对的键的值不能做修改;并且,该容器也会自行根据键的大小对存储的所有键值对做排序操作。和 map 容器的区别在于,multimap 容器中可以同时存储多(≥2)个键相同的键值对。

和 map 容器一样,实现 multimap 容器的类模板也定义在<map>头文件,并位于 std 命名空间中。因此,在使用 multimap 容器前,程序应包含如下代码:

#include <map>
using namespace std;

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

template < class Key,                                   // 指定键(key)的类型
           class T,                                     // 指定值(value)的类型
           class Compare = less<Key>,                   // 指定排序规则
           class Alloc = allocator<pair<const Key,T> >  // 指定分配器对象的类型
           > class multimap;

可以看到,multimap 容器模板有 4 个参数,其中后 2 个参数都设有默认值。大多数场景中,我们只需要设定前 2 个参数的值,有些场景可能会用到第 3 个参数,但最后一个参数几乎不会用到。

成员函数

成员方法 功能
begin() 返回指向容器中第一个(注意,是已排好序的第一个)键值对的双向迭代器。如果 multimap 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
end() 返回指向容器最后一个元素(注意,是已排好序的最后一个)所在位置后一个位置的双向迭代器,通常和 begin() 结合使用。如果 multimap 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
rbegin() 返回指向最后一个(注意,是已排好序的最后一个)元素的反向双向迭代器。如果 multimap 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。
rend() 返回指向第一个(注意,是已排好序的第一个)元素所在位置前一个位置的反向双向迭代器。如果 multimap 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。
cbegin() 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。
cend() 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。
crbegin() 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。
crend() 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。
find(key) 在 multimap 容器中查找首个键为 key 的键值对,如果成功找到,则返回指向该键值对的双向迭代器;反之,则返回和 end() 方法一样的迭代器。另外,如果 multimap 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
lower_bound(key) 返回一个指向当前 multimap 容器中第一个大于或等于 key 的键值对的双向迭代器。如果 multimap 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
upper_bound(key) 返回一个指向当前 multimap 容器中第一个大于 key 的键值对的迭代器。如果 multimap 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
equal_range(key) 该方法返回一个 pair 对象(包含 2 个双向迭代器),其中 pair.first 和 lower_bound() 方法的返回值等价,pair.second 和 upper_bound() 方法的返回值等价。也就是说,该方法将返回一个范围,该范围中包含的键为 key 的键值对。
empty() 若容器为空,则返回 true;否则 false。
size() 返回当前 multimap 容器中存有键值对的个数。
max_size() 返回 multimap 容器所能容纳键值对的最大个数,不同的操作系统,其返回值亦不相同。
insert() 向 multimap 容器中插入键值对。
erase() 删除 multimap 容器指定位置、指定键(key)值或者指定区域内的键值对。
swap() 交换 2 个 multimap 容器中存储的键值对,这意味着,操作的 2 个键值对的类型必须相同。
clear() 清空 multimap 容器中所有的键值对,使 multimap 容器的 size() 为 0。
emplace() 在当前 multimap 容器中的指定位置处构造新键值对。其效果和插入键值对一样,但效率更高。
emplace_hint() 在本质上和 emplace() 在 multimap 容器中构造新键值对的方式是一样的,不同之处在于,使用者必须为该方法提供一个指示键值对生成位置的迭代器,并作为该方法的第一个参数。
count(key) 在当前 multimap 容器中,查找键为 key 的键值对的个数并返回。

和 map 容器相比,multimap 未提供 at() 成员方法,也没有重载 [] 运算符。这意味着,map 容器中通过指定键获取指定指定键值对的方式,将不再适用于 multimap 容器。其实这很好理解,因为 multimap 容器中指定的键可能对应多个键值对,而不再是 1 个。

另外值的一提的是,由于 multimap 容器可存储多个具有相同键的键值对,因此上表中的 lower_bound()、upper_bound()、equal_range() 以及 count() 成员方法会经常用到。

创建C++ multimap容器的方法

multimap 类模板内部提供有多个构造函数,总的来说,创建 multimap 容器的方式可归为以下 5 种。

  1. 通过调用 multimap 类模板的默认构造函数,可以创建一个空的 multimap 容器:
std::multimap<std::string, std::string> mymultimap;
  1. 当然,在创建 multimap 容器的同时,还可以进行初始化操作。比如:
// 创建并初始化 multimap 容器
multimap<string, string>mymultimap{ {"C语言教程", "http://c.biancheng.net/c/"},
                                    {"Python教程", "http://c.biancheng.net/python/"},
                                    {"STL教程", "http://c.biancheng.net/stl/"} };

注意,使用此方式初始化 multimap 容器时,其底层会先将每一个{key, value}创建成 pair 类型的键值对,然后再用已建好的各个键值对初始化 multimap 容器。

实际上,我们完全可以先手动创建好键值对,然后再用其初始化 multimap 容器。下面程序使用了 2 种方式创建 pair 类型键值对,再用其初始化 multimap 容器,它们是完全等价的:

// 借助 pair 类模板的构造函数来生成各个pair类型的键值对
multimap<string, string> mymultimap{
    pair<string,string>{"C语言教程", "http://c.biancheng.net/c/"},
    pair<string,string>{ "Python教程", "http://c.biancheng.net/python/"},
    pair<string,string>{ "STL教程", "http://c.biancheng.net/stl/"}
};
// 调用 make_pair() 函数,生成键值对元素
// 创建并初始化 multimap 容器
multimap<string, string> mymultimap{
    make_pair("C语言教程", "http://c.biancheng.net/c/"),
    make_pair("Python教程", "http://c.biancheng.net/python/"),
    make_pair("STL教程", "http://c.biancheng.net/stl/")
};
  1. 除此之外,通过调用 multimap 类模板的拷贝(复制)构造函数,也可以初始化新的 multimap 容器。例如:
multimap<string, string> newmultimap(mymultimap);

由此,就成功创建一个和 mymultimap 完全一样的 newmultimap 容器。

在 C++ 11 标准中,还为 multimap 类增添了移动构造函数。即当有临时的 multimap 容器作为参数初始化新 multimap 容器时,其底层就会调用移动构造函数来实现初始化操作。举个例子:

// 创建一个会返回临时 multimap 对象的函数
multimap<string, string> dismultimap() {
    multimap<string, string>tempmultimap{ {"C语言教程", "http://c.biancheng.net/c/"},{"Python教程", "http://c.biancheng.net/python/"} };
    return tempmultimap;
}  
// 调用 multimap 类模板的移动构造函数创建 newMultimap 容器
multimap<string, string> newmultimap(dismultimap());

上面程序中,由于 dismultimap() 函数返回的 tempmultimap 容器是一个临时对象,因此在实现初始化 newmultimap 容器时,底层调用的是 multimap 容器的移动构造函数,而不再是拷贝构造函数。

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

  1. multimap 类模板还支持从已有 multimap 容器中,选定某块区域内的所有键值对,用作初始化新 multimap 容器时使用。例如:
// 创建并初始化 multimap 容器
multimap<string, string>mymultimap{ {"C语言教程", "http://c.biancheng.net/c/"},
                                    {"Python教程", "http://c.biancheng.net/python/"},
                                    {"STL教程", "http://c.biancheng.net/stl/"} };
multimap<string, string>newmultimap(++mymultimap.begin(), mymultimap.end());

这里使用了 multimap 容器的迭代器,选取了 mymultimap 容器中的最后 2 个键值对,用于初始化 newmultimap 容器。

  1. 前面讲到,multimap 类模板共可以接收 4 个参数,其中第 3 个参数可用来修改 multimap 容器内部的排序规则。默认情况下,此参数的值为std::less<T>,这意味着以下 2 种创建 multimap 容器的方式是等价的:
multimap<char, int> mymultimap{ {'a',1},{'b',2} };
multimap<char, int, std::less<char>> mymultimap{ {'a',1},{'b',2} };

mymultimap 容器中键值对的存储顺序为:

<a,1>
<b,2>

下面程序利用了 STL 模板库提供的std::greater<T>排序函数,实现令 multimap 容器对存储的键值对做降序排序:

multimap<char, int, std::greater<char>> mymultimap{ {'a',1},{'b',2} };

其内部键值对的存储顺序为:

<b,2>
<a,1>

在某些特定场景中,我们还可以为 multimap 容器自定义排序规则。

Guess you like

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