本部分内容包括容器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()。