C++順序容器刪除元素時的一個小陷阱(C++ primer第四版習題9.26)
阿新 • • 發佈:2019-02-05
C++ 中刪除順序容器的操作有以下幾種:
c.erase(p) 刪除迭代器p所指向的元素,返回一個迭代器,它指向被刪除元素後面的元素
c.erase(b,e) 刪除迭代器b和e所標記的範圍內的所有元素,返回一個迭代器,指向被刪除元素段後面的元素。
c.clear() 刪除容器c內的所有元素,返回void
c.pop_back() 刪除容器c的最後一個元素,返回void
c.pop_front() 刪除容器c的第一個元素,返回void,該操作只適用於list和deque容器。
需要注意的是,erase操作不會檢查它的引數,所以必須確保用作引數的迭代器是有效的。
下面看題:
假設有如下ia的定義,將ia複製到一個vector容器和一個list容器中。使用單個迭代器引數版本的erase函式將list容器中的奇數值元素刪除掉,然後將vector容器中的偶數值元素刪除掉。
int ia[] = { 0,1,1,2,3,5,8,13,21,55,89 };
我覺得很容易,程式碼如下:
執行,結果不對。在linux下出現的問題是:Segmentation fualt (core dumped)#include <iostream> #include <vector> #include <list> using namespace std; int main() { int ia[] = { 0,1,1,2,3,5,8,13,21,55,89 }; vector<int> ivec(11); list<int> ilist(11); //複製過程 vector<int>::iterator av = ivec.begin(); list<int>::iterator al = ilist.begin(); for (int i = 0;i < 11;i++) { *av = ia[i]; *al = ia[i]; av++; al++; } //刪除部分 vector<int>::iterator av1 = ivec.begin(); for (int i = 0;i < 11;i++) { if (i % 2 != 0) ivec.erase(av1); av1++; } list<int>::iterator al1 = ilist.begin(); for (int i = 0;i < 11;i++) { if (i % 2 == 0) ilist.erase(al1); al1++; } //顯示部分 cout << "vector: " << endl; for (vector<int>::iterator a = ivec.begin();a != ivec.end();a++) cout << *a << " "; cout << endl; cout << "list: " << endl; for (list<int>::iterator a = ilist.begin();a != ilist.end();a++) cout << *a << " "; cout << endl; return 0; }
後來我想,肯定是在刪除元素的時候,在迭代器的有效範圍之外。
我們注意到刪除部分:
//刪除部分
vector<int>::iterator av1 = ivec.begin();
for (int i = 0;i < 11;i++) {
if (i % 2 != 0)
ivec.erase(av1);
av1++;
}
當刪除一個元素時,也就是執行一次ivec.erase(av1);
此時迭代器會指向哪兒呢?該操作的返回值是指向被刪除元素後一個元素的迭代器,那麼我想,刪除一個元素之後,當前迭代器指向的也是下一個元素,這時再執行就使得在一個迴圈之內迭代器往後移了兩位,所以最後會超出迭代器有效範圍。av1++;
修改程式碼如下:
#include <iostream>
#include <vector>
#include <list>
using namespace std;
int main()
{
int ia[] = { 0,1,1,2,3,5,8,13,21,55,89 };
vector<int> ivec(11);
list<int> ilist(11);
//複製過程
vector<int>::iterator av = ivec.begin();
list<int>::iterator al = ilist.begin();
for (int i = 0;i < 11;i++) {
*av = ia[i];
*al = ia[i];
av++;
al++;
}
//刪除部分
vector<int>::iterator av1 = ivec.begin();
for (int i = 0;i < 11;i++) {
if (i % 2 != 0)
ivec.erase(av1);
else
av1++;
}
list<int>::iterator al1 = ilist.begin();
for (int i = 0;i < 11;i++) {
if (i % 2 != 0)
ilist.erase(al1);
else
al1++;
}
//顯示部分
cout << "vector: " << endl;
for (vector<int>::iterator a = ivec.begin();a != ivec.end();a++)
cout << *a << " ";
cout << endl;
cout << "list: " << endl;
for (list<int>::iterator a = ilist.begin();a != ilist.end();a++)
cout << *a << " ";
cout << endl;
return 0;
}
如我所料,vector部分顯示正常,但是list又有問題。在網上找了一下,因為vector容器的元素是連續排列的,就像陣列那樣,所以可以用下標訪問,而且迭代器可以使用類似p+n這樣的運算,p是迭代器,n是常數,因為連續排列意味著知道其中一個元素的地址,就知道了所有元素的地址,每兩個相鄰的地址之間總是相差一個元素的長度大小。而list卻不一樣,它類似於連結串列,兩個相鄰元素之間地址相差多少根本不知道,因為它不是連續排列的,也因此我們沒辦法使用p+n這類的運算。在使用erase操作的時候,list每刪除一個元素,迭代器就失效了,所以在刪除時必須記錄下一個元素的地址,這樣才能保證程式的正常執行,而vector則不需要。
修改之後,程式碼如下:
#include <iostream>
#include <vector>
#include <list>
using namespace std;
int main()
{
int ia[] = { 0,1,1,2,3,5,8,13,21,55,89 };
vector<int> ivec(11);
list<int> ilist(11);
//複製過程
vector<int>::iterator av = ivec.begin();
list<int>::iterator al = ilist.begin();
for (int i = 0;i < 11;i++) {
*av = ia[i];
*al = ia[i];
av++;
al++;
}
//刪除部分
vector<int>::iterator av1 = ivec.begin();
for (int i = 0;i < 11;i++) {
if (i % 2 != 0)
ivec.erase(av1);
else
av1++;
}
list<int>::iterator al1 = ilist.begin();
for (int i = 0;i < 11;i++) {
if (i % 2 == 0)
al1 = ilist.erase(al1);
else
al1++;
}
//顯示部分
cout << "vector: " << endl;
for (vector<int>::iterator a = ivec.begin();a != ivec.end();a++)
cout << *a << " ";
cout << endl;
cout << "list: " << endl;
for (list<int>::iterator a = ilist.begin();a != ilist.end();a++)
cout << *a << " ";
cout << endl;
return 0;
}
總結:
1.要理解不同容器的內部構造到底是什麼樣子的,而不是單純的記憶個容器有哪些操作。
2.時刻記得迭代器是可能失效的,並且可能超出有效範圍。
3.光想是沒用的,一定要落實到每一行程式碼,哪怕覺得很簡單。