11. 关联容器(重点)
关联容器中的元素是按关键字来保存和访问的。map 中的元素是一些关键字-值(key-value)对:关键字起到索引的作用,值则表示与索引相关联的数据。set 中每个元素只包含一个关键字;set 支持高效的关键字查询操作-检查一个给定关键字是否在 set 中。允许重复关键字的容器的名字中都包含单词 multi;不保持关键字按顺序存储的容器的名字都以单词 unordered 开头。
11.1 使用关联容器
map 是关键字-值对的集合。set 就是关键字的简单集合。类似顺序容器,关联容器也是模板。
当从 map 中提取一个元素时,会得到一个 pair 类型的对象,简单来说,pair 是一个模板类型,保存两个名为 first 和 second 的(公有)数据成员。map 所使用的 pair 用 first 成员保存关键字,用 second 成员保存对应的值。
若元素很小(例如 int),大致数量预先可知,在程序运行过程中不会剧烈变化,大部分情况下只在末尾添加或删除需要频繁访问任意位置的元素,则 vector 可带来最高的效率。若需要频繁在头部和尾部添加或删除元素,则 deque 是最好的选择。
如果元素较大(如大的类对象),数量预先不知道,或是程序运行过程中频繁变化,对元素的访问更多是顺序访问全部或很多元素,则 list 很适合。
map 很适合对一些对象按它们的某个特征进行访问的情形。如按学生名字来查询学生信息。
set 是集合类型。当需要保存特定的值集合-通常是满足/不满足某种要求的值集合。
11.2 关联容器概述
关联容器支持普通容器操作,但不支持顺序容器的位置相关操作。关联容器的迭代器都是双向的。
容器 multimap 和 multiset 都允许多个元素具有相同的关键字。
对于有序容器-map、multimap、set 以及 multiset,关键字类型必须定义元素比较的方法。所提供的操作必须在关键字类型上定义一个严格弱序。
当用 decltype 来获得一个函数指针类型时,必须加上一个 * 来支出我们要使用一个给定函数类型的指针。
一个 pair 保存两个数据成员。类似容器,pair 是一个用来生成特定类型的模板;与其他标准库类型不同,pair 的数据成员是 public 的,两个成员分别命名为 first 和 second。
map 的元素是 pair。创建 map 的三种方法:
data.push_back(pair<string, int> (s, v));
//情景:将 string 和 int 存入一个 pair 中,pair 保存在一个 vector 中。data.push_back({s, v});
//列表初始化data.push_back(make_pair(s, v));
//使用 make_pair
11.3 关联容器操作
当解引用一个关联容器迭代器时,我们会得到一个类型为容器的 value_type 的值的引用。一个 map 的 value_type 是一个 pair,我们可以改变 pair 的值,但不能改变关键字成员的值。set 的迭代器是 const 的。
当使用一个迭代器遍历一个 map、multimap、set 或 multiset 时,迭代器按关键字升序遍历元素。我们通常不对关联容器使用泛型算法,关联容器可用于只读取元素的算法。
关联容器的 insert 成员向容器中添加一个元素或一个元素范围。
对一个 map 进行 insert 操作时,必须记住元素类型是 pair。
word_count.insert({word, 1});
//向 word_count 插入 word 的 4 种方法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));
insert(或 emplace)返回的值依赖于容器类型和参数。对于不包含重复关键字的容器,添加单一元素的 insert 和 emplace 版本返回一个 pair,告诉我们插入操作是否成功。pair 的 first 成员是一个迭代器,指向具有给定关键字的元素;second 成员是一个 bool 值,指出元素是否插入成功还是已经存在于容器中。
对于重复关键字的容器,接受单个元素的 insert 操作返回一个指向新元素的迭代器。
关联容器提供一个额外的 erase 操作,它接受一个 key_value 参数。此版本删除所有匹配给定关键字的元素(如果存在的话),返回实际删除的元素的数量。
我们不能对一个 multimap 或一个 unordered_multimap 进行下标操作,因为这些容器中可能有多个值与一个关键字相关联。
由于下标运算符可能插入一个新元素,我们只可以对非 const 的 map 使用下标操作。使用一个不在容器中的关键字作为下标,会添加一个具有此关键字的元素到 map 中。
当对一个 map 进行下标操作时,会获得一个 mapped_type 对象;但当解引用一个 map 迭代器时,会得到一个 value_type 对象。当然,map 下标运算符返回的是一个左值。
lower_bound 和 upper_bound 不适用于无序容器;下标和 at 操作只适用于非 const 的 map 和 unordered_map。
使用下标运算符有一个严重的副作用:如果关键字还未在 map 中,下标操作会插入一个具有给定关键字的元素。这种行为是否正确完全依赖于我们的预期是什么。
如果一个 multimap 或 multiset 中有多个元素具有给定关键字,则这些元素在容器中会相邻存储。
find 查找关键字在容器中出现的位置,而 count 则还会统计关键字出现的次数。
如果关键字在容器中,lower_bound 返回的迭代器将指向第一个具有给定关键字的元素,而 upper_bound 返回的迭代器则指向最后一个匹配给定关键字的元素之后的位置。如果元素不在 multimap 中,则 lower_bound 和 upper_bound 会返回相等的迭代器-指向一个不影响排序的关键字插入位置。因此,用相同的关键字调用 lower_bound 和 upper_bound 会得到一个迭代器范围,表示所有具有该关键字的元素的范围。
11.4 无序容器
无序关联容器不是使用比较运算符来组织元素,而是使用一个哈希函数和关键字类型的 == 运算符。
无序容器在存储上组织为一组桶,每个桶保存零个或多个元素;无序容器的性能依赖于哈希函数的质量和桶的数量和大小。对于相同的参数,哈希函数必须总是产生相同的结果。理想情况下,哈希函数还能将每个特定的值映射到唯一的桶。但是,将不同的关键字的元素映射到相同的桶也是允许的。
默认情况下,无序容器使用关键字类型 == 运算符来比较元素,他们使用一个 hash 类型的对象来生成每个元素的哈希值。但是,我们不能直接定义关键字类型为自定义类类型的无序容器。
无序版本通常性能更好,使用也更为简单。有序版本的优势是维护了关键字的序。当元素的关键字类型没有明显的序关系,或是维护元素的序代价非常高时,无序容器非常有用。但当应用要求必须维护元素的序时,有序版本就是唯一的选择。
待完善…