詳解C++?STL模擬實現(xiàn)list
list 概述
相比于 vector 的連續(xù)線性空間,list 采用的是零散的空間,它的好處是每次插入或刪除一個元素,就配置或釋放一個元素空間。
list 是支持常數(shù)時間從容器任何位置插入和移除元素容器,但不支持快速隨機訪問。list 通常實現(xiàn)為雙向鏈表,與 forward_list 相比,list 的迭代器可以向前移動,但也因此需要在節(jié)點中多開辟一個指針變量,在空間上效率稍低。
接口總覽
namespace qgw { /// @brief list 中每個節(jié)點 /// @tparam T 節(jié)點存儲的數(shù)據(jù)的類型 template <class T> struct _list_node { _list_node(const T& data = val()); // 節(jié)點類的構(gòu)造函數(shù) _list_node<T>* _prev; // 指向前一節(jié)點 _list_node<T>* _next; // 指向后一節(jié)點 T _data; // 存儲節(jié)點數(shù)據(jù) }; /// @brief list 的迭代器 /// @tparam T list 數(shù)據(jù)的類型 /// @tparam Ref 數(shù)據(jù)的引用類型 /// @tparam Ptr 數(shù)據(jù)的指針類型 template <class T, class Ref, class Ptr> struct _list_iterator { typedef _list_iterator<T, T&, T*> iterator; typedef _list_iterator<T, Ref, Ptr> self; typedef T value_type; typedef Ptr pointer; typedef Ref reference; typedef _list_node<T> list_node; // 構(gòu)造函數(shù) _list_iterator(list_node* node = nullptr); // 各種運算符重載 bool operator==(const self& x) const; bool operator!=(const self& x) const; reference operator*() const; pointer operator->() const; self& operator++(); self operator++(int); self& operator--(); self operator++(int); list_node* _node; // 指向?qū)?yīng)的 list 節(jié)點 }; template <class T> class list { public: typedef T value_type; typedef T* pointer; typedef T& reference; typedef _list_node<T> list_node; typedef _list_iterator<T, T&, T*> iterator; typedef _list_iterator<T, const T&, const T*> const_iterator; public: // 默認成員函數(shù) list(); list(const list<T>& other); list<T>& operator=(const list<T>& other); ~list(); // 元素訪問 reference front(); reference back(); // 迭代器 iterator begin(); iterator end(); const_iterator begin() const; const_iterator end() const; // 容量 bool empty() const; size_t size() const; // 修改器 void clear(); iterator insert(iterator pos, const T& val); void push_front(const T& val); void push_back(const T& val); iterator erase(iterator pos); void pop_front(); void pop_back(); void swap(list& other); // 操作 void splice(iterator pos, list& other); void splice(iterator pos, list& other, iterator it); void splice(iterator pos, list& other, iterator first, iterator last); void merge(list& other); void remove(const T& value); void reverse(); void unique(); private: list_node* _node; // 指向鏈表頭節(jié)點 }; }
list 的節(jié)點
list 的節(jié)點我們設(shè)計成一個 _list_node 類,里面有三個成員變量,分別為前后指針和數(shù)據(jù)。
它的構(gòu)造函數(shù)將數(shù)據(jù)初始化為給定數(shù)據(jù),再將前后指針初始化為空即可。
/// @brief 節(jié)點類的構(gòu)造函數(shù) /// @param data 用來構(gòu)造節(jié)點的初值 _list_node(const T& data = T()) : _data(data) { _prev = nullptr; _next = nullptr; }
默認成員函數(shù)
默認構(gòu)造函數(shù)
SGI list 不僅是一個雙向鏈表,還是一個帶頭的循環(huán)鏈表。
/// @brief 構(gòu)造一個空鏈表 list() { _node = new list_node; // 創(chuàng)建一個頭節(jié)點 _node->_prev = _node; // 前面指向自己 _node->_next = _node; // 后面指向自己 }
析構(gòu)函數(shù)
list 的析構(gòu)函數(shù),先調(diào)用 clear 釋放數(shù)據(jù)資源,再 delete 掉頭節(jié)點即可。
/// @brief 釋放資源 ~list() { clear(); delete _node; _node = nullptr; }
拷貝構(gòu)造函數(shù)
用另一容器創(chuàng)建新對象。
先申請一個頭節(jié)點,然后遍歷 other 容器,將 other 中的數(shù)據(jù)逐一尾插到 *this 中。
/// @brief 用給定容器初始化 /// @param other 用來初始化的容器 list(const list<T>& other) { _node = new list_node; _node->_next = _node; _node->_prev = _node; for (const auto& e : other) { push_back(e); } }
復(fù)制賦值函數(shù)
先創(chuàng)建給定容器的拷貝 tmp,然后交換 *this 和 tmp,最后返回 *this。
/// @brief 替換容器內(nèi)容 /// @param other 用作數(shù)據(jù)源的另一容器 /// @return *this list<T>& operator=(const list<T>& other) { // tmp 出了作用域就銷毀了 list<T> tmp(other); swap(tmp); // 返回引用可以連續(xù)賦值 return *this; }
list 的迭代器
list 的節(jié)點在內(nèi)存中不是連續(xù)存儲的,因此不能使用原生指針作為 list 的迭代器。list 的迭代器必須有能力指向 list 的節(jié)點,并能夠正確的遞增、遞減、取值、成員存取等操作。正確的操作是指:遞增時指向下一節(jié)點,遞減時指向上一節(jié)點,取值時取的是節(jié)點的數(shù)據(jù)值,成員取用的是節(jié)點的成員。
由于 STL list 是一個雙向鏈表(double linked-list),迭代器必須具備前移、后移的能力,所以 list 提供的是 Bidirectional Iterators。
構(gòu)造函數(shù)
list 的迭代器中成員變量只有一個節(jié)點指針,將其指向給定節(jié)點即可。
/// @brief list 迭代器的構(gòu)造函數(shù) /// @param node 用來構(gòu)造的節(jié)點 _list_iterator(list_node* node = nullptr) { _node = node; }
operator==
判斷兩迭代器指向的節(jié)點是否為同一個,直接比較迭代器中節(jié)點的指針即可。切記不能比較指針中的值,因為不同節(jié)點的值可能相同。
/// @brief 判斷兩迭代器指向的節(jié)點是否相同 /// @param x 用來比較的迭代器 /// @return 相同返回 true,不同返回 false bool operator==(const self& x) const { return _node == x._node; }
operator!=
!= 的比較方法和 == 一樣。
/// @brief 判斷兩迭代器指向的節(jié)點是否不同 /// @param x 用來比較的迭代器 /// @return 不同返回 true,相同返回 false bool operator!=(const self& x) const { return _node != x._node; }
operator*
迭代器是模仿指針的,讓我們可以像使用指針一樣。因此可以對迭代器進行解引用操作,該操作得到的是迭代器中節(jié)點指針指向的數(shù)據(jù),并且返回引用,因為有可能修改該數(shù)據(jù)。
/// @brief 獲取指向節(jié)點中的數(shù)據(jù)值 /// @return 返回指向節(jié)點數(shù)據(jù)的引用 reference operator*() const { return _node->_data; }
operator->
-> 運算符的重載稍顯復(fù)雜,讓我們先看下面這個場景。
也就是 list 中存儲的是自定義類型,自定義類型中又有多個成員變量,我們想取出指定的成員變量,當(dāng)然這里用 . 也可以做到。
// 有一個學(xué)生類,里面有姓名和學(xué)號兩個成員 struct Stu { string name; string id; }; list<Stu> s; Stu s1 = { "qgw", "001" }; Stu s2 = { "wlr", "002" }; s.push_back(s1); s.push_back(s2); list<Stu>::iterator ptr = s.begin(); // 輸出第一個學(xué)生的姓名和學(xué)號 cout << (*ptr).name << endl; cout << s.begin()->id << endl;
/// @brief 獲取節(jié)點中數(shù)據(jù)的地址 /// @return 返回節(jié)點指向的數(shù)據(jù)的地址 pointer operator->() const { return &(operator*()); }
看到這你可能會疑惑,operator-> 返回的是節(jié)點的數(shù)據(jù)的地址,也是說上面 s.begin()-> 得到的是一個地址,那這條語句是怎么執(zhí)行的?
實際上這里確實應(yīng)該有兩個箭頭像這樣 s.begin()->->id,但這種方式的可讀性太差了,所以編譯器對此做了優(yōu)化,在編譯為我們添加一個箭頭。
operator++
operator++ 運算符的作用十分清晰,就是讓迭代器指向鏈表中下一節(jié)點。
前置實現(xiàn)的思路是:通過迭代器中的節(jié)點指針找到下一節(jié)點,然后賦值給迭代器中的節(jié)點指針。
后置實現(xiàn)的思路是:先保存當(dāng)前位置迭代器,然后調(diào)用前置 ++,最后返回臨時變量。
需要注意的是:前置 ++ 返回的是前進后迭代器的引用,后置 ++ 返回的是一個臨時變量。
/// @brief 前置++ /// @return 返回前進一步后的迭代器 self& operator++() { _node = _node->_next; return *this; } /// @brief 后置++ /// @param 無作用,只是為了與前置 ++ 進行區(qū)分,形成重載 /// @return 返回當(dāng)前的迭代器 self operator++(int) { self tmp = *this; // 直接調(diào)用前置 ++ ++(*this); return tmp; }
operator--
前置實現(xiàn)的思路是:通過迭代器中的節(jié)點指針找到前一節(jié)點,然后賦值給迭代器中的節(jié)點指針。
后置實現(xiàn)的思路是:先保存當(dāng)前位置迭代器,然后調(diào)用前置 --,最后返回臨時變量。
/// @brief 前置 -- /// @return 返回后退一步后的迭代器 self& operator--() { _node = _node->_prev; return *this; } /// @brief 后置 -- /// @param 無作用,只是為了與前置 -- 進行區(qū)分,形成重載 /// @return 返回當(dāng)前的迭代器 self operator--(int) { self tmp = *this; --(*this); return tmp; }
元素訪問
front
front 獲取第一個元素的引用,直接用 begin 獲取指向第一個元素的迭代器,再解引用即可。
/// @brief 返回容器首元素的引用 /// @return 首元素的引用 reference front() { return *begin(); }
back
end 獲取最后一個元素的引用,先用 end 獲取最后一個元素下一位置的迭代器,再回退一步,然后解引用就可以了。
/// @brief 返回容器中最后一個元素的引用 /// @return 最后元素的引用 reference back() { return *(--end()); }
迭代器
在 begin 和 end 實現(xiàn)之前,我們先來看下 list 的示意圖,下圖為有 3 個元素的鏈表:
begin
begin 獲取的是首元素的迭代器,根據(jù)上圖,begin 的實現(xiàn)也就非常清晰了,直接返回頭節(jié)點的下一位置即可。
/// @brief 返回指向 list 首元素的迭代器 /// @return 指向首元素的迭代器 iterator begin() { // 根據(jù)節(jié)點指針構(gòu)造迭代器 return iterator(_node->_next); } // const 版本供 const 容器使用 const_iterator begin() const { return const_iterator(_node->_next); }
end
end 獲取的是最后一個元素下一個位置的迭代器,根據(jù)上圖就是 _node 所指向的節(jié)點。
/// @brief 返回指向 list 末元素后一元素的迭代器 /// @return 指向最后元素下一位置的迭代器 iterator end() { // 調(diào)用 iterator 構(gòu)造函數(shù) return iterator(_node); } const_iterator end() const { return const_iterator(_node); }
容量
empty
begin 和 end 指向相同,說明鏈表此時只有一個頭節(jié)點,鏈表為空。
/// @brief 檢查容器是否無元素 /// @return 若容器為空則為 true,否則為 false bool empty() const { return begin() == end(); }
size
size 函數(shù)的作用是返回容器中元素的數(shù)量。
在 C++ 11 前,該函數(shù)的復(fù)雜度可能是常數(shù)的,也可能是線性的。從 C++ 11 起該函數(shù)的復(fù)雜度為常數(shù)。
下面代碼的時間復(fù)雜度是線性的,要想改成常數(shù)也很簡單,只需要在 list 中開辟一個成員變量記錄個數(shù)即可。
/// @brief 返回容器中的元素數(shù) /// @return 容器中的元素數(shù)量 size_t size() const { size_t sz = 0; auto it = begin(); while (it != end()) { ++it; ++sz; } return sz; }
修改器
insert
下圖為:只有 0、1 兩個元素的鏈表,在 1 之前插入元素值為 2 的節(jié)點的示意圖。
插入的思路比較清晰:
1.插入節(jié)點的 _next 指向 pos 位置的節(jié)點
2.插入節(jié)點的 _prev 指向 pos 前一位置的節(jié)點
3.pos 前一位置的節(jié)點的 _next 指向插入的節(jié)點
4.pos 位置節(jié)點的 _prev 指向插入的節(jié)點
/// @brief 插入元素到容器中的指定位置 /// @param pos 將內(nèi)容插入到 pos 之前 /// @param val 要插入的元素值 /// @return 指向被 插入 val 的迭代器 iterator insert(iterator pos, const T& val) { list_node* tmp = new list_node(val); // 創(chuàng)建要插入的節(jié)點 tmp->_next = pos._node; // (1) tmp->_prev = pos._node->_prev; // (2) (pos._node->_prev)->_next = tmp; // (3) pos._node->_prev = tmp; // (4) return tmp; }
push_front
push_front 的作用是在第一個元素之前插入一個節(jié)點,直接調(diào)用 insert 在 begin 之前插入就行。
/// @brief 添加給定元素 val 到容器起始 /// @param val 要添加的元素值 void push_front(const T& val) { insert(begin(), val); }
push_back
push_back 的作用是在容器的最后添加一個節(jié)點,直接調(diào)用 insert 在 end 之前插入就行。
/// @brief 添加給定元素 val 到容器尾 /// @param val 要添加的元素值 void push_back(const T& val) { insert(end(), val); }
erase
下圖為:有三個元素 0、1、2 的鏈表,刪除 pos 指向節(jié)點(值為 1)的示意圖。
刪除的思路也很清晰:
1.將 pos 前一節(jié)點的 _next 指針指向 pos 的下一節(jié)點
2.將 pos 下一節(jié)點的 _prev 指針指向 pos 的前一節(jié)點
3.delete 釋放掉 pos 所指向的節(jié)點
/// @brief 從容器擦除指定的元素 /// @param pos 指向要移除的元素的迭代器 /// @return 最后移除元素之后的迭代器 iterator erase(iterator pos) { list_node* nextNode = pos._node->_next; // 記錄 pos 指向節(jié)點的下一節(jié)點 list_node* prevNode = pos._node->_prev; // 記錄 pos 指向節(jié)點的前一節(jié)點 prevNode->_next = nextNode; // (1) nextNode->_prev = prevNode; // (2) delete (pos._node); return (iterator)nextNode; }
pop_front
pop_front 移除容器第一個元素,也就是 begin 指向的節(jié)點。
/// @brief 移除容器首元素 void pop_front() { erase(begin()); }
pop_back
pop_back 移除容器最后一個節(jié)點,也就是 end 指向的前一個節(jié)點。
/// @brief 移除容器的末元素 void pop_back() { erase(--end()); }
clear
clear 用于清空容器所有數(shù)據(jù),不清理頭節(jié)點。
采用遍歷的方式調(diào)用 erase 刪除每一個節(jié)點。
/// @brief 從容器擦除所有元素 void clear() { iterator it = begin(); while (it != end()) { it = erase(it); } }
swap
swap 用來交換兩個 list 容器,不用 list 中每個元素的值,直接交換 _node 指針即可。
/// @brief 將內(nèi)容與 other 的交換 /// @param other 要與之交換內(nèi)容的容器 void swap(list& other) { std::swap(_node, other._node); }
操作
splice
在實現(xiàn)該函數(shù)之前,先來看下 list 內(nèi)部的一個函數(shù) transfer。
list 內(nèi)部提供一個遷移操作(transfer):將某連續(xù)范圍的元素遷移都某個特定位置之前。
這個函數(shù)比上面所寫的要復(fù)雜的多,要對著圖仔細思考。
/// @brief 將 [first, last) 范圍的所有元素移動到 pos 之前 /// @param pos 將內(nèi)容移動到 pos 之前 /// @param first 范圍起始位置 /// @param last 范圍結(jié)束位置 void transfer(iterator pos, iterator first, iterator last) { if (pos != last) { last._node->_prev->_next = pos._node; // (1) first._node->_prev->_next = last._node; // (2) pos._node->_prev->_next = first._node; // (3) list_node* tmp = pos._node->_prev; // (4) pos._node->_prev = last._node->_prev; // (5) last._node->_prev = first._node->_prev; // (6) first._node->_prev = tmp; // (7) } }
有了上面的函數(shù),splice 的實現(xiàn)也就非常簡單了,下面共有三個重載實現(xiàn):
根據(jù)要轉(zhuǎn)移的元素選擇調(diào)用不同的函數(shù)。
/// @brief 將 other 接合于 pos 所指位置之前,兩者不能是同一 list /// @param pos 將內(nèi)容插入到它之前 /// @param other 要從它轉(zhuǎn)移內(nèi)容的另一容器 void splice(iterator pos, list& other) { if (!other.empty()) { transfer(pos, other.begin(), other.end()); } } /// @brief 將 it 所指元素接合到 pos 所指位置之前 /// @param pos 將內(nèi)容插入到它之前 /// @param other 要從它轉(zhuǎn)移內(nèi)容的另一容器 /// @param it 從 other 轉(zhuǎn)移到 *this 的元素 void splice(iterator pos, list& other, iterator it) { // 取得一個 [i, j) 的范圍,使得能調(diào)用 transfer iterator j = it; ++j; // 檢查是否有必要執(zhí)行 // pos == it 時說明 pos 和 it 指向的是同一節(jié)點 // pos == j 時說明,it 剛好在 pos 之前 if (pos == it || pos == j) { return; } transfer(pos, it, j); } /// @brief 將 [first, last) 內(nèi)的所有元素接合于 pos 所指位置之前 /// @param pos 將內(nèi)容插入到它之前 /// @param first 起始位置 /// @param last 結(jié)束位置 void splice(iterator pos, list& other, iterator first, iterator last) { if (first != last) { transfer(pos, first, last); } }
merge
merge 函數(shù)和歸并排序中的合并操作類似,該函數(shù)的作用是:合并兩個已遞增排序的鏈表。
/// @brief 合并兩個遞增鏈表,合并到 *this 上 /// @param other 要合并的另一容器 void merge(list& other) { iterator first1 = begin(); iterator end1 = end(); iterator first2 = other.begin(); iterator end2 = other.end(); while (first1 != end1 && first2 != end2) { if (*first2 < *first1) { iterator next = first2; transfer(first1, first2, ++next); first2 = next; } else { ++first1; } } if (first2 != end2) { // 將 other 鏈表剩余元素合并到 *this 中 transfer(first1, first2, end2); } }
remove
remove 用來刪除 list 中值等于 val 的元素,直接遍歷鏈表,找到就刪除。
/// @brief 移除等于 val 元素 /// @param val 要移除的元素的值 void remove(const T& val) { iterator first = begin(); iterator last = end(); while (first != last) { iterator next = first; ++next; if (*first == val) { erase(first); } first = next; } }
reverse
reverse 的作用是逆轉(zhuǎn)容器中的元素順序。
該函數(shù)的思路簡單,從第 2 個元素開始,以次頭插到鏈表頭部。
/// @brief 將 *this 的內(nèi)容逆置 void reverse() { // 空鏈表或只有一個元素直接返回 if (_node->_next == _node || _node->_next->_next == _node) { return; } iterator first = begin(); ++first; while (first != end()) { iterator old = first; // 第一次循環(huán)時指向第 2 個元素 ++first; // 第一次循環(huán)時指向第 3 個元素 transfer(begin(), old, first); } }
unique
unique 用來移除數(shù)值相同的連續(xù)元素,只保留一個。
該函數(shù)利用的是雙指針的思想,first 和 next 分別指向前后兩個元素,相同就刪除后一個。
/// @brief 移除數(shù)值相同的連續(xù)元素 void unique() { iterator first = begin(); iterator last = end(); if (first == last) { return; } iterator next = first; while (++next != last) { // 此時 next 指向 first 下一個節(jié)點 if (*first == *next) { // 連續(xù)的相同,就刪除后一個 erase(next); } else { // 不相同 first 向 end 移動 first = next; } // 使 next 再次指向 first // 這樣再進 while 時,++next 又使 next 指向 first 下一個 next = first; } }
以上就是詳解C++ STL模擬實現(xiàn)list的詳細內(nèi)容,更多關(guān)于C++ STL list的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C++結(jié)構(gòu)體struct和類class區(qū)別詳解
struct和class有什么區(qū)別?最本質(zhì)的一個區(qū)別就是默認的訪問控制:默認的繼承訪問權(quán)限,struct是public的,class是private的。2017-11-11利用C++?OpenCV?實現(xiàn)從投影圖像恢復(fù)仿射特性
我們通過相機拍攝的圖片存在各種畸變,其中投影畸變使得原本平行的直線不再平行,就會產(chǎn)生照片中近大遠小的效果。本文將具體介紹如何利用OPenCV實現(xiàn)從投影圖像恢復(fù)仿射特性,接下來跟著小編一起學(xué)習(xí)吧2021-11-11詳解如何從Matlab中導(dǎo)出清晰的結(jié)果圖片
寫文章的時候有時需要matlab導(dǎo)出清晰的圖片,如果直接用figure里面的保存的話不夠清晰,下面這篇文章主要給大家介紹了關(guān)于如何從Matlab中導(dǎo)出清晰的結(jié)果圖片的相關(guān)資料,需要的朋友可以參考下2022-06-06