1. 程式人生 > >C++順序容器刪除元素時的一個小陷阱(C++ primer第四版習題9.26)

C++順序容器刪除元素時的一個小陷阱(C++ primer第四版習題9.26)

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 };

我覺得很容易,程式碼如下:

#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;
}
執行,結果不對。在linux下出現的問題是:Segmentation fualt (core dumped)

後來我想,肯定是在刪除元素的時候,在迭代器的有效範圍之外。

我們注意到刪除部分:

//刪除部分
	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.光想是沒用的,一定要落實到每一行程式碼,哪怕覺得很簡單。