序列容器
Vector
概念
- vector维护的迭代器有三个:
- start:目前使用空间的头
- end:目前使用空间的尾
- end_of_storage:目前可用空间的尾
- 增加新元素时,如果超过当时的容量,则容量则会扩充2倍。扩充不是在原有空间上新增,而是需要重新配置、元素移动、释放原空间等过程。
- 当然,vector的每种实现都可以自由地选择自己的内存分配策略,分配多少内存取决于其实现方式,不同的库采用不同的分配策略。
- 2倍增长使得新分配的内存大小永远大于之前的总和,对cache不友好。
参考网站:https://www.zhihu.com/question/36538542
- 随机访问每个元素,所需要的时间为常量。
- 在末尾增加或删除元素所需时间与元素数目无关,在中间或开头增加或删除元素所需时间随元素数目呈线性变化。
迭代器失效
- 对于insert操作
- 若不大于capacity,则插入点及其之后的元素后移,即插入点及其之后的迭代器失效。
- 若大于capacity,则引起内存重新分配,所有迭代器失效
- 对于erase,则删除点之后的元素前移,即删除点及其后的迭代器失效
insert函数中的一些疑问
vector<T,Alloc>::insert(iterator position,size_type n,
const T& x)
{
if(n!=0)
{
if(size_type(end_of_storage-finish)>=n)
{
T x_copy=x;
const size_type elems_after=finish-position;
iterator old_finish=finish;
if(elems_after>n) //插入点后元素个数 大于 新增元素个数
{
uninitialized_copy(finish-n,finish,finish);
finish+=n;
copy_backward(position,old_finish,finish);
fill(position,position+n,x_copy);
}
else //插入点后元素个数 小于 新增元素个数
{
uninitialized_fill_n(finish,n-elems_after,x_copy);
finish+=n-elems_after;
uninitialized_copy(position,old_finish,finish);
finish+=elems_after;
fill(position,old_finish,x_copy);
}
}
//后面与本次主题无关,不再续写
}
}
疑问一:为什么不直接先copy_backward,然后fill
首先我们来看看copy_backward()函数
template<class> BidirectionalIterator1, class BidirectionalIterator2>
BidirectionalIterator2 copy_backward ( BidirectionalIterator1 first,
BidirectionalIterator1 last,
BidirectionalIterator2 result )
{
while (last!=first) *(--result) = *(--last);
return result;
}
明显,其中调用了对象的operator=(),如果不调用uninitialized_xxxx,相当于把raw mem当作一个对象,
比如: p = (obj*)raw_mem
- POD对象:直接调用operator=()没问题
- non—POD对象:non-POD的oprator=()函数会先析构再重新构造,然而对raw_mem进行析构无疑是错误的
参考网站:https://www.zhihu.com/question/29993514?sort=created
疑问二:为什么要判断新增元素个数与插入点后的元素个数
uninitiated_xxxx是对未初始化的地址(raw mem)进行操作
- 插入点后元素个数 < 新增元素个数:只需要用uninitiated_copy将插入点后的元素实现后移,再分别用uninitiated_fill和fill填充元素就行
- 插入点后元素个数 > 新增元素个数:用uninitiated_copy和copy_backward实现后移,再用fill填充元素。
List
概念
- 维护一个指针node
- node是一个位于尾端的空白节点
- node.next为第一个元素的节点
- node.pre为最后一个元素的节点
- node是一个位于尾端的空白节点
- 不能随机访问一个元素,可双向遍历。
- 在开头、末尾和中间任何地方增加或删除元素所需时间都为常量。
迭代器失效
- 对于insert操作:如同链表中插入节点,不会影响其他节点,即所有迭代器都不会失效。
- 对于erase操作:如同链表中删除节点,只有指向被删除元素的迭代器失效。
Deque
概念
- Deque维护一个中控器,两个迭代器
- map:中控器。map是一小块连续空间,其中每个元素(此处称为一个节点,node)都是指针,指向另一段(较大的)连续线性空间,称为缓冲区。缓冲区才是deque的储存空间主体。
- map_size:map内指针个数
- start:指向第一个node的迭代器
- finish:指向最后一个node的迭代器
- 随机访问每个元素,所需要的时间为常量。
- 在开头和末尾增加元素所需时间与元素数目无关,在中间增加或删除元素所需时间随元素数目呈线性变化。
迭代器失效
- 对于insert
- 如果在首尾,函数内已经维护了迭代器,所以不会有迭代器失效
- 如果不在首尾,插入点左右两侧中元素较少的一侧会相外移,此侧的迭代器及插入点的迭代器失效,另一侧的迭代器不失效。
- 如果map一端节点不足
- 如果map仍有空间,则map节点重排,所有迭代器失效
- 如果map没有空间,则map重新分配内存,所有迭代器失效
- 对于erase
- 如果在首尾,函数内已经维护了迭代器,所以不会有迭代器失效
- 如果不在首尾,删除点左右两侧中元素较少的一侧会相内移,此侧的迭代器及删除点的迭代器失效,另一侧的迭代器不失效。
迭代器失效应用举例
http://www.cppblog.com/Herbert/archive/2009/01/08/70479.html
Stack
- 属于adapter配接器:修改一个类的接口,形成另一种接口。
- 底部容器可以为deque、list
- 元素只能后进先出(LIFO)
- 不允许遍历,没有迭代器
queue
- 属于adapter配接器:修改一个类的接口,形成另一种接口。
- 底部容器可以为deque、list
- 元素只能先进先出(FIFO)
- 不允许遍历,没有迭代器
heap
- 堆的实现通过构造二叉堆(binary heap),这是一种complete binary tree
- 在SGI STL中用vector来实现堆,则当某个节点位于i处时,其左子节点和右子节点分别为2i,2i+1,父节点为i/2.
- 不提供遍历,没有迭代器
priority_queue
- 只能访问第一个元素,不能遍历整个priority_queue,不提供迭代器。
- 适配器,priority_queue默认使用vector作为底层容器,默认会在构造时通过max-heap建堆。
slist
- 不可双向遍历,只能从前到后地遍历,其余类似list
- 维护头节点head(不是指针)