元素在顺序容器中的顺序与其加入容器时的位置相对应。
关联容器中的元素位置由元素相关联的关键字值决定所有容器类都共享公共的接口,不同的容器按照不同的方式对其进行拓展。
- 一个容器就是一些特定类型对象的集合。顺序容器为程序员提供了控制元素存储和访问顺序的能力。
- 访问顺序不依赖于元素的值,而是与元素加入容器时的位置相对性。
- 标准库提供了三种容器适配器,分别为容器操作定义了不同的接口,来与容器类型适配。
1.1 顺序容器概览
所有的顺序容器都提供了快速访问元素的能力,但是这些容器在以下方面都有不同的性能折中:
- 向容器中添加或者从容器中删除元素的代价
- 非顺序访问元素的代价
顺序容器类型 | |||
---|---|---|---|
array | 固定大小数组 | 支持快速随机访问 | 不能添加或者删除 |
vector | 可变大小数组 | 支持快速随机访问 | 在尾部之外的位置插入或者删除元素可能很慢 |
string | 与vector相似的容器,但是专门用来保存字符 | 支持快速随机访问 | 在尾部插入和删除速度很快 |
deque | 双端队列 | 支持快速随机访问 | 在头尾位置插入和删除速度很快 |
list | 双向链表 | 支持双向顺序访问 | 在list中任何位置进行插入/删除操作速度都很快 |
forward_list | 单向链表 | 只支持单向顺序访问 | 在链表中任何位置进行插入/删除操作速度都很快 |
- 除了固定大小的array外,其他容器都提供高效灵活的内存管理,我们可以添加和删除元素。
- string和vector将元素保存在连续的内存空间中,根据元素下标来计算地址是非常快速的,但是在这两种元素中间位置添加或者删除元素就会非常耗时。
- list和forward_list两个容器的设计目的是另容器的中间位置添加或者删除元素都很快,最为代价这两个容器不支持随机访问。
- deque支持快速随机访问,与string和vector一样,在deque的中间位置添加或者删除元素的代价很高,在两端添加和删除元素很快。
- forward_list和array是C++新增的类型,与内置数组相比,array是一种更安全,更容易使用的数组类型。
1.2 容器库概览
- 有些操作是所有容器类型都提供的
- 另外一些只支持顺序容器或者关联容器或者无序容器
- 还有一些操作只适用于一小部分容器
一般来说,每个容器都定义在一个与类型名相同的头文件中
- queue定义在queue
- list定义在list
- 以此类推
顺序元素对保存的元素类型的限制
- 顺序元素几乎可以保存任意类型的元素,但是某些容器操作对元素类型有其特殊要求。
- 可以为不支持特定操作的类型定义容器,这种情况下只能使用那些没有特殊要求的容器操作。
容器操作 | ||
---|---|---|
类型别名 | ||
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(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中元素的数目(不支持forword_list) |
c.max_size() | c可保存的最大元素数目 |
c.empty() | 若c中存储了元素,返回false |
添加删除元素 | 在不同的容器中中这些接口都不同 |
---|---|
c.insert(args) | 将args中的元素拷贝进c |
c.emplace(inits) | 使用init构造c中的一个元素 |
c.erase(args) | 删除args指定的元素 |
c.clear(); | 删除c中所有的元素,返回void |
关系运算符 | |
---|---|
== != | 所有容器都支持 |
<,<=,>,>= | 关系运算符(无序容器不支持) |
获取迭代器 | |
---|---|
c.begin(),c.end() | 首位迭代器 |
c.cbegin(),c.cend() | 返回const_iterator |
反向容器的额外成员 | 不支持forward_list |
---|---|
reverse_iterator | 按逆序寻址元素迭代器 |
const_reverse_iterator | 不能修改元素的逆序迭代器 |
迭代器
迭代器
- 一个迭代器范围由一对迭代器表示。
- 元素范围为左闭右开[begin,end)
容器类型成员
begin()和end()成员
//显式制定类型
list<string>::iterator it5=a.begin();
list<string>::const_iterator it6=a.begin();
//当且仅当a是const时,it7是const_iterator
auto it7=a.begin();
auto it8=a.cbegin();//是const_iterator
容器的定义和初始化
构造函数 | |
---|---|
C c; | 默认构造函数 |
C c1(c2); | 构造c2的拷贝c1 |
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 c1(c2) c1=c2
。要求两个容器类型及其元素类型必须匹配。 - 拷贝由一个迭代器对指定的元素范围
C(b,e)
,不要求容器类型和元素类型是相同的,只要将要拷贝的元素转换为要初始化的容器的元素类型即可。
列表初始化
与顺序容器大小相关的构造函数
除了与关联容器相同的构造函数外,顺序容器(array除外)还提供宁外一种构造函数。接受一个容器大小和一个(可选的)元素初始值,如果不提供初始值,则标准库会创建一个值初始化器。
vector<int> ivec(10,-1);
list<string> svec(10,"hi!");
forward_list<int> ivec(10);//0
deque<string> svec(10);//空sting
没有默认构造参数的类型必须提供初始值。
只有顺序容器洁柔大小参数,关联容器不支持
标准库具有固定大小
当定义一个array时,除了指定数组元素,还要指定容器大小。
内置数组类型不能进行拷贝或者对象赋值操作,array可以。
要求要求两个容器类型及其元素类型必须匹配,而且大小也得一样。
array<int,10> ial;
array<int,10> ia2={
0,1,2,3,4};
3.顺序容器的操作
上一节说了顺序容器和关联容器都支持的操作,下面介绍支持顺序容器所特有的操作。
3.1 向顺序容器中添加元素
除了array,其他的顺序容器都可以动态添加或者删除元素。
下表array统统不适用
push_back
- 所有顺序容器vector,string,list,forward_list,deque包括string都可以push_back
push_front
- list,forward_list,deque,支持push_front,vector,string不行
指定位置插入insert
- vector,list,string,deque,都支持insert,forward_list支持特殊版本的insert。
- insert第一个参数是迭代器,指向插入元素位置的后一个元素
- 可以把insert用在没有push_front操作的容器中,比如vector,string,但是除了末尾位置都会很慢
svec.insert(svec.begin(),"hello");
- 把insert用在vector,string,deque,中间任何位置都可以,但是会很好事
insert 的几个版本
1.svec.insert(svec.end(),10,"anna");
2. svec.insert(svec.end(),v.end()-2,v.end());
3. svec.insert(svec.end(),{1,3,4,5});
第二种方法迭代器不能指向本身
使用insert的返回值可以在一个位置反复插入元素
list<string> lst;
auto iter=lst.begin();
while(cin>>word)
iter=lst.insert(iter,word);
使用emplace
- 新标准出了emplace_front,emplace和emplace_back
- 使用emplace_back时,将参数传递给元素类型的构造函数,会在容器管理的内存空间中直接创建对象,而调用push_back则会创建一个临时变量,并将其压入容器中。
3.2 访问元素
- 所有容器都有一个front函数
- 除了forward_list都有back函数
- 用这两个一定得保证容器不为空
if(!c.empty()){
auto val=*c.begin(),val2=c.front();
auto vals=*(--c.end());
auto val4=c.back();
}
- 访问(at,下标,front,back)返回的都是引用,如果是const对象返回的是const引用,如果不是conts,则返回的是普通引用,可以改变元素值;
while(!c.empty()){
c.front()=42;//改变值
auto &v=c.back();
v=43;
auto v2=c.back();
v=0;//未改变不是一个引用}
3.3删除元素
下表除array
3.5 改变容器大小
list<int> ilist(10,42);
ilist.resize(15);//+五个0
ilist.resize(25,-1);//加10个-1
ilist.resize(5);//删除20个元素
容器操作可能使迭代器失效
当向容器中添加删除元素的操作可能使指向容器元素的指针,引用迭代或者器失效
不要保存end返回的迭代器
必须反复调用end,不能在循环之前保存end返回的迭代器
vector<char> s={
'a','b','c','d'};auto b=s.begin();auto e=s.end();e--;while(b!=e){
char tmp=*b;*b=*e;*e=tmp;b++;e--;}
4.vector 对象是如何增长的