[STL源码剖析]序列容器

序列容器

Vector

概念
  1. vector维护的迭代器有三个:
    • start:目前使用空间的头
    • end:目前使用空间的尾
    • end_of_storage:目前可用空间的尾
  2. 增加新元素时,如果超过当时的容量,则容量则会扩充2倍。扩充不是在原有空间上新增,而是需要重新配置、元素移动、释放原空间等过程。
    • 当然,vector的每种实现都可以自由地选择自己的内存分配策略,分配多少内存取决于其实现方式,不同的库采用不同的分配策略。
    • 2倍增长使得新分配的内存大小永远大于之前的总和,对cache不友好。
      参考网站:https://www.zhihu.com/question/36538542
  3. 随机访问每个元素,所需要的时间为常量。
  4. 在末尾增加或删除元素所需时间与元素数目无关,在中间或开头增加或删除元素所需时间随元素数目呈线性变化。
迭代器失效
  • 对于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

概念
  1. 维护一个指针node
    • node是一个位于尾端的空白节点
      • node.next为第一个元素的节点
      • node.pre为最后一个元素的节点
  2. 不能随机访问一个元素,可双向遍历。
  3. 在开头、末尾和中间任何地方增加或删除元素所需时间都为常量。
迭代器失效
  • 对于insert操作:如同链表中插入节点,不会影响其他节点,即所有迭代器都不会失效。
  • 对于erase操作:如同链表中删除节点,只有指向被删除元素的迭代器失效。

Deque

概念
  1. Deque维护一个中控器,两个迭代器
    • map:中控器。map是一小块连续空间,其中每个元素(此处称为一个节点,node)都是指针,指向另一段(较大的)连续线性空间,称为缓冲区。缓冲区才是deque的储存空间主体。
    • map_size:map内指针个数
    • start:指向第一个node的迭代器
    • finish:指向最后一个node的迭代器
  2. 随机访问每个元素,所需要的时间为常量。
  3. 在开头和末尾增加元素所需时间与元素数目无关,在中间增加或删除元素所需时间随元素数目呈线性变化。
迭代器失效
  • 对于insert
    • 如果在首尾,函数内已经维护了迭代器,所以不会有迭代器失效
    • 如果不在首尾,插入点左右两侧中元素较少的一侧会相外移,此侧的迭代器及插入点的迭代器失效,另一侧的迭代器不失效。
    • 如果map一端节点不足
      • 如果map仍有空间,则map节点重排,所有迭代器失效
      • 如果map没有空间,则map重新分配内存,所有迭代器失效
  • 对于erase
    • 如果在首尾,函数内已经维护了迭代器,所以不会有迭代器失效
    • 如果不在首尾,删除点左右两侧中元素较少的一侧会相内移,此侧的迭代器及删除点的迭代器失效,另一侧的迭代器不失效。

迭代器失效应用举例

http://www.cppblog.com/Herbert/archive/2009/01/08/70479.html

Stack

  1. 属于adapter配接器:修改一个类的接口,形成另一种接口。
    • 底部容器可以为deque、list
  2. 元素只能后进先出(LIFO)
  3. 不允许遍历,没有迭代器

queue

  1. 属于adapter配接器:修改一个类的接口,形成另一种接口。
    • 底部容器可以为deque、list
  2. 元素只能先进先出(FIFO)
  3. 不允许遍历,没有迭代器

heap

  1. 堆的实现通过构造二叉堆(binary heap),这是一种complete binary tree
  2. 在SGI STL中用vector来实现堆,则当某个节点位于i处时,其左子节点和右子节点分别为2i,2i+1,父节点为i/2.
  3. 不提供遍历,没有迭代器

priority_queue

  1. 只能访问第一个元素,不能遍历整个priority_queue,不提供迭代器。
  2. 适配器,priority_queue默认使用vector作为底层容器,默认会在构造时通过max-heap建堆。

slist

  1. 不可双向遍历,只能从前到后地遍历,其余类似list
  2. 维护头节点head(不是指针)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值