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能夠通用,我們選擇使用模板來(lái)定義迭代器。
迭代器的結(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),在這里我們來(lái)實(shí)現(xiàn)迭代器的所有接口。
1、*運(yùn)算符重載
對(duì)于原生指向節(jié)點(diǎn)的指針來(lái)說*運(yùn)算符能讓我們拿到節(jié)點(diǎn),但還無(wú)法拿到節(jié)點(diǎn)數(shù)據(jù)域中的數(shù)據(jù),但是對(duì)于迭代器來(lái)說*運(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)的指針來(lái)說:++指針是讓指針移動(dòng)到下一個(gè)緊挨著的同類型的指針位置,但是對(duì)于迭代器來(lái)說:++是讓迭代器指向下一個(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;
于是我們來(lái)實(shí)現(xiàn):->運(yùn)算符的重載,我們先來(lái)看代碼:
// ->運(yùn)算符重載
Ptr operator->()
{
return &(_pnode->_data);
}
看到這里你可能會(huì)覺得很奇怪,覺得這段代碼是錯(cuò)誤的,下面我們就來(lái)詳細(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++為了代碼的簡(jiǎn)潔性在這里進(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)算符 != ==來(lái)判斷條件是否達(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ù)!下面我們來(lái)一起實(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鏈表的插入很簡(jiǎn)單,我們需要先申請(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ū)間來(lái)構(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ù)
一般來(lái)說,重載運(yùn)算符在實(shí)際的項(xiàng)目開發(fā)中會(huì)經(jīng)常的用到,但如果某些自定義類型通過簡(jiǎn)短幾行代碼重載一些常用的運(yùn)算符(如:+-*/),就能讓編程工作帶來(lái)方便,需要的朋友可以參考下本文2023-05-05
C++ 實(shí)現(xiàn)優(yōu)先隊(duì)列的簡(jiǎn)單實(shí)例
這篇文章主要介紹了C++ 實(shí)現(xiàn)優(yōu)先隊(duì)列的簡(jiǎn)單實(shí)例的相關(guān)資料,希望通過本文能幫助大家實(shí)現(xiàn)優(yōu)先隊(duì)列,需要的朋友可以參考下2017-08-08
OpenCV圖像特征提取之Shi-Tomasi角點(diǎn)檢測(cè)算法詳解
Harris角點(diǎn)檢測(cè)算法就是對(duì)角點(diǎn)響應(yīng)函數(shù)R進(jìn)行閾值處理,Shi-Tomasi原理幾乎和Harris一樣的,只不過最后計(jì)算角點(diǎn)響應(yīng)的公式發(fā)生了變化。本文將和大家詳細(xì)說說Shi-Tomasi角點(diǎn)檢測(cè)算法的原理與實(shí)現(xiàn),需要的可以參考一下2022-09-09
C++結(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-08
C++數(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
C語(yǔ)言課程設(shè)計(jì)之停車場(chǎng)管理問題
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言課程設(shè)計(jì)之停車場(chǎng)管理問題,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03

