首先對於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操作返回的迭代器都會失效。
刪除時,指向該刪除節點的迭代器失效
list
(1) vector
內部數據結構:數組。
隨機訪問每個元素,所需要的時間為常量。
(2)deque
內部數據結構:數組。
鍵可以不唯一。
其它特點與set相同。
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)特征與操作
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
copy(c.begin(), c.end(), out_iter);
對數組a可以改為:(const int SIZE = 100; float a[SIZE];)
copy(a, a + SIZE, c.begin());