第九章 顺序容器
梗概:本章主要是在第三章的基础上继续介绍c++语言中的顺序容器。
9.1 顺序容器概述
顺序容器包括:
- vector 可变大小数组,支持快速随机访问。尾部之外添加元素很慢。
- deque 双端队列。支持快速随机访问。在首尾插入删除很快。
- list 双向链表。只支持双向顺序访问。插入删除很快。
- forward_list 单向链表。只支持单向顺序访问。插入删除快。
- array 固定大小数组。支持快速随机访问,不能改变大小。
- string 类似vector的字符容器。随机访问快,尾部插入删除快。
顺序容器选择准则:
- 一般选择vector,除非特殊要求。
- 很多小元素且空间开销重要,不要选list和forward_list。
- 要求随机访问,使用vector或deque。
- 其它具体分析。
9.2 容器库概览
容器均定义为模板类,使用时需要提供元素类型,元素类型可以是其它容器。
某些容器要求元素有一些特殊要求。
9.2.1 迭代器
迭代器有公共接口,即拥有相同操作的迭代器实现方式相同。
迭代器范围由一对迭代器表示,都指向同一个容器中的元素或者尾后位置,成为begin和end。
需要保证begin不在end后面。
9.2.2 容器类型成员
容器有些类型成员,如size_type iterator和const_iterator。
value_type 表示元素类型。 reference或者const_reference表示元素类型的引用。
9.2.3 begin和end成员
begin和end函数分别返回容器的起始位置迭代器和尾后位置迭代器。
begin()函数和rbegin()是经过重载的,既可以接收常量也可以是非常亮。
当不需要写操作时,使用cbegin()和cend()。
9.2.4 容器定义和初始化
容器初始化包括常用的几种,默认初始化,拷贝初始化,直接初始化,列表初始化以及使用迭代器对初始化。
array容器的初始化比较特殊。
当拷贝初始化和直接初始化时,要求被拷贝的容器与新定义容器的类型完全一致。
但列表初始化和使用迭代器范围初始化时,只需要元素可以转换即可。
构造函数可以接受一个元素数量和初始值,没有默认构造函数的元素一定要给初始值。
array容器大小固定,可以赋值和拷贝。但类型包括大小必须完全符合。
9.2.5 赋值和swap
array不支持直接花括号赋值。
assign函数可以从一个不同但相容的类型赋值,或者从容器的一个子序列赋值。
assign函数的参数不能指向调用该函数的容器。
swap交换两个类型相同的容器中的元素,只交换了内部数据结构而非数据本身,引用迭代器等容易出错。
array调用swap会真的交换元素,因此比较慢。
9.2.7关系运算符
容器的相等调用的是元素的==运算符,其他操作都调用<运算符。
只有当容器中的元素定义了相关运算符,容器才可以进行运算。
9.3 顺序容器操作
9.3.1 向顺序容器添加元素
向顺序容器添加元素的操作主要有:
- push_back push_front 直接向容器中添加元素t
- emplace_back emplace_front 向容器中放置由参数args构造的新元素。
- insert 向容器迭代器指向的位置添加元素,返回添加的第一个元素迭代器。
向vector string deque中插入元素会使原来的迭代器和指针引用等都失效。
将元素插入容器或者使用对象初始化容器,是将其拷贝进容器。
insert可以插入单个对象,也可以插入迭代器范围。插入位置在给的迭代器之前,返回插入后指向第一个新元素的迭代器。
emplace是直接构造元素而不是拷贝,因此可以直接操作内存进行构造,push_back是先拷贝后压入。
c保存Sales_data,c.emplace(iter, "99999")。
9.3.2 访问元素
每个顺序容器都包含front()函数,除了forwar_list都包含back()。分别访问第一个元素和最后一个元素。
c[n]操作返回下标为n的引用, c.at(n)相同但是可以抛出out_of_range异常。
9.3.3 删除元素
pop_front 和 pop_back分别是从开始和最后删除一个元素。有的容器不包含这两个操作。
erase函数删除一个迭代器指向的元素,返回该元素后一个元素的迭代器。
clear清除所有元素,等同于c.erase(c.begin(),c.end())。
9.3.4 特殊的forward_list操作
单向链表,删除添加时都需要知道前一个节点的指针,因此forward_list有特殊的插入删除的函数。
lst.before_begin()首前迭代器。
insert_after emplace_after erase_after等等。
9.3.5 改变容器大小
resize可以增大或者缩小容器,array不支持。
resize大于当前元素数量的部分默认初始化,多的部分抛弃。
9.3.6 容器操作使迭代器失效
添加删除元素会使容器迭代器指针引用等I失效,因此每次操作时需要重新确认迭代器。
添加删除元素等操作时必须保证每个循环步都更新迭代器引用和指针。
不要保存end返回的迭代器。
9.4 vector对象是如何增长的
为了保证随机访问效率,vector元素在内存中的位置是连续的。
capacity函数返回当前vector不扩张情况下最大元素数量。
reserve(n)分配至少可以保存n个元素的内存空间,永远不会减少内存空间。
resize函数只改变容器的元素数量而非内存大小。
shrink_to_fit要求退回不必要的空间,但具体实现可能忽略。
vector起始容量为0, 每次capacity需要增长时一般变为当前的两倍。具体依赖于实现。
9.5 额外的string操作
9.5.1 构造string的其它方法
- string s(cp,n) 从cp指针开始拷贝n个字符来初始化。
- string s(s2, pos2) 从字符串s2位置pos2开始拷贝。
- string s(s2,pos2,len2) 从字符串s2位置pos2开始拷贝n个字符。
9.5.2 改变string的其它方法
string有额外的insert和erase版本。
insert(pos, args)接收不同的args形式,erase(pos,len)。
还有append函数和replace函数,使用erase+insert等于replace。
这几个函数接受的参数组合不尽相同,略蛋疼。
9.5.3 string搜索操作
每个搜索操作返回一个string::size_type类型值,返回string::npos表示搜索失败。
- s.find(args) s.rfind(args)分别从前从后搜索args,返回起始位置。
- s.find_first_of(args) 搜索args中在s中的第一个位置。同理find_last_of。
- s.find_first_not_of(args) 搜索s中args不包含的第一个。
9.5.5 数值转换
to_string(x) 将数值型x转换为string类型。
stoi stod stol 等等将string类型转化为相应的算数类型。参数转换失败抛出异常。
9.6容器适配器
适配器是标准库中的一个通用概念,是一种 让某种事物的行为看起来像另外一种事物。
包括stack、queue和priority_queue。
不同的适配器需要支持不同的操作,因此可以选用不同的容器来构造。
适配器只能够使用适配器定义的操作。