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è)自定義類型的指針,我們?cè)愋偷闹羔樤趘ector或者string中是可以直接typedef成為迭代器的,但是list底層是雙鏈表,數(shù)據(jù)結(jié)構(gòu)并非連續(xù)的,所以直接*it或者++it是不能夠完成迭代器的任務(wù)的,但是C++中支持對(duì)于自定義類型的運(yùn)算符重載,我們可以對(duì)解引用和自加兩個(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)屬于鏈表,不屬于迭代器,所以不用管它。
我們?cè)趥魅隿onst版本的list時(shí),list是const對(duì)象,需要的是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)勢(shì)!
迭代器我們說,它是像指針一樣的東西,如果它是指向的一個(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)對(duì)象,對(duì)象再.操作,拿到數(shù)據(jù)
it->調(diào)用operator->,返回對(duì)象的指針,(這里返回的是原生指針)再通過->調(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 返回++之后的引用對(duì)象
self& operator++()
{
_node = _node->_next;
return *this;
}
// it++ 返回++之前的對(duì)象
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;
}
// 返回對(duì)象的指針
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ì):對(duì)于正向迭代器的一種封裝
反向迭代器跟正想迭代器區(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é)束跟正向迭代器對(duì)稱
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;
}對(duì)于這兩個(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)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
在Visual Studio Code中配置C++編譯環(huán)境的問題
關(guān)于Visual Studio Code對(duì)C++環(huán)境的配置方法應(yīng)該有好多種,我這里用到了其中的兩種,具體內(nèi)容詳情文中給大家詳細(xì)介紹,對(duì)Visual Studio Code配置C++編譯環(huán)境相關(guān)知識(shí)感興趣的朋友一起看看吧2021-07-07
C++實(shí)現(xiàn)循環(huán)隊(duì)列
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)循環(huán)隊(duì)列,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-01-01
C++實(shí)現(xiàn)順序排序算法簡(jiǎn)單示例代碼
這篇文章主要介紹了C++實(shí)現(xiàn)順序排序算法簡(jiǎn)單示例代碼,對(duì)于學(xué)過C++的朋友一定不會(huì)陌生,現(xiàn)在重溫一下這個(gè)算法,需要的朋友可以參考下2014-08-08
C++中memcpy函數(shù)的使用以及模擬實(shí)現(xiàn)
memcpy是c和c++使用的內(nèi)存拷貝函數(shù),本文主要介紹了C++中memcpy函數(shù)的使用以及模擬實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07
C語(yǔ)言實(shí)現(xiàn)將字符串轉(zhuǎn)換為數(shù)字的方法
這篇文章主要介紹了C語(yǔ)言實(shí)現(xiàn)將字符串轉(zhuǎn)換為數(shù)字的方法,涉及系統(tǒng)函數(shù)atoi()函數(shù)的使用技巧,需要的朋友可以參考下2014-12-12
C++求解二叉樹的下一個(gè)結(jié)點(diǎn)問題
本文將通過C++求解以下問題:給定一個(gè)二叉樹其中的一個(gè)結(jié)點(diǎn),請(qǐng)找出中序遍歷順序的下一個(gè)結(jié)點(diǎn)并且返回。文中示例代碼講解詳細(xì),感興趣的可以了解一下2022-04-04
C語(yǔ)言中實(shí)現(xiàn)“17進(jìn)制”轉(zhuǎn)“10進(jìn)制”實(shí)例代碼
這篇文章主要介紹了C語(yǔ)言中實(shí)現(xiàn)“17進(jìn)制”轉(zhuǎn)“10進(jìn)制”實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2017-05-05

