C++STL 体系结构与内核分析(侯捷)——课程笔记(七)

本部分内容包括容器deque以及容器适配器stack和queue。

一、deque基本原理

deque对外表现为两端可以扩充的连续内存块。但其内部实际上是由一个个离散的内存段组成的,术语叫做buffer,这些内存段的头指针被保存在一个vector中,通过deque的迭代器的行为,就可以对外表现为一个连续的内存段。deque的G2.9源码如下:

template<class T, class Alloc = alloc,
         size_t BufSiz = 0>
class deque{
public:
    typedef T value_type;
    typedef __deque_iterator<T, T&, T*, BufSiz> iterator;
protected:
    typedef pointer* map_pointer;    //T**
protected:
    iterator start;
    iterator finish;
    map_pointer map;
    size_type map_size;
public:
    iterator begin() { return start; }
    iterator end() { return finish; }
    size_type size() const { return finish - start; }
......
};

//buffer的大小如何确定
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的迭代器
template<class T, class Ref, class Ptr, size_t BufSiz>
struct __deque_iterator{
    typedef random_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;
......
};

可以看出deque内部包含两个迭代器start和finish,分别指向头尾,除此之外还有一个map_pointer指向保存指向buffer的指针的内存段,以及一个表示map长度的map_size。

观察deque的迭代器我们可以看出,里面包含四个指针,其中cur,first,last是指示当前buffer中位置的指针,node是指示当前buffer在整个map中位置的指针。cur表示元素的当前位置,first和last分别标记该buffer的头尾,方便iterator进行后续的边界判断

二、deque的insert()成员函数

下面通过insert()函数来体会一下deque的行为,插入元素时,deque会选择元素较少的一端进行移动:

iterator insert(iterator position, const value_type& x){
    if(position.cur == start.cur){ //如果安插点是deque最前端,那么交给push_front()做插入
        push_front(x);
        return start;
    }
    else if(position.cur == finish.cur){ //如果安插点是deque最尾端,那么交给push_back()做插入
        push_back(x);
        iterator tmp = finish;
        --tmp;
        return tmp;
    }
    else{
        return insert_aux(position, x);
    }
}

template <class T, class Alloc, size_t BufSize>
typename deque<T, Alloc, BufSize>::iterator
deque<T, Alloc, BufSize>::insert_aux(iterator pos, const value_type& x){
    difference_type index = pos - start;    //安插点之前的元素个数
    value_type x_copy = x;
    if(index < size() / 2){                //如果安插点之前的元素个数较少
        push_front(front());               //在最前端加入与第一元素同值的元素
        ......
        copy(front2, pos1, front1);        //元素搬移
    }    
    else{                                   //安插点之后的元素个数较少
        push_back(back());                 //在尾端加入与最末元素同值的元素
        ......
        copy_backward(pos, back2, back1);  //元素搬移
    }
    *pos = x_copy;                         //在安插点设定新值
    return pos;
}

三、deque如何模拟连续空间

通过介绍一些比较重要的操作符重载函数,来说明deque对连续空间的模拟

1. 两个迭代器之间的距离

difference_type
operator-(const self& x) const
{
    return difference_type(buffer_size()) * (node - x.node - 1) +
           (cur - first) + (x.last - x.cur);
}

两个iterator之间的距离相当于(1)两个iterator之间的buffers的总长度+(2)itr到其buffer末尾的长度+(3)x至其buffer开头的长度

2. 前置与后置++的重载(--同理)

self& operator++(){    
    ++cur;                    //切换至下一个元素
    if (cur == last){         //如果到达当前buffer的尾端,就跳到下一个buffer的起点   
        set_node(node + 1);
        cur = first;
    }
    return *this;
}

self operator++(int){
    self tmp = *this;
    ++*this;
    return tmp;
}

void set_node(map_pointer new_node){
    node = new_node;
    first = *new_node;
    last = first +
           difference_type(buffer_size());
}

3. +=的重载(-=可以使用+= -n实现)

self& operator+=(difference_type n){
    difference_type offset = n + (cur - first);
    if (offset >= 0 && offset < difference_type(buffer_size()))
        //目标位置在同一buffer内
        cur += n;
    else{
        //目标位置不在同一buffer内
        difference_type node_offset = 
            offset > 0 ? offset / difference_type(buffer_size())
                                : -difference_type((-offset - 1) / buffer_size()) - 1;
        //切换至正确的buffer
        set_node(node + node_offset);
        //切换至正确的元素
        cur = first + (offset - node_offset * difference_type(buffer_size()));
    }
    return *this;
}

self operator+(difference_type n) const {
    self tmp = *this;
    return tmp += n;
}

四、G4.9的deque

与之前的容器一样,G4.9的deque也变得相当复杂。观察G4.9的结构可以总结出新版本容器实现的规律,容器都要继承一个容器的base,base里面内含一个容器的implementation,implementation又继承自allocator

五、容器适配器queue和stack

queue和stack里面都内含一个sequence,queue和stack的所有操作都是通过sequence的操作来实现的。queue和stack的默认sequence都是deque,但是理论上来说只要传入的容器有适配器需要的成员函数,就可以正确运行。所以list<T>也可以满足queue和stack的所有成员函数需求,但是如果传入的容器是vector<T>,那么queue和stack就可能有一些成员函数使用时会报错,因为vector<T>本身没有某些成员函数,比如pop_front()。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值