有時候會有這麼一種不太常見的需求:從一個map中刪除所有符合某種條件的項目,而這種條件與map的key基本沒啥關係。
比如,一個儲存學生花名冊的map,key為學號(int),value為姓名(string),如果要按學號刪除很容易,但如果需要刪除該map中所有姓“張”的學生,就只能遍曆它,逐一比較了。
using namespace std;<br />typedef map<int, string> MAP;<br />static bool canDrop(const MAP::value_type & v) {<br /> return 0 == strncmp(v.second.c_str(), "張", 2);<br />}
下面是顯而易見的錯誤方法
:
// 第一種方法(錯誤的方法)!!<br />for (MAP::iterator it = theMap.begin(); theMap.end()!=it; ++it) {<br /> if (canDrop(*it)) {<br /> // 錯誤:當erase執行以後,it立刻就失效了,在執行到for迴圈的++it時就會出現未知後果。<br /> theMap.erase(it);<br /> }<br /> }
如果用的是VC編譯器,由於它對erase方法的擴充,所以用下面的方法,可以正確完成需求:
// 第2種方法,僅適用於VC<br />for (MAP::iterator it = theMap.begin(); theMap.end()!=it;) {<br /> if (canDrop(*it)) {<br /> // VC擴充了erase方法,使它返回下一個合法的迭代器<br /> it = theMap.erase(it);<br /> } else {<br /> ++ it;<br /> }<br />}
可惜,C++標準中的map::erase()方法並不返回任何值,所以上述代碼在g++等編譯器中無法通過編譯。為了符合標準,下面有一種安全的方法——即將不需要刪除的項Copy到另一個新的容器裡,然後交換兩個容器的內容。
// 第3種方法,適用任何編譯器,但有額外開銷<br />MAP tmp; // 臨時容器<br />for (MAP::iterator it = theMap.begin(); theMap.end()!=it; ++it) {<br /> if (!canDrop(*it)) {<br /> // 複製不需要刪除的薦到臨時容器<br /> tmp.insert(*it);<br /> }<br />}<br />theMap.swap(tmp); // 交換兩個容器的內部,此時 theMap 中包含正確的項
這種“複製不需要刪除項到臨時容器”的方法符合C++標準,也很容易理解,但如果不需要刪除的項目數很多時(或者MAP的值拷貝開銷很大時),就不太適合了。下面是另一種方法:
// 第4種方法<br />for (MAP::iterator it = theMap.begin(); theMap.end()!=it;) {<br /> if (canDrop(*it)) {<br /> // 在erase之前,先遞增迭代器,此時由於erase尚未被調用,所以遞增後的迭代器有效<br /> // 然後用迭代器的原始值來調用erase,確何刪除正確的薦<br /> theMap.erase(it++);<br /> } else {<br /> ++it;<br /> }<br />}
上例中,it++這個操作,首先儲存了it的原始值,然後對it進行自增操作,由於此時erase尚未被調用,所以it的原始值有效,自增後的it也是有效;然後,將it的原始值傳遞給erase()方法,正確地刪掉項目。
現在的問題是,map的erase操作,除了影響被刪除項目的iterator(使指向這個項目的iterator變成無效),還會影響其它iterator嗎?比如說,下面這種方法來完成需求:
// 第5種方法<br />vector<MAP::iterator> itList;<br />// 儲存所有要刪除項目的iterator到臨時容器<br />for (MAP::iterator it=theMap.begin(); theMap.end() != it; ++it) {<br /> if (canDrop(*it)) {<br /> itList.push_back(it);<br /> }<br />}<br />// 遍曆臨時容器,取出裡面的iterator,逐一刪除<br />for (vector<MAP::iterator>::iterator it2 = itList.begin(); itList.end() != it2; ++it2) {<br /> theMap.erase( *it2 );<br />}
從測試結果來看,那些儲存在臨時容器裡的iterator,並不因為其它iterator的失效而失效。
不過,這是C++標準嗎?如果標準的確規定了erase並不影響其它iterator,那麼第四種方法顯然是最好的……可惜我查了半天沒查到。有知道的達人不妨告訴我一聲,謝謝。