1. 程式人生 > >C++關於迭代器刪除(erase)插入(insert)失效問題

C++關於迭代器刪除(erase)插入(insert)失效問題

初學者的我在學習迭代器的時候(今天這裡主要說的是vector的迭代器)碰到了一些問題,糾結了好些時候,總算弄明白了一點。

迭代器會在刪除插入等操作後失效,即在其刪除插入位置後的迭代器會失效,那所謂的失效是什麼意思?

失效一般是指迭代器指向了和你預期不一樣的位置了,但這個時候,你可以通過自增自減使它指向正確位置,但是,注意,有些時候,你是不能這樣做的,迭代器會完全失效,必須重新初始化。

迭代器像指標但又不是指標,指標是指向某個地之後,除非你改變它的指向,要不然它是不會因為其他操作使它的指向發生變化的,迭代器不一樣,在刪除插入後它的指向是會變化的,尤其是插入(迭代器有點像浮動的遊標)

例如:

std::vector data; 

std::vector<int>::iteratori;

data.erase(i);

erase之後,i失效;

這裡的失效,其實你在IDE中除錯監視i看的話會發現,i只是指向了刪除後的元素,並沒有發生什麼,但是在這之後你就是不能用它,一用它就會報錯,而必須這樣:i=data.erase(i)//i指向了被刪除前的下一個元素;這樣,你就可以用了。

其實刪除還好,它並沒有改變向量在記憶體的儲存位置,你只要像上邊這樣做既可(或許重新初始化i),而插入有時就比較嚴重了:vector是支援動態插入的,什麼是動態插入呢?就是原先分配給這個vector的記憶體如果不夠的話,你再插入,能成功,但其實這個時候,插入後的vertor記憶體地址完全改變了,這樣,原先的迭代器全部失效,必須重新初始化。你做插入動作,vector分配到的記憶體不夠,這個時候系統會重新開闢一個記憶體空間(你在初始化vector的時候系統會自動開闢記憶體儲存它,這個時候系統給它的記憶體空間你可以用vectorName.capacity()來檢視他的大小,不過系統一般使capacity是等於size的,除非你手動初始化它),將原先vector的值複製過來,再執行插入操作,這樣,你原先的迭代器就全部失效(因為地址完全改變了),不能再用了。

但如果你插入後,vector分配到的記憶體是夠的,迭代器就不會“失效”,這裡不會“失效”,是指vector還在這段記憶體上,只是插入後,插入位置後的迭代器指向了前一個元素,有點像刪除,這個時候你只要執行“--i;”語句就可以使i回到原先位置

但是,重要的來了,迭代器這裡你一定要重點關注下這個:iteratorName.end();這個位置很特殊,它無論是你刪除也好,插入也好,一旦執行了這個操作之後,它是完全無效了的,它不會因為刪除而指向後一個元素(這個好理解,後面是未定義的,當然不能),也不會因為插入而指向前一個元素(如果不是end位置,記憶體足夠的話,插入後,這個位置後的迭代器是變成指向前一個元素的,並沒有完全失效)。

說了這麼多,還是看一個比較典型的程式,更好說明:

01 #include <algorithm>
02 #include <iostream>
03 #include <string>
04 #include <vector>
05 #include <iterator>
06 #include <ciso646>
07 
08 intmain()
09 {
10 intv;
11 std::strings;
12 std::vector<int>data;
13 std::vector<int>::iteratori,p;
14 data.insert(data.begin(),std::istream_iterator<int>(std::cin),std::istream_iterator>());
15 i=data.begin();
16 while(i!=data.end())
17 {
18 
     v=*i;
19 
     p=std::lower_bound(data.begin(),i,v);//從向量begin()開始,到i中找到值大於等於v的第一個迭代器(p)
20 
     i=data.erase(i);//刪除i,並返回指向下一個元素的迭代器
21 
     data.insert(p,v);//在p位置之前插入資料v
22 
    ++i;
23 
 }
24 for(std::vector>::iteratoriter(data.begin());iter!=data.end();++iter)
25 
std::cout<<*iter<<' ';
26 system("pause");
27 }

這是Exploring C++一本書上的一個程式(對輸入的資料進行排序),但這個程式是錯誤的,起碼我在VS2010上執行會提示:Expression:vector iterator not incrementable.(迭代器未定義)(我用的是VS2010)

然後,我輸入“3 2 1”試著除錯,看看哪裡錯了,結果發現,前兩個迴圈正確,第三個迴圈,在執行++i;之前也都是正確的,事實上到這個時候,序列已經排好序了,只要跳出迴圈就好了,可是執行這句語句(++i;)後,報錯,我看了這個時候i的指向,data.end()前一個位置,按理說++i;後應該就可以和data.end()相等,這樣下一個while迴圈就可以跳出來了,可事實上沒有,這是為什麼?

一開始不知道具體原理,還以為因為刪除插入的原因,迭代器就馬上無效,可一想前兩個迴圈不也有刪除插入?怎麼就沒事?弄了好久才明白:最後一個迴圈的時候執行i=data.erase(i);,這個時候,i就指向了這個時候的data.end()位置,這是一個特殊的迭代器,之後執行插入操作,(data這個時候所擁有的記憶體是足夠的),i就無條件無效了,不能用了,成為歷史位置(==!不知道這樣理解對不對),它是不會因為插入而指向前一個元素的,所以,執行++i;報錯(end+1,位置未定義)。

那要實現原功能,怎麼辦,一開始,我這樣改:

在++i;語句前面加一個if判斷:

if(i!=data.end())

     ++i;

else

     break;

可這樣做還是錯了,報錯:Expression:vector iterator incompatible。迭代器不相容。

有了之前講的,就很容易理解,i這個時候已經成了“歷史”迭代器了,它和現在的data.end()完全是兩碼事,不能比較。

正確的做法是:(while迴圈裡面程式碼)

01 v=*i;
02 p=std::lower_bound(data.begin(),i,v);//從向量begin()開始,到i中找到值大於等於v的第一個迭代器(p)
03 i=data.erase(i);//刪除i,並返回指向下一個元素的迭代器
04 if(i==data.end())
05 {
06 data.insert(p,v);
07 break;
08 }
09 else
10 {
11 data.insert(p,v);//在p位置之前插入資料v
12 ++i;
13 }