C++ 之 【模擬實現 list(節點、迭代器、常見接口)】(將三個模板放在同一個命名空間就實現 list 啦)

1.前提準備

(1) list 的底層結構一般是帶頭雙向循環鏈表

(1)為避免命名沖突,需要創建一個命名空間來存放模擬實現的 list

(2)下面模擬實現list時,聲明和定義不分離(具體原因后續講解)

2.完整實現

2.1 鏈表節點

template<class T>//節點寫成類模板,適合不同的數據類型
struct __list_node//帶頭雙向循環鏈表的節點,因為下面會使用,用struct
{typedef __list_node<T> Node;//起個別名,謹防忘記實例化(即指定參數)Node* _next;Node* _prev;T _val;__list_node(const T& val = T()):_next(nullptr),_prev(nullptr),_val(val){}
};

(1)將鏈表節點寫成一個類模板,將數據類型指定為模板參數T,以支持不同類型的數據

(2)使用 struct 定義節點類,原因有二

1.后續所實現的鏈表,迭代器需要用到節點,使用 struct 方便暴露節點成員

2.用戶使用鏈表時,并不知曉節點的名稱等消息,強行使用而出錯的鍋不用實現者背

(3)不要將類模板名直接寫為node,因為后續還有樹等數據結構也有節點,

所以命名時通常加 list 前綴來區分

(1)節點包含指向前后的兩個指針,指針類型是節點類型,注意

類模板中,__list_node是類模板名,__list_node<T>是類型

謹防忘記,為類型起個別名

typedef __list_node<T> Node;//起個別名,謹防忘記實例化(即指定參數)Node* _next;Node* _prev;

(2)節點初始化,將指針置空,數據依用戶傳遞為主,缺省值為輔

__list_node(const T& val = T()):_next(nullptr),_prev(nullptr),_val(val){}

(1)后續實現鏈表時才會在堆上申請節點

節點內部并沒有申請其他資源,就沒必要在節點中寫析構函數等其他默認成員函數

等鏈表使用完畢,再在鏈表中進行資源釋放即可

2.2 鏈表中的迭代器

迭代器可以被理解為 抽象化的指針,它模擬指針的行為(遍歷和操作元素),

使用方式與指針一致,但它的適用范圍更廣

(1)vector是動態順序表,內存連續分布,指針可以遍歷和操作元素

所以在vector中,指針就是一種迭代器

但是 list 的內存分布一般不連續,節點指針不可以遍歷和操作元素,

所以,我們定義一個迭代器類來封裝節點指針,通過運算符重載來模擬指針的行為

此時,該迭代器類的對象就是鏈表的迭代器

	template<class T, class Ref, class Ptr>//節點指針類型是__list_node<T>*struct __list_iterator//list中需要使用迭代器,使用struct供下面使用{typedef __list_node<T> Node;typedef __list_iterator<T, Ref, Ptr> Self;Node* _node;//用節點指針構造迭代器__list_iterator(Node* node):_node(node){}//it1 != it2bool operator!=(const Self& it)const{return _node != it._node;}bool operator==(const Self& it)const{return _node == it._node;}//*Ref operator*()const//Ref是引用的意思,用來控制普通迭代器與const迭代器{return _node->_val;}Self& operator++(){_node = _node->_next;return *this;}Self operator++(int){iterator temp(*this);_node = _node->_next;return temp;}Self& operator--(){_node = _node->_prev;return *this;}Self operator--(int){Self temp(*this);_node = _node->_prev;return temp;}Ptr operator->()const{return &(_node->_val);}};

2.2.1 迭代器的細節點

迭代器類的成員變量就是節點指針

(1)節點類型為:__list_node<類型>,為了支持不同類型的數據,將類型指定為參數T,

同時為了簡便,為節點起個別名 Node?

typedef __list_node<T> Node;

所以指針類型就是 Node*,

Node* _node;//用節點指針構造迭代器

(1)使用 struct 定義迭代器類,原因有二

1.后續所實現的鏈表中的常用接口需要用到迭代器,使用struct暴露成員直接供其使用

2.用戶使用鏈表時,并未知曉迭代器的完整信息,強行使用而導致出錯的鍋不用實現者背

(2)命名迭代器類時通常加 list 前綴來區分

(3)迭代器對象名字太長,起個別名Self,self 就是 自己的意思

typedef __list_iterator<T, Ref, Ptr> Self;

2.2.2 迭代器的構造函數

__list_iterator(Node* node):_node(node)
{}

(1)后續實現鏈表時,顯示傳遞一個節點指針以構造一個迭代器

(2)迭代器內部并沒有申請其他資源,就沒必要在迭代器類中寫析構函數等其他默認成員函數

2.2.3 迭代器間的比較

bool operator!=(const Self& it)
{return _node != it._node;
}bool operator==(const Self& it)
{return _node == it._node;
}

(1)迭代器之間的比較實際上就是 節點指針之間的比較,運算符重載即可

2.2.4 迭代器訪問數據

Ref operator*()//Ref是引用的意思,用來控制普通迭代器與const迭代器
{return _node->_val;
}

(1)指針解引用可以訪問到它所指向的數據,迭代器模擬指針

通過運算符重載,使得解引用迭代器就是 獲取 節點指針指向的數據

(2)Ref是引用的意思,用來控制普通迭代器與const迭代器,下面來仔細講解

2.2.5 普通迭代器與const迭代器的主要區別

不加const修飾的鏈表包含的是 普通迭代器,const 修飾的鏈表包含的是 const迭代器。

不加const修飾的鏈表中,節點的數據可以被修改,const 修飾的鏈表則相反

所以普通迭代器與const迭代器的主要區別就是,二者訪問數據的權限間的區別

普通迭代器 可讀可修改數據, const 迭代器 只可讀 數據

Ref operator*()//Ref是引用的意思,用來控制普通迭代器與const迭代器
{return _node->_val;
}

所以,將解引用返回值類型指定為模板參數

后續通過顯示傳遞不同解引用返回值類型(T&, const T&)(T為數據類型),

就可以實現普通迭代器和const迭代器

2.2.6 迭代器的遍歷

Self& operator++()
{_node = _node->_next;return *this;
}Self operator++(int)
{iterator temp(*this);_node = _node->_next;return temp;
}Self& operator--()
{_node = _node->_prev;return *this;
}Self operator--(int)
{Self temp(*this);_node = _node->_prev;return temp;
}

(1)前置++,重載運算符,讓節點指針指向后一個節點,同時返回自己的引用即可

(2)后置++,重載運算符,讓節點指針指向后一個節點,同時返回自己的拷貝即可

(1)前置--,重載運算符,讓節點指針指向前一個節點,同時返回自己的引用即可

(2)后置--,重載運算符,讓節點指針指向前一個節點,同時返回自己的拷貝即可

2.2.7 重載 -> 運算符

Ptr operator->()const
{return &(_node->_val);
}
struct A
{int _a1;int _a2;A(int a1 = 0, int a2 = 0):_a1(a1),_a2(a2){}
};
void test_list2()
{list<A> lt;lt.push_back(A(1, 1));lt.push_back(A(2, 2));lt.push_back(A(3, 3));lt.push_back(A(4, 4));list<A>::iterator it = lt.begin();while (it != lt.end()){//cout << (*it)._a1 << ' ' << (*it)._a2 << endl;cout << it->_a1 << ' ' << it->_a2 << endl;//it->->_a1有省略++it;}cout << endl;
}

節點存儲的是一個自定義類型的數據的時候,想要訪問自定義類型的數據有兩個方法

//cout << (*it)._a1 << ' ' << (*it)._a2 << endl;
cout << it->_a1 << ' ' << it->_a2 << endl;//it->->_a1有省略

(1)解引用迭代器(*it)._a1,通過?operator*?獲取對象,再用?.?操作符訪問成員。

(2)調用?operator->it->_a1,通過?operator->?獲取對象的指針(或類指針對象),

再用?->?訪問成員

cout << it->_a1 << ' ' << it->_a2 << endl;//it->->_a1有省略

雖然訪問數據時省略了一個 ->

但是這行代碼是能正常運行并訪問數據的,實際上,為了簡潔,迭代器訪問自定義類型的數據時,只需要一個->即可,你可以認為編譯器能夠自動識別并補充這里的省略

重載->運算符返回的是自定義類型對象的地址(用指針接收)

將指針類型指定為模板參數Ptr,

后續通過顯示傳遞不同解引用返回值類型(T*, const T*)(T為數據類型),

就可以實現普通迭代器和const迭代器

2.3 鏈表中的常見接口

	template<class T>class list{typedef __list_node<T> Node;public:typedef __list_iterator<T, T&, T*> iterator;//迭代器需要放到外面使用,所以別名也要在public中typedef __list_iterator<T, const T&, const T*> const_iterator;//迭代器需要放到外面使用,所以別名也要在public中iterator begin(){return _head->_next;//單參數返回,調用構造函數生成臨時對象}iterator end(){return _head;}const_iterator begin()const{return _head->_next;}const_iterator end()const{return _head;}//構造函數void empty_init(){_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;}list(){empty_init();}//lt(lt1)list(const list<T>& lt){empty_init();for (auto& e : lt){push_back(e);}}//lt1 = lt2void swap(list<T>& lt){std::swap(_head, lt._head);std::swap(_size, lt._size);}list<T>& operator=(list<T> lt){swap(lt);return *this;}//析構函數~list(){clear();delete _head;_head = nullptr;}void clear(){auto it = begin();while (it != end()){it = erase(it);}_size = 0;}//尾插void push_back(const T& val){先找尾//Node* tail = _head->_prev;創造尾插節點//Node* newnode = new Node(val);_head tail newnode//_head->_prev = newnode;//newnode->_next = _head;//tail->_next = newnode;//newnode->_prev = tail;insert(end(), val);}void push_front(const T& val){insert(begin(), val);}void pop_back(){erase(--end());}void pop_front(){erase(begin());}//insert\eraseiterator insert(iterator pos, const T& val){//創建新節點Node* newnode = new Node(val);//使用結點指針而不是迭代器進行鏈接Node* cur = pos._node;//prev newnode curNode* prev = cur->_prev;prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;++_size;return newnode;}iterator erase(iterator pos){assert(pos != end());Node* cur = pos._node;//prev cur nextNode* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;cur = nullptr;--_size;return prev;}size_t size()const{return _size;}private:Node* _head;size_t _size;};

2.3.1 鏈表中的細節點

(1)鏈表同樣是一個類模板,模板參數就是節點數據類型

(2)成員變量包含 哨兵位節點的指針_head 和 鏈表的大小_size (不包含頭節點的節點個數)

(1)使用 class 定義 list

不同于上述的節點和迭代器, list 需要實現封裝,只提供相應接口供外部使用

(1)節點類型為:__list_node<類型>,為了支持不同類型的數據,將類型指定為參數T,

同時為了簡便,為節點起個別名 Node?

typedef __list_node<T> Node;

注意,為了防止外部使用 Node, 需要將這行代碼寫在訪問限定符private的作用域之內

(2)迭代器對象名字太長,為符合日常使用習慣起別名如下:

typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;

基于上述"普通迭代器與const迭代器的區別",指定相應參數即可達到

注意,為了外部可以使用迭代器iterator,?

所以需要將這行代碼寫在訪問限定符public的作用域之內

2.3.2 鏈表中迭代器的接口

		iterator begin(){return _head->_next;//單參數返回,調用構造函數生成臨時對象}iterator end(){return _head;}const_iterator begin()const{return _head->_next;}const_iterator end()const{return _head;}

(1)begin函數返回的是指向第一個有效節點(哨兵位的下一個位置)的迭代器

函數內部直接返回相應指針即可,

因為迭代器的構造函數是單參數類型,編譯器會用該指針構造出相應迭代器

(2)其他函數按要求返回相應指針即可

(3)注意,const迭代器相關函數只有const對象才能調用,所用需要用const修飾

2.3.3 鏈表的構造函數

		//構造函數void empty_init(){_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;}list(){empty_init();}

(1)初始化一個鏈表就是創建一個頭節點,使其前后指針指向自己(起到雙向循環的作用),

并把鏈表的大小置為0的過程,將這個過程抽象為一個函數,以供后續拷貝構造函數的使用

2.3.4 insert、erase函數

		iterator insert(iterator pos, const T& x){Node* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(x);prev->_next = newnode;newnode->_next = cur;cur->_prev = newnode;newnode->_prev = prev;++_size;return newnode;}

insert 通常是指 在 pos位置之前插入

(1)只實現的單元素的插入,插入位置 pos 的類型是迭代器,返回的是新插入節點的位置

(2)插入時只需要注意 插入節點,插入節點前一個節點與新節點之間的關系

因為迭代器改變指向有點麻煩,使用節點指針完成上述關系的鏈接

因為鏈表由哨兵位節點,不用考慮頭插為空

(3)插入一個節點,_size++

iterator erase(iterator pos){assert(pos != end());Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;--_size;return next;}

(1)不能刪除哨兵位節點,同時注意 _size--

(1)erase函數存在迭代器失效問題,返回被刪除節點的下一個節點的迭代器

(2)刪除時注意提前保存上一個節點和下一個節點即可

2.3.5 尾插尾刪頭插頭刪?

實現好了insert、erase函數以及迭代器的接口之后,

直接調用函數即可

		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());}

2.3.6 clear 與 析構函數?

        void clear(){iterator it = begin();while (it != end()){it = erase(it);}_size = 0;}~list(){clear();delete _head;_head = nullptr;}	

?(1)clear函數的作用是清理有效節點,不包含哨兵位

直接迭代器遍歷鏈表并刪除節點即可,注意將_size 置空

(2)析構函數就是在 clear 之后, 清理頭節點

2.3.7 拷貝構造函數

		list(const list<T>& lt){empty_init();for (auto& e : lt){push_back(e);}}

拷貝構造一個鏈表,被構造對象首先需要進行初始化,

然后遍歷鏈表進行尾插即可

2.3.8 賦值重載函數

		void swap(list<T>& lt){std::swap(_head, lt._head);std::swap(_size, lt._size);}//lt1 = lt2list<T>& operator=(list<T> lt){swap(lt);return *this;}

現代寫法,不用自己開空間,直接傳值傳參,

lt 是 lt2 的深拷貝(拷貝構造實現的是深拷貝),然后交換兩個鏈表,傳引用返回

2.3.9 size函數

		size_t size(){return _size;}

前面的insert 、erase、clear函數已經對_size變量進行了相應操作

這里直接返回即可

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/903807.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/903807.shtml
英文地址,請注明出處:http://en.pswp.cn/news/903807.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

解決Win10虛擬機“網絡連接不上”,“Ethernet0 網絡電纜被拔出”的問題

一、情景引入 今天用Win10虛擬機打開瀏覽器發現&#xff1a; 很奇怪&#xff0c;平常都沒有這個問題。 二、檢查網絡狀態 點擊更改適配器選項&#xff0c;發現如下&#xff1a; 三、解決問題 打開任務管理器&#xff0c;點擊服務&#xff0c;搜索欄搜索&#xff1a;VM …

2025藍橋杯省賽網絡安全組wp

文章目錄 黑客密室逃脫ezEvtxflowzipEnigma星際xml解析器EBC-TrainAES-CBC 黑客密室逃脫 提示猜文件名&#xff0c;猜幾個常見的&#xff0c;app.py讀到源碼 這里也是腦抽了一下&#xff0c;把密鑰看成1236了。。。卡了五分鐘左右&#xff0c;解出來的時候已經降到300多分了&a…

算法查找目錄

1. 基礎數據結構 數組與鏈表 動態數組 實現與自動擴容機制均攤分析ArrayList/Vector實現 單向鏈表 基本操作(插入、刪除、查找)鏈表反轉環檢測(Floyd判圈算法) 雙向鏈表 插入刪除操作優化雙向遍歷優勢邊界情況處理 循環鏈表 約瑟夫環問題單向循環鏈表雙向循環鏈表 跳表 基本原…

Windows11 管理員用戶下無權限操作的解決方法

問題 Program Files 目錄下無權限進行寫入操作。 Windows 各種功能因權限不足無法訪問。 啟動某些應用程序時&#xff0c;可能會遇到 用戶賬戶控制 (UAC, User Account Control) 彈窗提示&#xff0c;要求確認是否允許該程序對設備進行更改。 等等問題 解決方法 運行 p…

.NET中,const和readonly區別

在.NET中&#xff0c;const和readonly都用于定義不可變的值&#xff0c;但它們在行為和使用場景上有顯著區別。以下是兩者的詳細對比&#xff1a; 初始化時機 ? const ? 編譯時常量&#xff0c;必須在聲明時賦值。 ? 值在編譯時確定&#xff0c;并被直接嵌入到IL代碼中&…

郵件分類特征維度實驗分析

活動發起人小虛竹 想對你說&#xff1a; 這是一個以寫作博客為目的的創作活動&#xff0c;旨在鼓勵大學生博主們挖掘自己的創作潛能&#xff0c;展現自己的寫作才華。如果你是一位熱愛寫作的、想要展現自己創作才華的小伙伴&#xff0c;那么&#xff0c;快來參加吧&#xff01…

數字智慧方案6158丨智慧醫療解決方案精華版(58頁PPT)(文末有下載方式)

數字智慧方案6158丨智慧醫療解決方案精華版 詳細資料請看本解讀文章的最后內容。 引言 隨著信息技術的飛速發展&#xff0c;智慧醫療已成為現代醫療體系的重要組成部分。本文將對《數字智慧方案6158丨智慧醫療解決方案精華版》進行詳細解讀&#xff0c;探討如何通過先進的技…

Fiori學習專題二十三: Filtering

這節課我們將針對list增加一個篩選功能。 1.首先改造下InvoiceList.view.xml&#xff0c;加入id方便找到它以及標簽 <mvc:ViewcontrollerName"ui5.walkthrough.controller.InvoiceList"xmlns"sap.m"xmlns:core"sap.ui.core"xmlns:mvc"s…

大語言模型 05 運行、微調的顯存計算詳解與優化 全量微調、LoRA 優化策略

寫在前面 隨著Transformer架構的大語言模型&#xff08;LLM&#xff09;不斷發展&#xff0c;其參數規模也在迅速增加。無論是進行模型推理還是微調訓練&#xff0c;GPU顯存消耗都是開發和應用LLM時的重要考量。本文將詳細探討大模型運行&#xff08;推理&#xff09;與微調時…

對Electron打包的exe文件進行反解析

一、了解 Electron 打包的 exe&#xff0c;本質上就是打包了網頁 (HTMLCSSJS)&#xff0c;核心文件是 app.asar。超級容易還原&#xff0c;還原率接近 100% 為什么 Electron 特別容易&#xff1f; 因為 Electron 根本沒有真正編譯成機器碼&#xff0c;它只是把網頁資源&…

【Vue2】1-創建一個Vue實例

Vue2官方文檔 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title> </head&g…

【C語言練習】015. 聲明和初始化指針

015. 聲明和初始化指針 015. 聲明和初始化指針1. 聲明指針示例1:聲明一個指向整數的指針2. 初始化指針示例2:將指針初始化為`NULL`示例3:將指針初始化為某個變量的地址示例4:將指針初始化為動態分配的內存地址3. 使用指針訪問和修改變量的值示例5:使用指針訪問和修改變量的…

好未來golang后端開發

OSI網絡模型 TCP和UDP對比 HTTP和HTTPS對比 B樹 HTTP常見狀態碼 線程和進程的區別 goroutine的調度模型GMP 常見的排序了解哪些 快速排序 func quickSort(data []int) {if len(data) < 1 {return}base : data[0]l, r : 0, len(data)-1for i : 1; i < r; {if data[i] &g…

(持續更新)Ubuntu搭建LNMP(Linux + Nginx + MySQL + PHP)環境

LNMP&#xff08;Linux Nginx MySQL PHP&#xff09;環境是在Linux操作系統上構建的一個高性能Web服務器環境。M也可以指代其他數據庫&#xff0c;P也可以指代Python 1. 準備Linux系統 確保你已經在一臺服務器或虛擬機上安裝了Linux操作系統。推薦使用Ubuntu、CentOS或Debi…

服務器頻繁重啟日志分析與診斷

從你提供的日志來看&#xff0c;系統確實經歷了多次重啟。這個日志行顯示的是&#xff1a; reboot system boot 6.8.0-58-generic Tue Apr 29 17:54 - 14:26 (20:31)這表示系統在4月29日17:54啟動&#xff0c;運行了約20小時31分鐘后&#xff0c;于次日14:26結束&#xff08;可…

如何提升個人的穩定性?

提升自我的穩定性是一個系統性工程&#xff0c;需要從內在認知、情緒管理、行為習慣到外在環境等多個維度進行優化。 以下是一些具體建議&#xff0c;幫助你逐步增強內心的穩定感&#xff1a; 一、內在認知調整 1. 建立清晰的自我認知 通過反思&#xff08;如寫日記、冥想…

數值求解Eikonal方程的方法及開源實現

Eikonal方程是一類非線性偏微分方程&#xff0c;形式為 ( |\nabla u(x)| f(x) )&#xff0c;常見于波傳播、幾何光學、最短路徑等問題。以下是數值求解Eikonal方程的方法及開源實現參考&#xff1a; 一、數值求解方法 有限差分法&#xff08;FDM&#xff09; 快速行進法&#…

基于Redis實現-用戶簽到

基于Redis實現-用戶簽到 這個功能將使用到Redis中的BitMap來實現。 我們按照月來統計用戶簽到信息&#xff0c;簽到記錄為1&#xff0c;未簽到則記錄為0 把每一個bit位對應當月的每一天&#xff0c;形成了映射關系。用0和1標示業務狀態&#xff0c;這種思路稱為位圖(BitMap)。…

如何用GPU Instancing來優化樹木草石重復模型

1&#xff09;如何用GPU Instancing來優化樹木草石重復模型 2&#xff09;Unity ASTC壓縮后的紋理在部分安卓機型上不顯示 3&#xff09;現在大部分項目的豎版UI設計分辨率是多少 4&#xff09;Android上拖拽物體不實時跟隨手指的問題 這是第430篇UWA技術知識分享的推送&#x…

Java面試高頻問題(31-33)

三十一、服務網格&#xff1a;東西向流量治理與故障注入 服務網格架構分層 mermaid graph BT subgraph Control Plane APilot --> BEnvoy Sidecar CMixer --> B DCitadel --> B end subgraph Data Plane B --> E服務A B --> F服務B B --> G服務C end 核心能…