C++: STL迭代器及迭代器失效問題

轉載至:http://blog.csdn.net/wangshihui512/article/details/9791517程序員

 

迭代器失效: 典型的迭代器失效. 

首先對於vector而言,添加和刪除操做可能使容器的部分或者所有迭代器失效。那爲何迭代器會失效呢?vector元素在內存中是順序存儲,試想:若是當前容器中已經存在了10個元素,如今又要添加一個元素到容器中,可是內存中緊跟在這10個元素後面沒有一個空閒空間,而vector的元素必須順序存儲一邊索引訪問,因此咱們不能在內存中隨便找個地方存儲這個元素。因而vector必須從新分配存儲空間,用來存放原來的元素以及新添加的元素:存放在舊存儲空間的元素被複制到新的存儲空間裏,接着插入新的元素,最後撤銷舊的存儲空間。這種狀況發生,必定會致使vector容器的全部迭代器都失效。咱們看到實現上述所說的分配和撤銷內存空間的方式以實現vector的自增加性,效率是極其低下的。爲了使vector容器實現快速的內存分配,實際分配的容器會比當前所需的空間多一些,vector容器預留了這些額外的存儲區,用來存放新添加的元素,而不須要每次都從新分配新的存儲空間。你能夠從vector裏實現capacity和reserve成員能夠看出這種機制。capacity和size的區別:size是容器當前擁有的元素個數,而capacity則指容器在必須分配新存儲空間以前能夠存儲的元素總數。算法

 

vector迭代器的失效狀況:

1.當插入(push_back)一個元素後,end操做返回的迭代器確定失效。數組

2.當插入(push_back)一個元素後,capacity返回值與沒有插入元素以前相比有改變,則須要從新加載整個容器,此時begin和end操做返回的迭代器都會失效。數據結構

3.當進行刪除操做(erase,pop_back)後,指向刪除點的迭代器所有失效;指向刪除點後面的元素的迭代器也將所有失效。 

dom

deque迭代器的失效狀況:

1.在deque容器首部或者尾部插入元素不會使得任何迭代器失效。
2.在其首部或尾部刪除元素則只會使指向被刪除元素的迭代器失效。
3.在deque容器的任何其餘位置的插入和刪除操做將使指向該容器元素的全部迭代器失效。 

函數

List/set/map 迭代器的失效狀況:

刪除時,指向該刪除節點的迭代器失效ui

list<int> intList; 
list<int>::iterator it = intList.begin(); 
while(it != intList.end()) 

    it = intList.erase(it); 
    …… 
}spa

 

總結各類容器特色 

(1) vector

內部數據結構:數組。.net

隨機訪問每一個元素,所須要的時間爲常量。
在末尾增長或刪除元素所需時間與元素數目無關,在中間或開頭增長或刪除元素所需時間隨元素數目呈線性變化。
可動態增長或減小元素,內存管理自動完成,但程序員可使用reserve()成員函數來管理內存。
vector的迭代器在內存從新分配時將失效(它所指向的元素在該操做的先後再也不相同)。當把超過capacity()-size()個元素插入vector中時,內存會從新分配,全部的迭代器都將失效;不然,指向當前元素之後的任何元素的迭代器都將失效。當刪除元素時,指向被刪除元素之後的任何元素的迭代器都將失效。指針

(2)deque

內部數據結構:數組。
隨機訪問每一個元素,所須要的時間爲常量。
在開頭和末尾增長元素所需時間與元素數目無關,在中間增長或刪除元素所需時間隨元素數目呈線性變化。
可動態增長或減小元素,內存管理自動完成,不提供用於內存管理的成員函數。
增長任何元素都將使deque的迭代器失效。在deque的中間刪除元素將使迭代器失效。在deque的頭或尾刪除元素時,只有指向該元素的迭代器失效。

(3)list

內部數據結構:雙向環狀鏈表。
不能隨機訪問一個元素。
可雙向遍歷。
在開頭、末尾和中間任何地方增長或刪除元素所需時間都爲常量。
可動態增長或減小元素,內存管理自動完成。
增長任何元素都不會使迭代器失效。刪除元素時,除了指向當前被刪除元素的迭代器外,其它迭代器都不會失效。

(4)slist

內部數據結構:單向鏈表。
不可雙向遍歷,只能從前到後地遍歷。
其它的特性同list類似。

(5)stack

適配器,它能夠將任意類型的序列容器轉換爲一個堆棧,通常使用deque做爲支持的序列容器。
元素只能後進先出(LIFO)。
不能遍歷整個stack。

(6)queue

適配器,它能夠將任意類型的序列容器轉換爲一個隊列,通常使用deque做爲支持的序列容器。
元素只能先進先出(FIFO)。
不能遍歷整個queue。

(7)priority_queue

適配器,它能夠將任意類型的序列容器轉換爲一個優先級隊列,通常使用vector做爲底層存儲方式。
只能訪問第一個元素,不能遍歷整個priority_queue。
第一個元素始終是優先級最高的一個元素。

(8)set

鍵惟一。
元素默認按升序排列。
若是迭代器所指向的元素被刪除,則該迭代器失效。其它任何增長、刪除元素的操做都不會使迭代器失效。

(9)multiset 

鍵能夠不惟一。

其它特色與set相同。

(10)map

鍵惟一。
元素默認按鍵的升序排列。
若是迭代器所指向的元素被刪除,則該迭代器失效。其它任何增長、刪除元素的操做都不會使迭代器失效。

(11)multimap

鍵能夠不惟一。
其它特色與map相同。

 

1. 容器的iterator類型 

每種容器類型都定義了本身的迭代器類型,如vector: vector::iterator iter; 
這條語句定義了一個名爲iter的變量,它的數據類型是由vector定義的iterator類型。每一個標準庫容器類型都定義了一個名爲iterator的成員,這裏的iterator與迭代器實際類型的含義相同。

2. begin和end操做 

每種容器都定義了一對命名爲begin和end的函數,用於返回迭代器。若是容器中有元素的話,由begin返回的迭代器指向第一個元素: vector::iterator iter = ivec.begin(); 
上述語句把iter初始化爲由名爲begin的vector操做返回的值。假設vector不空,初始化後,iter即指該元素爲ivec[0]。 
由end操做返回的迭代器指向vector的"末端元素的下一個"。一般稱爲超出末端迭代器(off-the-end iterator),代表它指向了一個不存在的元素。若是vector爲空,begin返回的迭代器與end返回的迭代器相同。
由end操做返回的迭代器並不指向vector中任何實際的元素,相反,它只是起一個哨兵(sentinel)的做用,表示咱們已處理完vector中全部元素。

3. vector迭代器的自增和解引用運算 

迭代器類型定義了一些操做來獲取迭代器所指向的元素,並容許程序員將迭代器從一個元素移動到另外一個元素。 
迭代器類型可以使用解引用操做符(*操做符)來訪問迭代器所指向r 元素: 
*iter = 0; 
解引用操做符返回迭代器當前所指向的元素。假設iter指向vector對象ivec的第一個元素,那麼*iter和ivec[0]就是指向同一個元素。上面這個語句的效果就是把這個元素的值賦爲0。
迭代器使用自增操做符向前移動迭代器指向容器中下一個元素。從邏輯上說,迭代器的自增操做和int型對象的自增操做相似。對 int對象來講,操做結果就是把int型值"加1",而對迭代器對象則是把容器中的迭代器"向前移動一個位置"。所以,若是iter指向第一個元素,則++iter指向第二個元素。
因爲end操做返回的迭代器不指向任何元素,所以不能對它進行解引用或自增操做。 

4. 迭代器的其餘運算

 另外一對可執行於迭代器的操做就是比較:用==或!=操做符來比較兩個迭代器,若是兩個迭代器對象指向同一個元素,則它們相等,不然就不相等。 

5. 迭代器應用的程序示例

 假設已聲明瞭一個vector型的ivec變量,要把它全部元素值重置爲0,能夠用下標操做來完成: 
// reset all the elements in ivec to 0 
for (vector::size_type ix = 0; ix != ivec.size(); ++ix) 
    ivec[ix] = 0; 
上述程序用for循環遍歷ivec的元素,for循環定義了一個索引ix,每循環迭代一次ix就自增1。for循環體將ivec的每一個元素賦值爲0


總結:

一、對於關聯式容器(map, list, set)元素的刪除,插入操做會致使指向該元素的迭代器失效,其餘元素迭代器不受影響。
二、對於順序式容器(vector)元素的刪除、插入操做會致使指向該元素以及後面的元素的迭代器失效。

 

關於迭代器

(1)特徵與操做 
迭代器的基本特徵有: 
解除——支持解除引用(dereference)操做,以即可以訪問它引用的值。即,若是p是一個迭代器,則應該對*p和p->進行定義(似指針); 
賦值——可將一個迭代器賦給另外一個迭代器。即,若是p和q都是迭代器,則應該對錶達式p=q進行定義; 
比較——可將一個迭代器與另外一個迭代器進行比較。即,若是p和q都是迭代器,則應該對錶達式p==q和p!=q進行定義; 
遍歷——可使用迭代器來遍歷容器中的元素,這能夠經過爲迭代器p定義++p和p++操做來實現。


迭代器的操做有: 
讀——經過解除引用*來間接引用容器中的元素值,例如x = *p; 
寫——經過解除引用*來給容器中的元素賦值,例如*p = x; 
訪問——經過下標和指向引用容器中的元素及其成員,例如p[2]和p->m 
迭代——利用增量和減量運算(++和--、+和-、+=和-=)在容器中遍歷、漫遊和跳躍,例如p++、--p、p+五、p-=8 
比較——利用比較運算符(==、!=、<、>、<=、>=)來比較兩個迭代器是否相等或誰大誰小,例如if(p < q)……;、wihle(p != c.end())……;


(2)分類 
根據迭代器所支持的操做不一樣,在STL中定義了以下5種迭代器: 
輸入迭代器(input iterator)——用於讀取容器中的信息,但不必定可以修改它。 
  輸入迭代器iter經過解除引用(即*iter),來讀取容器中其所指向元素之值; 
  爲了使輸入迭代器可以訪問容器中的全部元素的值,必須使其支持(前/後綴格式的)++ 操做符; 
  輸入迭代器不能保證第二次遍歷容器時,順序不變;也不能保證其遞增後,先前指向的值不變。即,基於輸入迭代器的任何算法,都應該是單通(single-pass)的,不依賴於前一次遍歷時的值,也不依賴於本次遍歷中前面的值。
  可見輸入迭代器是一種單向的只讀迭代器,能夠遞增可是不能遞減,並且只能讀不能寫。適用於單通只讀型算法。 
輸出迭代器(output iterator)——用於將信息傳輸給容器(修改容器中元素的值),可是不能讀取。例如,顯示器就是隻能寫不能讀的設備,可用輸出容器來表示它。也支持解除引用和++操做,也是單通的。因此,輸出迭代器適用於單通只寫型算法。
前向迭代器(forward iterator正向迭代器)——只能使用++操做符來單向遍歷容器(不能用--)。與I/O迭代器同樣,前向迭代器也支持解除引用與++操做。與I/O迭代器不一樣的是,前向迭代器是多通的(multi-pass)。即,它老是以一樣的順序來遍歷容器,並且迭代器遞增後,仍然能夠經過解除保存的迭代器引用,來得到一樣的值。另外,前向迭代器既能夠是讀寫型的,也能夠是隻讀的。
雙向迭代器(bidirectional iterator)——能夠用++和--操做符來雙向遍歷容器。其餘與前向迭代器同樣,也支持解除引用、也是多通的、也是可讀寫或只讀的。
隨機訪問迭代器(random access iterator)——可直接訪問容器中的任意一個元素的雙向迭代器。 

可見,這5種迭代器造成了一個層次結構:I/O迭代器(均可++遍歷,可是前者只讀/後者只寫)最基本、前向迭代器可讀寫但只能++遍歷、雙向迭代器也可讀寫但能++/--雙向遍歷、隨機迭代器除了可以雙向遍歷外還可以隨機訪問。


(3)指針與迭代器 既然迭代器是廣義的指針,那麼指針自己是否是迭代器呢?其實,指針知足全部迭代器的要求,因此,指針就是一種迭代器。 迭代器是泛型算法的接口,而指針是迭代器。因此,各類STL算法,也可使用指針,來對非標準容器(如數組)進行操做。即,利用指針作迭代器,能夠將STL算法用於常規數組。 例如排序函數sort: sort(Ran first, Ran last); // Ran表示隨機訪問迭代器 對容器c爲: sort(c.begin(), c.end()); 對數組a能夠改成:(const int SIZE = 100; float a[SIZE];) sort(a, a + SIZE); 又例如複製函數copy: copy(In first, In last, Out res); // In和Out分別表示輸入和輸出迭代器 對容器c<int>可爲:(ostream_iterator<int> out_iter(cout);) copy(c.begin(), c.end(), out_iter); 對數組a能夠改成:(const int SIZE = 100; float a[SIZE];) copy(a, a + SIZE, c.begin()); 

相關文章
相關標籤/搜索