天天看點

《STL 源碼剖析》 list 實作原理

目錄

  • list概述
  • list疊代器
  • list資料結構
  • list 構造與記憶體管理
  • list 操作實作原理
    • list的transfer(前移操作)
    • splice實作原理
    • merge實作原理
    • list.sort 實作原理

list對空間的運用是精準的,不浪費的。對于任何位置的元素插入 或 元素移除,list永遠是常數時間。

list實作上就是一個雙向循環連結清單,list節點 有prev 和 next 兩個指針。

因為list是一個雙向連結清單,他的疊代器就必須具備前移、後移的能力。list提供的是 Bidirectional Iterators。

list性質:插入操作、接合操作,不會造成原有的list疊代器失效,即使删除操作,也隻是 被指向删除元素的 那個疊代器失效,其他不受影響。(這個和vector 有很大差别)

list是一個 環狀雙向連結清單,是以它隻需要一個指針就能夠 表現出整個完整連結清單

template <class T, class Alloc = alloc>
class list
{
protected:
    typedef __list_node<T> list_node;
public:
    typedef list_node* link_type;
 
protected:
    link_type node;// 隻要一個指針,就表示整個連結清單
...
};
           

node指向尾端的一個空白節點,就能符合 ”前閉後開“ 區間的要求。

基本操作:

iterator begin() { return (link_type)((*node).next); }
 
iterator end() { return node; }
 
bool empty() const { return node->next == node; }
 
size_type size() const { // C++11 之前 複雜度線性,11之後複雜度 常數
    size_type result = 0;
    distance(begin(),end(),result); // stl 全局函數
    return result;
}
 
 
reference front() { return *begin(); }
reference back() { return *(--end()); }
           

class list
{
protected:
    // 專屬的空間配置器,每次配置一個節點大小
    typedef simple_alloc<list_node, Alloc> list_node_allocator;
 
// list_node_allocator(n) 辨別配置n個節點空間
 
protected:
    link_type get_node() { return list_node_allocator::allocate(); } // 配置設定一個節點并傳回
    void put_node(link_type p) { list_node_allocator::deallocate(p); } // 釋放節點
 
    link_type create_node(const T& x) // 配置設定空間 并 構造
    {
        link_type p = get_node();
        construct(&p->data, x);// 構造工具
        return p;
    }
 
    void destory_node(link_type p) // 析構 并 釋放空間
    {
        destory(&p->data); // 析構
        put_node(p); // 釋放
    }
 
public:
    list() { empty_initialize(); }// 構造空連結清單
    void push_back(const T& x) { insert(end(), x); }
     
    iterator insert(iterator position, const T& x)
    {
        link_type tmp = create_node(x);
        tmp->next = position.node;
        tmp->prev = position.node->prev;
        (link_type(position.node->prev))->next = tmp;
        position.node->prev = tmp;
        return tmp;
    }
 
 
protected:
    void empty_initialize()
    {
        node = get_node();
        node->next = node;
        node->prev = node;
    }
 
 
...
};
           

  • push_front、push_back、erase、pop_front、pop_back 這些操作 實作上 和 通常手寫連結清單的實作 相同。

将連續範圍的元素遷移到特定位置之前,實作上就是 節點指針的重新連接配接。

transfer 是 其他複雜操作的基礎,如splice、sort、merge

《STL 源碼剖析》 list 實作原理

transfer 不是公開接口

對于transfer的巧妙運用

void splice(iterator position, list& x) //将 x 拼接到 position位置之前
{
    if(!x.empty())
        transfer(position, x.begin(), x.end());
}
 
void splice(iterator position, list&, iterator i) // 将i的元素拼接到position之前,兩者 可能是同一個list
{
    iterator j = i;
    ++j;
    if(position == i || position == j) return;
    transfer(position, i, j);
}
 
// 将[first, last)的元素接到position位置之前
// position 和 [first, last) 可能會指向同一個list
// 但是 position 不能位于[first,last)
void splice(iterator position, list&, iterator first, iterator last)
{
    if(first != last)
        transfer(position, first, last);
}
           

前提:兩個 已經 排好序的連結清單

周遊其中一個連結清單,記錄過程中 周遊 頭尾指針(周遊過程中進行大小比較)。将其拼接到另一個連結清單上。

實作上 和 普通連結清單合并 是類似的。

template <class T, class Alloc>
void list<T, Alloc>::merge(list<T, Alloc>& x)
{
    iterator firts1 = begin();
    iterator last1 = end();
    iterator first2 = x.begin();
    iterator last2 = x.end();
 
    // 周遊處理
    while(first1 ! last1 && first2 != last2)
    {
        if(*first2 < *first1)
        {
            iterator next = first2;
            transfer(first1, first2, ++next);
            first2 = next;
        }
        else
            ++first1;
    }
 
    if(first2!=last2) transfer(last1, first2, last2); // 如果末尾還有殘留部分
}
           

  • list不能使用STL的sort,是因為 STL 的 sort 隻接受 RamdonAccessIterator (随機通路的疊代器類型),而list中是 Bidirectional (雙向疊代器類型)
  • list.sort實作原理,思想非常類似于 歸并排序,但源碼實作上 卻不太好看出來 和 歸并類似,需要自己手動模拟才能 有所體會。
  • 其具體思想可檢視 參考部落格
  • 下圖是對應 該部落格描述的 步驟 和 實作源碼
    《STL 源碼剖析》 list 實作原理