📔 C++ Primer 0x0B 学习笔记

​更好的阅读体验(实时更新与修正)​

  • 关联容器支持高效的关键字查找和访问,​​map​​和​​set​​是两个主要的关联容器
  • 标准库提供8个关联容器
  • 按关键字有序保存元素
  • ​map​​:关联数组,保存关键字-值对
  • ​set​​:关键字即值,即值保存关键字的容器,底层实现是红黑树
  • ​multimap​​​:关键字可重复出现的​​map​
  • ​multiset​​​ :关键字可重复出现的​​set​
  • 无序集合
  • ​unordered_map​​​:用哈希函数组织的​​map​
  • ​unordered_set​​​:用哈希函数组织的​​set​
  • ​unordered_multimap​​​:哈希组织的​​map​​ ,关键字可重复出现
  • ​unordered_multiset​​​:哈希组织的​​set​​,关键字可重复出现

11.1 使用关联容器

  • 类似顺序容器,关联容器也是模板,使用的时候要明确关键字和值的类型
  • ​set​​可以去重
  • 可以对关联容器的元素进行列表初始化

11.2 关联容器概述

  • 关联容器不支持顺序容器的位置相关的操作如​​push_front​​​或​​push_back​
  • 关联容器的迭代器都是双向的

11.2.1 定义关联容器

  • 定义一个​​map​​,必须既指明关键字类型又指明值类型
  • 定义一个​​set​​​,只需指明关键字类型,因为​​set​​中没有值
  • 每个关联容器都定义了一个默认构造函数,创建一个指定类型的空容器
  • 关联容器初始化:同类型容器的拷贝,一个值范围,初始化列表

11.2.2 关键字类型的要求

  • 有序容器要求所提供的操作必须在关键字类型上定义一个严格弱序(可以自己定义)​​list​​迭代器不支持比较运算
  • 为了使用自定义的操作,必须在定义关联容器类型时提供此操作的类型

11.2.3 pair 类型

  • ​pair​​​类型保存两个数据成员(​​first​​​和​​second​​​),类似容器。定义在头文件​​utility​​中
  • ​pair​​的默认构造函数对数据进行值初始化
  • 函数要返回一个​​pair​​​可以对返回值进行列表初始化​​return {fisrt,second}​​​,早期​​C++​​不允许
  • 可以使用​​make_pair​​​来产生​​pair​​对象

11.3 关联容器操作

关联容器定义了几个类型别名

  • ​key_type​​此容器类型的关键字类型
  • ​mapped_type​​​每个关键字关联的类型,只适用于​​map​
  • ​value_type​​​对于​​set​​​和​​key_value​​​相同,对于​​map​​​为​​pair<const key_type,mapped_type>​

11.3.1 关联容器迭代器

  • 当解引用一个关联容器迭代器,我们会得到一个类型为容器的​​value_type​​ 的值的引用
  • 对于​​map​​​而言,​​value_type​​​是一个​​pair​​​类型,​​first​​​保存​​const​​​的关键字,​​second​​​保存值。我们可以改变​​pair​​的值,但不能改变关键字成员的值
  • ​set​​​的迭代器是​​const​​​的,可以用一个​​set​​迭代器来读取元素的值,但不能修改
  • 当使用一个迭代器遍历一个​​map​​​、​​multimap​​​、​​set​​​或​​multiset​​时,迭代器按关键字升序遍历元素
  • 我们通常不对关联容器使用泛型算法,关键字是​​const​​这一特性意味着不能将关联容器传递给修改或重排容器元素的算法;关联容器可用于只读取元素的算法,但是这类算法很多都要搜索序列,关联容器不支持通过它们的关键字进行快速查找,所以也没什么用。
  • 使用关联容器专用的​​find​​​成员会比调用泛型算法的​​find​​快得多
  • 实际编程中,我们对一个关联容器使用算法要么是将它当作源序列,要么当作一个目的位置,可以使用泛型算法的​​copy​​将元素从一个关联容器拷贝到另一个序列

11.3.2 添加元素

  • 由于​​map​​​和​​set​​(以及对应的无序类型)包含不重复的关键字,因此插入一个已存在的元素对容器没有任何影响
  • 向一个​​map​​​进行​​insert​​​操作时,必须记住元素是​​pair​
  • ​insert​​​或​​emplace​​​的返回值依赖于容器类型和参数,对于不包含重复关键字的容器,添加单一元素的​​insert​​​和​​emplace​​​返回一个​​pair​​​告诉我们插入是否成功,​​first​​​成员是一个迭代器指向具有给定关键字的元素,​​second​​​成员是个​​bool​​值,告诉我们插入成功还是已经在容器中了。如果是可重复类型的只返回一个迭代器

11.3.3 删除元素

关联容器提供三个版本的​​erase​

  • ​c.erase(k)​​​,从​​c​​​中删除每个关键字为​​k​​​的元素,返回一个​​size_type​​值,指出删除的元素数量,这个是顺序容器没有的
  • ​c.erase()​​​,从​​c​​​中删除迭代器​​p​​​指定的元素,​​p​​​必须指向一个真实元素不能是​​c.end()​​​,返回一个指向​​p​​之后元素的迭代器
  • ​c.erase(b,e)​​​,删除迭代器对表示的范围中的元素,返回​​e​

11.3.4 map的下标操作

  • ​set​​​,​​multimap​​​,​​unordered_multimap​​类型不支持下标
  • 对一个​​map​​​使用下标操作,其行为与数组或​​vecotr​​​上的下标操作很不相同,使用一个不在容器中的关键字作为下标,会添加一个具有此关键字的元素到​​map​​中
  • ​c[k]​​​返回关键字为​​k​​​的元素,如果​​k​​​不在​​c​​​中则添加一个关键字为​​k​​的元素,对其值进行值初始化
  • ​c.at(k)​​​访问关键字为​​k​​​的元素,带参数检查,如果​​k​​​不再​​c​​​中,跑出一个​​out_of_range​​异常
  • 对​​map​​​进行下标操作会得到一个​​mapped_type​​​对象,解引用​​map​​​迭代器时,会得到一个​​value_type​​​对象,这与​​vector​​​、​​string​​是不同的
  • ​map​​的下标运算符返回的是一个左值,我们既可以读也可以写元素

11.3.5 访问元素

  • ​lower_bound​​​和​​upper_bound​​不适用于无序容器
  • 下标和​​at​​操作适用于非​​const​​的​​map​​和​​unordered_map​
  • ​c.find(k)​​​返回一个迭代器,指向关键字为​​k​​的元素,如果​​k​​不在容器中,则返回尾后迭代器
  • ​c.count(k)​​​返回关键字等于​​k​​的元素数量,对于不允许重复关键字的容器,返回值永远是0或1
  • ​c.lower_bound(k)​​​返回一个迭代器,指向第一个关键字不小于​​k​​的元素,如果关键字不在容器中,则lower_bound会返回关键字的第一个安全插入点(不影响容器中元素顺序的插入位置)
  • ​c.upper_bound(k)​​​返回一个迭代器,指向第一个关键字大于​​k​​的元素
  • ​c.equal_range(k)​​​返回一个迭代器​​pair​​表示关键字等于​​k​​的元素范围,若​​k​​不存在,​​pair​​的两成员均等于​​c.end()​​(两个关键字都指向可以插入的位置)
  • 对​​map​​使用​​find​​代替下标操作
  • 如果​​multimap​​或​​multiset​​中有多个元素具有给定关键字,则这些关键字会在容器中相邻存储​​find(k)​​返回一个迭代器指向第一个关键字为​​k​​的元素
  • 可以通过​​lower_bound​​和​​upper_bound​​,获得一对迭代器对形成的范围,表示具有该关键字的元素的范围,如果没有与给定关键字匹配的元素,​​lower_bound​​和​​upper_bound​​返回相同的迭代器

11.4 无序容器

  • 无需关联容器不使用比较运算符来组织元素,而是使用一个哈希函数和关键字类型的​​==​​运算符
  • 如果关键字类型固有就是无序的,或者性能测试发现问题可以用哈希技术解决,就可以使用无序容器
  • 无序容器的性能依赖于哈希函数的质量和桶的数量和大小
  • 标准库为内置类型提供了​​hash​​​模板,还为一些标准库类型如​​string​​​、智能指针类型定义了​​hash​​​,所以我们可以直接定义关键字是内置类型(包括指针)、​​string​​、智能指针类型的无序容器
  • 不能直接定义关键字类型为自定义类型的无序容器,必须提供我们自己的​​hash​​​模板版本。为了使用自定义类型作为关键字,我们可以采用另一种方法:提供函数来替代​​==​​运算符和哈希值计算函数