第十一章 关联容器
关联容器与顺序容器有着根本的不同:
-
关联容器中的元素是按关键字来保存和访问的。
-
顺序容器中的元素是按它们在容器中的位置来顺序保存和访问的。
关联容器支持高效的关键字查找和访问,有两个主要的关联容器:
-
map,其元素是一些关键字-值对,关键字起到索引作用,值则表示与之相关的数据。
-
set,每个元素只包含一个关键字。
使用关联容器
map是关键字-值对的集合,通常被称为关联数组。关联数组与“正常”数组类似,不同之处在于其下标不必是整数。我们通过一个关键字而不是位置来查找值。
set就是关键字的简单集合。
具体使用案例见书本p375。
关联容器概述
关联容器(有序的和无序的)都支持9.2节(第294页)中介绍的普通容器操作。关联容器不支持顺序容器的位置相关的操作,例如push_front。
除了与顺序容器相同的操作之外,关联容器还支持一些顺序容器不支持的操作(见p388)和类型别名(见p381)。
关联容器的迭代器都是双向的。
定义关联容器
map<string, size_t> word_count; // 空容器
set<string> exclude = {"the", "but", "and"}; // 列表初始化
// 三个元素;authors将姓映射为名
map<string, string> authors = {
{"Joyce", "James"},
{"Austen", "Jane"},
{"Dickens", "Charles"}
};
初始化multimap或multiset
一个map或set中的关键字必须是唯一的,即,对于一个给定的关键字,只能有一个元素的关键字等于它。
multimap和multiset没有此限制,它们都允许多个元素具有相同的关键字(这些元素会相邻存储)。
关键字类型的要求
对于有序容器,关键字类型必须定义元素比较的方法,默认情况下,标准库使用关键字类型的<运算符来比较两个关键字。
使用关键字类型的比较函数
用来组织一个容器中元素的操作的类型也是该容器类型的一部分。为了指定使用自定义的操作,必须在定义关联容器类型时提供此操作的类型(比如一个函数指针类型)。
bool compareIsbn(const Sales_data &lhs, const Sales_data &rhs)
{
return lhs.isbn() < rhs.isbn();
}
multiset<Sales_data, decltype(compareIsbn)*> bookstore(compareIsbn);
pair类型
pair类型定义在头文件utility中。
一个pair保存两个数据成员,pair是一个用来生成特定类型的模板。
pair<string, string> anon; // 保存两个string
pair<string, vector<int>> line; // 保存string和vector<int>
pair的默认构造函数对数据成员进行值初始化。也可以为每个成员提供初始化器:
pair<string, string> author{"James", "Joyce"};
pair的数据成员是public的,两个成员分别是first,second。
创建pair对象的函数
pair<string, int>
process(vector<string> &v)
{
// 处理v
if (!v.empty())
return {v.back(), v.back().size()}; // 列表初始化
else
return pair<string, int>(); // 隐式构造返回值
}
关联容器操作
除了表9.2(第295页)中列出的类型,关联容器还定义了这些类型:
-
key_type, 此容器类型的关键字类型
-
mapped_type, 每个关键字关联的类型,只适用于map
-
value_type, 对于set,与key_type相同,对于map, 为
pair<const key_type, mapped_type>
关联容器迭代器
当解引用一个关联容器迭代器时,我们会得到一个类型为容器的value_type的值的引用。对map而言,value_type是一个pair类型。
Note
必须记住,一个map的value_type是一个pair,我们可以改变pair的值,但不能改变关键字成员的值。
set的迭代器是const的
与不能改名map元素的关键字一样,一个set中的关键字也是const的。可以用一个set迭代器来读取元素的值,但不能修改。
遍历关联容器
map和set类型都支持begin和end操作,我们可以利用这些函数获取迭代器,然后用迭代器来遍历容器。
auto map_it = word_count.cbegin();
while (map_it != word_count.cend()) {
// ...
++map_it; // 递增迭代器,移动到下一个元素
}
Note
当使用一个迭代器遍历一个map、multimap、set或multiset时,迭代器按关键字升序遍历元素。
关联容器和算法
我们通常不对关联容器使用泛型算法。更多讨论见书本p383。
添加元素
关联容器的insert成员向容器中添加一个元素或一个元素范围。由于map和set包含不重复的关键字,因此插入一个已存在的元素对容器没有任何影响。
向map添加元素
对一个map进行insert操作时,必须记住元素类型是pair。
word_count.insert({word, 1});
word_count.insert(make_pair(word, 1));
word_count.insert(pair<string, size_t>(word, 1));
word_count.insert(map<string, size_t>::value_type(word, 1));
向multiset或multimap添加元素
由于一个multi容器中的关键字不必唯一,在这些类型上调用insert总会插入一个元素:
multimap<string, string> authors;
// 插入第一个元素
authors.insert({"Barth, John", "Sot-Weed Factor"});
// 正确,添加第二个元素
authors.insert({"Barth, John"}, "Lost in the Funhouse");
对允许重复关键字的容器,接受单个元素的insert操作返回一个指向新元素的迭代器。
删除元素
关联容器定义了三个版本的erase:
-
与顺序容器一样,传递给erase一个迭代器或一个迭代器范围来删除一个元素或一个元素范围。
-
接受一个key_type参数,删除所有匹配给定关键字的元素(如果存在的话),返回实际删除的元素的数量。
对于保存不重复关键字的容器,erase的返回值总是0或1。
map的下标操作
map和unordered_map容器提供了下标运算符和一个对应的at函数。
set类型不支持下标操作,不能对一个multimap或一个unordered_multimap进行下标操作,因为这些容器中可能有多个值与一个关键字相关联。
map下标运算符接受一个索引获取与此关键字相关联的值,如果关键字不在map中,会为它创建一个元素并插入到map中,关联值将进行值初始化。
使用下标操作的返回值
当对一个map进行下标操作时,会获得一个mapped_type对象。
当解引用一个map迭代器时,会得到一个value_type对象。
Note
与vector与string不同,map的下标运算符返回的类型与解引用map迭代器得到的类型不同。
访问元素
如果我们关心的只不过是一个特定元素是否已在容器中,使用find比较好。
对于不允许重复关键字的容器,可能使用find还是count没什么区别。
对于允许重复关键字的容器,count会统计有多少个元素有相同的关键字。
无序容器
无序容器不是使用比较运算符来组织元素,而是使用一个哈希函数和关键字类型的==运算符。
在关键字类型的元素没有明显的序关系的情况下,无序容器是非常有用的。