首页 > 编程笔记 > C++笔记 阅读:22

C++ map容器的用法(非常详细)

虽然 vector 容器很好用,但它只能保存和管理单个个体的数据,例如一个整型数或一个类的对象。然而,有时数据是成对出现的,例如一个整型的员工序号总是和一个 Employee 对象相对应。

在这种情况下,我们希望在保存员工序号的同时,也保存对应的 Employee 对象。面对这种成对出现的数据,vector 就显得无能为力。不过没有关系,STL 为这种成对出现的数据提供了一个专门的容器,叫做 map 容器。

创建并初始化map容器

map 是 STL 中的一种关联容器,它提供了一种对{键,值}数据对进行保存和管理的能力。这种数据是成对出现的,其中:
例如,一个员工号和一个员工对象共同构成一个数据对,员工号是这个数据对的键,而员工对象就是这个数据对的值。在 map 容器的内部,它是使用一棵红黑树来实现的,这是一种非严格意义上的平衡二叉树,如下图所示:


图 1 红黑树实现的map容器

因为这棵树具有对数据自动排序的功能,所以 map 容器内部所有的数据都是有序的。正是这种特性,使得 map 容器在增加和删除节点时对迭代器的影响很小,除被操作的当前节点外,对其他节点都没有太大影响。因此,map 容器适用于保存和管理那些增加和删除操作比较多的大量数据。

与 vector 容器的使用相似,map 容器同样是一个类模板。在使用时,需要根据它所要保存的数据,提供具体的数据类型参数对其进行实例化,以形成能够保存特定数据类型的 map 模板类。因为保存的是数据对,所以 map 类模板需要两个数据类型作为模板类型参数,其中第一个是键的数据类型,第二个是对应的值的数据类型。

例如,我们需要一个 map 容器来保存 {员工序号,员工对象} 这种数据对:
// 包含 map 容器所在的头文件
#include <map>
// 使用 map 容器所在的命名空间 std
using namespace std;
// 创建一个 map 容器对象 mapEmp
// 这个容器需要保存 int-Employee 形式的数据对
// 所以以 int 和 Employee 作为 map 类模板的类型参数
map<int, Employee> mapEmp;
经过简单的定义,mapEmp 容器对象就可以用于保存员工序号和员工对象这样的数据对了。

与 vector 容器相似,我们在创建一个 map 容器对象的同时,可以使用初始化列表对其进行初始化,向其中添加必要的初始数据元素。例如:
// 使用初始化列表对map容器进行初始化
map<int,Employee> mapEmp =
{   // map容器的初始数据元素
    {1001, Employee()},   // 默认的Employee对象
    {1002, Employee("Zhang")},
    {1003, Employee("Wang")}
};

将数据保存到 map 容器中

STL 中的每个容器都提供了相应的操作函数,以对容器或者容器中的数据进行操作。前面我们已经见识过 vector 容器的操作函数,map 容器也不例外,同样提供了多个操作函数来完成对 map 容器的常见操作,其中最常用的操作函数如下表所示。

表:map容器的常用操作函数(以操作map容器m为例)
函数 说明
m.insert(pair) 首先,将 pair 这个数据对插入 map 容器 m 中,然后 map 容器将根据这个数据对的键,对它进行排序。这意味着 map 容器中的数据始终是排好序的
m.size() 返回容器 m 中元素的个数
m.count(KEY) 判断 KEY 这个键是否在容器 m 中出现。如果出现,则返回 1,否则返回 0
m.find(KEY) 返回容器 m 中指向键为 KEY 的数据对的迭代器,也就是找到键为 KEY 的数据对
m.erase(pos) 删除容器 m 中 pos 迭代器所指向的元素
m.empty() 判断容器 m 是否为空
m.clear() 删除容器 m 中的所有元素

有了表中这些操作函数的帮助,对 map 容器的操作就简单多了。例如,可以调用 insert() 函数向容器中插入数据对:
// 创建一个 Employee 对象
Employee emp1;
// 使用 pair<int, Employee>模板类建立员工号 1 和员工对象 emp1 的联系,形成数据对
// 将 pair<int, Employee>模板类创建的对象插入 map 容器中
mapEmp.insert(pair<int, Employee>(1, emp1));
这里,我们使用 pair<int, Employee> 模板类将数字 1 和 Employee 对象 emp1 组合起来,形成一个 pair<int, Employee> 模板类的对象,也就是形成{键,值}数据对,然后调用 insert() 函数将其插入 map 容器,这样员工号 1 和员工对象 emp1 就建立了一对一的映射关系。

除使用 pair<T,U> 类模板来打包数据形成数据对外,还可以使用 map 容器的 value_type 类型来实现数据的打包,完成数据的插入。例如:
// 使用value_type类型实现数据的插入
mapEmp.insert(map<int, Employee>::value_type(1, emp1));

除调用 insert() 函数向 map 容器插入数据外,我们还可以把 map 容器看作一个数组,直接利用 map 容器的“[ ]”运算符,以数据对的键作为索引值,用数据对中的值对其进行赋值,直接向 map 容器中插入数据对。例如:
// 向mapEmp容器中插入一个数据对(1,emp1)
mapEmp[1] = emp1;
这三种向 map 容器插入数据的方法是等效的,读者可以根据自己的喜好进行选用。

这里需要注意的是,因为 map 容器中的所有键都要在插入后进行排序,所以插入 map 容器的键必须是可以排序的。对于使用基本数据类型作为键的 map 容器,我们无须担心,因为它们本身已经支持“<”关系运算符排序。如果使用自定义的数据类型作为 map 容器的键,就需要重载它的“<”关系运算符,以实现排序功能。

另外需要注意的是,map 容器中 {键,值} 数据对中的键和值是一一对应的,我们需要根据键来获取它所对应的值。因此,在进行插入操作时,必须保证插入的键在 map 容器中是唯一的,否则将导致插入操作失败。而如果是 multimap,其中的键和值可以是一对多的关系,则没有这样的要求。

C++ map根据键找到对应的值

当数据被保存到 map 容器中后,接下来就可以访问容器中的数据了。因为 map 容器中的数据总是以{键,值}对的形式出现的,所以对 map 容器中数据的访问就变成了寻藤摸瓜,即根据某个键找到它所对应的值。

对于 STL 容器而言,最常见的就是对容器的遍历访问。与 vector 容器相同的是,对于 map 容器,同样可以使用 for 循环来遍历容器中的所有数据。例如:
// 利用 for 循环遍历 map 容器中的所有数据
for( auto it = mapEmp.begin();
    it != mapEmp.end(); ++it )
{
    // 通过迭代器输出数据对的键和值
    cout << "当前员工号是: " << it->first << endl;
    cout << "姓名: " << it->second.GetName() << endl;
    cout << "工资: " << it->second.GetSalary() << endl;
}
map 容器的迭代器对象实际上是一个 pair 对象,它有两个成员变量 it->first 和 it->second,分别代表键以及与这个键相对应的值。这里,map 容器中保存的值是 Employee 对象,所以 it->second 实际上就是 Employee 对象,可以直接调用它的成员函数获得相应的数据。

除对 map 容器的遍历访问外,对于 map 这种基于节点的容器,更多的是使用查找某个节点的方式来访问其中某个特定的数据。我们知道,map 容器中所保存的是由 {键,值} 构成的数据对,map 容器提供了一个 find() 函数来查找某一个键,该函数会返回指向拥有这个键的数据对的迭代器,从而可以访问这个键所对应的值,顺藤摸瓜成功。例如:
// 定义要查找的键
int nKey = 1;
// 调用 find() 函数查找键,返回指向拥有这个键的数据对的迭代器
// 如果 map 容器中没有这个键,则返回指向容器末尾位置的迭代器
auto it = mapEmp.find( nKey );

// 查看迭代器是否指向容器末尾位置,以此判断是否找到相应的数据对
if (mapEmp.end() == it )
{
    // 如果迭代器指向容器末尾位置,就表示没有找到对应的数据对
    cout << "无法找到键为" << nKey << "的数据对。" << endl;
}
else
{
    // 如果迭代器指向其他位置,则表示找到相应的数据对
    // find() 函数返回的迭代器指向的就是这个数据对的位置
    cout << "找到键为" << nKey << "的数据对。" << endl;
    // 通过迭代器访问这个数据对的值,也就是 Employee 对象
    cout << "姓名:" << it->second.GetName() << endl;
    cout << "工资:" << it->second.GetSalary() << endl;
}
在这里,通过 find() 函数在容器中查找具有某个键的数据对。如果能够找到,find() 函数会返回指向这个数据对的迭代器,通过这个迭代器就可以访问这个数据对的键和值了。

除访问某个特定键对应的数据对外,对于 map 容器,有时还需要访问某个范围内的数据对。例如,希望输出员工号从 1 到 1000 的所有员工的信息:
// 定义键的起止范围
int nFromKey = 1;
int nToKey = 1000;

// 用迭代器表示起始位置和终止位置
auto itfrom = mapEmp.lower_bound( nFromKey );
auto itto = mapEmp.upper_bound( nToKey );

// 判断是否找到正确的范围
if( mapEmp.end() != itfrom && mapEmp.end() != itto )
{
    // 输出范围内的所有数据
    for( auto it = itfrom; it != itto; ++it )
    {
        cout << "当前员工号是: " << it->first << endl;
        cout << "姓名: " << it->second.GetName() << endl;
        cout << "工资: " << it->second.GetSalary() << endl;
    }
    // 删除范围内的所有数据
    mapEmp.erase( itfrom, itto );
}
在这段代码中,分别调用了 lower_bound() 函数和 upper_bound() 函数来获得指向这个范围的起始位置和终止位置的迭代器,然后通过这两个迭代器所界定的范围来对某个键范围内的数据进行访问。最后,我们调用 erase() 函数删除这个键范围内的所有数据。

相关文章