最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C++list的模擬實現(xiàn)

 更新時間:2023年04月18日 11:08:32   作者:看到我請叫我滾去學(xué)習(xí)Orz  
list是數(shù)據(jù)結(jié)構(gòu)中的鏈表,在C++的STL中,有l(wèi)ist的模板,STL中的list的結(jié)構(gòu)是帶頭雙向循環(huán)鏈表,當(dāng)然STL中還有一個forward_list的鏈表,這個鏈表是一個帶頭的單鏈表。為了更好的理解list,我們來對其進行模擬實現(xiàn)。,需要的朋友可以參考

一、節(jié)點的結(jié)構(gòu),list的迭代器的結(jié)構(gòu),以及l(fā)ist的結(jié)構(gòu)

1、節(jié)點的結(jié)構(gòu)

對于鏈表的節(jié)點我們都很熟悉了,節(jié)點中包含兩個域,一個指針域一個數(shù)據(jù)域,為了讓list能夠通用,我們選擇使用模板。
節(jié)點的結(jié)構(gòu)如下:

template<class T>
//struct也能定義類,默認類的訪問限定符是 public
struct list_node
{
	//這個指針指向前一個節(jié)點
	list_node<T>* _prev;
	//這個指針指向后一個節(jié)點
	list_node<T>* _next;
	//這個是數(shù)據(jù)域中的元素
	T _data;
	
	//對節(jié)點使用匿名對象進行初始化
	list_node(const T& data = T())
		:_prev(nullptr)
		,_next(nullptr)
		,_data(data)
	{}
};

2、迭代器的結(jié)構(gòu)

現(xiàn)在我們已經(jīng)有了節(jié)點了,我們還要有迭代器,如果沒有迭代器我們就不能很好的訪問每一個節(jié)點。
對于迭代器我們要讓它指向我們想要的節(jié)點,這才能便于我們的訪問,于是很明顯我們迭代器的成員變量就要是一個節(jié)點的指針!同時為了讓list能夠通用,我們選擇使用模板來定義迭代器。
迭代器的結(jié)構(gòu)如下:

//這里后面的兩個參數(shù),在實際應(yīng)用時通常是T& , T* 或者是 const T& , const T*
//根據(jù)加與不加const 可以分別實例化出:普通正向迭代器與正向const迭代器
template<class T, class Ref, class Ptr>
struct __list_iterator
{
	//將節(jié)點的類型進行typedef方便使用
	typedef list_node<T> node;
	
	//將類自己進行typedef方便使用
	typedef __list_iterator<T,Ref,Ptr> self;

	//成員變量 是一個指向節(jié)點的指針
	node* _pnode;

	//構(gòu)造函數(shù) 用一個節(jié)點的地址對迭代器進行初始化,
	 __list_iterator(node* pnode)
		:_pnode(pnode)
	{}
};

3、list的結(jié)構(gòu)

由于list是帶頭雙向循環(huán)鏈表,我們只需要一個指向頭節(jié)點的指針便能夠管理所有的節(jié)點了。

template<class T>
class list
{
public:
	//將節(jié)點的類型進行typedef方便使用
	typedef list_node<T> node;
	//將迭代器進行typedef方便使用
	typedef __list_iterator<T, T&, T*> iterator;
	//將const迭代器進行typedef方便使用
	typedef __list_iterator<T, const T&, const T*> const_iterator;
	//默認構(gòu)造函數(shù)
	list()
	{
		empty_init();
	}
	//初始化函數(shù)
	void empty_init()
	{
		//申請一個頭節(jié)點,將節(jié)點的地址給_head
		_head = new node;
		//讓哨兵位節(jié)點的 前指針指向自己
		_head->_prev = _head;
		//讓哨兵位節(jié)點的 后指針指向自己
		_head->_next = _head;
	}
private:
	//指向哨兵位節(jié)點的指針
	node* _head;
};

到此為止我們一共建立了三個類,下面我們模擬實現(xiàn)鏈表的各種接口時,我們還要繼續(xù)豐富迭代器類的接口與list類的接口

二、迭代器的實現(xiàn)

由于鏈表的許多操作都要用到迭代器,但是迭代器的一些其他接口我們還沒有實現(xiàn),在這里我們來實現(xiàn)迭代器的所有接口。

1、*運算符重載

對于原生指向節(jié)點的指針來說*運算符能讓我們拿到節(jié)點,但還無法拿到節(jié)點數(shù)據(jù)域中的數(shù)據(jù),但是對于迭代器來說*運算符就要拿到容器中存儲的數(shù)據(jù),所以我們還要對迭代器的*運算符進行重載。

// *運算符重載
Ref operator*()
{
	//迭代器中的那個指針不能是nullptr
	assert(_pnode);
	//返回節(jié)點中的數(shù)據(jù)域中的數(shù)據(jù)
	return _pnode->_data;
}

2、++ 與 --運算符

++運算符分為兩種:一種是前置++一種是后置++,這兩個函數(shù)構(gòu)成函數(shù)重載,后置++的參數(shù)部分會多一個int類型。(--運算符同理)

對于原生指向節(jié)點的指針來說:++指針是讓指針移動到下一個緊挨著的同類型的指針位置,但是對于迭代器來說:++是讓迭代器指向下一個節(jié)點的位置,這兩者并不匹配,所以我們要對++運算符進行函數(shù)重載。

//前置++運算符
self& operator++()
{
	_pnode = _pnode->_next;
	return (*this);
}

//后置++運算符
self operator++(int)
{
	//先保存++之前的結(jié)果
	self tmp(*this);
	_pnode = _pnode->_next;
	//返回++之前的值
	return tmp;
}

//前置--運算符
self& operator--()
{
	_pnode = _pnode->_prev;
	return (*this);
}

//后置--運算符
self operator--(int)
{
	self tmp(*this);
	_pnode = _pnode->_prev;
	return tmp;
}

3、->運算符重載

雖然在前面我們已經(jīng)實現(xiàn)了迭代器*的運算符重載,已經(jīng)可以訪問數(shù)據(jù)域中的數(shù)據(jù)了。但是當(dāng)我們的list里面存儲的是自定義類型的數(shù)據(jù),而我們想要訪問自定義類型中的成員變量時迭代器*的運算符就不能夠幫到我們了。
例如:

struct Date
{
	int _year;
	int _month;
	int _day;
}
//it是迭代器,指向了存儲了Date類型的節(jié)點
//假設(shè):在沒有->操作符時,我們想要修改_year的值,
(*it)._year = 2023;
//如果有了-> 操作符,我們就能這樣操作,更加符合我們的使用習(xí)慣
it->_year = 2023;

于是我們來實現(xiàn):->運算符的重載,我們先來看代碼:

// ->運算符重載
Ptr operator->()
{
	return &(_pnode->_data);
}

看到這里你可能會覺得很奇怪,覺得這段代碼是錯誤的,下面我們就來詳細講解這里的問題和注意事項。

_pnode是迭代器的成員變量,是一個節(jié)點的指針,它使用的->是C++的內(nèi)置類型的操作符,這段代碼(_pnode->date) 是拿到的是節(jié)點中存儲的數(shù)據(jù),這段代碼&(_pnode->date) 是拿到的是節(jié)點中存儲的數(shù)據(jù)的地址,返回之后我們好像并沒有得到自定義類型中的數(shù)據(jù),好像還差一次->操作,比如這樣:

it->->_year = 2023; 
//it-> 等價于 (&(_pnode->date)) 

//(&(_pnode->date))->year = 2023;

實際上按上面的運算符重載函數(shù)寫法確實是少了一次->,但是C++為了代碼的簡潔性在這里進行了特殊處理,我們寫->的運算符重載時只需要返回list里面自定義類型的地址就行了,在外面實際應(yīng)用時,編譯器在編譯時會為我們自動加上一次->。

4、 !=運算符重載 與 ==運算符重載

我們在使用迭代器進行遍歷數(shù)據(jù)的時候,經(jīng)常要使用關(guān)系運算符 != ==來判斷條件是否達到,在這里我們對關(guān)系運算符 != ==進行函數(shù)重載。
判斷兩個迭代器是否相等的辦法就是兩個迭代器是不是指向同一個位置!

// !=運算符重載
bool operator!=(const self& s)
{
	return _pnode != s._pnode;
}

// ==運算符重載
bool operator==(const self& s)
{
	return _pnode == s._pnode;
}

三、list的實現(xiàn)

在實現(xiàn)完迭代器之后,我們就要實現(xiàn)list的其他接口了。

1、迭代器接口

雖然在list的類外我們已經(jīng)實現(xiàn)了迭代器的各種接口,但是list類內(nèi)我們還沒有提供使用迭代器的接口的函數(shù),這個函數(shù)就是我們常用的begin()與end()函數(shù)!下面我們來一起實現(xiàn)一下。

//正向迭代器
iterator begin()
{
	//_head指向的是哨兵位的頭節(jié)點,_head的下一個才是第一個節(jié)點!
	//這里使用的是一個指針構(gòu)造的匿名對象做返回值,編譯器會對此進行優(yōu)化,能夠增加效率
	return iterator(_head->_next);
}

iterator end()
{
	//由于是雙向循環(huán)鏈表,所以最后一個節(jié)點的下一個位置就是哨兵位節(jié)點
	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é)點存儲我們想要插入的數(shù)據(jù),然后將新節(jié)點的_prev指針指向前一個節(jié)點,同時新節(jié)點的_next指針指向當(dāng)前節(jié)點。同時再對當(dāng)前節(jié)點與前一個節(jié)點中相應(yīng)的指針進行更新,就完成了指針的鏈接。

void insert(iterator pos, const T& x)
{
	//先申請一個節(jié)點,存儲我們要插入的數(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()位置進行插入就是頭插!
	insert(begin(), x);
}

尾插函數(shù)

//尾插函數(shù)
void push_back(const T& x)
{
	//在end()位置進行插入,就是尾插
	insert(end(), x);
}

3、刪除函數(shù)

鏈表的刪除沒有順序表那么復(fù)雜,但是我們應(yīng)該注意:應(yīng)該先將前后節(jié)點的連接關(guān)系給建立好,然后再刪除節(jié)點!

iterator erase(iterator pos)
{
	assert(pos != end());
	//鏈接過程
	node* prev = pos._pnode->_prev;
	node* next = pos._pnode->_next;
	prev->_next = next;
	next->_prev = prev;
	
	//刪除節(jié)點
	delete pos._pnode;
	//返回指向原節(jié)點的下一個節(jié)點的迭代器,外部接收后可以防止迭代器失效!
	return iterator(next);
}

同理刪除函數(shù)寫完以后,我們的頭刪尾刪函數(shù)也就相當(dāng)于寫完了!

頭刪函數(shù)

void pop_front()
{
	erase(begin());
}

尾刪函數(shù)

void pop_back()
{
	//由于end()是最后一個節(jié)點的下一個位置,所以這里要對end()進行一次自減運算
	erase(--end());
}

4、清除函數(shù)

清除函數(shù)的作用就是刪除除了哨兵位節(jié)點以外所有節(jié)點,現(xiàn)在我們有了迭代器我們訪問每個節(jié)點都變得非常容易,刪除相應(yīng)的節(jié)點也變的非常容易,我們只需要遍歷一遍鏈表逐一進行刪除就行了。

void clear()
{
	list<T>::iterator it = begin();
	while (it != end())
	{
		//erase函數(shù)刪除相應(yīng)節(jié)點以后會返回下一個節(jié)點的迭代器
		it = erase(it);
	}
}

5、交換函數(shù)

對于鏈表的交換我們只需要交換list的成員變量中指向哨兵位節(jié)點的指針(即_head指針)就可以完成整個鏈表的交換了!

//swap函數(shù)
void swap(list<T>& lt)
{
	std::swap(_head, lt._head);
}

6、迭代器區(qū)間的構(gòu)造函數(shù)

此函數(shù)的作用就是用一個迭代器的區(qū)間來構(gòu)造一個鏈表,要實現(xiàn)這個函數(shù)我們只需要用迭代器進行遍歷,然后將遍歷到的數(shù)據(jù)一個一個尾插就能構(gòu)成一個新的鏈表了,同時為了能夠支持更多的迭代器能夠去構(gòu)造鏈表,我們可以將該函數(shù)變成一個函數(shù)模板。

//迭代器區(qū)間構(gòu)造,傳入的迭代器應(yīng)該至少是一個二元迭代器,能支持向前和向后遍歷,這時鏈表的最低要求。
template<class Biditerator>
list(Biditerator first, Biditerator last)
{
	//調(diào)用初始化函數(shù)
	empty_init();
	//遍歷迭代器同時將數(shù)據(jù)形成一個新節(jié)點插入鏈表中
	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òu)造
list(const list<T>& lt)
{
	//初始化
	empty_init();
	//用迭代器區(qū)間構(gòu)造創(chuàng)建一個新的list對象
	list<T> tmp(lt.begin(), lt.end());
	//將this指針指向的對象與這個新的tmp對象進行交換,拷貝就變相完成了
	swap(tmp);
}

8、賦值重載

有了拷貝構(gòu)造和交換函數(shù),我們還是可以采用現(xiàn)代版本的賦值重載,原理與上面的拷貝構(gòu)造同理。

//賦值運算符重載
//注意這里的傳參方式是傳值傳參
list<T>& operator=(list<T> lt)
{
	//將this指針指向的對象與這個lt對象進行交換,賦值就變相完成了
	swap(lt);
	return (*this);
}

9、析構(gòu)函數(shù)

最后就是析構(gòu)函數(shù)了,由于我們已經(jīng)實現(xiàn)過了clear函數(shù),所以我們可以先調(diào)用clear函數(shù)刪除所有有效節(jié)點,然后再刪除哨兵位的節(jié)點就行了!

~list()
{
	clear();
	delete _head;
	_head = nullptr;
}

到此這篇關(guān)于C++list的模擬實現(xiàn)的文章就介紹到這了,更多相關(guān)C++ ist實現(xiàn)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • C語言 動態(tài)分配數(shù)組案例詳解

    C語言 動態(tài)分配數(shù)組案例詳解

    這篇文章主要介紹了C語言 動態(tài)分配數(shù)組案例詳解,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下
    2021-08-08
  • IOS 開發(fā)UITextView回收或關(guān)閉鍵盤

    IOS 開發(fā)UITextView回收或關(guān)閉鍵盤

    這篇文章主要介紹了IOS 開發(fā)UITextView回收或關(guān)閉鍵盤的相關(guān)資料,需要的朋友可以參考下
    2017-06-06
  • 解析C++中構(gòu)造函數(shù)的默認參數(shù)和構(gòu)造函數(shù)的重載

    解析C++中構(gòu)造函數(shù)的默認參數(shù)和構(gòu)造函數(shù)的重載

    這篇文章主要介紹了解析C++中構(gòu)造函數(shù)的默認參數(shù)和構(gòu)造函數(shù)的重載,是C++入門學(xué)習(xí)中的基礎(chǔ)知識,需要的朋友可以參考下
    2015-09-09
  • C++淺析析構(gòu)函數(shù)的特征

    C++淺析析構(gòu)函數(shù)的特征

    既然在創(chuàng)建對象時有構(gòu)造函數(shù)(給成員初始化),那么在銷毀對象時應(yīng)該還有一個清除成員變量數(shù)據(jù)的操作咯,析構(gòu)函數(shù)與構(gòu)造函數(shù)功能相反,析構(gòu)函數(shù)不是完成對象的銷毀,局部對象銷毀工作是由編譯器完成的。而對象在銷毀時會自動調(diào)用析構(gòu)函數(shù),完成類的一些資源清理工作
    2022-07-07
  • C語言中的常量詳解

    C語言中的常量詳解

    本文主要講解C語言 常量,這里整理了 C語言常量的基礎(chǔ)知識,并附代碼示例和示例詳細講解,希望能幫助開始學(xué)習(xí)C 語言的同學(xué)
    2021-09-09
  • C++ 簡單實現(xiàn)MFC ListControl 點擊列頭排序

    C++ 簡單實現(xiàn)MFC ListControl 點擊列頭排序

    這篇文章主要介紹了C++ 簡單實現(xiàn)MFC ListControl 點擊列頭排序的相關(guān)資料,需要的朋友可以參考下
    2015-06-06
  • Qt實現(xiàn)高準(zhǔn)確率的語音識別

    Qt實現(xiàn)高準(zhǔn)確率的語音識別

    Vosk是一個開源的語音識別工具,支持中英文及多種語言,具備離線識別能力,且不依賴互聯(lián)網(wǎng),本文就來聊聊如何使用Vosk API在C++中進行中英文識別吧
    2024-11-11
  • 用C實現(xiàn)PHP擴展 Fetch_Url 類數(shù)據(jù)抓取的方法

    用C實現(xiàn)PHP擴展 Fetch_Url 類數(shù)據(jù)抓取的方法

    該擴展是基于libcurl基礎(chǔ)實現(xiàn)的網(wǎng)頁數(shù)據(jù)抓取
    2013-04-04
  • 一文搞懂C++ 動態(tài)內(nèi)存

    一文搞懂C++ 動態(tài)內(nèi)存

    這篇文章主要介紹了C++ 動態(tài)內(nèi)存的的相關(guān)資料,文中示例代碼非常詳細,幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下
    2020-06-06
  • 詳解C語言之操作符

    詳解C語言之操作符

    這篇文章主要以圖文結(jié)合的方式為大家詳細介紹了C語言的操作符知識,感興趣的小伙伴們可以參考一下,希望能給你帶來幫助
    2021-11-11

最新評論

嘉禾县| 淮阳县| 昌乐县| 宽城| 缙云县| 瑞安市| 彝良县| 五华县| 和田市| 岳阳市| 九台市| 尖扎县| 长阳| 科尔| 应城市| 泗阳县| 庄浪县| 梁山县| 江山市| 弥勒县| 靖安县| 汉阴县| 峡江县| 宜川县| 铁岭县| 北流市| 保靖县| 阿克陶县| 桃园县| 古丈县| 岳普湖县| 沙坪坝区| 容城县| 七台河市| 青神县| 明溪县| 工布江达县| 广河县| 昆山市| 卢氏县| 贡山|