【c++从菜鸡到王者】第一篇:STL之主要序列容器

第一篇

基础

c++STL 容器大纲呈现

  1. 分配器(空间配置器):一般的分配器的std:alloctor都含有两个函数allocate与deallocte ,这两个函数分别调用operator new()与delete(),这两个函数的底层又分别是malloc()and free();但是每次malloc会带来格外开销(因为每次malloc一个元素都要带有附加信息)
  2. 容器之间的实现关系以及分类

  1. list属于双向链表,其结点与list本身是分开设计的

    template<class T, class Alloc = alloc>
    class list {
    protected:
        typedef __list_node<T> list_node;
    public:
        typedef list_node *link_type;
        typedef __list_iterator<T, T &, T *> iterator;
    protected:
        link_type node;
    };
    //学习到了一个分析方法,拿到这样一个类,先看它的数据比如上面的link_type node,然后我们再看它的前缀,link_type,去上面在link_type,找到typedef list_node *link_type;按这个方法继续找到上面的typedef __list_node<T> list_node;我们发现__list_node<T>是下面的类,我们一层层的寻找,就能看懂这些源码
    
    template<class T>
    struct __list_node {
        typedef void *void_pointer;
        void_pointer prev;
        void_pointer next;
        T data;
    };
    

​ list是一个环状的双向链表,同时它也满足STL对于“前闭后开”的原则,即在链表尾端可以加上空白节点

  1. 【重点】:list的迭代器的设计:迭代器是泛化的指针所以里面重载了->,–,++,*(),等运算符,同时迭代器是算法与容器之间的桥梁,算法需要了解容器的方方面面,于是就诞生了5种关联类型,(这5种类型是必备的,可能还需要其他类型)我们知道算法传入的是迭代器或者指针,算法根据传入的迭代器或指针推断出算法所想要了解的容器里的5种关联类型的相关信息。由于光传入指针,算法推断不出来其想要的信息,所以我们需要一个中间商(萃取器)也就是我们所说的iterator traits类,对于一般的迭代器,它直接提供迭代器里的关联类型值,而对于指针和常量指针,它采用的类模板偏特化,从而提供其所需要的关联类型的值。

// 针对一般的迭代器类型,直接取迭代器内定义的关联类型
template<class I>
struct iterator_traits {
    typedef typename I::iterator_category 	iterator_category;
    typedef typename I::value_type 			value_type;
    typedef typename I::difference_type 	difference_type;
    typedef typename I::pointer 			pointer;
    typedef typename I::reference 			reference;
};

// 针对指针类型进行特化,指定关联类型的值
template<class T>
struct iterator_traits<T *> {
    typedef random_access_iterator_tag 		iterator_category;
    typedef T 								value_type;
    typedef ptrdiff_t 						difference_type;
    typedef T*								pointer;
    typedef T&								reference;
};

// 针对指针常量类型进行特化,指定关联类型的值
template<class T>
struct iterator_traits<const T *> {
    typedef random_access_iterator_tag 		iterator_category;
    typedef T 								value_type;		// value_tye被用于创建变量,为灵活起见,取 T 而非 const T 作为 value_type
    typedef ptrdiff_t 						difference_type;
    typedef const T*						pointer;
    typedef const T&						reference;
};

  1. 关于前置++,与后置++的不同形式
self& operator++() {
        node = (link_type) ((*node).next);
        return *this;
    }

const self operator++(int) {
        self tmp = *this;
        ++*this;
        return tmp;
    }
//为了区分前后置,重载函数是以参数类型来区分,在调用的时候,编译器默默给int指定为一个0
//第一为什么后置返回对象,而不是引用,因为后置为了返回旧值创建了一个临时对象,在函数结束的时候这个对象就会被销毁,如果返回引用,那么我请问你?你的对象对象都被销毁了,你引用啥呢?
//第二为什么后置前面也要加const,其实也可以不加,但是为了防止你使用i++++,连续两次的调用后置++重载符,为什么呢?原因有两个:它与内置类型行为不一致;你无法活得你所期望的结果,因为第一次返回的是旧值,而不是原对象,你调用两次后置++,结果只累加了一次,所以我们必须手动禁止其合法化,我就要在前面加上const。
//第三,处理用户的自定义类型时,最好使用前置++,因为他不会创建临时对象,进而不会带来构造和析构而造成的格外开销。

stl源码剖析

容器-序列容器

vector容器

  • vector概述:与数组非常相似,唯一差别就是vector是动态空间,能够自动扩充空间容纳元素

  • vector源码

    templeta<class T,class Alloc=alloc>
    class vector{
        public:
          typedef T value_type;
          typedef value_type* pointer;
          typedef value_type& reference;
          typedef value_type* iterator;
          typedef size_t      size_type;
          typedef ptrdiff_t   difference_type
        //嵌套类型定义,也可以是关联类型定义
        protected:
          typedef simple_alloc<value_type,Alloc> data_alloctor
        //空间配置器(分配器)
          iterator start;
          iterator finish;
          iterator end_of_storage;
        //这3个就是vector里的数据,所以一个vector就是包含3个指针12byte,下面有图介绍
        void insert_aux(iterator position ,const T& x);
        //这个就是vector的自动扩充函数,在下面章节我会拿出来分析
        void deallocate(){
            if(start)
                data_allocator::deallocate(start,end_of_storage);
        }
        //析构函数的部分实现函数
        void fill_initialize(size_type n,const T&value){
            start=allocate_and_fill(n,value);
            finish=start+n;
            end_of_storage=finish;
        }
        //构造函数的具体实现
        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);};
           //重载[]说明vector支持随机访问
           
           vector():start(0),end(0),end_of_storage(0){};
           vector(size_type n,const T&value)(fill_initialize(n,value););
           vector(long n,const T&value)(fill_initialize(n,value););
           vector(int  n,const T&value)(fill_initialize(n,value););
           explicit vector(size_type n){fill_initialize(n,T());};
           //重载构造函数
           ~vector(){
                destory(start,finish);//全局函数,析构对象
                deallocate();//成员函数,释放空间
           }
           
          //接下来就是一些功能函数
           reference front(){return *begin();};
           reference back(){return *(end()-1);};
           void push_back(const T&x){
               if(finsih!=end_of_storage){
                   construct(finish,x);
                   ++finish;
               }
               else insert_aux(end(),x);
               //先扩充在添加
           }
           void pop_back(){
               --finish;
               destory(finish);
           }
           iterator erase(iterator position){
               if(position+1!=end())
                copy(position+1,finish,position);
                --finish;
                destory(finish);
                return position;
           }
        void resize(size_type new_size,const T&x){
            if(new_size()<size())
                erase(begin()+new_size,end());
            else 
                insert(end(),new_size-size(),x);
        }
        void resize()(size_type new_size){resize(new_size,T());}
        void clear(){erase(begin(),end());}
        
        protected:
           //配置空间并填满内容
           iterator allocate_and_fill(size_type n,const T&x){
               iterator result=data_allocator::allocate(n);
                  uninitialized_fill_n(result,n,x);//全局函数
           }
    }
    
  • vector迭代器:由于vector维护的是一个线性区间,所以普通指针具备作为vector迭代器的所有条件,就不需要重载operator+,operator*之类的东西

    template <class T, class Alloc = alloc>
    class vector {
    public:
        typedef T value_type;
        typedef value_type* iterator; //vector的迭代器是原生指针
        ...
    };
    
  • vector的数据结构:线性空间。为了降低配置空间的成本,我们必须让其容量大于其大小。

  • vector的构造以及内存管理:当我们使用push_back插入元素在尾端的时候,我们首先检查是否还有备用空间也就是说end是否等于end_of_storage,如果有直接插入,如果没有就扩充空间

    template<class T,class Alloc>
        void vector<T,Alloc>::insert_aux(iterator position,const T&x){
            if(finish!=end_of_storage){//有备用空间
                consruct(finish,*(finish-1));//在备用空间处构造一个元素,以vector最后一个元素为其初值
                ++finish;
                T x_copy=x;
                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;
                //vector中如果没有元素就配置一个元素,如果有就配置2倍元素。
                iterator new_start=data_allocator::allocate(len);
                iterator new_finish=new_start;
                try{
                    //拷贝插入点之前的元素
                    new_finish=uninitialized_copy(start,position,new_start);
                    construct(new_finish,x);
                    ++new_finish;
                    //拷贝插入点之后的元素
                    new_finish=uninitialized_copy(position,finish,new_finish);
                }
                catch(){
                    destroy(new_start,new_finish);
                    data_allocator::deallocate(new_start,len);
                    throw;
                }
                //析构并释放原vector
                destory(begin(),end());
                deallocate();
                //调整迭代器指向新的vector
                start=new_start;
                finish=new_finish;
                end_of_storage=new_start+len;
            }
                
        }
    //整个分为3个部分,配置新空间,转移元素,释放原来的元素与空间,因此一旦引起空间配置指向以前vector的所有迭代器都要失效。
    

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

    • void pop_back(){
          --finish;
          destory(finish);
      }
      
    • //erase版本一:清除范围元素
      iterator erase(iterator first,iterator last){
          interator i=copy(last,finish,first);
          destory(i,finish);
          finish=finish-(last-first);
          return first;
      }
      //版本二:清除某个位置上的元素
      iterator erase(iterator position){
          if(position+1!=finish){
            copy(position+1,finish,position);
            --finish;
            distory(finish);
            return position;
          }
      }
      
    • void clear(){erase(begin(),end())};
      
    • template<class T,class Alloc>
      void vector<T,Alloc>::insert(iterator position ,size_type n,const T&x){
          if(n!=0){
               if(size_type(end_of_strage-finish)>n){
                   //备用空间大于插入的元素数量
                   T x_copy=x;
                   //以下计算插入点之后的现有元素个数
                   const size_type elems_after=finish-positon;
                   iterator old_finish=finish;
                   if(elems_after>n){
                   //插入点之后的元素个数大于要插入的元素个数
                      uninitialiazed_copy(finish-n,finish,finish);
                      finish+=n;//将vector的尾端标记后移
                      copy_backward(position,old_finish-n,old_finish);
                      fill(position,old_finish,x_copy);//从插入点之后开始插入新值
                   }
                   else{
                   //插入点之后的元素个数小于要插入的元素个数
                      uninitialiazed_fill_n(finish,n-elems_after,finish);
                      finish+=n-elems_after;
                      uninitialiazed_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);
                      //以下配置新的空间
                      iterator new_start=data_allocator::allocate(len);
                      iterator new_finish=new_start;
                      _STL_TRY{
                      //拷贝插入点之前的元素
                       new_finish=uninitialized_copy(start,position,new_start);
                      //把新增元素(初值皆为n)传入新空间    
                       new_finish=uninitialized_fill_n
                      //拷贝插入点之后的元素
                       new_finish=uninitialized_copy(position,finish,new_finish);
                      //这一段有利于理解上面的insert_aux函数    
                      }
               #ifdef _STL_USE_EXCEPTIONS
                   catch(){
                      //如果有异常发生
                      destroy(new_start,new_finish);
                      data_allocator::deallocate(new_start,len);
                      throw;
                      }
               #endif/* _STL_USE_EXCEPTIONS*/
                      //析构并释放原vector
                      destory(begin(),end());
                      deallocate();
                      //调整迭代器指向新的vector
                      start=new_start;
                      finish=new_finish;
                      end_of_storage=new_start+len;
                  }  
               }
                   
          }
      }
      

deque容器

  • deque概述:deque是一个双端开口的连续线性空间,其内部为分段连续的空间组成,随时可以增加一段新的空间并链接 注意:由于deque的迭代器比vector要复杂,这影响了各个运算层面,所以除非必要尽量使用vector;为了提高效率,在对deque进行排序操作的时候,我们可以先把deque复制到vector中再进行排序最后在复制回deque

  • deque中控器:deque是由一段一段的定量连续空间构成。一旦有必要在其头端或者尾端增加新的空间,便配置一段定量连续空间,串接在整个deque的头端或者尾端【好处:避免“vector的重新配置,复制,释放”的轮回,维护连整体连续的假象,并提供随机访问的接口;坏处:其迭代器变得很复杂】。

deque采用一块map作为主控,其中的每个元素都是指针,指向另一片连续线性空间,称之为缓存区,这个区才是用来储存数据的。

template<class T,class Alloc=alloc,size_t Bufsize=0>
class deque{
    public:
      typedef T value_type ;
      typedef value_type pointer*;
      typedef size_t size_type;
      ...
    public:
      typedef _deque_iterator<T,T&,T*,BufSiz> iterator;   
    protected:
      typedef pointer* map_pointer;
    protected:
              iterator start;
              iterator finish;
              map_pointer map;//指向map
              size_type map_size;//map内可容纳多少指针
}
//map其实是一个T**
  • deque迭代器
template<class T,class Ref,class Ptr,size_t BufSiz>
struct _deque_iterator{
    typedef _deque_iterator<T,T&,T*,BufSiz> iterator;
    typedef _deque_iterator<T,cosnt T&,const T*,BufSiz>const_iterator;
    static size_t buffer_size(){return _deque_buf_size(BufSiz,sizeof(T));};
    
    
    typedef randem_access_iterator_tag iterator_category;
    typedef T value_type;
    typedef Ptr pointer;
    typedef Ref reference;
    typedef size_t size_type;
    typedef ptrdiff_t difference_type;
    typedef T** map_pointer;
    typedef _deque_iterator self;
    
    T* cur;
    T* first;
    T* last;
    map_pointer node;
    ...
    //这是一个决定缓存区大小的函数
    inline size_t _deque_buf_size(size_t n,size_t sz){
        return n!=0?n;(sz<512?size_t(512/sz):size_t(1));
    }
}

deque拥有两个数据成员-start与finish迭代器,分别由deque:begin()与deque:end()传回;

//迭代器的关键行为,其中要注意的是一旦遇到缓冲区边缘,可能需要跳一个缓存区
void set_node(map_pointer new_node){
    node=new_node;
    first=*new_node;
    last=first+difference_type(buffer_size());
}
//接下来重载运算子是_deque_iterator<>成功运作的关键
reference operator*()const{return *cur;}
pointer operator->()const{return &(operator*());}
difference_type operator—(const self& x)const{
    return difference_type(buffer_szie())*(node-x.node-1)+(cur-first)+(x.last-x.cur);
}
self& operator++(){
    ++cur;
    if(cur==last){
        set_node(node+1);
        cur=first;
    }
    return *this;
}
self operator++(int){
    self temp=*this;
    ++*this;
    return temp;
}
self& operator--(){
    if(cur==first){
        set_node(node-1);
        cur=last;
    }
    --cur;
    return *this;
}
self operator-(int){
    self temp=*this;
    --*this;
    return temp;
}
//以下实现随机存取,迭代器可以直接跳跃n个距离
self& operator+=(difference_type n){
    difference_type offest=n+(cur-first);
    if(offest>0&&offest<difference_type(buffer_size()))
        cur+=n;
    else{
        offest>0?offest/fifference_type(buffer_size()):-difference_type((-offest-1)/buffer_size())-1;
        set_node(node+node_offest);
        cur=first+(offest-node_offest*difference_type(buffer_size()));
    }
    return *this;    
}
self operator+(differnece_type n){
    self tmp=*this;
    return tmp+=n;
}
self operator-=(){return *this+= -n;}
self operator-(difference_type n){
    self temp=*this;
    return *this-=n;
}
rference operator[](difference_type n){
    return *(*this+n);
}
bool operator==(const self&x)const{return cur==x.cur;}
bool operator!=(const self&x)const{return !(*this==x);}
bool operatoe<(const self&x)const{
    return (node==x.node)?(cur<x.cur):(node-x.node);
}
  • deque数据结构:deque除了维护一个map指针以外,还维护了start与finish迭代器分别指向第一缓冲区的第一个元素,和最后一个缓冲区的最后一个元素的下一个元素,同时它还必须记住当前map的大小。具体结构和源代码看上面

  • deque的构造与管理

//deque首先自行定义了两个空间配置器
typedef simple_alloc<value_type,Alloc>data_allocator;
typedef simple_alloc<pointer,Alloc>map_allocator;
deque中有一个构造函数用于构造deque结构并赋初值
deque(int n,const value_type& value):start(),finish(),map(0),map_size(0){
    fill_initialize(n,value);//这个函数就是用来构建deque结构,并设立初值
}
template<class T,class Alloc,size_t BufSize)
void deque<T,Alloc,BufSize>::fill_initialize(size_type n,const value_type &value){
    creat_map_and_node(n);//安排结构
    map_pointer cur;
    _STL_TRY{
        //为每个缓存区赋值
        for(cur=start.node;cur<finish.node;++cur)
            uninitalized_ fill(*cur,*cur+buffer_size(),value);
            //设置最后一个节点有一点不同
            uninitalized_fill(finish.first,finish.cur,value);
         }
    catch(){
        ...
    }
}
template<class T,class Alloc,size_t Bufsize>
void deque<T,alloc,Bufsize>::creat_map_and_node(size_type num_elements){
    //需要节点数=元素个数/每个缓存区的可容纳元素个数+1
    size_type num_nodes=num_elements/Buf_size()+1;
    map_size=max(initial_map_size(),num_nodes+2);//前后预留2个供扩充
    //创建一个大小为map_size的map
    map=map_allocator::allocate(map_size);
    
    //创建两个指针指向map所拥有的全部节点的最中间区段
    map_pointer nstart=map+(map_size()-num_nodes)/2;
    map_poniter nfinish=nstart+num_nodes-1;
    
    map_pointer cur;
    _STL_TRY{
        //为每个节点配置缓存区
        for(cur=nstart;cur<nfinish;++cur)
            +cur=allocate_node();
    }
    catch(){
        ...
    }
   //最后为deque内的start和finish设定内容
   start.set_node(nstart);
   finish.set_node(nfinish);
   start.cur=start.first;
   finish.cur=finish.first+num_elements%buffer_szie();
}

接下来就是插入操作的实现,第一,首先判断是否有扩充map的需求,若有就扩,然后就是在插入函数中,首先判断是否在结尾或者开头从而判断是否跳跃节点。

void push_back(const value_type&t){
    if(finish.cur!=finish.last-1){
        construct(finish.cur,t);
        ++finish.cur;
       }
    else 
        push_back_aux(t);
}
//由于尾端只剩一个可用元素空间(finish.cur=finish.last-1),所以我们必须重新配置一个缓存区,在设置新元素的内容,然后更改迭代器的状态
tempalate<class T,class Alloc,size_t BufSize>
void deque<T,alloc,BufSize>::push_back_aux(const value_type&t){
    value_type t_copy=t;
    reserve_map_at_back();
    *(finish.node+1)=allocate_node();
    _STL_TRY{
        construct(finish.cur,t_copy);
        finish.set_node(finish.node+1);
        finish.cur=finish.first;
    }
    -STL_UNWIND{
        deallocate_node(*(finish.node+1));
    }
}
//push_front也是一样的逻辑

stack(栈)与queue(队列)

  • 概述:栈与队列被称之为duque的配接器,其底层是以deque为底部架构。通过deque执行具体操作

  • stack无迭代器
  • 源码剖析
template<class T,class Sequence=deque<T>>
class stack{//_STL_NULL_TMPL_ARGS展开为<>
    friend bool operator==_STL_NULL_TMPL_ARGS(const stack&,const stack&);
    friend bool operator<_STL_NULL_TMPL_ARGS(coonst stack&,const stack&);
 public:
    typedef typename Sequence:: value_type value_type;
    typedef typename Sequence:: size_type  size_type;
    typedef typename Sequence:: reference reference;
    typedef typename Sequence:: const_reference const_refernece;
 protected:
    Sequence c;
 public:
    bool empty()const{return c.empty();}
    size_type size()const{return c.size();}
    reference top()(return c.back();)
    const_rference top()const{return c.back();}
    void push(const value_type&x){c.push_back(x);}
    void pop_back(){c.pop_back();}
};
template <class T,class Sequence>
bool operator==(const stack<T,Sequence>&x,const stack<T,Sequence>&y){
    return x.c==y.c;
}
template <class T,class Sequence>
bool operator<(const stack<T,Sequence>&x,const stack<T,Sequence>&y){
    return x.c<y.c;
}
//queue的源码和stack差不多
  • heap(堆):建立在完全二叉树上,分为两种,大根堆,小根堆,其在STL中做priority_queue的助手,即,以任何顺序将元素推入容器中,然后取出时一定是从优先权最高的元素开始取,完全二叉树具有这样的性质,适合做priority_queue的底层

  • priority_queue,优先队列,也是配接器。其内的元素不是按照被推入的顺序排列,而是自动取元素的权值排列,确省情况下利用一个max-heap完成,后者是以vector—表现的完全二叉树。
template<class T,class Sequence=vector<T>,class Compare=less<typename Sequence::value_type>>
class priority_queue{
    public:
     typedef typename Sequence:: value_type value_type;
     typedef typename Sequence:: size_type  size_type;
     typedef typename Sequence:: reference reference;
     typedef typename Sequence:: const_reference const_refernece;
    protected:
     Sequence c;//底层容器
     Compare comp//容器比较大小标准
    public:
     priority_queue():c(){}
     explicit priority_queue(const Compare &x):c(),comp(x){}
    //以下用到的make_heap(),push_heap(),pop_heap()都是泛型算法
    //任何一个构造函数都可以立即在底层产生一个heap
    template <class InputIterator>
    priority_queue(InputIterator first,InputIterator last const Compare&x)
        :c(first,last),comp(x){make_heap(c.begin(),c.end(),comp);}
    template <class InputIterator>
    priority_queue(InputIterator first,InputIterator last const Compare&x)
        :c(first,last) {make_heap(c.begin(),c.end(),comp);}
    bool empty()const{return c.empty();}
    size_type size()const{return c.size();}
    const_reference top()const{return c.front();}
    void push(const value_type&x){
        _STL_TRy{
            c.push_back(X);
            push_heap(c.begin(),c.end(),comp);
        }
        _STL_UNWIND{c.clear()};
    }
    void pop(){
        _STL_TRY{
            pop_heap(c.begin(),c.end(),comp);
            c.pop_back();
        }
        _STL_UNWEIND{c.clear()};
    }
};
/priority_queue无迭代器
 typedef typename Sequence:: size_type  size_type;
 typedef typename Sequence:: reference reference;
 typedef typename Sequence:: const_reference const_refernece;
protected:
 Sequence c;//底层容器
 Compare comp//容器比较大小标准
public:
 priority_queue():c(){}
 explicit priority_queue(const Compare &x):c(),comp(x){}
//以下用到的make_heap(),push_heap(),pop_heap()都是泛型算法
//任何一个构造函数都可以立即在底层产生一个heap
template <class InputIterator>
priority_queue(InputIterator first,InputIterator last const Compare&x)
    :c(first,last),comp(x){make_heap(c.begin(),c.end(),comp);}
template <class InputIterator>
priority_queue(InputIterator first,InputIterator last const Compare&x)
    :c(first,last) {make_heap(c.begin(),c.end(),comp);}
bool empty()const{return c.empty();}
size_type size()const{return c.size();}
const_reference top()const{return c.front();}
void push(const value_type&x){
    _STL_TRY{
        c.push_back(X);
        push_heap(c.begin(),c.end(),comp);
    }
    _STL_UNWIND{c.clear()};
}
void pop(){
    _STL_TRY{
        pop_heap(c.begin(),c.end(),comp);
        c.pop_back();
    }
    _STL_UNWEIND{c.clear()};
}
};//priority_queue无迭代器
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值