C++//顺序容器

1. 顺序容器

后文的“前”即首部的方向,“后”为尾部的方向。
forward_list只能递增。从前向后遍历。
C++//顺序容器_第1张图片
允许随机访问的,插入和删除一般不快。插入和删除快的,一般不支持随机访问。deque

forward_list的目的是达到与最好的手写的单向链表数据结构相当的性能,所以不支持size操作,因为保存和计算size会带来开销。

list和forward_list是唯一删除效率高的顺序容器。

没有必需用其他容器的理由,请使用vector。如果需要在中间插入元素且需要随机访问,就比较vector和list的缺点谁带来的损失小。

2. 容器库概览

容器上操作的层次:1. 所有容器类型都提供。 2. 仅针对顺序容器、关联容器、无序容器。 3. 只适用于小部分容器。
所有容器都提供的操作:
C++//顺序容器_第2张图片
C++//顺序容器_第3张图片

容器都定义为模板类。大部分容器需要额外提供元素类型信息。
容器类元素类型可能会影响可以进行的容器操作。如:
在这里插入图片描述
这种容器就不能使用只接受一个参数的构造函数版本。

2.1 迭代器

迭代器有公共接口,所有类型的迭代器对某个操作的实现方式都是一样的(前提是提供该操作)。
迭代器范围是指从begin到end的一个左闭右开区间。
迭代器声明:所访问对象名::iterator 迭代器名。

begin和end有多个版本,带r的版本返回反向迭代器,带c的版本返回const迭代器。如下:
C++//顺序容器_第4张图片
C++//顺序容器_第5张图片
当用容器初始化另一个容器时,两个容器的类型和元素类型必须匹配。当传递迭代器参数只拷贝一个范围时,不要求容器类型和容器元素类型相同,只要元素类型可相互转换即可。新容器大小和范围中元素数目相同,新元素用范围中对应元素的值来初始化
如下:
C++//顺序容器_第6张图片
顺序容器具有和关联容器不同的构造函数:使用值初始化。只有顺序容器才可接受容器大小参数,关联容器不可。值初始化示例如下:
在这里插入图片描述

2.1.1 标准库array

元素类型和容器大小是array类型的一部分,不管是在声明还是在使用中都需要带上
在这里插入图片描述
在这里插入图片描述
默认构造的array是非空的。包含和大小一样多的默认初始化的元素。
虽然不能对内置数组完成数组拷贝和赋值,但对array没有这个限制,只要求双方类型和大小一致。
array可用列表初始化,但是不可用列表赋值
C++//顺序容器_第7张图片

2.2 赋值和swap

赋值运算将左边容器的全部元素替换为右边容器元素的拷贝。要求左边和右边的运算对象具有相同的类型。
赋值运算会导致左边容器的内部迭代器、引用和指针失效。swap操作不会,string除外。

assign操作:assign用参数所指定的元素替换左边容器中的所有元素。且允许从一个不同但是相容的类型拷贝。可以说assign是用于补充赋值运算符的。
assign指定元素有两种方式:1. 迭代器范围。 2. 类似值初始化方式,传递数目以及初始值作为参数。

swap操作:交换两个容器的内容。元素本身并未交换,只交换了容器的内部数据结构
除了array外,swap不对任何元素进行拷贝、删除、插入操作,所以可以保证在常数时间内完成。
swap后,指向容器元素的迭代器、指针和引用仍然指向之前的位置,但是值已经被交换过了。

swap的两个特例,string和array:

swap会真正交换array的元素。交换array的时间和数组长度成正比。
swap两个string对象会导致迭代器、引用和指针失效

2.3 容器大小操作

有三个:size,empty,max_size。
每个容器都支持==!=操作,除了无序容器外的所有容器都支持关系运算符(>,>=,<,<=)。
关系运算符两侧要求容器类型和容器元素类型相同。
容器的比较和string类似。容器的相等运算符实际上是对元素使用==运算符实现的,容器的其他运算符是基于元素的<运算符实现的,所以容器要进行该类关系运算,要求元素能进行对应的运算

3. 顺序容器操作

3.1 插入元素

顺序容器支持的操作:
C++//顺序容器_第8张图片
只有list,forward_list,deque既能往头部插入/删除,也能往尾部插入/删除。
vector,string只能往尾部插入/删除。
array不能用这些东西。

向vector,string的非尾部插入元素会导致元素移动。且无论往哪里插入元素,都导致重新分配对象空间,并将元素从旧空间移动到新空间。
C++//顺序容器_第9张图片
insert主要两部分,第一部分是插入位置,传入一个迭代器指针,后面是插入内容,有各式各样的表达方式。

要注意,当使用迭代器指针来表示范围时,传入的迭代器不能指向添加元素的目标容器。如下:
C++//顺序容器_第10张图片

除了array以外的顺序容器基本都能使用insert。其中forward_list提供了一个特殊版本的insert.

为什么Insert要设置为在迭代器p之前插入?
因为p有可能是尾后迭代器,而且会有在第一个元素之前插入数据的需求,在p之前插入可以保证所有地方都能插入。

insert返回第一个新加元素的迭代器。如果插入范围为空,不插入元素,则会返回第一个参数。示例如下,要理解为什么等价于push_front。
C++//顺序容器_第11张图片
使用insert和push时,需要传入一个创建好的对象,然后将其拷贝到容器内。emplace只需传入参数,然后该成员函数会在容器管理的内存空间内直接构造元素。
C++//顺序容器_第12张图片
第一和第三种方法是不同的,使用push_back不能自动构造新对象,需要显式构造一个临时局部对象然后才能拷贝到容器内。使用emplace会在容器管理的内存空间内直接创建对象。

3.2 访问元素

C++//顺序容器_第13张图片
除了forward_list没有back成员函数外。所有容器包括array都有front和back成员函数。
at和下标运算符区别在于使用at会抛出异常,而不是产生未定义行为。可以通过捕捉异常更好的确定错误的位置。

3.3 删除元素

C++//顺序容器_第14张图片

3.4 forward_list(单向链表)操作

因为单向链表单向访问的特性,想要在某个位置删除或者添加元素,需要获得它的前驱节点。为此,引入了获取容器首元素之前迭代器的成员函数before_begin(),返回一个首前迭代器
鉴于自身实现特点,forward_list版本的insert和erase都是插入/删除**之后的元素。
C++//顺序容器_第15张图片
实际使用时,需要关注两个位置:要处理的位置和其前驱。示例:
C++//顺序容器_第16张图片
在这里插入图片描述

3.5 改变容器大小

array不支持。
C++//顺序容器_第17张图片

3.6 插入或者删除元素对迭代器的影响

C++//顺序容器_第18张图片
C++//顺序容器_第19张图片
不要保存end处的迭代器。当添加/删除vector,string元素,或者在deque尾部添加/删除元素,或者在deque首部添加元素,都会导致尾后指针end失效。因此添加/删除元素的循环程序必须不断调用end。所以C++的end操作被设计的很快,部分也是这个原因

4. vector是如何增长的

为了支持连续访问—>
vector将元素连续存储—>
空间不足不能简单的随便找块空间分配给容器—>
需要开辟一块新空间,把旧元素和新元素全部复制过去,并释放旧空间—>
每次添加新元素都要完成一次分配和复制,开销太大—>
当空间不足时,vector分配比所需的空间大得多的空间,作为备用,这样不用每次都分配和复制。具体多分配多少随标准库具体实现而定,C++是两倍。

4.1 管理容量的成员函数

容量指的是容器最大能存储的元素数目,不是空余空间数
C++//顺序容器_第20张图片
reserve函数,只有当当前容量小于所需容量n时才起作用,分配大于等于n的空间。否则,reserve什么也不做,也不会缩减当前容量。
shrink_to_fit请求将空闲空间退还给内存,但只是请求,标准库不保证退还。

5. 额外string操作

5.1 创建string

C++//顺序容器_第21张图片
s可以是string也可以是const char*。
如果参数没有给定长度和起始位置,在使用char初始化时,拷贝操作会遇到空字符才停止,所以char结尾最好是"\0";
C++//顺序容器_第22张图片
在这里插入图片描述

5.2 修改string

string提供了下标版本的插入和删除,以下标方式指明插入/删除的位置。
在这里插入图片描述
除此以外,string还提供了C风格的insert和assign(用char* 代替string):
在这里插入图片描述

5.3 额外的append和replace

C++//顺序容器_第23张图片
string重载函数参数的说明:
C++//顺序容器_第24张图片

5.4 string搜索

搜索操作返回一个string::size_type的值,如果搜索失败,返回一个string::nops的static成员,nops被设置为const string::size_type类型,初始化为-1,由于size_type是unsigned的,所以nops事实上是最大的size_type值。
C++//顺序容器_第25张图片
C++//顺序容器_第26张图片

5.5 compare函数

根据s是大于、等于、还是小于参数指定的字符串,返回整数、0、负数。
C++//顺序容器_第27张图片

5.6 string转数值

C++//顺序容器_第28张图片

在这里插入图片描述

6. 容器适配器

适配器是一种机制,使得某个事物的行为看起来像另一种事物。
容器、函数、迭代器都有适配器。
容器适配器接受一种已有容器类型,使他看起来向另一种类型。使用容器适配器后不能使用底层容器的操作,只能用适配器的操作管理内存。
C++//顺序容器_第29张图片
默认情况下,stack和queue是基于deque实现的,priority_queue是基于vector实现的。但是可以在创建适配器时指定底层实现所用的容器:
C++//顺序容器_第30张图片
这种指定也并非毫无限制。如下:
stack适配器:要求在尾部插入,弹出,和访问尾首元素。底层可用除了array和forward_list。
C++//顺序容器_第31张图片

queue适配器:要求要求能访问首尾元素,不能基于vector构造,可使用list和deque。
priority_queue适配器:要求随机访问能力。不可基于list构造。和队列queue的区别在于,queue是先进先出的,priority是优先级高的先出,什么时候进不重要,可以理解为常规队列,但是优先级高的能插队。
priority_queue无法访问尾元素。
它可以在O(n log n)的时间排序数组,O(log n) 的时间插入任意值,O(1) 的时间获得最大值,O(log n) 的时间删除最大值。priority_queue 常用于维护数据结构并快速获取最大或最小值。
C++//顺序容器_第32张图片
C++//顺序容器_第33张图片

你可能感兴趣的:(C++,c++,开发语言)