🔥 本文專欄:c++
🌸作者主頁:努力努力再努力wz
💪 今日博客勵志語錄:
種子破土時從不問‘會不會有光’,它只管生長
★★★ 本文前置知識:
模版
1.什么是vector
那么想必大家都學過順序表這個數據結構,那么順序表就是通過開辟一個連續的內存空間來存儲數據,在c語言期間,我們如果要自己去實現一個順序表,那么首先就需要定義一個動態數組,然后還需要定義關于順序表的增刪改查相關操作的函數,比如insert插入函數以及erase刪除函數等等
那么假設有這么一個場景,你的程序中涉及到一個存儲int類型數據的一個順序表,那么此時你就得定義一個存儲int類型的動態數組,并且還得手寫關于該順序表的增刪改查的相關操作的函數,但是如果你的程序還涉及到一個存儲double類型的順序表,那么意味著你現在不僅得定義一個存儲double類型的動態數組,并且還要手寫一份針對于double類型數據的順序表的增刪改查相關的函數,如果說此時你的代碼還涉及到存儲char等其他類型數據的順序表的話,那么想必你肯定非常的痛苦
那么從上面這個場景,我們就能夠認識到c語言實現順序表的一個局限,那么一旦我們程序涉及到多個存儲不同數據類型的順序表,那么意味著我們得針對不同的數據類型的順序表定義多份函數,并且對于相同操作的函數來說,他們彼此之間的邏輯是相同的,唯一的區別就是處理的數據不同,所以針對存儲不同的數據類型的順序表定義多份相關的函數,不僅會導致代碼冗余,而且還不方便維護,那么至于如何解決,那么想必小伙伴們心中早已有答案,那么這個問題正是模版的應用場景
所以為了c++的vector便解決了c語言實現順序表的窘境,那么vector是一個模版類,它內部維護的就是一個順序表,并且底層采取的是動態數組的方式實現,并且關于順序表的增刪改查的相關的成員函數,那么vector類內部都有提供,并且支持擴容的邏輯,那么vector類最關鍵的就是它是一個模板類,所以我們不必要再擔心如果我們的代碼中涉及到多種不同數據類型的順序表,需要我們手動造輪子的問題
那么由于vector是模版類,那么編譯器會識別到我們代碼中創建的vector對象,然后根據其存儲的數據類型,來實例化一份存儲對應數據類型的vector類,那么對于存儲特定數據類型的順序表的維護以及增刪改查,這些內容統統都交給了vector來完成,因為其底層都有對應的實現,那么我們只需要站在巨人的肩膀上,放心使用vector類我們提供的各種成員函數即可
那么在知道了什么是vector之后,那么接下來我將會從兩個維度來帶你全面認識vector,分別是vector如何使用以及其底層的實現原理,那么廢話不多說,我們首先先來認識vector如何使用
2.vector如何使用以及其底層實現原理
那么要知道vector如何使用,那么我們就得從兩方面來認識vector,分別是vector的成員變量以及vector的成員函數,那么首先先來介紹一下vector的成員變量
1.成員變量
如果讀者學習接觸的第一個容器是string的話,那么讀者可能會習慣認為vector會采取string的成員變量的設計,也就是vector內部會維護一個指向動態數組的首元素的指針以及一個size變量以及一個capacity變量,但是vector的成員變量其實是三個迭代器,那么這三個迭代器的本質就是指針
那么這里就得注意了,從包括vector開始以及之后學習的容器比如list以及queue等等,我們就得習慣迭代器作為容器的成員變量,因為歷史原因,string的誕生是早于STL的,而STL規定了容器的成員變量以及成員函數的規范,那么對于成員變量,那么統一都是以迭代器作為成員變量,因為我們訪問STL的容器都是統一通過迭代器來進行訪問容器里面存儲的元素,其次就是容器中的成員函數,那么STL對容器里的成員函數的命名做了統一,比如返回容器中存儲有效數據的個數的函數都被命名為了size()函數,以及返回容器當前的容量則是都命名為capacity()函數等等,那么統一命名規范的好處是我們能夠熟悉的使用各個不同的容器,并且還降低了我們學習的成本
而vector內部維護了三個指針,分別是start以及finish和end_of_storage,那么從三個成員變量的名字我們就能夠猜到他們的作用了,那么這個三個指針就是用來維護vector中開辟的動態數組,其中start指向動態數組的起始位置,而finish指向動態數組的有效數據結尾的下一個位置,而end_of_storage則是指向動態數組結尾的下一個位置
那么有的讀者可能會對finish以及end_of_storage的指向有一點疑問,為什么finish和end_of_storage不直接指向有效數據的結尾以及動態數組的末尾,那么是因為vector類中遍歷一個迭代器區間,統一將這個迭代器區間認為是左閉右開[first,last)的區間,所以這里finish以及end_of_storage得各自指向末尾的下一個元素,那么他們的作用我們可以類比字符串末尾的’\0’來幫組我們理解
2.成員函數
那么知道了成員變量之后,那么接下來就來認識vector的成員函數
構造函數
那么認識一個類,首先得從構造函數說起,那么vector類中提供了多個重載版本的構造函數來滿足用戶不同的初始化需求,其中就包括無參的構造函數:
vector();
那么無參的構造函數的所做的內容就是將vector類中的三個指針給初始化為空,也就是得到一個空數組的vector對象
模擬實現:
vector():_start(nullptr), _finish(nullptr), _end_of_storage(nullptr){}
其次就是接受n個元素來初始化的構造函數:
vector (size_type n, const value_type& val = value_type());
那么這里就要注意的是,這里第二個參數也就是要初始化的元素值,這里vector的為其提供了缺省值,那么缺省值就是該數據類型的默認值,那么我們知道對于內置類型來說,那么它的默認值給0即可,但這里的元素的類型可能是自定義類型,那么對于自定義類型來說,那么它的初始值是調用無參的構造函數的對象,但是要知道vector是一個模版類,那么其中的數據類型都是被定義為了模版參數,我們不可能對模版參數來進行所謂的比較判斷,如果其等于自定義類型,那么就給0的缺省值,而如果其是自定義類型就提供一個調用無參構造的臨時對象
不能這么多的原因,首先是語法層面上不允許我們這樣做,其次就是模版是一種泛型編程,所謂的泛型編程就是忽略數據類型,而這里我們卻添加針對于數據類型的比較邏輯,這明顯也和模版的思想相違背
所以為了解決這個問題,那么c++在引入模版之后,提供了一個新的初始化方式:
myclass tmp=myclass();
那么如果tmp是內置類型,那么會將其設置為0,如果tmp是自定義類型,會提供一個調用無參的構造函數初始化的臨時對象來賦值給tmp,并且我們還可以在括號內自己設置初始值,那么我們可以寫一段簡單的代碼來熟悉這個語法:
#include<iostream>
#include<string>
int main()
{int a = int();std::cout << "a= " << a << std::endl;std::string b = std::string();std::cout << "b=" << b << std::endl;double c = double(8.7);std::cout << "c=" <<c<< std::endl;std::string d = std::string("1223");std::cout << "d=" << d << std::endl;return 0;
}
然后則是拷貝構造函數:
那么拷貝構造函數則是接收一個vector對象,那么這里要注意的就是拷貝構造函數實現的是深拷貝而不是淺拷貝,也就是說這里復制的時候,我們會先開辟一個新的動態數組,然后將vector對象中對應的元素給依次拷貝到該動態數組中的對應位置,然后再初始化三個指針start以及finish和end_of_storage指向該動態數組
vector(const vector<T>& v);
那么這里會有一個坑:
這里的拷貝,很多小伙伴估計會直接調用memcpy來進行拷貝,那么對于內置類型是可以的,但是對于自定義類型就會出錯,因為memcpy拷貝采取的是值拷貝,而對于需要深拷貝的自定義類型來說,比如以string為例,那么memcpy結束后,此時對應位置的string對象中的動態的字符數組就會有兩個string對象共同指向,那么會帶來析構兩次等問題,所以這里解決的策略就是通過賦值來解決,因為自定義類型內部肯定會提供賦值運算符重載函數,并且其底層是一定支持深拷貝,所以這里直接遍歷vector數組中的對應元素來依次賦值,不僅滿足內置類型的拷貝,其次也能滿足自定義類型的深拷貝的需求。
模擬實現:
vector(const vector<T>& v1){T* tmp = new T[v1.capacity()];for (size_t i = 0;i < v1.size();i++){tmp[i] = v1[i];}_start = tmp;_finish = _start + v1.size();_end_of_storage = _start + v1.capacity();}
size函數
那么這里的size函數則是返回vector對象中存儲有效數據的個數,那么實現的原理就是通過指針相減,那么這里讓finish指針減去start指針(finish-start),得到start指針與finish指針之間的元素個數,但是前提是兩個指針得指向同一塊連續的內存區域
其次vector提供了兩個版本的size重載函數,因為有的vector對象會被const修飾,那么被const修飾的對象只能被const修飾的this指針所接收,所以這里提供了兩個版本的size函數
size_t size();
size_t size() const;
模擬實現:
size_t size(){return _finish - _start;}size_t size() const{return _finish - _start;}
capacity函數
那么capacity函數則是返回當前容器的容量,也就是動態數組的最大長度,那么其實現原理和size是一樣的通過指針的算術運算來實現,也就是end_of_storage減去start得到目前容器能夠容納多少個元素
同樣capacity函數也提供了兩個版本,一個支持非const的vector對象,一個支持const修飾的vector對象
size_t capacity();
size_t capacity() const;
模擬實現:
size_t capacity(){return _end_of_storage - _start;}size_t capacity() const{return _end_of_storage - _start;}
reserve函數
那么reserve函數的作用就是預開辟一定大小的數組,那么他會接收一個size_t的參數,那么該參數就是新的動態數組的大小,那么注意reserve函數底層實現的時候,那么vector不一定是一個空數組,所以存在我們reserve申請開辟的空間可能小于當前數組的容量,但reserve函數沒有所謂的縮容的行為,那么這里我們得判斷新開辟的空間與capacity的大小,只有比capacity大才能擴容,接著開辟一個新的動態數組,然后拷貝舊空間的數據,那么這里會是會面臨和拷貝構造函數一樣的問題,如果數組中的元素類型是需要深拷貝的自定義類型,那么這里我們不能通過memcpy來淺拷貝,由于自定義類型有支持深拷貝的賦值運算符重載函數,那么就得依次對應位置采取賦值的方式來拷貝
void reserve(size_t n);
模擬實現:
void reserve(size_t n){if (n > capacity()){T* tmp = new T[n];size_t odsize = size();for (size_t i = 0;i < odsize;i++){tmp[i] = _start[i];}delete[] _start;_start = tmp;_finish = _start + odsize;_end_of_storage = _start + n;}}
swap函數
那么swap函數則是交換兩個vector對象的值,而我們知道由于標準庫里面已經實現了一個swap函數,而這里對于vector對象來說,那么它要完成與另一個對象的成員變量的交換,那么就是交換三個迭代器的值,所以這里swap函數可以來調用標準庫中的swap函數來交換三個迭代器即可
void swap( vector<T>& v1)
模擬實現:
void swap(const vector<T>& v1){std::swap(_start, v1._start);std::swap(_finish, v1._finish);std::swap(_end_of_storage, v1._end_of_storage);}
運算符重載函數
1.[]下標訪問運算符重載函數
那么由于vector底層維護的是一個動態數組,而數組的空間是連續并且支持隨機訪問,那么相比于通過迭代器來訪問vector對象中數組的元素,那么直接使用下標訪問運算符來訪問則更為直觀形象,那么下標訪問運算符返回的就是一個vector數組中元素的引用,因為我們可以直接通過下標訪問的方式來修改vector對象中數組中的元素
那么下標訪問運算符重載函數會接收一個size_t的數組索引,在函數內部我們還得判斷該索引是否在有效程度之內
T& operator[](size_t pos);const T& operator[](size_t pos) const
并且這里下標訪問運算符重載函數有兩個版本,分別支持非const對象以及const對象,那么非const對象能夠通過該運算符重載函數來訪問并且能夠修改元素,而const對象則只能讀不能寫
模擬實現:
T& operator[](size_t pos){assert(0 <= pos && pos < size());return _start[pos];}const T& operator[](size_t pos) const{assert(0 <= pos && pos < size());return _start[pos];}
2.賦值運算符重載函數
那么賦值運算符重載函數的實現可以采取上文的拷貝構造函數的方式,但在這里我們可以采取一個新的方式來實現,因為這里賦值運算符重載函數是傳引用,所以這里我們在構造函數內部可以創建一個臨時對象,然后調用該臨時對象的拷貝構造函數用傳進來的對象的引用來初始化該臨時對象,接著我們在調用swap函數,將this指針指向的對象與臨時對象進行交換,那么此時臨時對象中的維護的就是原來的this指針指向的對象的動態數組,那么一旦函數調用結束,那么局部變量會隨著函數棧幀一起被銷毀,那么此時編譯器會調用該臨時變量的析構函數,所以無需我們來手動釋放舊空間,而此時this指向的對象保存的則是之前臨時對象中的動態數組,那么這里采取的就是一個巧妙的函數復用
那么注意賦值運算符的返回值是this對象,因為我們有連續賦值的語法,也就是通過右側的賦值運算符重載函數的返回值來繼續依次賦值給左側的對象
vector<T>& operator=(const vector<T>& v1)
模擬實現:
vector<T>& operator=(const vector<T>& v1){vector<T> tmp(v1);swap(tmp);return *this;}
push_back函數
那么push_back函數則是尾插一個元素,那么我們了解了vector類的成員變量之后,那么push_back如何進行尾插的原理其實很簡答,那么push_back函數內部會首先判斷當前是否需要擴容,也就是如果size()==capacity(),那么代表當前容器已滿,那么需要擴容,那么擴容完成之后,由于finish指向的是有效數據結尾的下一個位置,那么我們只需要在finish指向的位置給設置為要插入的值,通過指針的解引用,然后再將finish往后移動一個單位即可
void push_back (const value_type& val);
模擬實現:
void push_back(const T& val){if (size() == capacity()){size_t newsize;capacity() == 0 ? newsize = 2 : newsize = 2 * capacity();reserve(newsize);}*_finish = val;_finish++;}
要注意這里push_back要進行擴容的時候,那么這里我首先對capacity進行了一個判斷,判斷其是否為0,因為存在這樣的場景,用戶可能創建了一個調用無參的構造函數的vector對象,然后再調用push_back函數插入一個元素,由于此時該vector對象是空數組,那么空數組意味著capacity()=0,那么我們擴容的邏輯是采取的是2倍擴容,也就是新的動態數組的容量是2*capacity(),但是如果按照剛才的場景,由于是空數組,此時進入push_back函數內部會先進行一個容量的判斷,而對于空數組來說此時size()==capacity()==0,所以肯定會擴容,但是如果我們直接無腦的reserve( 2 * capacity()),那么此時你發現2 * capacity()的值還是0,擴了個寂寞,所以這里我加了一個capacity()的條件判斷,如果為空,那么就先分配長度為2的數組,不為空,就可以直接2倍擴容,并且這里我們將新的容量大小都記錄在newsize中,然后調用reserve
insert函數
那么push_back函數比較局限,它只能在尾部插入,而不能在動態數組內的有效長度內的任意位置進行插入,但是push_back由于是尾插,那么它的插入的時間代價是O(1),非常高效,不需要元素的移動,而這里如果我們要在動態數組的有效長度內的任意位置插入,那么就需要調用insert函數
那么vector也提供了多個insert函數的重載版本,因為我們插入的可能只有一個元素也可能插入多個元素
對于第一個重載版本,那么insert函數首先會接受一個迭代器,指向vector對象中保存的數組中的有效長度中的某個位置,第二個參數就是插入的元素的值
iterator insert (iterator pos, const value_type& val);
那么這里insert函數內部會首先判斷接收的第一個參數,也就是迭代器所指向的位置的合法性,也就是該指針指向的位置是否在[start,finish)區間內,如果合法,接著會判斷容量,如果size()==capacity(),那么說明當前容量已滿,那么需要擴容,那么擴容完成之后的下一個環節,便是先將[pos,finish)區間中的所有元素整體向后移動一個單位,然后再將pos位置的值給設置為插入元素的值
模擬實現:
iterator insert(iterator pos, const T& val){assert(_start <= pos && pos <= _finish);if (size() >= capacity()){size_t newsize;capacity() == 0 ? newsize = 4 : newsize = 2 * capacity();size_t len = pos - _start;reserve(newsize);pos = _start + len;}iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;end--;}*pos = val;return pos;}
那么這里模擬實現該版本的insert函數的時候會有一個坑
如果insert函數內部執行了擴容,那么注意,此時會創建一個新的動態數組,然后將原來的動態數組給delete釋放掉,而pos迭代器還是指向原本舊的動態數組,所以這里我們就得更新pos,指向新的動態數組,那么學過物理的讀者,會知道物理里面有一個叫相對距離的概念,雖然這里開辟了一個新的動態數組,但是pos位置與start位置的相對距離是不會變化的,所以這里在擴容之前,我們需要先記錄一下pos與start之間的相對距離len,獲取其間隔幾個元素,通過簡單的指針的算術運算來得到,那么reserve完之后,start指向了新的動態數組的起始位置,那么這里我們就將star+len的值賦給pos從而更新pos位置
而第二個重載版本,那么插入的不是一個元素,而是多個相同值的元素,那么這個版本的重載函數的實現原理相較于上面,區別就是這里我們不是將[pos,finish)區間中的所有元素整體向后移動一個單位,而是向后移動n個單位
iterator insert(iterator pos,size_t n,const value_type& val);
模擬實現:
iterator insert(iterator pos, size_t n, const T& val){assert(_start <= pos && pos <= _finish);if (size() + n >= capacity()){size_t newsize = size() + n + 2;size_t len = pos - _start;reserve(newsize);pos = _start + len;}iterator end = _finish - 1;while (end >= pos){*(end + n) = *end;end--;}end = pos;for (int i = 0;i < n;i++){(*end) = val;end++;}return pos;}
而第三個版本就是通過一個迭代器區間初始化,那么前面的步驟和之前的都一樣,也就是判斷位置合法性,以及是否需要擴容,只不過在擴容之前,我們需要計算一下迭代器區間中元素的個數n,然后判斷size()+n==capacity(),接著在遍歷迭代器區間,那么遍歷的時候,就得注意這里我們采取的是雙指針,定義一個src指針指向pos位置處,再定義一個des指針指向pos+n位置處,然后將src位置處的值拷貝到des位置處,那么拷貝完之后,兩個指針同時向后移動,直到src指針到達finish指針指向的位置,那么說明此時將[pos,finish)區間的所有元素向后移動了n個單位,那么接著我們再將的迭代器區間中的值拷貝到[pos,pos+n)區間即可
iterator insert (iterator position, iterator first,iterator last);
模擬實現:
iterator insert(iterator pos, iterator first, iterator last){assert(_start <= pos && pos <= _finish);size_t ns = last - first;if (size() + ns >= capacity()){size_t newsize = size() + ns + 2;size_t len = pos - _start;reserve(newsize);pos = _start + len;}iterator end = _finish - 1;while (end >= pos){*(end + ns) = *end;end--;}end = pos;while (first != last){*end = *first;end++;first++;}return pos;}
那么這就是三個insert的重載函數,那么有的小伙伴可能會發現insert函數的返回值是一個迭代器,那么這里為什么insert函數會返回一個迭代器呢?
那么這就和迭代器失效有關:
那么有的小伙伴在使用insert函數會有這樣的習慣,那么在對該vector對象調用insert函數在pos位置插入完了之后,接著繼續通過pos迭代器來訪問,那么這里就會有一個后果,因為你不知道這里調用insert插入完成之后,你的vector對象是否發生了擴容,那么一旦發生了擴容,我們知道insert是傳值拷貝給形參,雖然我們在insert函數內部更新了pos,但是形參的改變不影響實參,所以這里的pos位置仍然指向了是舊的空間,那么此時舊的空間已經被釋放,那么我們通過該指針去訪問,就是非法訪問內存
那么可能有的小伙伴會抬杠說,那么這里如果vector沒有進行擴容,那么意味著此時pos還是指向原來的空間,那么這里不就可以訪問了嗎,并且vs平臺下是2倍擴容,那么從0->1->2->4->8->…來依次向后擴容,那么意味著我可以知道vs平臺下擴容的時機,意味著我可以來判斷什么時候可以繼續使用傳給insert函數的迭代器去訪問,什么時候不能用該迭代器去訪問了
那么這里我想說的就是,確實存在有些小伙伴說的那樣,如果此時insert沒有擴容,那么理論上來說pos確實可以正確訪問,但是你的代碼并不一定只在vs平臺下面跑,也可能在Linux平臺等其他平臺下跑,那么不同平臺,他們底層的vector的實現是有區別的,那么意味著不同平臺下vector的擴容的邏輯是不同,所以這里我們統一的認為此時調用完insert之后的pos迭代器是失效的,不能再通過它去訪問,并且人家STL庫的設計者也考慮到了這個問題,所以會返回一個指向新的pos位置的迭代器,所以我們還是老老實實的使用insert返回值去訪問,別去鉆這個空子,并且在vs平臺下,還進行了嚴格的檢查,不允許我們調用完insert還有繼續使用迭代器的行為:
pop_back函數
那么pop_back函數就是在刪除vector對象中數組的最后一個有效元素,那么其實現的原理也很簡單,那么首先我們得先判斷一下當前數組是否為空,不為空,我們直接將finish指針想前移動一個單位即可,因為finish指針代表的是有效數組結尾的下一個位置,那么我們遍歷通常是遍歷到finish指向的位置結束,而不會訪問包括finish以及finish之后的所有元素,所以我們只需要移動finish即可,而不需要手動覆蓋
模擬實現:
void pop_back(){assert(size() != 0);_finish--;}
erase函數
那么pop_back函數只能刪除結尾的元素那么erase則是能夠刪除vector數組中有效長度內的任意元素,那么vector類中提供了兩個重載版本的erase
那么第一個版本就是刪除有效長度內的任意一個元素,那么這里erase會接收一個迭代器
iterator erase(iterator pos)
那么它的底層的實現原理就是首先會檢查該迭代器指向的位置是否有效,然后接著會進行元素的移動,那么這里會將[pos,finish)區間的整體向前移動一個單位,那么此時我們可以在erase函數中定義一個end指針,指向pos位置,然后將end指向的位置的下一個位置的值來覆蓋當前end指向的位置,然后end往后移動一個單位,直到達到finish位置,那么是說明此時移動完畢,最后我們在將finish指針往前移動一個單位
iterator erase(iterator pos)
模擬實現:
iterator erase(iterator pos){assert(_start <= pos && pos < _finish);iterator end = pos;while (end < _finish){*end = *(end + 1);end++;}_finish--;return pos;}
那么第二個重載版本則是接收兩個迭代器first和last,那么這兩個迭代器分別指向該vector對象內的數組的某個連續區間的起始以及結束位置,那么我們就要刪除這個連續的區間
iterator erase(iterator first, iterator last)
那么首先第一步我們還是得判斷該區間的合法性,也就是檢查這兩個迭代器是否在[start,finish)中,并且last要大于first,然后就是元素的移動,那么這里我們首先要計算一下[first,last)區間中的元素個數n,那么通過指針算術運算可以得到,那么這里我采取的是雙指針的方式來實現,那么定義一個指針src指向first位置,再定義另一個指針des指向last位置,然后將des指向的位置的值覆蓋src指向的位置,然后兩個指針同時往后移動,那么知道des到達finish位置,那么說明此時元素已經移動完畢,然后再更新finish的指向
模擬實現:
iterator erase(iterator first, iterator last){assert(_start <= first && first < _finish && first < last && _start <= last && last < _finish);iterator src = first;iterator des = last;while (des < _finish){*src = *des;src++;des++;}_finish = src;return first;}
那么這里注意這里erase的返回值是一個迭代器,那么之前insert函數返回迭代器,那么我們知道是因為擴容問題會導致的迭代器失效,那么這里為什么erase也會返回一個迭代器呢,而這里erase函數不需要擴容,所以不存在所謂的迭代器失效啊?
那么這里雖然erase函數不需要擴容,但是要注意的就是erase函數它刪除完元素之后,那么vector對象中的動態數組的元素會移動,那么此時該迭代器指向的位置的值已經不再是原本的值了,所以我們再用迭代器訪問就可能會引發后果
假設你刪除的位置是最后一個位置的話,那么此時你刪除完最后一個元素,那么再通過迭代器去訪問,那么該位置的內容可能是隨機值
并且在vs平臺下編譯器還進行嚴格的檢查,那么不允許你調用erase之后,再使用迭代器來繼續訪問。所以我們還是得老老實實用erase的返回值來繼續去訪問
resize函數
那么resize函數就是來調整vector中的有效長度,resize數組會接收一個size_t的參數n,表示vector數組新的有效長度,那么如果n小于size(),那么我們就直接截斷,也就是將finish指針指向star+n,而如果n大于size但是小于capacity(),那么這里我們就將[start+size(),start+n)用接收到的第二參數的值來填充,這第二份參數也提供了缺省值,那么這個缺省值在上文的構造函數內容中有詳細講過,最后調整finish指針,而如果n大于capacity(),那么則需要擴容,然后填充[start+size(),start+n)區間的值
void resize(size_t n, const T& val = T())
模擬實現:
void resize(size_t n, const T& val = T()){if (n > size()){if (n > capacity()){reserve(n+1);}for (size_t i = size();i < n;i++){_start[i] = val;}}else {_finish = _start +n;}}
源碼
myvector.h
#pragma once
#include<iostream>
#include<assert.h>
namespace wz
{template<typename T>class vector{public:typedef T* iterator;typedef const T* const_iterator;vector():_start(nullptr), _finish(nullptr), _end_of_storage(nullptr){}vector(size_t n, const T& val = T()):_start(nullptr), _finish(nullptr), _end_of_storage(nullptr){reserve(n);for (size_t i = 0;i < n;i++){push_back(val);}}vector(const vector<T>& v1){T* tmp = new T[v1.capacity()];for (size_t i = 0;i < v1.size();i++){tmp[i] = v1[i];}_start = tmp;_finish = _start + v1.size();_end_of_storage = _start + v1.capacity();}template<typename inputiterator>vector(inputiterator first, inputiterator last){while (first != last){push_back(*first);first++;}}~vector(){delete[] _start;_start = nullptr;_finish = nullptr;_end_of_storage = nullptr;}size_t size(){return _finish - _start;}size_t size() const{return _finish - _start;}size_t capacity(){return _end_of_storage - _start;}size_t capacity() const{return _end_of_storage - _start;}iterator begin(){return _start;}const_iterator begin() const{return _start;}iterator end(){return _finish;}const_iterator end() const{return _finish;}void reserve(size_t n){if (n > capacity()){T* tmp = new T[n];size_t odsize = size();for (size_t i = 0;i < odsize;i++){tmp[i] = _start[i];}delete[] _start;_start = tmp;_finish = _start + odsize;_end_of_storage = _start + n;}}T& operator[](size_t pos){assert(0 <= pos && pos < size());return _start[pos];}const T& operator[](size_t pos) const{assert(0 <= pos && pos < size());return _start[pos];}void swap(vector<T>& v1){std::swap(_start, v1._start);std::swap(_finish, v1._finish);std::swap(_end_of_storage, v1._end_of_storage);}vector<T>& operator=(const vector<T>& v1){vector<T> tmp(v1);swap(tmp);return *this;}void push_back(const T& val){if (size() == capacity()){size_t newsize;capacity() == 0 ? newsize = 2 : newsize = 2 * capacity();reserve(newsize);}*_finish = val;_finish++;}void pop_back(){assert(size() != 0);_finish--;}void resize(size_t n, const T& val = T()){if (n > size()){if (n > capacity()){reserve(n+1);}for (size_t i = size();i < n;i++){_start[i] = val;}}else {_finish = _start +n;}}void clear(){_finish = _start;}bool empty(){return _start == _finish;}iterator insert(iterator pos, const T& val){assert(_start <= pos && pos <= _finish);if (size() >= capacity()){size_t newsize;capacity() == 0 ? newsize = 4 : newsize = 2 * capacity();size_t len = pos - _start;reserve(newsize);pos = _start + len;}iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;end--;}*pos = val;return pos;}iterator insert(iterator pos, size_t n, const T& val){assert(_start <= pos && pos <= _finish);if (size() + n >= capacity()){size_t newsize = size() + n + 2;size_t len = pos - _start;reserve(newsize);pos = _start + len;}iterator end = _finish - 1;while (end >= pos){*(end + n) = *end;end--;}end = pos;for (int i = 0;i < n;i++){(*end) = val;end++;}return pos;}iterator insert(iterator pos, iterator first, iterator last){assert(_start <= pos && pos <= _finish);size_t ns = last - first;if (size() + ns >= capacity()){size_t newsize = size() + ns + 2;size_t len = pos - _start;reserve(newsize);pos = _start + len;}iterator end = _finish - 1;while (end >= pos){*(end + ns) = *end;end--;}end = pos;while (first != last){*end = *first;end++;first++;}return pos;}iterator erase(iterator pos){assert(_start <= pos && pos < _finish);iterator end = pos;while (end < _finish){*end = *(end + 1);end++;}_finish--;return pos;}iterator erase(iterator first, iterator last){assert(_start <= first && first < _finish && first < last && _start <= last && last < _finish);iterator src = first;iterator des = last;while (des < _finish){*src = *des;src++;des++;}_finish = src;return first;}private:iterator _start;iterator _finish;iterator _end_of_storage;};
}
main.cpp:
#include"myvector.h"
int main()
{wz::vector<int> s1(10, 6);for (int i = 0;i < s1.size();i++){std::cout << s1[i] << " ";}std::cout << std::endl;std::cout << "----------------------" << std::endl;s1.clear();s1.push_back(1);s1.push_back(3);s1.push_back(5);s1.push_back(10);s1.pop_back();auto it = s1.begin();while (it != s1.end()){std::cout << *it << " ";it++;}std::cout << std::endl;std::cout << "--------------------------" << std::endl;s1.insert(s1.begin(), 200);wz::vector<int> s2(2, 400);s1.insert(s1.begin()+1, s2.begin(), s2.end());wz::vector<int>:: iterator it1 = s1.begin();while (it != s1.end()){std::cout << *it << " ";it++;}std::cout << std::endl;std::cout << "--------------------------" << std::endl;s1.resize(1);it = s1.begin();while (it != s1.end()){std::cout << *it<< " ";it++;}std::cout << std::endl;std::cout << "--------------------------" << std::endl;s1.erase(s1.begin());it = s1.begin();while (it != s1.end()){std::cout << *it << " ";it++;}std::cout << std::endl;std::cout << "--------------------------" << std::endl;s1.push_back(1000);wz::vector<int> s3;s3 = s1;wz::vector<int>:: iterator it2 = s3.begin();while (it2 != s3.end()){std::cout << *it2 << " ";it2++;}std::cout << std::endl;std::cout << "---------------------------" << std::endl;return 0;
}
運行截圖:
結語
那么這就是本篇文章關于vector的全部內容,那么帶你全方面剖析vector,希望讀者下去也能自己實現vector類,那么我的下一期博客將會講解list,那么我會持續更新,希望你能夠多多關注,如果本文有幫組到你的話,還請三連加關注哦,你的支持就是我創作的最大的動力!