C++深入探究list的模擬實現(xiàn)
示意圖:

迭代器
正向迭代器類
我們之前所理解的是:迭代器理解為像指針一樣的東西,但是在list中有些不同
// 迭代器邏輯
while(it!=l.end())
{
*it; // 解引用取數(shù)據(jù)
++it;// 自加到達下一個位置
}
我們可以來觀察一下STL源碼中大佬是怎么封裝的:

我們可以看到,只有一個成員,那就是一個結(jié)點的指針node,link_type又是一個自定義類型的指針,我們原生類型的指針在vector或者string中是可以直接typedef成為迭代器的,但是list底層是雙鏈表,數(shù)據(jù)結(jié)構(gòu)并非連續(xù)的,所以直接*it或者++it是不能夠完成迭代器的任務的,但是C++中支持對于自定義類型的運算符重載,我們可以對解引用和自加兩個運算符重載。
++it:就是當前指針存放下一個結(jié)點的地址
*it:解引用當前節(jié)點,取出值來
迭代器中,拷貝構(gòu)造、運算符賦值重載、析構(gòu)都不需要自己實現(xiàn),使用默認生成的即可(即淺拷貝),因為迭代器是用自定義類型的指針封裝的,訪問修改鏈表,節(jié)點屬于鏈表,不屬于迭代器,所以不用管它。
我們在傳入const版本的list時,list是const對象,需要的是const_iterator,這里會出現(xiàn)錯誤,不能將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)绾螌崿F(xiàn)一個const的迭代器呢?
意思就是只可以讀不能夠?qū)憽?梢?code>++,- -,*解引用,但是解引用時不能修改數(shù)據(jù)。
可以想到這種寫法:
const T& operator*()const
{
return _node->_data;
}
T& operator*()
{
return _node->_data;
}但是并不是迭代器是const的,而是我們傳入的list容器是const版本的。
我們可以將寫一個const_iterator 的類版本,這樣普通迭代器和const迭代器就不是一個類型了,是兩個不同的類型了,這樣會造成代碼冗余。
template<class T>
struct __const_list_iterator
{
//...
// __list_iterator全部替換為__const_list_iterator
};
優(yōu)化:
增加一個模板參數(shù)class Ref

這樣第一個模板參數(shù)都是T,我們可以根據(jù)傳入的第二個參數(shù)來推出時T&還是const T&,本來我們是要實現(xiàn)兩個類的,現(xiàn)在只需要增加一個模板參數(shù)即可,這里體現(xiàn)出了C++泛型的優(yōu)勢!
迭代器我們說,它是像指針一樣的東西,如果它是指向的一個結(jié)構(gòu)體,需要用它的成員變量,我們還需要重載->箭頭
struct Date {
int _year;
int _month;
int _day;
Date(int year = 0, int month = 0, int day = 0)
//這里要給默認參數(shù),因為需要構(gòu)建一個哨兵位頭結(jié)點
:_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);
}

進一步解釋:
*it調(diào)用operator*,返回一個結(jié)點對象,對象再.操作,拿到數(shù)據(jù)
it->調(diào)用operator->,返回對象的指針,(這里返回的是原生指針)再通過->調(diào)用用結(jié)構(gòu)體成員,這里實際上應該是it->->_year,但是這樣寫,可讀性很差,所以編譯器做了一個優(yōu)化,省略了一個->,所以所有的類型只要想要重載->,都會這樣優(yōu)化省略一個->
這里又會衍生出一個問題,那就是如果使用const_iterator,使用->也會修改數(shù)據(jù),所以再增加一個模板參數(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)// 迭代器的實質(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;
}
};反向迭代器類
實質(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)
{}
// 比較巧妙,解引用取的是當前位置的前一個位置的數(shù)據(jù)
// operator*取前一個位置, 主要就是為了讓反向迭代器開始和結(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;*/
// 復用insert函數(shù)
insert(end(), x);
}
push_front頭插函數(shù)
// 頭插
void push_front(const T& x)
{
// 復用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é)點位置的迭代器
}注意:這里list的insert函數(shù),pos位置的迭代器不會失效,因為pos指向的位置不會改變,vector中迭代器失效的原因是因為挪動數(shù)據(jù),導致指向的位置的數(shù)據(jù)發(fā)生變化。
erase刪除函數(shù)

iterator erase(iterator pos)
{
assert(pos != end());//不能將哨兵位的頭結(jié)點給刪除了
Node* prev = pos._node->_prev;
Node* next = pos._node->_next;
delete pos._node;
prev->_next = next;
next->_prev = prev;
return iterator(next);// 返回pos位置的下一個位置的迭代器
}注意:這里的pos位置的迭代器一定會失效,因為都已經(jīng)將結(jié)點給刪除了。
pop_front函數(shù)
// 復用erase函數(shù)
void pop_front()
{
erase(begin());
}pop_back函數(shù)
// 復用erase函數(shù)
void pop_back()
{
erase(--end());
}
構(gòu)造函數(shù)
list()
{
_head = new Node();
_head->_next = _head;
_head->_prev = _head;
}
// 函數(shù)模板,用迭代器區(qū)間進行初始化
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òu)造函數(shù)一起使用可能會存在問題,填充版本和構(gòu)造器版本可能會存在沖突,如下例子:
struct Date {
int _year;
int _month;
int _day;
Date(int year = 0, int month = 0, int day = 0)//這里要給默認參數(shù),因為有一個哨兵位頭結(jié)點需要初始化
:_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;
}對于這兩個:在實例化時會調(diào)用更加匹配的構(gòu)造函數(shù)初始化
list lt1(5, Date(2022, 9, 9))它會正常調(diào)用list(size_t n, const T& val = T())
list lt2(5, 1)而它會將5和1推演成兩個int,進而去匹配這個迭代器版本的構(gòu)造函數(shù)
template < class InputIterator> list(InputIterator first, InputIterator last),但是與我們的本意,用n個val初始化原意相背,而其中有個*first,這里int去解引用必會報錯
改進:再多提供第一個參數(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é)點給刪除了
delete _head;
_head = nullptr;
}
list拷貝構(gòu)造函數(shù)
淺拷貝會崩潰的原因是,同一塊空間被析構(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);
}
}注意: 因為要調(diào)用push_back函數(shù),push_back的前提是這個鏈表(lt2)已經(jīng)被初始化了,所以必須要先搞一個哨兵位頭結(jié)點,不然會崩潰
現(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需要一個哨兵位頭結(jié)點
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é)點
_head->_next = _head;
_head->_prev = _head;
*/
iterator it = begin();
while (it != end())
{
erase(it++); // 也可以復用erase,it++返回加加之前的值
}
}到此這篇關于C++深入探究list的模擬實現(xiàn)的文章就介紹到這了,更多相關C++ list模擬實現(xiàn)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
在Visual Studio Code中配置C++編譯環(huán)境的問題
關于Visual Studio Code對C++環(huán)境的配置方法應該有好多種,我這里用到了其中的兩種,具體內(nèi)容詳情文中給大家詳細介紹,對Visual Studio Code配置C++編譯環(huán)境相關知識感興趣的朋友一起看看吧2021-07-07
C++中memcpy函數(shù)的使用以及模擬實現(xiàn)
memcpy是c和c++使用的內(nèi)存拷貝函數(shù),本文主要介紹了C++中memcpy函數(shù)的使用以及模擬實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-07-07
C語言實現(xiàn)將字符串轉(zhuǎn)換為數(shù)字的方法
這篇文章主要介紹了C語言實現(xiàn)將字符串轉(zhuǎn)換為數(shù)字的方法,涉及系統(tǒng)函數(shù)atoi()函數(shù)的使用技巧,需要的朋友可以參考下2014-12-12
C語言中實現(xiàn)“17進制”轉(zhuǎn)“10進制”實例代碼
這篇文章主要介紹了C語言中實現(xiàn)“17進制”轉(zhuǎn)“10進制”實例代碼的相關資料,需要的朋友可以參考下2017-05-05

