1. 程式人生 > >STL — STL迭代器的原理以及迭代器失效

STL — STL迭代器的原理以及迭代器失效

當我們對普通iterator型別解引用時,得到對某個元素的非const引用。而如果我們對const_iterator型別解引用時,可以得到

一個指向const物件的引用,如同任何常量一樣,該物件不能進行重寫。例如,如果text是vector<string>型別,程式設計師想要

歷它,輸出每個元素,可以這樣編寫程式:


  // use const_iterator because we won't change the elements


  for (vector<string>::const_iterator iter = text.begin();


  iter != text.end(); ++iter)


  cout << *iter << endl; // print each element in text


除了是從迭代器讀取元素值而不是對它進行賦值之外,這個迴圈與前一個相似。由於這裡只需要藉助迭代器進行讀
,不需要寫,這

把iter定義為 const_iterator型別。當對const_iterator型別解引用時,返回的是一個const值。不允許用const_iterator進行賦值:


  for (vector<string>::const_iterator iter = text.begin();

  iter != text.end(); ++ iter)

  *iter = " "; // error: *iter is const

使用const_iterator型別時,我們可以得到一個迭代器,它自身的值可以改變,但不能用來改變其所指向的元素的值。可以對迭代

器進行自增以及使用解引用操作符來讀取值,但不能對該元素值賦值.

迭代器的實現原理

當你創造出來一個東西的時候,那麼你對他的使用或者對於他的bug可能爛熟於心了吧.首先我們知道迭代器就是通過一定的封裝

然後暴露出介面,再然後進行使用. 我們不需要知道底層的封裝,我們記住它的方法如何使用就可以了. 但是我今天偏偏就要明

迭代器的底層實現,通過我最近檢視源代碼,其實跟iterator跟指標是非常相似的,擁有 *

-> 操作,並且具有

 ++,--,== ,!= ,目前為止我們就只實現這幾個就夠了.現在我要寫一個山寨版的list容器的iterator的定義.  

其他容器大概都是這麼一個框架實現,只是實現具體方法不同.

程式碼實現:

template<class T, class Ref, class Ptr>
struct __ListIterator
{
	typedef __ListNode<T> Node;
	typedef __ListIterator<T, Ref, Ptr> Self;

	Node* _node;

	__ListIterator(Node* x)
		:_node(x)
	{}

	Ptr operator->()
	{
		return &(operator*())
	}
	Ref operator*()
	{
		return _node->_data;
	}

	Ptr operator->() const
	{
		return &(operator*()) const;
	}

	Ref operator*() const
	{
		return _node->_data const;
	}
	bool operator != (const Self& s)
	{
		return _node != s._node;
	}

	inline Self& operator++()//前置++
	{
		_node = _node->_next;
		return *this;
	}
	Self& operator++(int)  //後置++
	{
		//第一種先初始化再返回.
		/*Self tmp(*this);
		_node = _node->_next;
		return tmp;*/

		//第二種返回的時候再初始化.
		Node* cur = _node;
		_node = _node->_next;
		return Self(cur);

		//第一種做了三件事情. 首先呼叫了建構函式構造一個tmp,返回的時候再呼叫拷貝構造把自己拷給外部儲存,最後呼叫解構函式讓自己析構.
		//第二種做了一件事情,在外部儲存初始化一個tmp就Ok了. 所以效率來說第二種方法是最優解決方案.
	}

	inline Self& operator--()
	{
		_node = _node->_prev;
		return *this;
	}

	Self& operator--(int)
	{
		Node* cur = _node;
		_node = _node->_prev;
		return Self(cur);
	}

	bool operator != (const Self& s) const
	{
		return this->_node != s._node;
	}

	bool operator ==(const Self& s) const
	{
		return !(operator!=(s));
	}
};

我們看到迭代器的建構函式是一個單引數建構函式,其實我們發現迭代器跟智慧指標蠻相似的,都是底層封裝一個指標,最後對

個類進行各種各樣的運算子過載. 讓它成為一個擁有指標功能的特殊類. 接下來我們在list容器當中定義它. 

template<class T>
class List
{
typedef __ListNode<T> Node;
public:
 
typedef __ListIterator<T, T&, T*> Iterator;
 
typedef __ListIterator<const T, const T&, const T*> ConstIterator;
}

可能上面有的朋友看不懂為什麼會有三個模板引數?  現在明白了吧.這些都是為了程式碼的複用.要不然當你要實現const_iterator

的時候又得重新寫一個const__ListIterator的類了. T& T* 這些都是要在程式碼中高頻出現所以我們也給他們開出來一個模板引數

位置,到時候iterator和const_iterator都要使用相同的模板框架,直接巢狀進去就ok. 如圖所示:

 

因為iterator指向節點所以他就可以訪問並操作節點節點的內容,而const_iterator只能訪問到元素不能夠操作節點. 這是迭代器

定義.有木有覺得它其實沒有你想的那麼抽象. 仔細想一想我相信你可以想明白了.接下來是一個迭代器很重要的問題> 迭代器

失效. 接下來我們引出來這個問題吧. 首先我們開始完善我們的山寨List,什麼push_back,popback這些我們寫過很多遍. 我下面

會有完整程式碼的.  我們今天著重研究一下erase刪除函式. 像我們正常實現的erase函式只是將引數改為迭代器即可. 

下面是我對該函式的實現;

void erase(Iterator& it)
{
assert(it != End() && it._node);
 
Node* cur = it._node;
Node* prev = cur->_prev;
Node* next = cur->_next;
 
prev->_next = next;
next->_prev = prev;
 
delete cur;
}

這個刪除對於雙向迴圈連結串列應該是沒有bug的. 然後這個時候我做一個操作, 我利用迭代器刪掉元素當中所有偶數:

void Test()
{
List<int> l;
l.PushBack(1);
l.PushBack(2);
l.PushBack(3);
l.PushBack(4);
 
List<int>::Iterator it = l.Begin();
 
while (it != l.End())
{
if (*it % 2 == 0)
{
l.erase(it);
}
++it;
}
it = l.Begin();
while (it != l.End())
{
cout << *it << " ";
++it;
}
cout << endl;
}

執行結果:


正常情況就是最後視窗打印出來1 3. 好的那我們執行程式.結果程式崩了,居然還是越界. 經過我除錯之後我發現程式在呼叫

earse之前沒問題,呼叫earse之後就出現問題. 我再繼續除錯,發現第一次刪除後,節點被刪除了是沒有問題的,而且連線也

沒有問題,但是接下來的一次*it就訪問越界了,

所以我有理由相信這裡的問題出在it上面,所以我返回第一次刪除前除錯.


我們發現第一次earse之前,It的值還沒問題. 

 

但是earse之後,我們發現it變成一個隨機值,原來這個就是我們程式崩潰的罪魁禍首.當刪除掉該節點之後,該節點的iterator

為隨機值,所以後面的++it,生成的還是一個隨機值,最後下次*it是後就會發生訪問越界. 這個就是我們的迭代器失效問題.

突然一個很重要的問題就被引出來了,這就是迭代器失效,就是你刪除該節點後節點的迭代器因為變成一個隨機值,沒有辦

法跳轉到下一個節點. 這個時候你的迭代器++之後就是一個隨機值,最後程式再次使用到it時,程式崩潰. 這就類似於野指

針的問題.

那麼問題來了,我們該如何解決它? 既然節點被刪了,它的迭代器也就成隨機值了,那我每次刪除完就要對iterator重新定

義一次,我們每次刪除結束之後我們把就是刪除節點的上一個節點的Iterator,賦給it,這時候++it的時候,迭代器就會跳

轉到下一個節點的位置. 具體如何實現呢? 那我們就來嘗試一下.

void earse(Iterator& it)
{
assert(it != End() && it._node);
 
Node* cur = it._node;
Node* prev = cur->_prev;
Node* next = cur->_next;
 
prev->_next = next;
next->_prev = prev;
 
delete cur;
it = prev;//這裡發生了隱式轉換 類似於 it = Iterator(prev); 單引數的建構函式. 就會發生隱式轉換.
}

我把it的引用傳進去,然後在刪除cur之後,把it賦值為cur前面的節點的迭代器. 接下來我們來看程式結果:


從結果上面來看,我們應該是成功了,迭代器失效每個容器導致的原因好像都不一樣,但是他們的原理都是因為不當操作使得

iterator失去它自己的內容變為隨機值,我們應該避免這些東西. 就能夠熟練掌握迭代器. 下面我把整個山寨List的全套程式碼

貼在下面,有空我們可以去嘗試山寨別的容器,提高自己對容器的理解和認識.

List實現程式碼:

template<class T>
struct __ListNode
{
	__ListNode<T>* _next;
	__ListNode<T>* _prev;
	T _data;

	__ListNode(const T& x)
		:_data(x)
		, _next(NULL)
		, _prev(NULL)
	{}
};

template<class T, class Ref, class Ptr>
struct __ListIterator
{
	typedef __ListNode<T> Node;
	typedef __ListIterator<T, Ref, Ptr> Self;

	Node* _node;

	__ListIterator(Node* x)
		:_node(x)
	{}

	//T& operator*()
	Ptr operator->()
	{
		return &(operator*())
	}
	Ref operator*()
	{
		return _node->_data;
	}

	Ptr operator->() const
	{
		return &(operator*()) const;
	}

	Ref operator*() const
	{
		return _node->_data const;
	}
	bool operator != (const Self& s)
	{
		return _node != s._node;
	}

	inline Self& operator++()
	{
		_node = _node->_next;
		return *this;
	}
	Self& operator++(int)
	{
		//第一種先初始化再返回.
		/*Self tmp(*this);
		_node = _node->_next;
		return tmp;*/

		//第二種返回的時候再初始化.
		Node* cur = _node;
		_node = _node->_next;
		return Self(cur);

		//第一種做了三件事情. 首先呼叫了建構函式構造一個tmp,返回的時候再呼叫拷貝構造把自己拷給外部儲存,最後呼叫解構函式讓自己析構.
		//第二種做了一件事情,在外部儲存初始化一個tmp就Ok了. 所以效率來說第二種方法是最優解決方案.
	}

	inline Self& operator--()
	{
		_node = _node->_prev;
		return *this;
	}

	Self& operator--(int)
	{
		Node* cur = _node;
		_node = _node->_prev;

		return Self(cur);
	}

	bool operator != (const Self& s) const
	{
		return this->_node != s._node;
	}

	bool operator ==(const Self& s) const
	{
		return !(operator!=(s));
	}
};

template<class T>
class List
{
	typedef __ListNode<T> Node;
public:

	typedef __ListIterator<T, T&, T*> Iterator;

	typedef __ListIterator<const T, const T&, const T*> ConstIterator;

	List()
	{
		_head = new Node(T());
		_head->_next = _head;
		_head->_prev = _head;
	}

	Iterator Begin()
	{
		return Iterator(_head->_next);
	}

	ConstIterator Begin() const
	{
		return ConstIterator(_head->_next);
	}

	Iterator End()
	{
		return Iterator(_head);
	}

	ConstIterator End() const
	{
		return ConstIterator(_head);
	}

	T& Front()
	{
		return _head->next;
	}

	T& back()
	{
		return _head->_prev;
	}
	void PushBack(const T& x)
	{
		Insert(End(), x);
	}

	void PushFront(const T& x)
	{
		Insert(Begin(), x);
	}

	void popFront()
	{
		earse(Begin());
	}

	void popBack()
	{
		earse(--End());
	}

	void Insert(Iterator pos, const T& x)
	{
		assert(pos._node);

		Node* next = pos._node;
		Node* prev = next->_prev;
		Node* cur = new Node(x);

		prev->_next = cur;
		cur->_prev = prev;

		cur->_next = next;
		next->_prev = cur;
	}


	void earse(Iterator& it)
	{
		assert(it != End() && it._node);

		Node* cur = it._node;
		Node* prev = cur->_prev;
		Node* next = cur->_next;

		prev->_next = next;
		next->_prev = prev;

		delete cur;
		it = prev;//這裡發生了隱式轉換 類似於 it = Iterator(prev); 單引數的建構函式. 就會發生隱式轉換.	
	}

	void Clear()
	{
		Iterator it = Begin();
		while (it != End())
		{
			Node* del = it._node;
			++it;
			delete del;
		}
		_head->_next = _head;
		_head->_prev = _head;
	}

	Iterator Find(const T& x)
	{
		Iterator it = Begin();
		while (it != End())
		{
			if (*it == x)
				return it;
			++it;
		}

		return End();
	}

protected:
	Node* _head;
};