yo!這里是STL::list類簡單模擬實現

目錄

前言

重要接口實現

框架

默認成員函數

迭代器(重點)

1.引言

2.list迭代器類實現?

3.list類中調用實現?

增刪查改

后記


前言

? ? ? ? 我們知道,stl中的vector對應數據結構中的順序表,string類對應字符串,而今天要講的list類對應帶頭雙向鏈表,并不是對應單鏈表,帶頭雙向鏈表的基本操作在數據結構課程中已經學過,所以今天即將要講的常見接口并不是重點,重點是list的迭代器的實現。

????????我們也知道,string、vector的迭代器就是原生指針,如果使用原生指針可以實現list的迭代器嗎?答案是不行,因為list的數據并不是一塊連續的存儲空間,無法像指針那樣取訪問元素,但是為了保持所有容器迭代器的使用一致,我們如何實現list的迭代器才能像原生指針那樣通過++、--去控制,這里就體現出了封裝的重要性,快往下看看吧!

重要接口實現

  • 框架

? ? ? ? 通過下方代碼可以看出,實現了一個節點類模板存儲節點,一個鏈表類模板存儲鏈表,

①使用類模板是因為存儲元素可以自由指定,不是像以前一樣通過typedef固定了每個鏈表的元素類型;

②節點類模板使用struct,而不使用class,是因為struct的默認權限是public,鏈表類模板可以自由訪問其成員變量,而class默認權限是private,當然用class指定public權限也可以,

節點類模板中通過構造函數初始化元素,鏈表類模板成員是一個節點指針,可以在構造函數中申請一個節點充當頭節點。

代碼:

template <class T>
struct ListNode
{//構造函數用來創節點ListNode(const T& x = T()):_data(x), _prev(nullptr), _next(nullptr){}T _data;ListNode<T>* _prev;ListNode<T>* _next;
};template <class T>
class List
{typedef ListNode<T> Lnode;  //作用:下面寫ListNode<T>較麻煩,typedef一下,使用Lnode較方便public://...private:Lnode* _head;
};
  • 默認成員函數

? ? ? ? 因為在所有構造函數前都需要先初始化成員變量(為頭節點申請空間并將左右指針置空),所以封裝了一個函數empty_init在每個構造函數前直接調用,

? ? ? ? 所有默認成員函數的實現與string、vector中的如出一轍,不太理解的可以參考一下之前的文章,不是重點這里不再贅述。

代碼:

	//創建并初始化頭節點,放于構造函數的最前面void empty_init(){_head = new Lnode();_head->_next = _head->_prev = _head;}//構造函數List(){empty_init();}//普通拷貝構造函數List(const List& L)   //注意:類外必須是List<類型名>,類里可以是List,但建議List<T>{empty_init();auto it = L.begin();while (it != L.end()){push_back(*it);it++;}}void swap(List<T>& L){std::swap(_head, L._head);}//傳迭代器區間構造函數template <class InputIterator>  List(InputIterator first, InputIterator last){empty_init();while (first != last){push_back(*first);++first;}}//拷貝構造函數//現代寫法List(const List<T>& L){empty_init();List<T> tmp(L.begin(), L.end());swap(tmp);}//賦值運算符重載//現代寫法List<T>& operator=(const List<T>& L){List<T> tmp(L.begin(), L.end());swap(tmp);return *this;}//更狠的現代寫法List<T>& operator=(List<T> L)   //直接傳一個拷貝過來,相當于上面的tmp,函數結束自動釋放{swap(L);return *this;}//清除除了頭節點之外所有的節點void clear(){auto it = begin();while (it != end()){it = erase(it);}}//析構函數~List(){clear();_head->_next = _head->_prev = nullptr;delete _head;_head = nullptr;}
  • 迭代器(重點)

1.引言

? ? ? ? 還記得vector、string的迭代器實現嗎?typedef T* iterator;? ?typedef const T* const_iterator;? ?,只是原生指針對不對,然后typedef一下,就可以使用了,因為指針可以在一塊連續的地址空間++或--訪問元素,但是鏈表中的節點指針++、--訪問元素可以嗎,答案是不可以,但為了保持迭代器使用一致,就應該遇到鏈表的迭代器使用++、--也可以達到一樣的效果,因此就想到了操作符重載,將++、--、*? 重載成我們希望達到的效果,而實現操作符重載就必須將其封裝成一個類。

? ? ? ? 從引言中就可以看出list與之前學過的容器迭代器實現的不同之處,list迭代器是通過封裝成類實現,但迭代器有兩種(暫時先不提反向迭代器),一種普通迭代器,一種const迭代器,兩種迭代器的實現應該大致內容都一樣,小部分不一樣(比如,const迭代器的解引用應該返回const不可類型的變量),那我們應該先寫好普通迭代器的實現,再復制粘貼成const迭代器然后修修改改嗎?漏!大漏特漏!接觸過模板這個概念之后,應該可以想到這里用到模板。

2.list迭代器類實現?

1)框架?

? ? ? ? 見下方代碼,?實現list的迭代器這個__list_iterator類模板,參數列表中的T、Ref、Ptr分別是數據類型、此類型的引用、此類型的指針(比如,T是int,Ref就是int&,Ptr就是int*)(為什么需要指針參數在操作符->重載處有說明),填入的參數不同就是不同的類,這里list的迭代器需要兩個類,一個普通迭代器的類,一個const迭代器的類,在list類實現中去定義即可。

? ? ? ? 針對list迭代器類模板的實現,成員變量是節點的指針,而構造函數則是傳入一個節點指針可初始化一個list迭代器,不需要自己提供析構函數。

代碼:

template <class T, class Ref, class Ptr>
struct __list_iterator
{//注意:這兩個typedef只是因為ListNode<T>、__list_iterator<T, Ref, Ptr>很麻煩寫,所以簡化一下,也方便理解typedef ListNode<T> Lnode;typedef __list_iterator<T, Ref, Ptr> iterator;//構造函數__list_iterator(Lnode* p):_node(p){}//...Lnode* _node;   //鏈表中的迭代器表示節點的指針
};

2)?關系運算符重載

? ? ? ? 判斷兩個迭代器相不相等即判斷作為成員變量的結點指針是不是同一個指針變量,很容易理解,加上const是無論普通list對象還是const?list對象都可以調用。

代碼:

    bool operator==(const iterator& it) const{return _node == it._node;}bool operator!=(const iterator& it) const{return !(*this == it);}

?3)運算符++、--重載

? ? ? ? 針對list類,迭代器的++就是訪問下一個節點的迭代器,--是訪問上一個節點的迭代器,再注意好前置與后置的實現即可,不是很難。

代碼:

    iterator& operator++(){_node = _node->_next;return *this;}iterator operator++(int){iterator tmp(_node);_node = _node->_next;return tmp;}iterator& operator--(){_node = _node->_prev;return *this;}iterator operator--(int){iterator tmp(_node);_node = _node->_prev;return tmp;}

4)操作符*重載

? ? ? ? list類中的迭代器解引用就是訪問此節點的數據,并且返回引用類型,即可操作其中的值,普通迭代器的Ref是普通引用類型,即可讀可寫,const迭代器的Ref是const引用類型,只可讀不可寫。

? ? ? ? 注意:不需要將此成員函數設置成const類型,就像本作者初學時所疑惑的,如果Ref是const T&,不應該對應const成員函數(Ref operator*() const)嗎?其實不然,list類的迭代器使用了類模板,參數不同就是不同的類,直接將兩種迭代器分開了,如果是普通迭代器,Ref就會傳進來T&,調用解引用重載時返回引用,可讀可寫,如果是const迭代器,Ref就會傳進來const T&,調用解引用重載時返回const引用,只可讀不可寫。

代碼:

	Ref operator*(){return _node->_data;}

?5)操作符->重載

? ? ? ? 正常情況下,操作符->可以解引用結構體指針再取其成員,那對于底層是節點指針的list迭代器,->就是對迭代器解引用再取其成員,所以針對_data如果是個自定義類型,那么->就可以取其成員,比如,_data是個自定義類型POS,有兩個成員,一個x,一個y,那么it->x,it->y表示迭代器取的POS中的x、y,如下圖測試,

????????仔細觀察->操作符重載的實現,it->x應該寫成it->->x才是對的,因為it->返回自定義類型指針,再->x返回其成員,這里其實編譯器做了處理,省略了一個->,提高了可讀性。

? ? ? ? 這里的Ptr就是數據類型的指針變量,將其地址返回,與引用形式一樣,需要從類模板參數列表輸入。

代碼:

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

?測試:

3.list類中調用實現?

? ? ? ? 在實現完__list_iterator這個list類迭代器類模板之后,通過在list類中輸入不同的模板參數定義不同的類,這里需要普通迭代器類和const迭代器類,如下代碼,begin()是返回首元節點的迭代器,而end()是返回最后一個元素節點的下一個位置迭代器,即頭節點迭代器。

?代碼:

    typedef __list_iterator<T, T&, T*> iterator;  //普通迭代器類typedef __list_iterator<T, const T&, const T*> const_iterator;   //const迭代器類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);}
  • 增刪查改

? ? ? ? 在理解了list的迭代器實現之后,對list的增刪改查想必是游刃有余,這里實現一下基本的插入刪除操作,結合之前在數據結構中的知識,insert、erase的實現應該可以很快寫出,值得注意的是,insert返回新插入節點的迭代器,erase返回所刪節點的下一位置的迭代器,而尾插、尾刪、頭插、頭刪直接復用即可。

代碼:

	iterator insert(iterator pos, const T& x){Lnode* newNode = new Lnode(x);pos._node->_prev->_next = newNode;newNode->_prev = pos._node->_prev;newNode->_next = pos._node;pos._node->_prev = newNode;return iterator(newNode);  //返回插入位置的迭代器}//尾插void push_back(const T& x){insert(end(), x);}//頭插void push_front(const T& x){insert(begin(), x);}iterator erase(iterator pos){assert(pos != end());Lnode* tmp = pos._node->_next;pos._node->_prev->_next = pos._node->_next;pos._node->_next->_prev = pos._node->_prev;delete pos._node;return iterator(tmp);}//尾刪void pop_back(){erase(--end());}//頭刪void pop_front(){erase(begin());}

后記

? ? ? ? 在list類的實現中,默認成員函數、操作符重載以及增刪查改已不再是重點,重點是掌握迭代器的實現,因為與string、vector中的迭代器實現不同,也沒有那么簡單,上面總結的很清楚,不懂的可以私我或者寫在評論區,加油,拜拜!


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

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

相關文章

Unity C# 之 Http 獲取網頁的 html 數據,并去掉 html 格式等相關信息

Unity C# 之 Http 獲取網頁的 html 數據&#xff0c;并去掉 html 格式等相關信息 目錄 Unity C# 之 Http 獲取網頁的 html 數據&#xff0c;并去掉 html 格式等相關信息 一、簡單介紹 二、實現原理 三、注意事項 四、效果預覽 五、關鍵代碼 一、簡單介紹 Unity中的一些知…

Linux網絡基礎(中)

目錄&#xff1a; 再談“協議” HTTP協議 認識URL&#xff1a; urlnecode和urldecode HTTP協議格式&#xff1a; HTTP的方法&#xff1a; 簡易HTTP服務器&#xff1a; 傳輸層 再談端口號&#xff1a; 端口號范圍劃分&#xff1a; netstat&#xff1a; pidof&…

Mybatis三劍客(一)在springboot中手動使用Mybatis

1、pom.xml中引入依賴【注意根據自己的spring boot版本選擇對應的mysql和mybatis版本】 <dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>org.mybatis…

Ubantu安裝Docker(完整詳細)

先在官網上查看對應的版本:官網 然后根據官方文檔一步一步跟著操作即可 必要準備 要成功安裝Docker Desktop&#xff0c;必須&#xff1a; 滿足系統要求 擁有64位版本的Ubuntu Jammy Jellyfish 22.04&#xff08;LTS&#xff09;或Ubuntu Impish Indri 21.10。 Docker Deskto…

Redis基礎命令大全

這里寫目錄標題 第一章、Redis 命令大全1.1&#xff09;通用命令語法&#xff1a;ping語法&#xff1a;dbsize語法&#xff1a;select db語法&#xff1a;flushdb語法&#xff1a;exit 或 quit語法&#xff1a;redis-cli 1.2&#xff09;Redis 的 Key 的操作命令語法&#xff1…

【Java基礎】- JVM之Dump文件詳解

Java基礎 - JVM之Dump文件詳解 文章目錄 Java基礎 - JVM之Dump文件詳解一、什么是Dump三、為什么需要Dump分析思路 四、Dump記錄哪些內容4.1 Java dump 文件的格式和內容段格式行格式 4.2 常用分類heap dump和thread dumpheap dumpthread dump 五、如何生產Dump文件5.1 獲取hea…

Elasticsearch之kibana相關命令

1.中文分詞器相關命令 2.拼音分詞器相關命令

服務器之LNMP

lnmp的構成 L&#xff1a;linux系統,操作系統。 N&#xff1a;nginx網站服務&#xff0c;前端,提供前端的靜態頁面服務。同時具有代理,轉發的作用。 轉發&#xff1a;主要是轉發后端請求。轉發到PHP。nginx沒有處理動態資源的功能,他有可以支持轉發動態請求的模塊。 M&…

正則表達式練習

正則表達式練習 工具目的代碼運行結果 工具 pycharm 目的 https://www.77xsw.cc/fenlei/1_1/&#xff1a;第一頁的網址 https://www.77xsw.cc/fenlei/1_2/&#xff1a;第二頁的網址 ... https://www.77xsw.cc/fenlei/1_10/&#xff1a;第十頁的網址 代碼 import requests im…

REDIS主從配置

目錄 前言 一、概述 二、作用 三、缺點 四、redis主從復制的流程 五、搭建redis主從復制 總結 前言 Redis的主從配置是指在Redis集群中&#xff0c;將一個Redis節點配置為主節點&#xff08;master&#xff09;&#xff0c;其他節點配置為從節點&#xff08;slave&#xff09;…

【數據結構?堆】堆排序(理論基礎)

堆的定義  ? 堆是一個完全二叉樹   –所有葉子在同一層或者兩個連續層   –最后一層的結點占據盡量左的位置  ? 堆性質   –為空, 或者最小元素在根上   –兩棵子樹也是堆 存儲方式  ? 最小堆的元素保存在heap[1..hs]內   – 根在heap[1]   –K的左兒子是2k,…

細胞——求細胞數量 C++詳解

細胞——求細胞數量 C詳解 求細胞數量題目描述輸入格式輸出格式樣例樣例輸入樣例輸出 提示數據規模與約定 解法代碼 求細胞數量 題目描述 一矩形陣列由數字 0 0 0 到 9 9 9 組成&#xff0c;數字 1 1 1 到 9 9 9 代表細胞&#xff0c;細胞的定義為沿細胞數字上下左右若還…

vue3中使用component動態組件常見問題

一. 在vue3中使用動態組件問題警告處理 1. 代碼如下 <template><div v-for"(item, index) in navItems" :key"index"><component :is"item.component" :key"item.gameId"></component></div> </te…

nbcio-boot升級springboot、mybatis-plus和JSQLParser后的LocalDateTime日期json問題

升級后&#xff0c;運行顯示項目的時候出現下面錯誤 2023-08-12 10:57:39.174 [http-nio-8080-exec-3] [1;31mERROR[0;39m [36morg.jeecg.common.aspect.DictAspect:104[0;39m - json解析失敗Java 8 date/time type java.time.LocalDateTime not supported by default: add Mo…

Leetcode-每日一題【劍指 Offer 26. 樹的子結構】

題目 輸入兩棵二叉樹A和B&#xff0c;判斷B是不是A的子結構。(約定空樹不是任意一個樹的子結構) B是A的子結構&#xff0c; 即 A中有出現和B相同的結構和節點值。 例如: 給定的樹 A: 3 / \ 4 5 / \ 1 2 給定的樹 B&#xff1a; 4 / 1 返回 true&#xff0…

ffmpeg ts列表合并為mp4

操作系統&#xff1a;ubuntu 注意事項&#xff1a; 1.ts文件順序必須正確&#xff0c;也就是下一幀的dst和pst要比上一幀的大&#xff0c;否則會報錯 2.codecpar->codec_tag要設置為0&#xff0c;否則報錯Tag [27][0][0][0] incompatible with output codec id ‘27’ (avc1…

docker版jxTMS使用指南:使用jxTMS采集數據之二

本文是如何用jxTMS進行數據采集的第二部分&#xff0c;整個系列的文章請查看&#xff1a;docker版jxTMS使用指南&#xff1a;4.4版升級內容 docker版本的使用&#xff0c;請查看&#xff1a;docker版jxTMS使用指南 4.0版jxTMS的說明&#xff0c;請查看&#xff1a;4.0版升級內…

Vue + MapBox快速搭建

一、說明&#xff1a; 1.mapbox-gl自2.0版本開始不再開源&#xff0c;需要用戶在官網申請key使用。 2.maplibre GL JS是一個開源庫&#xff0c;它起源于 mapbox-gl-js 的開源分支。該庫的初始版本&#xff08;1.x&#xff09;旨在替代Mapbox的OSS版本。簡單來說maplibre是mapb…

異步場景加載詳解

異步場景加載詳解 介紹 異步場景加載是一種在Unity中加載場景的方式&#xff0c;它允許在加載過程中執行其他操作&#xff0c;并提供了加載進度的反饋。通過異步加載&#xff0c;可以避免加載大型場景時的卡頓現象&#xff0c;提高游戲的流暢性和用戶體驗。 方法 在Unity中…

C++——缺省參數

缺省參數的定義 缺省參數是聲明或定義函數時為函數的參數指定一個缺省值。在調用該函數的時候&#xff0c;如果沒有指定實參&#xff0c;則采用該形參的缺省值&#xff0c;否則使用指定的實參。 void Func(int a 0) {cout << a << endl; } int main() { Func()…