1. 关联容器
🌰关联容器
🔥11.0 概述
关联容器中的元素
按关键字
来保存和访问顺序容器中的元素按他们在
容器中的位置
来保存和访问关联容器与顺序容器许多行为相同,但是有着根本不同,不同之处反应关键字作用
关联容器支持高效的关键字查找和访问
关联容器包括 map 和 set。
头文件定义
map
和multimap
在头文件 map 中,set
和multiset
在头文件 set 中,无序 map 和无序 set 分别在头文件 unordered_map 和 unordered_set 中。
🔥11.1 使用关联容器
使用 map
map 一般用来保存关键字和值的映射,例如关键字是姓名,值是电话,爱好等。
map 可以使用范围 for 循环,得到的结果是pair
使用 set
set 通常用来判断一个值是否存在
set 可以使用范围 for 循环
🔥11.2 关联容器概述
所有的有序、无序关联容器都支持下面这些普通容器的公共操作。
关联容器不支持顺序容器的位置相关的操作,例如
push_front,push_back
等关联容器中的元素是根据关键字存储的。关联容器的迭代器都是双向的。
关联容器特有操作
🎃11.2.1 定义关联容器
需要指定元素类型。
关联容器的初始化可以使用
直接初始化(使用小括号)
、列表初始化(使用花括号)
、拷贝初始化(使用等号)
。使用迭代器范围进行直接初始化时,
如果迭代器范围中有重复关键字,生成的 set 和 map 会自动去除重复的元素。
值初始化
除了三种构造函数外,关联容器可以进行
值初始化
。初始化器必须能转换为容器中元素的类型。
注意:初始化 map 时需要将每个关键字-值
包围在花括号中{key-value}
初始化 multiset 和 multimap
一个 map 和 set 中的关键字必须是
唯一
的,但是对于 multiset 和 multmap 可以有多个相同的关键字
。使用
迭代器范围进行直接初始化
时,无论有没有重复关键字,都会生成包含所有元素的 multiset 和 multimap。
🎃11.2.2 关键字类型要求
对于有序容器,关键字类型必须定义元素比较的方法。
默认是<
。
有序容器关键字类型
对于有序容器,关键字类型必须定义元素比较的方法。
默认是<
。可以使用如 vector 等容器的迭代器来作为有序容器的关键字。
重载了 < 运算符的类可以直接用作关键字。
可以向算法提供一个
自己定义的比较操作
,操作必须在关键字类型上定义一个严格弱序
两个关键字不能同时”小于等于“对方。
A < B, B < C 则 A 必须小于 C(传递性)
如果两个关键字互不”小于等于“对方,那么两个就是等价的。容器将它们看做相等。
注意:当俩个关键字是等价的,容器会将这俩个看做相等,当用作 map 的关键字时,只能有一个元素与这俩个关键字关联,我们可以用俩着中的任意一个来访问
使用关键字类型的比较函数
当自己定义了比较操作,
必须在定义关联容器时指出来
自定义的操作类型(函数指针类型)应在尖括号中紧跟元素类型给出。并将函数名作为参数传递给构造函数。
比较函数应该返回 bool 值,两个参数的类型应该都是容器的关键字类型。当第一个参数小于第二个参数时返回真。
注意:当使用 decltype 获取函数指针时要加上 * 运算符。
🎃11.2.3 pair 类型
在 utility 头文件中定义。
一个 pair 保存两个数据成员,两个类型不要求一样。
pair 定义
pair 的默认构造函数对数据成员进行
值初始化
,因此 string,vector 等类型会默认为空,int 会默认为 0。
创建 pair 类型的返回值
如果一个函数返回一个 pair,可以对返回值进行列表初始化或隐式构造返回值
🔥11.3 关联容器操作
关联容器还有特殊的类型别名
注意
set 的 key_type 类型不是常量,pair 的 first 成员也不是常量,只有 map 的 value_type 中的 first 成员是常量。
由于不能改变一个元素的关键字,因此这些 pair 的关键字部分是
const
的
🎃11.3.1 关联容器迭代器
解引用一个关联容器迭代器时,会得到一个类型为容器的
value_type
的值的引用。对于 map 来说 value_type 是一个 pair 类型,first 成员保存 const 的关键字,second 成员保存值
注意:
一个 map 的 value_type 是一个 pair,可以改变 pair 的值,但不能改变关键字成员的值
set 的迭代器是 const
set 的关键值与 map 的关键值一样,都是不能改变的。
可以用 set 的迭代器读取元素值,但不能修改。
遍历关联容器
map 和 set 都支持 begin 和 end 操作
遍历关联容器:使用 begin 和 end,遍历 map、multimap、set、multiset 时,迭代器按关键字升序遍历元素。
关联容器和算法
一般不对关联容器使用泛型算法,因为
cosnt
这一特性意味着不能将关联容器传递给修改或重排容器元素的算法。当对关联容器使用泛型算法时,一般要么把它作为源序列,要么把它作为目的序列。比如从关联容器拷贝元素,向关联容器插入元素等。
要是用关联容器所特有的
find
算法
🎃11.3.2 添加元素
由于map和set(以及对应的无序类型)包含不重复的关键字,因此往容器中插入一个存在的元素对容器没有影响
像 map 中添加元素
关联容器添加元素一般使用 insert 成员,可以
添加一个元素
也可以添加一个元素范围
,或者初始化列表。
使用 emplace 添加元素
检查 insert 的返回值
insert 返回值依赖于容器的类型和参数
对于不重复的map和set,添加的单一元素的 insert 和 emplace 都返回一个 pair,pair 内是具有给定关键字的元素的迭代器和一个 bool 值
对于不重复的map和set,添加多个元素都返回 void在向 map 或 set 添加元素时,检测 insert 的返回值可以很有用,要灵活使用。
向 multiset 或 multimap 添加元素
在 multiset 或 multimap 上调用 insert 总会插入元素。
插入单个元素的 insert 返回一个指向新元素的迭代器。
🎃11.3.3 删除元素
关联容器定义了三个版本的 erase,其中俩个与顺序容器相似的版本(通过传递迭代器或一个迭代器对来删除一个元素或一个元素范围,返回 void)
与顺序容器相似版本:注意顺序容器会返回删除元素后一个元素的迭代器,
而这里的 erase 返回 void
额外版本:输入参数为关键字(key_value 注意不是关键字的迭代器),返回删除的元素数量,对于非重复关键字的容器,
返回值总是 1 或 0。
删除关联容器的最后一个元素
遍历容器删除元素
🎃11.3.4 map 的下标操作
map 和 unordered_map 都支持下标操作和对应的 at 函数。
set 类型则不支持下标。multimap 和 unordered_multimap 不支持下标操作。
如果关键字不再 map 中,会创建一个元素并插入到 map 中,关联值将进行值初始化。
注意:
因为关联值是值初始化,所以在单词计数程序中,可以直接 map[word]++ ,不必特意插入元素。
map 的下标操作只能返回非常量引用(不同于顺序容器的下标操作),如果 map 本身是常量,则无法使用下标访问元素,这时要用 at() 函数。
使用下标操作的返回值
与 vector 和 string 不同,map 下标运算符返回的类型与解引用 map 迭代器得到的类型不同
解引用一个 map 迭代器会得到一个
value_type对象
,对 map 进行下标操作得到一个mapped_type
对象
🎃11.3.5 访问与查找元素
访问元素
map 可以通过下标或 at() 函数访问元素。
set 只能通过迭代器来访问元素。
访问 map/set 的最后一个元素:m.rbegin() 或 --m.end()。
查找元素
关联容器查找一个指定元素的方法有多种。
一般 find
是最佳选择。对于不允许重复关键字
的容器,find 和 count 差别不大,对于允许重复关键字的容器
,count 会统计有多少个元素有相同的关键字
。
注意
lower_bound 和 upper_bound 不适用于无序容器。
下标和 at 操作只适用于非 const 的 map 和 unordered_map。
检查元素是否存在
检查元素是否存在用 find 或 count。
在 multimap 或 multiset 中查找元素
在不允许重复关键字的关联容器查找一个元素简单,要在 multimap 或 multiset 中查找所有具有给定关键字的元素比较麻烦。
如果一个 mutimap 或 mutiset 中有多个元素具有给定关键字,则这些元素在容器中会相邻存储
三种方法:
使用 find 和 count 配合
,找到第一个关键字为 k 的元素和所有关键字为 k 的元素数目,遍历完成。使用 lower_bound 和 upper_bound 配合
。注意当关键字 k 不存在时,这两个函数返回相同的迭代器,可能是尾后迭代器也可能不是。使用 equal_range
。最直接的方法
🔥11.4 无序容器
有序容器使用比较运算符来组织元素;无序容器使用
哈希函数
和关键字类型的==运算符
。无序容器用于
关键字类型不好排序的情况。
无序容器在存储上组织为
一组桶(bucket)
,每个桶保存零个或多个元素
。无序容器使用一个哈希函数将元素映射到桶
。
使用无序容器
无序容器也有 find,insert,迭代器等操作。
在大多数情况下,可以用无序容器替换对应的有序容器,反之亦然。但是注意无序容器中元素未按顺序存储。
管理桶
无序容器在存储上组织为一组桶,
每个桶保存零个或多个元素
。无序容器使用
哈希函数
将元素映射到桶,并将具有一个特定哈希值的所有元素保存在相同的桶中。如果容器允许重复关键字,那所有具有相同关键字的元素也都在同一个桶中。不同关键字的元素也可能映射到相同的桶。对于相同的参数,哈希函数总是产生相同的结果。
当一个桶中保存了多个元素,需要顺序搜索这些元素来查找想要的那个。计算一个元素的哈希值和在桶中搜索通常都很快。
无序容器对关键字的要求
默认情况下,
无序容器使用 == 运算符比较关键字,使用用一个 hash<key_type> (hash 模板)类型的对象来生成每个元素的哈希值。
标准库为内置类型(包括指针)和 string、智能指针等都定义了 hash,
因此内置类型,string 和智能指针类型都能直接用来作为无序容器的关键字。
对于自定义的类类型,不能直接用来作为无序容器的关键字
,因为他们不能直接使用 hash 模板,除非提供自己的 hash 模板版本。
评论