【C++】STL---list

STL---list

  • 一、list 的介紹
  • 二、list 的模擬實現
    • 1. list 節點類
    • 2. list 迭代器類
      • (1)前置++
      • (2)后置++
      • (3)前置- -、后置- -
      • (4)!= 和 == 運算符重載
      • (5)* 解引用重載 和 -> 重載
    • 3. list 類
      • (1)迭代器
      • (2)修改相關的接口
        • swap()
        • insert()
        • erase()
        • push_back、push_front、pop_back、pop_front
        • clear()
      • (3)空鏈表初始化
      • (4)構造函數
      • (5)拷貝構造函數
      • (6)賦值運算符重載
      • (7)析構函數
    • 4. 打印容器的接口
      • (1)打印鏈表整型的接口
      • (2)打印 list 的接口
      • (3)打印容器的接口

一、list 的介紹

  1. list 是可以在常數范圍內在任意位置進行插入和刪除的序列式容器,并且該容器可以前后雙向迭代。
  2. list 的底層是雙向鏈表結構,雙向鏈表中每個元素存儲在互不相關的獨立節點中,在節點中通過指針指向其前一個元素和后一個元素。
  3. listforward_list 非常相似:最主要的不同在于 forward_list 是單鏈表,只能朝前迭代,已讓其更簡單高效。
  4. 與其他的序列式容器相比(array,vector,deque),list通常在任意位置進行插入、移除元素的執行效率更好。
  5. 與其他序列式容器相比,listforward_list 最大的缺陷是不支持任意位置的隨機訪問,比如:要訪問 list 的第6個元素,必須從已知的位置(比如頭部或者尾部)迭代到該位置,在這段位置上迭代需要線性的時間開銷;list 還需要一些額外的空間,以保存每個節點的相關聯信息(對于存儲類型較小元素的大 list 來說這可能是一個重要的因素)。

二、list 的模擬實現

list 學習時也要學會查看文檔:list 文檔介紹,在實際中我們熟悉常見的接口就可以,下面我們直接開始模擬實現,在模擬實現中我們實現的是常見的接口,并且會在實現中講解它們的使用以及注意事項。

首先跟以往不一樣的是,list 是一個個節點連接起來的,所以它不是連續的物理空間,這也就意味著,它不用擴容,每次插入的時候只需要申請一個節點,然后連接起來即可;

其次,list 底層的迭代器實現也跟 stringvector 不一樣,它們兩個的迭代器可以說是原生指針,但是 list 的迭代器是要讓節點指向下一個節點,所以底層實現也不一樣;例如我們想讓迭代器 it,往后迭代,就是 ++it,但是底層的實現卻不是真的讓節點++,因為它們的空間不是連續的,所以我們要把 list 迭代器封裝成一個類。

首先我們先創建一個自己的命名空間,把 list 節點的類,list 迭代器的類,list 類都放進去;

1. list 節點類

list 節點類如下,因為是雙向鏈表,所以應該有一個數據,兩個指針;

		namespace Young{// 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 迭代器類

首先我們先定義一個類模板,其參數有三個,分別是類型類型的引用(const 和 非const)類型的指針(const 和 非const)

為什么要定義三個模板參數呢,因為考慮到 const 迭代器const 迭代器和普通迭代器不是同一個類,不能直接在 iterator 前直接加 const,如 const iterator ,這不是 const 迭代器,因為這里的 const 修飾的是迭代器本身,就是迭代器本身不能修改,但是我們期望的是迭代器本身可以被修改,如 it++、++it,只是期望迭代器指向的內容不能被修改,如 *it = 10、it->10

這就類比 const T*T* constconst T*const 是修飾指向的內容不能被修改,而 T* constconst 修飾的是指針本身不能被修改;而我們需要實現的 const 迭代器 是要滿足第一種的,所以 list普通迭代器const 迭代器 是兩個完全不一樣的類,應該寫成兩個類,但是我們可以通過增加兩個模板參數 類型的引用(const 和 非const)類型的指針(const 和 非const) 來復用普通迭代器,具體實現如下:

		// 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){}}

首先我們先將節點類起別名為 Node,再將自己的類起別名為 self;迭代器本身也是一個指針,只是它內部實現不一樣,所以我們需要一個 _node 節點的指針,構造函數實例化一個節點的指針,比如說 list<int>::iterator it = lt.begin();,這里的 it 就會調構造函數,實例化一個 lt.begin() 節點的指針,其實 lt.begin() 就是指向頭節點的指針。

接著我們重載一些迭代器常用的運算符:

(1)前置++

就是讓迭代器往后迭代,具體的實現就是讓節點的指針指向下一個節點:

			// 前置 ++self& operator++(){_node = _node->_next;return *this;}

(2)后置++

跟前置++的區別就是,后置++需要拷貝,返回++以前的迭代器,所以一般都不用后置++;

			// 后置 ++self operator++(int){self tmp(*this);_node = _node->_next;return tmp;}

(3)前置- -、后置- -

前置- -、后置- - 與 ++ 的區別就是, - -返回上一個節點的迭代器;

			// 前置 --self& operator--(){_node = _node->_prev;return *this;}// 后置--self operator--(int){self tmp(*this);_node = _node->_prev;return tmp;}

(4)!= 和 == 運算符重載

!= 運算符重載就是比較它們的節點是否相等;== 運算符就相反;

			// != 運算符重載   iterator it != lt.begin();bool operator!=(const self& s){return s._node != _node;}// == 運算符重載   iterator it == lt.begin();bool operator==(const self& s) {return s._node == _node;}

(5)* 解引用重載 和 -> 重載

解引用重載-> 重載 就是改變迭代器指向內容的兩個運算符,所以我們定義的三個模板參數,就在這里起作用了;比如我們實例化的模板參數是 const 迭代器__list_iterator<T, const T&, const T*>,這里的 const T& 就是 Refconst T* 就是 Ptr,這里就可以直接用 Ref (解引用重載)和 Ptr(箭頭重載) 作返回值;

如果是 非const 迭代器__list_iterator<T, T&, T*>T& 就是 RefT* 就是 Ptr;所以就可以根據它們的類型返回對應的迭代器類型,就不需要我們自己寫兩個迭代器的類了。

			// * 解引用重載Ref operator*(){return _node->_data;}// -> 重載Ptr operator->(){return &_node->_data;}

解引用-> 重載的使用:

假設 list 里面存的類型是一個自定義類型,這個自定義類型中有兩個成員變量,那么我們在使用 解引用-> 重載的時候,應該訪問哪一個呢?這時候就需要我們指定訪問了,如下代碼:

		struct AA{AA(int a1 = 0, int a2 = 0):_a1(a1), _a2(a2){}int _a1;int _a2;};void test4(){Young::list<AA> lt;lt.push_back(AA(1, 1));lt.push_back(AA(2, 2));lt.push_back(AA(3, 3));Young::list<AA>::iterator it = lt.begin();while (it != lt.end()){// 使用解引用//cout << (*it)._a1<<" "<<(*it)._a2 << endl;//使用 ->cout << it->_a1 << " " << it->_a2 << endl;++it;}cout << endl;}

上面的 cout << it->_a1 << " " << it->_a2 << endl; 調用了->重載,實際上是 cout << it.operator->()->_a1 << " " << it.operator->()->_a2 << endl;,本來應該是有兩個 -> 的,即 it->->_a1 但是這樣寫可讀性不好,所以編譯器特殊處理,省略了一個 ->

3. list 類

list 類首先將 const 迭代器和非 const 迭代器類型起別名為 const_iteratoriterator ;成員變量有 _head 哨兵位節點和 _size 記錄鏈表的長度,如下:

		// list 類template <class T>class list{public:typedef list_node<T> Node;typedef __list_iterator<T, T&, T*> iterator;typedef __list_iterator<T, const T&, const T*> const_iterator;private:Node* _head;size_t _size;};

(1)迭代器

注意,begin() 是哨兵位的下一個節點,end() 是哨兵位節點。

begin()end() 返回的類型也是一個迭代器,這里 iterator(_head->_next) 是調用迭代器類的構造函數,構造一個節點的指針返回;也可以寫成 _head->_next,因為支持隱式類型的轉換;

			// 非 const 迭代器iterator begin(){return iterator(_head->_next);}iterator end(){return iterator(_head);}// const 迭代器const const_iterator begin() const{return const_iterator(_head->_next);}const const_iterator end() const{return const_iterator(_head);}

(2)修改相關的接口

swap()

交換鏈表數據,需要借助標準庫的 swap 函數實現:

			// 交換鏈表數據void swap(list<T>& lt){std::swap(_head, lt._head);std::swap(_size, lt._size);}

insert()

pos 迭代器插入節點;新開一個節點,然后插入指定迭代器的位置,連接好 prevcur 的位置即可;因為 list 的底層結構為帶頭結點的雙向循環鏈表,因此在 list 中進行插入時是不會導致 list 的迭代器失效的;

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

erase()

刪除 pos 迭代器位置的節點;在刪除時迭代器會失效,并且失效的只是指向被刪除節點的迭代器,其他迭代器不會受到影響,所以 erase() 函數執行后,it 所指向的節點已被刪除,因此 it 無效,在下一次使用 it 時,必須先給其賦值;

			// 刪除節點iterator erase(iterator pos){Node* prev = pos._node->_prev;Node* next = pos._node->_next;prev->_next = next;next->_prev = prev;delete pos._node;pos._node->_next = pos._node->_prev = nullptr;--_size;return next;}

push_back、push_front、pop_back、pop_front

只需要復用 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());}

clear()

清空鏈表數據,刪除除了哨兵位的節點即可;

			// 清空鏈表數據void clear(){iterator it = begin();while (it != end()){it = erase(it);}}		

以上修改接口配合迭代器的使用如下圖:

在這里插入圖片描述

在這里插入圖片描述

(3)空鏈表初始化

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

(4)構造函數

構造函數只需要創建一個哨兵位即可;

			// 構造函數list(){empty_init();}

(5)拷貝構造函數

拷貝構造函數直接初始化,然后插入數據即可;

			// 拷貝構造函數 -- lt2(lt1)list(const list<T>& lt){empty_init();for (auto e : lt){push_back(e);}}

(6)賦值運算符重載

現代寫法,傳參的時候調用拷貝構造,然后交換數據即可;

			// 賦值運算符重載 -- lt2 = lt1list<T>& operator=(list<T> lt){swap(lt);return *this;}

(7)析構函數

清空鏈表數據之后再釋放哨兵位的節點即可;

			// 析構函數~list(){clear();delete _head;_head = nullptr;}

4. 打印容器的接口

(1)打印鏈表整型的接口

vectorlist 這些容器都沒有重載流插入運算符,所以我們可以自己實現一個打印的接口函數;我們先來實現一下打印鏈表整型的接口:

		// 打印鏈表 -- 只能針對 int 類型void print_list(const list<int>& lt){list<int>::const_iterator it = lt.begin();while (it != lt.end()){//*it = 10; errorcout << *it << " ";++it;}cout << endl;}

此接口可以打印鏈表的數據,但是只能針對 int 類型,我們可以對它進行改造一下,使用模板。

(2)打印 list 的接口

我們學了模板,就可以利用模板實現泛型編程,將類型改為模板的泛型,即可打印 list 中的不同類型,如下:

		// 打印鏈表 -- 只能打印 list 容器template<typename T>void print_list(const list<T>& lt){typename list<T>::const_iterator it = lt.begin();while (it != lt.end()){//*it = 10; errorcout << *it << " ";++it;}cout << endl;}

這里的模板參數使用了 typedef 關鍵字,這里必須使用 typedef 關鍵字,而且在指定類域前還要加上 typedef 關鍵字,如 typename list<T>::const_iterator it = lt.begin();;因為在模板還沒有進行實例化的時候, const_iterator 就到 list<T> 的類域中尋找類型,此時類中還沒有實例化參數 T,所以編譯器分不清它是類型還是靜態變量,不能去 list<T> 里面找,所以在前面加 typedef 關鍵字就說明它是個類型,編譯器在等 list<T> 實例化后,再去類里面去取根據類型去取類型。

但是上面的接口還是不夠完美,要是我想打印 vector 呢?那還是不能打印出來,所以我們可以實現一個專門打印容器的接口;

(3)打印容器的接口

我們使用模板參數代表容器,讓編譯器到指定容器去取它的迭代器即可;

		// 打印容器 -- 能打印各種容器template<typename container>void print_container(const container& con){typename container::const_iterator cit = con.begin();while (cit != con.end()){cout << *cit << " ";++cit;}cout << endl;}

使用如下圖:

在這里插入圖片描述

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

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

相關文章

css3新增屬性

文章目錄 css3新增屬性box-shadowborder-radius設置橢圓 position: sticky;漸變背景線性漸變可重復的漸變背景 徑向漸變可重復的漸變背景 過渡分屬性 動畫關鍵幀與transition的關系demo 變形平移使用 旋轉使用 其他使用立體效果perspective元素位于3D空間還是平面中 縮放變形的…

tornado在模板中遍歷二維數組

要在Tornado模板中遍歷一個二維數組&#xff0c;你可以使用Tornado的模板語法來實現迭代和顯示數組中的每個元素。 以下是一個示例&#xff0c;演示如何在Tornado模板中遍歷和顯示二維數組的內容&#xff1a; template.html: <!DOCTYPE html> <html> <head&g…

小米分享 | 解密面試題:網易面試如何回答“創建線程有哪幾種方式?”

大家好&#xff0c;我是你們的小米&#xff01;今天要和大家一起探討一個在技術面試中常見的問題&#xff1a;創建線程有哪幾種方式&#xff1f;這可是個經典面試題哦&#xff01;不過別擔心&#xff0c;小米在這里為你詳細解析&#xff0c;幫你輕松應對&#xff0c;讓你在面試…

深度學習在MRI運動校正中的應用綜述

運動是MRI中的主要挑戰之一。由于MR信號是在頻率空間中獲取的&#xff0c;因此除了其他MR成像偽影之外&#xff0c;成像對象的任何運動都會導致重建圖像中產生偽影。深度學習被提出用于重建過程的幾個階段的運動校正。廣泛的MR采集序列、感興趣的解剖結構和病理學以及運動模式&…

用dcker極簡打包java.jar鏡像并啟動

用dcker極簡打包java.jar鏡像并啟動 一、本地打包好jar包 二、新建文件夾&#xff0c;將步驟1中的jar包拷貝到文件夾下 三、同目錄下新建Dockerfile ## 基礎鏡像&#xff0c;這里用的是openjdk:8 FROM openjdk:8## 將步驟一打包好的jar包 拷貝到鏡像的 跟目錄下[目錄可以自定義…

Oracle字段長度不足位數補零

Oracle字段長度不足位數補零 有時候從數據庫中取出的月份值是1&#xff0c;而不是01&#xff0c;該怎么辦呢 SELECTLPAD( CODE_MONTH, 2, 0 ) FROMtb_cube_TY001 WHERECODE_BM_MEATYPE TY20 AND code_measure MYLX01 AND code_month <> ~ AND CODE_ENTITY 01A AND…

【實戰】十一、看板頁面及任務組頁面開發(二) —— React17+React Hook+TS4 最佳實踐,仿 Jira 企業級項目(二十四)

文章目錄 一、項目起航&#xff1a;項目初始化與配置二、React 與 Hook 應用&#xff1a;實現項目列表三、TS 應用&#xff1a;JS神助攻 - 強類型四、JWT、用戶認證與異步請求五、CSS 其實很簡單 - 用 CSS-in-JS 添加樣式六、用戶體驗優化 - 加載中和錯誤狀態處理七、Hook&…

“深入探索JVM:解析Java虛擬機的工作原理與優化“

標題&#xff1a;深入探索JVM&#xff1a;解析Java虛擬機的工作原理與優化 摘要&#xff1a;本篇博客將深入探討Java虛擬機&#xff08;JVM&#xff09;的工作原理以及如何優化JVM的性能。我們將介紹JVM的組成部分、類加載過程、內存管理、垃圾回收機制以及常見的性能優化技術…

記一次線上OOM事故

OOM 問題 linux內核有個機制叫OOM killer(Out-Of-Memory killer)&#xff0c;當系統需要申請內存卻申請不到時&#xff0c;OOM killer會檢查當前進程中占用內存最大者&#xff0c;將其殺掉&#xff0c;騰出內存保障系統正常運行。 一般而言&#xff0c;一個應用的內存逐漸增加&…

__setitem__和__getitem和__delitem__

目錄 一、__setitem__ 二、__getitem__ 三、__delitem__與__delattr__ python從小白到總裁完整教程目錄:https://blog.csdn.net/weixin_67859959/article/details/129328397?spm1001.2014.3001.5502 class Foo:def __init__(self, name):self.name namedef __getitem__(s…

mekefile 編寫

mekefile 編寫 參考 Linux下使用 autoconf和automake 自動構建 項目 make file文件 makefile 中加入shell語句 if shell 參考 foo.bak: foo.barecho "foo"if [ -d "~/Dropbox" ]; then echo "Dir exists"; fi Or foo.bak: foo.barecho &quo…

文心一言 VS 訊飛星火 VS chatgpt (76)-- 算法導論7.3 1題

一、如果用go語言&#xff0c;為什么我們分析隨機化算法的期望運行時間&#xff0c;而不是其最壞運行時間呢? 文心一言&#xff1a; 在分析算法的性能時&#xff0c;期望運行時間是一個重要的指標&#xff0c;它描述了算法在平均情況下的表現。期望運行時間考慮了所有可能的…

管理類聯考——邏輯——真題篇——按知識分類——匯總篇——二、論證邏輯——支持加強——第二節——分類2——正面支持

文章目錄 第二節 支持加強-分類2-正面支持題-支持加強-分類2-正面支持-表達“確實如此”題-支持加強-分類2-正面支持-表達“確實如此”-正面支持不直觀:轉為削弱反面更直觀真題(2010-38)-支持加強-分類2-正面支持真題(2018-29)-支持加強-分類2-正面支持-支持關鍵詞真題(…

musl libc ldso 動態加載研究筆記:02

前言 本篇繼續研究 musl libc ldso 的動態加載過程中遇到的關鍵性的概念&#xff1a;到底要加載ELF 文件的哪些內容到 內存 當前如果遇到 ELF 動態加載&#xff0c;當前系統需要有【文件系統】&#xff0c;并且有較大的內存&#xff0c;因為 ELF 文件是無法直接運行的&#xf…

IDEA兩種方法修改生成的jar包名字

方法一&#xff1a; 直接修改pom文件中的如下部分 <artifactId>excelreport</artifactId> <version>0.0.1-SNAPSHOT</version> <name>excelreport</name> <description>excelreport</description> 修改完成后&#xff0c;點…

SpringBoot3集成Kafka

標簽&#xff1a;Kafka3.Kafka-eagle3&#xff1b; 一、簡介 Kafka是一個開源的分布式事件流平臺&#xff0c;常被用于高性能數據管道、流分析、數據集成和關鍵任務應用&#xff0c;基于Zookeeper協調的處理平臺&#xff0c;也是一種消息系統&#xff0c;具有更好的吞吐量、內…

跟著美團學設計模式(感處)

讀了著篇文章之后發現真的是&#xff0c;你的思想&#xff0c;你的思維是真的比比你擁有什么技術要強的。 注 開閉原則 開閉原則&#xff08;Open-Closed Principle&#xff09;是面向對象設計中的基本原則之一&#xff0c;它的定義是&#xff1a;一個軟件實體應該對擴展開放…

python生成旗幟--比如美國國旗生成

目錄 1、解釋說明&#xff1a; 2、使用示例&#xff1a; 3、注意事項&#xff1a; 1、解釋說明&#xff1a; 在Python中&#xff0c;生成國旗可以通過使用第三方庫或者自定義函數來實現。通常&#xff0c;我們可以使用Pillow庫來處理圖像&#xff0c;以及使用matplotlib庫來…

python爬蟲7:實戰1

python爬蟲7&#xff1a;實戰1 前言 ? python實現網絡爬蟲非常簡單&#xff0c;只需要掌握一定的基礎知識和一定的庫使用技巧即可。本系列目標旨在梳理相關知識點&#xff0c;方便以后復習。 申明 ? 本系列所涉及的代碼僅用于個人研究與討論&#xff0c;并不會對網站產生不好…

carla中lka實現(二)

前言&#xff1a; 首先計算之前檢測出來的車道線的中線與輸入圖像的中線進行計算距離&#xff0c;&#xff0c;并設置不同的閾值對于不同的方向進行相關的調整。 一、車輛中心線 一般而言將攝像頭架設在車輛的正中心軸上&#xff0c;所獲得的圖像的中間線極為車輛的中心。 …