C++ STL_vector 迭代器失效問(wèn)題的解決方法
1、前言
**迭代器的主要作用就是讓算法能夠不用關(guān)心底層數(shù)據(jù)結(jié)構(gòu),其底層實(shí)際就是一個(gè)指針,或者是對(duì)指針進(jìn)行了封裝,比如:string的迭代器就是原生指針char,vector的迭代器就是原生態(tài)指針T 。因此迭代器失效,實(shí)際就是迭代器底層對(duì)應(yīng)指針?biāo)赶虻目臻g被銷毀了,而使用一塊已經(jīng)被釋放的空間,造成的后果是程序崩潰(即如果繼續(xù)使用已經(jīng)失效的迭代器,程序可能會(huì)崩潰)。
對(duì)迭代器失效我們了解了,那么現(xiàn)在我們就分析,在vector中哪些操作會(huì)導(dǎo)致迭代器失效。
2、情況一:底層空間改變的操作
存在底層空間改變的函數(shù)接口有:resize、reserve、insert、assign、push_back等。
產(chǎn)生的原因:
這幾個(gè)接口都存在擴(kuò)容的問(wèn)題,擴(kuò)容的時(shí)候存在異地?cái)U(kuò)容,當(dāng)異地?cái)U(kuò)容后,原本的空間被釋放,但是迭代器指的是被釋放空間,這就會(huì)導(dǎo)致迭代器的失效問(wèn)題,會(huì)引發(fā)程序崩潰的問(wèn)題。
解決方法:
一旦存在擴(kuò)容,擴(kuò)容后對(duì)迭代器更新一次,重新給迭代器賦值即可。
舉例:
我們看一下insert接口。
我們由圖中可以看到,當(dāng)我們需要在3之前插入數(shù)據(jù)30,但是空間已經(jīng)滿了,因此我們需要進(jìn)行擴(kuò)容,擴(kuò)容是異地開(kāi)空間,開(kāi)好空間將舊空間的數(shù)據(jù)拷貝回來(lái),并將舊空間釋放掉,_start指向新的空間頭部,但是it指的是舊空間的位置,這就是迭代器失效。我們記住it相對(duì)于_start的相對(duì)位置,在新空間開(kāi)好后,更新it,讓其指向新空間的相對(duì)位置。(方式:計(jì)算出it到_start的距離len,開(kāi)好新空間后,更新it為新的_start+len)。
代碼實(shí)現(xiàn):
iterator insert(iterator pos, const T& x) { assert(pos >= _start); assert(pos <= _finish); if (_finish == _endOfStorage) { size_t len = pos - _start;//先記下_start到pos位置的距離,因?yàn)閿U(kuò)容后迭代器pos就會(huì)失效 reserve(capacity() == 0 ? 4 : 2 * capacity()); pos = _start + len;//新的空間需要更新迭代器pos } iterator end = _finish - 1; //挪動(dòng)數(shù)據(jù) while (end >= pos) { *(end + 1) = *end; --end; } *pos = x; ++_finish; return pos; }
3、情況二:指定位置元素的刪除操作
對(duì)于erase接口也會(huì)導(dǎo)致迭代器失效問(wèn)題。那它是怎么導(dǎo)致的呢,我們來(lái)分析一下。
產(chǎn)生原因:
在erase刪除pos位置元素后,pos位置之后的元素會(huì)往前搬移,沒(méi)有導(dǎo)致底層空間的改變,理論上講迭代器不應(yīng)該會(huì)失效,但是:如果pos剛好是最后一個(gè)元素,刪完之后pos剛好是end的位置,而end位置是沒(méi)有元素的,那么pos就失效了。因此刪除vector中任意位置上元素時(shí),vs就認(rèn)為該位置迭代器失效了。
#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所在位置的iterator vector<int>::iterator pos = find(v.begin(), v.end(), 3); // 刪除pos位置的數(shù)據(jù),導(dǎo)致pos迭代器失效。 v.erase(pos); cout << *pos << endl; // 此處會(huì)導(dǎo)致非法訪問(wèn) return 0; }
解決方法:
本質(zhì)是因?yàn)槲矂h導(dǎo)致的迭代器失效問(wèn)題,因此我們?cè)谖矂h完后,返回it的下一個(gè)位置,我們的模擬實(shí)現(xiàn)是數(shù)據(jù)覆蓋(it+1覆蓋it),因此返回的還是it,一刪之后 --_finish,當(dāng) it指的位置就是_finish 的時(shí)候正好也就停止了,因此也就解決了迭代器失效的問(wèn)題。
代碼實(shí)現(xiàn):
iterator erase(iterator pos) { assert(pos >= _start); assert(pos < _finish); iterator it = pos + 1; //挪動(dòng)數(shù)據(jù) while (it < _endOfStorage) { *(it - 1) = *it; ++it; } --_finish; return pos; }
4、g++編譯器對(duì)迭代器失效檢測(cè)
Linux下,g++編譯器對(duì)迭代器失效的檢測(cè)并不是非常嚴(yán)格,處理也沒(méi)有vs2019下極端。
我們來(lái)看下面這幾種情況下,代碼在vs2019和g++下不同的表現(xiàn)。
4.1 擴(kuò)容
int main() { vector<int> v{1,2,3,4,5}; for(size_t i = 0; i < v.size(); ++i) cout << v[i] << " "; cout << endl; auto it = v.begin(); cout << "擴(kuò)容之前,vector的容量為: " << v.capacity() << endl; v.reserve(100); cout << "擴(kuò)容之后,vector的容量為: " << v.capacity() << endl; while(it != v.end()) { cout << *it << " "; ++it; } cout << endl; return 0; }
g++下運(yùn)行結(jié)果:
vs2019下運(yùn)行結(jié)果:
vs2019下程序崩潰了。
結(jié)論:當(dāng)擴(kuò)容后迭代器就是失效的,g++下雖然能運(yùn)行,但是結(jié)果出錯(cuò)了,vs下直接程序崩潰。
4.2 erase刪除任意位置(非尾刪)
#include <vector> #include <algorithm> using namespace std; int main() { vector<int> v{1,2,3,4,5}; vector<int>::iterator it = find(v.begin(), v.end(), 3); v.erase(it); cout << *it << endl; while(it != v.end()) { cout << *it << " "; ++it; } cout << endl; return 0; }
g++下運(yùn)行結(jié)果:
vs2019下運(yùn)行結(jié)果:
結(jié)論:在非尾刪的刪除中,空間是沒(méi)有變的,迭代器指的是還是那塊空間,g++下迭代器沒(méi)有失效,刪除后后面的數(shù)據(jù)前移,it位置沒(méi)失效,vs下只要是erase,就判斷為迭代器失效了。
4.3 erase尾刪
int main() { vector<int> v{1,2,3,4,5,6}; auto it = v.begin(); while (it != v.end()) { if (*it % 2 == 0) v.erase(it); ++it; } for (auto e : v) cout << e << " "; cout << endl; return 0; }
g++下運(yùn)行結(jié)果:
vs2019下不用看,直接崩潰。
結(jié)論:當(dāng)在尾刪的時(shí)候,刪除之后存在數(shù)據(jù)挪動(dòng),一挪動(dòng)_finish與it是一個(gè)位置了,erase本就返回被刪除位置的下一個(gè)位置,此時(shí)迭代器失效,再++it程序直接崩潰。
5、總結(jié)
本篇主要講了擴(kuò)容、插入、刪除造成的迭代器失效,g++對(duì)迭代器失效檢測(cè)的不嚴(yán)格,而vs對(duì)迭代器失效檢測(cè)很嚴(yán)格,直接崩潰。
1、擴(kuò)容一般都要更新迭代器,我們不知道哪一次的擴(kuò)容是異地?cái)U(kuò)。
2、插入任意位置時(shí),一旦存在擴(kuò)容就要更新迭代器,本質(zhì)就是擴(kuò)容要更新迭代器。
3、刪除任意位置時(shí),g++下非尾刪不考慮迭代器失效問(wèn)題,尾刪一定要注意迭代器失效問(wèn)題;vs2019中刪除就認(rèn)定為迭代器失效,直接崩潰。
以上就是C++ STL_vector 迭代器失效問(wèn)題的解決方法的詳細(xì)內(nèi)容,更多關(guān)于C++ STL_vector 迭代器失效的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C/C++: Inline function, calloc 對(duì)比 malloc
以下是對(duì)c/c++中的malloc函數(shù)與calloc函數(shù)的區(qū)別以及它們之間的聯(lián)系進(jìn)行了介紹,需要的朋友可以過(guò)來(lái)參考下2016-07-07C++ 創(chuàng)建桌面快捷方式 開(kāi)始菜單的實(shí)現(xiàn)代碼
這篇文章介紹了C++ 創(chuàng)建桌面快捷方式,開(kāi)始菜單的實(shí)現(xiàn)代碼,需要的朋友可以參考一下2013-06-06用pybind11封裝C++實(shí)現(xiàn)的函數(shù)庫(kù)的方法示例
這篇文章主要介紹了用pybind11封裝C++實(shí)現(xiàn)的函數(shù)庫(kù),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02C++利用socket傳輸大文件的實(shí)現(xiàn)代碼
這篇文章主要為大家詳細(xì)介紹了C/C++如何使用socket傳輸大文件的實(shí)現(xiàn)代碼,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的小伙伴可以了解一下2023-10-10static_cast,dynamic_cast,reinterpret_cast和const_cast的區(qū)別詳解
以下是對(duì)static_cast,dynamic_cast,reinterpret_cast和const_cast之間的區(qū)別進(jìn)行了詳細(xì)的介紹,需要的朋友可以過(guò)來(lái)參考下2013-09-09C++利用鏈表實(shí)現(xiàn)圖書(shū)信息管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C++利用鏈表實(shí)現(xiàn)圖書(shū)信息管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11C語(yǔ)言實(shí)現(xiàn)的順序表功能完整實(shí)例
這篇文章主要介紹了C語(yǔ)言實(shí)現(xiàn)的順序表功能,結(jié)合完整實(shí)例形式分析了C語(yǔ)言順序表的創(chuàng)建、添加、刪除、排序、合并等相關(guān)操作技巧,需要的朋友可以參考下2018-04-04