C++之list類(超詳細)

在上一節中我們學習了STL中的vector這個容器,這節我們來學習一下另外一個常用的容器——list。

文章目錄

前言

一、list的介紹

二、list的使用及相關接口

1.list的使用

2.list的迭代器使用

3.list的相關接口

3.1 list capacity

3.2 list element access

3.3 list modifiers

三、list與vector的對比

四、list的模擬實現


前言

我們在vector中就已經介紹了C++中引入了STL的方便,這里我們就不多贅述了。我們上節所學習的vector,這個容器使用于那個存儲空間連續的結構,因為它的底層設計是數組實現的。對于那些存儲空間不連續的結構,如我們在數據結構中所學習的鏈表,它是有一個一個的指針連接起來的,這里我們就來學習一下這種容器——list。


一、list的介紹

上面這個介紹是引入C++文檔指導的,我們從上面的文檔中,我們可以知道list就是一個允許在任意位置進行插入/刪除的序列容器,它同樣可以使用迭代器,因此我們可以通過迭代器輕松遍歷list中的內容。

如上圖所示,我們在C++中所學習的list就是我們在數據結構中學習的帶頭雙向循環鏈表,因此我們可以往前遍歷,也可以往后遍歷。因此它的頭結點是一個關鍵的結點,我們要注意,后面我們在list的模擬實現中會著重介紹這個的。

二、list的使用及相關接口

1.list的使用

我們現在如果想要使用C++中的STL都是需要提前寫出它們的頭文件,并把它們包含起來,那么在主函數中,我們就可以直接使用它們了。list的頭文件包含就是#include<list>,我們包含這個頭文件之后,我們不僅可以使用list的迭代器還可以使用它的各種接口。

對于list對象的創建和之前的vector是一樣的,我們在實例化對象時,我們寫類類型的時候,我們要將類模板與數據類型一起寫上去,這樣才能夠算是一個數據類型。

template<class T> //我們定義類模板中的元素類型
list<T>   //這就是一個list對象的數據類型

對于list的構造函數同樣有好幾種函數原型:

如上圖所示,我們可以構造一個空的list,也可以構造一個包含n個值為val的元素的list,我們還可以使用迭代器區間中的元素來構造list,除了上面哪幾種構造函數,我們其實還可以使用初始化列表來初始化list中的內容,初始化列表就是使用一對{ },里面放置的是我們想要進行初始化的內容。(這種構造方法我覺得挺香的,可以自己來初始化想要的內容,不然我們還得創建一個空的list,然后再逐個插入元素,才能得到我們想要的list)

2.list的迭代器使用

這里我們可以將迭代器認為是一個指針,但是它實際上比不是指針,只是功能與指針很相似,這點我們需要注意,我們學習就將它們進行一下類比學習。list中的迭代器與我們vector迭代器不一樣,由于vector中的存儲空間是連續的,因此它的迭代器就相當于原生指針,使用起來十分方便,但是list的存儲空間不連續,那么它的迭代器就不能用原生指針來代替。這里我們只是來初步介紹一下如何使用list的迭代器,至于list中的迭代器是如何實現的,我們在后面的模擬實現中會著重講解的。

對于容器的使用方式都是一個模板,我們首先先使用我們想要的容器類型來定義一個迭代器變量并給它初始化,我們一般把第一個迭代器作為初始值。后面我們根據自己的需求來使用迭代器。對于list的迭代器,我們就將容器類型換成list<int>即可。如下代碼是我們使用list迭代器進行遍歷訪問list中的元素。

對于迭代器的函數基本都是一樣的,如下圖中的那幾個函數就是迭代器的接口函數,我們根據不同的場合進行使用。

?

注意

1.begin,end是正向迭代器函數,我們實行++操作的時候,迭代器向后移動;

2.rbegin,rend是反向迭代器函數,我們實行++操作的時候,迭代器向前移動。

3.list的相關接口

3.1 list capacity

對于list容器,它沒有像vector,string那樣的capacity函數,因為list是一個存儲空間不連續的結構,因此它的容量大小,我們無法通過函數接口來獲取,我們只能得到它當前的元素個數size,以及list是否為空。這兩個接口使用起來也是十分簡單的,和之前幾種容器的使用方法一樣,我們直接調用函數即可。

3.2 list element access

我們在介紹list的時候就已經說了list是一個帶頭雙向循環鏈表。因此對于它有兩個元素我們是可以直接獲取的:表頭元素,表尾元素。我們可以分別使用front和back這兩個函數來獲取。

3.3 list modifiers

list中那些增刪函數與vector中的函數接口基本差不多,兩者上面主要的區別就是list可以在表頭,表尾進行插入/刪除,而vector只能夠進行尾刪/尾插操作。它們兩個都沒有了find函數了(string類中有find函數),它們如果想要使用這個函數,可以直接使用庫中的find函數,對于庫中的find函數,原型如下所示:

輸入參數:我們要輸入一個迭代器區間,還有一個我們想要查找的值。我們一般使用這個函數使用下面這個模板(以list舉例):

#include<algorithm>
#include<iostream>
#include<list>
using namespace std;
int main()
{int x;cin >> x;auto it = find(lt.begin(), lt.end(),x);  //auto 推導出來的是迭代器類型return 0;
} 

如下代碼,是插入/刪除等函數的代碼演示:

對于insert/erase這兩個函數,我要好好來介紹一下。這里list中的insert/erase函數,它們的返回值類型都是迭代器類型(這里vector中的也是一樣的)。而且我們傳遞的參數也是迭代器類型的參數或者迭代器區間。在vector中對于insert/erase中都會出現迭代器失效的情況,但是在list中insert并不會出現迭代器失效的情況,只有erase才會使得迭代器失效。因為我們在前面已經說了list是一個帶頭雙向循環鏈表,這里迭代器失效即迭代器所指的結點無效,即該結點被刪除了。因此插入結點時迭代器是不會失效的(我們每次插入的結點都是一個新的結點,都是有效的迭代器),而且erase導致迭代器失效的也只是指向被刪除的那個結點的迭代器失效了,其他的結點并沒有被影響。

這是文檔中list::insert的介紹,我們傳遞的參數是迭代器位置,我們插入的位置是我們傳遞的迭代器位置的前一個迭代器位置,這一點我們要記住。至于它的返回值就是我們插入的位置的迭代器。

這是文檔中list::erase的介紹,我們傳遞的參數迭代器就是我們想要刪除位置的迭代器,但是它的返回值我們要注意了,它的返回值是我們被刪除位置的下一個位置的迭代器。我們在vector中就說過了如何解決迭代器失效的方法:更新迭代器的值。因此,我們為了保證迭代器不會失效,我們可以將erase的函數返回值賦值給迭代器,那樣迭代器進行了更新,就不會出現迭代器失效的情況了。

void TestListIterator1()
{
int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };list<int> l(array, array+sizeof(array)/sizeof(array[0]));auto it = l.begin();while (it != l.end()){// erase()函數執行后,it所指向的節點已被刪除,因此it無效,在下一次使用it時,必須先給
其賦值l.erase(it); ?++it;}
}
// 改正
void TestListIterator()
{
int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };list<int> l(array, array+sizeof(array)/sizeof(array[0]));auto it = l.begin();while (it != l.end()){l.erase(it++); ? ?// it = l.erase(it);}
}

三、list與vector的對比

vector與list都是STL中非常重要的序列式容器,由于兩個容器的底層結構不同,導致其特性以及 應用場景不同,其主要不同如下:

四、list的模擬實現

#pragma once#include<assert.h>namespace hjc
{// 慣例// 全部都是公有,一般用structtemplate<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){}};// typedef list_iterator<T, T&, T*> iterator;// typedef list_iterator<T, const T&, const T*> const_iterator;template<class T, class Ref, class Ptr> //我們定義模板的時候,我們定義多個參數//我們除了定義元素類型的參數,還定義一個引用類型的參數和一個指針類型的參數//這個對于我們后面實例化出普通迭代器與const迭代器有著奇效struct list_iterator{typedef list_node<T> Node;typedef list_iterator<T, Ref, Ptr> Self;  //重命名的時候,我們要將上面模板中定義的參數都帶上Node* _node;list_iterator(Node* node):_node(node){}//下面重載*和—>運算符的使用,我們要使用上面定義的參數作為返回值//因為,我們普通迭代器與const迭代器的區別就是在這解引用,我們const修飾的是迭代器所指向的內容即頂層constRef operator*(){return _node->_data;}Ptr 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;}bool operator==(const Self& s){return _node == s._node;}};template<class T>class list{typedef list_node<T> Node;public:/*typedef list_iterator<T> iterator;typedef list_const_iterator<T> const_iterator;*///本質上還是實現了兩個迭代器的類,這里不過是我們傳遞給模板參數,然后編譯器幫我們實例化出來兩種迭代器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);}//begin,end函數需要我們自己來重新定義,上面模板生成的迭代器,只是對那些運算符進行了重載const_iterator begin() const{return const_iterator(_head->_next);}const_iterator end() const{return const_iterator(_head);}void empty_init(){_head = new Node();_head->_next = _head;_head->_prev = _head;_size = 0;  //初始化時,size=0}list(){empty_init();}// lt2(lt1)  拷貝構造,我們傳遞的參數是類類型的引用類型//我們先對其初始化,然后我們將值逐個插入到我們要拷貝的鏈表中list(const list<T>& lt){empty_init();for (auto& e : lt){push_back(e);}}// lt2 = lt3//list& operator=(list lt)//對于賦值運算符的重載,我們可以直接將兩個鏈表中的內容交換(交換一下頭指針即可)list<T>& operator=(list<T> lt){swap(lt);   //使用我們自己實現的swap函數  這里實際是    this.swap(lt)return *this; //這是賦值后的鏈表內容,原來的鏈表內容交換給了lt//由于lt是一個局部變量,在這個函數結束的時候,然后它就自動銷毀了}//~list(){clear();delete _head;_head = nullptr;}//鏈表中的交換函數(直接交換兩個頭指針即可)std中的swap函數則需要重新創建兩個空間,然后分別將值拷貝過去void swap(list<T>& tmp){std::swap(_head, tmp._head);std::swap(_size, tmp._size); //兩個鏈表中的有效元素個數也要進行交換}//使用迭代器+erase直接將鏈表中的元素都刪除掉,但是這里可能會導致迭代器失效的情況,因此我們需要對迭代器進行更新//erase函數的返回值類型是迭代器類型,返回的迭代器是被刪除的緊接著的下一個迭代器位置void clear(){auto it = begin();while (it != end()){it = erase(it);  //因此我們直接將erase的返回值賦給it,這樣就相當于對迭代器更新+向后移動一位了}}//構造函數(構造n個相同值)list(size_t n, const T& val = T()){empty_init();//初始化 for (size_t i = 0; i < n; i++) //由于這里已經明確給了多少個元素了,于是我們就使用普通的for循環來插入值{push_back(val);}}//尾插 我們首先要定義一個要插入的新結點,然后找出插入結點與頭結點等結點的關系//使用使用指針連續關系即可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來進行復用//end()即_head位置,這里是頭結點之前的位置,即鏈表尾的位置}void push_front(const T& x){insert(begin(), x);  //begin()=>_head->_next,頭結點的下一結點位置}void pop_front(){erase(begin());}void pop_back(){erase(--end());  //--end()指向的是頭結點的上一個元素,即鏈表的最后一個元素}//插入位置是我們指定位置(pos)之前一個位置插入元素iterator insert(iterator pos, 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;++_size;return iterator(newnode);}//返回的位置為被刪除位置的下一個迭代器位置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;--_size;return iterator(next);}private://這里,我們定義了兩個成員變量,一個頭指針,一個是元素的有效個數//size我們直接定義為成員變量,在上面的函數中如果有修改的地方我們就直接進行修改Node* _head;size_t _size;};//函數模板,這是std庫中的//這個函數模板需要重新創建空間,然后拷貝內容過去template <class T>void swap(T& a, T& b){T c(a); a = b; b = c;}//這是list庫中的swap函數模板template <class T>void swap(list<T>& a, list<T>& b)  //傳遞兩個鏈表類型的引用作為參數{a.swap(b); //實際是對我們上面那個swap的函數的一個封裝,寫這個函數是想讓編譯器優先使用這個函數,而不用上面那個函數模板}
}

對于上面的list的模擬實現,我主要講解一下,迭代器的模擬實現。由于list是個鏈表由一個個的節點所組成的,因此我們并不能夠像之前的vector那樣,直接使用原生指針typedef即可。我們需要使用類來進行封裝。我們要將迭代器的那些操作(運算符的重載)都封裝到一個類中,但是迭代器又有還幾種迭代器(這里我們就實現比較簡單的迭代器:普通迭代器和const迭代器,至于方向迭代器我們這里就暫時不模擬實現)我們知道兩種迭代器的實現功能都是不一樣的但是又很相似,如果我們為了const迭代器再重寫一個類,這樣就顯得有點冗余(其中許多操作都是相似的,只是幾處有所差別)于是,我們就在模板上做點手腳:我們在模板參數中多設定幾個參數:一般我們寫模板參數時只寫一個參數:類中對象的數據類型,這里我們加了兩個參數:一個引用類型的參數Ref,一個指針類型的參數Ptr。對于const迭代器,const修飾的是迭代器所指向的內容,我們并不能簡單的使用const來修飾我們最初模擬實現的普通迭代器然后typedef就行了。我們需要修改它們解引用操作的那幾個運算符,因此我們直接在模板中給定參數,在我們實例化時,我們給定我們想要的參數就行了。其實,我們在模板中多設定幾個參數,然后再傳遞參數來實例化就是讓編譯器幫我們寫兩個迭代器的類,這樣的粗活就不用我們自己親自來做了。

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

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

相關文章

mysql、oracle、SQLserver之間的區別和優勢

MySQL、Oracle和SQL Server都是常見的關系型數據庫管理系統&#xff08;RDBMS&#xff09;&#xff0c;它們在某些方面有一些區別和優勢。 MySQL&#xff1a; MySQL是一種開源的RDBMS&#xff0c;由Oracle公司開發和維護。它具有快速、穩定和易于使用的特點。MySQL適用于中小型…

Python依賴包遷移到斷網環境安裝

首先&#xff0c;我應該確認兩臺電腦的操作系統都是Windows&#xff0c;所以架構和版本應該兼容。Python版本必須一致&#xff0c;否則可能會有問題。比如&#xff0c;如果電腦B用的是Python 3.8.5&#xff0c;電腦A也得裝同樣的版本&#xff0c;否則有些包可能不兼容。所以第一…

75.HarmonyOS NEXT ImageItemView組件深度剖析:手勢交互與動畫實現(二)

溫馨提示&#xff1a;本篇博客的詳細代碼已發布到 git : https://gitcode.com/nutpi/HarmonyosNext 可以下載運行哦&#xff01; HarmonyOS NEXT ImageItemView組件深度剖析&#xff1a;手勢交互與動畫實現(二) 一、手勢系統架構 .gesture(GestureGroup(GestureMode.Exclusiv…

Qt 控件概述 QWdiget

Qt為我們提供了很多控件&#xff0c;這些控件拿過來就可以使用 目錄 QWidget 屬性 WindowFrame的影響 QWidget Qt中所有的組件都是繼承自QWidget Qt Creator中的右側可以看到QWidget的各種屬性 其中各種屬性都可以在Qt文檔中找到說明 ? 屬性 enabled&#xff1a;描述該組…

適合企業內訓的AI工具實操培訓教程(37頁PPT)(文末有下載方式)

詳細資料請看本解讀文章的最后內容。 資料解讀&#xff1a;適合企業內訓的 AI 工具實操培訓教程 在當今數字化時代&#xff0c;人工智能&#xff08;AI&#xff09;技術迅速發展&#xff0c;深度融入到各個領域&#xff0c;AIGC&#xff08;人工智能生成內容&#xff09;更是成…

Axios 請求取消:從原理到實踐

Axios 請求取消&#xff1a;從原理到實踐 在現代前端開發中&#xff0c;網絡請求是不可或缺的一部分。Axios 是一個基于 Promise 的 HTTP 客戶端&#xff0c;廣泛應用于瀏覽器和 Node.js 環境中。然而&#xff0c;在某些場景下&#xff0c;我們可能需要取消正在進行的請求&…

Spring Boot對接twilio發送郵件信息

要在Spring Boot應用程序中對接Twilio發送郵件信息&#xff0c;您可以使用Twilio的SendGrid API。以下是一個簡單的步驟指南&#xff0c;幫助您完成這一過程&#xff1a; 1. 創建Twilio賬戶并獲取API密鑰 注冊一個Twilio賬戶&#xff08;如果您還沒有的話&#xff09;。在Twi…

【最后203篇系列】015 幾種消息隊列的思考

背景 隊列還是非常重要的中間件&#xff0c;可以幫助我們&#xff1a;提高處理效率、完成更復雜的處理流程 最初&#xff0c;我覺得只要掌握一種消息隊列就夠了&#xff0c;現在想想挺好笑的。 過去的探索 因為我用python&#xff0c;而rabbitmq比較貼合快速和復雜的數據處…

TensorFlow 與 TensorFlow Lite:核心解析與層應用

1. 引言 TensorFlow 是 Google 開發的開源機器學習框架&#xff0c;支持從數據預處理、模型訓練到推理部署的完整生命周期。然而&#xff0c;在嵌入式和移動設備上&#xff0c;原生 TensorFlow 過于龐大&#xff0c;因此 Google 推出了輕量級版本——TensorFlow Lite&#xff…

DeepSeek大模型在政務服務領域的應用

DeepSeek大模型作為國產人工智能技術的代表&#xff0c;近年來在政務服務領域的應用呈現多點開花的態勢。通過多地實踐&#xff0c;該技術不僅顯著提升了政務服務的效率與智能化水平&#xff0c;還推動了政府治理模式的創新。以下從技術應用場景、典型案例及發展趨勢三個維度進…

電子電氣架構 --- 分布到集中的動カ系統及基于域控制器的架構

我是穿拖鞋的漢子,魔都中堅持長期主義的汽車電子工程師。 老規矩,分享一段喜歡的文字,避免自己成為高知識低文化的工程師: 所有人的看法和評價都是暫時的,只有自己的經歷是伴隨一生的,幾乎所有的擔憂和畏懼,都是來源于自己的想象,只有你真的去做了,才會發現有多快樂。…

深入理解C/C++堆數據結構:從原理到實戰

一、堆的本質與特性 1.1 什么是堆數據結構&#xff1f; 堆&#xff08;Heap&#xff09;是一種特殊的完全二叉樹&#xff0c;它滿足以下核心性質&#xff1a; 堆序性&#xff1a;每個節點的值都滿足特定順序關系 結構性&#xff1a;完全二叉樹的結構特性&#xff08;除最后一…

Python學習第十七天

Django框架-SQLite3 介紹 Django內置了對 SQLite3 數據庫的支持。SQLite3 是一個輕量級的嵌入式數據庫引擎&#xff0c;非常適合開發、測試和小型項目。以下是關于 Django 中 SQLite3 的介紹和應用指南。&#xff08;除了這些還支持mysql、oracle以及其他查詢文檔&#xff0c;…

Docker 》》Docker Compose 》》network 網絡 compose

docker 默認的網絡 三種模式 # 列出所有當前主機上或Swarm集群上的網絡 docker network ls#查看網絡詳情 docker network inspect network名稱# 清除未使用的docker網絡 docker network prune -f# 創建網絡 ocker network create -d bridge 網絡名稱 docker network create –s…

Python數字信號處理之最佳等波紋濾波器階數估計原理

Matlab中的階數估計函數 在MATLAB中&#xff0c;使用firpmord函數可以估算等波紋FIR濾波器的最小階數。該方法基于Parks-McClellan算法&#xff0c;通過通帶和阻帶的頻率邊界、幅度響應及允許的最大誤差來自動計算參數。 rp 3; % Passband ripple in dB rs 40; …

JumpServer基礎功能介紹演示

堡壘機可以讓運維人員通過統一的平臺對設備進行維護&#xff0c;集中的進行權限的管理&#xff0c;同時也會對每個操作進行記錄&#xff0c;方便后期的溯源和審查&#xff0c;JumpServer是由飛致云推出的開源堡壘機&#xff0c;通過簡單的安裝配置即可投入使用&#xff0c;本文…

C++和C的區別

C和C語言雖然共享相似的語法&#xff0c;但在設計理念和功能特性上有顯著區別。以下是兩者的主要差異&#xff1a; 1. 編程范式 C&#xff1a;純過程式編程&#xff0c;強調函數和步驟。C&#xff1a;支持多范式&#xff0c;包括面向對象編程&#xff08;類、繼承、多態&…

Android LeakCanary 使用 · 原理詳解

一、簡介 LeakCanary 是 Square 公司開源的 Android 內存泄漏檢測工具&#xff0c;通過自動化監控和堆轉儲分析&#xff0c;幫助開發者快速定位內存泄漏根源。其核心設計輕量高效&#xff0c;已成為 Android 開發中必備的調試工具。 二、使用方式 1. 集成步驟 在項目的 buil…

每日一題---dd愛框框(Java中輸入數據過多)

dd愛框框 實例&#xff1a; 輸入&#xff1a; 10 20 1 1 6 10 9 3 3 5 3 7 輸出&#xff1a; 3 5 這道題要解決Java中輸入的數過多時&#xff0c;時間不足的的問題。 應用這個輸入模板即可解決&#xff1a; Java中輸入大量數據 import java.util.*; import java.io.*;pu…

redis部署架構

一、redis多實例部署 實例1 安裝目錄&#xff1a;/app/6380 數據目錄&#xff1a;/app/6380/data 實例2 安裝目錄&#xff1a;/app/6381 數據目錄&#xff1a;/app/6381/data 1、創建實例安裝目錄 2、拷貝實例的配置文件 3、編輯實例的配置文件 第…