第11章 关联容器
关联容器类型 | 描述 |
---|---|
map | 保存关键字-值对 |
multimap | 关键字可重复的map |
set | 保存关键字 |
multiset | 关键字可重复的set |
unordered_map | 哈希函数组织的map |
unordered_multimap | 哈希函数组织的,关键字可重复的map |
unordered_set | 哈希函数组织的set |
unordered_multiset | 哈希函数组织的,关键字可重复的set |
- 关联容器与顺序容器的根本不同:关联容器按关键字来保存和访问元素;顺序容器按位置保存和访问元素。
- map是关键字-值对集合,关键字用于索引,值是与索引相关联的数据。
- set是关键字集合,支持高效的关键字查询操作。
- map和multimap定义在头文件map;set和multiset定义在头文件set;unordered_map、unordered_multimap定义在头文件unordered_map;unordered_set、unordered_multiset定义在头文件unordered_set。
- multi前缀代表元素的关键字可以重复,unordered前缀代表无序关联容器。
11.1 使用关联容器
- map中的元素是pair,first保存关键字,second保存对应的值。
- 定义map需指定关键字和值的类型,定义set需指定关键字类型。
11.2 关联容器概述
- 关联容器按关键字存储元素,故不支持顺序容器的位置相关的操作,如push_front、push_back等。
- 关联容器不支持接受一个元素值和一个数量值的构造函数或插入操作。
- 无序关联容器不支持>、>=、<、<=。
- assign仅支持顺序容器,不支持关联容器。
- 关联容器的迭代器都是双向迭代器。
11.2.1 定义关联容器
- 定义map需指定关键字和值的类型,定义set只需指定关键字类型。
- 关联容器可用同类型容器来初始化,或一个可转换的值范围初始化。
11.2.2 关键字类型的要求
- 对于有序关联容器(map、multimap、set、multiset),关键字类型必须定义元素比较的方法,默认使用<运算符。
- 有序关联容器的比较函数必须是严格弱序。<是严格弱序,<=不是严格弱序。
- 两个关键字等价,即任何一个都不“小于”对方。
- 严格弱序的比较函数F具备的基本性质:
-- 若F(k1,k2)=true,则F(k2,k1)=false;
-- 若F(k1,k2)=true, F(k2,k3)=true,则F(k1,k3)=ture;
-- 若F(k1,k2)=false, F(k2,k1)=false,则k1=k2。若k1=k2,k2=k3,则k1=k3。 - 可以使用严格弱序的自定义比较函数替代<运算符。
bool compareIsbn(const Sales_data &lhs, const Sales_data &rhs)
{
return lhs.isbn() < rhs.isbn();
}
multiset<Sales_data, decltype(compareIsbn) *> bookstore(compareIsbn);
multiset<Sales_data, bool (*)(const Sales_data &lhs, const Sales_data &rhs)> bookstore(compareIsbn);
11.2.3 pair类型
pair操作 | 描述 |
---|---|
pair<T1,T2> p; | p是一个数据成员类型为T1和T2的pair 对first和second值初始化 |
pair<T1,T2> p(v1, v2); pair<T1,T2> p = {v1, v2}; |
p是一个数据成员类型为T1和T2的pair 对first,second用v1, v2初始化 |
make_pair(v1, v2) | 返回一个用v1和v2初始化的pair pair的类型由v1和v2的类型推断出来 |
p.first p.second |
返回p的名为first的public数据成员 返回p的名为second的public数据成员 |
p1 relop p2 | 关系运算符(<、<=、>、>=)按字典序定义 |
p1 == p2 p1 != p2 |
相等性判断利用元素的==运算符实现。 |
- pair定义在头文件utility。
- pair类似于容器,是一个用来生成特定类型的模板。一个pair保存两个public数据成员,分别是first和second。
- map的元素是pair。
- 比较pair:若 p1.first<p2.first,或者 !(p2.first<p1.first) && p1.second<p2.second,则p1<p2。若p1.first=p2.first && p1.second=p2.second,则p1=p2。
- 在早期C++版本的函数中,不允许用花括号包围的初始化器来返回pair,必须显示构造返回值。
11.3 关联容器操作
关联容器额外的类型别名 | 描述 |
---|---|
key_value | 关键字类型 |
mapped_type | 值类型,只适用于map |
value_type | 对于set,与const key_type相同 对于map,为pair<const key_type, mapped_type> |
11.3.1 关联容器迭代器
- 关联容器迭代器指向类型为value_type的元素。
- 对于map,value_type是pair<const key_type, mapped_type>,其first成员保存const的关键字,second成员保存值。
- 对于set,value_type是const key_type。iteraroe与const_iterator都只能读取而不可修改set中的元素。
- 使用迭代器遍历map、multimap、set、multiset时,是按关键字升序遍历。
- 关联容器可用于只读算法;不宜对关联容器使用泛型搜索算法(如find等),因为关联容器中的元素可以直接通过关键字快速查找;不能将关联容器传递给修改或重排容器元素的算法,因为set的value_type是const key_type,map的value_type是pair<const key_type, mapped_type>,const不可修改。
- 通常不对关联容器使用泛型算法。在实际编程中,若真要对关联容器使用泛型算法,则关联容器要么是一个源序列,要么是一个目的位置。
11.3.2 添加元素
关联容器insert操作 | 描述 |
---|---|
c.insert(v) c.emplace(args) |
1. v是value_type类型的对象; args用来构造一个元素。 2. 对于map和set,只有当关键字不存在时才会插入或构造元素。 返回pair,first是一个迭代器,指向具有给定关键字的元素, second是bool值,表示是否插入成功。 3. 对于multimap和multiset,总会插入或构造给定元素。 返回一个指向新元素的迭代器。 |
c.insert(p, v) c.emplace(p, args) |
1. p是迭代器,指示从p开始搜索新元素的插入位置。 2. 返回一个迭代器,指向具有给定关键字的元素。 |
c.insert(b, e) c.insert(il) |
1. b和e是迭代器,表示一个value_type类型值的范围; il是该值的花括号列表。 2. 返回void。 3. 对于map和set,只插入关键字不在c中的元素。 4. 对于multimap和multiset,则会插入范围内的所有元素。 |
- 对于非multi的关联容器,插入一个元素前需先看其关键字是否存在。若关键字已存在,则什么也不做;若关键字不存在,则插入该元素。
- map的元素类型是pair,添加元素时须构建pair对象。
11.3.3 删除元素
关联容器删除元素 | 描述 |
---|---|
c.erase(k) | 从c中删除所有关键字为k的元素 返回一个size_type值,指出删除的元素个数 |
c.erase(p) | 从c中删除迭代器p指向的元素 p必须指向c中一个真实存在的元素,不能等于c.end() 返回一个指向p之后元素的迭代器,若p指向尾元素,则返回c.end() |
c.erase(b, e) | 删除[b,e)内的元素 返回e |
11.3.4 map的下标操作
map的下标操作 | 描述 |
---|---|
c[k] | 返回关键字为k的元素 若k不在c中,则添加一个关键字为k的元素,并对元素的值进行值初始化 |
c.at(k) | 访问关键字为k的元素 若k不在c中,则抛出out_of_range异常 |
- 只有map和unordered_map支持下标运算符和at函数,因为set只有关键字,无对应的值;multimap和unodered_multimap一个关键字可能对应多个值。
- 只能对非const的map和unordered_map使用下标操作,因为下标运算符可能会插入一个新元素。
- 通常情况,迭代器解引用返回的类型与下标运算符返回的类型一样,但map不同:解引用一个map迭代器得到一个value_type对象,对map进行下标操作得到一个mapped_type对象。
- 下标运算符返回的结果是左值。
11.3.5 访问元素
关联容器查找元素操作 | 描述 |
---|---|
c.find(k) | 返回一个迭代器,指向第一个关键字为k的元素 若k不在c中,则返回c.end() |
c.count(k) | 返回关键字为k的元素的个数 对于非multi的关联容器,返回值只能是0或1 |
c.lower_bound(k) | 返回一个迭代器,指向第一个关键字大于等于k的元素 |
c.upper_bound(k) | 返回一个迭代器,指向第一个关键字大于k的元素 |
c.equal_range(k) | 返回一个pair,[first, second)表示关键字等于k的元素的范围 |
- lower_bound和upper_bound不适用于unordered的关联容器。
- 若k在c中,lower_bound返回的迭代器指向第一个具有给定关键字的元素,upper_bound返回的迭代器指向最后一个匹配关键字的元素之后的位置;若k不在c中,lower_bound和upper_bound返回相等的迭代器——指向一个不影响排序的关键字插入位置
- equal_range返回一个pair对象,[first, second)表示关键字等于k的元素的范围,若k在c中,first指向第一个匹配关键字的元素,second指向最后一个匹配关键字的元素之后的位置;若k不在c中,first=second,都指向关键字可以插入的位置。
11.3.6 一个单词转换的map
- c[key] = value 与 c.insert({key,value}) 的区别:对于multi的关联容器插入多个关键字相同的元素,前者取最后一次插入的元素,后者取第一次插入的元素。
11.4 无序容器
桶接口 | 描述 |
---|---|
c.bucket_count() | 正在使用的桶的数目 |
c.max_bucket_count() | 容器可容纳的最多的桶的数量 |
c.bucket_size(n) | 第n个桶中元素的个数 |
c.bucket(k) | 关键字为k的元素对应的桶 |
桶迭代 | 描述 |
---|---|
local_iterator | 用于访问桶中元素的迭代器类型 |
const_local_iterator | 桶迭代器的const版本 |
c.begin(n), c.end(n) | 第n个桶中的首元素迭代器和尾后元素迭代器 |
c.cbegin(n), c.cend(n) | 第n个桶中const版本的首元素迭代器和尾后元素迭代器 |
哈希策略 | 描述 |
---|---|
c.local_factor() | 每个桶的平均元素数量,返回float |
c.max_load_facor() | c试图维护的平均桶大小,返回float c会在需要时添加新桶使local_factor()<=max_load_facor |
c.rehash(n) | 重组存储,使得bucket_count>=n,且bucket_count>size/max_load_facor |
c.reserver(v) | 重组存储,使得c可以保存n个元素且不必rehash |
- 有序关联容器通过比较运算符组织元素;无序关联容器使用哈希函数和==运算符组织元素。
- 若元素没有明显的序关系,或维护元素的序的代价高昂,则建议使用无序容器。
- 通常可用一个无序容器替换对应的有序容器,反之亦然。由于存储顺序的不同,两者的输出通常不同。
- 无序容器是桶集合,每个桶可保存零个或多个元素。访问元素时,容器先计算元素的哈希值,再搜索哈希值对应的桶。
- 若关键字是内置类型(包括指针类型)、string和智能指针类型,则可以直接定义对应类型的无序容器。
- 不能直接定义关键字类型是自定义类型的无序容器。对于自定义类型,hash模板不能直接使用,必须提供自定义的hash模板版本。
- 在无序或有序容器中,具有相同关键字的元素都是相邻存储的。
// 使用一个标准库hash类型对象来计算isbn成员的哈希值,该hash类型建立在string类型之上
size_t hasher(const Sales_data &sd)
{
return hash<string>()(sd.isbn());
}
// 通过比较isbn来比较两个Sales_data
bool eqOp(const Sales_data &lhs, const Sales_data &rhs)
{
return lhs.isbn() == rhs.isbn();
}
// 别名声明
using SD_multiset = unordered_multiset<Sales_data, decltype(hasher)*, decltype(eqOp)*>
// 定义自定义类型的无序容器
// 参数:桶大小,哈希函数指针,相等性判断运算符指针
SD_multiset bookstores(42, hasher, eqOp)
// 若自定义类型已定义==运算符,则可以只重载哈希函数
unordered_set<Foo, decltype(FooHash)*> fooSet(10, FooHash);