程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C++迭代器失效的問題 匯總

C++迭代器失效的問題 匯總

編輯:C++入門知識

首先對於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)後,指向刪除點的迭代器全部失效;指向刪除點後面的元素的迭代器也將全部失效。
deque迭代器的失效情況:
1.在deque容器首部或者尾部插入元素不會使得任何迭代器失效。
2.在其首部或尾部刪除元素則只會使指向被刪除元素的迭代器失效。
3.在deque容器的任何其他位置的插入和刪除操作將使指向該容器元素的所有迭代器失效。

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

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

list intList;
list::iterator it = intList.begin();
while(it != intList.end())
{
it = intList.erase(it);
……
}

總結各種容器特點

(1) vector

內部數據結構:數組。

隨機訪問每個元素,所需要的時間為常量。
在末尾增加或刪除元素所需時間與元素數目無關,在中間或開頭增加或刪除元素所需時間隨元素數目呈線性變化。
可動態增加或減少元素,內存管理自動完成,但程序員可以使用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

總結:
1、對於關聯式容器(map, list, set)元素的刪除,插入操作會導致指向該元素的迭代器失效,其他元素迭代器不受影響。
2、對於順序式容器(vector)元素的刪除、插入操作會導致指向該元素以及後面的元素的迭代器失效。

關於迭代器
(1)特征與操作
l 迭代器的基本特征有:
解除——支持解除引用(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+5、p-=8
比較——利用比較運算符(==、!=、<、>、<=、>=)來比較兩個迭代器是否相等或誰大誰小,例如if(p < q)……;、wihle(p != c.end())……;
(2)分類
根據迭代器所支持的操作不同,在STL中定義了如下5種迭代器:
l 輸入迭代器(input iterator)——用於讀取容器中的信息,但不一定能夠修改它。
輸入迭代器iter通過解除引用(即*iter),來讀取容器中其所指向元素之值;
為了使輸入迭代器能夠訪問容器中的所有元素的值,必須使其支持(前/後綴格式的)++ 操作符;
輸入迭代器不能保證第二次遍歷容器時,順序不變;也不能保證其遞增後,先前指向的值不變。即,基於輸入迭代器的任何算法,都應該是單通(single-pass)的,不依賴於前一次遍歷時的值,也不依賴於本次遍歷中前面的值。
可見輸入迭代器是一種單向的只讀迭代器,可以遞增但是不能遞減,而且只能讀不能寫。適用於單通只讀型算法。
l 輸出迭代器(output iterator)——用於將信息傳輸給容器(修改容器中元素的值),但是不能讀取。例如,顯示器就是只能寫不能讀的設備,可用輸出容器來表示它。也支持解除引用和++操作,也是單通的。所以,輸出迭代器適用於單通只寫型算法。
l 前向迭代器(forward iterator正向迭代器)——只能使用++操作符來單向遍歷容器(不能用--)。與I/O迭代器一樣,前向迭代器也支持解除引用與++操作。與I/O迭代器不同的是,前向迭代器是多通的(multi-pass)。即,它總是以同樣的順序來遍歷容器,而且迭代器遞增後,仍然可以通過解除保存的迭代器引用,來獲得同樣的值。另外,前向迭代器既可以是讀寫型的,也可以是只讀的。
l 雙向迭代器(bidirectional iterator)——可以用++和--操作符來雙向遍歷容器。其他與前向迭代器一樣,也支持解除引用、也是多通的、也是可讀寫或只讀的。
l 隨機訪問迭代器(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可為:(ostream_iterator out_iter(cout);)
copy(c.begin(), c.end(), out_iter);
對數組a可以改為:(const int SIZE = 100; float a[SIZE];)
copy(a, a + SIZE, c.begin());

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved