今天我們來看deque這個數據結構。
我們在C語言的數據結構之中,應該是沒有deque這個數據結構的,但是我們肯定有兩個數據結構,一個叫做quene(就是所謂的隊列),還有一個叫做stack(也就是所謂棧),當然對於我們來說應該是自己寫出來的,但是在c++當中他們兩個的實現就不一樣了,他們采用同一個底層數據結構叫做deque的來進行擴充。
deque這個結構體可以說是list和vector的一個混合結構,所以我們也不能說他是連續空間還是非連續空間。但是起碼在應用層(也就是我們看到的這層),他是一個連續空間(因為可以隨機跳轉)。那么接下來我們就說一下這個deque在源碼當中的實現。
一、為什么要用deque
二、Deque是什么
三、Deque的源碼實現
四、Deque迭代器的實現
五、deque中包含的各種函數說明和軟件架構
六、Deque的優點和缺點
一、為什么要用deque
在C語言中我們有很多的數據結構,比如隊列,鏈表等等。這些大部分都在STL容器當中實現了,但是隊列和棧我們始終沒有實現。所以deque就出現了。
二、Deque是什么
Deque的用途既包含了數據結構中隊列的功能,也包含了棧的功能(因為他是兩端都可以插入的),當然我們說list也可以兩端插入的,不過list不支持隨機讀取,但是Deque可以支持隨機查找,但是它的插入效率要低於vector(這也好理解,因為判斷的東西多了嘛)。
三、deque的源碼實現
首先我們來看一下deque的內存模型:
我們來分析一下deque的內存模型。首先deque中包含一個map(注意,這個map不是STL中的map),他是一個指針數組,里面存放了指針,每個指針中指向了一段內存,而這段內存才是真正存放數據的地方。所以,deque當中必須包含兩個迭代器指針,分別是start和finish這兩個迭代器,每個迭代器中又包含了4個指針,分別是start,finish,current以及size這四個部分。所以,一個空的deque應該是有16個指針的。
這里說一下,對於源碼來說,我們使用的時候,比如*iterator這個函數,迭代器會自動把當前數據放到cur當中,非常方便,但是這個要花點時間,因為畢竟函數重載了嘛。
好的,接下來我們說一下deque的源碼實現。deque的源碼部分我們可以分成三個部分:
一、初始化部分
二、insert部分(包括頭部添加,尾部添加,中間添加)
三、刪除部分
一、初始化部分
我們首先來看第一部分初始化部分
template<typename _Tp, typename _Alloc>
void
_Deque_base<_Tp, _Alloc>::
_M_initialize_map(size_t __num_elements)
{
const size_t __num_nodes = (__num_elements/ __deque_buf_size(sizeof(_Tp))
+ 1);
this->_M_impl._M_map_size = std::max((size_t) _S_initial_map_size,
size_t(__num_nodes + 2));
this->_M_impl._M_map = _M_allocate_map(this->_M_impl._M_map_size);
// For "small" maps (needing less than _M_map_size nodes), allocation
// starts in the middle elements and grows outwards. So nstart may be
// the beginning of _M_map, but for small maps it may be as far in as
// _M_map+3.
_Map_pointer __nstart = (this->_M_impl._M_map
+ (this->_M_impl._M_map_size - __num_nodes) / 2);
_Map_pointer __nfinish = __nstart + __num_nodes;
__try
{ _M_create_nodes(__nstart, __nfinish); }
__catch(...)
{
_M_deallocate_map(this->_M_impl._M_map, this->_M_impl._M_map_size);
this->_M_impl._M_map = _Map_pointer();
this->_M_impl._M_map_size = 0;
__throw_exception_again;
}
this->_M_impl._M_start._M_set_node(__nstart);
this->_M_impl._M_finish._M_set_node(__nfinish - 1);
this->_M_impl._M_start._M_cur = _M_impl._M_start._M_first;
this->_M_impl._M_finish._M_cur = (this->_M_impl._M_finish._M_first
+ __num_elements
% __deque_buf_size(sizeof(_Tp)));
}
這里我要說明的部分有兩點:
首先是這句話:
this->_M_impl._M_map_size = std::max((size_t) _S_initial_map_size,
size_t(__num_nodes + 2));
這句話指定的是一開始map中節點的個數。我們可以看到,map所用的是初始化節點和我們指定的節點的最大值,而初始化節點大小是8,所以map中節點最少是8,或者是我們指定節點的數字加2,為什么要加2呢?因為頭部和尾部各加一個,這樣比較方便。
然后這里是第二句話:
_Map_pointer __nstart = (this->_M_impl._M_map
+ (this->_M_impl._M_map_size - __num_nodes) / 2);
這句話指定的是頭節點nstart所使用的節點數。我們注意,deque的start不是在開頭,而是在中間,為什么要這么設計呢?誠如之間所說,deque本身是兩頭擴充的,這樣做比較方便。
接下來我們來看一下inert的部分
我們以push_back為例:
push_back(bool __x)
{
if (this->_M_impl._M_finish._M_p != this->_M_impl._M_end_addr())
*this->_M_impl._M_finish++ = __x;
else
_M_insert_aux(end(), __x);
}
這個代碼比較好理解,就是如果說沒到節點的尾端,那么直接插入,否則的話執行_M_insert_aux這個函數
typename deque<_Tp, _Alloc>::iterator
deque<_Tp, _Alloc>::
_M_insert_aux(iterator __pos, const value_type& __x)
{
value_type __x_copy = __x; // XXX copy
#endif
difference_type __index = __pos - this->_M_impl._M_start;
if (static_cast<size_type>(__index) < size() / 2)
{
push_front(_GLIBCXX_MOVE(front()));
iterator __front1 = this->_M_impl._M_start;
++__front1;
iterator __front2 = __front1;
++__front2;
__pos = this->_M_impl._M_start + __index;
iterator __pos1 = __pos;
++__pos1;
_GLIBCXX_MOVE3(__front2, __pos1, __front1);
}
else
{
push_back(_GLIBCXX_MOVE(back()));
iterator __back1 = this->_M_impl._M_finish;
--__back1;
iterator __back2 = __back1;
--__back2;
__pos = this->_M_impl._M_start + __index;
_GLIBCXX_MOVE_BACKWARD3(__pos, __back2, __back1);
}
*__pos = _GLIBCXX_MOVE(__x_copy);
return __pos;
它的這個插入是很有趣的,它可以這樣看,首先是對其進行賦值(拷貝一下),然后我們判斷是否當前的插入位置在前半段還是后半段,這是為什么呢?我們接着往后看,如果是前半段,那么我們push_front這個函數,然后將他移動到中間。這就明白了吧,這樣可以減少移動次數。
四、Deque迭代器的使用
我們這里以operator++舉例
_Self&
operator++() _GLIBCXX_NOEXCEPT
{
++_M_cur;
if (_M_cur == _M_last)
{
_M_set_node(_M_node + 1);
_M_cur = _M_first;
}
return *this;
}
我們看到,如果當前節點不到last的時候M_cur正常++,反之則要跳到下一個節點。我們繼續看
void
_M_set_node(_Map_pointer __new_node) _GLIBCXX_NOEXCEPT
{
_M_node = __new_node;
_M_first = *__new_node;
_M_last = _M_first + difference_type(_S_buffer_size());
}
再這里,我們跳入了下一個節點,並且重新將M_first,last重新定義,並且再返回時候將first賦值給了current。
以上就是deque的源碼,今天就分享到這了。