之前在**中使用map::erase函式時,誤搬了vector::erase的用法,導致server down掉了,好在在測試環境就及時發現了問題,在上線前進行了補救==。
以下總結一下map::erase的正確用法。
首先看一下在迴圈中使用vector::erase時我習慣的用法:
for(vector::iterator it = vecint.begin(); it != vecint.end();)
else
}
程式從乙個vector中刪除值為0的元素,利用了vector::erase函式根據iterator刪除某個元素時會返回下乙個元素的iterator的性質:
c++98
iterator erase (iterator position);
這一種用法是沒有問題的。
然而當想當然的在map::erase上照搬上面erase的用法時,就有問題了,檢視 上的說明:
c++98
(1)
void erase (iterator position);
(2)
size_type erase (const key_type& k);
(3)
void erase (iterator first, iterator last);
如上所示,c++98中map::erase並沒有返回值為iterator的原型函式。
那麼問題來了it=map.erase(it),然後對it進行操作會發生什麼呢?會發生傳說中的「未定義的行為」!包括但不限於程式掛掉、機器宕機、地球**、宇宙毀滅等–原因是什麼呢?在執行map.erase(it)之後,it這個iterator已經失效了,考慮c語言中乙個失效釋放了的指標,再次引用它會導致什麼問題呢?
在迴圈中正確使用map::erase的方法是什麼呢?如下:
for(map::iterator it = mapint.begin(); it != mapint.end();)
else
}
在網上找mapint.erase(it++)的說明,比較詳細的一種解釋為:
該方法中利用了字尾++的特點,這個時候執行mapint.erase(it++);這條語句分為三個過程
1、先把it的值賦值給乙個臨時變數做為傳遞給erase的引數變數
3、再呼叫erase函式,釋放掉第一步中儲存的要刪除的it的值的臨時變數所指的位置。
然而個人感覺比較費解,意思是第一步先把it的值傳給了函式呼叫的形參,然後又回去執行i+1的操作嗎?這樣總感覺it++的執行被硬生生的切成了兩部分,只能硬記住這一結論。
直到後來看了《stl原始碼剖析》中的++i和i++實現方式的區別,然後某一天,再看到《more effective c++》裡的說明,突然開竅了,mapint.erase(it++)的機理終於不再神秘。
其實在mapint.erase(it++)中,it++確實是作為乙個完整的執行過程,it++的具體實現**其實類似以下:
// postfix form: fetch and increment
map::iterator operator++(int)//通過乙個多餘的int引數與prefix++區分
上面**的最終返回的值其實是tmp,tmp儲存的是*this的舊值,this後來通過increment函式自增了,但是tmp的依然保持原值,最後將tmp返回賦值作為erase的引數,所以在mapint.erase(it++)中,其實it++是作為乙個整體執行完成了的,在傳值給erase函式之前,it其自身其實已經+1了,不過字尾++返回的卻是乙個未執行+1操作的舊值,所以後面erase函式依然刪除的是原it位置的值,同時該迭代器失效,然而之前it已經+1自增過了,所以不受其影響噢。
關於上面**中呼叫的字首++**類似如下:
// prefix form: increment and fetch
map::iterator& operator++()
也正因為字尾++會比字首++的操作多乙個臨時變數,並且其是以傳值複製的方式返回給呼叫方,所以一般而言字尾++的效率會比字首++效率低一些。
值得一提的是,在最新的c++11標準中,已經新增了乙個map::erase函式執行後會返回下乙個元素的iterator,然而不知道啥時候c++11才能達到現在c++98的覆蓋程度,謹慎一點還是使用map.erase(it++)比較保險。
c++11
(1)
iterator erase (const_iterator position);
(2)
size_type erase (const key_type& k);
(3)
iterator erase (const_iterator first, const_iterator last);
最後,有的小夥伴可能會問為啥字首++和字尾++的返回值乙個是迭代器引用,乙個卻是迭代器傳值?簡單來說,字首++返回的便是傳參進來的迭代器,自然可以返回迭代器本身的引用,然而字尾++返回的是乙個函式內部的臨時變數,在函式執行完後便析構了,必然不能傳引用。注意既然是通過傳值的方式返回,對其返回值的修改對於原it是沒有影響的,舉例來說(it++)++的結果其實it只自增了一次,第二次++只是對其(it++)的返回值執行了++,對原it沒有任何效果。 STL 配置器和容器,序列容器和關聯容器總結
stl中定義了許多容器型別,這些型別在c 中非常實用。但是很多人卻並沒有理解為什麼叫配置器和容器,下面做個總結 vector 實現為一塊連續的記憶體,可以理解為動態擴張的array,具有隨機訪問的特性。但是不適合頻繁的隨機插入 刪除。list 實現為雙向鍊錶結構,適合頻繁隨機插入 刪除的操作。deq...
STL 關聯容器
1 關聯容器與順序容器的本質區別 關聯容器通過鍵 key 儲存和讀取元素,而順序容器則通過元素在容器中的位置順序儲存和訪問元素。2 關聯容器的型別 map set multimap multiset 3 pair型別 pair型別的比較 p1 p1 p2 如果兩個pair物件的first和secon...
stl 關聯容器
簡介 對於關聯容器,它的每個元素都有乙個鍵 key 容器中的元素的順序並不能人為隨意決定,而是按照鍵的取值公升序排列的。也就是說,對於乙個關聯容器s,使用迭代器在 s.begin s.end 區間內遍歷,訪問到的序列總是公升序的。分類 按照容器中是否允許出現重複鍵值,關聯容器可分為單重關聯容器和多重...