SGISTL源码阅读十四 deque容器上
deque
概述
之前我们学习过vector
,我们知道他的扩容方式是从尾部扩容,也就是它是单向开口的。deque
比vector
和list
都要更复杂一些,它是双向开口的,也就是说可以在头部和尾部做同样的操作(我们要向vector
的头部添加一个元素代价是相当大的,需要将所有元素向后移动,但是对于deque
来说,在头部和尾部操作都是同样的时间复杂度)。这样看起来deque
是一段连续线性空间,但是它只是逻辑上的一整段连续线性空间,内部实现是由一小段一小段的定量连续空间构成的。下面我们来深入了解它。
深入源码
deque
的数据结构
map(deque
的中控器)
这里的map
和容器map
不一样,它是deque
的中控器。前面提到deque
是由一小段一小段的定量连续空间构成的,将这些小段的定量连续空间整合在一起,就是它的作用了。它用到了一个二级指针。
//缺省使用SGISTL的空间配置器
template <class T, class Alloc = alloc, size_t BufSiz = 0>
class deque {
public:
typedef T value_type;
//当前类型的指针 别名为pointer
typedef value_type* pointer;
typedef const value_type* const_pointer;
//...
protected:
//二级指针
typedef pointer* map_pointer;
typedef simple_alloc<pointer, Alloc> map_allocator;
//...
//map初始长度为8
static size_type initial_map_size() { return 8; }
//...
protected:
//它的内部维护了两个迭代器,分别指向了deque的第一个节点和最后一个节点
iterator start;
iterator finish;
map_pointer map;
size_type map_size; //map内的指针个数
一些基本的操作
public:
//返回第一个节点
iterator begin() { return start; }
//返回末尾节点
iterator end() { return finish; }
const_iterator begin() const { return start; }
const_iterator end() const { return finish; }
//重载操作符(调用__deque_iterator的重载操作符[]),实现下标访问
reference operator[](size_type n) { return start[difference_type(n)]; }
const_reference operator[](size_type n) const {
return start[difference_type(n)];
}
//返回第一个节点的值
reference front() { return *start; }
//返回末尾值,由于[start,finish)
reference back() {
iterator tmp = finish;
--tmp;
return *tmp;
}
const_reference front() const { return *start; }
const_reference back() const {
const_iterator tmp = finish;
--tmp;
return *tmp;
}
我们通过图片来加深对deque
的理解
宏观
微观
deque
的迭代器
deque
的迭代器类型是random_access_iterator_tag
,但是它并不是简单的普通指针。
//全局函数
//如果n不为0,则直接返回n
//如果n为0,sz < 512bytes,则返回512 / sz
//如果n为0,sz > 512bytes,则返回size_t(1)
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));
}
//...
public:
#ifndef __STL_NON_TYPE_TMPL_PARAM_BUG
typedef __deque_iterator<T, T&, T*, BufSiz> iterator;
typedef __deque_iterator<T, const T&, const T&, BufSiz> const_iterator;
#else /* __STL_NON_TYPE_TMPL_PARAM_BUG */
typedef __deque_iterator<T, T&, T*> iterator;
typedef __deque_iterator<T, const T&, const T*> const_iterator;
#endif /* __STL_NON_TYPE_TMPL_PARAM_BUG */
//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, const T&, const T*, BufSiz> const_iterator;
//调用了全局函数__deque_buf_size
static size_t buffer_size() {return __deque_buf_size(BufSiz, sizeof(T)); }
//没有继承std::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;
//维护了3个指针
T* cur; //当前迭代器所指缓冲区中的当前(current)元素
T* first; //当前迭代器所指缓冲区的头
T* last; //当前得带其所指缓冲区的尾
map_pointer node; //指向中控器
//deque迭代器的构造函数
__deque_iterator(T* x, map_pointer y)
: cur(x), first(*y), last(*y + buffer_size()), node(y) {}
__deque_iterator() : cur(0), first(0), last(0), node(0) {}
__deque_iterator(const iterator& x)
: cur(x.cur), first(x.first), last(x.last), node(x.node) {}
set_node()
这个函数相当的重要。我们能调用它跳出当前线性空间(缓冲区)
void set_node(map_pointer new_node) {
node = new_node;
first = *new_node;
last = first + difference_type(buffer_size());
}
操作符重载
//重载*,返回cur指向元素的值
reference operator*() const { return *cur; }
#ifndef __SGI_STL_NO_ARROW_OPERATOR
//重载->,返回cur指向元素的值
pointer operator->() const { return &(operator*()); }
#endif /* __SGI_STL_NO_ARROW_OPERATOR */
/* 重载操作符-
* 计算两个迭代器指向的元素之间的个数
* (node - x.node - 1)计算的是两个迭代器所在的线性空间中间跨过的完整的线性空间个数
*/
difference_type operator-(const self& x) const {
return difference_type(buffer_size()) * (node - x.node - 1) +
(cur - first) + (x.last - x.cur);
}
//重载后++
self& operator++() {
++cur;
如果+1后指向last,说明需要到下一段线性空间(缓冲区)中去取值
if (cur == last) {
//调用set_node函数找到下一段线性空间
set_node(node + 1);
cur = first;
}
return *this;
}
//重载前++
self operator++(int) {
self tmp = *this;
++*this;
return tmp;
}
//重载后--
self& operator--() {
if (cur == first) {
set_node(node - 1);
cur = last;
}
--cur;
return *this;
}
//重载前--
self operator--(int) {
self tmp = *this;
--*this;
return tmp;
}
//重载+=
//如果+n后的值不在当前线性空间(缓冲区),仍然需要set_node到相应线性空间(缓冲区)去
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()));
}
return *this;
}
self operator+(difference_type n) const {
self tmp = *this;
//调用+=
return tmp += n;
}
//类似于+=
self& operator-=(difference_type n) { return *this += -n; }
self operator-(difference_type n) const {
self tmp = *this;
//调用-=
return tmp -= n;
}
//重载[],可用下标访问,实现了随机访问
reference operator[](difference_type n) const { return *(*this + n); }
//判断迭代器所指向的是否为同一段线性空间(缓冲区)的同一个值
bool operator==(const self& x) const { return cur == x.cur; }
bool operator!=(const self& x) const { return !(*this == x); }
bool operator<(const self& x) const {
return (node == x.node) ? (cur < x.cur) : (node < x.node);
}
在学习了deque
的迭代器之后,我们再用一个图来深入理解deque
的结构体以及它的迭代器
总结
我们主要介绍了deque
的数据结构以及它的迭代器。它确实是一种相对来说比较复杂的容器,但是仔细阅读源码并结合图示也并不难理解。
因为我们本节并没有介绍到关于节点node
的概念,其实他由就是map
所维护的。
之后我们将继续介绍deque
的构造以及内存管理等。