本文主要介绍C++编程语言的STL(Standard Template Library)中map容器及相关容器的相关知识,同时通过示例代码介绍这些容器的常见用法。

1 概述

关联容器(associative-container)和顺序容器有着根本的不同:关联容器中的元素是按关键字来保存和访问的,而顺序容器中的元素是按它们在容器中的位置来顺序保存和访问的。

虽然关联容器的很多行为与顺序容器相同,但其不同之处反映了关键字的作用。关联容器支持高效的关键字查找和访问。

两个主要的关联容器类型为mapset。map中的元素是一些“关键字-值对(key-value)”:“关键字”起索引的作用,“值”则表示与索引相关联的数据。set中每个元素只包含一个关键字。

2 map

2.1 特性

map的特性如下:

  • 每个关键字只能出现一次;
  • 默认按key的升序存储元素;
  • 使用红黑树(red-black trees)作为底层数据结构。

2.2 常见用法

2.2.1 构造map

构造map的方法如下(以键值对为“int-string”类型为例):

map<int, string> mapStudent;

2.2.2 插入元素

map提供了几种插入元素的方法。

使用insert函数插入pair数据:

mapStudent.insert(pair<int, string>(1, "zhao"));
mapStudent.insert(pair<int, string>(2, "qian"));
mapStudent.insert(pair<int, string>(3, "sun"));

使用数组方式插入数据:

mapStudent[1] = "zhao";
mapStudent[2] = "qian";
mapStudent[3] = "sun";

说明:上面的两种方法是有区别的,用insert函数插入数据,涉及到集合的唯一性的概念,即当map中有这个关键字时,insert操作是不能实现数据插入的;但是数组方式能够插入数据,插入的数据会覆盖该关键字之前对应的值。

2.2.3 遍历数据

可以通过map的迭代器iterator、调用map对象的begin()和end()函数,实现对于map中数据的遍历操作,示例代码如下:

// 遍历
cout << "content of map as followed: " << endl;
mapStudent_t::iterator iter;
for (iter = mapStudent.begin(); iter != mapStudent.end(); iter++)
{
    cout << iter->first << " " << iter->second << endl;
}

说明:

1)begin()和end()两个成员函数,分别对应map对象中第一个和最后一个数据条目,这两个数据条目的类型是iterator;

2)通过map对象的方法获取到的iterator数据类型是一个“std::pair”对象,iterator数据类型包括下面两个数据:

  • iterator->first: 关键字(key)
  • iterator->second: 存储的数据(value)

2.2.4 查找元素

可以通过使用map的迭代器iterator、调用find函数(传入的参数为要查找的key),来查找具体的元素,示例代码如下:

// 查找
iter = mapStudent.find(2);
if (iter != mapStudent.end())
{
    cout << "Find it, the relative value is: " << iter->second << endl;
}
else
{
    cout << "Can not find the relative value." << endl;
}

说明:find函数的入参为map的key,并且入参必须完全匹配map的key,才能找到对应元素的value。例如,对于以下map结构:

mapStudent.insert(pair<string, string>("zhao", "zhaoyun"));

如果要查找该map中的元素,必须在find函数中输入完整的key(即“zhao”)才行,如果输入的入参为“zh”、或“zha”,都不能找到该元素。

2.2.5 删除数据

对于map中的数据删除操作,分为以下两种情况:

1)如果想要清空map中的所有数据,可以使用clear函数;

2)如果想要删除map中的指定数据,可以通过使用map的迭代器iterator、调用erase函数来实现,示例代码如下:

// 删除
iter = mapStudent.find(3);
mapStudent.erase(iter);

2.3 示例程序

2.3.1 示例程序1

此处展示一个输出map结构内容的示例代码。代码(map_test1.cpp)内容如下:

#include <iostream>
#include <map>

using namespace std;

int main()
{
    // <int, string>结构的map
    typedef std::map<int, string> mapStudent_t;
    mapStudent_t mapStudent;

    mapStudent.insert(pair<int, string>(1, "zhao"));
    mapStudent.insert(pair<int, string>(2, "qian"));
    mapStudent.insert(pair<int, string>(3, "sun"));

    // map简单的输出方法
    cout << "mapStudent[1] is: " << mapStudent[1] << endl;
    cout << "mapStudent[3] is: " << mapStudent[3] << endl;

    // <string, string>结构的map
    typedef std::map<string, string> mapRole_t;
    mapRole_t mapRole;

    mapRole.insert(pair<string, string>("mage", "alliance"));
    mapRole.insert(pair<string, string>("paladin", "alliance"));
    mapRole.insert(pair<string, string>("warlock", "Horde"));

    // map简单的输出方法
    cout << "mapRole[\"mage\"] is: " << mapRole["mage"] << endl;
    cout << "mapRole[\"warlock\"] is: " << mapRole["warlock"] << endl;

    return 0;
}

编译并执行上述代码,结果如下:

2.3.2 示例程序2

示例代码(map_test2.cpp)内容如下:

#include <iostream>
#include <map>
#include <string>

using namespace std;

int main()
{
    typedef std::map<int, string> mapStudent_t;
    mapStudent_t mapStudent;

    mapStudent.insert(pair<int, string>(1, "zhao"));
    mapStudent.insert(pair<int, string>(2, "qian"));
    mapStudent.insert(pair<int, string>(3, "sun"));

    // 遍历
    cout << "content of map as followed: " << endl;
    mapStudent_t::iterator iter;
    for (iter = mapStudent.begin(); iter != mapStudent.end(); iter++)
    {
        cout << iter->first << " " << iter->second << endl;
    }

    // 查找
    iter = mapStudent.find(2);
    if (iter != mapStudent.end())
    {
        cout << "Find it, the relative value is: " << iter->second << endl;
    }
    else
    {
        cout << "Can not find the relative value." << endl;
    }

    // 删除
    iter = mapStudent.find(3);
    mapStudent.erase(iter);

    // 再次遍历,观察删除操作是否成功
    cout << "after delete, content of map as followed: " << endl;
    for (iter = mapStudent.begin(); iter != mapStudent.end(); iter++)
    {
        cout << iter->first << " " << iter->second << endl;
    }
    
    return 0;
}

上述代码运行结果如下:

3 unordered_map

C++标准库提供了四个无序关联容器(unordered associated container),这些容器不是使用比较运算符,而是使用一个哈希函数(hash function)和关键字类型的“==”运算符来组织元素。

在关键字类型的元素没有明显的“序关系”的情况下,无序容器是非常有用的。在某些应用中,维护元素的序的代价非常高昂,此时无序容器也很有用。

unordered_map就是C++标准库提供的四个无序关联容器之一。

3.1 特性

unordered_map的特性如下:

  • 每个关键字(key)只能出现一次;
  • 根据key的哈希值将元素存储在指定位置,因此存储的元素是无序的;
  • 使用哈希表(Hash Table)作为底层数据结构。

3.2 常见用法

3.2.1 构造unordered_map

构造unordered_map的方法如下(以key和value均为char类型为例):

unordered_map<char, char> unoMap;

3.2.2 插入元素

可以通过[]运算符来执行插入元素操作,示例代码如下:

unoMap['a'] = 'b';

说明:如果key对应的value存在,则执行更新元素操作,否则执行添加元素操作。

3.2.3 查找元素

可以通过使用unordered_map的迭代器iterator、调用find函数(传入的参数为要查找的key),来查找具体的元素,示例代码如下:

// 查找
if (mapStudent.find(2) != mapStudent.end())
{
    ...
}

此外,也可以通过[]运算符来执行查找元素操作,示例代码如下:

char chValue = unoMap['a'];

3.2.4 计算字符串中各个字符出现的次数

利用unordered_map计算字符串中各个字符出现的次数,示例代码如下:

string strDemo = "aabcccc";
unordered_map<char, int> unoMap;
for (auto ch:strDemo) {
    // unoMap[ch]即为ch字符对应的出现次数
    unoMap[ch]++;
}

3.2.5 查看元素出现的次数

查询某元素在unordered_map中出现的次数,方法如下(返回0或1):

m.count(i)

4 关于有序与无序

STL默认提供的map是“有序”的(即“map”),同时也提供了对应的“无序”版本(即“unordered_map”)。

对于map容器来说,存储于其中的元素是否有序,只会在使用迭代器iterator等场景下对我们有影响,而当我们使用“key-value”获取元素内容时,容器是否有序是无影响的。

下面给出一个简单的示例代码:

#include <map>
#include <unordered_map>
#include <iostream>

using namespace std;

int main()
{
    map<int, char> Map;
    Map[1] = 'a';
    Map[2] = 'b';
    Map[3] = 'c';
    Map[4] = 'd';
    Map[5] = 'e';

    cout << "the Map output is: " << endl;
    for (auto i = Map.begin(); i != Map.end(); i++) {
        std::cout << i->first << ' ' << i->second << std::endl;
    }   
    cout << "Map[2] is: " << Map[2] << endl;

    unordered_map<int, char> unoMap;
    unoMap[1] = 'a';
    unoMap[2] = 'b';
    unoMap[3] = 'c';
    unoMap[4] = 'd';
    unoMap[5] = 'e';
        
    cout << "the unordered_map output is: " << endl;
    for (auto i = unoMap.begin(); i != unoMap.end(); i++) {
        std::cout << i->first << ' ' << i->second << std::endl;
    }   
    cout << "unoMap[2] is: " << unoMap[2] << endl;

    return 0;
}

上方代码的运行结果如下:

从上述代码运行结果可以看出:

  • 当使用迭代器iterator访问map容器中元素时,map与unordered_map结果不同,其中map是有序的,而unordered_map是无序的(似乎是倒序,待确认);
  • 使用下标法获取元素值时,无论map容器中元素是否有序,都可以获取到正确的结果。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐