顺序容器概述
所有顺序容器都提供了快速顺序访问元素的能力。
vector//可变大小数组。支持快速随机访问。在尾部之外的位置插入或删除元素可能很慢
deque//双端队列。支持快速随机访问。在头尾位置插入/删除速度很快
list//双向链表。只支持双向顺序访问。在list中任何位置进行插入/删除操作速度都很快
forward_list//单向链表。只支持单向顺序访问。在链表任何位置进行插入/删除操作速度都很快
array//固定大小数组。支持快速随机访问。不能添加或删除元素
string//与vector 相似的容器,但专门用于保存字符。随机访问快。在尾部插入/删除速度快
string 和 vector 将元素保存在连续的内存空间中。
由于元素是连续存储的,由元素的下标来计算其地址是非常快速的。但是,在这两种容器的中间位置添加或删除元素就会非常耗时:在一次插入或删除操作后,需要移动插入/删除位置之后的所有元素,来保持连续存储。而且,添加一个元素有时可能还需要分配额外的存储空间。在这种情况下,每个元素都必须移动到新的存储空间中。
list 和 forward list 两个容器任何位置的添加和删除操作都很快速。
作为代价,这两个容器不支持元素的随机访问:为了访问一个元素,我们只能遍历整个容器。而且,与vector、deque和 array相比,这两个容器的额外内存开销也很大。
deque 的中间位置添加或删除元素的代价很高,但在两端添加或删除元素很快。
array对象大小是固定的,不支持添加和删除元素以及改变容器大小的操作。
forward_list 没有 size 操作,forward_list 的设计目标是达到与最好的手写的单向链表数据结构相当的性能,保存或计算 forward_list 大小就会比手写链表多出额外的开销。
容器选择:
- 没有特殊的理由,使用 vector 是最好的选择;
- 有很多小的元素,不用 list 或 forward_list;
- 随机访问元素,用 vector 和 deque;
- 在容器中间插入或删除元素,用 list 和 forward_list;
- 在头尾位置插入或删除元素,但不在中间位置插入或删除元素,用deque;
- 在中间位置插入,而后随机访问:
①向 vector 中追加数据,再调用 sort 函数来排序;
②在输入阶段用 list ,输入完成后拷贝到 vector 中。
容器库概览
构造函数:同类拷贝、迭代器范围初始化、列表初始化
C c;//默认构造函数,构造空容器
C c1(c2);// 拷贝构造函数,容器类型与元素类型必须都相同,c2->c1
C c1(c2.begin(),c2.end());// 要求两种元素类型可以转换即可。
C c1{a,b,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
添加/删除元素
c.insert(args); // 将 args 中的元素拷贝进 c
c.emplace(inits); // 使用 inits 构造 c 中的一个元素
c.erase(args); // 删除指定的元素
c.clear();
反向容器的额外成员
迭代器
迭代器范围由一对迭代器表示,两个迭代器表示的范围都是左闭合空间,即 [begin,end) :如果 begin 和 end 相等,则为空。
所有迭代器都可以递增,forward_list 不可以递减
// iter是通过list<string>定义的一个迭代器类型
list<string>::iterator iter;
//count是通过vector<int>定义的一个difference_type类型
vector<int>::difference_type count;
当不需要写访问时,应使用 cbegin和cend。加c后的版本是 const_iterator。
容器定义和初始化
当将一个容器初始化为另一个容器的拷贝时,两个容器的容器类型和元素类型都必须相同。
只有顺序容器的构造函数才接受大小参数,关联容器并不支持。
对 array 进行列表初始化,初始值的数目必须等于或小于 array 的大小。
array<int, 10>ial;//10个默认初始化的int
array<int,10> ia2 = {0,1,2,3,4,5,6,7,8,9};//列表初始化
array<int, 10>ia3 = (42); // ia3[0]为42,剩余元素为0
如果定义的 array 很大并且需要初始化,可以先默认初始化然后用 fill 函数填充值。
array 可以拷贝或赋值,使用一个 array 对另一个 array 赋值,需要两个array 元素类型与大小都相同。
赋值和 swap
“=”赋值
对容器使用赋值运算符(除 array 外),将会使该容器的所有元素被替换。如果两个容器大小不等,赋值后都与右边容器的原大小相同。
array要求赋值前大小就必须一样。
assign:允许从一个不同但相容的类型赋值,或者从容器的一个子序列赋值。
list<string> names;
vector<const char*>oldstyle;
names = oldstyle;//错误:容器类型不匹配
//正确:可以将 const char* 转换为 string
names.assign (oldstyle.cbegin(),oldstyle.cend());
swap:除array外,交换两个容器内容的操作保证会很快—元素本身并未交换,swap 只是交换了两个容器的内部数据结构。
顺序容器操作
添加元素
向 vector、string 或 deque 插入元素会使所有指向容器的迭代器、引用和指针失效。
emplace函数在容器中直接构造元素。传递给emplace函数的参数必须与元素类型的构造函数相匹配。
emplace_front、 emplace 和 emplace_back 操作构造而不是拷贝元素。
访问元素
访问元素首先保证容器是非空的。
if(!c.empty()){}
删除元素
删除元素之前,首先要保证它是存在的。
特殊的 forward_list 操作
forward_list 是单向链表,添加和删除操作都会同时改变前驱和后继结点,因此一般的添加和删除都不适用于 forward_list。
改变容器大小
容器操作可能使迭代器失效
添加元素后:
- 如果容器是vector或string,且存储空间被重新分配,则指向容器的迭代器、指针和引用都会失效。如果存储空间未重新分配,指向插入位置之前的元素的迭代器、指针和引用仍有效,但指向插入位置之后元素的迭代器、指针和引用将会失效。
- 对于 deque,插入到除首尾位置之外的任何位置都会导致迭代器、指针和引用失效。如果在首尾位置添加元素,迭代器会失效,但指向存在的元素的引用和指针不会失效。
- 对于 list和forward_list,指向容器的迭代器(包括尾后迭代器和首前迭代器)、指针和引用仍有效。
删除元素后:
- 对于list和 forward list,指向容器其他位置的迭代器(包括尾后迭代器和首前迭代器)、引用和指针仍有效。
- 对于 deque,如果在首尾之外的任何位置删除元素,那么指向被删除元素外其他元素的迭代器、引用或指针也会失效。如果是删除 deque 的尾元素,则尾后迭代器也会失效,但其他迭代器、引用和指针不受影响;如果是删除首元素,这些也不会受影响。
- 对于vector和string,指向被删元素之前元素的迭代器、引用和指针仍有效。注意:当我们删除元素时,尾后迭代器总是会失效。
不要保存 end 返回的迭代器:push、pop、首尾 emplace 操作都没有返回值,但是都会改变尾后迭代器。
保证每次改变容器的操作之后都正确地重新定位迭代器
vector 对象
为了支持快速访问,vector 将元素连续存储。
为了避免每增加一个元素就要重新分配一次空间,在每次必须获取新的内存空间时,通常会分配比新的空间需求更大的内存空间。容器预留多的空间作为备用。
容量管理:
容器的size是指它已经保存的元素的数目;而capacity则是在不分配新的内存空间的前提下它最多可以保存多少元素。
c.reserve(n)
不会减小容量,只会增大容量,当需求容量大于当前容量,才会分配内存,否则什么都不做。
c.shrink_to_fit()
只是一个请求,实现时标准库可能会不执行。
额外的 string 操作
1.提供string类和C风格字符数组之间的相互转换;
2.允许用下标代替迭代器。
构造 string 的其他方法
//n, len2和pos2都是无符号值
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 (),构造函数的行为未定义。不管1en2的值是多少,构造函数至多拷贝s2.size ()-pos2个字符
substr 操作
substr操作返回一个string,它是原始string 的一部分或全部的拷贝。可以传递给substr一个可选的开始位置和计数值:
s.substr(pos, n)//返回一个string,包含s中从pos 开始的n个字符的铂贝。pos的默认值为0。n的默认值为s.size()-pos,即铂贝从pos 开始的所有字符
改变 string 的其他方法
string 搜索操作
string 搜索函数返回 string:: size_typ e值,该类型是一个 unsigned 类型。
注意:find 和 rfind 查找的是给定的整个 args,而剩下的查找的是给定的 args 中包含的任意一个字符。
compare 函数
用于比较两个字符串,可以是比较整个或一部分字符串。
小于返回负数,大于返回正数,等于返回零。
int F = s.compare(s2);
int F = s.compare(pos1,n1,s2); // 将 s 中 pos1 开始的前 n1 个字符与 s2 比较
int F = s.compare(pos1,n1,s2,pos2,n2); // 将 s 中 pos1 开始的前 n1 个字符与 s2 中从 pos2 开始的 n2 个字符进行比较
int F = s.compare(cp) // 将 s 与 cp 指向的字符数组比较
int F = s.compare(pos1,n1,cp);
int F = s.compare(pos1,n1,cp,n2);
数值转换
string 与 数值之间的转换:
容器适配器
标准库定义了三个顺序容器适配器:stack、queue、priority_queue。
适配器是一种机制,是某种事物的行为看起来像另一种事物。
适配器类型
定义一个适配器
每个适配器都定义两个构造函数:默认构造函数创建一个空对象,接受一个容器的构造函数拷贝该容器来初始化适配器。
stack<int> stk(deq);//从deq拷贝元素到stk
默认情况下, stack 和 queue 是基于 deque 实现的, priority_queue 是在 vector 之上实现的。
在创建适配器时用一个顺序容器作为第二个类型参数,来重载默认容器类型:
//在vector上实现的空栈
stack<string, vector<string>> str_stk;
//str_stk2在vector上实现,初始化时保存svec的拷贝
stack<string, vector<string>> str_stk2(svec);
栈适配器
队列适配器
queue 和 priority_queue 都定义在头文件 queue 中。
queue 使用一种先进先出(first-in, first-out,FIFO)的存储和访问策略。队尾进,队首出。
priority_queue 允许为队列中的元素建立优先级。新加入的元素会排在所有优先级比它低的已有元素之前。
重要术语
首前迭代器 :表示一个 forward_list 开始位置之前(不存在的)元素的迭代器。是 forward_list 的成员函数 befor_begin 的返回值。与 end() 迭代器类似,不能被解引用。