1. 程式人生 > >SGISTL原始碼閱讀十一 list容器上

SGISTL原始碼閱讀十一 list容器上

SGISTL原始碼閱讀十一 list容器上

list概述

之前我們學習了連續線性空間的vector,當容量不夠用時它會簡單粗暴地直接擴大到原來的兩倍,難免會造成一定的空間浪費。
list就不一樣,它的好處是每插入或者刪除一個元素,就配置或釋放一個元素空間。它對空間的使用絕對精準,一點也不浪費。而且在元素的插入刪除操作上,時間複雜度是常數級別,對於vector插入刪除操作相當的麻煩。


深入原始碼

list的資料結構及其迭代器

list的節點

list本身和它的節點不是同一個結構,是分開設計的。
通過以下程式碼我們不難看出,list是一個雙向連結串列。

template <class T>
struct __list_node {
  typedef void* void_pointer;	//
  void_pointer next;		//指向前一個節點
  void_pointer prev;		//指向後一個節點
  T data;
};

在這裡插入圖片描述

list的資料結構

其實list除了是一個雙向連結串列外,它還是一個雙向迴圈連結串列,它使用了一個node指標

template <class T, class Alloc = alloc>
class list {
protected:
  typedef void* void_pointer;
  typedef __list_node<T> list_node; 
	//...
protected:
  link_type node;		//list本身包含一個節點,用來表示整雙向迴圈連結串列
	//...

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

為空時,仍然會存在一個node節點,它的prevnext都指向了它本身。

通過一下幾個操作我們可以更清楚list的結構

//因為是雙向迴圈連結串列,node指向尾端空節點,它的下一個節點便是頭節點
iterator begin() { return (link_type)((*node).next); }
const_iterator begin() const { return (link_type)((*node).next); }

//node就是list的尾端
iterator end() { return node; }
const_iterator end() const { return node; }

//判斷list是否為空,就看node節點的下一個節點是不是它本身(學習了list的構造你可能會更清楚)
bool empty() const { return node->next == node;}

//獲取頭節點元素
reference front() { return *begin(); }
const_reference front() const { return *begin(); }
//獲取最後一個節點元素
reference back() { return *(--end()); }

如圖所示
在這裡插入圖片描述

list的迭代器

list的資料結構使它不能像vector那樣簡單地使用普通指標作為迭代器。
list的迭代器必須能夠指向list的節點,並且能夠進行正確的++、–、通過*取值等。

template<class T, class Ref, class Ptr>
struct __list_iterator {
  typedef __list_iterator<T, T&, T*>             iterator;
  typedef __list_iterator<T, const T&, const T*> const_iterator;
  typedef __list_iterator<T, Ref, Ptr>           self;
  
  //它的迭代器型別是bidirectional_iterator(不支援隨機訪問)
  //定義了迭代器的五種相應型別
  typedef bidirectional_iterator_tag iterator_category;
  typedef T value_type;
  typedef Ptr pointer;
  typedef Ref reference;
  typedef __list_node<T>* link_type;
  typedef size_t size_type;
  typedef ptrdiff_t difference_type;
  
  //內部維護的node節點,它的型別是list節點的指標型別
  //這個node相當重要
  link_type node;

  //迭代器的建構函式
  __list_iterator(link_type x) : node(x) {}
  __list_iterator() {}
  __list_iterator(const iterator& x) : node(x.node) {}
  
  //以下函式均為操作符過載
  bool operator==(const self& x) const { return node == x.node; }
  bool operator!=(const self& x) const { return node != x.node; }
  reference operator*() const { return (*node).data; }

#ifndef __SGI_STL_NO_ARROW_OPERATOR
  pointer operator->() const { return &(operator*()); }
#endif /* __SGI_STL_NO_ARROW_OPERATOR */

  //前++
  self& operator++() {
    node = (link_type)((*node).next);
    return *this;
  }
  //後++
  self operator++(int) {
    self tmp = *this;
    ++*this;
    return tmp;
  }
  //前--
  self& operator--() {
    node = (link_type)((*node).prev);
    return *this;
  }
  //後--
  self operator--(int) {
  self tmp = *this;
  --*this;
  return tmp;
  }
};

值得我們注意的是,list迭代器的型別是bidirectional_iterator,它具備前移、後移的能力,但是不能隨機訪問。

list的構造及空間分配

list的建構函式
  • 預設建構函式
link_type get_node() { return list_node_allocator::allocate(); }
  //...
void empty_initialize() {
    node = get_node();
    node->next = node;
    node->prev = node;
  }
  //...
list() { empty_initialize(); }

我們可以看到預設建構函式的操作是建立一個node節點,並讓它的prevnext都指向它本身。

  • 初始化n個節點的值
  void fill_initialize(size_type n, const T& value){
    //申請一個node並初始化它的指向
    empty_initialize();
    __STL_TRY {
      //申請成功則以頭插法插入進list
      insert(begin(), n, value);
    }
    //申請失敗則將所有節點全部銷燬
    __STL_UNWIND(clear(); put_node(node));
  }
  //...
//將n個節點的值都賦值為value
list(size_type n, const T& value) { fill_initialize(n, value); }
list(int n, const T& value) { fill_initialize(n, value); }
list(long n, const T& value) { fill_initialize(n, value); }
//將n個節點的值都賦值為預設值(explicit之前講過,就是防止隱式轉換)
explicit list(size_type n) { fill_initialize(n, T()); }
  • 傳入迭代器範圍
#ifdef __STL_MEMBER_TEMPLATES
  template <class InputIterator>
  void range_initialize(InputIterator first, InputIterator last) {
    //申請一個空白節點
    empty_initialize();
    __STL_TRY {
      //頭插法插入迭代器first,last指向範圍的元素
      insert(begin(), first, last);
    }
    //異常處理,銷燬空間,防止記憶體洩露
    __STL_UNWIND(clear(); put_node(node));
  }
#else  /* __STL_MEMBER_TEMPLATES */
  //針對普通指標的過載版本
  void range_initialize(const T* first, const T* last) {
    empty_initialize();
    __STL_TRY {
      insert(begin(), first, last);
    }
    __STL_UNWIND(clear(); put_node(node));
  }
  //過載版本
  void range_initialize(const_iterator first, const_iterator last) {
    empty_initialize();
    __STL_TRY {
      insert(begin(), first, last);
    }
    __STL_UNWIND(clear(); put_node(node));
  }
#endif /* __STL_MEMBER_TEMPLATES */
//...
  template <class InputIterator>
  list(InputIterator first, InputIterator last) {
    range_initialize(first, last);
  }
  list(const T* first, const T* last) { range_initialize(first, last); }
  list(const_iterator first, const_iterator last) {
    range_initialize(first, last);
  }
  • 拷貝建構函式
  list(const list<T, Alloc>& x) {
    range_initialize(x.begin(), x.end());
  }
list的解構函式
~list() {
	//呼叫clear刪除所有list節點(除node外)
    clear();
    //銷燬node(put_node負責刪除某個節點)
    put_node(node);
  }
list節點的申請和釋放

在前面我們已經介紹了get_node,申請一個節點。與get_node所對應的是put_node,刪除一個節點

link_type get_node() { return list_node_allocator::allocate(); }
//直接銷燬
void put_node(link_type p) { list_node_allocator::deallocate(p); }

其他的相關操作還有create_nodedestory_node

//申請並初始化該節點
link_type create_node(const T& x) {
	//申請一個節點
    link_type p = get_node();
    __STL_TRY {
      //構造該節點
      construct(&p->data, x);
    }
    //異常處理
    __STL_UNWIND(put_node(p));
    return p;
}
//析構並釋放該節點
void destroy_node(link_type p) {
    destroy(&p->data);		//全域性函式,析構基本工具
    //刪除該節點
    put_node(p);
}

總結

我們介紹了list的資料結構,為一個雙向迴圈連結串列,list本身維護了一個node,就算list為空,也會存在一個node節點。還介紹了list的各種建構函式和空間分配的相關內容,後面我們將繼續介紹list的相關操作。