关联容器和顺序容器的本质差别在于:关联容器通过键(key)存储和读取元素,而顺序容器则通过元素在容器中的位置顺序存储和访问元素。
关联容器(Associative containers)支持通过键来高效地查找和读取元素。两个基本的关联容器类型是 map 和 set。map 的元素以键-值(key-value)对的形式组织:键用作元素在 map中的索引,而值则表示所存储和读取的数据。set 仅包含一个键,并有效地支持关于某个键是否存在的查询。
map和set的例子:在做某种文本处理时,可使用 set 保存要忽略的单词。而字典则是 map 的一种很好的应用:单词本身是键,而它的解释说明则是值。
开始介绍关联容器之前,必须先了解一种与之相关的简单的标准库类型——pair
pairs 类型提供的操作
pair 类型的使用相当繁琐,可考虑利用 typedef 简化其声明:
typedef pair Author;
Author proust("Marcel", "Proust");
Author joyce("James", "Joyce");
其成员都是仅有的,分别命名为 first 和second。只需使用普通的点操作符——成员访问标志即可访问其成员:
string firstBook;
// access and test the data members of the pair
if (author.first == "James" && author.second == "Joyce")
firstBook = "Stephen Hero";
标准库还定义了一个 make_pair 函数,由传递给它的两个实参生成一个新的 pair 对象。
pair next_auth;
string first, last;
while (cin >> first >> last) {
// generate a pair from first and last
next_auth = make_pair(first, last);
// process next_auth...
}
由于 pair 的数据成员是公有的,因而可如下直接地读取输入:
while (cin >> next_auth.first >> next_auth.second) {
// process next_auth...
}
关联容器共享大部分——但并非全部——的顺序容器操作。关联容器不提供front、 push_front、 pop_front、back、push_back 以及 pop_back 操作。
关联容器构造函数:
C c; // creates an empty container
// c2 must be same type as c1
C c1(c2); // copies elements from c2 into c1
// b and e are iterators denoting a sequence
C c(b, e); // copies elements from the sequence into c
关联容器不能通过容器大小来定义,因为这样的话就无法知道键所对应的值是什么。
容器元素根据键的次序排列:在迭代遍历关联容器时,我们可确保按键的顺序的访问元素,而与元素在容器中的存放位置完全无关。
要使用 map 对象,则必须包含 map 头文件。在定义 map 对象时,必须分别指明键和值的类型(value type)
map word_count; // empty map from string to int
在使用关联容器时,它的键不但有一个类型,而且还有一个相关的比较函数。默认情况下(允许自定义),标准库使用键类型定义的 < 操作符来实现键(key type)的比较。
对于键类型,唯一的约束就是必须支持 < 操作符,至于是否支持其他的关系或相等运算,则不作要求。
需谨记 value_type 是 pair 类型,它的值成员可以修改,但键成员不能修改。
对迭代器进行解引用时,将获得一个引用,指向容器中一个 value_type 类型的值。对于 map 容器,其 value_type 是 pair 类型:
map::iterator map_it = word_count.begin();
// *map_it is a reference to a pairobject
cout << map_it->first; // prints the key for this element
cout << " " << map_it->second; // prints the valu e of the element
map_it->first = "new key"; // error: key is const
++map_it->second; // ok: we can change value through a n iterator
对迭代器进行解引用将获得一个 pair 对象,它的 first成员存放键,为 const,而 second 成员则存放值。
该项工作可使用 insert 成员实现;或者,先用下标操作符获取元素(即键值),然后给获取的元素赋值。
map word_count; // empty map
// insert default initialzed element with key Anna; then assign 1
to its value
word_count["Anna"] = 1;
将发生以下事情:
1. 在 word_count 中查找键为 Anna 的元素,没有找到。
2. 将一个新的键-值对插入到 word_count 中。它的键是 const string 类型
的对象,保存 Anna。而它的值则采用值初始化,这就意味着在本例值为 0。
3. 将这个新的键-值对插入到 word_count 中。
4. 读取新插入的元素,并将它的值赋为 1。
使用下标访问 map 与使用下标访问数组或 vector 的行为截然不同:用下标访问不存在的元素将导致在 map容器中添加一个新元素,它的键即为该下标值。如果该键已在容器中,则 map 的下标运算与 vector 的下标运算行为相同:返回该键所关联的值。只有在所查找的键不存在时,map 容器才为该键创建一个新的元素,并将它插入到此 map 对象中。
显然,map 迭代器返回 value_type 类型的值——包含 const key_type 和 mapped_type 类型成员的 pair 对象;下标操作符则返回一个mapped_type 类型的值。
对于 map 容器,如果下标所表示的键在容器中不存在,则添加新元素,这一特性可使程序惊人地简练:
// count number of times each word occurs in the input
map word_count; // empty map from string to int
string word;
while (cin >> word)
++word_count[word];
这段程序创建一个 map 对象,用来记录每个单词出现的次数。
而插入元素的
另一个方法是:直接使用 insert 成员,其语法更紧凑:
// if Anna not already in word_count, inserts new element with value 1
word_count.insert(map::value_type("Anna", 1));
这个 insert 函数版本的实参:
map
下标操作符给出了读取一个值的最简单方法:
map word_count;
int occurs = word_count["foobar"];
如果该键不在 map 容器中,那么下标操作会插入一个具有该键的新元素,其关联的值为 0。
map 容器提供了两个操作:count 和 find,用于检查某个键是否存在而不会插入该键。
对于 map 对象,count 成员的返回值只能是 0 或 1。map 容器只允许一个
键对应一个实例,所以 count 可有效地表明一个键是否存在。
int occurs = 0;
if (word_count.count("foobar"))
occurs = word_count["foobar"];
执行 count 后再使用下标操作符,实际上是对元素作了两次查找。如果希望当元素存在时就使用它,则应该用 find 操作。
find 操作返回指向元素的迭代器,如果元素不存在,则返回 end 迭代器:
int occurs = 0;
map::iterator it = word_count.find("foobar");
if (it != word_count.end())
occurs = it->second;
从 map 容器中删除元素的 erase 操作有三种变化形式(表 10.7)。与顺序容器一样,可向 erase传递一个或一对迭代器,来删除单个元素或一段范围内的元素。其删除功能类似于顺序容器,但有一点不同:map 容器的 erase 操作返回 void,而顺序容器的 erase 操作则返回一个迭代器,指向被删除元素后面的元素。
除此之外,map 类型还提供了一种额外的 erase 操作,其参数是 key_type类型的值,如果拥有该键的元素存在,则删除该元素。erase 函数返回被删除元素的个数。对于 map 容器,该值必然是 0 或 1。如果返回 0,则表示欲删除的元素在 map 不存在。
与其他容器一样,map 同样提供 begin 和 end 运算,以生成用于遍历整个容器的迭代器。
map::const_iterator map_it = word_count.begin();
while (map_it != word_count.end()) {
cout << map_it->first << " occurs "
<< map_it->second << " times" << endl;
++map_it;
}
set 容器只是单纯的键的集合。
除了两种例外情况,set 容器支持大部分的 map 操作,包括下面几种:
• 第 10.2 节列出的所有通用的容器操作。
• 表 10.3 描述的构造函数。
• 表 10.5 描述的 insert 操作。
• 表 10.6 描述的 count 和 find 操作。
• 表 10.7 描述的 erase 操作。
set 不支持下标操作符,而且没有定义 mapped_type 类型。在 set 容器中,value_type 不是 pair 类型,而是与 key_type 相同的类型。它们指的都是 set 中存储的元素类型。
vector ivec;
set iset(ivec.begin(), ivec.end());
可使用 insert 操作在 set 中添加元素:
set set1; // empty set
set1.insert("the"); // set1 now has one element
另一种用法是,调用 insert 函数时,提供一对迭代器实参,插入其标记范
围内所有的元素。
set2.insert(ivec.begin(), ivec.end()); // set2 has 10 elements
set 容器不提供下标操作符。为了通过键从 set 中获取元素,可使用 find 运算。 如果只需简单地判断某个元素是否存在,同样可以使用 count 运算,返回 set 中该键对应的元素个数(0或1)。
iset.find(1) // returns iterator that refers to the el ement with key == 1
正如不能修改 map 中元素的键部分一样,set 中的键也为 const。在获得
指向 set 中某元素的迭代器后,只能对其做读操作,而不能做写操作:
set::iterator set_it = iset.find(1);
*set_it = 11; // error: keys in a set are read-only
cout << *set_it << endl; // ok: can read the key
由于键不要求是唯一的,因此每次调用 insert 总会添加一个元素(不管key是否相同都添加)。
带有一个键参数的 erase 版本将删除拥有该键的所有元素,并返回删除元素的个数。而带有一个或一对迭代器参数的版本只删除指定的元素,并返回 void 类型:
// erase all elements with this key; returns number of elements removed
multimap::size_type cnt =
authors.erase(search_item);
关联容器 map 和 set 的元素是按顺序存储的,如果某个键对应多个实例,则这些实例在容器中将相邻存放。
sz_type entries = authors.count(search_item);
multimap::iterator iter =
authors.find(search_item);
for (sz_type cnt = 0; cnt != entries; ++cnt, ++iter)
cout << iter->second << endl; // print each title
可用于普通的 map 和 set 容器,但更常用于 multimap 和 multiset。所有这些操作都需要传递一个键,并返回一个迭代器。
若该键没有关联的元素,则 lower_bound 和 upper_bound 返回相同的迭代器:都指向同一个元素或同时指向 multimap 的超出末端位置。
equal_range 函数返回存储一对迭代器的 pair 对象。如果该值存在,则 pair 对象中的第一个迭代器指向该键关联的第一个实例,第二个迭代器指向该键关联的最后一个实例的下一位置。
关联容器的元素按键排序和访问。关联容器支持通过键高效地查找和读取元素。键的使用,使关联容器区别于顺序容器,顺序容器的元素是根据位置访问的。
map 和 multimap 类型存储的元素是键-值对。它们使用在 utility 头文件中定义的标准库 pair 类,来表示这些键-值对元素。对 map 或multimap 迭代器进行解引用将获得 pair 类型的值。pair 对象的 first成员是一个 const 键,而 second 成员则是该键所关联的值。set 和multiset 类型则专门用于存储键。在 map 和 set类型中,一个键只能关联一个元素。而 multimap 和 multiset类型则允许多个元素拥有相同的键。
关联容器共享了顺序容器的许多操作。除此之外,关联容器还定义一些新操作,并对某些顺序容器同样提供的操作重新定义了其含义或返回类型,这些操作的差别体现了关联容器中键的使用。
关联容器的元素可用迭代器访问。标准库保证迭代器按照键的次序访问元素。begin 操作将获得拥有最小键的元素,对此迭代器作自增运算则可以按非降序依次访问各个元素。