STL源碼分析--list

更多精彩內容,請關注微信公衆號:後端技術小屋node

1 相關文件

list
list.h
stl_list.h

2 鏈表節點結構

基類_List_node_base只有_M_prev, _M_prev,分別指向前置節點和後繼節點,由此看出STL list是雙向鏈表(首節點爲空)c++

struct _List_node_base {
  _List_node_base* _M_next;
  _List_node_base* _M_prev;
}

派生類_List_node還有_M_data,用於存放數據項算法

template <class _Tp>
struct _List_node : public _List_node_base {
  _Tp _M_data;
}

3 鏈表的內存分配

vector, 申請內存的調用鏈以下。allocatorSTL源碼分析--內存分配器後端

_Alloc_type::allocate
  -> simple_alloc<_List_node<_Tp>, _Alloc>::allocate
    -> _Alloc::allocate (list中_Alloc缺省爲__STL_DEFAULT_ALLOCATOR(_Tp))
      -> allocator<_Tp>

4 list接口

4.1 默認構造

注意用戶可爲list實例自定義內存分配器,內存分配器類型經過模板參數傳入,內存分配器實例經過函數參數傳入。_M_get_node從內存分配器中申請一個鏈表節點_List_node<_Tp>大小的內存。使用默認構造函數時,list中並無任何元素,所以_M_get_node中只申請內存,但並不在其上構造_Tp對象,即該節點是空的哨兵節點。數組

explicit list(const allocator_type& __a = allocator_type()) : _Base(__a) {}

  _List_base(const allocator_type&) {
    _M_node = _M_get_node();
    _M_node->_M_next = _M_node;
    _M_node->_M_prev = _M_node;
  }

4.2 從元素個數與值構造

實現:連續向list中插入__n個值爲__value的元素
調用鏈以下。微信

list::list
  -> list::insert(批量版本,一次插入n條數據)
    -> list::_M_fill_insert
      -> list::insert(非批量,一次插入1條數據)

相關代碼片斷以下:函數

``` c++
// list::list
  list(size_type __n, const _Tp& __value,
       const allocator_type& __a = allocator_type())
    : _Base(__a)
    { insert(begin(), __n, __value); }
   

// list::insert(批量版本,一次插入n條數據)
 void insert(iterator __pos, size_type __n, const _Tp& __x)
    { _M_fill_insert(__pos, __n, __x); }
   
   
   
// list::_M_fill_insert
template <class _Tp, class _Alloc>
void 
list<_Tp, _Alloc>::_M_fill_insert(iterator __position,
                                  size_type __n, const _Tp& __x)
{
  for ( ; __n > 0; --__n)
    insert(__position, __x);
}

// list::insert(非批量,一次插入1條數據)  
  iterator insert(iterator __position, const _Tp& __x) {
    _Node* __tmp = _M_create_node(__x);
    __tmp->_M_next = __position._M_node;
    __tmp->_M_prev = __position._M_node->_M_prev;
    __position._M_node->_M_prev->_M_next = __tmp;
    __position._M_node->_M_prev = __tmp;
    return __tmp;
  }

_M_create_node也會新建鏈表節點,其與_M_get_node的不一樣在於會在內存上構造值爲__x_Tp對象。源碼分析

_Node* _M_create_node(const _Tp& __x)
  {
    _Node* __p = _M_get_node();
    __STL_TRY {
      _Construct(&__p->_M_data, __x);
    }
    __STL_UNWIND(_M_put_node(__p));
    return __p;
  }

4.3 從容器區間構造

遍歷容器區間[__first, __last), 依次將元素插入listthis

// We don't need any dispatching tricks here, because insert does all of
  // that anyway.  
  template <class _InputIterator>
  list(_InputIterator __first, _InputIterator __last,
       const allocator_type& __a = allocator_type())
    : _Base(__a)
    { insert(begin(), __first, __last); }

template <class _Tp, class _Alloc>
void 
list<_Tp, _Alloc>::insert(iterator __position,
                         const_iterator __first, const_iterator __last)
{
  for ( ; __first != __last; ++__first)
    insert(__position, *__first);
}

4.4 複製構造

實現上同從容器區間構造相似,在此略過。spa

list(const list<_Tp, _Alloc>& __x) : _Base(__x.get_allocator())
    { insert(begin(), __x.begin(), __x.end()); }

4.5 析構函數

實現上分兩步:

  • 對於每一個除_M_node的節點,依次遍歷並對其中的_Tp執行析構,並釋放節點內存。
  • 對於頭節點,直接釋放節點內存。

_M_put_node調用_Alloc_type::deallocate釋放內存。

~list() { }

  ~_List_base() {
    clear();
    _M_put_node(_M_node);

template <class _Tp, class _Alloc>
void 
_List_base<_Tp,_Alloc>::clear() 
{
  _List_node<_Tp>* __cur = (_List_node<_Tp>*) _M_node->_M_next;
  while (__cur != _M_node) {
    _List_node<_Tp>* __tmp = __cur;
    __cur = (_List_node<_Tp>*) __cur->_M_next;
    _Destroy(&__tmp->_M_data);
    _M_put_node(__tmp);
  }
  _M_node->_M_next = _M_node;
  _M_node->_M_prev = _M_node;
}

  void _M_put_node(_List_node<_Tp>* __p) { _Alloc_type::deallocate(__p, 1); }

4.6 resize

遍歷一遍list, 獲得鏈表長度,而後根據長度判斷是否對鏈表進行增加或減短

  • 若是是前者,調用insert在鏈表尾部插入new_size - __len個值爲__x的節點
  • 若是是後者,調用erase在鏈表尾部刪除__len - new_size個節點
    由於會遍歷所有節點,最好不要執行list::resize, list.size同理
template <class _Tp, class _Alloc>
void list<_Tp, _Alloc>::resize(size_type __new_size, const _Tp& __x)
{
  iterator __i = begin();
  size_type __len = 0;
  for ( ; __i != end() && __len < __new_size; ++__i, ++__len)
    ;
  if (__len == __new_size)
    erase(__i, end());
  else                          // __i == end()
    insert(end(), __new_size - __len, __x);
}

4.7 reverse

從頭節點_M_node開始逆序遍歷鏈表,交換全部節點的prev和next指針

inline void __List_base_reverse(_List_node_base* __p)
{
  _List_node_base* __tmp = __p;
  do {
    __STD::swap(__tmp->_M_next, __tmp->_M_prev);
    __tmp = __tmp->_M_prev;     // Old next node is now prev.
  } while (__tmp != __p);
}

4.8 sort

sort代碼以下所示。list排序時,鏈表中維護臨時鏈表__carry和鏈表數組__counter[64], 和__fill狀態。其中__counter用於暫存當前的排序結果,__fill表示當前已被使用的__counter數組中的鏈表數量。

注意:代碼中調用merge方法時,會將兩個list按照順序合併成一個。假設有兩個list a與b, 調用a.merge(b)以後,b中全部節點都被轉移到a中,b爲空鏈表,a中包含了全部的節點,並按照順序排列。

template <class _Tp, class _Alloc>
void list<_Tp, _Alloc>::sort()
{
  // Do nothing if the list has length 0 or 1.
  if (_M_node->_M_next != _M_node && _M_node->_M_next->_M_next != _M_node) {
    list<_Tp, _Alloc> __carry;
    list<_Tp, _Alloc> __counter[64];
    int __fill = 0;
    while (!empty()) {
      __carry.splice(__carry.begin(), *this, begin());
      int __i = 0;
      while(__i < __fill && !__counter[__i].empty()) {
        __counter[__i].merge(__carry);
        __carry.swap(__counter[__i++]);
      }
      __carry.swap(__counter[__i]);         
      if (__i == __fill) ++__fill;
    } 

    for (int __i = 1; __i < __fill; ++__i)
      __counter[__i].merge(__counter[__i-1]);
    swap(__counter[__fill-1]);
  }
}

舉個例子可能更好理解一些:假設鏈表中有4個值,從前到後分別爲v0, v1, v2, v3。__counter數組長度爲4, 即__counter[0], __counter[1], __counter[2], __counter[3]

  • 初始狀態:__fill = 0, __counter中全部鏈表爲空
  • 第一輪循環:加入v0, 狀態變成__counter[0] = {v0}, __fill = 1
  • 第二輪循環: 加入v1, 變成__counter[0] = {}, __counter[1] = {v0, v1}, __fill = 2
  • 第三輪: 加入v2, 變成__counter[0] = {v2}, __counter[1] = {v0, v1}, __fill = 2
  • 第四輪: 加入v3, 變成__counter[0] = {}, __counter[1] = {}, __counter[2] = {v0, v1, v2, v3}, __fill = 3
  • 最終獲得一組__counter[4],從左到右merge全部__counter中的鏈表,最終獲得排過序的{v0, v1, v2, v3}結果, 並經過swap將結果轉移給list對象自己。

其實list中sort算法就是非遞歸版本的歸併排序,時間複雜度爲O(n logn)

推薦閱讀

更多精彩內容,請掃碼關注微信公衆號:後端技術小屋。若是以爲文章對你有幫助的話,請多多分享、轉發、在看。
二維碼

相關文章
相關標籤/搜索