8 STL【map介绍】【map创建/成员函数/迭代器】【map键值对插入】【multimap介绍】

0 - 前言

参考:C++ STL map容器详解

1 - 什么是map

作为关联式容器的一种,map 容器存储的都是 pair 对象,也就是用 pair 类模板创建的键值对。其中,各个键值对的键和值可以是任意数据类型,包括 C++ 基本数据类型(int、double 等)、使用结构体或类自定义的类型。

通常情况下,map 容器中存储的各个键值对都选用 string 字符串作为键的类型。

与此同时,在使用 map 容器存储多个键值对时,该容器会自动根据各键值对的键的大小,按照既定的规则进行排序。默认情况下,map 容器选用std::less<T>排序规则(其中 T 表示键的数据类型),其会根据键的大小对所有键值对做升序排序。当然,根据实际情况的需要,我们可以手动指定 map 容器的排序规则,既可以选用 STL 标准库中提供的其它排序规则(比如std::greater<T>),也可以自定义排序规则。

另外需要注意的是,使用 map 容器存储的各个键值对,键既不能重复也不能被修改。换句话说,map 容器中存储的各个键值对不仅键的值独一无二,键的类型也会用 const 修饰,这意味着只要键值对被存储到 map 容器中,其键的值将不能再做任何修改。

前面提到,map 容器存储的都是 pair 类型的键值对元素,更确切的说,该容器存储的都是 pair<const K, T> 类型(其中 K 和 T 分别表示键和值的数据类型)的键值对元素。

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

#include <map>
using namespace std;

2 - 创建map

//1) 通过调用 map 容器类的默认构造函数,可以创建出一个空的 map 容器
map<string, int>myMap;

//2)创建 map 容器的同时,也可以进行初始化
map<string, int>myMap{
    
     {
    
    "C语言教程",10},{
    
    "STL教程",20} };
map<string, int>myMap{
    
    make_pair("C语言教程",10),make_pair("STL教程",20)};//make_pair函数的使用需要包含头文件 #include <utility>

//3)利用先前已创建好的 map 容器,再创建一个新的 map 容器
map<string, int>newMap(myMap);

//4) map 类模板还支持取已建 map 容器中指定区域内的键值对,创建并初始化新的 map 容器
map<string, int>myMap{
    
     {
    
    "C语言教程",10},{
    
    "STL教程",20} };
map<string, int>newMap(++myMap.begin(), myMap.end());

在以上几种创建 map 容器的基础上,我们都可以手动修改 map 容器的排序规则。默认情况下,map 容器调用 std::less 规则,根据容器内各键值对的键的大小,对所有键值对做升序排序。因此,如下 2 行创建 map 容器的方式,其实是等价的:

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

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

下面程序手动修改了 myMap 容器的排序规则,令其作降序排序:

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

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

3 - map成员函数

在这里插入图片描述

在这里插入图片描述

4 - map迭代器

C++ STL 标准库为 map 容器配备的是双向迭代器(bidirectional iterator)。这意味着,map 容器迭代器只能进行 ++p、p++、–p、p–、*p 操作,并且迭代器之间只能使用 == 或者 != 运算符进行比较。

在这里插入图片描述

//遍历map
#include <iostream>
#include <map>      // pair
#include <string>       // string
using namespace std;
int main() {
    
    
    //创建并初始化 map 容器
    map<string, 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;
}

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 容器中很少用到。

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。

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

5 - map获取键对应的值

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

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

    1. map 类模板中对[ ]运算符进行了重载,这意味着,类似于借助数组下标可以直接访问数组中元素,通过指定的键,我们可以轻松获取 map 容器中该键对应的值。
#include <iostream>
#include <map>      // map
#include <string>   // string
using namespace std;
int main() {
    
    
    //创建并初始化 map 容器
    map<string, 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;
}

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

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

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

6 - map插入新键值对

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

map 类模板中还提供有 insert() 成员方法,该方法专门用来向 map 容器中插入新的键值对。

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

1、 无需指定插入位置,直接将键值对添加到 map 容器中。insert() 方法的语法格式有以下 2 种:

//1、引用传递一个键值对
pair<iterator,bool> insert (const value_type& val);
//2、以右值引用的方式传递键值对
pair<iterator,bool> insert (P&& val);

//其中,val 参数表示键值对变量,同时该方法会返回一个 pair 对象,其中 pair.first 表示一个迭代器,pair.second 为一个 bool 类型变量:
  • 如果成功插入 val,则该迭代器指向新插入的 val,bool 值为 true;
  • 如果插入 val 失败,则表明当前 map 容器中存有和 val 的键相同的键值对(用 p 表示),此时返回的迭代器指向 p,bool 值为 false。
#include <iostream>
#include <map>  //map
#include <string> //string
using namespace std;
int main()
{
    
    
    //创建一个空 map 容器
    map<string, string> mymap;
   
    //创建一个真实存在的键值对变量
    pair<string, string> STL = {
    
     "STL教程","http://c.biancheng.net/stl/" };
   
    //创建一个接收 insert() 方法返回值的 pair 对象
    pair<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>

以下两种方式创建临时键值对变量,等价

//调用 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/"));

2、 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 具有相同键的那个键值对。

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

3、 insert() 方法还支持向当前 map 容器中插入其它 map 容器指定区域内的所有键值对

template <class InputIterator>
  void insert (InputIterator first, InputIterator last);

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

4、 insert() 方法还允许一次向 map 容器中插入多个键值对

void insert ({
    
    val1, val2, ...});//vali 都表示的是键值对变量。


//创建空的 map 容器
map<string, string>mymap;
//向 mymap 容器中添加 3 个键值对
mymap.insert({
    
     {
    
    "STL教程", "http://c.biancheng.net/stl/"},
              {
    
     "C语言教程","http://c.biancheng.net/c/" },
              {
    
     "Java教程","http://c.biancheng.net/java/" } });

emplace和emplace_hint

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

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

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

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

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

emplace_hint() 方法的功能和 emplace() 类似,其语法格式如下:

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

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

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

7 - multimap

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

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

#include <map>
using namespace std;

7 - 1 创建multimap

//1、调用 multimap 类模板的默认构造函数,可以创建一个空的 multimap 容器
multimap<string, string>mymultimap;

//2、创建 multimap 容器的同时,还可以进行初始化操作
multimap<string, string>mymultimap{
    
     {
    
    "C语言教程", "http://c.biancheng.net/c/"},
                                    {
    
    "Python教程", "http://c.biancheng.net/python/"},
                                    {
    
    "STL教程", "http://c.biancheng.net/stl/"} };

//3、 除此之外,通过调用 multimap 类模板的拷贝(复制)构造函数,也可以初始化新的 multimap 容器
multimap<string, string>newmultimap(mymultimap);

//4、 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 类模板共可以接收 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} };

内部:
<a,1>
<b,2>

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

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

内部:
<b,2>
<a,1>

7 - 2 multimap成员方法

在这里插入图片描述

在这里插入图片描述

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

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

猜你喜欢

转载自blog.csdn.net/weixin_44484715/article/details/115833869