C++ STL迭代器失效情況總結
本文轉自:https://www.cnblogs.com/Commence/p/7526421.html
1.對於序列式容器:vector,queue(C++的序列式容器有vector、deque、list、forward_list、array和string幾種)等,序列式容器就是陣列式容器,刪除當前的iterator會使得後邊所有元素的iterator都失效。這是因為其使用了連續分配的記憶體,闡述一個元素導致後面所有的元素都會向前移動一個位置,所以不能使用erase(iter++)的方式。但是erase方法可以返回下一個有效的iterator
for (iter = cont.begin(); iter != cont.end();) { (*it)->doSomething(); if (shouldDelete(*iter)) iter = cont.erase(iter); //erase刪除元素,返回下一個迭代器 else ++iter; }
迭代器失效的情況:(錯誤的)
void vectorTest() { vector<int> container; for (int i = 0; i < 10; i++) { container.push_back(i); } vector<int>::iterator iter; for (iter = container.begin(); iter != container.end(); iter++) { if (*iter > 3) container.erase(iter); } for (iter = container.begin(); iter != container.end(); iter++) { cout<<*iter<<endl; } }
這段程式碼在刪除後對迭代器自增,實際其本身已經失效了。 對於vector,刪除當前的iterator會使得用棉所有元素的iterator都失效,因為序列式容器記憶體是連續分配的,刪除一個元素導致後面所有的元素都會向前移動一個位置。
所以此時iter++指向的未知位置。正確的做法是向前面一樣的處理。
vector迭代器的幾種失效的情況:(對於插入操作,只有vector才可能失效)
1.當插入(push_back)一個元素後,end操作返回的迭代器肯定失效。
2.當插入(push_back)一個元素後,capacity返回值與沒有插入元素之前相比有改變,則需要重新載入整個容器,此時first和end操 作返回的迭代器都會失效。
3.當進行刪除操作(erase,pop_back)後,指向刪除點的迭代器全部失效;指向刪除點後面的元素的迭代器也將全部失效。
對於關聯式容器如(map,set,multimap,multiset),刪除當前的iterator,僅僅會使當前的iterator失效,只要在erase時,遞增當前iterator即可。這是因為map之類的容器,使用了紅黑樹來實現,插入、刪除一個節點不會對其他節點造成影響,erase迭代器只是被刪元素的迭代器失效,但是返回值為void,所以要採用erase(iter++)的方式刪除迭代器。
void mapTest()
{
map<int, string> dataMap;
for (int i = 0; i < 100; i++)
{
string strValue = "Hello, World";
stringstream ss;
ss<<i;
string tmpStrCount;
ss>>tmpStrCount;
strValue += tmpStrCount;
dataMap.insert(make_pair(i, strValue));
}
cout<<"MAP元素內容為:"<<endl;
map<int, string>::iterator iter;
for (iter = dataMap.begin(); iter != dataMap.end(); iter++)
{
int nKey = iter->first;
string strValue = iter->second;
cout<<strValue<<endl;
}
cout<<"內容開始刪除:"<<endl;
/////////////////////////////////////////////擦除操作引發迭代器失效
for (iter = dataMap.begin(); iter != dataMap.end();iter++)
{
int nKey = iter->first;
string strValue = iter->second;
if (nKey % 2 == 0)
{
dataMap.erase(iter);
}
/* cout<<iter->second<<endl;*/
}
}
上述程式碼會出現問題:dataMap.erase(iter)之後,iterator就已經失效了,所以iter無法自增,解決的辦法就是在其失效之前完成自增。
做出如下修改:
for (iter = dataMap.begin(); iter != dataMap.end();)
{
int nKey = iter->first;
string strValue = iter->second;
if (nKey % 2 == 0)
{
dataMap.erase(iter++);
auto a = iter;
}
else {
iter ++;
}
}
解析:dataMap.erase(iter++);這句話分幾步走,先把iter傳到erase裡面,然後iter自增,然後再erase,所以iter在失效前已經自增了。
map是關聯式容器,以紅黑樹或者平衡二叉樹組織資料,雖然刪除了一個元素,整棵樹也會調整,以符合二叉樹或者紅黑樹的規範,但是單個節點的改變必然會調整樹的結構,其中單個節點在記憶體中的地址沒有變化且各個節點的指向關係。另外一種保險和比較易讀的寫法是寫一個臨時迭代器儲存當前迭代器,之後迭代器本身自增,在erase臨時的
程式碼如下:
if (nKey % 2 == 0)
{
map<int, string>::iterator tmpIter = iter;
iter++;
dataMap.erase(tmpIter);
//dataMap.erase(iter++) 這樣也行
}
總結:迭代器的失效分三種情況,分為陣列形,連結串列性,樹形資料結構
陣列形資料結構:該資料結構元素是分配在連續的記憶體中。
連結串列性資料結構:其也是用了不連續分配的記憶體
樹形資料結構:插入不會使得任何迭代器失效。