【C++】vector容器實現

目錄

一、vector的成員變量

二、vector手動實現

(1)構造

(2)析構

(3)尾插

(4)擴容

(5)[ ]運算符重載

5.1 迭代器的實現:

(6)尾刪

(7)插入

(8)刪除

(9)迭代器失效

9.1 reserve擴容迭代器失效

9.2 insert后迭代器失效

9.3 erase迭代器失效

(10)resize初始化

(11)普通拷貝構造

(12)=運算符重載拷貝構造

三、其他構造方法

(一)initializer_list初始化

(二)迭代器初始化

四、動態二維數組


vector的開始也代表STL學習的開始。接下來將講解如何手動實現部分vector常用接口。

希望在看下面文章之前已經對string類的實現比較了解,或者看過我之前描述string類實現的文章,這樣對理解vector會比較友好。

正文:

一、vector的成員變量

vector學習時一定要學會查看文檔https://cplusplus.com/reference/vector/vector/,vector在實際中非常的重要,我們熟悉常見的接口就可以。

下面就不帶大家看整個vector源碼了,只截取了它成員變量在底層如何聲明的小部分原碼

下圖可知vector原碼核心成員變量就三個迭代器,迭代器跳轉過去其實也是個typedef原生指針(和string類似)所以三個變量就是三個指針。再去看構造函數,它把三個指針初始化成空(不展示),那么之前數組都有大小,vector沒有直接給出就進一步去看原碼怎么算大小和容量的(不展示)。下面實現就模仿庫的形式也定三個迭代器變量。

二、vector手動實現

類模版的聲明和定義一般寫在同一個文件下。創建一個vector.h(類實現過程)和test.cpp(接口測試),為了防止出現命名沖突,我外層套了一層命名空間zss,大家練習過程中可以不套。

下面是模擬原碼實現的vector基礎框架,成員就三個迭代器,vector本身是個順序結構_start代表整個數組,_finish指向最后一個數據的下一個位置,_end_of_storage表示整個數組空間大小。

(1)構造

由于我們在聲明的時候三個指針都給了缺省值nullptr,只要給缺省值了,用戶不傳參初始化列表會直接用缺省值初始化三個成員變量,所以我們提供的構造可以什么都沒有。雖然什么都沒有但是不能直接刪掉,因為如果實現其他構造函數,要初始化三個指針還是得先走初始化列表。

(2)析構

到時候插入數據是需要自己手動new申請一段數組空間的,自己申請的空間析構也要對應匹配delete[ ]清理數據,并把指針置為空。

(3)尾插

push_back尾插之前必須確保有足夠大的空間進行尾插數據,如果沒有就進行空間的擴容,而要獲取到數組空間大小用capacity()函數返回,順便把size也求一下。由于擴容這一操作后面會經常使用所以封裝成一個函數。

(4)擴容

reserve擴容采取深拷貝的思想,先開一段足夠大小的tmp新空間,memcpy將舊空間數據一個字節一個字節拷給新空間,再將舊空間_start釋放掉,把新空間tmp賦值給_start(reserve函數調用結束tmp自動銷毀)最后更新一下_finish和_end_of_storage數據。

這里要注意的點是size()大小問題,大家看我還要再定個oldsize 變量存size()大小可能感到很奇怪,上面不是已經實現了size()函數,已經可以獲取到數組大小了嗎,怎么還多此一舉存一下。

這里涉及到拷貝后_finish大小報錯問題:

一開始我們計算的size()大小是還沒替換空間前size的大小,替換空間后_start已經不再是原來空間而size卻還是原來空間,兩個不同空間的指針相加是會報錯的,所以要用oldsize 變量存size()大小,這樣_finish = _start + oldsize加的大小才是正確的。

(5)[ ]運算符重載

在數組的遍歷中,最常用的就是[ ]、迭代器和范圍for。內置類型下標遍歷轉換為解引用,自定義類型要實現下標遍歷得重載運算法。有時候打印的數據具有常性不允許改變需要調用const版本,所以下面也實現了個const版本。

[ ]的實現:

5.1 迭代器的實現:

迭代器的實現也有分普通類型和const類型,const類型迭代器并不是在迭代器前面加個const就行,這是限制迭代器本身不能改變,而我們要的是數據不能改變,所以要typedef提供一個新的迭代器類型。

迭代器的行為是模擬指針并不代表它就是指針

使用:

范圍for遍歷:
范圍for看起來很高大上,很便利,其實底層是依靠迭代器的實現,只要實現了迭代器范圍for直接用。所以變層看似有[ ]、迭代器和范圍for三種遍歷方式,實際上只有[ ]和迭代器。

(6)尾刪

pop_back尾刪很簡單,想象一下數組尾刪是不是只要改變數組個數就好,同理vector尾刪--_finish就行,到時候插入也是從_finish位置重新覆蓋插入。

(7)插入

insert在任意位置插入一個元素,只要和插入有關都先考慮內存夠不夠問題,不夠擴容。

如果是在中間插入是要把pos位置及之后數據向后移動一位再進行插入,移動利用迭代器更高效。

(8)刪除

erase刪除pos位置元素,同樣利用迭代器將數據移動覆蓋,最后--_finish個數。

要注意的是erase返回值還是一個迭代器,這是為了防止迭代器失效問題。

(9)迭代器失效

迭代器的主要作用就是讓算法能夠不用關心底層數據結構,其底層實際就是一個指針,或者是對指針進行了封裝,比如:vector的迭代器就是原生態指針T* 。因此迭代器失效,實際就是迭代器底層對應指針所指向的空間被銷毀了,而使用一塊已經被釋放的空間,造成的后果是程序崩潰(即如果繼續使用已經失效的迭代器,程序可能會崩潰)。 對于vector可能會導致其迭代器失效的操作有:會引起其底層空間改變的操作,都有可能是迭代器失效,比如:resize、reserve、insert、assign、push_back等。

9.1 reserve擴容迭代器失效

第一張圖代碼是一開始正常邏輯沒修改過的代碼,reserve內部會開新空間然后拷貝賦值銷毀,通過調試看到,原本指向上面空間的_start指針指向了下面新開的空間,這些都很合理,但是insert在pos位置插入數據,這個pos還指向被銷毀了的原空間,當下面while循環時it在新空間內向左移動找舊pos是永遠找不到的。

遇到這種問題,要更新迭代器讓pos指向新空間的原始位置,怎么計算就是:一開始要算出pos距離首元素大小,等擴容完了更新,請看第二張代碼圖。

9.2 insert后迭代器失效

VS默認insert后迭代器全部失效,這條沒有解決辦法,唯一的辦法就是:別用!!!

9.3 erase迭代器失效

通過以下部分代碼演示:刪除數組中的所有偶數,在刪除的過程中++it會使判斷條件被跳過

圖二:erase內部刪除pos位置元素,后面元素會自動向前移一位,這時3覆蓋2,但++it使3被跳過判斷了。

圖三:如果偶數在最后一個呢?_finish覆蓋4,但又++it使結束條件直接跳過,野指針訪問

圖一? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 圖二? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?圖三

以上情況也是更新一下迭代器就行,erase有返回值只要重新接收返回值就OK

(10)resize初始化

resize的改變會影響數組的數據個數,數據不夠就插入,太多就刪數據,不夠還空間不足就擴容+插入

第二個參數不是必須給的,當用戶沒給第二個參數時匿名對象初始化;int就是0,指針就nullptr

(11)普通拷貝構造

有兩種寫法v2(v1)和v2=v1,這兩種都可以考慮復用寫過的代碼實現

(12)=運算符重載拷貝構造

現代寫法:

一種很妙的寫法,通過參數傳遞的淺拷貝思想和swap實現。v里面的數據等函數運行結束自動銷毀,而我們想要的已經通過*this返回了。

三、其他構造方法

(一)initializer_list初始化

下面寫法大家在刷題時是不是經常看到,這是C++11的一種構造方式,專門用于支持花括號 {}?初始化語法。它提供了一種統一的方式來初始化各種容器和自定義類型。

用法:

實現:復用reserve和push_back

(二)迭代器初始化

迭代器初始化引入了一個新概念,類模板的成員函數也可以是模板,這樣不用固定整個類的迭代器,任意類型的迭代器都可以初始化了。

實現:還是復用了push_back,使整體代碼更簡潔

四、動態二維數組

vector<vector<int>>它本質上是一個二維動態數組,模版變量可以是任何類型的指針,當然也可以是vector<int>*指針類型。

想象?vector<vector<int>>?就像一組可以伸縮的抽屜柜:

外層 vector:這是一個大柜子,里面可以放很多抽屜(每個抽屜就是一個?vector<int>)

每個內層 vector:每個抽屜里可以放很多整數(int),而且每個抽屜的大小可以不一樣

案例:力扣——楊輝三角

與靜態數組對比的好處:

靜態數組(如?int arr[3][4]):大小固定、每行長度必須相同、內存連續

vector<vector<int>>:大小可變、每行長度可以不同、內存不一定完全連續(外層連續,內層各自連續)

完整vector手動實現代碼如下:

#pragma once
#include<iostream>
#include<assert.h>
using namespace std;namespace zss
{template<class T>class vector{public:typedef T* iterator;//const 迭代器typedef const T* const_iterator;size_t capacity()const{return _end_of_storage - _start;}size_t size()const{return _finish - _start;}//迭代器iterator begin(){return _start;}iterator end(){return _finish;}//const迭代器const_iterator begin()const{return _start;}const_iterator end()const{return _finish;}//1.構造//這玩意什么都不寫也不能因為在聲明給缺省值就刪掉//因為自己實現了拷貝構造或者其他構造刪掉就不是自動生成了//可能我們自己也會寫其他構造initialize_list,允許用 { } 初始化對象vector(){}//13.initializer_list初始化vector(initializer_list<T> il){//走初始化列表把三個指針初始化了然后直接開空間尾插reserve(il.size());for (auto& e : il){push_back(e);}}//14.迭代器初始化//類模板的成函數模板,這樣不用類的迭代器,任意迭代器都可以template<class InputIierator>vector(InputIierator first, InputIierator end){while (first != end){push_back(*first);++first;}}//2.析構~vector(){delete[] _start;_start = _finish = _end_of_storage = nullptr;}//3.尾插void push_back(const T& x){if (_finish == _end_of_storage){//擴容//沒有容量就通過capacity()函數獲取一個reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = x;++_finish;}//4.擴容void reserve(size_t n){//可能reserve被單獨調用所以再判斷一次容量if (n > capacity()){//這里原本_start、_finish、_end_of_storage都指向同一塊空間//拷貝了_start指向新的空間,你用兩個不同空間指針相減不可能得到size//所以更新前保存一下size()size_t oldsize = size();T* tmp = new T[n];//memcpy(tmp, _start, sizeof(T) * oldsize);//自定義類型string不能用memcpy,會指向同一塊空間釋放多次for (int i = 0; i < oldsize; i++){tmp[i] = _start[i];}delete[] _start;_start = tmp;_finish = _start + oldsize;_end_of_storage = _start + n;}}//5.[]T& operator[](size_t i){assert(i < size());return _start[i];}//6.const[]const T& operator[](size_t i)const{assert(i < size());return _start[i];}//7.尾刪void pop_back(){assert(_finish > _start);--_finish;}//8.插入void  insert(iterator pos, const T& x){assert(pos >= _start);assert(pos <= _finish);//擴容if (_finish == _end_of_storage){size_t len = pos - _start;reserve(capacity() == 0 ? 4 : capacity() * 2);pos = _start + len;}//插入,因為是迭代器不存在沒有小于0的情況iterator it = _finish - 1;while (it >= pos){*(it + 1) = *it;--it;}*pos = x;++_finish;}//9.刪除元素iterator erase(iterator pos){assert(pos >= _start);assert(pos <= _finish);iterator it = pos + 1;while (it < _finish){*(it - 1) = *it;++it;}_finish--;return pos;}//10.迭代器失效處理//11.resizevoid resize(size_t n, const T& val = T()){if (n < size())_finish = _start + n;else{reserve(n);while (_finish != _start + n){*_finish = val;++_finish;}}}//12.拷貝構造v2(v1)vector(const vector<T>& v){reserve(v.size());for (auto& e : v){push_back(e);}}//v2=v1//vector<T>& operator=(const vector<T>& v)//{//	if (this != &v)//	{//		//如果有值先釋放掉//		delete[] _start;//		_start = _finish = _end_of_storage = nullptr;//		//開新的空間//		reserve(v.size());//		//拷貝數據//		for (auto& e : v)//		{//			push_back(e);//		}//	}//	return *this;//}//現代寫法void swap(vector<T>& v){std::swap(v._start, _start);std::swap(v._finish, _finish);std::swap(v._end_of_storage,_end_of_storage);}vector<T>& operator=(vector<T> v){swap(v);return *this;}private://模擬STL庫的實現iterator _start=nullptr;iterator _finish=nullptr;iterator _end_of_storage=nullptr;};
}

下次繼續一起學習,感謝觀看~

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

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

相關文章

PostgreSQL日常維護

目錄 一、PostgreSQL 概述 二、基本使用 &#xff08;一&#xff09;登錄數據庫 &#xff08;二&#xff09;數據庫操作 1. 列出數據庫 2. 創建數據庫 3. 刪除數據庫 4. 切換數據庫 5. 查看數據庫大小 &#xff08;三&#xff09;數據表操作 1. 列出表 2. 創建表 …

廣東省省考備考(第十六天5.21)—言語:語句排序題(聽課后強化)

錯題 解析 對比選項&#xff0c;確定首句。①句介紹目前人類可以利用一些技術手段進入元宇宙&#xff0c;憑借網絡重新定義自己&#xff0c;體驗一種全新的生活&#xff0c;②句介紹對于多數人來說&#xff0c;首先要弄清楚什么是元宇宙&#xff0c;③句介紹元宇宙是指超越現實…

高并發架構設計之限流

一、引言 再強大的系統&#xff0c;也怕流量短事件內集中爆發&#xff0c;就像銀行怕擠兌一樣&#xff0c;所以&#xff0c;高并發另一個必不可少的模塊就是限流。限流是一種通過控制請求的速率或數量來保護系統免受過載的技術。流控的精髓是限制單位時間內的請求量&#xff0…

視頻監控聯網系統GB28181協議中設備控制流程詳解

文章目錄 9.3 設備控制9.3.1 基本要求9.3.2 命令流程9.3.2.1 無應答命令流程 9.3.3 協議接口9.3.3.1 請求命令9.3.3.2 應答命令 智聯視頻超融合平臺介紹 9.3 設備控制 9.3.1 基本要求 控制滿足以下基本要求&#xff1a; a) 源設備向目標設備發送控制命令&#xff0c;控制命令…

深入剖析原型模式:原理、實現與應用實踐

在軟件開發的世界里,設計模式如同建筑師手中的藍圖,為復雜系統的構建提供了行之有效的解決方案。其中,原型模式(Prototype Pattern)作為創建型設計模式的重要一員,以其獨特的對象創建方式,在提高代碼復用性、增強系統靈活性等方面發揮著關鍵作用。本文將深入剖析原型模式…

圖繪Linux:基礎指令脈絡閣

目錄 Linux命令行介紹 目錄操作 ls 目錄所含文件信息 ls 常用選項 pwd 在那個目錄下 cd 進入目錄 mkdir 創建目錄 文件操作 touch 創建普通文件 echo向文件寫入 cat 輸出文件內容 cp 拷貝文件/目錄 mv剪切重命名 rm 刪除文件/目錄 查找 * 匹配符 man 查找指令 …

數據分析 —— 數據預處理

一、什么是數據預處理 數據預處理&#xff08;Data Preprocessing&#xff09;是數據分析和機器學習中至關重要的步驟&#xff0c;旨在將原始數據轉換為更高質量、更適合分析或建模的形式。由于真實世界的數據通常存在不完整、不一致、噪聲或冗余等問題&#xff0c;預處理可以…

【Redis】哨兵(Sentinel)機制

文章目錄 1. Redis Sentinel的概念1.1 基本概念1.2 引出高可用 2. Redis Sentinel的部署&#xff08;基于docker&#xff09;2.1 部署2.2 驗證2.3 選舉流程 Redis 的主從復制模式下&#xff0c;?旦主節點由于故障不能提供服務&#xff0c;需要人工進行主從切換&#xff0c;同時…

初識Linux · 五種IO模型和非阻塞IO

目錄 前言&#xff1a; 五種IO模型 什么是IO IO模型 非阻塞IO 前言&#xff1a; 前文我們已經將網絡的基本原理介紹完了&#xff0c;都是通過圍繞TCP/IP四層協議&#xff0c;將應用層&#xff0c;傳輸層&#xff0c;網絡層&#xff0c;數據鏈路層全部介紹完畢&#xff0c…

Node.js 24發布:性能與安全雙提升

在科技的迅速發展中&#xff0c;Node.js作為一個備受青睞的開源跨平臺Java運行環境&#xff0c;近日迎來了其24.0版本的正式發布。此次更新不僅承諾提升性能和安全性&#xff0c;還為開發者提供了更為順暢的開發體驗&#xff0c;值得我們深入探討。 Node.js 24.0的最大亮點之一…

SLAM文獻之-SuperOdometry: Lightweight LiDAR-inertial Odometry and Mapping

《Super Odometry: IMU-centric LiDAR-Visual-Inertial Estimator for Challenging Environments》是一篇旨在增強 SLAM 系統在惡劣環境下魯棒性的工作&#xff0c;尤其關注塵霧、煙霧等遮擋條件下的魯棒估計。下面從算法原理、公式推導、創新點和應用場景四個方面進行詳細解析…

指令燒錄ORIN NANO操作系統

1 概述 模組為ORIN NANO 4GB版本 Ubuntu系統為18.04虛擬機 說明&#xff1a;刷機過程會有重新連接USB的操作&#xff0c;燒寫過程需要注意虛擬機提示&#xff0c;官方不建議使用虛擬機&#xff0c;建議直接使用ubuntu操作系統的機器。 2 下載燒錄所需文件 進入到下載網址&am…

游戲引擎學習第287天:加入brain邏輯

Blackboard&#xff1a;動態控制類似蛇的多節實體 我們目前正在處理一個關于實體系統如何以組合方式進行管理的問題。具體來說&#xff0c;是在游戲中實現多個實體可以共同或獨立行動的機制。例如&#xff0c;我們的主角擁有兩個實體組成部分&#xff0c;一個是身體&#xff0…

QML定時器Timer和線程任務WorkerScript

定時器 Timer 屬性 interval: 事件間隔毫秒repeat: 多次執行&#xff0c;默認只執行一次running: 定時器啟動triggeredOnStart: 定時器啟動時立刻觸發一次事件 信號 triggered(): 定時時間到&#xff0c;觸發此信號 方法 restart(): 重啟定時器start(): 啟動定時器stop(): 停止…

Linux中的域名解析服務器

一、DNS&#xff08;域名系統&#xff09;詳解 1. 核心功能與特點 特性說明核心作用將域名&#xff08;如 www.example.com&#xff09;轉換為 IP 地址&#xff08;如 192.168.1.1&#xff09;&#xff0c;實現人類可讀地址與機器可讀地址的映射。端口與協議- 默認端口&#…

Springboot2

1、搭建環境 2、配置文件 application.properties application.yml 3、springboot接收請求 springspringmvc 接收請求 響應數據 4、springboot集成jdbc spring-boot-starter-jdbc.jar JdbcTemplate(update|query) 5、springboot自動裝配原理&#xff08;重點&#x…

【課堂筆記】核方法和Mercer定理

文章目錄 Kernal引入定義Mercer定理描述有限情形證明一般情形證明 Kernal 引入 在實際數據中常常遇到不可線性分割的情況&#xff0c;此時通常需要將其映射到高維空間中&#xff0c;使其變得線性可分。例如二維數據&#xff1a; 通過映射 ? ( x 1 , x 2 ) ( x 1 2 , 2 x 1…

談談未來iOS越獄或巨魔是否會消失

2024年10月的預測&#xff0c;先說結論&#xff1a; 巨魔iOS17.1消失概率為99%。 因為巨魔強依賴的漏洞就是一個簽名漏洞&#xff0c;攻擊面有限又經過2輪修復&#xff0c;第3次出現漏洞的概率極低。而越獄的話由于系統組件和服務較多&#xff0c;所以出現漏洞概率高攻擊面多&…

根據當前日期計算并選取上一個月和上一個季度的日期范圍,用于日期控件的快捷選取功能

1.選擇月份范圍 代碼如下&#xff1a; <el-date-picker v-model"value" type"monthrange" align"right" unlink-panels range-separator"至"start-placeholder"開始月份" end-placeholder"結束月份" :picker-…

用戶棧的高效解析邏輯

一、背景 在之前的博客 內核邏輯里抓取用戶棧的幾種方法-CSDN博客 里&#xff0c;介紹了使用內核邏輯進行用戶棧的函數地址的抓取邏輯&#xff0c;但是并沒有涉及如何解析出函數符號的邏輯。 就如perf工具一樣&#xff0c;它也是分為兩個步驟&#xff0c;一個步驟是內核態抓取…