摘要:并且由于的連續性,且循環中有迭代器的自加,所以在刪除一個元素后,迭代器需要減。隸書方案二與方案一在迭代器的處理上是類似的,不過對元素的訪問采用了迭代器的方法。
一、vector底層實現機制刨析
通過分析 vector 容器的源代碼不難發現,它就是使用 3 個迭代器(可以理解成指針)來表示的:
其中statrt指向vector 容器對象的起始字節位置;
finish指向當前最后一個元素的末尾字節
end_of指向整個 vector 容器所占用內存空間的末尾字節。
如圖 演示了以上這 3 個迭代器分別指向的位置
如圖 演示了以上這 2個迭代器分別指向的位置
在此基礎上,將 3 個迭代器兩兩結合,還可以表達不同的含義,例如:
start 和 finish 可以用來表示 vector 容器中目前已被使用的內存空間;
finish 和 end_of可以用來表示 vector 容器目前空閑的內存空間;
start和 end_of可以用表示 vector 容器的容量。
二、vector的核心框架接口的模擬實現
1.vector的迭代器實現
typedef T* Iteratot;
typedef T* const_Iteratot;
Iteratot cend()const { return final_end; } Iteratot cbegin()const { return start; } Iteratot end() { return final_end; } Iteratot begin() { return start; }
vector的迭代器是一個原生指針,他的迭代器和String相同都是操作指針來遍歷數據:
2.reserve()擴容
void reserve(size_t n) { if (n > capacity()) { T* temp = new T [n]; //把statrt中的數據拷貝到temp中 size_t size1 = size(); memcpy(temp, start, sizeof(T*) * size()); start = temp; final_end = start + size1; finally = start + n; } }
當 vector 的大小和容量相等(size==capacity)也就是滿載時,如果再向其添加元素,那么 vector 就需要擴容。vector 容器擴容的過程需要經歷以下 3 步:
這也就解釋了,為什么 vector 容器在進行擴容后,與其相關的指針、引用以及迭代器可能會失效的原因。
由此可見,vector 擴容是非常耗時的。為了降低再次分配內存空間時的成本,每次擴容時 vector 都會申請比用戶需求量更多的內存空間(這也就是 vector 容量的由來,即 capacity>=size),以便后期使用。
vector 容器擴容時,不同的編譯器申請更多內存空間的量是不同的。以 VS 為例,它會擴容現有容器容量的 50%。
使用memcpy拷貝問題
reserve擴容就是開辟新空間用memcpy將老空間的數據拷貝到新開空間中。
假設模擬實現的vector中的reserve接口中,使用memcpy進行的拷貝,以下代碼會發生什么問題?
int main(){bite::vector<bite::string> v;v.push_back("1111");v.push_back("2222");v.push_back("3333");return 0;}
問題分析:
void push_back(const T&var) { if (final_end ==finally) { size_t newcode = capacity() == 0 ? 4 : capacity() * 2; reserve(newcode); } *final_end = var; ++final_end; void pop_back() { final_end--; }
插入問題一般先要判斷空間是否含有閑置空間,如果沒有,就要開辟空間。我們final_end==finally來判斷是否含有閑置空間。如果容器含沒有空間先開辟4字節空間,當滿了后開2capacoity()空間。在final_end部插入數據就行了。對final_end加以操作。
4.對insert()插入時迭代器失效刨析
Iteratot insert(Iteratot iterator,const T&var) { assert(iterator <= final_end && iterator >= start); size_t pos = iterator - start; if (final_end == finally) { size_t newcode = capacity() == 0 ? 4 : capacity() * 2; reserve(newcode); } //插入操作 auto it = final_end; while (it >= start+pos) { *(it+1)=*it; it--; } *iterator = var; final_end++; return iterator; }
假設這是一段vector空間要在pos插入數據,但是剛剛好final_end和final在同一位置,這個容器滿了,要對這這個容器做擴容操作。首先對開辟和這個空間的2唄大小的空間
接著把老空間數據拷貝到新空間中釋放老空間。
由于老空間釋放了pos指向的內存不見了。pos指針就成了野指針。
這如何解決呢就是在老空間解決之間保存這個指針,接著讓他重新指向新空間的原來位置。
而insert()函數返回了這個位置迭代器這為迭代器失效提供了方法,這個方法就是重新賦值。讓這個指針重新指向該指向的位置。
5.對erase()數據刪除時迭代器失效刨析
Iteratot erase(Iteratot iterator) { assert(iterator <= final_end && iterator >= start); auto it = iterator; while (it <final_end) { *it = *(it+1); it++; } final_end--; return iterator; }
vector使用erase刪除元素,其返回值指向下一個元素,但是由于vector本身的性質(存在一塊連續的內存上),刪掉一個元素后,其后的元素都會向前移動,所以此時指向下一個元素的迭代器其實跟剛剛被刪除元素的迭代器是一樣的。
以下為解決迭代器失效方案:
#include #include using namespace std; int main(){ int a[] = {1, 4, 3, 7, 9, 3, 6, 8, 3, 3, 5, 2, 3, 7}; vector<int> vector_int(a, a + sizeof(a)/sizeof(int)); /*方案一*/ // for(int i = 0; i < vector_int.size(); i++) // { // if(vector_int[i] == 3) // { // vector_int.erase(vector_int.begin() + i); // i--; // } // } /*方案二*/ // for(vector::iterator itor = vector_int.begin(); itor != vector_int.end(); ++itor) // { // if (*itor == 3) // { // vector_int.erase(itor); // --itor; // } // } /*方案三*/vector<int>::iterator v = vector_int.begin();while(v != vector_int.end()){ if(*v == 3) { v = vector_int.erase(v); cout << *v << endl; } else{ v++; }} /*方案四*/// vector::iterator v = vector_int.begin(); // while(v != vector_int.end())// {// if(*v == 3)// {// vector_int.erase(v); // }// else{// v++;// }// } for(vector<int>::iterator itor = vector_int.begin(); itor != vector_int.end(); itor++) { cout << * itor << " "; } cout << endl; return 0;}
一共有四種方案。
方案一表明vector可以用下標訪問元素,顯示出其隨機訪問的強大。并且由于vector的連續性,且for循環中有迭代器的自加,所以在刪除一個元素后,迭代器需要減1。
方案二與方案一在迭代器的處理上是類似的,不過對元素的訪問采用了迭代器的方法。
方案三與方案四基本一致,只是方案三利用了erase()函數的返回值是指向下一個元素的性質,又由于vector的性質(連續的內存塊),所以方案四在erase后并不需要對迭代器做加法。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/124077.html
摘要:源碼源碼行被點擊了點擊了,即委托的事件被點擊了優先添加委托,再添加其他即委托在上的事件數量在下標為的位置插入委托事件解析可以看到,是優先添加委托事件,再添加自身事件,觸發事件的時候也是按這個順序。 showImg(https://segmentfault.com/img/remote/1460000019419722); 前言:請先回顧下我之前寫的一篇文章:JavaScript之事件委...
摘要:檢查數據是否是格式判斷是否為有效郵件地址判斷是否為有效網址判斷字符串是否為空判斷是否為指定長度內字符串判斷是否為合法用戶名判斷是否為合法用戶密碼判斷是否為合法電話號碼判斷是否是某一范圍內的合法值判斷是否為合法郵編固定長度判斷上傳文件的擴展名 // ※CheckMoney($C_Money) 檢查數據是否是99999.99格式// ※CheckEmailAddr($C_mailaddr)...
摘要:讀到一個非數字非英文字母非下劃線字符。此時立即跳轉回狀態。以一個雙引號開始,并以一個雙引號結束。另外,在讀和時源代碼不許結束,即讀到符號,若結束,則判定為詞法錯誤。對于而言,也有一些其他的詞法錯誤判定,如,不能換行。 對于非 Normal 狀態,我只需要關心兩個過程: 何時從 Normal 跳轉到該狀態; 何時從該狀態跳回 Normal 狀態。 在上一章中,我已經寫好了從 Nor...
摘要:簡評網格模塊是創建網站模型的絕佳工具。如果你對網格完全陌生,你可能要瀏覽一下我的分鐘介紹網格的文章。每一行代表一行,每一個字符,,,代表一個網格元素。無論標簽在標記中是如何放置的,我們都能隨意轉換。這被稱為源代碼的獨立性,這是的一大進步。 簡評:CSS 網格模塊是創建網站模型的絕佳工具。它是我嘗試過的任何其他系統中最快讓你體驗布局的工具。 我們的網格 我們將從模仿一個經典網站的非常基本...
閱讀 3029·2021-11-22 09:34
閱讀 2505·2021-09-30 09:47
閱讀 1438·2021-09-03 10:32
閱讀 3702·2021-08-16 10:49
閱讀 1784·2019-08-30 15:55
閱讀 2451·2019-08-30 15:52
閱讀 3316·2019-08-30 15:44
閱讀 1343·2019-08-30 15:44