探索 List 的奧秘:自己動手寫一個 STL List?

📖引言

大家好!今天我們要一起來揭開 C++ 中?list?容器的神秘面紗——不是直接用 STL,而是親手實現一個簡化版的?list!🎉

你是不是曾經好奇過:

  • list?是怎么做到高效插入和刪除的?🔍

  • 迭代器到底是怎么工作的?🔄

  • 為什么?list?是雙向循環鏈表?🔗

在這篇博客中,我們將從零開始,一步步實現一個名為?amber::list?的模板類,包含:

  • ? 雙向鏈表結構?list_node

  • ? 迭代器?__list_iterator(支持?++--*->

  • ? 常用接口:push_backpush_frontinserteraseclear...

  • ? 拷貝構造、賦值重載、初始化列表構造

  • ? 異常安全的資源管理 🛡?

我們還會深入探討:

  • 🧠 迭代器的設計哲學

  • 🔄 雙向鏈表的插入與刪除邏輯

  • 💡 模板編程中的技巧與陷阱

不管你是剛學完數據結構,還是想深入理解 STL 的實現,這篇博客都會讓你收獲滿滿!🌟

接下來,就讓我們一起進入?list?的世界吧!👇


在 list 的模擬實現中,我們一共會用到三個類,分別是?list_node,__list_iterator 和?list。我們需要多加關注的是如何利用c++的特性去模擬實現STL中的list(例如一個模板完成兩種迭代器的實現)。

list<T> │├── 包含多個 list_node<T> 節點│└── 提供 iterator 和 const_iterator│└── 由 __list_iterator<T, Ref, Ptr> 實現│└── 內部持有 list_node<T>* 用于遍歷

1. list_node<T>:鏈表節點類

template<class T>
struct list_node
{T _data;list_node<T>* _next;list_node<T>* _prev;explicit list_node(const T& x = T()):_data(x), _next(nullptr), _prev(nullptr){}
};

list_node<T>是鏈表節點類,用于list類的數據存儲。

這個類一共有三個成員對象,存儲數據的_data和用于指向前后節點的_prev和_next指針。

list_node構造函數初始化了一個階段存放了數據 x,這里的參數接口設計采用了c++的匿名對象參數缺省值的語法,然后賦值給const修飾的T類型引用的x形參,缺省值用匿名對象有效的防止了創建節點時未傳參的情況。

如果沒傳參在會創建一個T類型的對象并且調用對應的默認構造,可以在不傳參構建一個節點(在用list創建的鏈表對象的哨兵位節點_head就采用了這一特性,哨兵位節點只用于找鏈表的頭與尾,不存儲有效數據)。

我們通過初始化列表,在將x賦值給_data后把_next和_prev指針都初始化為nullptr空指針。

然后我們explicit關鍵字修飾構造函數防止了隱式類型轉換,在編譯時能夠有效的發現代碼編寫錯誤。

2. __list_iterator<T, Ref, Ptr>:迭代器類

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*(){return _node->_data;//返回當前節點的數據類型對象}//自定義類型成員數據訪問運算符Ptr operator->(){return &_node->_data;}//前置++Self& operator++(){_node = _node->_next;//迭代器只想下一個節點return *this;//返回當前迭代器對象}//后置++Self operator++(int){Node* temp = _node;//創建一個臨時對象保存當前迭代器指向_node = _node->_next;//迭代器指向下一個節點return temp;//返回臨時對象}//前置--Self& operator--(){_node = _node->_prev;//迭代器指向當前節點的前一個節點return *this;//返回當前對象}//后置--Self operator--(int){Node* temp = _node;//創建一個臨時對象保存當前迭代器指向_node = _node->_prev;//迭代器指向當前節點的前一個節點return temp;//返回臨時對象}//不等于比較運算符bool operator!=(const Self& it) const{return _node != it._node;//兩個迭代器指向節點不相等返回true}//等于比較運算符bool operator==(const Self& it) const{return _node == it._node;//兩個迭代器指向節點相等返回true}
};

迭代器類采用了三個模版參數 數據類型T數據對象引用?Ref 控制訪問權限參數 Ptr

  • T:元素類型。

  • Ref:引用類型(T&?或?const T&)。

  • Ptr:指針類型(T*?或?const T*)。

該類有一個成員對象為_node,并且把list_node<T>類型和__list_iterator<T, Ref, Ptr>類型進行了typedef為Node和Self便于讀寫以及實現不同版本的迭代器iterator和常量迭代器const_iterator,由于兩種迭代器的實現僅僅只有類型的不同,所以我們通過一個迭代器模板就有效地減少了代碼的冗余,符合STL一貫的書寫習慣。

由于list鏈表在物理空間上的不連續性,無法通過簡單的typedef指針類型來進行迭代器模擬。

基于此原因,我們需要單獨把模擬實現的list的迭代器封裝到一個類里面,并且自主實現前置后置版本的++和--,以及比較運算符,以便于模擬迭代器的行為有助于list鏈表對象的遍歷。

->操作符

基于->操作符的特殊性,這里我們需要單獨講解一下->操作符:

//自定義類型成員數據訪問運算符Ptr operator->(){return &_node->_data;}

對于有多個成員的內置結構類型的指針,我們可以通過一次->訪問到其對應的成員,例如:

typedef struct student
{int score;int grade;
}student;student s1 = {99,1};
student* ps1 = &s1;//內置類型箭頭訪問操作
ps1->grade

而對于list的模板數據類型T也有可能是有多個成員的結構體類型或者類類型,我們就需要重載出對應的->訪問操作符,但這里要特殊強調的是編譯的底層理解。

void list_test_5()
{student s1 = { 100,1 };student* ps1 = &s1;std::cout << ps1->score << ps1->grade << std::endl;amber::list<student> slt;slt.push_back({ 99,4 });slt.push_back({ 98,5 });slt.push_back({ 97,6 });slt.push_back({ 96,7 });amber::list<student>::iterator sit = slt.begin();while (sit != slt.end()){std::cout << "{" << sit->grade << "," << (*sit).grade << "}" << " ";sit++;}std::cout << std::endl;
}

上面這段代碼實現了一個自定義結構體類型的list對象的遍歷和成員變量的訪問,其中 sit->

grade看似是調用了一次->操作符,但實際上從編譯器的角度來看是兩次,先調用了一次迭代器的重載,然后調用了內置類型的->操作符

sit->grade│├── 第1次->:調用 sit.operator->()│     ↓│    返回 student* (指向真實數據)│└── 第2次->:對返回的指針使用 ->↓訪問真實的 grade 成員

3.?list<T>:鏈表容器類

template<class T>
class list
{typedef list_node<T> Node;
public:typedef __list_iterator<T, T&, T*> iterator;//迭代器typedef __list_iterator<T, const T&, const T*> const_iterator;//常量迭代器iterator begin(){return iterator(_head->_next);//返回哨兵位的下一個節點(返回鏈表的頭節點)}iterator end(){return iterator(_head);//返回哨兵位節點}const_iterator begin() const{return const_iterator(_head->_next);//返回哨兵位的下一個節點(返回鏈表的頭節點)}const_iterator end() const{return const_iterator(_head);//返回哨兵位節點}//空鏈表初始化void empty_init(){_head = new Node;//new一個節點但不初始化賦值給哨兵位節點_head->_next = _head;_head->_prev = _head;//哨兵位的前后指針指向自己}//默認構造函數list(){empty_init();//調用空鏈表初始化}//拷貝構造函數list(const list<T>& lt){empty_init();//調用空鏈表初始化for (auto e : lt){push_back(e);//遍歷lt對象并逐個尾插到當前鏈表}}///初始化鏈表構造list(std::initializer_list<T> il){empty_init();//調用空鏈表初始化for (const auto e : il){push_back(e);//遍歷初始化鏈表的所有對象并逐個尾插到當前鏈表}}//成員交換函數void swap(list<T>& lt){std::swap(_head, lt._head);//調用std標準庫交換函數,交換哨兵位節點std::swap(_size, lt._size);//調用std標準庫交換函數,交換_size}//賦值運算符重載list<T>& operator=(list<T> lt){swap(lt);//創建一個形參lt并與當前對象交換return (*this);//返回當前對象}//析構函數~list(){clear();//清空鏈表delete _head;//回收哨兵位頭節點資源_head = nullptr;//置空}//鏈表清空void clear(){iterator it = begin();while (it != end()){it = erase(it);//遍歷鏈表并將成員逐個erase掉}}//尾插void push_back(const T& x){insert(end(), x);//復用指定位置插入}//頭插void push_front(const T& x){insert(begin(), x);//復用指定位置插入}//尾刪void pop_back(){erase(_head->_prev);//復用指定位置刪除}//頭刪void pop_front(){erase(_head->_next);//復用指定位置刪除}//指定位置插入iterator insert(iterator pos, const T& val){Node* cur = pos._node;//保存當前指定位置Node* newnode = new Node(val);//new一個新節點出來并用val初始化Node* prev = cur->_prev;//保存當前位置的前一個節點newnode->_next = cur;newnode->_prev = prev;prev->_next = newnode;cur->_prev = newnode;_size++;//更新_sizereturn newnode;//返回新節點}//指定位置刪除iterator erase(iterator pos){if (_size == 0 || pos._node == _head){return end();//如果鏈表為空或者刪除頭節點時返回end()}Node* cur = pos._node;//保存當前指定位置Node* next = cur->_next;Node* prev = cur->_prev;if (prev != nullptr && next != nullptr)//確保前后指針的有效性{prev->_next = next;next->_prev = prev;//鏈接刪除位置的前后節點}delete cur;//刪除當前位置的節點_size--;//更新_sizereturn iterator(next);}//返回容量大小size_t size() const{return _size;}//全局友元交換函數friend void swap(list<T>& lhs, list<T>& rhs);private:Node* _head;//哨兵位節點size_t _size = 0;//節點數量
};// 在類外部定義友元函數模板
template<class T>
void swap(list<T>& lhs, list<T>& rhs) {lhs.swap(rhs);
}

迭代器區間

在一般的迭代器區間函數里,begin()指向的容器的第一個元素,end()指向的容器的最后一個元素的下一個位置,但是在list鏈表里,由于其首尾相接的特性,最后一個元素的下一個位置是哨兵位頭節點_head

通過這個實現項目,我們深入理解了:

  1. 數據結構:雙向鏈表的實現原理和優勢

  2. C++模板:模板編程的強大能力和靈活性和其他語言泛型的區別

  3. 迭代器設計:STL迭代器接口的設計哲學

  4. 內存管理:RAII原則和異常安全編程

  5. 軟件工程:接口設計、代碼復用、可維護性

🚀 結束語

實現一個完整的list容器不僅僅是一個編程練習,更是對C++核心概念的深度探索。從這個項目中,我們:

"不僅學會了如何寫代碼,更學會了如何設計代碼"

💪 掌握的技能:

  • 模板元編程的藝術

  • 迭代器設計的精髓

  • 內存管理的最佳實踐

  • 異常安全的編程思維

  • STL兼容的接口設計

? 最后的思考

C++的魅力在于它提供了從底層內存管理到高級抽象的全方位控制能力。通過親手實現標準庫組件,我們不僅加深了對語言特性的理解,更培養了系統級的編程思維。

記住:好的代碼不是寫出來的,是設計出來的。

希望這個list實現之旅對你有所啟發,繼續在C++的世界里探索前行!🎉

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

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

相關文章

mysql占用高內存排查與解決

mysql占用高內存排查-- 查看當前全局內存使用情況&#xff08;需要啟用 performance_schema&#xff09; SELECT * FROM sys.memory_global_total; -- 查看總內存使用 SELECT * FROM sys.memory_global_by_current_bytes LIMIT 10; -- 按模塊分類查看內存使用排行memory/perfor…

構建真正自動化知識工作的AI代理

引言&#xff1a;新一代生產力范式的黎明 自動化知識工作的人工智能代理&#xff08;AI Agent&#xff09;&#xff0c;或稱“智能體”&#xff0c;正迅速從理論構想演變為重塑各行各業生產力的核心引擎。這些AI代理被定義為能夠感知環境、進行自主決策、動態規劃、調用工具并持…

青少年機器人技術(四級)等級考試試卷-實操題(2021年12月)

更多內容和歷年真題請查看網站&#xff1a;【試卷中心 -----> 電子學會 ----> 機器人技術 ----> 四級】 網站鏈接 青少年軟件編程歷年真題模擬題實時更新 青少年機器人技術&#xff08;四級&#xff09;等級考試試卷-實操題&#xff08;2021年12月&#xff09; …

最新短網址源碼,防封。支持直連、跳轉。 會員無廣

最新短網址源碼&#xff0c;防封。支持直連、跳轉。 會員無廣告1.可將長網址自動縮短為短網址&#xff0c;方便記憶和使用。2.短網址默認為臨時有效&#xff0c;可付費升級為永久有效&#xff0c;接入支付后可自動完成&#xff0c;無需人工操作。3.系統支持設置圖片/文字/跳轉頁…

緩存-變更事件捕捉、更新策略、本地緩存和熱key問題

緩存-基礎知識 熟悉計算機基礎的同學們都知道&#xff0c;服務的存儲大多是多層級的&#xff0c;呈現金字塔類型。通常來說本機存儲比通過網絡通信的外部存儲更快&#xff08;現在也不一定了&#xff0c;因為網絡傳輸速度很快&#xff0c;至少可以比一些過時的本地存儲設備速度…

報表工具DevExpress .NET Reports v25.1新版本亮點:AI驅動的擴展

DevExpress Reporting是.NET Framework下功能完善的報表平臺&#xff0c;它附帶了易于使用的Visual Studio報表設計器和豐富的報表控件集&#xff0c;包括數據透視表、圖表&#xff0c;因此您可以構建無與倫比、信息清晰的報表。 DevExpress Reporting控件日前正式發布了v25.1…

kubernetes中pod的管理及優化

目錄 2 資源管理方式 2.1 命令式對象管理 2.2 資源類型 2.2.1 常用的資源類型 2.2.2 kubectl常見命令操作 2.3 基本命令示例 2.4 運行和調試命令示例 2.5 高級命令示例 3 pod簡介 3.1 創建自主式pod&#xff08;生產環境不推薦&#xff09; 3.1.1 優缺點 3.1.2 創建…

解釋一下,Linux,shell,Vmware,Ubuntu,以及Linux命令和shell命令的區別

Linux 操作系統概述Linux 是一種開源的類 Unix 操作系統內核&#xff0c;由 Linus Torvalds 于 1991 年首次發布。作為現代計算的基礎設施之一&#xff0c;它具有以下核心特征&#xff1a;多用戶多任務特性允許多個用戶同時操作系統資源&#xff0c;而模塊化設計使其能夠適應從…

Windows 系統中,添加打印機主要有以下幾種方式

在 Windows 系統中,添加打印機主要有以下幾種方式,我將從最簡單到最復雜為您詳細介紹。 方法一:自動安裝(推薦首選) 這是 Windows 10 和 Windows 11 中最簡單、最現代的方法。系統會自動搜索網絡(包括無線和有線網絡)上可用的打印機并安裝驅動程序。 操作步驟: 進入…

Mixture of Experts Guided by Gaussian Splatters Matters

Mixture of Experts Guided by Gaussian Splatters Matters: A new Approach to Weakly-Supervised Video Anomaly Detection ICCV2025 https://arxiv.org/pdf/2508.06318 https://github.com/snehashismajhi/GS-MoEAbstract 視頻異常檢測&#xff08;VAD&#xff09;是一項具有…

SeaTunnel Databend Sink Connector CDC 功能實現詳解

Databend 是一個面向分析型工作負載優化的 OLAP 數據庫&#xff0c;采用列式存儲架構。在處理 CDC&#xff08;Change Data Capture&#xff0c;變更數據捕獲&#xff09;場景時&#xff0c;如果直接執行單條的 UPDATE 和 DELETE 操作&#xff0c;會嚴重影響性能&#xff0c;無…

算法230. 二叉搜索樹中第 K 小的元素

題目&#xff1a;給定一個二叉搜索樹的根節點 root &#xff0c;和一個整數 k &#xff0c;請你設計一個算法查找其中第 k 小的元素&#xff08;從 1 開始計數&#xff09;。示例 1&#xff1a;輸入&#xff1a;root [3,1,4,null,2], k 1 輸出&#xff1a;1 示例 2&#xff1…

Seaborn數據可視化實戰:Seaborn多變量圖表繪制高級教程

Seaborn多變量圖表實戰&#xff1a;從數據到洞察 學習目標 本課程將帶領學員深入了解Seaborn庫中用于繪制多變量圖表的高級功能&#xff0c;包括聯合圖&#xff08;Joint Plot&#xff09;、對角線圖&#xff08;Pair Plot&#xff09;等。通過本課程的學習&#xff0c;學員將能…

【數智化人物展】首衡科技CTO李蒙:算法會過時,數據會貶值,只有系統智能才具未來性

李蒙本文由首衡科技CTO李蒙投遞并參與由數智猿數據猿上海大數據聯盟共同推出的《2025中國數智化轉型升級先鋒人物》榜單/獎項評選。大數據產業創新服務媒體——聚焦數據 改變商業“算法會過時&#xff0c;數據會貶值。”當我第一次在內部戰略會上拋出這句話時&#xff0c;現場…

word——將其中一頁變成橫向

在word中如何將其中一頁變成橫向&#xff1f; 在需要橫向的這一頁和上一頁插入分節符&#xff08;連續&#xff09; 1.點擊布局→分隔符→分節符&#xff08;連續&#xff09; 2.在所需要橫向頁將紙張方向改為橫向即可。

使用WORD實現論文格式的樣式化制作【標題樣式、自動序列、頁號(分節)、自動目錄(修改字體類型)】

背景 每家院校對論文的格式都有一系列的特定要求&#xff0c;相應的會有一份格式標準的說明文檔&#xff0c;該說明文檔中會羅列對文檔各個項的格式標準要求&#xff08;例如&#xff1a;題目、1級標題、2級標題、頁號、每個級別的字體字號&#xff0c;行距&#xff0c;段前段…

分享一個免費開源的網站跟蹤分析工具Open-Web-Analytics(和GoogleAnalytics一樣)

做獨立網站的福音&#xff0c;這個是免費開源的&#xff0c;可增改性強。 開源地址&#xff1a;https://github.com/Open-Web-Analytics/Open-Web-Analytics 下載源碼包 接著下載PHP工具&#xff1a;我用XP小皮 phpstudy_pro 地址&#xff1a;phpStudy - Windows 一鍵部署 …

Maxscript如何清理3dMax場景?

在3ds Max的創作過程中,隨著項目的推進,場景往往會積累許多冗余元素,如孤立幫助對象、隱藏對象以及空層等,它們不僅讓場景顯得雜亂無章,還會占用資源、降低視口性能,影響工作效率。別擔心,在本教程中,我們將為大家帶來實用妙招——通過簡單的Maxscript腳本片段,快速清…

JavaScript 性能優化實戰:從分析到落地的全指南

一、引言&#xff1a;為什么 JS 性能優化至關重要&#xff1f;用戶體驗的直接影響&#xff1a;加載慢、交互卡頓如何流失用戶&#xff08;引用 Google 研究&#xff1a;頁面加載延遲 1 秒&#xff0c;轉化率下降 7%&#xff09;業務價值關聯&#xff1a;性能優化對 SEO、留存率…

線性回歸學習筆記

一、線性回歸簡介1. 核心定義線性回歸是一種通過屬性的線性組合進行預測的線性模型&#xff0c;核心目標是找到一條直線&#xff08;二維&#xff09;、一個平面&#xff08;三維&#xff09;或更高維的超平面&#xff0c;使模型的預測值與真實值之間的誤差最小化。2. 適用場景…