總結一下map::erase的正確用法。
首先看一下在循環中使用vector::erase時我習慣的用法:
for(vector<int>::iterator it = vecInt.begin(); it != vecInt.end();) { if(*it == 0) { it = vecInt.erase(it); } else { it++; } }
程序從一個vector中刪除值為0的元素,利用了vector::erase函數根據iterator刪除某個元素時會返回下一個元素的iterator的性質:
http://www.cplusplus.com/reference/vector/vector/erase/
C++98 iterator erase (iterator position);
這一種用法是沒有問題的。
然而當想當然的在map::erase上照搬上面erase的用法時,就有問題了,查看http://www.cplusplus.com/reference/map/map/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<int,int>::iterator it = mapInt.begin(); it != mapInt.end();) { if(it->second == 0) { mapInt.erase(it++); } else { it++; } }
在網上找mapInt.erase(it++)的說明,比較詳細的一種解釋為:
http://blog.csdn.net/lmh12506/article/details/9167653
該方法中利用了后綴++的特點,這個時候執行mapInt.erase(it++);這條語句分為三個過程
1、先把it的值賦值給一個臨時變量做為傳遞給erase的參數變量
2、因為參數處理優先於函數調用,所以接下來執行了it++操作,也就是it現在已經指向了下一個地址。
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<int, int>::iterator operator++(int)//通過一個多余的int參數與prefix++區分 { map<int, int>::iterator tmp = *this; // fetch increment(); // increment,map內部由紅黑樹實現,此函數負責指向下一個有序元素的iterator return tmp; // return what was }
上面代碼的最終返回的值其實是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<int, int>::iterator& operator++() { increment(); // increment return *this; // fetch }
也正因為后綴++會比前綴++的操作多一個臨時變量,並且其是以傳值復制的方式返回給調用方,所以一般而言后綴++的效率會比前綴++效率低一些。
值得一提的是,在最新的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沒有任何效果。