C++實現(xiàn)list增刪查改模擬的示例代碼
前言
本篇博客采用SGI版本,同時考慮到看到此篇博客大部分為初學(xué)者,為此博主僅僅給出有用片段。C++:list增刪查改模擬實現(xiàn)就是用C++復(fù)寫雙鏈表,非常簡單。難點主要在迭代器實現(xiàn)
一、list底層雙鏈表驗證、節(jié)點構(gòu)造
1.1 list底層數(shù)據(jù)結(jié)構(gòu)
list底層使用什么數(shù)據(jù)結(jié)構(gòu)來實現(xiàn)的呢?我們先來看看SGI庫中l(wèi)ist的成員函數(shù)和初始化吧。

我們發(fā)現(xiàn)list實現(xiàn)中,只有node一個成員變量。構(gòu)造函數(shù)構(gòu)造出一個頭尾相連的哨兵位。同時驗證了list底層是一個帶哨兵位的雙鏈表。
1. 2 節(jié)點構(gòu)造
節(jié)點和雙鏈表一樣有三個成員:節(jié)點數(shù)據(jù)、指向前一個節(jié)點(prev)、指向后一個節(jié)點(next)。
//節(jié)點
template<class T>
struct List_node
{
T _data;
List_node<T>* _prev;
List_node<T>* _next;
List_node(const T& x = T())
:_data(x)
,_prev(nullptr)
,_next(nullptr)
{}
};
小tips:
- 我們這里類名和庫中一樣(List_node),后續(xù)在其他類中使用時在typedef。
- 這里類名使用struct而不是class原因在于struct默認訪問權(quán)限為公有,后續(xù)其他類只都需要大量使用。如果使用class需要使用大量友元類,過于繁瑣。
二、迭代器封裝實現(xiàn)(重點、難點)
2.1 前置說明
- 我們知道迭代器的最大用途便是遍歷數(shù)據(jù)。但何時停在,迭代器指向空間存儲數(shù)據(jù)時是什么…導(dǎo)致我們需要使用!=、++、*等操作。但自定義類型是無法支持使用這些操作符的。為此給出的解決辦法是:封裝加運算符重載。
- 迭代器使用上分為普通迭代器和const迭代器(還分為單向迭代器、雙向迭代器和隨機訪問迭代器)。其中一種最簡單的實現(xiàn)方式就是實現(xiàn)兩個類。但。。。我們知道兩個迭代器的不同之處在于const迭代器無法修改數(shù)據(jù),只是j相關(guān)借口(這里有*、->)不同而已便實現(xiàn)兩個類未免過于“小題大做”。
所以接下來我們來看看庫中是如何巧妙的解決這個問題吧!
2.2 迭代器實現(xiàn)
前置說明中以及解決了如何實現(xiàn)一個類達到目的。接下來的實現(xiàn)過于簡單就不單獨說明了。
//迭代器封裝
template<class T, class Ref, class Ptr>
struct __list_iterator
{
typedef List_node<T> Node;//節(jié)點類名重命名
//由于迭代器實現(xiàn)中,如++、--等重載函數(shù)返回值類型為__list_iterator,名字過長,這里我們重命名self意味自身
typedef __list_iterator<T,Ref, Ptr> self;
Node* _node;//成員變量
__list_iterator(Node* node)//構(gòu)造出一個節(jié)點
:_node(node)
{}
//前置++
self& operator++()
{
_node = _node->_next;
return *this;
}
//前置--
self& operator--()
{
_node = _node->_prev;
return *this;
}
//后置++
self operator++(int)
{
self tmp(*this);
_node = _node->_next;
return tmp;
}
//后置--
self operator--(int)
{
self tmp(*this);
_node = _node->_prev;
return tmp;
}
//解引用操作符重載
Ref operator*()
{
return _node->_data;
}
//用于訪問迭代器指向?qū)ο笾写鎯Φ氖亲远x類型
Ptr operator->()
{
return &_node->_data;
}
bool operator!=(const self& s)
{
return _node != s._node;
}
bool operator==(const self& s)
{
return _node == s._node;
}
};
三、list實現(xiàn)
3.1 基本框架
list模擬中,我們和庫中一樣,給出一個頭節(jié)點_head、_size兩個成員變量。同時我們將節(jié)點、迭代器進行重命名。迭代器重命名不是單純圖方便,更重要的是提供統(tǒng)一接口(例如sting、vector、set等接口都一樣),屏蔽底層的變量和成員函數(shù)屬性,實現(xiàn)過程和差異。
//list模擬實現(xiàn)
template<class T>
class List
{
typedef List_node<T> Node;
public:
//迭代器重命名,提供統(tǒng)一的接口,屏蔽底層實現(xiàn)細節(jié)和差異
typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;
private:
Node* _head;//頭節(jié)點
int _size;
};
3.2 迭代器和const迭代器
下面是begin()、end()指向一個有效雙線表的位置。

實現(xiàn):
const_iterator begin()const
{
//return const_iterator(_head->_next);或
return _head->_next;//單參數(shù)類型支持隱式類型轉(zhuǎn)換
}
const_iterator end()const
{
return _head;
}
iterator begin()
{
return _head->_next;
}
iterator end()
{
return _head;
}
3.2 構(gòu)造函數(shù)、析構(gòu)函數(shù)、拷貝構(gòu)造、賦值重載
3.2.1 構(gòu)造函數(shù)
構(gòu)造函數(shù)的實現(xiàn)和開頭中看到的一樣:構(gòu)造函數(shù)中調(diào)用一個函數(shù)(empty_Init)來是實現(xiàn)。empty_Init()用來完成初始化。(empty_Init()函數(shù)后續(xù)其他接口也要調(diào)用)
//初始化
void empty_Init()
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
_size = 0;
}
//無參構(gòu)造
List()
{
empty_Init();
}
3.2.2 析構(gòu)函數(shù)
析構(gòu)函數(shù)我們調(diào)用一個clear函數(shù)來將數(shù)據(jù)全部清空,在將_head變量釋放。
//析構(gòu)函數(shù)
~List()
{
clear();
delete _head;
_head = nullptr;
}
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it);
}
}
3.2.3 拷貝構(gòu)造
拷貝構(gòu)造時首先要初始化出一個節(jié)點,然后將需要拷貝的數(shù)據(jù)依次尾插即可(尾插接口后續(xù)給出實現(xiàn))
//拷貝構(gòu)造
List(const List<T>& It)
{
empty_Init();
for (auto e : It)
{
push_back(e);
}
}
3.2.4 賦值重載
賦值重載有很多方式。比較簡單的參數(shù)我們直接傳值,然后將待賦值的變量和傳值傳參省生成的臨時變量的數(shù)據(jù)進行交換即可。
void swap(const List<T>& It)
{
std::swap(_head, It._head);
}
//賦值重載
List<T>& operator=(const List<T> It)
{
swap(It);
return *this;
}
3.3 任意位置插入、任意位置刪除、尾插、尾刪、頭插、頭刪
3.3.1任意位置插入
首先new出待插入節(jié)點newnode,然后將pos前一個節(jié)點prev、newnode、pos相連。最后++_size即可。

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;
_size++;
return newnode;
}
3.3.2任意位置插入刪除
將待刪除數(shù)據(jù)的前后節(jié)點先保存起來,然后將刪除pos處節(jié)點,再將前后節(jié)點連接起來。最后–_size即可。
iterator erase(iterator pos)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* next = cur->_next;
delete cur;
prev->_next = next;
next->_prev = prev;
_size--;
return next;
}
3.3.3 尾插、尾刪、頭插、頭刪
尾插、尾刪、頭插、頭刪都是復(fù)用任意位置插入、任意位置刪除接口。
void push_back(const T& x)
{
insert(end(), x);
}
void push_front(const T& x)
{
insert(begin(), x);
}
void pop_back()
{
erase(--end());
}
void pop_front()
{
erase(begin());
}
四、list功能完善
4.1 迭代器operator->()
我們先來看看下面這段代碼對嗎?
struct AA
{
AA(int a1 = 0, int a2 = 0)
:_a1(a1)
,_a2(a2)
{}
int _a1;
int _a2;
};
void test_list3()
{
list<AA> lt;
lt.push_back(AA(1, 1));
lt.push_back(AA(2, 2));
lt.push_back(AA(3, 3));
list<AA>::iterator it = lt.begin();
while (it != lt.end())
{
cout<<*it<<" ";
++it;
}
cout << endl;
}
由于list沒有重載<<,所以對存儲的是自定義類型之間訪問會編譯報錯。
那我們重載下<<運算符不就行了嗎?很不幸的是C++庫在list中不支持<<,很大原因也在于編譯器不知到我們?nèi)绾稳?shù)據(jù)
所以對于自定義類型我們可以先解引用在去訪問成員,也可以在迭代器中重載operator->()函數(shù)。但需要注意的是編譯器隱藏了一個->,具體原生行為如下:
struct AA
{
AA(int a1 = 0, int a2 = 0)
:_a1(a1)
,_a2(a2)
{}
int _a1;
int _a2;
};
void test_list3()
{
list<AA> lt;
lt.push_back(AA(1, 1));
lt.push_back(AA(2, 2));
lt.push_back(AA(3, 3));
list<AA>::iterator it = lt.begin();
while (it != lt.end())
{
//cout << (*it)._a1<<" "<<(*it)._a2 << endl;
cout << it->_a1 << " " << it->_a2 << endl;
//上面編譯器訪問成員變量的真正行為如下:
//cout << it.operator->()->_a1 << " " << it.operator->()->_a1 << endl;
++it;
}
cout << endl;
}
4.2 打印數(shù)據(jù)
//大多數(shù)情況模板是class還是typename是一樣的,但當有未實例化模板時,必須使用typename
//template<typename T>
void print_list(const list<T>& lt)
{
// list<T>未實例化的類模板,編譯器不能去他里面去找
// 編譯器就無法list<T>::const_iterator是內(nèi)嵌類型,還是靜態(tài)成員變量
// 前面加一個typename就是告訴編譯器,這里是一個類型,等list<T>實例化
// 再去類里面去取
typename list<T>::const_iterator it = lt.begin();
while (it != lt.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
優(yōu)化:上面打印數(shù)據(jù)是針對list,下面是針對容器的。
// 模板(泛型編程)本質(zhì),本來應(yīng)該由我們干的活交給編譯器
template<typename Container>
void print_container(const Container& con)
{
typename Container::const_iterator it = con.begin();
while (it != con.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
五·、所有代碼以及測試用例
giteeC++:list增刪查改模擬實現(xiàn)代碼以及測試用例
推薦:giteeC++:list增刪查改模擬實現(xiàn)代碼(最終版本、感覺版本?。。。?/a>
到此這篇關(guān)于C++實現(xiàn)list增刪查改模擬的文章就介紹到這了,更多相關(guān)C++ list增刪查改 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Ubuntu20.04安裝使用jsoncpp、json-c庫的方法實例
這篇文章主要給大家介紹了關(guān)于Ubuntu20.04安裝使用jsoncpp、json-c庫的相關(guān)資料,文中通過代碼介紹的非常詳細,對大家的學(xué)習或者工作就有一定的參考借鑒價值,需要的朋友可以參考下2024-04-04
Qt數(shù)據(jù)庫應(yīng)用之實現(xiàn)通用數(shù)據(jù)庫請求
這篇文章主要為大家介紹了Qt中是如何實現(xiàn)通用數(shù)據(jù)庫請求的,文中的示例代碼講解詳細,對我們學(xué)習Qt有一定幫助,感興趣的小伙伴可以了解一下2022-03-03

