C++list的模擬實(shí)現(xiàn)
一、節(jié)點(diǎn)的結(jié)構(gòu),list的迭代器的結(jié)構(gòu),以及l(fā)ist的結(jié)構(gòu)
1、節(jié)點(diǎn)的結(jié)構(gòu)
對(duì)于鏈表的節(jié)點(diǎn)我們都很熟悉了,節(jié)點(diǎn)中包含兩個(gè)域,一個(gè)指針域一個(gè)數(shù)據(jù)域,為了讓list能夠通用,我們選擇使用模板。
節(jié)點(diǎn)的結(jié)構(gòu)如下:
template<class T> //struct也能定義類,默認(rèn)類的訪問限定符是 public struct list_node { //這個(gè)指針指向前一個(gè)節(jié)點(diǎn) list_node<T>* _prev; //這個(gè)指針指向后一個(gè)節(jié)點(diǎn) list_node<T>* _next; //這個(gè)是數(shù)據(jù)域中的元素 T _data; //對(duì)節(jié)點(diǎn)使用匿名對(duì)象進(jìn)行初始化 list_node(const T& data = T()) :_prev(nullptr) ,_next(nullptr) ,_data(data) {} };
2、迭代器的結(jié)構(gòu)
現(xiàn)在我們已經(jīng)有了節(jié)點(diǎn)了,我們還要有迭代器,如果沒有迭代器我們就不能很好的訪問每一個(gè)節(jié)點(diǎn)。
對(duì)于迭代器我們要讓它指向我們想要的節(jié)點(diǎn),這才能便于我們的訪問,于是很明顯我們迭代器的成員變量就要是一個(gè)節(jié)點(diǎn)的指針!同時(shí)為了讓list能夠通用,我們選擇使用模板來定義迭代器。
迭代器的結(jié)構(gòu)如下:
//這里后面的兩個(gè)參數(shù),在實(shí)際應(yīng)用時(shí)通常是T& , T* 或者是 const T& , const T* //根據(jù)加與不加const 可以分別實(shí)例化出:普通正向迭代器與正向const迭代器 template<class T, class Ref, class Ptr> struct __list_iterator { //將節(jié)點(diǎn)的類型進(jìn)行typedef方便使用 typedef list_node<T> node; //將類自己進(jìn)行typedef方便使用 typedef __list_iterator<T,Ref,Ptr> self; //成員變量 是一個(gè)指向節(jié)點(diǎn)的指針 node* _pnode; //構(gòu)造函數(shù) 用一個(gè)節(jié)點(diǎn)的地址對(duì)迭代器進(jìn)行初始化, __list_iterator(node* pnode) :_pnode(pnode) {} };
3、list的結(jié)構(gòu)
由于list是帶頭雙向循環(huán)鏈表,我們只需要一個(gè)指向頭節(jié)點(diǎn)的指針便能夠管理所有的節(jié)點(diǎn)了。
template<class T> class list { public: //將節(jié)點(diǎn)的類型進(jìn)行typedef方便使用 typedef list_node<T> node; //將迭代器進(jìn)行typedef方便使用 typedef __list_iterator<T, T&, T*> iterator; //將const迭代器進(jìn)行typedef方便使用 typedef __list_iterator<T, const T&, const T*> const_iterator; //默認(rèn)構(gòu)造函數(shù) list() { empty_init(); } //初始化函數(shù) void empty_init() { //申請(qǐng)一個(gè)頭節(jié)點(diǎn),將節(jié)點(diǎn)的地址給_head _head = new node; //讓哨兵位節(jié)點(diǎn)的 前指針指向自己 _head->_prev = _head; //讓哨兵位節(jié)點(diǎn)的 后指針指向自己 _head->_next = _head; } private: //指向哨兵位節(jié)點(diǎn)的指針 node* _head; };
到此為止我們一共建立了三個(gè)類,下面我們模擬實(shí)現(xiàn)鏈表的各種接口時(shí),我們還要繼續(xù)豐富迭代器類的接口與list類的接口
二、迭代器的實(shí)現(xiàn)
由于鏈表的許多操作都要用到迭代器,但是迭代器的一些其他接口我們還沒有實(shí)現(xiàn),在這里我們來實(shí)現(xiàn)迭代器的所有接口。
1、*運(yùn)算符重載
對(duì)于原生指向節(jié)點(diǎn)的指針來說*運(yùn)算符能讓我們拿到節(jié)點(diǎn),但還無法拿到節(jié)點(diǎn)數(shù)據(jù)域中的數(shù)據(jù),但是對(duì)于迭代器來說*運(yùn)算符就要拿到容器中存儲(chǔ)的數(shù)據(jù),所以我們還要對(duì)迭代器的*運(yùn)算符進(jìn)行重載。
// *運(yùn)算符重載 Ref operator*() { //迭代器中的那個(gè)指針不能是nullptr assert(_pnode); //返回節(jié)點(diǎn)中的數(shù)據(jù)域中的數(shù)據(jù) return _pnode->_data; }
2、++ 與 --運(yùn)算符
++運(yùn)算符分為兩種:一種是前置++一種是后置++,這兩個(gè)函數(shù)構(gòu)成函數(shù)重載,后置++的參數(shù)部分會(huì)多一個(gè)int類型。(--運(yùn)算符同理)
對(duì)于原生指向節(jié)點(diǎn)的指針來說:++指針是讓指針移動(dòng)到下一個(gè)緊挨著的同類型的指針位置,但是對(duì)于迭代器來說:++是讓迭代器指向下一個(gè)節(jié)點(diǎn)的位置,這兩者并不匹配,所以我們要對(duì)++運(yùn)算符進(jìn)行函數(shù)重載。
//前置++運(yùn)算符 self& operator++() { _pnode = _pnode->_next; return (*this); } //后置++運(yùn)算符 self operator++(int) { //先保存++之前的結(jié)果 self tmp(*this); _pnode = _pnode->_next; //返回++之前的值 return tmp; } //前置--運(yùn)算符 self& operator--() { _pnode = _pnode->_prev; return (*this); } //后置--運(yùn)算符 self operator--(int) { self tmp(*this); _pnode = _pnode->_prev; return tmp; }
3、->運(yùn)算符重載
雖然在前面我們已經(jīng)實(shí)現(xiàn)了迭代器*
的運(yùn)算符重載,已經(jīng)可以訪問數(shù)據(jù)域中的數(shù)據(jù)了。但是當(dāng)我們的list
里面存儲(chǔ)的是自定義類型的數(shù)據(jù),而我們想要訪問自定義類型中的成員變量時(shí)迭代器*
的運(yùn)算符就不能夠幫到我們了。
例如:
struct Date { int _year; int _month; int _day; } //it是迭代器,指向了存儲(chǔ)了Date類型的節(jié)點(diǎn) //假設(shè):在沒有->操作符時(shí),我們想要修改_year的值, (*it)._year = 2023; //如果有了-> 操作符,我們就能這樣操作,更加符合我們的使用習(xí)慣 it->_year = 2023;
于是我們來實(shí)現(xiàn):->運(yùn)算符的重載,我們先來看代碼:
// ->運(yùn)算符重載 Ptr operator->() { return &(_pnode->_data); }
看到這里你可能會(huì)覺得很奇怪,覺得這段代碼是錯(cuò)誤的,下面我們就來詳細(xì)講解這里的問題和注意事項(xiàng)。
_pnode是迭代器的成員變量,是一個(gè)節(jié)點(diǎn)的指針,它使用的->是C++的內(nèi)置類型的操作符,這段代碼(_pnode->date) 是拿到的是節(jié)點(diǎn)中存儲(chǔ)的數(shù)據(jù),這段代碼&(_pnode->date) 是拿到的是節(jié)點(diǎn)中存儲(chǔ)的數(shù)據(jù)的地址,返回之后我們好像并沒有得到自定義類型中的數(shù)據(jù),好像還差一次->操作,比如這樣:
it->->_year = 2023; //it-> 等價(jià)于 (&(_pnode->date)) //(&(_pnode->date))->year = 2023;
實(shí)際上按上面的運(yùn)算符重載函數(shù)寫法確實(shí)是少了一次->,但是C++為了代碼的簡潔性在這里進(jìn)行了特殊處理,我們寫->的運(yùn)算符重載時(shí)只需要返回list里面自定義類型的地址就行了,在外面實(shí)際應(yīng)用時(shí),編譯器在編譯時(shí)會(huì)為我們自動(dòng)加上一次->。
4、 !=運(yùn)算符重載 與 ==運(yùn)算符重載
我們?cè)谑褂玫鬟M(jìn)行遍歷數(shù)據(jù)的時(shí)候,經(jīng)常要使用關(guān)系運(yùn)算符 != ==來判斷條件是否達(dá)到,在這里我們對(duì)關(guān)系運(yùn)算符 != ==進(jìn)行函數(shù)重載。
判斷兩個(gè)迭代器是否相等的辦法就是兩個(gè)迭代器是不是指向同一個(gè)位置!
// !=運(yùn)算符重載 bool operator!=(const self& s) { return _pnode != s._pnode; } // ==運(yùn)算符重載 bool operator==(const self& s) { return _pnode == s._pnode; }
三、list的實(shí)現(xiàn)
在實(shí)現(xiàn)完迭代器之后,我們就要實(shí)現(xiàn)list的其他接口了。
1、迭代器接口
雖然在list的類外我們已經(jīng)實(shí)現(xiàn)了迭代器的各種接口,但是list類內(nèi)我們還沒有提供使用迭代器的接口的函數(shù),這個(gè)函數(shù)就是我們常用的begin()與end()函數(shù)!下面我們來一起實(shí)現(xiàn)一下。
//正向迭代器 iterator begin() { //_head指向的是哨兵位的頭節(jié)點(diǎn),_head的下一個(gè)才是第一個(gè)節(jié)點(diǎn)! //這里使用的是一個(gè)指針構(gòu)造的匿名對(duì)象做返回值,編譯器會(huì)對(duì)此進(jìn)行優(yōu)化,能夠增加效率 return iterator(_head->_next); } iterator end() { //由于是雙向循環(huán)鏈表,所以最后一個(gè)節(jié)點(diǎn)的下一個(gè)位置就是哨兵位節(jié)點(diǎn) return iterator(_head); }
//const迭代器的思路與普通迭代器類似 const_iterator begin() const { return const_iterator(_head->_next); } const_iterator end() const { return const_iterator(_head); }
2、插入函數(shù)
list
鏈表的插入很簡單,我們需要先申請(qǐng)一個(gè)新節(jié)點(diǎn)存儲(chǔ)我們想要插入的數(shù)據(jù),然后將新節(jié)點(diǎn)的_prev
指針指向前一個(gè)節(jié)點(diǎn),同時(shí)新節(jié)點(diǎn)的_next
指針指向當(dāng)前節(jié)點(diǎn)。同時(shí)再對(duì)當(dāng)前節(jié)點(diǎn)與前一個(gè)節(jié)點(diǎn)中相應(yīng)的指針進(jìn)行更新,就完成了指針的鏈接。
void insert(iterator pos, const T& x) { //先申請(qǐng)一個(gè)節(jié)點(diǎn),存儲(chǔ)我們要插入的數(shù)據(jù) node* new_node = new node(x); node* prev = pos._pnode->_prev; //鏈接過程 prev->_next = new_node; new_node->_prev = prev; new_node->_next = pos._pnode; pos._pnode->_prev = new_node; }
插入函數(shù)寫完以后,我們的頭插尾插函數(shù)也就相當(dāng)于寫完了
頭插函數(shù)
void push_front(const T& x) { //在begin()位置進(jìn)行插入就是頭插! insert(begin(), x); }
尾插函數(shù)
//尾插函數(shù) void push_back(const T& x) { //在end()位置進(jìn)行插入,就是尾插 insert(end(), x); }
3、刪除函數(shù)
鏈表的刪除沒有順序表那么復(fù)雜,但是我們應(yīng)該注意:應(yīng)該先將前后節(jié)點(diǎn)的連接關(guān)系給建立好,然后再刪除節(jié)點(diǎn)!
iterator erase(iterator pos) { assert(pos != end()); //鏈接過程 node* prev = pos._pnode->_prev; node* next = pos._pnode->_next; prev->_next = next; next->_prev = prev; //刪除節(jié)點(diǎn) delete pos._pnode; //返回指向原節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)的迭代器,外部接收后可以防止迭代器失效! return iterator(next); }
同理刪除函數(shù)寫完以后,我們的頭刪尾刪函數(shù)也就相當(dāng)于寫完了!
頭刪函數(shù)
void pop_front() { erase(begin()); }
尾刪函數(shù)
void pop_back() { //由于end()是最后一個(gè)節(jié)點(diǎn)的下一個(gè)位置,所以這里要對(duì)end()進(jìn)行一次自減運(yùn)算 erase(--end()); }
4、清除函數(shù)
清除函數(shù)的作用就是刪除除了哨兵位節(jié)點(diǎn)以外所有節(jié)點(diǎn),現(xiàn)在我們有了迭代器我們?cè)L問每個(gè)節(jié)點(diǎn)都變得非常容易,刪除相應(yīng)的節(jié)點(diǎn)也變的非常容易,我們只需要遍歷一遍鏈表逐一進(jìn)行刪除就行了。
void clear() { list<T>::iterator it = begin(); while (it != end()) { //erase函數(shù)刪除相應(yīng)節(jié)點(diǎn)以后會(huì)返回下一個(gè)節(jié)點(diǎn)的迭代器 it = erase(it); } }
5、交換函數(shù)
對(duì)于鏈表的交換我們只需要交換list
的成員變量中指向哨兵位節(jié)點(diǎn)的指針(即_head
指針)就可以完成整個(gè)鏈表的交換了!
//swap函數(shù) void swap(list<T>& lt) { std::swap(_head, lt._head); }
6、迭代器區(qū)間的構(gòu)造函數(shù)
此函數(shù)的作用就是用一個(gè)迭代器的區(qū)間來構(gòu)造一個(gè)鏈表,要實(shí)現(xiàn)這個(gè)函數(shù)我們只需要用迭代器進(jìn)行遍歷,然后將遍歷到的數(shù)據(jù)一個(gè)一個(gè)尾插就能構(gòu)成一個(gè)新的鏈表了,同時(shí)為了能夠支持更多的迭代器能夠去構(gòu)造鏈表,我們可以將該函數(shù)變成一個(gè)函數(shù)模板。
//迭代器區(qū)間構(gòu)造,傳入的迭代器應(yīng)該至少是一個(gè)二元迭代器,能支持向前和向后遍歷,這時(shí)鏈表的最低要求。 template<class Biditerator> list(Biditerator first, Biditerator last) { //調(diào)用初始化函數(shù) empty_init(); //遍歷迭代器同時(shí)將數(shù)據(jù)形成一個(gè)新節(jié)點(diǎn)插入鏈表中 while (first != last) { push_back(*first); ++first; } }
7、拷貝構(gòu)造
有了迭代器區(qū)間構(gòu)造和交換函數(shù)我們就可以寫現(xiàn)代寫法的拷貝構(gòu)造了!
現(xiàn)代寫法的拷貝構(gòu)造就是用迭代器區(qū)間構(gòu)造一個(gè)完整的鏈表,然后交換給拷貝對(duì)象。
//拷貝構(gòu)造 list(const list<T>& lt) { //初始化 empty_init(); //用迭代器區(qū)間構(gòu)造創(chuàng)建一個(gè)新的list對(duì)象 list<T> tmp(lt.begin(), lt.end()); //將this指針指向的對(duì)象與這個(gè)新的tmp對(duì)象進(jìn)行交換,拷貝就變相完成了 swap(tmp); }
8、賦值重載
有了拷貝構(gòu)造和交換函數(shù),我們還是可以采用現(xiàn)代版本的賦值重載,原理與上面的拷貝構(gòu)造同理。
//賦值運(yùn)算符重載 //注意這里的傳參方式是傳值傳參 list<T>& operator=(list<T> lt) { //將this指針指向的對(duì)象與這個(gè)lt對(duì)象進(jìn)行交換,賦值就變相完成了 swap(lt); return (*this); }
9、析構(gòu)函數(shù)
最后就是析構(gòu)函數(shù)了,由于我們已經(jīng)實(shí)現(xiàn)過了clear
函數(shù),所以我們可以先調(diào)用clear
函數(shù)刪除所有有效節(jié)點(diǎn),然后再刪除哨兵位的節(jié)點(diǎn)就行了!
~list() { clear(); delete _head; _head = nullptr; }
到此這篇關(guān)于C++list的模擬實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)C++ ist實(shí)現(xiàn)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于C++的重載運(yùn)算符和重載函數(shù)
一般來說,重載運(yùn)算符在實(shí)際的項(xiàng)目開發(fā)中會(huì)經(jīng)常的用到,但如果某些自定義類型通過簡短幾行代碼重載一些常用的運(yùn)算符(如:+-*/),就能讓編程工作帶來方便,需要的朋友可以參考下本文2023-05-05C++ 實(shí)現(xiàn)優(yōu)先隊(duì)列的簡單實(shí)例
這篇文章主要介紹了C++ 實(shí)現(xiàn)優(yōu)先隊(duì)列的簡單實(shí)例的相關(guān)資料,希望通過本文能幫助大家實(shí)現(xiàn)優(yōu)先隊(duì)列,需要的朋友可以參考下2017-08-08OpenCV圖像特征提取之Shi-Tomasi角點(diǎn)檢測算法詳解
Harris角點(diǎn)檢測算法就是對(duì)角點(diǎn)響應(yīng)函數(shù)R進(jìn)行閾值處理,Shi-Tomasi原理幾乎和Harris一樣的,只不過最后計(jì)算角點(diǎn)響應(yīng)的公式發(fā)生了變化。本文將和大家詳細(xì)說說Shi-Tomasi角點(diǎn)檢測算法的原理與實(shí)現(xiàn),需要的可以參考一下2022-09-09C++結(jié)構(gòu)體數(shù)組詳細(xì)解析
定義結(jié)構(gòu)體數(shù)組和定義結(jié)構(gòu)體變量類似,定義結(jié)構(gòu)體數(shù)組時(shí)只需聲明其為數(shù)組即可2013-10-10純c實(shí)現(xiàn)異常捕獲try-catch組件教程示例
這篇文章主要為大家介紹了純c實(shí)現(xiàn)異常捕獲try-catch組件教程示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08C++數(shù)據(jù)結(jié)構(gòu)之AVL樹的實(shí)現(xiàn)
AVL樹是高度平衡的而二叉樹,它的特點(diǎn)是AVL樹中任何節(jié)點(diǎn)的兩個(gè)子樹的高度最大差別為1,本文主要給大家介紹了C++如何實(shí)現(xiàn)AVL樹,需要的朋友可以參考下2022-06-06