容器由序列式容器和关联式容器构成,前者按是线性的,后者则是以键:值形式的数据结构(set和unordered_set的键值是一样的)。
序列式容器:其中的元素都可序,但未必有序。
vector
可变长数组 vector的特点在于物理上其位于一个线性的空间,但是其长度是可以扩充的——这体现在两个方面,一个是分配内存的时候并不是以当前元素个数来分配,而是会多一些,这使其获得了变长的能力,其二是当预分配的内存满了之后,它会找一个2倍于当前长度的空间,并整体移动过去。
vector的成员有三个指针:
如上文所言,当vector预先分配的空间被使用完毕后,vector会进行空间扩容,一个2倍于当前长度的空间,并整体移动过去,对应的开销是很大的,因此若是进行若干次插入且其值很大的话,可以提早用reserve
使其预分配一个足够大的空间,这样就省去其空间多次增长和移动的开销了。
因为是一整块的连续空间,因此迭代器遍历非常方便,早期的vector迭代器甚至是原生指针。
插入元素会导致其后的迭代器失效,若进行扩容则迭代器全部失效。
迭代器可以任意访问其中元素,因此是random_access类型
deque
双向队列 双向队列,顾名思义,可以向着两个方向扩充,与vector不同。它的特点是分段连续,通过多个相同长度的小区域拼接而成,因此扩充相对容易。不过也因为其物理上实际上是不连续的,因此迭代器的设计更为复杂。
为了管理一个个小区块,deque内部有一个数组(其名为map),其指向每个小区块,区块中的每个元素类型为T,因此指向小区块的指针为T*,因此指向map的指针为T**类型。
迭代器有4个部分:
因为物理上是分段的,因此迭代器在前进后退的时候需要有跳转功能,有时要跳转多个区块,这可以通过 len/size of buffer 计算出来,然后在map中直接跳转,同时两个迭代器进行相减(获得它们相差的距离)也不能直接相减,而是需要计算它们直接差的小区块。
因为deque是顺序的,对其操作也会导致其迭代器失效,比如插入、删除、map扩容等等。
虽然物理上不连续,但是deque还是可以通过O(1)的复杂度来访问其成员,因此是random_access类型
list
双向环形链表 list为双向环形链表,其头指针指向一个空白节点,即尾节点(end的位置),尾节点的next指向的节点即开始位置(begin)。双向说明其指针域有两个指针,指向前一个元素和后一个元素。因此若是list为空,则空白节点的next和pre都指向自己。
链表本身一般只有指向特定位置的指针,链表指向的节点还需另外定义。
链表节点被继承为两个部分,父类为指针域,子类为数据域,我的猜测是这样可以通过父类指针直接指向子类而不用关注子类中数据的类型。(早起没有使用继承关系的时候指针域是void*类型)
一个指针,指向空白节点。因为是双向环形链表,因此一个指针就可以很方便地找到其头尾。
还有一个显示其长度的值。(根据侯捷老师的课件,早期没有,获取长度需要遍历一遍)
迭代器其实就是一个指针,指向节点即可。
链表插入删除不会影响其他节点,因此指向其他节点的迭代器是不会失效的。
双向访问,且不能随机访问,因此是bidirectional类型
因为sort需要random_access_iterator类型的迭代器,因此list自己定义了对应的排序方法,其为一个很巧妙的非递归归并排序。
其使用一个数组(用来挂载链表),长度为64,说明其上限为2^64
主要思路:数组的第i个位置的上限为2^(i+1) -1,因此第0个位置可以挂载0/1个节点,挂载2个节点后会发生转移,将其转移到第1个位置上,在第1个位置上从0->2->4后,也会发生转移,将4个节点转移到第2个位置上,这样一直向上转移,在转移的时候使用merge操作对其进行合并,因此在最高位置的那一串链表以及其他零散的链表在进行merge就是排序结果。
链表合并的大致过程:
第i位置: 0 1 2 3 4
1
0 2
1 2
0 0 4
1 0 4
0 2 4
1 2 4
0 0 0 8
上面的数字是对应位置有几个节点,就这样一次次往上传递
可供参考:list的sort图解
forward_list
单向链表 C++11新增的容器,结构与list几乎完全,据说被用于对性能比较敏感的时候。
为forward类型,即只能往后前进(因此只有++运算符)
stack
stack即采用先进后出,是数据结构中一个非常重要的部分。
stack实际上就是对内部封装的其他容器的操作,因此被称为容器适配器(container adapter)
只要拥有stack对其内部使用的方法,这个容器便可以称为stack的底层容器,因此对应容器有: deque(默认类型),list,vector
内部封装的容器
因为操作都是内部容器实现,且要实现严格的先进后出,所以stack没有遍历功能也没有迭代器。
queue
queue即采用先进先出,同样是数据结构中一个非常重要的部分。
和stack一样,queue内部封装了一个容器,因此也是一个容器适配器。
可以完全适配的有两个容器:deque(默认)和list。
只有一个,即内部封装的容器
queue同样不提供遍历功能和迭代器。
heap
:被定义于算法形式 heap即堆,为一种特殊的二叉树,其根节点的值比子树的节点都要小/大,对应的为小/大根堆。
因为堆为满二叉树结构,因此可以将其的物理结构转换为一个列表,从0开始的话,第i个节点的子节点的位置是2*i+1
与2*i+2
。
heap实际上被实现为算法形式,通过其对序列式容器进行操作,从而达成对应的效果(即位置在
,且为函数模板的形式。
priority_queue
优先队列 优先队列即内部封装了一个vector(默认),对其操作实际上都是heap相关操作,因此它也是一个容器适配器。
优先队列提供类似队列的操作,只允许push和pop进行修改,插入的元素会按照堆的规则进行排列(push_heap)。
在定义的时候可以指定其compare函数,即比较规则,因此可以自定义是大根堆还是小根堆。