SGISTL原始碼閱讀十一 list容器上
阿新 • • 發佈:2018-11-14
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
節點,它的prev
和next
都指向了它本身。
通過一下幾個操作我們可以更清楚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
節點,並讓它的prev
和next
都指向它本身。
- 初始化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_node
,destory_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
的相關操作。