stl源码剖析-序列式容器 之 vector

  vector的数据安排以及操作方式,与array(c++自身提供的序列式容器)非常相似。两者唯一的差别在于空间的运用的灵活性。array是静态空间,一旦配置了将不能随意更改其大小,若要更改需要重新配置一块新的空间,如何将元素从旧址中一一搬迁,再释放原来的系统。而vector是动态空间,随着元素的加入,它的内部机制会自行扩充空间已容纳新元素。(实际vector每次动态分配的空间大小是有限的,超过了这个临界值同样是要像array一样进行元素搬迁,下面将会进行详细介绍)vector 是最常用的 C++ 容器,其动态扩容的特性是普通数组无法具备的,大大增加了编程的灵活性。

vector迭代器

  vector维护的是一个连续线性空间,所以不论其元素型别为何,普通指针都可以作为vector的迭代器而满足所有必要条件,因为vector迭代器所需要的操作,如operator*、operator->、operater++、operater--、operator-、operator+、operator+=、operator-=,普通指针天生就具备。vecotr支持随机存取,而普通指针正有这样的能力,所以,vecotr提供的是Random Access Iterators。

 template<class T, class Alloc = alloc>  
  class vector{  
  public:  
      typedef T   value_type;  
      typedef value_type* iterator;//vector的迭代器是普通指针  
      ...  
  };

vector数据结构

iterator start;//指向vector目前使用空间头,即vector.begin()

iterator finish;//指向vector目前使用空间尾,即vector.end()

iterator end_of_storage;//指向vector目前可用空间尾

  为了降低空间配置时的开销成本,vector每次配置的大小会比需求量更大,剩余的空间用于将来扩充的需要,这就是容量(capacity)的观念。也就是说,一个vector的容量永远大于或等于其大小,一大容量等于其大小,就是满载,下次再有元素新增,将要像array进行元素搬迁一样,另外开辟一个空间移动已有元素,另外开辟空间的大小一般情况下是现有空间大小的两倍。

  运用 start,finish,end_of_stroage三个迭代器,可以轻易地提供首尾标示、大小、容量、空容器判断...等等机能

template<class T, class Alloc = alloc>  
  class vector{
  ...
  public:
      iterator begin() {return start;}
      iterator end() {return finish;}
      size_type size() const {return size_type(end()-begin());}
      size_type capacity const{
          return size_type(end_of_storage-begin());
      }
      bool empty const{return begin()==end();}
      reference operator[](size_type n){return *(begin()+n);}
      
      reference front(){return *begin();}
      reference back(){return *(end()-1);}
  ...
  }

vector的构造与内存管理

  以下是vector配置空间过程的部分代码:

//vector提供的许多constructor
vector() : start(0), finish(0), end_of_storage(0) {}
    vector(size_type n, const T& value) {
        fill_initialize(n, value);
    }
    vector(int n, const T& value) {
        fill_initialize(n, value);
    }
    vector(long n, const T& value) {
        fill_initialize(n, value);
    }
    explicit vector(size_type n) {
        fill_initialize(n, T());
    }
....
 //填充并予以初始化
void fill_initialize(size_type n, const T& value) {
        start = allocate_and_fill(n, value);
        finish = start + n;
        end_of_storage = finish;
    }
 ....
//配置而后填充
iterator allocate_and_fill(size_type n, const T& x) {
        iterator result = data_allocator::allocate(n);
        __STL_TRY
        {
            uninitialized_fill_n(result, n, x);
            return result;
        }
        __STL_UNWIND(data_allocator::deallocate(result, n));
    }
//uninitialized_fill_n会根据第一参数的型别来决定算法使用fill_n()或者反复调用construct()来完成任务

  当我们以push_back()将新元素插入vector尾端时,该函数先检查是否还有备用空间,如果有就直接在备用空间上构造元素,并调整迭代器finish,使vector变大。不过没有备用空间,就扩充空间(重新配置、移动数据、释放原空间):

void push_back(const T& x) {
        if (finish != end_of_storage) {  //还有备用空间
            construct(finish, x);  //全局函数
            ++finish;
        } else  //无备用空间
            insert_aux(end(), x);  //成员函数,后续会分析
}
....
template <class T, class Alloc>
void vector<T, Alloc>::insert_aux(iterator position, const T& x) {
    if (finish != end_of_storage) {  //还有备用空间
        //在备用空间的起始处构造一个元素,并以 vector 最后一个元素值为其初值
        construct(finish, *(finish - 1));
    //调整水位
        ++finish;
        T x_copy = x;
        //全局函数,从后面finish-1往前复制[position,finish-2]的值
        copy_backward(position, finish - 2, finish - 1);
        *position = x_copy;
    } else { //已无后备空间
        const size_type old_size = size();
        const size_type len = old_size != 0 ? 2 * old_size : 1;  //为当前空间两倍大小
        iterator new_start = data_allocator::allocate(len);
        iterator new_finish = new_start;
        __STL_TRY
        {
            //复制[start,position)复制到new_start,new_finish为new_start+(position-start)
            new_finish = uninitialized_copy(start, position, new_start);
            construct(new_finish, x);
            ++new_finish; //调整水位
            //复制[position,finish)复制到new_finish
            new_finish = uninitialized_copy(position, finish, new_finish);
        }
        catch(...) {
            destroy(new_start, new_finish);
            data_allocator::deallocate(new_start, len);
            throw;
        }
        destroy(begin(), end()); //调用旧空间元素的析构函数
        deallocate(); //释放旧空间
        start = new_start;
        finish = new_finish;
        end_of_storage = new_start + len;
 }
}

  所谓动态增加大小,并不是在原空间之后接续空间(因为无法包装原空间之后尚有可配置的空间),而是以原大小的两倍另外配置一块较大的空间,然后将原来内容拷贝过来,然后才开始在原内容之后构造新元素,并释放原空间。因此对vector的任何操作,一旦引起空间重新配置,指向原vector的所有迭代器就都失效了。

vector的元素操作:pop_back,erase,clear,insert

  pop_back():

void pop_back() {
        --finish;
        destroy(finish);    //finish->~T 这里仅仅是调用指针finish所指对象的析构函数,不能释放内存
    }

  erase():

iterator erase(iterator first, iterator last) {
        iterator i = copy(last, finish, first);
        destroy(i, finish);
        finish = finish - (last - first);
        return first;
    }

iterator erase(iterator position) {

        if (position + 1 != end())
            copy(position + 1, finish, position);  //全局函数

        --finish;
        destroy(finish);
        return position;
    }

  

  clear():

 void clear() { erase(begin(), end()); }

  insert():

template <class T, class Alloc>
void vector<T, Alloc>::insert(iterator position, size_type n, const T& x) {
    if (n != 0) {  //当 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 - n, old_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);
            }
        } else {
            //备用空间小于新增元素个数(那就必须配置额外的内存)
            //首先决定新长度:旧长度的两倍,或旧长度 + 新增元素个数
            const size_type old_size = size();
            const size_type len = old_size + max(old_size, n);
            //以下配置新的vector空间
            iterator new_start = data_allocator::allocate(len);
            iterator new_finish = new_start;
            __STL_TRY
            {
                //旧vector插入点之前的元素复制到新空间
                new_finish = uninitialized_copy(start, position, new_start);
                //新增元素填入新空间
                new_finish = uninitialized_fill_n(new_finish, n, x);
                //旧vector插入点之后的元素复制到新空间
                new_finish = uninitialized_copy(position, finish, new_finish);
            }
            catch(...) {
                //如果发生异常,实现commit和rollback操作
                destroy(new_start, new_finish);
                data_allocator::deallocate(new_start, len);
                throw;
            }
            //以下清除并释放旧的vector
            destroy(start, finish);
            deallocate();
            //以下调整水位
            start = new_start;
            finish = new_finish;
            end_of_storage = new_start + len;
        }
    }
}

  插入完成后,新节点将位于哨兵迭代器所指节点的前方,这是stl对于插入操作的标准规范,下图展示来插入操作:

 

 

原文地址:https://www.cnblogs.com/LEEYATWAH/p/11704647.html

时间: 2024-10-28 13:41:15

stl源码剖析-序列式容器 之 vector的相关文章

stl源码剖析-序列式容器 之 list

较久以前学过数据结构,对链表的定义和行为结构有过了解,所以阅读源码学习stl定义的list容器的并不算吃力. list与vector都是两个常用的容器,与vector不同,list不是连续线性空间的,list是一个双向链表.每次插入或者删除一个元素,将配置或者释放一个元素空间,因此,list对于空间的运用有着绝对的精准,不会造成浪费现象.而且对于任何位置的元素插入或者删除,其操作时间永远是常数时间.(缺点是不能进行随机的访问) list节点 list链表本身和list节点是分开设计的,以下是li

STL源码剖析——序列式容器#2 List

list就是链表的实现,链表是什么,我就不再解释了.list的好处就是每次插入或删除一个元素,都是常数的时空复杂度.但遍历或访问就需要O(n)的时间. List本身其实不难理解,难点在于某些功能函数的实现上,例如我们会在最后讨论的迁移函数splice().反转函数reverse().排序函数sort()等等. list的结点 设计过链表的人都知道,链表本身和链表结点是不一样的结构,需要分开设计,这里的list也不例外,以下是STL list的结点结构: 1 template <class T>

STL源码剖析(4):容器(vector)

容器是很多人对STL的第一印象,vector,stack,queue,set,map等等都是容器. 这里先介绍 STL中的序列式容器. 所谓序列式容器,其中的元素可序(ordered),但未必有序(sorted).C++ 本身提供了一个序列式容器--数组(array),STL中还提供了向量(vector),链表(list),堆栈(stack),队列(queue),优先队列(priority queue)等,其中stack和queue只是将deque(双端队列)设限而得到的,技术上可以被归为一种配

STL源码剖析—序列容器

对于STL中的容器,存在一定的内含关系,例如,heap内含一个vector,priority-queue内含一个hep,stack和queue都含有一个deque,set/map/multiset/multimap都内含一个RB-tree,hash_x都内含一个hashtable. 对于序列容器来说,vector和list的插入都是在指向迭代器之前进行插入.同时需要注意的就是,对于vector来说,调用erase()函数也就是清除了几个连续空间上的元素,调用其析构函数,并没用释放原来元素占用的内

STL源码剖析(4):容器(list)

相较于vector的连续线性空间,list就显得复杂许多,它的好处是每次插入或删除一个元素,就配置或释放一个元素空间.因此,list对于空间的运用有绝对的精准,一点也不浪费.而且,对于任何位置的元素插入或元素移除,list永远是常数时间. list 内部为双向链表,内部元素互相以link串接起来,每个元素都知道其前一个元素以及下一个元素的位置. template <class T> struct __list_node { typedef void* void_pointer; void_po

STL源码剖析 容器 stl_vector.h

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie vector ---------------------------------------------------------------------- 描述: 1.迭代器 vector 维护的是一个连续线性空间,它的迭代器是普通指针, 能满足 RandomAccessIterator 所有必要条件:operator*, operator->,operator++,operator--,

STL源码剖析 容器 stl_set.h

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie set ------------------------------------------------------------------------ 所有元素都会根据元素的键值自动被排序. 不可以通过 set 的迭代器改变 set 的元素值.因为 set 元素值就是其键值,关系到 set 元素的排列规则. set<T>::iterator 被定义为底层 RB-tree 的 const

STL源码剖析 容器 stl_tree.h

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie RB-tree(红黑树) -------------------------------------------------------------------------- 平衡二叉搜索树 --> 平衡可提高搜索效率 常见的平衡二叉搜索树有: AVL-tree(任何节点的左右子树高度相差最多 1).红黑树.AA-tree AVL-tree 破坏平衡的情况及恢复平衡的方法 恢复时要先找到失

STL源码剖析 容器 stl_map.h

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie map -------------------------------------------------------------------------------- 所有元素都会根据元素的键值自动被排序. map的所有元素都是 pair,同时拥有实值和键值. 不可以修改元素的键值,因为它关系到 map 元素的排列规则 可以修改元素的实值,因为它不影响 map 的排列规则 map ite