一、引言
? ? ? ? list的實現,還是比較簡單的,大家只要想著土家樓的形狀,畫出圖來就好了,不需要過多擔心。本次的博客會發出一個完整的實現List的List.hpp,以后也會這樣,主要是分段發被說孩子分段生。
二、模擬List
? ? ? ? 由于list中的結構需要特定的類型和特定的指定地址的,所以我們先要實現list中的結點和迭代器。正所謂"工欲善其事,必先利其器"。
? ? ? ? 2.1? ? ? ? List內的結點制造
?????????????????可以理解為一個包裹,用箱子包裹著的網購物品,list相當于分配的快遞線。list結點要有前后指針,肯定也要有存儲數據的類型。所以list_node中設計三個數據。
// 模版的使用。
// 可以套用任何類型,讓編譯器自己去推。
template< class T >
// 用struct是因為struct默認public訪問(也就是允許訪問任何資源)。
struct List_Node
{// 缺省參數,未傳參時,可以默認初始化_val。// _next和_prev都是nullptr,初始化好,方便list類蓋。List_Node(T val = T()):_val(val), _next(nullptr), _prev(nullptr){;}// C++11的萬能引用,等些到萬能引用會粘貼好的。template < class X >List_Node(X&& val):_val(val), _next(nullptr), _prev(nullptr){;}T _val;List_Node* _next;List_Node* _prev;
};
? ? ? ? 2.2? ? ? Iterator的模擬實現
? ? ? ? ? ? ? ? iterator迭代器是用來代替指針指向list中的地址的,所以肯定要有list結點的指針。在iterator迭代器中我們需要重載他們的運算符,重載運算符是C++的特性(operator 要重載的運算符,值得一提的是,重載的運算符不能違背運算符本身之外的操作,不然很容易報錯)。我們根據迭代器類比指針就可以知道要重載什么運算符,由于list迭代器是雙向迭代器,不支持+、-某一位置的單位距離,所以我們不能支持Self operator + ()和Self operator - (),不僅如此還要刪除掉他們。使用delete關鍵字,令這兩個函數賦值delete,兩個函數就會停止生成。
template< class T , class Ptr , class Ref >
struct Iterator
{// 設置list中的List_Node,一個具有標識性的名字。// 封裝好一點。typedef List_Node < T > Node;// 可變性// 為了實現在list中的普通類型和const類型的迭代器。// 不用實現兩份代碼。// 在接下來的list中會講。// 最主要的原因是list的迭代器不能像vector中的迭代器直接加const。// 如果只加const,說明迭代器指向了另一個結點類型,根本無法適用。typedef Iterator < T , Ptr , Ref > Self;// 而且這樣不用寫一大串類型名。// 初始化對象。Iterator(Node* node):_node(node){;}Ref operator * (){return _node->_val;}Ptr operator -> (){return &(_node->_val);}Self& operator ++ (){_node = _node->_next;return (*this);}Self& operator -- (){_node = _node->_prev;return _node;}Self operator ++ (int){Self tmp(_node);_node = _node->_next;return tmp;}Self operator -- (int){Self tmp(_node);_node = _node->_next;return tmp;}Self& operator = (const Self& s){_node = s._node;return (*this);}// 減少拷貝,怕臨時值搗亂,無法左值引用。bool operator == (const Self& node){return _node == node._node;}bool operator != (const Self& node){return _node != node._node;}// 刪除這兩個函數,不讓這兩個函數被錯誤調用。Self& operator + (size_t i) = delete;Self& operator - (size_t i) = delete;Node* _node;
};
? ? ? ? 2.3? ? ? ? List中的基本結構和初始化函數
? ? ? ? ? ? ? ? List中一定包含著List_Node類型結點,就像快遞線上有著的包裹,回轉壽司有著壽司,土家族樓有著房間一樣。上一章我們說過如果設計一個環形的結構,不知道以哪個結點為開頭,所以我們新增一個帶頭鏈接點為開頭,避免群龍無首的事情發生。另外插入過程中,我們無法通過結點直接的運算得到插入結點總數量為多少,所以我們添加一個變量,記錄插入結點的總數量。
// 無參構造
List()
{_lt = new Node();_lt->_next = _lt;_lt->_prev = _lt;
}// 初始化列表構造,就把它當做數組就行了。
// 另外initializer_list是C++11的內容,使用時記得調好編譯器選項。
List(initializer_list<T> lt)
{_lt = new Node();for (auto& e : lt){Emplace_back(e);}
}Node* _lt;
size_t _n;
? ? ? ? 2.4? ? ? ? size()函數和empty()函數
? ? ? ? ? ? ? ? List不像Vector和String一樣有capacity()有著空間大小,只要有_n就行了。根據_n我們可以判斷List中是否存在數據以及插入數據有多少個。
size_t size()
{return _n;
}bool empty()
{return _n == 0;
}
? ? ? ? 2.5? ? ? ? Insert()和Erase()函數
? ? ? ? ? ? ? ? Insert()函數和Erase()函數都是在指定位置插入刪除。
????????????????
? ? ? ? ? ? ? ? 我們先從插入開始講起。插入只需要將pos位置上的結點(我們將這個結點設為結點A)和要pos位置之前(_prev)的結點(我們將這個結點設為結點B)和該位置的結點的_prev、_next指針做修改就可以了。由圖可知讓結點A(pos)的_prev指針指向新結點的地址,結點B(--pos上的結點)的_next指針指向新結點的地址,再讓新結點的_prev指向結點B,_next指針指向結點A。
? ? ? ? ? ? ? ? 講完插入,就要將刪除了。刪除就更簡單了,將要刪除結點pos之前的位置和要刪除結點之后的位置直接相連。再將pos上的結點delete掉,返回現在處于pos位置上的結點,也就是pos位置上的下一個結點。
????????另外為什么需要返回插入、刪除位置上新結點?
? ? ? ? 是為了更新迭代器顯示的pos位置上的結點避免訪問出現錯誤,那為什么不直接在函數內修改pos呢?
? ? ? ? 這個主要是出于有些迭代器不需要去修改,這個只能讓用戶去進行決定,比如說insert(begin()),begin()需要修改內部的指針嗎?況且有時候這是一份吃力不討好的工作,畢竟拷貝也是有效率犧牲的。其前面也提到過C++追求極致的效率,是不可能損害效率的。
iterator Insert(iterator pos , const T& val)
{// 前節點Node* prev = ((pos._node)->_prev);// 后節點Node* next = pos._node;// 新插入節點。Node* tmp = new Node (val);// 新節點插入tmp->_next = next;tmp->_prev = prev;// 前節點更新next(記錄下一個節點的地址。)prev->_next = tmp;// 后節點節點更新prev(記錄上一個節點的地址。)next->_prev = tmp;// 更新結點個數。++_n;return iterator(tmp);
}// 按照STL的思想,應該是返回后一個位置,因為返回刪除后該位置的指針,后一個位置已經頂替了該位置的指針。
iterator Erase(iterator pos)
{// 判斷是否存在數據。assert(empty());// 刪除位置不能為確定位置的頭結點。assert(pos != End());// 前節點Node* prev = ((pos._node)->_prev);// 后節點Node* next = (pos._node)->_next;// 刪除該節點。next->_prev = prev;prev->_next = next;pos._node->_next = pos._node->_prev = nullptr;delete pos._node;// 更新結點個數。--_n;// 構建匿名對象(方便返回),相當于調用一個Iterator的初始化函數。return iterator(next);
}
? ? ? ? 2.6????????begin()函數和end()函數
? ? ? ? ? ? ? ? begin()指明List中的起始位置(當List中沒有結點時,會等于end()),所以begin()應返回的是定位的頭結點。end()可以設為定位頭結點本身。
iterator begin(){return iterator(_lt->_next);}iterator end(){return iterator(_lt);}
? ? ? ? 2.7? ? ? ? push_back()函數和pop_back()函數????????
? ? ? ? ? ? ? ?至于這個就更簡單了,代碼應該講究能復用時就復用,這樣能提高代碼的健壯性?,畢竟改代碼只用改一個地方還是改幾個地方還是很便捷的,最重要的是使代碼變得簡潔,以后用的時候也好用。所以我們直接復用insert和erase。
void push_back(T val)
{Insert(end(),val);
}void pop_back()
{Erase(end());
}
????????2.8? ? ? ? emplace()函數和empalce_back()函數????????
? ? ? ? ? ? ? ? 這個我保證C++11的時候絕對會講。到時候放個鏈接在這里。
三、全部代碼
#include <iostream>
#include <initializer_list>
#include <cassert>
using namespace std;namespace Link_List
{template< class T >struct List_Node{List_Node(T val = T()):_val(val), _next(nullptr), _prev(nullptr){;}template < class X >List_Node(X&& val):_val(val), _next(nullptr), _prev(nullptr){;}T _val;List_Node* _next;List_Node* _prev;};template< class T , class Ptr , class Ref >struct Iterator{typedef List_Node < T > Node;typedef Iterator < T , Ptr , Ref > Self;Iterator(Node* node):_node(node){;}Ref operator * (){return _node->_val;}Ptr operator -> (){return &(_node->_val);}Self& operator ++ (){_node = _node->_next;return (*this);}Self& operator -- (){_node = _node->_prev;return _node;}Self operator ++ (int){Self tmp(_node);_node = _node->_next;return tmp;}Self operator -- (int){Self tmp(_node);_node = _node->_next;return tmp;}// 減少拷貝,怕臨時值搗亂,無法左值引用。bool operator == (const Self& node){return _node == node._node;}bool operator != (const Self& node){return _node != node._node;}// 刪除這兩個函數,不讓這兩個函數被錯誤調用。Self& operator + (size_t i) = delete;Self& operator - (size_t i) = delete;Node* _node;};template < class T > class List{public:typedef List_Node < T > Node;typedef Iterator < T , T* , T& > iterator;typedef Iterator < T, const T*, const T& > Const_Iterator;List(){_lt = new Node();_lt->_next = _lt;_lt->_prev = _lt;}List(initializer_list<T> lt){// 設計一個帶頭結點。_lt = new Node();_lt->_next = _lt;_lt->_prev = _lt;for (auto& e : lt){Emplace_back(e);}}~List(){size_t n = _n;for(int i = 0;i < n;++i){Erase(begin());}//cout << endl;//cout << _n << ":" << n << endl;//iterator it = Begin();//while (it != End())//{// cout << *it << endl;// ++it;//}delete _lt;_lt = nullptr;}iterator begin(){return iterator(_lt->_next);}iterator end(){return iterator(_lt);}iterator Insert(iterator pos , const T& val){// 前節點Node* prev = ((pos._node)->_prev);// 后節點Node* next = pos._node;// 新插入節點。Node* tmp = new Node (val);// 新節點插入tmp->_next = next;tmp->_prev = prev;// 前節點更新next(記錄下一個節點的地址。)prev->_next = tmp;// 后節點節點更新prev(記錄上一個節點的地址。)next->_prev = tmp;// 更新結點個數。++_n;return iterator(tmp);}// 按照STL的思想,應該是返回后一個位置,因為返回刪除后該位置的指針,后一個位置已經頂替了該位置的指針。iterator Erase(iterator pos){// 判斷是否存在數據。// assert()不滿足則觸發。// 本質是一種檢查。// 例如assert(false);常常被拿來做防御式編程assert(size());// 刪除位置不能為確定位置的頭結點。assert(pos != end());// 前節點Node* prev = ((pos._node)->_prev);// 后節點Node* next = (pos._node)->_next;// 刪除該節點。next->_prev = prev;prev->_next = next;pos._node->_next = pos._node->_prev = nullptr;delete pos._node;// 更新結點個數。--_n;// 構建匿名對象(方便返回),相當于調用一個Iterator的初始化函數。return iterator(next);}size_t size(){return _n;}bool empty(){return _n == 0;}void push_back(T val){Insert(end(),val);}void pop_back(){Erase(end());}iterator add(iterator it){return it;}// 接收參數包的類型,參數包不是單純相同類型的集成。template < class X , class... ARGS >// 參數包后面...是展開的意思。// 前面則類似聲明的意思。iterator add(iterator pos, X&& val, ARGS&&... args){iterator it = Insert(pos , val);return add(it, args...);}template < class... ARGS >iterator Emplace(iterator cit, ARGS&&... args){iterator it (cit);return add(it,args...);}template < class... ARGS >void Emplace_back(ARGS&&... args){Emplace(end(),args...);}private:Node* _lt = nullptr;size_t _n;};// 測試Insert函數void test01(){List < int > lt;for (int i = 0; i < 13; ++i){lt.push_back(i);}//lt.Erase(lt.begin());//lt.Erase(lt.end());}void test02(){List < int > lt = { 1,2,3,4,5,6,7 };for (auto& e : lt){std::cout << e << " ";}std::cout << std::endl;}void test03(){//List < int > lt = { 11121,131,0321 ,1311,33,113,1321 };List < int > lt;int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };for (int i = 0; i < sizeof(a) / sizeof(int); ++i){lt.push_back(a[i]);}List<int>::iterator it = lt.begin();while (it != lt.end()){cout << *it << ' ';++it;}cout << endl;}
}int main()
{Link_List::test02();return 0;
}