【C++初階】從零開始模擬實現vector(含迭代器失效詳細講解)

目錄

1、基本結構

1.1成員變量

?1.2無參構造函數

1.3有參構造函數

preserve()的實現

代碼部分:?

push_back()的實現?

代碼部分:

代碼部分:

1.4拷貝構造函數

代碼部分:

1.5支持{}初始化的構造函數

?代碼部分:

?1.6支持迭代器區間初始化的構造函數

代碼部分:

?1.7支持一次插入多個相同元素的構造函數

代碼部分:

resize()函數的實現:改變大小?

代碼部分:

1.8析構函數

代碼部分:?

2、插入和刪除

2.1尾插

代碼部分:?

2.2在任意位置插入

代碼部分:?

?2.3尾刪

代碼部分:

2.4在任意位置刪除?

思路:

代碼部分:?

3、運算符的重載

3.1賦值運算符的重載

1、傳統寫法

代碼部分:?

2、現代寫法

swap()的實現

代碼部分:?

3.2operator []的實現

代碼實現:

4、迭代器失效問題(重點)

4.1迭代器失效的原因和后果

4.2常見迭代器失效的操作

4.2.1擴容相關操作

4.2.2刪除相關操作

總結:


1、基本結構

1.1成員變量

vector類有三個成員變量,_start指向數據塊起始位置,_finish指向有效數據塊的結尾位置,_end_of_storage指向有效空間容量的結束位置

在這里給它們去缺省值

namespace yyc
{template <class T>class vector{public:typedef T* iterator;private://成員變量初始化為空iterator _start = nullptr;//指向數據塊起始位置iterator _finish = nullptr;//指向有效數據的尾iterator _end_of_storage = nullptr;//指向空間容量的尾};
}

?1.2無參構造函數

這里的無參構造函數,也可以不寫初始化列表(因為成員變量給的缺省值會自動給初始化列表)

vector()
{} 
//vector() 
//: _start(nullptr)
//, _finish(nullptr)
//, _endOfStorage(nullptr) 
//{}

1.3有參構造函數

初始化一個給定大小的vector,并使用默認值填充

在實現構造之前,我們可以實現一個擴容成員函數reserve()和一個尾插成員函數push_back()

preserve()的實現

實現reserve的過程還可以實現size()capacity()函數

在實現preserve時要注意的是:

1、這里用到的并不是memcpy來進行數據拷貝,而是用for循環來一個一個賦值

這是因為在面對T為string類型時,使用memcpy會導致淺拷貝

2、在擴容后要進行迭代器的更新,防止出現迭代器失效的問題

代碼部分:?
		size_t size() const{return _finish - _start;}size_t capacity() const{return _end_of_storage - _start;}void reserve(size_t n){if (n > capacity()){size_t oldsize = size();//將原有空間中有效數據長度記錄下來T* tmp = new T[n];//memcpy(tmp,_start,sizeof(T)*size())//memcpy進行的是淺拷貝,會導致迭代器失效for (size_t i = 0; i < oldsize; ++i){tmp[i] = _start[i];//將原有空間中的資源轉到新開的空間}delete[] _start;//銷毀舊空間_start = tmp;_finish = _start + oldsize;//finish重新指向原本要指向的位置_end_of_storage = _start + n;}}

push_back()的實現?

代碼部分:
		void push_back(const T& x){if (_finish == _end_of_storage){reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = x;++_finish;}

然后我們直接復用這兩個成員函數就可以實現初始化

但要實現范圍for就必須先實現兩個返回首尾位置的迭代器

代碼部分:

		typedef T* iterator;typedef const T* const_iterator;//實現范圍foriterator begin(){return _start;}iterator end(){return _finish;}const_iterator begin() const{return _start;}

通過一系列的復用來實現構造函數,這樣就極大的提高代碼的簡潔性以及易于修改?

代碼部分:
		vector(const vector<T>& x){reserve(x.size());for (auto a : x){push_back(a);}}

1.4拷貝構造函數

拷貝構造函數實現的思路和有參構造函數差不多,也是進行復用來實現

代碼部分:
		vector(const vector<T>& x){reserve(x.size());for (auto a : x){push_back(a);}}

1.5支持{}初始化的構造函數

?這是C++版本才新出的一種初始化方式

它支持vector<int> v = {1,2,3,4,5};這種方式初始化

?代碼部分:

		vector(initializer_list<T> il){reserve(il.size());for (auto& a : il){push_back(a);}}

初始化實現:?

	yyc::vector<int> v1 = { 1,2,3,4,5,6 };//一種一種隱式類型轉換yyc::vector<int> v2({ 1,2,3,4,5,6 });//調用對應構造

?1.6支持迭代器區間初始化的構造函數

代碼部分:
		template <class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);++first;}}

這種初始化方式就可以實現

yyc::vector<int> v1(10,1);
yyc::vector<int> v2(v1.begin(), v1.begin() + 3);

?1.7支持一次插入多個相同元素的構造函數

可以實現初始化一次插入多個元素

代碼部分:
		vector(int n, const T& val = T()):_start(new T[n]),_finish(_start+n),_end_of_storage(_finish){//while (n--)//{//	push_back(val);//}for (int i = 0; i < n; ++i){_start[i] = val;}}

初始化實現:?

yyc::vector<int> v1(10,1);

但還有一種簡潔的方式實現該構造函數

1、一種是復用reserve()和push_back()l來實現

2、直接復用resize()函數

resize()函數的實現:改變大小?

代碼部分:
		void resize(size_t n, const T& val = T())//這里的的val給缺省值{if (n <= size())//當n<=size()時,會進行數據的刪除{_finish =_start;return;}else if (n > capacity())//當n>capacity()時,就進行擴容{reserve(n);//復用寫好的reserve()//多出來的空間插入數據while (_finish != _start + n){//從原本的_finish處進行尾插*_finish = val;++_finish;}}}

復用resize(),通過n的大小來進行數據的插入?

vector(size_t n, const T& val = T())  //  這里我們實現了兩個函數  
{										//  一個是size_t 的 n  一個是intresize(n, val);						// 可以有效的匹配不同調用場景
}										// 防止負數隱式轉換
vector(int n, const T& val = T())
{resize(n, val);
}

1.8析構函數

代碼部分:?
		~vector(){delete[] _start;_start = _finish = _end_of_storage = nullptr;}

2、插入和刪除

2.1尾插

代碼部分:?
		void push_back(const T& x){if (_finish == _end_of_storage){reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = x;++_finish;}

2.2在任意位置插入

?注意事項:

當插入過程中需要擴容時,就需要進行迭代器的更新,防止迭代器失效

代碼部分:?
		void insert(iterator pos, const T& val){assert(pos >= _start);assert(pos <= _finish);if (_finish == _end_of_storage){//保留pos位置,防止擴容后迭代器失效size_t len = pos - _start;reserve(capacity() == 0 ? 4 : capacity() * 2);pos = _start + len;//更新迭代器}iterator it = _finish - 1;while (it >= pos){*(it + 1) = *it;//挪動數據--it;}*pos = val;//在指定位置插入數據++_finish;}

?2.3尾刪

代碼部分:
		void pop_back(){assert(_finish > _start);--_finish;}

2.4在任意位置刪除?

思路:

將pos+1后面的數據一個一個往前移,進行數據的覆蓋

最后返回刪除位置的迭代器,以此來更新迭代器

代碼部分:?
		iterator erase(iterator pos)//刪除指定位置數據{assert(pos >= _start);assert(pos <= _finish);//用后面的數據覆蓋前面的數據iterator it = pos + 1;while (it < _finish){*(it - 1) = *it;it++;}_finish--;return pos;}

3、運算符的重載

3.1賦值運算符的重載

1、傳統寫法

· 先進行原有空間的釋放和迭代器的置空

· 在復用reserve()和push_back()來進行數據的拷貝

代碼部分:?
		vector<T>& operator=(const vector<T>& v){if (*this != v){delete[] _start;_start = _finish = _end_of_storage = nullptr;reserve(v.size());for (auto& x : v){push_back(x);}}return *this;}

2、現代寫法

?現代寫法就比較簡潔,不需要我們來做復雜的工作,,只要實現好了swap()拷貝構造函數就可以實現

swap()的實現
		void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_end_of_storage, v._end_of_storage);}
代碼部分:?
		void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_end_of_storage, v._end_of_storage);}vector<T>& operator=(vector<T>& v){swap(v);return *this;}

3.2operator []的實現

代碼實現:

		T& operator[](size_t i){assert(i < size());return _start[i];}const T& operator[](size_t i) const{assert(i < size());return _start[i];}

4、迭代器失效問題(重點)

4.1迭代器失效的原因和后果

使用迭代器的主要原因就是為了不暴露容器的底層結構,不需要關心底層結構,讓使用更簡單

它的底層實際上就是一個指針或對指針進行了封裝,比如:vector的迭代器就是原生態指針T*?

因此,迭代器失效就是迭代器底層對應指針所指向的空間被銷毀了,而使用一塊已經被釋放的空間,造成的后果是程序崩潰(即如果繼續使用已經失效的迭代器,程序可能會崩潰)

4.2常見迭代器失效的操作

4.2.1擴容相關操作

當?vector?需要擴展容量時,會分配新的內存空間并將原有元素搬移到新的位置。此時,所有的迭代器將會失效,它的本質其實就是野指針

· reserve()

· resize()

· insert()

· push_back()

· assign()

#include <iostream>
using namespace std;
#include <vector>
int main()
{vector<int> v{1,2,3,4,5,6};auto it = v.begin();// 將有效元素個數增加到100個,多出的位置使用8填充,操作期間底層會擴容// v.resize(100, 8);// reserve的作用就是改變擴容大小但不改變有效元素個數,操作期間可能會引起底層容量改變// v.reserve(100);// 插入元素期間,可能會引起擴容,而導致原空間被釋放// v.insert(v.begin(), 0);// v.push_back(8);// 給vector重新賦值,可能會引起底層容量改變v.assign(100, 8);/*出錯原因:以上操作,都有可能會導致vector擴容,也就是說vector底層原理舊空間被釋放掉而在打印時,it還使用的是釋放之間的舊空間,在對it迭代器操作時實際操作的是一塊已經被釋放的空間,而引起代碼運行時崩潰。解決方式:在以上操作完成之后,如果想要繼續通過迭代器操作vector中的元素,只需給it重新賦值即可。*/while(it != v.end()){cout<< *it << " " ;++it;}cout<<endl;return 0;
}    //  程序運行會崩掉

因此,在進行了擴容后默認迭代器就失效了?

4.2.2刪除相關操作

刪除操作會使指向被刪除元素及其后續元素的迭代器失效。

#include<iostream>
using namespace std;
#include <vector>
int main()
{int a[] = { 1, 2, 3, 4 };vector<int> v(a, a + sizeof(a) / sizeof(int));// 使用find查找3所在位置的iteratorvector<int>::iterator pos = find(v.begin(), v.end(), 3);// 刪除pos位置的數據,導致pos迭代器失效。v.erase(pos);cout << *pos << endl; // 此處會導致非法訪問return 0;
}

erase刪除pos位置元素后,pos位置之后的元素會往前搬移,沒有導致底層空間的改變,理論上講迭代器不應該會失效,但是:如果pos剛好是最后一個元素,刪完之后pos剛好是end的位置,而end位置是沒有元素的,那么pos就失效了。因此刪除vector中任意位置上元素時,vs就認為該位置迭代器失效了

int main()
{xxx::vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);for (const auto& e : v1){cout << e << " ";}cout << endl;//  迭代器失效  //刪除所有的偶數:在不同系統下實現結果各有所不同/*auto it = v1.begin();while (it != v1.end()){if (*it % 2 == 0){v1.erase(it);}++it;}*///刪除所有的偶數/*auto it = v1.begin();//這種方法在linux下可以進行,但在VS下無法實現//vs會強制檢查while (it != v1.end()){if (*it % 2 == 0){v1.erase(it);}else{++it;}}*/               
//  前面兩種刪除方式都會使迭代器失效  it已經不是指向該指向的位置//該方法適用于多平臺	// 迭代器完全體  auto it = v1.begin();while (it != v1.end()){if (*it % 2 == 0){it = v1.erase(it);  // 刪除之后重新賦值迭代器,這也是為什么earse要返回迭代器的原因}else{++it;}}for (const auto& e : v1){cout << e << " ";}cout << endl;return 0;
}

總結:

在進行了擴容或刪除操作后,參與其中的迭代器就默認失效了,解決辦法就是對迭代器進行更新

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

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

相關文章

Java實習生面試題(2025.3.23 be)

一、v-if與v-show的區別 v-show 和 v-if 都是 Vue 中的條件渲染指令&#xff0c;它們的主要區別在于渲染策略&#xff1a;v-if 會根據條件決定是否編譯元素&#xff0c;而 v-show 則始終編譯元素&#xff0c;只是通過改變 CSS 的 display 屬性來控制顯示與隱藏。 二、mybatis-…

stm32標準庫開發需要的基本文件結構

使用STM32標準庫&#xff08;STM32 Standard Peripheral Library&#xff0c;SPL&#xff09;開發時&#xff0c;項目中必須包含一些必要的文件&#xff0c;這些文件確保項目能夠正常運行并與MCU硬件交互。以下詳細說明&#xff1a; 一、標準庫核心文件夾說明 使用標準庫開發S…

學生管理系統(需求文檔)

需求&#xff1a; 采取控制臺的方式去書寫學生管理系統 分析&#xff1a; 初始菜單&#xff1a; “----------歡迎來到java學生管理系統----------” “1:添加學生” “2&#xff1a;刪除學生” “3&#xff1a;修改學生” “4&#xff1a;查詢學生” “5&#xff1a;…

Java算法OJ(13)雙指針

目錄 1.前言 2.正文 2.1快樂數 2.2盛最多水的容器 2.3有效的三角形的個數 2.4和為s的兩個數 2.5三數之和 2.6四數之和 3.小結 1.前言 哈嘍大家好吖&#xff0c;今天繼續加練算法題目&#xff0c;一共六道雙指針&#xff0c;希望能對大家有所幫助&#xff0c;廢話不多…

SpringBoot分布式定時任務實戰:告別重復執行的煩惱

場景再現&#xff1a;你剛部署完基于SpringBoot的集群服務&#xff0c;凌晨3點突然收到監控告警——優惠券發放量超出預算兩倍&#xff01;檢查日志發現&#xff0c;兩個節點同時執行了定時任務。這種分布式環境下的定時任務難題&#xff0c;該如何徹底解決&#xff1f; 本文將…

MySQL 設置允許遠程連接完整指南:安全與效率并重

一、為什么需要遠程連接MySQL&#xff1f; 在分布式系統架構中&#xff0c;應用程序與數據庫往往部署在不同服務器。例如&#xff1a; Web服務器&#xff08;如NginxPHP&#xff09;需要連接獨立的MySQL數據庫數據分析師通過BI工具直連生產庫多服務器集群間的數據同步 但直接…

系統架構書單推薦(一)領域驅動設計與面向對象

本文主要是個人在學習過程中所涉獵的一些經典書籍&#xff0c;有些已經閱讀完&#xff0c;有些還在閱讀中。于我而言&#xff0c;希望追求軟件系統設計相關的原則、方法、思想、本質的東西&#xff0c;并希望通過不斷的學習、實踐和積累&#xff0c;提升自身的知識和認知。希望…

動態規劃-01背包

兜兜轉轉了半天&#xff0c;發現還是Carl寫的好。 看過動態規劃-基礎的讀者&#xff0c;大概都清楚。 動態規劃是將大問題&#xff0c;分解成子問題。并將子問題的解儲存下來&#xff0c;避免重復計算。 而背包問題&#xff0c;就是動態規劃延申出來的一個大類。 而01背包&…

使用VS2022編譯CEF

前提 選擇編譯的版本 CEF自動編譯&#xff0c;在這里可以看到最新的穩定版和Beta版。 從這里得出&#xff0c;最新的穩定版是134.0.6998.118&#xff0c;對應的cef branch是6998。通過這個信息可以在Build requirements查到相關的軟件配置信息。 這里主要看Windows下的編譯要…

C++20:玩轉 string 的 starts_with 和 ends_with

文章目錄 一、背景與動機二、string::starts_with 和 string::ends_with&#xff08;一&#xff09;語法與功能&#xff08;二&#xff09;使用示例1\. 判斷字符串開頭2\. 判斷字符串結尾 &#xff08;三&#xff09;優勢 三、string_view::starts_with 和 string_view::ends_w…

智能飛鳥監測 守護高壓線安全

飛鳥檢測新紀元&#xff1a;視覺分析技術的革新應用 在現代化社會中&#xff0c;飛鳥檢測成為了多個領域不可忽視的重要環節。無論是高壓線下的安全監測、工廠內的生產秩序維護&#xff0c;還是農業區的作物保護&#xff0c;飛鳥檢測都扮演著至關重要的角色。傳統的人工檢測方…

ADC噪聲全面分析 -04- 有效噪聲帶寬簡介

為什么要了解ENBW&#xff1f; 了解模數轉換器 (ADC) 噪聲可能具有挑戰性&#xff0c;即使對于最有經驗的模擬設計人員也是如此。 Delta-sigma ADC 具有量化和熱噪聲的組合&#xff0c;這取決于 ADC 的分辨率、參考電壓和輸出數據速率 (ODR)。 在系統級別&#xff0c;額外的信…

STM32單片機uCOS-Ⅲ系統10 內存管理

目錄 一、內存管理的基本概念 二、內存管理的運作機制 三、內存管理的應用場景 四、內存管理函數接口講解 1、內存池創建函數 OSMemCreate() 2、內存申請函數 OSMemGet() 3、內存釋放函數 OSMemPut() 五、實現 一、內存管理的基本概念 在計算系統中&#xff0c;變量、中…

考研課程安排(自用)

文章目錄 408數據結構&#xff08;王道&#xff09;計算機組成原理&#xff08;王道&#xff09;操作系統&#xff08;王道&#xff09;計算機網絡&#xff08;湖科大版&#xff09; 數學一高等數學&#xff08;微積分&#xff09;線性代數和概率論 408 數據結構&#xff08;王…

ultraiso制作u盤啟動

UltraISO制作U盤啟動盤的方法 UltraISO是一款功能強大的工具&#xff0c;可以幫助用戶將ISO鏡像文件寫入U盤&#xff0c;從而制作成可啟動的系統安裝盤。以下是詳細的步驟和注意事項&#xff1a; 1. ?準備工作? ?硬件準備?&#xff1a;一個容量至少為8GB的U盤&#xff0…

C語言-發布訂閱模式詳解與實踐

文章目錄 C語言發布訂閱模式詳解與實踐1. 什么是發布訂閱模式&#xff1f;2. 為什么需要發布訂閱模式&#xff1f;3. 實際應用場景4. 代碼實現4.1 UML 關系圖4.2 頭文件 (pubsub.h)4.3 實現文件 (pubsub.c)4.4 使用示例 (main.c) 5. 代碼分析5.1 關鍵設計點5.2 實現特點 6. 編譯…

藍橋杯2023年第十四屆省賽真題-異或和之差

題目來自DOTCPP&#xff1a; 思路&#xff1a; 什么是異或和&#xff1f; ①題目要求我們選擇兩個不相交的子段&#xff0c;我們可以枚舉一個分界線i&#xff0c;子段1在 i 的左邊&#xff0c; 子段2在 i 的右邊&#xff0c;分別找到子段1和子段2的最大值、最小值。 ②怎么確…

Linux作業2——有關文件系統權限的練習

1、創建/www目錄&#xff0c;在/www目錄下新建name和https目錄&#xff0c;在name和https目錄下分別創建一個index.html文件&#xff0c;name下面的index.html文件中包含當前主機的主機名&#xff0c;https目錄下的index.html文件中包含當前主機的ip地址。 #創建/www目錄&…

leeCode 70. 爬樓梯

假設你正在爬樓梯。需要 n 階你才能到達樓頂。 每次你可以爬 1 或 2 個臺階。你有多少種不同的方法可以爬到樓頂呢&#xff1f; 示例 1&#xff1a; 輸入&#xff1a;n 2 輸出&#xff1a;2 解釋&#xff1a;有兩種方法可以爬到樓頂。 1. 1 階 1 階 2. 2 階 示例 2&#x…

算法題(105):小貓爬山

審題&#xff1a; 本題需要我們找出將n個小貓放在有限重的纜車上運下山所需的最小纜車數 時間復雜度分析&#xff1a;本題的數據量小于等于18&#xff0c;所以我們在做好剪枝的前提下可以使用深度優先搜索解題 思路&#xff1a; 方法一&#xff1a;dfs 搜索策略&#xff1a;將小…