欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C++深入探究list的模擬實(shí)現(xiàn)

 更新時(shí)間:2022年07月04日 08:38:14   作者:Hero?2021  
list相較于vector來說會(huì)顯得復(fù)雜,它的好處是在任意位置插入,刪除都是一個(gè)O(1)的時(shí)間復(fù)雜度,本文主要介紹了C++中List的模擬實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

示意圖:

迭代器

正向迭代器類

我們之前所理解的是:迭代器理解為像指針一樣的東西,但是在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)境的問題

    在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-07
  • C++實(shí)現(xiàn)循環(huán)隊(duì)列

    C++實(shí)現(xiàn)循環(huán)隊(duì)列

    這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)循環(huán)隊(duì)列,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-01-01
  • C語言 指針與二維數(shù)組詳解

    C語言 指針與二維數(shù)組詳解

    本文主要介紹C語言 指針與二維數(shù)組,這里整理了詳細(xì)的資料及示例代碼,有需要的小伙伴可以參考下
    2016-08-08
  • C++實(shí)現(xiàn)順序排序算法簡單示例代碼

    C++實(shí)現(xiàn)順序排序算法簡單示例代碼

    這篇文章主要介紹了C++實(shí)現(xiàn)順序排序算法簡單示例代碼,對于學(xué)過C++的朋友一定不會(huì)陌生,現(xiàn)在重溫一下這個(gè)算法,需要的朋友可以參考下
    2014-08-08
  • C++中memcpy函數(shù)的使用以及模擬實(shí)現(xiàn)

    C++中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-07
  • C語言實(shí)現(xiàn)將字符串轉(zhuǎn)換為數(shù)字的方法

    C語言實(shí)現(xiàn)將字符串轉(zhuǎn)換為數(shù)字的方法

    這篇文章主要介紹了C語言實(shí)現(xiàn)將字符串轉(zhuǎn)換為數(shù)字的方法,涉及系統(tǒng)函數(shù)atoi()函數(shù)的使用技巧,需要的朋友可以參考下
    2014-12-12
  • C++求解二叉樹的下一個(gè)結(jié)點(diǎn)問題

    C++求解二叉樹的下一個(gè)結(jié)點(diǎn)問題

    本文將通過C++求解以下問題:給定一個(gè)二叉樹其中的一個(gè)結(jié)點(diǎn),請找出中序遍歷順序的下一個(gè)結(jié)點(diǎn)并且返回。文中示例代碼講解詳細(xì),感興趣的可以了解一下
    2022-04-04
  • C語言枚舉與聯(lián)合圖文梳理講解

    C語言枚舉與聯(lián)合圖文梳理講解

    枚舉顧名思義就是把所有的可能性列舉出來,像一個(gè)星期分為七天我們就可以使用枚舉,聯(lián)合體是由關(guān)鍵字union和標(biāo)簽定義的,和枚舉是一樣的定義方式,不一樣的是,一個(gè)聯(lián)合體只有一塊內(nèi)存空間,什么意思呢,就相當(dāng)于只開辟最大的變量的內(nèi)存,其他的變量都在那個(gè)變量占據(jù)空間
    2023-01-01
  • String類的寫時(shí)拷貝實(shí)例

    String類的寫時(shí)拷貝實(shí)例

    下面小編就為大家?guī)硪黄猄tring類的寫時(shí)拷貝實(shí)例。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-01-01
  • C語言中實(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í)例代碼

    這篇文章主要介紹了C語言中實(shí)現(xiàn)“17進(jìn)制”轉(zhuǎn)“10進(jìn)制”實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下
    2017-05-05

最新評論