第9章 顺序容器
- 元素在顺序容器中的顺序与其加入容器时的位置相对应(控制元素存储和访问顺序的能力)
- 关联容器中元素的位置由元素相关联的关键字值决定
- 所有容器类都共享公共的接口,不同容器按不同方式扩展,每种容器都提供了不同性能和功能的权衡
- 容器适配器 为容器操作定义了不同接口
9.1 顺序容器概述
- 都提供快速顺序访问元素的能力
- 向容器添加或从容器中删除元素的代价
- 非顺序访问容器中元素的代价
表9.1 顺序容器类型 | |
---|---|
vector | 可变大小数组。支持快速随机访问。在尾部之外的位置插入或删除元素可能很慢 |
deque | 双端队列。支持快速随机访问。在头尾位置插入/删除速度很快。 |
list | 双向链表。只支持双向顺序访问。在list 中任何位置进行插入/删除操作速度都很快 |
forward_list | 单向链表。只支持单向顺序访问。在链表任何位置进行插入/删除操作速度都很快 |
array | 固定大小数组。支持快速随机访问。不能添加或者删除元素。 |
string | 与vector 相似的容器,但专门用于保存字符。随机访问块。在尾部插入/删除速度快 |
- 除array外都能添加/删除元素,扩张/收缩容器大小
- string/vector中间位置删除/插入元素耗时 须移动之后所有元素;添加元素可能需重新分配内存空间,每个元素都移动到新的存储空间
- list/
forward_list
任何位置添加/删除快速;不支持随机访问 - deque中间位置添加。删除代价高;两端添/删很快
array
不支持添加/删除元素/改变容器大小;forward_list
没有size操作- 确定使用哪种顺序容器
- 通常vector是最好选择
- 很多小元素/空间额外开销很重要 不要用list/forward_list
- 要求随机访问元素 vector/deque
- 容器中间位置插入/删除元素 list/forward_list
- 头尾位置插入/删除元素 不会在中间位置插入/删除操作 deque
- 只有在读取输入时才需在中间位置插入元素 随后随机访问元素
- 确认是否真的需要在容器中间位置插入元素 用sort重排可避免中间位置添加元素
- 须在中间位置插入元素 输入阶段用list 输入完成将list中内容拷贝到vector中
- 既需随机访问 又需在容器中间位置插入元素 应用中占主导位置的操作决定容器类型
- 不确定使用何种容器 使用vector/list公共操作(迭代器) ,不使用下标来避免随机访问
9.2 容器库概览
- 适用于所有容器的操作
- 仅针对顺序容器/关联容器/无序容器的操作
- 只适用于一小部分容器的操作
- 每个容器都定义在一个头文件中,均定义为模板类需提供额外信息来生成特定的容器类型,大多需额外提供元素类型信息
- 对容器可以保存的元素类型的限制
- 顺序容器 几乎任意类型元素(也可为另一容器),但某些容器操作对元素类型有特殊要求
表9.2 容器操作(所有容器类型都提供) | |
---|---|
类型别名 | |
iterator | 此容器类型的迭代器类型 |
const_iterator | 可以读取元素,但不能修改元素的迭代器类型 |
size_type | 无符号整数类型,足够保存此种容器类型最大可能容器的大小 |
difference_type | 带符号整数类型,足够保存两个迭代器之间的距离 |
value_type | 元素类型 |
reference | 元素的左值类型;和value_type & 含义相同 |
const_reference | 元素的const 左值类型,(即const value_type & ) |
构造函数 | |
C c; | 默认构造函数,构造空容器 |
C c1(c2); | 构造c2 的拷贝c1 |
C c(b, e) | 构造c ,将迭代器b 和e 指定范围内的所有元素拷贝到c |
C c(a, b, c...) | 列表初始化c |
赋值和swap | |
c1 = c2; | 将c1 中的元素替换成c2 中的元素 |
c1 = {a, b, c...} | 将c1 中的元素替换成列表中的元素(不适用于array ) |
a.swap(b) | 交换a 和b 的元素 |
swap(a, b) | 与a.swap(b) 等价 |
大小 | |
c.size() | c 中元素的数目(不支持forward_list ) |
c.max_size() | c 可保存的最大元素数目 |
c.empty() | 若c 中存储了元素,返回false ,否则返回true |
添加/删除元素 | (不适用于array 不同容器中操作接口都不同) |
c.insert(args) | 将args 中的元素拷贝进c |
c.emplace(inits) | 使用inits 构造c中的一个元素 |
c.erase(args) | 删除args 指定的元素 |
c.clear() | 删除c 中的所有元素,返回void |
关系运算符 | |
==,!= | 所有容器都支持相等/不等操作 |
<,<=,>,>= | 关系运算符(无序关联容器不支持) |
获取迭代器 | |
c.begin(),c.end() | 返回指向c 的首元素和尾元素之后位置的迭代器 |
c.cbegin(),c.cend() | 返回const_iterator |
反向容器的额外成员 | (不支持forward_list ) |
reverse_iterator | 按逆序寻址元素的迭代器 |
const_reverse_iterator | 不能修改元素的逆序迭代器 |
c.rbegin(),c.rend() | 返回指向c 的尾元素和首元素之前位置的迭代器 |
c.crbegin(),c.crend() | 返回const_reverse_iterator |
9.2.1 迭代器
- 迭代器公共接口
- 解引用运算符 访问容器中的元素
- 递增/递减运算符 从当前元素移动到下/上一元素
- 例外:
forward_list
不支持递减运算符- 算术运算 只能应用于
string/vector/dequearray
- 迭代器范围
- 一对迭代器表示
- 同一容器中的元素/尾后位置
- 左闭合区间[begin,end) [first,last)
- 可反复递增begin来到达end,end不在begin之前
- 一对迭代器表示
- 使用左闭合范围蕴含的编程假定
- 若[begin,end)构成合法迭代器
- begin,end相等,范围为空
- begin,end不等,至少含一个元素 begin指向第一个元素
- 对begin递增若干次,使得begi==end
- 可用一循环来处理元素范围(安全)
- 范围为空 退出循环
- 不为空 begin指向第一个元素(可安全解引用)
- 由于每次对begin递增 循环最终会结束
- 若[begin,end)构成合法迭代器
9.2.2 容器类型成员
- 反向迭代器
- 反向遍历容器
- ++ 得到上一个元素
- 类型别名(在泛型编程中非常有用)
- 可在不了解容器元素类型的情况下使用它
- 需元素类型 可用value_type
- 需元素类型引用 可用reference,const_reference
- 使用作用域运算符显式使用其类名
9.2.3 begin和end成员
- 生成指向容器中首元素/尾后位置的迭代器,形成包含容器所有元素的迭代器范围
- 前缀带r返回反向迭代器,前缀带c返回const迭代器
- 不以c开头的函数都是被重载过的 即const对象调用返回const_iterator,非常量对象调用返回iterator;以c开头为c++11新引入,支持auto,可获得const_iterator而不管容器的类型是什么
- 不需要写访问时,应使用cbegin和cend
9.2.4 容器定义和初始化
- 每个容器类型都定义了一个默认构造函数
- 除array外都创建了一个指定类型的空容器(可接受容器大小和元素初始值)
表9.3 容器定义和初始化 | |
---|---|
C c; | 默认构造函数。如果C是一个array,则c中元素按默认方式初始化;否则c为空 |
C c1(c2) | c1初始化为c2的拷贝。c1和c2必须是相同类型(相同容器类型&&保存相同元素类型,对于array还须相同大小) |
C c1=c2 | 同上 |
C c{a,b,c...} | c初始化为初始化列表中元素的拷贝。列表中元素的类型必须与C的元素类型相容。对于array,列表中元素数目须等于或小于array的大小,任何遗漏的元素都进行值初始化 |
C c={a,b,c...} | 同上 |
C c(b,e) | c初始化为迭代器b和e指定范围中的元素的拷贝。范围中元素的类型必须与C的元素类型相容(array不适用) |
只有顺序容器(不包含array)的构造函数才能接受大小参数 | |
C seq(n) | seq包含n个元素,这些元素进行了值初始化;此构造函数是explict的(string不适用) |
C seq(n,t) | seq包含n个初始化为值t的元素 |
- 将一个容器初始化为另一个容器的拷贝
- 直接拷贝整个容器(相同容器类型&&保存相同元素类型,对于array还须相同大小)
- 拷贝由一个迭代器对指定的元素范围(不要求容器类型相同,只要拷贝元素类型能转换成对应类型即可)(可拷贝子序列,左开右闭不包含右迭代器指向的元素)
- 列表初始化
- 显式指定容器中每个元素值
- 隐含指定容器的大小与初始值一样多的元素(除array外)
- 与顺序容器大小相关的构造函数(array除外)
- 接受一个容器大小和一个(可选)元素初始值。如不提供元素初始值,则标准库会创建一个值初始化器,(对于内置类型或具有默认构造函数的类类型,可只为构造函数提供一个容器大小参数;若元素类型没有默认构造函数,除大小参数外还须指定显式元素初始值)
- 只有顺序容器构造函数才接受大小参数,关联容器并不支持
- 标准库array具有固定大小
- 标准库array大小也是类型的一部分,定义array除指定元素类型外还要指定容器大小
array<int,42>
- array不支持普通的容器构造函数(会确定容器的大小)
- 一个默认构造的array是非空的,包含与大小一样多的元素(都被默认初始化)
- 若对array进行列表初始化,初始化数目须等于或小于array的大小,若小于则剩余元素都会进行值初始化(若元素为类类型须有一个默认构造函数)
- 可对arry进行拷贝或对象赋值(内置数组类型不行),要求 初始值类型须与要创建的容器类型相同,array还要求元素类型和大小一样
- 标准库array大小也是类型的一部分,定义array除指定元素类型外还要指定容器大小
9.2.5 赋值和swap
- 赋值运算符将左边容器中的全部元素替换为右边容器中元素的拷贝
- 赋值后两容器的大小都与右边容器的原大小相同
- 与内置数组不同,array类型允许赋值,赋值号左右运算对象须具相同的类型
- 由于右边运算对象的大小可能与左边运算对象大小不同,array不支持assign,也不允许用花括号包围的值列表进行赋值
表9.4 容器赋值运算 | |
---|---|
c1 = c2; | 将c1 中的元素替换成c2 中的元素 。c1和c2必须具有相同的类型 |
c1 = {a, b, c...} | 将c1 中的元素替换成初始值列表中元素的拷贝(array 不适用) |
swap(c1, c2) | 交换c1 和c2 的元素。c1和c2必须具有相同的类型。swap通常比从c2向c1拷贝元素快得多 |
c1.swap(c2) | 同上 |
seq.assign(b, e) | 将seq 中的元素替换成迭代器b 和e 表示范围中的元素,迭代器b 和e 不能指向seq 中的元素 |
seq.assign(il) | 将seq 中的元素替换成初始化列表il 中的元素 |
seq.assign(n, t) | 将seq 中的元素替换为n 个值是t 的元素 |
- 赋值相关运算会导致指向左边容器内部的迭代器、引用和指针失效。swap交换容器内容则不会(array/string情况除外)
- 使用assign(仅顺序容器)
- 赋值运算将将右边运算对象中所有元素拷贝到左边运算对象中要求对象具有相同的类型
- 但assign允许从一个不同但相容的类型赋值,或者从容器的一个子序列赋值
- 用参数所指定的元素替换左边容器中的所有元素
- assign参数决定了容器中含多少元素以及元素的值
- 由于旧容器被替换,传递给assign的迭代器不能指向调用assign的容器
- assign的第二个版本接受一个整型值和一个元素值,用指定数目具有相同给定值的元素替换容器中原有的元素
- 使用swap
- 交换两个相同类型容器的内容
- 除array外蒋欢两个容器内容的操作保证会很快(元素本身未交换,只是交换两个容器的内部数据结构,不对任何元素进行拷贝/删除/插入操作,可保证在常数时间内完成)
- 元素不会被移动意味着除string外,指向容器的迭代器/引用/指针在swap操作之后后不会失效,但这些元素已经属于不同的元素了
- 对string调用swap会导致迭代器/引用/指针失效
- swap交换两个array会真正交换他们的元素,交换两个array所需的时间与array中元素的数目成正比,指针/引用/迭代器所绑定的元素保持不变,但元素值已经与另一个array中对应的元素的进行了交换
- 标准库中提供成员函数与非成员函数版(在泛型编程中很重要)的swap
9.2.6 容器大小操作
- 除一个例外每个容器类型都有三个与大小相关的操作
size
返回容器中元素的数目empty
当size为0时返回true,否则falsemax_size
返回一个大于或等于该类型容器所能容纳的最大元素数的值
- forward_list支持max_size和empty,但不支持size
9.2.7 关系运算符
- 每个容器类型都支持相等运算符(==和!=)
- 除无序关联容器外 所有容器都支持关系运算符(> >= < <=)
- 关系运算符左右两边的运算对象必须是相同类型的容器 且必须保存相同类型的元素
- 比较两个容器实际上是进行元素的逐对比较(与string的类似)
- 若两容器具有相同大小且所有元素都两两对应相等,则这两个容器相等,否则不相等
- 容器大小不同 较小容器中每个元素都等于较大容器中的对应元素 则较小容器小于较大容器
- 若两容器都不是另一个容器的前缀子序列 则比较结果取决于第一个不相等的元素的比较结果
- 容器的关系运算符使用元素的关系运算符完成比较
- 只有当其元素定义了相应比较运算符时 才可以使用关系运算符来比较两个对象
9.3 顺序容器操作
- 顺序容器和关联容器的不同之处在于两者组织元素的方式
- 关系到元素如何存储/访问/添加/删除
- 9.2节为所有容器都支持的操作 本章剩余部分介绍顺序容器所特有的操作
9.3.1 向顺序容器添加元素
- 除array外 所有标准库容器都提供灵活内存管理 运行时可动态添加或删除元素来改变容器大小
- 下表中的操作会改变容器的大小,array不支持这些操作
- forward_list有自己专有版本的insert和emplace
- forward_list不支持push_back和emplace_back
- vector和string不支持push_front和emplace_front
表9.5 向顺序容器添加元素的操作 | |
---|---|
c.push_back(t) | 在c 尾部创建一个值为t 或由args 创建的元素,返回void |
c.emplace_back(args) | 同上 |
c.push_front(t) | 在c 头部创建一个值为t 或由args 创建的元素,返回void |
c.emplace_front(args) | 同上 |
c.insert(p, t) | 在迭代器p 指向的元素之前创建一个值为t 或由args 创建的元素,返回指向新添加的元素的迭代器 |
c.emplace(p, args) | 同上 |
c.insert(p, n, t) | 在迭代器p 指向的元素之前插入n 个值为t 的元素,返回指向第一个新元素的迭代器;若n 为0,则返回p |
c.insert(p, b, e) | 将迭代器b 和e 范围内的元素插入到迭代器p 指向的元素之前。b 和e 不能指向c 中的元素。返回指向新添加的第一个元素的迭代器;若范围为空,则返回p |
c.insert(p, il) | il 是一个花括号包围的元素值列表。将这些给定值插入到迭代器p 指向的元素之前;返回指向新添加的第一个元素的迭代器;若列表为空空,则返回p |
- 向一个vector、string、deque插入元素会使所有指向容器的迭代器/引用/指针失效
- 不同容器使用不同策略来分配元素空间,这些策略直接影响性能
- vector/string尾部以外,deque首位之外 的位置添加元素 都需要移动元素
- 向vector/string添加元素可能引起整个对象存储空间的重新分配(分配新的内存,并将元素从旧的空间移动到新的空间中)
- 使用push_back
- 将一个元素追加到容器尾部
- 除array和forward_list外每个顺序容器都支持push_back
- 容器元素是拷贝
- 用一个对象来初始化容器/将一个对象插入到容器中,实际上放入到容器中的是对象值的拷贝,不是对象本身
- 类似将一个对象传递给非引用参数 容器中元素与提供的对象之间没有任何关联 随后对容器中元素的任何改变都不会影响到原始对象 反之亦然
- 使用push_front
- list、forward_list、deque支持push_front 将元素插入到容器头部
- 在循环中以这种方式将元素添加到容器中,最终会形成逆序
- 在容器中的特定位置添加元素
- insert允许在容器任意位置插入0个或多个元素
- vector/deque/list/string都支持insert,forward_list提供了特殊版本的insert
- insert都接受一个迭代器作为第一个参数(指出容器在什么位置放置新元素,可为容器任意位置,包括尾后),将元素插入到迭代器所指定的位置之前
- 虽然有的容器不支持push_front(将元素插入到容器的开始位置),但对于insret无类似限制
- 插入范围内元素
- insert除第一个迭代器参数外,还可接受一个元素数目和一个值,将指定数量的元素添加到指定位置之前,这些元素都按给定值初始化
- 也可接受一对迭代器或一个初始化列表的insert版本将给定范围中的元素插入到指定位置之前(不能指向添加元素的目标容器)
- 新标准下,接受元素个数或范围的insert版本返回指向第一个新加入元素的迭代器,如果范围为空 不插入任何元素 insert操作会将第一个参数返回
- 使用insert的返回值
- 通过使用insert的返回值 可以在容器中一个特定位置反复插入元素
- 使用emplace操作
- emplace_front/emplace/emplace_back构造而不是拷贝元素,对应push_front/insert/push_back将元素放置在容器头部/一个指定位置之前/容器尾部
- 调用push/insert时将传递元素类型对象,这些对象被拷贝到容器中;调用emplace时,将参数传递给元素类型的构造函数(使用参数在容器管理的内存空间中直接构造元素,参数必须与元素类型的构造函数相匹配)
9.3.2 访问元素
- 若容器中没有元素,访问操作的结果是未定义的
- 每个顺序容器(包括array)都有一个front成员函数,除forward_list外都有一个back函数,分别返回首元素和尾元素的引用
- 在解引用一个迭代器或调用front/back之前检查是否有元素(非空)
- 获取容器首元素/尾元素的引用的方法
- 直接的方法:调用front/back
- 间接的方法:解引用begin返回的迭代器/通过递减然后解引用end返回的迭代器
at
和下标操作只适用于string
、vector
、deque
、array
。back
不适用于forward_list
表9.6 在顺序容器中访问元素的操作 | |
---|---|
c.back() | 返回c 中尾元素的引用。若c 为空,函数行为未定义 |
c.front() | 返回c 中头元素的引用。若c 为空,函数行为未定义 |
c[n] | 返回c 中下标是n 的元素的引用,n 时候一个无符号整数。若n>=c.size() ,则函数行为未定义 |
c.at(n) | 返回下标为n 的元素引用。如果下标越界,则抛出一out_of_range 异常 |
- 对空容器调用front/back,就和使用一个越界的下标一样,是严重的程序设计错误
- 访问成员函数返回的是引用
- front/back/下标和at返回的都是引用
- 如果容器是一个const对象,则返回const的引用;若不是const的,返回普通引用,可以改变元素的值
- 当使用auto变量保存这些函数返回值并希望使用此变量来改变元素的值,记得将变量定义为引用类型
- 下标操作和安全的随机访问
- 提供快速访问的容器(string/vector/deque/array)都提供下标运算符,返回容器中该位置元素的引用,下标须在合理范围内,越界为严重程序设计错误,编译器不检查这种错误
- 希望确保下标是合法的,可以使用at成员函数,若下标越界会抛出out_of_range异常
9.3.3 删除元素
- 删除操作会改变容器的大小,不适用于array
- forward_list有特殊版本的erase
- forward_list不支持pop_back;vector和string不支持pop_front
表9.7 顺序容器的删除操作 | |
---|---|
c.pop_back() | 删除c 中尾元素,若c 为空,则函数行为未定义。函数返回void |
c.pop_front() | 删除c 中首元素,若c 为空,则函数行为未定义。函数返回void |
c.erase(p) | 删除迭代器p 指向的元素,返回一个指向被删除元素之后的元素的迭代器,若p 指向尾元素,则返回尾后迭代。,若p 本身是尾后迭代器,则函数行为未定义 |
c.erase(b, e) | 删除迭代器b 和e 范围内的元素,返回指向最后一个被删元素之后元素的迭代器,若e 本身就是尾后迭代器,则函数也返回尾后迭代器 |
c.clear() | 删除c 中所有元素,返回void |
-
删除deque首位元素以外的元素都会使所有迭代器/引用/指针失效
-
指向vector/string中删除点之后位置的迭代器/引用/指针失效
-
删除元素的成员函数不检查其参数,删前须确保元素存在
-
pop_front和pop_back成员函数
- 分别删除首元素/尾元素,vector和string不支持push_front/pop_front,forward_list不支持pop_back
- 不能对一个空容器执行弹出操作
-
从容器内部删除一个元素
- erase从容器指定位置删除元素
- 删除由一个迭代器指定的单个元素
- 由一对迭代器指定的范围内所有元素
- 都返回指向删除的最后一个元素之后位置的迭代器
- erase从容器指定位置删除元素
-
删除多个元素
- 接受一对迭代器版本的erase(a,b)
- 第一个参数a 指向要删除的第一个元素
- 第二个参数b 指向要删除的最后一个元素之后的位置
- 即删除[a,b)范围内的元素
- 删除所有元素
- s.clear();
- s.earse(s.begin(),s.end());
- 接受一对迭代器版本的erase(a,b)
9.3.4 特殊的forward_list操作
- forward_list特殊版本的添加/删除操作
- 删除单向链表中的一个元素会改变序列中的链接,添/删元素之前的那个元素的后继会发生变化,添/删前须访问其前驱,以便改变前驱的链接
- forward_list中添/删元素通过改变给定元素之后的元素来完成,总是可以访问到添/删操作所影响的元素
- 由于操作实现方式不同,forward_list没有定义insert/emplace/erase,而是定义了insert_after/emplace_after/erase_after
- forward_list还定义了首前迭代器before_begin,允许在不存在的首前元素之后添/删元素(即链表首元素之前)
表9.8 在forward_list中插入/删除元素的操作 | |
---|---|
lst.before_begin() | 返回指向链表首元素之前不存在的元素的迭代器,此迭代器不能解引用,cbefore_begin()返回一个const_iterator |
lst.cbefore_begin() | 同上 |
lst.insert_after(p, t) | 在迭代器p 之后插入元素。t 是一个对象。返回一个指向最后一个插入元素的迭代器。如果范围为空,则返回p 。若p 为尾后迭代器,则函数行为未定义 |
lst.insert_after(p, n, t) | ⬆ n 是数量 |
lst.insert_after(p, b, e) | ⬆ b 和e 表示范围的一对迭代器(b,e不能指向lst内) |
lst.insert_after(p, il) | ⬆ il是一个花括号列表 |
emplace_after(p, args) | 使用args 在p 之后的位置,创建一个元素,返回一个指向这个新元素的迭代器。若p 为尾后迭代器,则函数行为未定义。 |
lst.erase_after(p) | 删除p 指向位置之后的元素,返回一个指向被删元素之后的元素的迭代器,若不存在这样的元素,则返回尾后迭代器。若p 指向lst 的尾元素或者是一个尾后迭代器,则函数行为未定义。 |
lst.erase_after(b, e) | ⬆ 删除从b 之后直到(但不包含)e 之间的元素 |
- forward_list中删/添元素,关注两个迭代器
-
- 一个指向要处理的元素
-
- 指向其前驱
-
9.3.5 改变容器大小
- 可用resize来增大/缩小容器(array不支持)
- 当前大小大于参数大小,容器后部元素被删除
- 当前大小小于新大小,会将新元素添加到容器后部
- resize接受一个可选的元素值参数,用来初始化添加到容器中的元素;若未提供,则新元素进行值初始化;若容器保存的是类类型,则须提供初始值,或该元素类型提供默认构造函数
表9.9 顺序容器大小操作 | |
---|---|
c.resize(n) | 调整c 的大小为n 个元素,若n<c.size() ,则多出的元素被丢弃。若必须添加新元素,对新元素进行值初始化 |
c.resize(n, t) | 调整c 的大小为n 个元素,任何新添加的元素都初始化为值t |
- 若resize缩小容器。则指向被删除元素的迭代器/指针/引用都会失效
- 对vector/string/deque进行resize可能会导致迭代器/指针/引用失效
9.3.6 容器操作可能使迭代器失效
- 向容器添/删元素可能会导致指向容器元素的迭代器/指针/引用失效
- 向容器添加元素后:
- vector/string且储存空间被重新分配,则指向容器元素的迭代器/指针/引用失效;若储存空间未被重新分配,则插入元素前的元素的迭代器/指针/引用仍有效,插入后元素的迭代器/指针/引用失效
- deque 插入到首位位置之外都会导致指向容器元素的迭代器/指针/引用失效;首尾添加元素,迭代器会失效,但引用/指针不会失效
- list/forward_list 指向容器元素的迭代器(包括首前/尾后迭代器)/指针/引用仍有效
- 从容器中删除元素时
- 指向被删除元素的迭代器/指针/引用都会失效
- list/forward_list 指向容器其他位置的迭代器(包括首前/尾后迭代器)/指针/引用仍有效
- deque 首位之外任何位置删除元素 指向被删除元素外其他元素的迭代器/指针/引用失效;若删除尾元素,则尾后迭代器失效,其他元素的迭代器/指针/引用仍有效;若删除首元素则其他元素的也不会受影响
- vector/string 指向删除元素前元素的迭代器/引用/指针仍有效;
- 删除元素时,尾后迭代器总是会失效
- 管理迭代器
- 最小化程序片段(要求迭代器必须保持有效)
- 每次改变容器后重新定位迭代器
- 编写改变容器的循环程序
- 添/删 vector/string/deque的程序须考虑迭代器/引用/指针可能失效的问题
- 须每个循环步更新迭代器/引用/指针
- 若调用了insert/erase则这些操作都返回迭代器
- insert在给定位置元素前插入新元素,返回新插入元素的迭代器,须递增两次到下一元素
- erase删除给定位置元素,返回最后一个被删除元素之后元素的迭代器,所以不需递增
- 不要保存end返回的迭代器
- 添/删 vector/string元素后 ;deque首元素以外添/删元素,原来end返回的迭代器(尾后迭代器)总会失效,故在循环中须反复调用end,而不能保存循环前end返回的迭代器。c++标准库中end()操作都很快
9.4 vector对象是如何增长的
- 为支持快速随机访问,vector元素连续存储
- 当vector/string添加元素后,没有空间容纳新元素,则容器须分配新的内存空间来保存已有元素和新元素,将已有元素从旧位置移动到新空间,然后添加元素,释放旧存储空间
- 为避免每次添加元素就进行内存分配/释放操作(性能慢),采用可减少容器空间重新分配次数的策略,当不得不获取新空间时vector/string的实现通常会分配比新的空间需求更大的内存空间,这种扩张操作通常比list/deque还快
- 管理容量的成员函数
shrink_to_fit
只适用于vector
、string
和deque
capacity
和reverse
只适用于vector
和string
表9.10 容器大小管理操作 | |
---|---|
c.shrink_to_fit() | 将capacity() 减少到和size() 相同大小,但具体实现可以选择忽略此请求 |
c.capacity() | 不重新分配内存空间的话,c 可以保存多少个元素 |
c.reverse(n) | 分配至少能容纳n 个元素的内存空间 |
- reserve不改变容器中元素的数量,仅影响vector预先分配多大的内存空间;reserve永远也不会减少容器占用的内存空间
- resize只改变容器中元素的数目,而不是容器的容量,不能使用resize来减少容器预留的内存空间
- capacity和size
- size 已经保存的元素的数目
- capacity 在不分配新的内存空间的前提下最多可保存多少元素
- capacity≥size
- 只要没有操作需求超出vector的容量,vector就不能重新分配内存空间,只有当迫不得已时才可以分配新的内存空间
- 确保用push_back向vector添加元素的操作有高效率
9.5 额外的string操作
- 提供string类和C风格字符数组之间的相互转换/增加允许用下标代替迭代器的版本
9.5.1 构造string的其他方法
n
,len2
,pos2
都是无符号值
表9.11 构造string的其他方法 | |
---|---|
string s(cp, n) | s 是cp 指向的数组中前n 个字符的拷贝,此数组至少应该包含n个字符 |
string s(s2, pos2) | s 是string s2 从下标pos2 开始的字符的拷贝。若pos2 > s2.size() ,构造函数的行为未定义。 |
string s(s2, pos2, len2) | s 是string s2 从下标pos2 开始的len2 个字符的拷贝。若pos2 > s2.size() ,构造函数的行为未定义。不管len2 的值是多少,构造函数至多拷贝s2.size()-pos2 个字符 |
-
这些构造函数接受一个string/const char*参数,还有(可选)指定拷贝多少字符
-
传const char*为传递计数器&&未以空字符结尾 || 给定计数值大于数组大小,构造函数的行为未定义
-
substr操作
- 原始string的一部分/全部拷贝
- 可传递一个可选的开始位置和计数值
- 若开始位置超出string大小 则抛出out_of_range异常
- 若开始位置加上计数值大于string大小,substr会调整计数值,只拷贝到string末尾
表9.12 子字符串操作 | |
---|---|
s.substr(pos, n) | 返回一个string ,包含s 中从pos 开始的n 个字符的拷贝。pos 的默认值是0。n 的默认值是s.size() - pos ,即拷贝从pos 开始的所有字符 |
9.5.2 改变string的其他方法
- string类型支持assign/insert/erase
- 还定义了额外的insert/erase版本
- 接受下标的版本
- 指出开始删除的位置
- 或insert到给定值之前的位置
- 标准库string提供接受C风格字符数组的insert/assign版本
- 将以空字符结尾的字符数组insert/assign给一个string
- s.assign(字符数组,要拷贝的字符数)
- s.insert(s要插入的位置之后元素的迭代器,开始拷贝的位置(最多拷到结尾空字符处))
- s.insert(s要插入的位置之后元素的迭代器,str,str开始拷贝的位置,拷贝n个字符)
- append和replace函数
- append在string末尾进行插入
- replace调用erase和insert,删除指定元素后插入一个任意长度的新字符
表9.13 修改string的操作 | |
---|---|
s.insert(pos, args) | 在pos 之前插入args 指定的字符。pos 可以是一个下标或一个迭代器。接受下标的版本返回指向s 的引用;接受迭代器的版本返回指向第一个插入字符的迭代器。 |
s.erase(pos, len) | 删除从pos 开始的len 个字符,如果len 被省略,则删除从pos开始直至s末尾的所有字符,返回一个指向s 的引用。 |
s.assign(args) | 将s 中的字符替换为args 指定的字符。返回一个指向s 的引用。 |
s.append(args) | 将args 追加到s ,返回一个指向s 的引用。 |
s.replace(range, args) | 删除s 中范围range 中的字符,替换成args 指定的字符。range 可以是一个下标和一个长度/一对指向s的迭代器。返回一个指向s 的引用。 |
args可能的形式 | append/assign可以使用所有形式,str不能与s相同,迭代器b和e不能指向s |
str | 字符串str |
str,pos,len | str中从pos开始最多len个字 |
cp,len | cp指向的字符数组的前len个字符 |
cp | cp指向的以空字符结尾的字符数组 |
n,c | n个字符c |
b,e | 迭代器b和e指定的范围内的字符 |
初始化列表 | 花括号包围的,以逗号分隔的字符列表 |
- replace/insert所允许的args形式依赖于range和pos是如何指定的
replace | replace | insert | insert | args 可为 |
---|---|---|---|---|
(pos,len,args) | (b,e,args) | (pos,args) | (iter,args) | |
是 | 是 | 是 | 否 | str |
是 | 否 | 是 | 否 | str,pos,len |
是 | 是 | 是 | 否 | cp,len |
是 | 是 | 否 | 否 | cp |
是 | 是 | 是 | 是 | n,c |
否 | 是 | 否 | 是 | b2,e2 |
否 | 是 | 否 | 是 | 初始化列表 |
- 改变string的多种重载函数
- assign/append/insert/replace有多个重载版本
- assign/append无需指定要替换string中的哪个部分
- assign默认替换string中所有内容
- append总是将新字符追加到string末尾
- replace删除元素范围
- 一个位置和一个长度
- 一个迭代器范围
- insert插入元素位置
- 一个下标或迭代器,在这些位置之前插入元素
9.5.3 string搜索操作
- 6种不同的搜索操作(大小写敏感),每个函数都有4个重载版本,每个搜索操作都返回一个string::size_type值,表示匹配发生位置的下标,搜索失败返回string::npos
- string::npos 类型为 const string::size_type,初始化值为-1,unsignesd类型,npos等于任何string最大的可能大小。(用int/其他带符号类型来保存搜索函数返回值不是好主意)
表9.14 string搜索操作 | |
---|---|
搜索操作返回指定字符出现的下标,如果未找到则返回npos | |
s.find(args) | 查找s 中args 第一次出现的位置 |
s.rfind(args) | 查找s 中args 最后一次出现的位置 |
s.find_first_of(args) | 在s 中查找args 中任何一个字符第一次出现的位置 |
s.find_last_of(args) | 在s 中查找args 中任何一个字符最后一次出现的位置 |
s.find_first_not_of(args) | 在s 中查找第一个不在args 中的字符 |
s.find_last_not_of(args) | 在s 中查找最后一个不在args 中的字符 |
args必须是一下的形式之一 | |
c, pos | 从s 中位置pos 开始查找字符c 。pos 默认是0 |
s2, pos | 从s 中位置pos 开始查找字符串s2 。pos 默认是0 |
cp, pos | 从s 中位置pos 开始查找指针cp 指向的以空字符结尾的C风格字符串。pos 默认是0 |
cp, pos, n | 从s 中位置pos 开始查找指针cp 指向的前n 个字符。pos 和n 无默认值。 |
- 指定在哪里开始搜索
- 可给find传递一个可选的开始搜索位置(默认为0)
- (设计模式)可利用该可选参数在字符串中循环搜索子字符串出现的所有位置(递增pos)
- 逆向搜索
- 从右至左搜索的操作
s.find_last_of(args)
在s
中查找args
中任何一个字符最后一次出现的位置s.find_last_not_of(args)
在s
中查找最后一个不在args
中的字符- 每个操作都接受一个可选的第二参数 指出从什么位置开始搜索
9.5.4 compare函数
- 与C标准库的strcmp函数很相似
- 根据s是等于/大于/小于参数指定的字符串,返回0/正数/负数
- 比较两个string/一个string和一个字符数组,可比较整个/一部分字符串
表9.15 s.compare的几种参数形式 | |
---|---|
s2 | 比较s 和s2 |
pos1, n1, s2 | 将s 中从pos1 开始的n1 个字符与s2 进行比较 |
pos1, n1, s2, pos2, n2 | 将s 从pos1 开始的n1 个字符与s2 中从pos2 开始n2 个字符进行比较 |
cp | 比较s 和cp 指向的以空字符结尾的字符数组 |
pos1, n1, cp | 将s中 从pos1 开始的n1 个字符与cp 指向的以空字符结尾的字符数组进行比较 |
pos1, n1, cp, n2 | 将s 中从pos1 开始的n1 个字符与指针cp 指向的地址开始n2 个字符 |
9.5.5 数值转换
- 新标准引入多个函数,可实现数值数据与标准库string之间的转换
- 要转换为数值的string第一个非空白符须是数值中可能出现的字符(可用
find_first_of("+-.0123456789")
),直到遇到第一个不可能是数值一部分的字符- +/-或数字开头
- 可0x/0X开头表示16进制
- 可以.开头
- 可包含e/E表示指数部分
- 根据基数不同,可包含字母字符表示大于数字9的数
- 若string不能转换为一个数值,则函数抛出invalid_argument异常
- 若转换得到的数值无法用任何类型表示。则抛出out_of_range异常
表9.16 string和数值之间的转换 | |
---|---|
to_string(val) | 一组重载函数,返回数值val 的string 表示。val 可以是任何算术类型。对每个浮点类型和int 或更大的整型,都有相应版本的to_string() 。与往常一样,小整型会被提升。 |
stoi(s, p, b) | 返回s 起始子串(表示整数内容)的数值,b 是转换所用的基数,默认值为10,p 是size_t 指针,用来保存s中第一个非数值字符的下标,p默认为0,即函数不保存下标。返回值类型int |
stol(s, p, b) | ⬆ 返回值类型long |
stoul(s, p, b) | ⬆ 返回值类型unsigned long |
stoll(s, p, b) | ⬆ 返回值类型long long |
stoull(s, p, b) | ⬆ 返回值类型unsigned long long |
stof(s, p) | 返回s 起始子串(表示浮点数内容)的数值,参数p 作用与整形转换函数一样。返回值类型float |
stod(s, p) | ⬆ 返回值类型double |
stold(s, p) | ⬆ 返回值类型long double |
9.6 容器适配器
- stack/queue/priority_queue
- 适配器 (一种机制) 能使某种事物的行为看起来像另外一种事物一样
- 一个容器接受一种已有的容器类型,使其行为看起来像一种不同的类型
- stack接受一个顺序容器(除array和forward_list外),使其操作看起来像一个stack一样
- 一个容器接受一种已有的容器类型,使其行为看起来像一种不同的类型
表9.17 所有容器适配器都支持的操作和类型 | |
---|---|
size_type | 一种类型,足以保存当前类型的最大对象的大小 |
value_type | 元素类型 |
container_type | 实现适配器的底层容器类型 |
A a; | 创建一个名为a 的空适配器 |
A a(c) | 创建一个名为a 的适配器,带有容器c 的一个拷贝 |
关系运算符 | 每个适配器都支持所有关系运算符:== 、!= 、< 、 <= 、> 、>= 这些运算符返回底层容器的比较结果 |
a.empty() | 若a 包含任何元素,返回false ;否则返回true |
a.size() | 返回a 中的元素数目 |
swap(a, b) | 交换a 和b 的内容,a 和b 必须有相同类型,包括底层容器类型也必须相同 |
a.swap(b) | 同上 |
- 定义一个适配器
- 每个适配器都定义了两个构造函数
- 默认构造函数创建一个空对象
- 接受一个容器的构造函数拷贝该容器来初始化适配器
- 默认情况下
- stack/queue默认基于deque实现
- priority_queue默认在vector上实现
- 可在创建适配器时将一个命名的顺序容器作为第二个参数来重载默认容器类型
- 所有适配器须有
- 添/删元素/访问尾元素的能力
- 因此任何适配器都不能构造在array/forward_list上
- 添/删元素/访问尾元素的能力
- stack要求有 push_back/pop_back/back操作
- 可构造在除array/forward_list之外的任何容器上,即vector/deque/list
- queue要求有 back/push_back/front/push_front
- 可构造在list/deque之上,不能基于vector构造
- priority_queue要求有front/push_back/pop_back/随机访问能力
- 可构造于vector/deque之上,但不能基于list构造
- 可构造于vector/deque之上,但不能基于list构造
- 每个适配器都定义了两个构造函数
- 栈适配器(stack定义于stack头文件)
表9.18 栈额外支持的操作 | |
---|---|
s.pop() | 删除栈顶元素,不返回该元素值 |
s.push(item) | 创建一个新元素压入栈顶,该元素通过拷贝或移动item 而来 |
s.emplace(args) | ⬆ 或者由args 构造。 |
s.top() | 返回栈顶元素,但不将元素弹出栈 |
-
每个容器适配器都基于底层容器类型定义了自己的特殊操作。我们只可以使用适配器操作,不能使用底层容器类型的操作
-
队列适配器(queue/priority_queue定义于queue头文件)
表9.19 queue/priority_queue额外支持的操作 | |
---|---|
q.pop() | 删除但不返回队首元素/priority_queue最高优先级的元素 |
q.front() | 返回队首/尾元素的值,但不删除此元素 |
q.back() | ⬆ 只适用于queue |
q.top() | 返回具有最高优先级元素,但不删除该元素 |
q.push(item) | 在queue末尾/priority_queue中恰当位置创建一个元素,其值为item |
q.emplace(args) | ⬆ 或者由args 构造 |
- 队存储/访问策略
- queue 先进先出
- 进入队列对象放队尾 离开队列对象从队首删除
- priority_queue 允许为队列元素建立优先级
- 新加入元素排在优先级比它低的已有元素之前
- 默认情况下元素 类型使用<运算符来确定相对优先级
- queue 先进先出
小结
- 标准库容器 模板类型 保存给定类型对象
- 顺序容器中 按顺序存放 通过位置访问
- 公共标准接口 若两容器提供同一特定操作 在两容器具相同接口和含义
- 顺序容器中 按顺序存放 通过位置访问
- 容器(除array外)都提供高效的动态内存管理
- 向容器添加元素,不必担心存储在哪,容器负责管理自身的存储
- vector/string通过reserve/capacity来提供更细致的内存管理控制
- 向容器添加元素,不必担心存储在哪,容器负责管理自身的存储
- 每个容器定义了 构造函数/ 添加/删除元素的操作 /确定容器大小的操作/返回指向特定元素迭代器的操作
- 其他操作如 排序/搜索 不是由容器类型定义的,而是由标准库算法实现
- 添/删元素的容器操作
- 可能会使指向容器中元素的迭代器/指针或引用失效
- insert/erase返回新的迭代器,来帮助维护容器中的位置
- 循环中改变容器的大小,要小心迭代器/指针或引用的使用
术语表
- 适配器 接受一个类型/函数/迭代器,使其行为像另外一个类型/函数/迭代器一样
- forward_list 单向链表 中的元素只能顺序访问;从一个给定位置开始,为访问一个元素,只能遍历两者间所有的元素;迭代器不支持递减运算–;支持任意位置快速删除/插入操作,插入/删除发生在一个给定迭代器之后的位置,因此还具有一个首前迭代器;添加新元素后,原有迭代器仍有效;删除元素后,只有原来指向被删元素的迭代器才会失效
- list 双向链表 只能顺序访问;从一个给定位置开始,为访问一个元素,只能遍历两者间所有的元素;支持递增++/递减–;支持任意位置快速删除/插入操作,添加新元素后,原有迭代器仍有效;删除元素后,只有原来指向被删元素的迭代器才会失效
- priority_queue 顺序容器适配器,队列插入元素根据特定的优先级排列,默认情况下元素 类型使用<运算符来确定相对优先级