C++深入探究list的模擬實(shí)現(xiàn)
示意圖:
迭代器
正向迭代器類
我們之前所理解的是:迭代器理解為像指針一樣的東西,但是在list中有些不同
// 迭代器邏輯 while(it!=l.end()) { *it; // 解引用取數(shù)據(jù) ++it;// 自加到達(dá)下一個(gè)位置 }
我們可以來觀察一下STL源碼中大佬是怎么封裝的:
我們可以看到,只有一個(gè)成員,那就是一個(gè)結(jié)點(diǎn)的指針node,link_type又是一個(gè)自定義類型的指針,我們原生類型的指針在vector或者string中是可以直接typedef成為迭代器的,但是list底層是雙鏈表,數(shù)據(jù)結(jié)構(gòu)并非連續(xù)的,所以直接*it或者++it是不能夠完成迭代器的任務(wù)的,但是C++中支持對于自定義類型的運(yùn)算符重載,我們可以對解引用和自加兩個(gè)運(yùn)算符重載。
++it
:就是當(dāng)前指針存放下一個(gè)結(jié)點(diǎn)的地址
*it
:解引用當(dāng)前節(jié)點(diǎn),取出值來
迭代器中,拷貝構(gòu)造、運(yùn)算符賦值重載、析構(gòu)都不需要自己實(shí)現(xiàn),使用默認(rèn)生成的即可(即淺拷貝),因?yàn)榈魇怯米远x類型的指針封裝的,訪問修改鏈表,節(jié)點(diǎn)屬于鏈表,不屬于迭代器,所以不用管它。
我們在傳入const版本的list時(shí),list是const對象,需要的是const_iterator,這里會(huì)出現(xiàn)錯(cuò)誤,不能將const的list的迭代器傳給普通迭代器。如下所示例子:
void print_list(const list<int>& lt) { // lt.begin()是const迭代器(只可讀) // it是普通迭代器(可讀可寫) list<int>::iterator it = lt.begin(); while (it != lt.end()) { cout << *it << " "; ++it; } }
現(xiàn)在我們?nèi)绾螌?shí)現(xiàn)一個(gè)const的迭代器呢?
意思就是只可以讀不能夠?qū)憽?梢?code>++,- -
,*
解引用,但是解引用時(shí)不能修改數(shù)據(jù)。
可以想到這種寫法:
const T& operator*()const { return _node->_data; } T& operator*() { return _node->_data; }
但是并不是迭代器是const的,而是我們傳入的list容器是const版本的。
我們可以將寫一個(gè)const_iterator 的類版本,這樣普通迭代器和const迭代器就不是一個(gè)類型了,是兩個(gè)不同的類型了,這樣會(huì)造成代碼冗余。
template<class T> struct __const_list_iterator { //... // __list_iterator全部替換為__const_list_iterator };
優(yōu)化:
增加一個(gè)模板參數(shù)class Ref
這樣第一個(gè)模板參數(shù)都是T,我們可以根據(jù)傳入的第二個(gè)參數(shù)來推出時(shí)T&還是const T&,本來我們是要實(shí)現(xiàn)兩個(gè)類的,現(xiàn)在只需要增加一個(gè)模板參數(shù)即可,這里體現(xiàn)出了C++泛型的優(yōu)勢!
迭代器我們說,它是像指針一樣的東西,如果它是指向的一個(gè)結(jié)構(gòu)體,需要用它的成員變量,我們還需要重載->
箭頭
struct Date { int _year; int _month; int _day; Date(int year = 0, int month = 0, int day = 0) //這里要給默認(rèn)參數(shù),因?yàn)樾枰獦?gòu)建一個(gè)哨兵位頭結(jié)點(diǎn) :_year(year) , _month(month) , _day(day) {} }; void test_list2() { list<Date> lt; lt.push_back(Date(2022, 1, 1)); lt.push_back(Date(2022, 1, 2)); lt.push_back(Date(2022, 1, 3)); lt.push_back(Date(2022, 1, 4)); // 現(xiàn)在來遍歷日期類 list<Date>::iterator it = lt.begin(); while (it != lt.end()) { cout << (*it)._year << "/" << (*it)._month << "/" << (*it)._day << endl; ++it; } cout << endl; }
這里的*
解引用然后再去.
,我們可以重載->
,讓他可以去調(diào)用結(jié)構(gòu)體的成員,這樣更加快捷高效方便。
T* operator->() { return &(_node->_data); }
進(jìn)一步解釋:
*it
調(diào)用operator*
,返回一個(gè)結(jié)點(diǎn)對象,對象再.
操作,拿到數(shù)據(jù)
it->
調(diào)用operator->
,返回對象的指針,(這里返回的是原生指針)再通過->
調(diào)用用結(jié)構(gòu)體成員,這里實(shí)際上應(yīng)該是it->->_year
,但是這樣寫,可讀性很差,所以編譯器做了一個(gè)優(yōu)化,省略了一個(gè)->
,所以所有的類型只要想要重載->
,都會(huì)這樣優(yōu)化省略一個(gè)->
這里又會(huì)衍生出一個(gè)問題,那就是如果使用const_iterator,使用->也會(huì)修改數(shù)據(jù),所以再增加一個(gè)模板參數(shù)
// 正向迭代器類 template<class T, class Ref, class Ptr> struct __list_iterator { typedef ListNode<T> Node; typedef __list_iterator<T, Ref, Ptr> self;// 再次typedef,方便后續(xù)的修改 Node* _node; __list_iterator(Node* x)// 迭代器的實(shí)質(zhì),就是自定義類型的指針 :_node(x) {} // ++it 返回++之后的引用對象 self& operator++() { _node = _node->_next; return *this; } // it++ 返回++之前的對象 self operator++(int) { self tmp(this); _node = _node->_next; return tmp; } // --it self& operator--() { _node = _node->_prev; return *this; } // it-- self operator--(int) { self tmp(this); _node = _node->_prev; return tmp; } // 返回引用,可讀可寫 Ref operator*() { return _node->_data; } // 返回對象的指針 Ptr operator->() { return &(_node->_data); } bool operator!=(const self& it)const { return _node != it._node; } bool operator==(const self& it)const { return _node == it._node; } };
反向迭代器類
實(shí)質(zhì):對于正向迭代器的一種封裝
反向迭代器跟正想迭代器區(qū)別就是++,- -的方向是相反的
所以反向迭代器封裝正向迭代器即可,重載控制++,- -的方向
#pragma once // reverse_iterator.h namespace sjj { template <class Iterator, class Ref, class Ptr> class reverse_iterator { typedef reverse_iterator<Iterator,Ref,Ptr> self; public: reverse_iterator(Iterator it) :_it(it) {} // 比較巧妙,解引用取的是當(dāng)前位置的前一個(gè)位置的數(shù)據(jù) // operator*取前一個(gè)位置, 主要就是為了讓反向迭代器開始和結(jié)束跟正向迭代器對稱 Ref operator *() { Iterator tmp = _it; return *--tmp; } Ptr operator->() { return &(operator*()); } self& operator++() { --_it; return *this; } self operator++(int) { self tmp = *this; --_it; return tmp; } self& operator--() { ++_it; return *this; } self operator--(int) { self tmp = *this; ++_it; return tmp; } bool operator!=(const self& rit) { return _it != rit._it; } bool operator==(const self& rit) { return _it == rit._it; } private: Iterator _it; }; }
push_back尾插函數(shù)
void push_back(const T& x) { // 先找尾記錄 /*Node* tail = _head->_prev; Node* newnode = new Node(x); tail->_next = newnode; newnode->_prev = tail; _head->_prev = newnode; newnode->_next = _head; tail = tail->_next;*/ // 復(fù)用insert函數(shù) insert(end(), x); }
push_front頭插函數(shù)
// 頭插 void push_front(const T& x) { // 復(fù)用insert函數(shù) insert(begin(), x); }
insert插入函數(shù)
// 在pos位置前插入 iterator insert(iterator pos, const T& x) { Node* cur = pos._node; Node* prev = cur->_prev; Node* newnode = new Node(x); prev->_next = newnode; newnode->_prev = prev; newnode->_next = cur; cur->_prev = newnode; return iterator(newnode);// 返回新插入結(jié)點(diǎn)位置的迭代器 }
注意:這里list的insert函數(shù),pos位置的迭代器不會(huì)失效,因?yàn)閜os指向的位置不會(huì)改變,vector中迭代器失效的原因是因?yàn)榕矂?dòng)數(shù)據(jù),導(dǎo)致指向的位置的數(shù)據(jù)發(fā)生變化。
erase刪除函數(shù)
iterator erase(iterator pos) { assert(pos != end());//不能將哨兵位的頭結(jié)點(diǎn)給刪除了 Node* prev = pos._node->_prev; Node* next = pos._node->_next; delete pos._node; prev->_next = next; next->_prev = prev; return iterator(next);// 返回pos位置的下一個(gè)位置的迭代器 }
注意:這里的pos位置的迭代器一定會(huì)失效,因?yàn)槎家呀?jīng)將結(jié)點(diǎn)給刪除了。
pop_front函數(shù)
// 復(fù)用erase函數(shù) void pop_front() { erase(begin()); }
pop_back函數(shù)
// 復(fù)用erase函數(shù) void pop_back() { erase(--end()); }
構(gòu)造函數(shù)
list() { _head = new Node(); _head->_next = _head; _head->_prev = _head; } // 函數(shù)模板,用迭代器區(qū)間進(jìn)行初始化 template<class InputIterator> list(InputIterator first, InputIterator last) { _head = new Node(); _head->_next = _head; _head->_prev = _head; while (first != last) { push_back(*first); ++first; } } list(size_t n, const T& val = T()) { _head = new Node(); _head->_next = _head; _head->_prev = _head; for (size_t i = 0; i < n; ++i) { push_back(val); } }
注意:
這兩個(gè)構(gòu)造函數(shù)一起使用可能會(huì)存在問題,填充版本和構(gòu)造器版本可能會(huì)存在沖突,如下例子:
struct Date { int _year; int _month; int _day; Date(int year = 0, int month = 0, int day = 0)//這里要給默認(rèn)參數(shù),因?yàn)橛幸粋€(gè)哨兵位頭結(jié)點(diǎn)需要初始化 :_year(year) , _month(month) , _day(day) {} }; void test_list4() { list<Date> lt1(5, Date(2022, 9, 9)); for (auto e : lt1) { cout << e._year << "/" << e._month << "/" << e._day << endl; } cout << endl; list<int> lt2(5, 1); for (auto e : lt2) { cout << e << " "; } cout << endl; }
對于這兩個(gè):在實(shí)例化時(shí)會(huì)調(diào)用更加匹配的構(gòu)造函數(shù)初始化
list lt1(5, Date(2022, 9, 9))它會(huì)正常調(diào)用list(size_t n, const T& val = T())
list lt2(5, 1)而它會(huì)將5和1推演成兩個(gè)int,進(jìn)而去匹配這個(gè)迭代器版本的構(gòu)造函數(shù)
template < class InputIterator> list(InputIterator first, InputIterator last)
,但是與我們的本意,用n個(gè)val初始化原意相背,而其中有個(gè)*first
,這里int去解引用必會(huì)報(bào)錯(cuò)
改進(jìn):再多提供第一個(gè)參數(shù)是int重載版本的list(int
n, const T& val = T())構(gòu)造函數(shù)
list(int n, const T& val = T()) { _head = new Node(); _head->_next = _head; _head->_prev = _head; for (size_t i = 0; i < n; ++i) { push_back(val); } }
析構(gòu)函數(shù)
~list() { clear(); // 析構(gòu)與clear不同,要將哨兵位頭結(jié)點(diǎn)給刪除了 delete _head; _head = nullptr; }
list拷貝構(gòu)造函數(shù)
淺拷貝會(huì)崩潰的原因是,同一塊空間被析構(gòu)了兩次,所以我們要完成深拷貝
傳統(tǒng)寫法
// 傳統(tǒng)寫法 // lt2(lt1) list(const list<T>& lt) { _head = new Node(); _head->_next = _head; _head->_prev = _head; for (auto e : lt) { push_back(e); } }
注意: 因?yàn)橐{(diào)用push_back函數(shù),push_back的前提是這個(gè)鏈表(lt2)已經(jīng)被初始化了,所以必須要先搞一個(gè)哨兵位頭結(jié)點(diǎn),不然會(huì)崩潰
現(xiàn)代寫法
// 函數(shù)模板 template<class InputIterator> list(InputIterator first, InputIterator last) { _head = new Node(); _head->_next = _head; _head->_prev = _head; while (first != last) { push_back(*first); ++first; } } // lt2(lt1) list(const list<T>& lt) { _head = new Node(); _head->_next = _head; _head->_prev = _head; list<T> tmp(lt.begin(), lt.end()); std::swap(_head, tmp._head); }
注意:lt2需要一個(gè)哨兵位頭結(jié)點(diǎn)
list賦值重載函數(shù)
傳統(tǒng)寫法
// lt2=lt1 list<T>& operator=(const list<T>& lt) { if (this != lt) { clear(); // 將lt2清空 for (auto e : lt)// 再將值全部拷貝過去 { push_back(e); } } return *this; }
現(xiàn)代寫法
// 現(xiàn)代寫法 list<T>& operator=(list<T> lt) { std::swap(_head, lt._head); return *this; }
其他函數(shù)
// 清空 void clear() { /* iterator it = begin(); while (it!=end()) { iterator del = it++;// 利用后置++,返回加加前的迭代器 delete del._node; } // 最后剩下哨兵位頭結(jié)點(diǎn) _head->_next = _head; _head->_prev = _head; */ iterator it = begin(); while (it != end()) { erase(it++); // 也可以復(fù)用erase,it++返回加加之前的值 } }
到此這篇關(guān)于C++深入探究list的模擬實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)C++ list模擬實(shí)現(xiàn)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
在Visual Studio Code中配置C++編譯環(huán)境的問題
關(guān)于Visual Studio Code對C++環(huán)境的配置方法應(yīng)該有好多種,我這里用到了其中的兩種,具體內(nèi)容詳情文中給大家詳細(xì)介紹,對Visual Studio Code配置C++編譯環(huán)境相關(guān)知識(shí)感興趣的朋友一起看看吧2021-07-07C++實(shí)現(xiàn)循環(huán)隊(duì)列
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)循環(huán)隊(duì)列,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-01-01C++中memcpy函數(shù)的使用以及模擬實(shí)現(xiàn)
memcpy是c和c++使用的內(nèi)存拷貝函數(shù),本文主要介紹了C++中memcpy函數(shù)的使用以及模擬實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07C語言實(shí)現(xiàn)將字符串轉(zhuǎn)換為數(shù)字的方法
這篇文章主要介紹了C語言實(shí)現(xiàn)將字符串轉(zhuǎn)換為數(shù)字的方法,涉及系統(tǒng)函數(shù)atoi()函數(shù)的使用技巧,需要的朋友可以參考下2014-12-12C++求解二叉樹的下一個(gè)結(jié)點(diǎn)問題
本文將通過C++求解以下問題:給定一個(gè)二叉樹其中的一個(gè)結(jié)點(diǎn),請找出中序遍歷順序的下一個(gè)結(jié)點(diǎn)并且返回。文中示例代碼講解詳細(xì),感興趣的可以了解一下2022-04-04C語言中實(shí)現(xiàn)“17進(jìn)制”轉(zhuǎn)“10進(jìn)制”實(shí)例代碼
這篇文章主要介紹了C語言中實(shí)現(xiàn)“17進(jìn)制”轉(zhuǎn)“10進(jìn)制”實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2017-05-05