【C++】- 掌握STL List類:帶你探索雙向鏈表的魅力

在這里插入圖片描述

文章目錄

  • 前言:
  • 一.list的介紹及使用
    • 1. list的介紹
    • 2. list的使用
      • 2.1 list的構造
      • 2.2 list iterator的使用
      • 2.3 list capacity
      • 2.4 list element access
      • 2.5 list modi?ers
      • 2.6 list的迭代器失效
  • 二.list的模擬實現
    • 1. list的節點
    • 2. list的成員變量
    • 3.list迭代器相關問題
      • 3.1 普通迭代器
      • 3.2 const迭代器
    • 4. list的成員函數
      • 4.1 list的空初始化
      • 4.2 push_back
      • 4.3 構造函數
      • 4.4 insert
      • 4.4 erase
      • 4.5 push_front
      • 4.6 pop_front
      • 4.7 pop_back
      • 4.8 clear
      • 4.8 析構函數
      • 4.9 swap
      • 4.10 賦值運算符重載
  • 最后想說:

前言:

?C++中的List容器是標準模板庫(STL)中的一種序列容器,它實現了雙向鏈表的功能。與數組(如vector)和單向鏈表相比,List容器提供了更加靈活的元素插入和刪除操作,特別是在容器中間位置進行這些操作時。

一.list的介紹及使用

1. list的介紹

  • 雙向鏈表結構: list容器使用雙向鏈表來存儲元素,每個元素(節點)都包含數據部分兩個指針,分別指向前一個元素和后一個元素。這種結構使得在鏈表的任何位置進行插入和刪除操作都非常高效,時間復雜度為O(1)
  • 動態大小: list容器的大小可以在運行時動態改變,即可以在程序運行過程中添加或移除元素。
  • 不支持隨機訪問:vectorarray等連續內存的容器不同,list不支持隨機訪問迭代器,不能直接通過索引獲取元素,而需要通過迭代器遍歷。
  • 迭代器穩定性: 在list中插入或刪除元素不會導致其他迭代器失效(除了指向被刪除元素的迭代器)。這是因為它通過調整相鄰節點的指針來維護鏈表結構,而不需要移動元素或重新分配內存。

2. list的使用

list的使用參考文檔:list的文檔介紹
在這里插入圖片描述

2.1 list的構造

構造函數接口說明
list (size_type n, const value_type& val =value_type() )構造的list中包含n個值為val的元素
list()構造空的list
list (const list& x)拷貝構造函數
list (InputIterator ?rst, InputIterator last)用[?rst, last)區間中的元素構造list

代碼演示:

#include<list>
int main()
{list<int> l1;//構造空的l1;list<int> l2(4,100);//l2中存放4個值為100的元素list<int> l3(l2.begin(),l2.end());//用l2的[begin,end)左開右閉區間構造l3;list<int> l4(l3);//用l3拷貝構造l4// 以數組為迭代器區間構造l5int array[] = { 16,2,77,29 };list<int> l5(array, array + sizeof(array) / sizeof(int));// 列表格式初始化C++11list<int> l6{ 1,2,3,4,5 };// 用迭代器方式打印l5中的元素list<int>::iterator it = l5.begin();while (it != l5.end()){cout << *it << " ";++it;}cout << endl;// C++11范圍for的方式遍歷for (auto& e : l5)cout << e << " ";cout << endl;return 0;
}

2.2 list iterator的使用

此處,大家可暫時將迭代器理解成一個指針,該指針指向list中的某個節點。

函數聲明接口說明
begin返回第一個元素的迭代器
end返回最后一個元素下一個位置的迭代器
rbegin返回一個指向容器中最后一個元素的反向迭代器(即容器的反向起始)
rend返回一個反向迭代器,該迭代器指向列表容器中第一個元素之前的理論元素(該元素被認為是其反向結束)。

注意:

  1. begin與end為正向迭代器,對迭代器執行++操作,迭代器向后移動
  2. rbegin(end)與rend(begin)為反向迭代器,對迭代器執行++操作,迭代器向前移動

代碼演示:

int main()
{int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };list<int> l(array, array + sizeof(array) / sizeof(array[0]));// 使用正向迭代器正向list中的元素// list<int>::iterator it = l.begin();   // C++98中語法auto it = l.begin();                     // C++11之后推薦寫法while (it != l.end()){cout << *it << " ";++it;}cout << endl;// 使用反向迭代器逆向打印list中的元素// list<int>::reverse_iterator rit = l.rbegin();auto rit = l.rbegin();while (rit != l.rend()){cout << *rit << " ";++rit;}cout << endl;return 0;
}

2.3 list capacity

函數聲明接口說明
front檢測list是否為空,是返回true,否則返回false
size返回list中有效節點的個數

2.4 list element access

函數聲明接口說明
front返回list的第一個節點中值的引用
back返回list的最后一個節點中值的引用

2.5 list modi?ers

函數聲明接口說明
push_front在list首元素前插入值為val的元素
pop_front刪除list中第一個元素
push_back在list尾部插入值為val的元素
pop_back刪除list中最后一個元素
insert在list position 位置中插入值為val的元素
erase刪除list position位置的元素
swap交換兩個list中的元素
clear清空list中的有效元素

代碼演示:

#include<iostream>
#include<vector>
using namespace std;void PrintList(const list<int>& l)
{// 注意這里調用的是list的 begin() const,返回list的const_iterator對象for (list<int>::const_iterator it = l.begin(); it != l.end(); ++it){cout << *it << " ";}cout << endl;
}// list插入和刪除
// push_back/pop_back/push_front/pop_front
void TestList1()
{int array[] = { 1, 2, 3 };list<int> L(array, array + sizeof(array) / sizeof(array[0]));// 在list的尾部插入4,頭部插入0L.push_back(4);L.push_front(0);PrintList(L);// 刪除list尾部節點和頭部節點L.pop_back();L.pop_front();PrintList(L);
}// insert /erase 
void TestList2()
{int array1[] = { 1, 2, 3 };list<int> L(array1, array1 + sizeof(array1) / sizeof(array1[0]));// 獲取鏈表中第二個節點auto pos = ++L.begin();cout << *pos << endl;// 在pos前插入值為4的元素L.insert(pos, 4);PrintList(L);// 在pos前插入5個值為5的元素L.insert(pos, 5, 5);PrintList(L);// 在pos前插入[v.begin(), v.end)區間中的元素vector<int> v{ 7, 8, 9 };L.insert(pos, v.begin(), v.end());PrintList(L);// 刪除pos位置上的元素L.erase(pos);PrintList(L);// 刪除list中[begin, end)區間中的元素,即刪除list中的所有元素L.erase(L.begin(), L.end());PrintList(L);
}// resize/swap/clear
void TestList3()
{// 用數組來構造listint array1[] = { 1, 2, 3 };list<int> l1(array1, array1 + sizeof(array1) / sizeof(array1[0]));PrintList(l1);// 交換l1和l2中的元素list<int> l2;l1.swap(l2);PrintList(l1);PrintList(l2);// 將l2中的元素清空l2.clear();cout << l2.size() << endl;
}int main()
{TestList1();TestList2();TestList3();return 0;
}

運行結果:
在這里插入圖片描述

2.6 list的迭代器失效

?前面已經說過了,此處可以將迭代器理解為類似于指針的東西,迭代器失效即迭代器指向的節點失效了,即該節點被刪除了。因為list的底層結構為帶頭結點的雙向循環鏈表,因此在list進行插入操作時不會導致迭代器失效,只有刪除時才會失效,并且失效的是被刪除節點的迭代器,其他迭代器不會受到影響。

二.list的模擬實現

1. 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){}
};

2. list的成員變量

template<class T>
class list
{typedef list_node<T> Node;
public://成員函數
private:Node* _head; //哨兵位的頭節點
};

?沒有用訪問限定符限制的成員class默認是私有的,struct默認是公有的,如果一個類既有公有也有私有就用class,全部為公有一般用struct。這不是規定,只是個慣例。

3.list迭代器相關問題

簡單分析:
&emsp; 這里不能像以前一樣給一個結點的指針作為迭代器,如果it是typedef的節點的指針,it解引用得到的是節點,不是里面的數據,但是我們期望it解引用是里面的數據,++it我們期望走到下一個節點去,而list中++走不到下一個數據,因為數組的空間是連續的,++可以走到下一個數據。但是鏈表達不到這樣的目的。所以原身指針已經無法滿足這樣的行為,怎么辦呢?這時候我們的類就登場了
?用類封裝一下節點的指針,然后重載運算符,模擬指針。
例如:

reference operator*()const
{return (*node).data;
}
self& opertor++()
{node = (link_type)((*node).next);return *this;
}

3.1 普通迭代器

template<class T>
struct list_iterator
{typedef list_node<T> Node;typedef list_iterator<T> Self;Node* _node;list_iterator(Node* node):_node(node){}T& operator*() //用引用返回可以讀數據也可以修改數據{return _node->_data;}T* operator->(){return &_node->_data;}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;}bool operator!=(const Self& s){return _node != s._node;}
};

3.2 const迭代器

const迭代器在定義的時候不能直接定義成typedef const list_iterator<T> const_iterator,const迭代器的本質是限制迭代器指向的內容不能被修改,而前面的這種寫法限制了迭代器本身不能被修改,所以迭代器就不能進行++操作。那該怎能辦呢?答案是我們可以實現一個單獨的類:

template<class T>
struct list_const_iterator
{typedef list_node<T> Node;typedef list_const_iterator<T> Self;Node* _node;list_const_iterator(Node* node):_node(node){}const T& operator*(){return _node->_data; //返回這個數據的別名,但是是const別名,所以不能被修改}const T* operator->(){return &_node->_data; //我是你的指針,const指針}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;}bool operator!=(const Self& s){return _node != s._node;}
};

普通法迭代器與const迭代器的區別就是:普通迭代器可讀可寫,const迭代器只能讀
上面是我們自己實現的普通迭代器和const迭代器,用兩個類,并且這兩個類高度相似,下來就讓我們一起看一看庫里面是怎么實現的吧!

在這里插入圖片描述
我們可以看到庫里面是寫了兩個模板,讓編譯器去生成對應的類。其本質上也是寫了兩個類,只不過是讓編譯器去生成對應的類。

迭代器不需要我們自己寫析構函數、拷貝構造函數、賦值運算符重載函數,因為這里要的是淺拷貝,例如我把一個迭代器賦值給另外一個迭代器,就是期望兩個迭代器指向同一個節點,這里用淺拷貝即可,拷貝給你我們兩個迭代器就指向同一個節點。

4. list的成員函數

4.1 list的空初始化

void empty_init() //空初始化
{_head = new Node();_head->_next = _head;_head->_prev = _head;
}

4.2 push_back

//普通版本
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版本insert(end(),x);

4.3 構造函數

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

4.4 insert

iterator insert(iterator position; const T& val)
{Node* cur = pos._node;Node* newnode = new Node(val);Node* prev = cur->_prev;//prev newnode curprev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;return iterator(newnode);
}

4.4 erase

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

4.5 push_front

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

4.6 pop_front

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

4.7 pop_back

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

4.8 clear

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

4.8 析構函數

~list()
{clear();delete _head;_head = nullptr;
}

4.9 swap

void swap(list<T>& tmp)
{std::swap(_head, tmp._head);//交換哨兵位的頭節點
}

4.10 賦值運算符重載

//現代寫法
//lt2=lt3
//list<T>& operator=(list<T> lt)
list& operator=(list lt) //不加模板參數
{swap(lt);//交換就是交換哨兵位的頭節點return *this;
}//lt3傳給lt去調用拷貝構造,所以lt就和lt3有一樣大的空間一樣大的值,lt2很想要,也就是/this想要,lt2之前的數據不想要了,交換給lt,此時lt2就和lt3有一樣大的空間一樣大的值,
//lt出了作用域就被銷毀了

構造函數和賦值運算符重載函數的形參和返回值類型可以只寫類名 list,不需要寫模板參數,這種寫法在類里面可以不加,只能在類里面可以這樣寫,類外面是不行的,一般情況下加上好一點。


最后想說:

本章我們STL的List就介紹到這里,下期我將介紹關于stackqueue的有關知識,如果這篇文章對你有幫助,記得點贊,評論+收藏 ,最后別忘了關注作者,作者將帶領你探索更多關于C++方面的問題。

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

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

相關文章

Docker--Docker Container(容器) 之容器實戰

對docker容器的前兩篇文章 Docker–Docker Container(容器) 之 操作實例 Docker–Docker Container(容器&#xff09; Mysql容器化安裝 我們可以先在Docker Hub上查看對應的Mysql鏡像,拉取對應的鏡像&#xff1a; 拉取mysql5.7版本的鏡像&#xff1a; docker pull mysql:5.7…

ModuleNotFoundError: No module named ‘torchvision.transforms.functional_tensor‘

問題&#xff1a; 運行代碼時&#xff0c;報錯&#xff1a; … File “/home/xzy/anaconda3/envs/groundinggpt/lib/python3.10/site-packages/pytorchvideo/transforms/augmix.py”, line 6, in from pytorchvideo.transforms.augmentations import ( File “/home/xzy/anac…

【匯編語言】內中斷(二) —— 安裝自己的中斷處理程序:你也能控制0號中斷

文章目錄 前言1. 編程處理0號中斷1.1 效果演示1.2 分析所要編寫的中斷處理程序1.2.1 引發中斷1.2.2 中斷處理程序1.2.3 中斷處理程序do0應該存放的位置1.2.4 中斷向量表的修改1.2.5 總結 1.3 程序框架1.4 注意事項1.5 從CPU的角度看中斷處理程序1.6 一些問題的思考與解答 2. 安…

華為OD E卷(100分)23-連續字母長度

前言 工作了十幾年&#xff0c;從普通的研發工程師一路成長為研發經理、研發總監。臨近40歲&#xff0c;本想辭職后換一個相對穩定的工作環境一直干到老, 沒想到離職后三個多月了還沒找到工作&#xff0c;愁腸百結。為了讓自己有點事情做&#xff0c;也算提高一下自己的編程能力…

VS2019中無法跳轉定義_其中之一情況

我習慣了使用VS2019看stm的代碼&#xff1b; 遇到的問題&#xff0c;在導入代碼后&#xff0c;發現有些函數調用不能跳轉到定義&#xff1b; 問題描述步驟 1、導入代碼 2、跳轉&#xff0c;無法跳轉 1、中文路徑 2、刪除.vs文件 和網上查的都沒辦法解決 最后發現是VS不支持 …

讓 Win10 上網本 Debug 模式 QUDPSocket 信號槽 收發不丟包的方法總結

在前兩篇文章里&#xff0c;我們探討了不少UDP丟包的解決方案。經過幾年的摸索測試&#xff0c;其實方法非常簡單, 無需修改代碼。 1. Windows 下設置UDP緩存 這個方法可以一勞永逸解決UDP的收發丟包問題&#xff0c;只要添加注冊表項目并重啟即可。即使用Qt的信號與槽&#…

【設計模式】觀察者模式深度講解

文章目錄 概覽一、定義與特點二、角色與職責三、實現方式四、應用場景五、優缺點 Java實現Python實現 概覽 觀察者模式&#xff08;Observer Pattern&#xff09;是一種行為型設計模式&#xff0c;它定義了一種一對多的依賴關系&#xff0c;讓多個觀察者對象同時監聽某一個主題…

Elasticsearch:ES|QL 中的全文搜索 - 8.17

細心的開發者如果已經閱讀我前兩天發布的文章 “Elastic 8.17&#xff1a;Elasticsearch logsdb 索引模式、Elastic Rerank 等”&#xff0c;你就會發現在 8.17 的發布版中&#xff0c;有一個重要的功能發布。那就是 ES|QL 開始支持全文搜索了。在今天的文章中我們來嘗試一下。…

SQL和Python 哪個更容易自學?

SQL和Python不是一個物種&#xff0c;Python肯定更難學習。如果你從事數據工作&#xff0c;我建議先學SQL、有余力再學Python。因為SQL不光容易學&#xff0c;而且前期的投入產出比更大。 SQL是數據查詢語言&#xff0c;場景限于數據查詢和數據庫的管理&#xff0c;對大部分數據…

【unity】從零開始制作平臺跳躍游戲--界面的認識,添加第一個角色!

在上一篇文章中&#xff0c;我們已經完成了unity的環境配置與安裝?? 【Unity】環境配置與安裝-CSDN博客 接下來&#xff0c;讓我們開始新建一個項目吧&#xff01; 新建項目 首先進入unityHub的項目頁面&#xff0c;點擊“新項目”&#xff1a; 我們這個系列將會以2D平臺…

怎么禁用 vscode 中點擊 go 包名時自動打開瀏覽器跳轉到 pkg.go.dev

本文引用怎么禁用 vscode 中點擊 go 包名時自動打開瀏覽器跳轉到 pkg.go.dev 在 vscode 設置項中配置 gopls 的 ui.navigation.importShortcut 為 Definition 即可。 "gopls": {"ui.navigation.importShortcut": "Definition" }ui.navigation.i…

Unity3D實現抽象類的應用場景例子

系列文章目錄 unity知識點 文章目錄 系列文章目錄??前言??一、示例??二、使用步驟??三、抽象類和接口的區別??3-1、抽象類??3-2、接口類??壁紙分享??總結??前言 假設我們正在制作一個游戲,游戲中有多種不同類型的角色,這些角色都有一些共同的行為(比如移…

數據倉庫工具箱—讀書筆記01(數據倉庫、商業智能及維度建模初步)

數據倉庫、商業智能及維度建模初步 記錄一下讀《數據倉庫工具箱》時的思考&#xff0c;摘錄一些書中關于維度建模比較重要的思想與大家分享&#x1f923;&#x1f923;&#x1f923; 博主在這里先把這本書"變薄"~有時間的小伙伴可以親自再讀一讀&#xff0c;感受一下…

docker啟動一個helloworld(公司內網服務器)

這里寫目錄標題 容易遇到的問題&#xff1a;1、docker連接問題 我來介紹幾種啟動 Docker Hello World 的方法&#xff1a; 最簡單的方式&#xff1a; docker run hello-world這會自動下載并運行官方的 hello-world 鏡像。 使用 Nginx 作為 Hello World&#xff1a; docker…

計算機組成原理(五):程序裝載

在計算機組成原理中&#xff0c;程序裝載&#xff08;Program Loading&#xff09;是指將程序從外存&#xff08;如磁盤&#xff09;加載到內存中&#xff0c;并為其運行做好準備的過程。程序裝載是實現程序從靜態存儲狀態到動態運行狀態的關鍵環節&#xff0c;涉及地址映射、內…

Python+OpenCV系列:模版匹配

文章目錄 1. 模板匹配基本原理2. cv2.matchTemplate() 函數函數原型&#xff1a; 3. 模板匹配步驟4. 單目標模板匹配示例5. 多目標模板匹配多目標模板匹配示例代碼解析&#xff1a; 6. 多模板匹配多模板匹配示例代碼解析 7. 總結 模板匹配是一種在圖像中尋找模板的位置的方法。…

基于IEEE 802.1Qci的時間敏感網絡(TSN)主干架構安全分析及異常檢測系統設計

中文標題&#xff1a;基于IEEE 802.1Qci的時間敏感網絡&#xff08;TSN&#xff09;主干架構安全分析及異常檢測系統設計 英文標題&#xff1a;Security Analysis of the TSN Backbone Architecture and Anomaly Detection System Design Based on IEEE 802.1Qci 作者信息&…

怎樣提升企業網絡的性能?

企業網絡的穩定性和高效性直接影響員工的工作效率。以下從多維度分析了一些有效策略&#xff0c;幫助公司提升網絡性能&#xff0c;營造更高效的辦公環境。 1. 升級網絡設備 采用性能更高的網絡硬件是優化網絡體驗的重要基礎。選擇支持高吞吐量、低延遲的設備&#xff08;如企業…

scala基礎_數據類型概覽

Scala 數據類型 下表列出了 Scala 支持的數據類型&#xff1a; 類型類別數據類型描述Scala標準庫中的實際類基本類型Byte8位有符號整數&#xff0c;數值范圍為 -128 到 127scala.Byte基本類型Short16位有符號整數&#xff0c;數值范圍為 -32768 到 32767scala.Short基本類型I…

力扣239.滑動窗口最大值

文章目錄 一、前言二、單調隊列 一、前言 力扣239.滑動窗口最大值 滑動窗口最大值&#xff0c;這道題給定一個數組&#xff0c;以及一個窗口的長度&#xff0c;這個窗口會往后滑動&#xff0c;直到數組最后一個元素。 要求每個滑動窗口的中的最大值。對于這道題&#xff0c;我…