STL序列式容器

STL序列式容器

一,概述

 所谓序列式容器,指容器中的数据都是可序的,即元素集合呈线性关系排列,但未必是有序的(符合某比较关系)。

C++语言本身提供了一个序列式容器array,但是它不可以动态扩展。

STL另外提供了vector,list,deque,stack,queue,priority-queue等序列式容器。

stack,queue,缺省由deque改装而成,被归为配接器。


二,标准容器

1,vector

1.1 vector概述

vector,可以理解为动态数组。它的数组安排和操作方式与array非常相似。array是静态空间,而vector是动态空间,随元素的加入,内部会自行扩充空间。

对于array,如果空间不够用了,一些复杂的工作,得我们自己来安排。首先,申请一块新空间,然后将数据一一搬过去,最后把原空间释放。

而对于vector,这些工作它自动就完成了,完成的工作也是这样的。


1.2 vector部分定义

template<class T, class Alloc = alloc>

class vector
{
public:
    .......
    .......
    ///一些类型定义,具体请参阅STL源码
protected:
    iterator start;          //目前使用空间的头部位置
    iterator finish;         //目前使用空间的尾部位置
    iterator end_of_storage; //目前可用空间的尾部位置
public:
    iterator begin();
    iterator end();
    size_type size();
    size_type capacity();
    bool empty() const;
    reference operator[](size_type n);

    vector();
    vector(size_type n ,const T&value);
    vector(int n,const T&value);
    vector(long n,const T&value);
    explicit vector(size_type n);  //explicit 构造函数不能在隐式转换中使用
    ~vector();
    
    reference front();
    reference back();
    void push_back(const T&x);
    void pop_back();
    
    iterator erase(iterator pos);
    void resize(size_type newsize,const T& x);
    void resize(size_type newsize);
    void clear();
};

1.3 vector的迭代器

vector维护的是一个连续线性空间,所有普通指针可以作为vector的迭代器,并且满足所有必要条件。
vector迭代器所需要的行为包括 operator*,operator->,operator++,operator--,operator+,operator-,operator+=,operator-=
所以,vector提供的迭代器是Random Access Iterators

迭代器失效:
在插入的过程中,可能遇到扩容的可能,扩容的时候会重新分配空间。而原来的迭代器还指在初始位置上,这样就会引起迭代器失效。

可以在源码中看到

template<class T, class Alloc = alloc>

class vector
{
public:
    typedef T   value_type;
    typedef value_type* iterator;  //即 T*,一个普通的指针
    ...
    ..
};

1.4 vector的数据结构

template<class T, class Alloc = alloc>

class vector
{

protected:
    iterator start;          //目前使用空间的头部位置
    iterator finish;         //目前使用空间的尾部位置
    iterator end_of_storage; //目前可用空间的尾部位置

};


    iterator begin(){return start;}
    iterator end(){return finash;}
    size_type size()const
    {
        return size_type(end()-begin());
    }
    size_type capacity()const
    {
        return end_of_storage-begin();
    }

为了降低空间配置时的速度成本,vector实际配置的大小可能比用户需求更大一些,以备将来扩充。

2,list

2.1 list概述

相对于vector的连续线性空间,list就显得复杂太多,它的好处是每次插入或者删除就会配置或者释放一个元素空间。

它对于空间的运用绝对精准,一点也不多余。而且对于指定位置元素的插入或移除,list都是常数时间。

2.2 list节点

对于链表,list本身,和list节点时不同的结构,需要分开设计。


这是list的节点结构,很显然,它是一个双向链表

template<class T>
struct _list_node
{
    typedef void* void_pointer;
    void_pointer prev; //也可以设为 _list_node<T>*
    void_pointer next;
    T data;
};

2.3 list迭代器

由于不再保证空间的连续性,list迭代器,就不能以普通指针代替。

由于list是双向链表,迭代器应为 BidirectionIterators

这样的话,必须定义一个类,重载++,--等等即可。

template <class T,class Ref,class Ptr>
struct _list_iterator
{
    typedef _list_iterator<T,T&,T*> iterator;
    typedef _list_iterator<T,Ref,Ptr> self;
    typedef _list_node<T>* link_type;
    //其他定义不列出
    link_type node;     //迭代器内部当然需要一个指针了,指向list节点

    self& operator++()
    {
        node = (link_type)((*node).next);
        return *this;
    }
    self operator ++(int)
    {
        self tmp = *this;
        ++ *this;
        return tmp;
    }
     self& operator--()
    {
        node = (link_type)((*node).prev);
        return *this;
    }
    self operator --(int)
    {
        self tmp = *this;
        -- *this;
        return tmp;
    }
   /其他操作不列出
};


2.4 list的数据结构

template <class T,class Alloc=alloc>
class list
{
protected:
    typedef _list_node<T> list_node;
public:
    typedef list_node* link_type;
protected:
    link_type node;     //list是一个环,一个指针就可表示整个链表
};

node指针指向刻意置于尾端的一个空白节点,这样整个list就符合ST对于“前闭后开”区间的要求。



此时,首尾迭代器就定义为。

    iterator begin()
    {
        return (link_type)((*node).next);
    }
    iterator end()
    {
        return node ;
    }

这样做的好处在于,仅有一个指针就标明了首尾。

当试图在尾端和首端插入时就是常量时间了。


3,slist

3.1,slist概述

list是一个双向链表,而slist则是一个单向链表。主要差别在于,前者的迭代器是双向的Bidirectional Iterator,后者则是Forward Iterator。

为此,slist的功能受到了一些限制,不过,单向链表所耗用的空间更小,某些操作更快。


根据STL习惯,插入操作会将新元素放在指定位置之前。而作为一个单向链表,slist没有任何方便的办法可以回到前一个位置,因此,必须从头开始找起。这显得很耗时。

为此slist特别提供了insert_after()和erase_after()供灵活使用。


3.2 slist的的节点

template <class T>
struct _slist_node_base
{
    _slist_node_base* next;
};

template <class T>
struct _slist_node:public _slist_node_base
{
    T data;
};
注意到,此时,只有一个next指针。


3.3,slist的迭代器

struct _slist_iterator_base
{
    typedef forward_iterator_tag iterator_categort; //单向的
    _slist_iterator_base *node;
    
    void incr(){node = node->next};
    ...
    ...
};
template <class T, class Ref,class Ptr>
struct _slist_iterator : public _slist_iterator_base
{
    ...
    ...
    typedef _slist_iterator<T,Ref,Ptr>  self;
    self& operator ++()
    {
        incr();
        return *this;
    }
    self& operator ++(int) 
    {
        self tmp = *this;
        incr();
        return tmp;
    }
    //没有实现operatpr --
    //
};

在这里,slist迭代器,只需要实现operator ++ 即可。


3.4,slist的数据结构

template <class T,class Alloc = alloc>
class slist
{
private:
    typedef _slist_node<T,T&,T*>    iterator;
    typedef _slist_node<T>          slist_node;
    typedef _slist_node_base        slist_node_base;
    ...
    
    list_node_base head;  //这里是一个实物,不是指针。 没有数据域
    
    iterator begin(){return iterator((slist_node*)head.next);}
    iterator end(){return iterator(0);}
    ....
}



4,deque

4.1,deque概述

相较于vector是单向开口的连续线性空间,deque则是一个双向开口的线性空间。

虽然模型图如此,但实际上,STL deque的结构远比这复杂。


它是动态地以分段连续空间组合而成。随时可以增加一段新的空间并连接起来。

虽然deque也提供Random Access Iterator,但是,它的迭代器并不是普通指针,这都是由于deque特殊的结构造成的。

这影响了deque各个函数的性能。例如deque的排序操作,为了最高效率, 可将deque西安完成复制到一个vector身上,将vector排序后,再复制回来。


4.2,deque的中控器

deque是由一段一段的定量连续空间构成。一旦有必要在deque的前端或尾端增加新空间,便要配置一段定量的连续空间,串接在整个deuqe的头部或者尾部。

deque的最大任务,便是在这些分段的定量连续空间上, 维护其整体连续的假象,并提供随机存取的接口。

为此,deque采用一段所谓的map(不是STL中的map)作为主控。这里所谓的map是一小块连续的空间,每个元素都是一个指针,指向另一段连续空间,称为缓冲区。

缓冲区才是存放数据的地方。

template <class T,class Alloc = alloc,size_t BufSize=0>
class deque
{
public:
    typedef T value_type;
    typedef T* pointer;
protected:
    typedef pointer* map_pointer;
    
    map_pointer map;  //指向map,其内部每个元素都是一个指针。
    size_type map_size; //mao 可容纳的指针
};

4.3,deque的迭代器

deque是分段连续空间。维持其“整体连续”假象的任务,就交由迭代器来实现。具体就在于operator++和operator--两个子运算上。

template <class T,clas Ref,class Ptr,size_t BufSize=0>
struct _deque_iterator
{
    typedef _deque_iterator<T,T&,T*,BufSize>    iterator;
    ...
    typedef random_access_iterator_tag  iterator_category;
    ...
    T* cur;  //此迭代器所指的缓冲区的当前元素
    T* first;//此迭代器所指的缓冲区的头部元素
    T* last; //此迭代器所指的缓冲区的尾部元素
    
    map_pointer node ;//指向中控器
};

实现代码为:

    void set_node(map_pointer new_node)
    {
        node = new_node;
        first = *new_node;
        last = first+ difference_type(buffer_size());
    }
    self& operator ++()
    {
        cur ++ ;
        if(cur == last)  //如果满了,就切换到下一缓冲区
        {
            set_node(node+1);
            cur = first;
        }
         return *this;   
    }
    self& operator--()
    {
        if(cur == first)  //已经是缓冲区的头部
        {
            set_node(node-1);
            cur = last;
        }
        --cur;
        return *this;
    }
    self& operator +=(difference_type n) //随机存储
    {
        difference_type offset = n+ (cur-first);
        if(offset >=0 && offset < difference_type(buffer_size()))
        {
            //目标位置在同一缓冲区
            cur += n;  //直接调用普通指针的随机存取
        }
        else
        {
            difference_type node_offset = offset > 0? offset /  difference_type(buffer_size())
                                                    : - difference_type((-offset -1)/buffer_size()) -1;
            set_node(node + node_offset); //切换到正确的缓冲区
            cur = first + (offset - node_offset*difference_type(buffer_size());
        }
    }


三,容器配接器

stack,queue,虽然不符合标准容器的配置,但也是一种非常常用的“容器”。

1,stack

1.1,stack概述

stack是一种先进先出的数据结构(FIFO)。它只有一个出口。

只允许从顶端插入,移除元素。不支持遍历行为。


1.2,stack实现

template <class T,class Sequence=deque<T>>
class stack
{
    //__STL_NULL_TMPL_ARGS展开会变成<> 为了实现 bound friend templates
    //也就是说,class template的某个具体实现与其friend function template的某个具体实现有一对一的关系
friend bool operator == __STL_NULL_TMPL_ARGS(const stack&,const stack&);
friend bool operator < __STL_NULL_TMPL_ARGS(const 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_reference;
protected:
    Sequence c; //底层容器
public:
    bool empty(){return c.empty()};
    size_type size()const{return c.size();}
    reference top(){return c.back();}
    void push(const value_type&x){c.push_back(x);}
    void pop(){c.pop_back();}
};
bool operator == __STL_NULL_TMPL_ARGS(const stack<T,Sequence>&,const stack<T,Sequence>&)
{
    return x.c == y.c;
}
bool operator < __STL_NULL_TMPL_ARGS(const stack<T,Sequence>&,const stack<T,Sequence>&)
{
    return x.c < y .c;
}

可以看出,stack默认是使用deque作为底层容器。

某些时候,也是可以实用list作为底层容器。

stack<int ,list<int>> istack


2,queue

2.1,queue概述

queue是一种先进先出的数据结构。它有两个开口。

queue运行在尾部增加元素,在首部取出元素。

和stack一样,不支持随机访问,也不能遍历。


2.2,queue实现

template <class T,class Sequence=deque<T>>
class queue
{
    //__STL_NULL_TMPL_ARGS展开会变成<> 为了实现 bound friend templates
    //也就是说,class template的某个具体实现与其friend function template的某个具体实现有一对一的关系
friend bool operator == __STL_NULL_TMPL_ARGS(const queue&,const queue&);
friend bool operator < __STL_NULL_TMPL_ARGS(const queue&,const 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_reference;
protected:
    Sequence c; //底层容器
public:
    bool empty(){return c.empty()};
    size_type size()const{return c.size();}
    reference front(){return c.front();}
    reference back(){return c.back();}
    void push(const value_type&x){c.push_back(x);}
    
    void pop(){c.pop_front();}
};
bool operator == __STL_NULL_TMPL_ARGS(const queue<T,Sequence>&,const queue<T,Sequence>&)
{
    return x.c == y.c;
}
bool operator < __STL_NULL_TMPL_ARGS(const queue<T,Sequence>&,const queue<T,Sequence>&)
{
    return x.c < y .c;
}

可以看出,queue默认是使用deque作为底层容器。

某些时候,也是可以实用list作为底层容器。

queue<int ,list<int>> iqueue

四,其他

1, heap

STL采用了一种非常聪明的方式来构造heap——完全二叉树。

完全二叉树整棵树内都没有任何节点漏洞,这带来了极大的好处。可以使用vector来存储节点。假如使用一个小技巧,将vector的0号元素保留,那么当树中某个节点位于i处时,其左子节点为2*i,右孩子为2*i+1,父节点为i/2.

通过这么简单的位置规则,vector可是轻易实现完全二叉树。

这种以vector/array来表示tree 的方法,称为隐式表述法。


这样,一个vector,再加上一组heap算法来插入、删除元素,取极值等,就完成了一个heap。


2,priority_queue

缺省情况下,priority_queue利用一个max-heap完成。后者以vector来表现完全二叉树。
max_heap可满足priority_queue所需要的特性。

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_reference;
protected:
    Sequence c; //底层容器
    Compare comp; //元素大小比较规则
public:
    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):c(first,last)
    {
        make_heap(c.begin(),c.end(),comp);
    }   
    void push(const value_type& x)
    {
        __STL_TRY
        {
            c.push_back(x);         //先将元素推入末端,再重排heap
            push_heap(c.begin(),c.end(),comp);
        }
        __STL_UNWIND(c.clear());
    }
    void pop()
    {
        __STL_TRY
        {
            //重排heap,再使用pop_back取的弹出的元素
            push_heap(c.begin(),c.end(),comp);
            c.pop_back();
        }
        __STL_UNWIND(c.clear());
    }
};




  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值