C++STL-list 底層實現

目錄

一、實現框架

二、list_node節點類的模擬實現

節點構造函數

三、list_iterator迭代器的模擬實現

迭代器類的模板參數說明

構造函數

*運算符重載

++運算符的重載

--運算符的重載

==運算符的重載

!=運算符的重載

list的模擬實現

默認成員函數

構造函數

拷貝構造函數

賦值運算符重載函數

析構函數

迭代器相關函數

begin和end

插入、刪除函數

push_back

insert

erase

push_front

pop_back

pop_front

size

swap

clear


一、實現框架

namespace lzg
{//模擬實現list當中的結點類template<class T>struct list_node{//成員變量T _data;list_node<T>* _next;list_node<T>* _prev;list_node(const T x& = T()):_data(x),_next(nullptr),_prev(nullptr){}};//模擬實現list迭代器//這里寫的一種是模板,最后由編譯器實例化對應的類型template<class T, class Ref, class Ptr>struct list_iterator{typedef list_node<T> Node;typedef list_iterator<T, Ref, Ptr> Self;Node* _node;list_iterator(Node* node):_node(node){}//各種運算符重載Ref operator*();Ptr operator->();Self& operator++();Self& operator--();Self operator++(int);Self operator--(int);bool operator!=(const Self& s);bool operator==(const Self& s);};template<class T>class list{typedef list_node<T> Node;public://迭代器有兩種(const和非const),通過傳遞的參數(有無const)來實例化對應迭代器typedef  list_iterator<T, T&, T*> iterator;typedef  list_iterator<T, const T&, const T*> const_iterator;//初始化空的帶哨兵位的雙向循環鏈表void list_empty();list();//默認構造函數(調用list_empty();)list(const list<T>& lt);//拷貝構造函數list(size_t n, const T& val = T());//初始化n個值為val的鏈表list<T>& operator=(list<T> lt); //賦值重載~list();  //析構//迭代器相關函數iterator begin();iterator end();const_iterator begin() const;const_iterator end() const;//鏈表的操作函數void push_front(const T& x);void pop_front();void pop_back();void push_back(const T& x);iterator insert(iterator pos, const T& val);iterator erase(iterator pos);void clear();void swap(list<T>& tmp);size_t size();private:Node* _head;size_t _size;};}

二、list_node節點類的模擬實現

list的底層是帶頭雙向循環鏈表

因此,我們若要實現list,則首先需要實現一個結點類。而一個結點需要存儲的信息有:數據、前一個結點的地址、后一個結點的地址,于是該結點類的成員變量也就出來了(數據、前驅指針、后繼指針)

而對于該結點類的成員函數來說,我們只需實現一個構造函數即可。因為該結點類只需要根據數據來構造一個結點即可,而結點的釋放則由list的析構函數來完成。

節點構造函數

list_node(const T x& = T()):_data(x),_next(nullptr),_prev(nullptr)
{}

傳值時利用匿名構造來實現,這樣節點就能存儲任意類型的值

三、list_iterator迭代器的模擬實現

在之前string和vector中模擬實現迭代器我們都是在類里面完成的,并沒有單獨拎出來封裝成一個類,這是因為在前兩者中,物理地址空間是連續的可以完成自增自減,但鏈表不一樣,兩個節點的地址不是連續的不能通過+1來實現從一個節點到另一個節點的操作。

你可能還會問,那么為什么不在list里面封裝形成一個內部類,這也是不合理的,這樣會

?降低靈活性?:難以共享迭代器實現細節(如const和非const迭代器)

增加耦合?:迭代器實現與特定容器綁定

模板特化困難?:不利于針對不同迭代器類別進行優化

迭代器類的模板參數說明

這里我們所實現的迭代器類的模板參數列表當中為什么有三個模板參數?

template<class T, class Ref, class Ptr>

我們是仿照c++底層源碼實現的,Ref是reference(引用)的縮寫,Ptr是pointer(指針)的縮寫

我們在list中typedef了兩種迭代器

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

傳遞的形參沒有加const(<T, T&, T*> )也就是對應普通迭代器( iterator;)

加上了const修飾那么就是const迭代器 (const_iterator)

可以理解我們寫的是一個迭代器模板,通過傳遞的參數不同由編譯器幫我們實例化出是普通迭代器還是const迭代器,如果不用Ref和Ptr我們就要寫兩種迭代器并且它們是高度相似的

構造函數

迭代器類實際上就是對結點指針進行了封裝,其成員變量就只有一個,那就是結點指針,其構造函數直接根據所給結點指針構造一個迭代器對象即可。

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

*運算符重載

當我們使用解引用操作符時,是想得到該位置的數據內容。因此,我們直接返回當前結點指針所指結點的數據即可,但是這里需要使用引用返回,因為解引用后可能需要對數據進行修改。

Ref operator*()
{return _node->_data;//訪問節點中存儲的數據
}

->運算符重載

Ptr operator->()
{return &_node->_data;
}

++運算符的重載

typedef list_iterator<T, Ref, Ptr> Self;

前置++

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

后置++

Self operator++(int)
{Self tmp(*this);//利用拷貝構造創建一個臨時對象_node = _node->_next;return tmp;
}

--運算符的重載

前置--

Self& operator--()
{_node=_node->_prev;return *this;
}

后置--

Self operator--(int)
{Self tmp(*this);_node = _node->_prev;return tmp;
}

==運算符的重載

判斷迭代器是否相等,也就是看兩個迭代器的指針指向是否相同

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

!=運算符的重載

可以復用==,也可以看兩個迭代器的指針指向是否不同

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

四、list的模擬實現

默認成員函數

構造函數

list是一個帶頭雙向循環鏈表,在構造一個list對象時,直接申請一個頭結點,并讓其前驅指針和后繼指針都指向自己即可。

void list_empty()
{_size = 0;_head = new Node();_head->_next = _head;_head->_prev = _head;
}list()
{list_empty();
}

拷貝構造函數

拷貝構造函數就是根據所給list容器,拷貝構造出一個新對象。對于拷貝構造函數,我們先調用list_empty函數把鏈表初始化為空,再通過復用push_back函數一個個尾插到新構造的容器后面即可。

//lt2(lt1)
list(const list<T>& lt)
{list_empty();for (auto& e : lt){push_back(e);//將容器lt當中的數據一個個尾插到新構造的容器后面}
}

賦值運算符重載函數

對自定義類型傳值傳參要調用對應的拷貝構造函數,我們通常加上&(引用)是為了減少傳值傳參帶來的拷貝冗余,但是我們這里故意不用引用,那么lt就是lt1的拷貝,通過交換lt來實現賦值,這樣把lt1賦值給了lt2,并且我們交換的是lt(lt1的拷貝)并不會影響lt的數據,并且出函數時,臨時對象lt會自動析構

// lt2=lt1                 
list<T>& operator=(list<T> lt)
{            swap(lt);   return *this;
}

析構函數

//析構函數
~list()
{clear(); //清理容器delete _head; //釋放頭結點_head = nullptr; //頭指針置空
}

clear的實現在后面,并且clear也是復用erase函數

迭代器相關函數

begin和end

begin函數返回的是第一個有效數據的迭代器,end函數返回的是最后一個有效數據的下一個位置的迭代器。

由于返回類型是iterator (list_iterator<T,T&,T*>) 如果寫出return?_head->_next,編譯器不會自動將指針轉為自定義類類型,這里我們用迭代器的匿名構造返回地址

iterator begin()
{return iterator(_head->_next);
}iterator end()
{return iterator(_head);
}

當然,還需要重載一對用于const對象的begin函數和end函數

const_iterator begin() const
{	return const_iterator(_head->_next);
}
const_iterator end() const
{return const_iterator(_head);
}

插入、刪除函數

push_back

畫圖后操作一目了然,完成insert函數后就能復用

void push_back(const T& x)
{/*Node* new_node = new Node(x);Node* tail = _head->_prev;tail->_next = new_node;new_node->_prev = tail;new_node->_next = _head;_head->_prev = new_node;*/insert(end(), x);
}

insert

iterator insert(iterator pos, const T& val)
{Node* cur = pos._node;       //pos是一個類需要的是里面的_node值Node* newnode = new Node(val);Node* prev = cur->_prev;//prev newnode curnewnode->_next = cur;cur->_prev = newnode;newnode->_prev = prev;prev->_next = newnode;_size++;return iterator(newnode);
}

erase

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

push_front

void push_front(const T& x)
{insert(begin(), x);
}

pop_back

void pop_back()
{erase(--end());
}

pop_front

void pop_front()
{erase(begin());
}

size

為了方便在list中我們還定義了_size來表明鏈表中插入節點的個數,并且在上面insert和erase函數中都更新了對_size的操作,所以我們直接返回就行了

size_t size()
{return _size;
}

swap

交換兩個鏈表的哨兵位節點就能交換兩個鏈表

void swap(list<T>& tmp)
{swap(_head,tmp._head);swap(_size, tmp._size);
}

clear

clear函數用于清空容器,我們通過遍歷的方式,逐個刪除結點,只保留頭結點即可。

void clear()
{auto it = begin();while (it != end()){it = erase(it);}
}

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

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

相關文章

解決網站圖片加載慢:從架構原理到實踐

在當前的數字商業環境中&#xff0c;用戶的在線體驗至關重要。當一個潛在客戶訪問企業網站或電商平臺時&#xff0c;如果頁面加載過程遲緩&#xff0c;特別是圖片和視頻內容無法快速顯示&#xff0c;用戶的耐心會迅速耗盡。研究數據表明&#xff0c;網站加載時間與用戶跳出率和…

windows注冊表:開機自啟動程序配置

目錄 一、注冊表位置 系統范圍的開機自啟動程序 當前用戶的開機自啟動程序 二、配置步驟 三、注意事項 四、其他方法 任務計劃程序 啟動文件夾 1. 創建程序快捷方式 2. 打開 Startup 文件夾 3. 將快捷方式移動到 Startup 文件夾 4. 驗證程序是否自動啟動 注意事項 …

(11)用于無GPS導航的制圖師SLAM(一)

文章目錄 前言 1 安裝 RPLidar 和 Pixhawk 2 檢查 RPLidar 的串行端口 3 安裝更多軟件包 4 創建Catkin工作空間 5 安裝 RPLidar 節點 6 安裝 Google Cartographer 前言 本頁展示了如何使用 RPLidarA2 激光雷達(RPLidarA2 lidar)設置 ROS 和 Google Cartographer SLAM&a…

車載診斷架構 --- 基于整車功能的正向診斷需求開發

我是穿拖鞋的漢子,魔都中堅持長期主義的汽車電子工程師。 老規矩,分享一段喜歡的文字,避免自己成為高知識低文化的工程師: 做到欲望極簡,了解自己的真實欲望,不受外在潮流的影響,不盲從,不跟風。把自己的精力全部用在自己。一是去掉多余,凡事找規律,基礎是誠信;二是…

字帖生成器怎么用?電腦手機雙端操作指南

字帖生成器是一款支持電腦端和手機端的免費練字工具&#xff0c;可一鍵生成PDF格式字帖并直接打印使用。本文基于官方公開版本&#xff0c;提供無廣告、無營銷的實測操作指南。 工具基礎信息 軟件名稱&#xff1a;字帖生成器適用設備&#xff1a;Windows、安卓/鴻蒙核心功能&…

pycharm 遠程連接服務器報錯

配置遠程鏈接的時候出現報錯 Command finished with exit code 139 Execution was killed due to timeout Failed to execute command Rsync command ‘rsync’ was not found neither in local PATH nor as full executable path Starting introspection for Python… 放假前好…

局域網共享文件夾

準備工作&#xff1a; A電腦&#xff08;共享端&#xff09; B電腦&#xff08;本機&#xff09;在A電腦&#xff0c;選好要共享的目錄&#xff0c;然后右鍵屬性 > 高級共享 > 共享此文件夾 > 權限(全開)然后找到此電腦&#xff0c;右鍵&#xff0c;打開屬性&#xff…

時序數據庫全景指南:從場景選型到內核拆解

1. 什么是時序數據 時序數據&#xff08;Time-Series Data&#xff09; 是在時間上連續產生、且帶有時間戳的觀測值序列&#xff0c;典型特征&#xff1a;維度描述高并發寫百萬點/秒&#xff0c;追加為主寫多讀少90 % 查詢是降采樣或聚合時效性越新越熱&#xff0c;舊數據價值遞…

深入解析 Oracle 內存架構:駕馭 SGA 與 PGA 的性能藝術

引言&#xff1a;數據庫的心臟與大腦如果說磁盤上的數據文件是 Oracle 數據庫的“身體”&#xff0c;是永久存儲的基石&#xff0c;那么內存結構就是其“心臟與大腦”。它負責所有計算活動的發生&#xff0c;決定了數據泵送的速度與效率。一個配置得當、運行順暢的內存體系&…

竣工驗收備案識別技術:通過AI和OCR實現智能化文檔處理,提升效率與準確性,推動建筑行業數字化轉型。

竣工驗收備案是建設工程項目投入使用的最終法定程序&#xff0c;是確保工程符合規劃、質量、消防、環保等各項要求的核心關口。傳統的備案流程依賴大量紙質文檔和人工審核&#xff0c;效率低下且易出錯。隨著人工智能與大數據技術的崛起&#xff0c;竣工驗收備案識別技術應運而…

76 最小覆蓋子串

76 最小覆蓋子串 文章目錄76 最小覆蓋子串1 題目2 解答1 題目 給你一個字符串 s 、一個字符串 t 。返回 s 中涵蓋 t 所有字符的最小子串。如果 s 中不存在涵蓋 t 所有字符的子串&#xff0c;則返回空字符串 "" 。 注意&#xff1a; 對于 t 中重復字符&#xff0c;…

趣味學Rust基礎篇(變量與可變性)

這篇文章將用通俗的比喻和清晰的邏輯&#xff0c;帶你深入理解 Rust 變量背后的核心思想&#xff0c;讓你不僅“會用”&#xff0c;更能“明白為什么”。 Rust 的“盒子哲學”&#xff1a;變量、可變性、常量與隱藏 想象一下&#xff0c;Rust 里的變量就像一個個盒子。你把值&a…

2025年- H100-Lc208--912.排序數組(快速選擇排序)--Java版

1.題目2.思路 快速選擇排序的平均時間復雜度是O&#xff08;nlogn&#xff09;&#xff0c;最壞時間復雜度是O&#xff08;n^2&#xff09;&#xff0c;最好的時間復雜度是O&#xff08;nlogn&#xff09;&#xff0c;空間復雜度是O&#xff08;nlogn&#xff09;。 排序算法中…

解決 pdf.mjs 因 MIME 類型錯誤導致的模塊加載失敗問題

Mozilla PDF.js V4 開始&#xff0c;它官方分發確實只提供了 ESM 模塊&#xff08;.mjs&#xff09;&#xff0c;沒有以前的 pdf.js、pdf.worker.js UMD 版本了。 這個問題本質上是 瀏覽器要求以 application/javascript MIME 類型加載 ES Module&#xff0c;而你引入的 pdf.mj…

STM32八大模式

前言&#xff1a;STM32存在八大模式&#xff0c;分別如下推挽輸出&#xff0c;開漏輸出&#xff0c;復用推挽輸出&#xff0c;復用開漏輸出浮空輸入&#xff0c;上拉輸入&#xff0c;下拉輸入&#xff0c;模擬輸入STM32標準IO結構圖如下&#xff1a;其中如下電路為保護電路&…

OpenCV4.X庫功能全解---個人筆記

文章目錄前言1.Core核心功能1.1 基本數據類型和結構&#xff1a;1.2 數組操作&#xff1a;1.3 數學函數&#xff1a;1.4 隨機數生成&#xff1a;1.5 線性代數運算&#xff1a;1.6 常用數據結構和算法&#xff1a;1.7 XML/YAML文件讀寫&#xff1a;1.8 錯誤處理&#xff1a;1.9時…

代碼隨想錄刷題Day44

二叉搜索樹的最近公共祖先 這道題&#xff0c;可以沿用二叉樹的最近公共祖先的求法進行求解&#xff0c;也就是root判斷-左右子樹遞歸求LCA-根據左右子樹的LCA結果返回值這一套。 但是&#xff0c;如果要用上搜索二叉樹的有序性這個信息的話&#xff0c;就可以直接在遞歸時候確…

springmvc的數據校驗和處理的一個例子

JSR-303是Java 的標準規范&#xff0c;而 Spring MVC 對其提供了完美的支持和集成 1.JSR-303 的身份 JSR-303 是 Java 標準 JSR&#xff1a;Java Specification Request&#xff08;Java 規范請求&#xff09; JSR-303&#xff1a;Bean Validation 1.0&#xff08;Bean 驗證規范…

SlowFast使用指南(三)——自建數據集

寫在前面 在前兩個章節初步使用了SlowFast&#xff0c;使用的都是官方給出的數據集。 附上鏈接&#xff1a; SlowFast使用指南&#xff08;一&#xff09;——demo運行-CSDN博客 SlowFast使用指南&#xff08;二&#xff09;——訓練ava數據集-CSDN博客 本文嘗試了使用自己的數…

Day26 樹的層序遍歷 哈希表 排序算法 內核鏈表

day26 樹的層序遍歷 哈希表 排序算法 內核鏈表 實現樹的層序遍歷&#xff08;廣度遍歷&#xff09; 使用隊列輔助實現二叉樹的層序遍歷。算法核心思想是&#xff1a;從根節點開始&#xff0c;依次將每一層的節點入隊&#xff0c;出隊時訪問該節點&#xff0c;并將其左右子節點&…