STL之deque
Posted randyniu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STL之deque相关的知识,希望对你有一定的参考价值。
deque为了维持在头和尾进行插入的假象,这些操作全部都交给了迭代器去做了。
deque由一段一段定量连续空间构成,一旦有必要在deque的前端或者是尾端增加新的空间,便配置一段定量的连续空间,串在头部或者是尾部。
deque采用的是一块所谓的map作为主控,这里的map是一小段连续的空间,其中每个元素都是一个指针,指向另一端较大的连续线性空间,称为缓冲区。缓冲区才是真正的存储主体。允许指定大小的缓冲区,默认是512字节的缓冲区。
//deque迭代器的设计 //*deque是分段连续的线性空间,迭代器设计时必须能够进行operator++或operator--操作 //* 迭代器的功能: //* 1、必须知道缓冲区的位置 //* 2、能够判断是否处于其所在缓冲区的边界 //* 3、能够知道其所在缓冲区当前所指位置的元素 //******************************************** template <class _Tp, class _Ref, class _Ptr> struct _Deque_iterator { typedef _Deque_iterator<_Tp, _Tp&, _Tp*> iterator; typedef _Deque_iterator<_Tp, const _Tp&, const _Tp*> const_iterator; static size_t _S_buffer_size() { return __deque_buf_size(sizeof(_Tp)); } typedef random_access_iterator_tag iterator_category; typedef _Tp value_type; typedef _Ptr pointer; typedef _Ref reference; typedef size_t size_type; typedef ptrdiff_t difference_type; typedef _Tp** _Map_pointer; typedef _Deque_iterator _Self; //以下是迭代器设计的关键,访问容器的节点 _Tp* _M_cur;//指向缓冲区当前的元素 _Tp* _M_first;//指向缓冲区的头(起始地址) _Tp* _M_last;//指向缓冲区的尾(结束地址) _Map_pointer _M_node;//指向中控器的相应节点 _Deque_iterator(_Tp* __x, _Map_pointer __y) : _M_cur(__x), _M_first(*__y), _M_last(*__y + _S_buffer_size()), _M_node(__y) {} _Deque_iterator() : _M_cur(0), _M_first(0), _M_last(0), _M_node(0) {} _Deque_iterator(const iterator& __x) : _M_cur(__x._M_cur), _M_first(__x._M_first), _M_last(__x._M_last), _M_node(__x._M_node) {} //**************************************************************************** //*********************以下是迭代器_Deque_iterator的操作********************** //* 这些操作符的重载方便我们访问容器的内容 //* 也是让deque从接口看来是维护连续线性空间的关键 //**************************************************************************** reference operator*() const { return *_M_cur; }//解除引用,返回当前元素 #ifndef __SGI_STL_NO_ARROW_OPERATOR pointer operator->() const { return _M_cur; } #endif /* __SGI_STL_NO_ARROW_OPERATOR */ //返回两个迭代器之间的距离 difference_type operator-(const _Self& __x) const { return difference_type(_S_buffer_size()) * (_M_node - __x._M_node - 1) + (_M_cur - _M_first) + (__x._M_last - __x._M_cur); } //前缀自增++操作符重载 _Self& operator++() { ++_M_cur; //普通自增操作,移至下一个元素 if (_M_cur == _M_last) { //若已达到缓冲区的尾部 _M_set_node(_M_node + 1);//切换至下一缓冲区(节点) _M_cur = _M_first; //的第一个元素 } return *this; } //后缀自增++操作符重载 //返回当前迭代器的一个副本 _Self operator++(int) { _Self __tmp = *this;//定义当前迭代器的一个副本 ++*this;//这里前缀++不是普通的++操作,是上一步骤已经重载过的前缀++ return __tmp; } //前缀自减--操作符重载 //基本思想类似于前缀自增操作 _Self& operator--() { if (_M_cur == _M_first) { //若是当前缓冲区的第一个元素 _M_set_node(_M_node - 1);//切换到上一个缓冲区 _M_cur = _M_last; //的尾部(即最后一个元素的下一个位置) } --_M_cur;//普通的自减操作,移至前一个元素 return *this; } //后缀自减--操作符重载 //返回当前迭代器的副本 _Self operator--(int) { _Self __tmp = *this;//定义一个副本 --*this; //迭代器自减操作 return __tmp; } //以下实现随机存取,迭代器可以直接跳跃n个距离 //将迭代器前移n个距离,当n负值时就为下面的operator-=操作 _Self& operator+=(difference_type __n) { difference_type __offset = __n + (_M_cur - _M_first);//定义一个中间变量 if (__offset >= 0 && __offset < difference_type(_S_buffer_size())) //若前移n个距离后,目标依然在同一个缓冲区 //则直接前移n个距离 _M_cur += __n; else { //若前移n个距离后,目标超出该缓冲区范围 //__offset / difference_type(_S_buffer_size())计算向后移动多少个缓冲区 //-difference_type((-__offset - 1) / _S_buffer_size()) - 1计算向前移动多少个缓冲区 difference_type __node_offset = __offset > 0 ? __offset / difference_type(_S_buffer_size()) : -difference_type((-__offset - 1) / _S_buffer_size()) - 1; //调整到正确的缓冲区 _M_set_node(_M_node + __node_offset); //切换至正确的元素 _M_cur = _M_first + (__offset - __node_offset * difference_type(_S_buffer_size())); } return *this; } //操作符+重载 //返回操作之后的副本 _Self operator+(difference_type __n) const { _Self __tmp = *this; //调用operator+=操作 return __tmp += __n; } //利用operator+=操作实现 _Self& operator-=(difference_type __n) { return *this += -__n; } _Self operator-(difference_type __n) const { _Self __tmp = *this; return __tmp -= __n; } //返回指定位置的元素,即实现随机存取 //该函数调用operator+,operator* reference operator[](difference_type __n) const { return *(*this + __n); } bool operator==(const _Self& __x) const { return _M_cur == __x._M_cur; } bool operator!=(const _Self& __x) const { return !(*this == __x); } bool operator<(const _Self& __x) const { return (_M_node == __x._M_node) ? (_M_cur < __x._M_cur) : (_M_node < __x._M_node); } bool operator>(const _Self& __x) const { return __x < *this; } bool operator<=(const _Self& __x) const { return !(__x < *this); } bool operator>=(const _Self& __x) const { return !(*this < __x); } //调整到正确的缓冲区 //切换到正确的元素位置 void _M_set_node(_Map_pointer __new_node) { _M_node = __new_node;//指向新的节点 _M_first = *__new_node;//指向新节点的头部 _M_last = _M_first + difference_type(_S_buffer_size());//指向新节点的尾部 } }; //operator+迭代器向前移动n个位置 template <class _Tp, class _Ref, class _Ptr> inline _Deque_iterator<_Tp, _Ref, _Ptr> operator+(ptrdiff_t __n, const _Deque_iterator<_Tp, _Ref, _Ptr>& __x) { return __x + __n; } #ifndef __STL_CLASS_PARTIAL_SPECIALIZATION template <class _Tp, class _Ref, class _Ptr> inline random_access_iterator_tag iterator_category(const _Deque_iterator<_Tp,_Ref,_Ptr>&) { return random_access_iterator_tag(); } template <class _Tp, class _Ref, class _Ptr> inline _Tp* value_type(const _Deque_iterator<_Tp,_Ref,_Ptr>&) { return 0; } template <class _Tp, class _Ref, class _Ptr> inline ptrdiff_t* distance_type(const _Deque_iterator<_Tp,_Ref,_Ptr>&) { return 0; } #endif /* __STL_CLASS_PARTIAL_SPECIALIZATION */ #ifdef __STL_USE_STD_ALLOCATORS // Base class for ordinary allocators. //这里只负责中控器节点内存管理 template <class _Tp, class _Alloc, bool __is_static> class _Deque_alloc_base { public: typedef typename _Alloc_traits<_Tp,_Alloc>::allocator_type allocator_type; allocator_type get_allocator() const { return _M_node_allocator; } _Deque_alloc_base(const allocator_type& __a) : _M_node_allocator(__a), _M_map_allocator(__a), _M_map(0), _M_map_size(0) {} protected: typedef typename _Alloc_traits<_Tp*, _Alloc>::allocator_type _Map_allocator_type; allocator_type _M_node_allocator; _Map_allocator_type _M_map_allocator; _Tp* _M_allocate_node() { return _M_node_allocator.allocate(__deque_buf_size(sizeof(_Tp))); } void _M_deallocate_node(_Tp* __p) { _M_node_allocator.deallocate(__p, __deque_buf_size(sizeof(_Tp))); } _Tp** _M_allocate_map(size_t __n) { return _M_map_allocator.allocate(__n); } void _M_deallocate_map(_Tp** __p, size_t __n) { _M_map_allocator.deallocate(__p, __n); } _Tp** _M_map; //指向中控器map size_t _M_map_size;//中控器map可容纳指针的个数 };
deque 迭代器中有的数据也就4个指针,因此如果sizeof的话应该大小是16。
迭代器中联系了缓存区中的位置,以及在map控制中心的位置。
deque的数据结构
deque中维护了指向map的指针,还维护了start和finish两个迭代器,分别指向第一个缓冲区的第一个元素,和最后一个缓冲区的最后一个元素。同是记住map大小,因为当map节点不足的时候,需要更新配置一个更大的一块map。
template <class _Tp, class _Alloc> class _Deque_base : public _Deque_alloc_base<_Tp,_Alloc, _Alloc_traits<_Tp, _Alloc>::_S_instanceless> { public: typedef _Deque_alloc_base<_Tp,_Alloc, _Alloc_traits<_Tp, _Alloc>::_S_instanceless> _Base; typedef typename _Base::allocator_type allocator_type; typedef _Deque_iterator<_Tp,_Tp&,_Tp*> iterator; typedef _Deque_iterator<_Tp,const _Tp&,const _Tp*> const_iterator; //参数__num_elements表示缓冲区(节点)存储元素的个数 _Deque_base(const allocator_type& __a, size_t __num_elements) : _Base(__a), _M_start(), _M_finish() { _M_initialize_map(__num_elements); } _Deque_base(const allocator_type& __a) : _Base(__a), _M_start(), _M_finish() {} ~_Deque_base(); protected: void _M_initialize_map(size_t); void _M_create_nodes(_Tp** __nstart, _Tp** __nfinish); void _M_destroy_nodes(_Tp** __nstart, _Tp** __nfinish); enum { _S_initial_map_size = 8 };//中控器map默认大小 protected: iterator _M_start;//指向第一个缓冲区的第一个元素 iterator _M_finish;//指向最后一个缓冲区的最后一个元素的下一个位置 };
deque中有两个迭代器,还有一个map指针,还有一个size_type ,因此大小应该是40个字节。
template <class _Tp, class _Alloc> void deque<_Tp,_Alloc>::_M_reallocate_map(size_type __nodes_to_add, bool __add_at_front) { //map原始节点数 size_type __old_num_nodes = _M_finish._M_node - _M_start._M_node + 1; //map新的节点数 size_type __new_num_nodes = __old_num_nodes + __nodes_to_add; _Map_pointer __new_nstart; if (_M_map_size > 2 * __new_num_nodes) {//若map的大小比新节点数的两倍还大,则只需调整迭代器start和finish __new_nstart = _M_map + (_M_map_size - __new_num_nodes) / 2 + (__add_at_front ? __nodes_to_add : 0); if (__new_nstart < _M_start._M_node) copy(_M_start._M_node, _M_finish._M_node + 1, __new_nstart); else copy_backward(_M_start._M_node, _M_finish._M_node + 1, __new_nstart + __old_num_nodes); } else {//否则需重新分配一个新的map空间 size_type __new_map_size = _M_map_size + max(_M_map_size, __nodes_to_add) + 2; _Map_pointer __new_map = _M_allocate_map(__new_map_size); __new_nstart = __new_map + (__new_map_size - __new_num_nodes) / 2 + (__add_at_front ? __nodes_to_add : 0); //拷贝原始map的内容 copy(_M_start._M_node, _M_finish._M_node + 1, __new_nstart); //释放原始map空间 _M_deallocate_map(_M_map, _M_map_size); //更新map的起始地址和大小 _M_map = __new_map; _M_map_size = __new_map_size; } //更新迭代器start和finish _M_start._M_set_node(__new_nstart); _M_finish._M_set_node(__new_nstart + __old_num_nodes - 1); }
这个函数时用来设置map的相关内容。最少是管理8个节点,最多是所需节点的2倍。
当finish.cur == finish.last-1的时候,是需要一个新的缓冲空间,如果缓存空间还有的话就直接使用,如果没有了就需要换一块新的缓存空间。
由于会不停的增删元素,因此会导致deque的map会向一边偏狭,因此有时候就需要重新调整map。在不够用的时候,在进行重新申请空间之前进行判断是否是真的需要换空间,如果当前map空间 是大于 2 倍的被占用的实际空间,就进行调整,否则就进行新空间的分配。不过deque的重新配置要比vector简单很多,不用涉及到大量的元素移动,要拷贝的也仅仅是map中的指针而已,因此deque的生长要比vector简单一点 。
void pop_back() { if (_M_finish._M_cur != _M_finish._M_first) { --_M_finish._M_cur; destroy(_M_finish._M_cur); } else _M_pop_back_aux(); } void pop_front() { if (_M_start._M_cur != _M_start._M_last - 1) { destroy(_M_start._M_cur); ++_M_start._M_cur; } else _M_pop_front_aux(); }
添加元素的时候要考虑空间扩展的问题,那么当进行弹出元素的时候就要考虑空间收缩的问题。
当最后缓冲区只剩下一个元素了,那么弹出的时候要调整当前的指针,同时析构当前元素,然后进行缓冲区的释放工作。
当前面的缓冲区只有最后一个元素存在的时候,如果这个时候要将前面的元素给弹出,这个时候要进行前面缓冲区的释放工作。同时设置好当前迭代的指针,每次进行增加或者是删除的时候,都需要进行迭代器指向的重置工作。
// Called only if _M_finish._M_cur == _M_finish._M_first. template <class _Tp, class _Alloc> void deque<_Tp,_Alloc>::_M_pop_back_aux() { _M_deallocate_node(_M_finish._M_first); _M_finish._M_set_node(_M_finish._M_node - 1); _M_finish._M_cur = _M_finish._M_last - 1; destroy(_M_finish._M_cur); } template <class _Tp, class _Alloc> void deque<_Tp,_Alloc>::_M_pop_front_aux() { destroy(_M_start._M_cur); _M_deallocate_node(_M_start._M_first); _M_start._M_set_node(_M_start._M_node + 1); _M_start._M_cur = _M_start._M_first; }
下面的clear函数是用来清除整个deque,deque的最初状态是只有一个缓冲区,因此clear之后的deque是没有任何元素,同时仅仅保留了一个缓冲区这样的状态。
首先释放头和尾意外的缓冲区,
如果还剩下两个缓冲区,就将头缓冲区保存下来,当然析构对象是在所难免的了。
如果仅剩下一个缓冲区,就仅仅进行对象的析构工作。
template <class _Tp, class _Alloc> void deque<_Tp,_Alloc>::clear() { //首先清空缓冲区是满数据的节点 for (_Map_pointer __node = _M_start._M_node + 1; __node < _M_finish._M_node; ++__node) { destroy(*__node, *__node + _S_buffer_size()); _M_deallocate_node(*__node); } //清除缓冲区还存在可用空间的节点,即只存储部分数据 if (_M_start._M_node != _M_finish._M_node) { destroy(_M_start._M_cur, _M_start._M_last); destroy(_M_finish._M_first, _M_finish._M_cur); _M_deallocate_node(_M_finish._M_first); } else destroy(_M_start._M_cur, _M_finish._M_cur); _M_finish = _M_start;//表示容器为空
接下来的例子是erase函数。
首先计算那个前面和后面部分那个元素少,那个元素少就移动那个方向的元素。这么考虑也是很自然的事情。
template <class _Tp, class _Alloc> typename deque<_Tp,_Alloc>::iterator deque<_Tp,_Alloc>::erase(iterator __first, iterator __last) { if (__first == _M_start && __last == _M_finish) {//若擦除整个容器内容 clear(); return _M_finish; } else { difference_type __n = __last - __first; difference_type __elems_before = __first - _M_start; if (__elems_before < difference_type((this->size() - __n) / 2)) { copy_backward(_M_start, __first, __last); iterator __new_start = _M_start + __n; destroy(_M_start, __new_start); _M_destroy_nodes(__new_start._M_node, _M_start._M_node); _M_start = __new_start; } else { copy(__last, _M_finish, __first); iterator __new_finish = _M_finish - __n; destroy(__new_finish, _M_finish); _M_destroy_nodes(__new_finish._M_node + 1, _M_finish._M_node + 1); _M_finish = __new_finish; } return _M_start + __elems_before; } }
最后说的是插入工作,删除需要移动元素,同样,插入也需要移动元素,所以用的方法和erase是一样的,都需要判断前面还有后面那边的元素少就移动那边的元素,
当然有两种特殊情况,如果是在最前面,就直接调用push_front函数去处理,如果是在后面插入就直接调用push_back来进行,否则就进行判断。
记得要更新迭代器哦。
大概deque就这么多了,其实deque的抽象确实是很棒的,这些都是迭代器的功劳。
以上是关于STL之deque的主要内容,如果未能解决你的问题,请参考以下文章