1. 程式人生 > >STL_vector(01)迭代器失效問題

STL_vector(01)迭代器失效問題

我們先來看看下面的程式碼:

#include<iostream>
#include<vector>
#include<windows.h>
using namespace std;

int main()
{
	vector<int>v2(6, 8);
        v2.push_back(3);
	v2.push_back(5);
	vector<int>::iterator pos = v2.begin();
	while (pos != v2.end())
	{
		if (*pos % 2 == 0)
		{
			v2.erase(pos);
                        ++pos;
		}
	}
	system("pause");
	return 0;
}
	

編譯執行這串程式碼,你會發現它毫不猶豫的崩潰了。當進行v.erase()操作時,作用點前的元素沒有改變,但作用點後的元素向前移動了一位,使得pos後的各元素迭代器失效。同樣v.insert()操作也會使得迭代器失效,失效和v.erase()操作同理。

那麼如何解決這個問題呢?看如下程式碼:

#include<iostream>
#include<vector>
#include<windows.h>
using namespace std;

int main()
{
	vector<int>v2(6, 8);
        v2.push_back(3);
	v2.push_back(5);
	vector<int>::iterator pos = v2.begin();
	while (pos != v2.end())
	{
		if (*pos % 2 == 0)
		{
		     pos=v2.erase(pos);     
		}
                 else
                 {
                     ++pos;
                 }
	}
	system("pause");
	return 0;
}
	

進行刪除操作後,erase會重新返回一個有效的迭代器,這時重新賦值個pos,所以這個迭代器的操作就能正常進行下去了。所以,對於在定義迭代器之後進行的插入和刪除操作必須格外小心,防止出現迭代器失效問題。或者說盡量不要在迭代器定義之後進行插入和刪除操作。

第二種情況程式碼如下:

#include<iostream>
#include<vector>
#include<windows.h>
using namespace std;

int main()
{
	vector<int>v2(6, 8);
        v2.push_back(3);
        v2.push_back(5);
	vector<int>::iterator pos = v2.begin();
	v2.push_back(3);//當v2.capacity滿的時候,發生記憶體拷貝,所有的迭代器均會失效
        while (pos != v2.end())
	{
		if (*pos % 2 == 0)
		{
		     pos=v2.erase(pos);     
		}
                 else
                 {
                     ++pos;
                 }
	}
	
    return 0;
}

這裡我們在迭代器定義之後進行了尾插操作,但程式執行正常,其結果和預期相符。這是因為pos後沒有元素。這個例子佐證了作用點pos插入和刪除並不影響作用點前面元素。

再來看如下程式碼:

#include<iostream>
#include<vector>
#include<windows.h>
using namespace std;

int main()
{
	vector<int>v2(6, 8);
        v2.push_back(3);
        v2.push_back(5);
        v2.push_back(9);//此時v2.size()=v2.capacity()
	vector<int>::iterator pos = v2.begin();
	v2.push_back(3);//當v2.capacity滿的時候,發生記憶體拷貝,所有的迭代器均會失效
         while (pos != v2.end())
	{
		if (*pos % 2 == 0)
		{
		     pos=v2.erase(pos);     
		}
                 else
                 {
                     ++pos;
                 }
	}
    return 0;
}

v2.capacity=9,初始時我們插入了6個元素,隨後在定義迭代器前插入了兩個元素,此時v2.capacity=8;為什麼要關注v2.capacity呢?因為只有當我們實際需要的容量大於當前分配的容量時,vector為了存放多餘的元素,它必須申請重新開闢空間,這個時候就會發生記憶體拷貝,這個時候迭代器將會全部失效。

根據以上情況我們得出迭代器失效情況:

  • 在進行插入、刪除等操作
  • 擴容時發生記憶體拷貝

如何避免迭代器失效問題的發生

最簡單的方法就是儘量不要在定義迭代器後進行插入和刪除等操作。如果必須這樣操作,請務必謹慎。你可能發現,如果當迭代器返回的時begin的值且容量足夠,之後進行push_back並不會讓迭代器失效。儘管可以這樣,但我還是不建議你這樣做。

C++的靈活賦予了它強大的功能,但同時特帶來了極大的危險性。在C++中需要注意的問題有很多,難免不會操作失誤,我們能做的就是將失誤的可能性降到最低。規範的使用才能更好的運用。

下面給出教材上對於迭代器失效問題的總結:

《C++標準程式庫》第六章STL容器

  1. 使用者在一個較小索引位置上安插和移除操作 
  2. 由於容量變化而引起記憶體分配
  • 當在vector中的某一位置安插(v.insert())或移除(v.erase())某個元素時,且當安插操作時,容器還有一定的容量(v.capacity())來容納這個元素。如此一來,安插和移除操作不會因容器滿而重新分配記憶體。在安插和移除操作後,作用點位置前的元素並沒改變,而操作位置後的元素向後或向前移動一位。因此作用點後的各元素迭代器失效。注意此處的失效只是與安插或移位前定義的迭代器的意圖出現稍微偏差。例如安插之前定義了一個逆向迭代器,安插後便無法通過該迭代器遍歷所有元素(會丟掉一個元素}。 
  • 首先,我們知道vector實際上是連續儲存的動態陣列,因此當容器滿時,為了保證連續儲存需要重新開闢空間並將其原有陣列拷貝到新空間,這使得原來空間的迭代器全部失效。此處的失效會在除錯時出現debug assertion failed錯誤,這是由訪問一個野指標而引起。注意vector為其分配記憶體的機制為:每次當容器滿而重新分配記憶體時,都會分配比所需記憶體多的空間,因此,大多數安插和移除元素並不會發生記憶體的重新分配。這在一定程度上優化了vector的效率,避免了每次插入都進行記憶體重新分配這一耗時的操作。但是,儘管如此,我們也應堅決杜絕使用安插操作前定義的迭代器。
     

以下是對於vector介面的練習程式碼:

#include<iostream>
#include<vector>
#include<windows.h>
using namespace std;

int main()
{
	//Vector 建立和列印
	int a[] = { 1,2,3,4,5 }; 
	vector<int>v1(a, a + sizeof(a) / sizeof(a[0]));
	vector<int>v2(6, 8);//6個整型空間,值為8
	vector<int>v3(v2.begin(), v2.end());//迭代器構造v3
	vector<int>v4(v2);//拷貝構造

	//vector<int>::iterator it = v2.begin();
	//while (it != v2.end())//正向迭代器列印
	//{
	//	cout << *it << " ";
	//	++it;
	//}
	//cout << endl;

	//for (unsigned int i = 0; i < v3.size(); i++)//陣列方式列印
	//{
	//	cout << v3[i] << " ";
	//  //cout << v3.at(i);
	//}
	//cout << endl;
	//
	//for (auto i : v4)//範圍for列印
	//{
	//	cout << i<<" ";
	//}
	//cout << endl;

	//v2.push_back(1);//尾插資料
	//v2.push_back(3);

	//vector<int>::reverse_iterator rit = v2.rbegin();
	//while (rit != v2.rend())//反向迭代器列印
	//{
	//	cout << *rit << " ";
	//	++rit;
	//}
	//cout << endl;
	//
	////capacity
	//unsigned int size = v2.size();//獲取元素個數
	//unsigned int capa = v2.capacity();//獲取容量
	//int ifempty = v2.empty();//檢查是否為空
	//cout << size << " " << capa << " " << ifempty << endl;
	//v2.resize(7,8);//如果resize比原來size大且比capacity大,首先擴充套件capacity,然後將多餘的位置使用設定的字元填充,如不設定則預設為0。否則只改變size,不改變capacity。
	//v2.reserve(7);//設定capacity,只有要設定的容量比原來的大,capacity才會改變
	//cout << v2.capacity() << endl;
	//
	////vector的增刪改查
	//v2.push_back(2);
	//v2.pop_back();//尾刪
	//v2._Pop_back_n(2);//尾刪2個數據
	//v2.push_back(4);
	//vector<int>::iterator pos = find(v2.begin(), v2.end(), 4);// 使用find查詢4所在位置的iterator
	//v2.insert(pos, 6);
	//pos = find(v2.begin(), v2.end(), 8);//從前到後查詢第一個8所在位置的iterator
	//v2.erase(pos);
	//v2.swap(v3);//v2與v3交換
	//for (auto i : v2)//範圍for列印
	//{
	//	cout << i<<" ";
	//}
	//cout << endl;

	//vector的失效問題
	v2.push_back(3);
	v2.push_back(9);//此時v2.size()=v2.capacity()
	vector<int>::iterator pos = v2.begin();
	/*vector<int>::iterator pos=v2.end();
	cout << v2.size() << " " << v2.capacity() << endl;
	v2.push_back(3);//end返回的迭代器操作之後進行插入操作,修改了迭代器,使得迭代器失效*/
	v2.push_back(3);//當v2.capacity滿的時候,發生記憶體拷貝,所有的迭代器均會失效
	while (pos != v2.end())
	{
		if (*pos % 2 == 0)
		{
			pos=v2.erase(pos);
		}
		else
		{
			++pos;
		}
	}
	for (auto i : v2)
	{
	cout << i << " ";
	}
	system("pause");
	return 0;
}