9.1 顺序容器(sequential container)概述
vector:可变大小数组,deque:双端队列,list:双向链表,forward_list:单向链表,array:固定大小数组,string:专门用于保存字符的可变大小容器。- 与内置数组相比,
array是一种更安全、更容易使用的数组类型。 forward_list没有size操作,因为保存和计算其大小就会比手写链表多出额外的开销。
9.2 容器库概览
- 反向容器的额外成员(不支持
forward_list)
reverse_iterator // 按逆序寻址元素的迭代器
const_reverse_iterator // 不能修改元素的逆序迭代器
c.rbegin(), c.rend() // 返回指向c的尾元素和首元素之前位置的迭代器
c.crbegin(), c.crend() // 返回 const_reverse_iterator
9.2.1 迭代器
forward_list迭代器不支持递减运算符(–)。
9.2.2 容器类型成员
- 通过 类型别名 我们可以在不了解容器中元素类型的情况下使用它。
9.2.4 容器定义和初始化
- 当传递迭代器参数来拷贝一个范围时,就不要求新容器和原容器的类型是相同的了,只要能将要拷贝的元素转换为要初始化的容器的元素类型即可。
- 只有顺序容器的构造函数才接受大小参数,关联容器并不支持。
- 我们不能对内置数组类型进行拷贝或者对象赋值操作,但是
array并无此限制。array要求初始值的类型必须与要创建的容器大小和类型均相同。
9.2.5 赋值和 swap
- 由于右边运算对象的大小可能与左边运算对象的大小不同,因此
array类型不支持assign,也不允许用花括号包围的值列表进行赋值。 - 除
array外,swap交换两个容器内容的操作元素本身并未交换,只是交换了两个容器的内部数据结构。 - 与其他容器不同,
swap两个array会真正的交换它们的元素。因此,指针、引用和迭代器所绑定的元素保持不变,但是元素值已经与另一个array中对应元素的值进行了交换。(指向的内存地址没变,内存里的内容调换了)。
// 例如:假定iter在swap之前指向svec1[0]的元素a
array<string, 1> svec1 = { "a" }, svec2 = { "b" };
auto iter = svec1.begin();
swap(svec1, svec2);
// 那么在swap之后它还是指向svec1[0]的元素,但是元素值变成了 b
iter == svec1.begin(); // true
&iter == "b"; // true
- 除
string外,指向容器的迭代器、引用和指针在swap操作之后都不会失效。它们仍指向swap操作之前所指的那些元素 (指向的内存地址调换了,内存里的内容没换)。
// 例如:假定iter在swap之前指向svec1[0]的元素a
vector<string> svec1 = { "a" }, svec2 = { "b" };
auto iter = svec1.begin();
swap(svec1, svec2);
// 那么在swap之后它指向svec2[0]的元素,且元素值还是a。
iter == svec2.begin(); // true
&iter == "a"; // true
- 与其他容器不同,对一个
string调用swap会导致迭代器、引用和指针失效。 - 非成员版本的
swap在泛型编程中是非常重要的。统一使用非成员版本的swap是一个好习惯。
9.2.7 关系运算符
- 只有当其元素类型定义了相应的比较运算符时,才能使用关系运算符来比较两个容器。
9.3 顺序容器操作
9.3.1 向顺序容器添加元素
forward_list有自己专有版本的insert和emplace;且不支持push_back和emplace_back。vector和string不支持push_front和emplace_front。insert函数将元素插入到迭代器所指定的位置之前。emplace函数在容器中直接构造元素。传递给emplace函数的参数必须与元素类型的构造函数想匹配。
9.3.2 访问元素
vector<int> c = { 1 };
auto& v1 = c.back(); // 获得指向最后一个元素的引用
auto v2 = c.back(); // 获得最后一个元素的拷贝
9.3.3 删除元素
- 删除
deque中除首尾位置之外的任何元素都会使所有迭代器、引用和指针失效。 - 指向
vector或string中删除点之后位置的迭代器、引用和指针都会失效。 pop_front和pop_back操作返回void。
9.3.4 特殊的 forward_list 操作
- 在一个单向链表中没有简单的方法来获取一个元素的前驱。出于这个原因,在一个
forward_list中添加和删除元素的操作是通过改变给定元素之后的元素来完成的。于是定义了insert_after、emplace_after和erase_after操作与普通顺序容器里的insert、emplace和erase操作相对应。 before_begin()返回指向链表首元素之前不存在的元素的迭代器。首前迭代器(off_the_beginning iterator)不能解引用。
9.3.5 改变容器大小
- 对
vector、string或deque进行resize可能导致迭代器、指针和引用失效。
9.3.6 容器操作可能使迭代器失效
- 对于
deque,如果在首尾位置添加元素,迭代器会失效,但指向存在的元素的引用和指针不会失效。 - 当我们添加/删除
vector或string的元素后,或在deque中首元素之外任何位置添加/删除元素后,原来end返回的迭代器总是会失效,所以不要缓存end返回的迭代器。
9.4 vector 对象是如何增长的
- 调用
reserve不会减少容器占用的内存空间。调用resize只改变容器中元素的数目,而不是容器的容量,也不能减少容器预留的内存空间。 - 调用
shrink_to_fit来要求deque、vector或string退回不需要的内存空间。但是不能保证调用该函数后一定会退回多余的内存空间。
9.5 额外的 string 操作
9.5.1 构造 string 的其他方法
- 当我们从一个
const char*创建string时,如果未传递计数值且数组也未以空字符结尾,或者给定计数值大于数组大小,则构造函数的行为是未定义的。 s.substr(pos, n)返回一个string,包含s中从pos开始的n个字符的拷贝。
9.5.2 改变 string 的其他方法
assgin和append函数无须指定要替换string中哪个部分:assgin总是替换所有内容,append总是将新字符追加到末尾。replace函数提供了两种指定删除元素范围的方式:可以通过一个位置和一个长度来指定范围,也可以通过一个迭代器范围来指定。insert函数允许两种方式指定插入点:用一个下标或一个迭代器。元素都会插入到给定下标(或迭代器)之前的位置。
9.5.3 string 搜索操作
- 如果搜索失败,返回一个名为
string::npos的static成员,标准库将npos定义为一个const string::size_type类型。 - 搜索是 大小写敏感 的。
9.5.5 数值转换
- 如果
string不能转换为一个数值,这些函数抛出一个invalid_argument异常,如果转换得到的数值无法用任何类型来表示,则抛出一个out_of_range异常。
9.6 容器适配器(adaptor)
- 适配器是标准库中的一个通用概念。容器、迭代器和函数 都有适配器,本质上一个适配器是一种机制,能使某种事物的行为看起来像另外一种事物一样。
- 除了顺序容器外,标准库还定义了三个顺序容器适配器:
stack、queue和priority_queue。
// 每种容器适配器都定义两个构造函数:默认构造函数创建一个空对象;接受一个容器的构造函数拷贝该容器来初始化适配器。
deque<int> deq;
stack<int> stk; // 默认构造函数
stack<int> stk2(deq); // 拷贝容器内容
// 我们可以在创建一个适配器时将一个命名的顺序容器作为第二个类型参数来重载默认容器类型。
vector<int> vc;
stack<int, vector<int>> vc_stk;
stack<int, vector<int>> vc_stk2(vc);
- 所有适配器都要求容器具有添加和删除以及访问尾元素的能力,因此适配器不能构造在
array和forward_list之上。 - 可以使用除
array和forward_list之外的任何容器类型来构造stack。 queue可以构造于list或deque之上,但是不能基于vector构造。priority_queue可以构造于vector或deque之上,但是不能基于list构造。priority_queue允许我们为队列中的元素建立优先级。新加入的元素会排在所有优先级比它低的已有元素之前。
创作等级
会员等级