之前就做題的時候就經常碰到與迭代器失效有關的問題,但是一直對這個問題也沒有深究,處於似懂非懂的狀態,今天就對迭代器失效這部分知識做一個總結。
迭代器
迭代器(iterator)是一個可以對其執行類似指針的操作(如:解除引用(operator*())和遞增(operator++()))的對象,我們可以將它理解成爲一個指針。但它又不是我們所謂普通的指針,我們可以稱之爲廣義指針,你可以通過sizeof(vector::iterator)來查看,所佔內存並不是4個字節。
如下圖所示:
這裏我們定義了一個vector迭代器,對其求sizeof(),發現是12個字節,並不是一個指針的大小。
那麼我們常說的迭代器失效到底是什麼呢?都有哪些場景會導致失效問題呢?我們一起來看以下具體場景及解決辦法。
一、序列式容器迭代器失效
對於序列式容器,例如vector、deque;由於序列式容器是組合式容器,噹噹前元素的iterator被刪除後,其後的所有元素的迭代器都會失效,這是因爲vector,deque都是連續存儲的一段空間,所以當對其進行erase操作時,其後的每一個元素都會向前移一個位置。
#include<iostream>
using namespace std;
#include<vector>
void VectorTest()
{
vector<int> vec;
for (int i = 0; i < 5; i++)
{
vec.push_back(i);
}
vector<int>::iterator it;
cout << sizeof(it) << endl;
for (it = vec.begin(); it != vec.end(); it++)
{
if (*it>2)
vec.erase(it);//此處會發生迭代器失效
}
for (it = vec.begin(); it != vec.end(); it++)
cout << *it << " ";
cout << endl;
}
int main()
{
VectorTest();
system("pause");
return 0;
}
運行結果,程序終止:
給出的報錯信息是:vector iterator not incrementable
已經失效的迭代器不能進行++操作,所以程序中斷了。不過vector的erase操作可以返回下一個有效的迭代器,所以只要我們每次執行刪除操作的時候,將下一個有效迭代器返回就可以順利執行後續操作了,代碼修改如下:
void VectorTest()
{
vector<int> vec;
for (int i = 0; i < 5; i++)
{
vec.push_back(i);
}
vector<int>::iterator it;
cout << sizeof(it) << endl;
for (it = vec.begin(); it != vec.end(); )
{
if (*it==3)
{
it = vec.erase(it);//更新迭代器it
}
it++;
}
for (it = vec.begin(); it != vec.end(); it++)
cout << *it << " ";
cout << endl;
}
運行結果:
這樣刪除後it指向的元素後,返回的是下一個元素的迭代器,這個迭代器是vector內存調整過後新的有效的迭代器。此時就可以進行正確的刪除與訪問操作了。
上面只是舉了刪除元素造成的vector迭代器失效問題,對於vector的插入元素也可以同理得到驗證,這裏就不再進行舉例了。
vector迭代器失效問題總結
(1)當執行erase方法時,指向刪除節點的迭代器全部失效,指向刪除節點之後的全部迭代器也失效
(2)當進行push_back()方法時,end操作返回的迭代器肯定失效。
(3)當插入(push_back)一個元素後,capacity返回值與沒有插入元素之前相比有改變,則需要重新加載整個容器,此時first和end操作返回的迭代器都會失效。
(4)當插入(push_back)一個元素後,如果空間未重新分配,指向插入位置之前的元素的迭代器仍然有效,但指向插入位置之後元素的迭代器全部失效。
deque迭代器失效總結:
(1)對於deque,插入到除首尾位置之外的任何位置都會導致迭代器、指針和引用都會失效,但是如果在首尾位置添加元素,迭代器會失效,但是指針和引用不會失效
(2)如果在首尾之外的任何位置刪除元素,那麼指向被刪除元素外其他元素的迭代器全部失效
(3)在其首部或尾部刪除元素則只會使指向被刪除元素的迭代器失效。
二、關聯式容器迭代器失效
對於關聯容器(如map, set,multimap,multiset),刪除當前的iterator,僅僅會使當前的iterator失效,只要在erase時,遞增當前iterator即可。這是因爲map之類的容器,使用了紅黑樹來實現,插入、刪除一個結點不會對其他結點造成影響。erase迭代器只是被刪元素的迭代器失效,但是返回值爲void,所以要採用erase(iter++)的方式刪除迭代器。
首先來看一下map迭代器失效的一個例子:
void mapTest()
{
map<int, int>m;
for (int i = 0; i < 10; i++)
{
m.insert(make_pair(i, i + 1));
}
map<int, int>::iterator it;
for (it = m.begin(); it != m.end(); it++)
{
if ((it->first)>5)
m.erase(it);
}
}
int main()
{
mapTest();
system("pause");
return 0;
}
運行結果:
這裏顯示迭代器失效,不能進行++ 操作,只要稍作修改就可以了:
void mapTest()
{
map<int, int>m;
for (int i = 0; i < 10; i++)
{
m.insert(make_pair(i, i + 1));
}
map<int, int>::iterator it;
for (it = m.begin(); it != m.end(); )
{
if (it->first==5)
m.erase(it++);
it++;
}
for (it = m.begin(); it != m.end();it++)
{
cout << (*it).first << " ";
}
cout << endl;
}
運行結果:
此時就可以成功刪除key值爲5的元素了,而且迭代器++也沒有問題了。
這裏主要解釋一下erase(it++)的執行過程:這句話分三步走,先把iter傳值到erase裏面,然後iter自增,然後執行erase,所以iter在失效前已經自增了。
map是關聯容器,以紅黑樹或者平衡二叉樹組織數據,雖然刪除了一個元素,整棵樹也會調整,以符合紅黑樹或者二叉樹的規範,但是單個節點在內存中的地址沒有變化,變化的是各節點之間的指向關係。