STL序列式容器中刪除元素的方法和陷阱 一 (轉)
在STL(標準模板庫)中經常會碰到要刪除容器中部分元素的情況,本人在中就經常編寫這方面的程式碼,在編碼和測試過程中發現在STL中刪除容器有很多陷阱,網上也有不少網友提到如何在STL中刪除元素這些問題。本文將討論程式設計過程中最經常使用的兩個序列式容器vector、list中安全刪除元素的方法和應該注意的問題, 其它如queue、stack等配接器容器(container adapter),由於它們有專屬的操作行為,沒有迭代器(iterator),不能採用本文介紹的刪除方法,至於deque,它與vector的刪除方法一樣。STL容器功能強大,but no siliver bullet,如果你使用不當,也將讓你吃盡苦頭。
1.手工編寫for迴圈程式碼刪除STL序列式容器中元素的方法:namespace prefix = o ns = "urn:schemas--com::office" />
例如,你能看出以下程式碼有什麼問題?
例1:
#include
#include
using namespace std;
void main( ) {
vector
int i;
// 初始化vector容器
for (i = 0; i < 5; i++ ) {
vectInt.push_back( i );
}
// 以下程式碼是要刪除所有值為4的元素
vector
for ( ; itVect != vectInt.end(); ++itVect ) {
if ( *itVect == 4 ) {
vectInt.erase( itVect );
}
}
int iSize = vectInt.size();
for ( i = 0 ; i < iSize; i++ ) {
cout << " i= " << i <
}
}
例2:
#include
#include
using namespace std;
void main( ) {
vector
int i;
// 初始化vector容器
for ( i = 0; i < 5; i++ ) {
vectInt.push_back( i );
if ( 3 == i ) {
// 使3的元素有兩個,並且相臨。這非常關鍵,否則將發現不了。
// 具體解釋見下。
vectInt.push_back( i );
}
}
vector
vector
// 以下程式碼是要刪除所有值為3的元素
for ( ; itVect != itVectEnd; ++itVect ) {
if ( *itVect == 3 ) {
itVect = vectInt.erase( itVect );
}
}
int iSize = vectInt.size();
for ( i = 0 ; i < iSize; i++ ) {
cout << " i= " << i <
}
例3:
#include
#include
using namespace std;
void main( ) {
vector
int i;
vectInt[ 0 ] = 0;
vectInt[ 1 ] = 1;
vectInt[ 2 ] = 2;
vectInt[ 3 ] = 3;
vectInt[ 4 ] = 4; // 替換為 vectInt[ 4 ] = 3;試試
vector
vector
// 以下程式碼是要刪除所有值為3的元素
for ( ; itVect != itVectEnd; ) {
if ( *itVect == 3 ) {
itVect = vectInt.erase( itVect );
}
else {
++itVect;
}
}
int iSize = vectInt.size();
for ( i = 0 ; i < iSize; i++ ) {
cout << " i= " << i <
}
}
分析:
這裡最重要的是要理解erase成員,它刪除了itVect迭代器指向的元素,Mailto:e4e dateTime=2002-10-28T12:47>並且返回要被刪除的itVect之後的迭代器,迭代器相當於一個智慧指標,指向容器中的元素,現在刪除了這個元素,將導致重新分配,相應指向這個元素的迭代器之後的迭代器就失效了,但erase成員函式返回要被刪除的itVect之後的迭代器。
例1將導致未定義的錯誤,在中即是訪問記憶體,程式當掉。因為vectInt.erase( itVect );後itVect之後的迭代器已無效了,所以當++itVect後,*itVect訪問了非法記憶體。例1也是初學者最容易犯的錯誤,這個錯誤也比較容易發現。
例2可能會導致不能把vectInt中所有為3的元素刪除掉。因為第一次刪除成功時,itVect = vectInt.erase( itVect );itVect為指向3之後的位置,之後再執行++itVect,itVect就掉過了被刪除元素3之後的元素3,導致只刪除了一個為3的元素,這個bug比較隱蔽,因為如果不是兩個均為3的元素相臨,就將很難捕捉到這個bug,程式有可能在一段時間執行良好,但如碰到容器中兩值相同的元素相臨,則程式就要出問題。
例3,對於本例你可能要說程式沒有任何問題,解決了上面的兩個bug,程式也執行正常。但且慢,你把 “vectInt[ 4 ] = 4;” 這一行改為 “vectInt[ 4 ] = 3;”試試,一執行,程式當掉,訪問非法記憶體!你疑惑不解:從程式看不出bug,而且我還把vectInt.end()放在外面計算以防止for多重計算,提高。哈哈,問題就出在最後一句話!演算法大師Donald Knuth有一句名言:不成熟的是一切惡果的根源( Permature optimization is the of all evil )。由於在for迴圈中要刪除元素,則vectInt.end()是會變化的,所以不能在for迴圈外計算,而是每刪除一次都要重新計算,所以應放在for迴圈內。那你要問,為什麼把 “vectInt[ 4 ] = 4;” 這一行改為 “vectInt[ 4 ] = 3;”程式就會當掉,而不改程式就很正常呢?這就跟vector的實現機制有關了。下面以圖例詳細解釋。
vectInt的初始狀態為:
| end
0 1 2 3 4
刪除3後,
|新的end | 原來的end
0 1 2 4 4
注意上面“新的end”指向的記憶體並沒有被清除,為了效率,vector會申請超過需要的記憶體儲存資料,刪除資料時也不會把多餘的記憶體刪除。
然後itVect再執行++itVect,因為此時*itVect等於4,所以繼續迴圈, 這時itVect 等於“新的end”但不等於“原來的end”(它即為itVectEnd),所以繼續,因為 *itVect訪問的是隻讀記憶體得到的值為4,不等於3,故不刪除,然後執行++itVect此時itVect等於itVectEnd退出迴圈。從上面過程可以看出,程式多迴圈了一次(刪除幾次,就要多迴圈幾次),但程式正常執行。
如果把 “vectInt[ 4 ] = 4;” 這一行改為 “vectInt[ 4 ] = 3;”過程如下:
| end
0 1 2 3 3
刪除3後,
|新的end |原來的 end
0 1 2 3 3
刪除第2個3後,
|新的end |原來的 end
0 1 2 3 3
這時itVect 等於“新的end”但不等於“原來的end”(它即為itVectEnd),所以繼續,因為 *itVect訪問的是隻讀記憶體得到的值為3,等於3,所以執行刪除,但因為*itVect訪問的是隻讀記憶體不能刪除,所以程式當掉。
綜上,我們知道當要刪除的值在容器末尾時,會導致程式刪除非法記憶體,程式當掉;即使程式正常執行,也是for迴圈多執行了等於刪除個數的迴圈。所以把vectInt.end()放在for迴圈外面執行,完全是錯誤的。對於list容器,list.end()在刪除過程中是不會變的,可以把它放在for迴圈外面計算,但由於list.end()是個常量,把list.end()放在for迴圈中計算應該可以最佳化它。從安全考慮,除非你能保證for迴圈中不會改變容器的大小,否則都應該對容器的值在for迴圈中計算,對於 vectInt.size()這樣的計算,也應該在for迴圈中計算,不要因為微小的最佳化而導致程式出錯。
正確的方法:
例4:
#include
#include
using namespace std;
void main( ) {
vector
int i;
for ( i = 0; i < 5; i++ ) {
vectInt.push_back( i );
if ( 3 == i ) {
// 使3的元素有兩個,並且相臨。
vectInt.push_back( i );
}
}
vector
// 以下程式碼是要刪除所有值為3的元素
for ( ; itVect != vectInt.end(); ) { // 刪除 ++itVect{
if ( *itVect == 3 ) {
itVect = vectInt.erase( itVect );
}
else {
++itVect;
}
}
// 把vectInt.size()放在for迴圈中
for ( i = 0 ; i < vectInt.size(); i++ ) {
cout << " i= " << i <
}
執行結果為:
i= 0, 0
i= 1, 1
i= 2, 2
i= 3, 4
從結果顯示值為3的元素確實被刪除了。來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10748419/viewspace-1004615/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- STL關聯式容器中刪除元素的方法和陷阱 四 (轉)
- STL.vector容器刪除單個元素、部分元素、全部元素
- C++ 順序容器的刪除操作C++
- PHP從陣列中刪除元素的方法PHP陣列
- 建立元素和刪除元素
- Python 中刪除列表元素的三種方法Python
- Java ArrayList刪除特定元素的方法Java
- Java刪除ArrayList中的重複元素的2種方法Java
- 關於二叉樹的前序遍歷、中序遍歷、刪除元素、插入元素二叉樹
- 批量刪除容器和映象
- c/c++ 標準順序容器 容器的訪問,刪除 操作C++
- JavaScript 建立和刪除元素JavaScript
- C++ 順序容器中訪問元素C++
- docker 批量刪除容器和映象Docker
- 三種方法刪除列表中重複的元素及效率分析!
- 如何刪除ArrayList中的重複元素
- Python列表刪除元素的方法有哪些?Python
- 進階篇_STL中的容器
- docker刪除所有容器和映象命令Docker
- STL容器的各個函式方法函式
- js刪除陣列中重複的元素JS陣列
- js刪除陣列中的重複元素JS陣列
- js刪除陣列元素中的指定值JS陣列
- JS] JS 之刪除陣列中的元素JS陣列
- 刪除陣列中的元素(連結串列)陣列
- jQuery為元素新增和刪除classjQuery
- jQuery如何新增和刪除元素jQuery
- JS刪除陣列裡的某個元素方法JS陣列
- 關於javascript中陣列元素刪除問題的討論 (轉)JavaScript陣列
- jQuery刪除元素jQuery
- jQuery 刪除元素jQuery
- oracle 快速刪除和快速插入的方法之一Oracle
- ES6刪除字串中重複的元素字串
- 啟動或刪除Docker容器和映象Docker
- JavaScript動態新增和刪除div元素JavaScript
- javascript如何動態新增和刪除元素JavaScript
- Python優雅遍歷字典刪除元素的方法Python
- PHP 如何根據鍵值刪除一個陣列中的元素PHP陣列