一步一步的理解C++STL迭代器


一步一步的理解C++STL迭代器

        “指針對全部C/C++的程序猿來說,一點都不陌生。

在接觸到C語言中的malloc函數和C++中的new函數后。我們也知道這兩個函數返回的都是一個指針。該指針指向我們所申請的一個。提到。就不得不想到。從C/C++程序設計的角度思考,最大的差別是由系統自己主動分配而且自己主動回收,而則是由程序猿手動申請。而且顯示釋放。假設程序猿不顯示釋放。便會造成內存泄漏。內存泄漏的危害大家知道,嚴重時會導致系統崩潰。

       既然“指針”的使用者一不小心就可能導致內存泄漏,那么我們怎樣可以使得指針的使用變得更安全呢?從C++面向對象的角度分析,我們有沒有可能將“指針”封裝起來,使得用戶不直接接觸指針,而使用一個封裝后的對象來替代指針的操作呢?

        答案是顯然的,“智能指針”(smart pointer)正解決這類問題,尤其是在防止內存泄漏方面做得很突出。

C++標准庫std中提供了一種“智能指針類”名為"auto_ptr",先看以下的代碼:

void f()
{
	int *p=new int(42);
	////////此處發生異常////
	delete p;
}

       正如上面的代碼所看到的。假設在兩條語句中間發生異常,會導致指針p所指的那塊內存泄漏。由於在執行delete之前發生異常,就不會自己主動釋放堆。然而。假設使用auto_ptr對象取代常規指針,將會自己主動釋放內存,由於編譯器可以保證提前執行其析構函數。這樣,我們就行將上述代碼改為:

#include<memory>//auto_ptr的頭文件
void f()
{
	auto_ptr<int>p(new int (42));
}

       通過以上的分析。我們便對智能指針有了一定的了解。

迭代器iterator就是一種智能指針。它對原始指針進行了封裝,而且提供一些等價於原始指針的操作,做到既方便又安全。

       一提到STL,必需要立即想到其基本的6個組成部件,各自是:容器、算法、迭代器、仿函數、適配器和空間分配器。本文主要介紹迭代器。

迭代器是連接容器和算法的一種重要橋梁。

為什么呢?我們舉個樣例來說明原因。

#include<iostream>
#include<algorithm>//用到find函數
#include<vector>
using namespace std;
int main()
{
	vector<int>vec;
	int i;
	for(i=0;i<10;i++)
	{
		vec.push_back(i);
	}
	if(vec.end()!=find(vec.begin(),vec.end(),7))
	{
		cout<<"find!"<<endl;
	}
	else
	{
		cout<<"not find!"<<endl;
	}
	system("pause");
	return 0;
}

        上述代碼中。值得注意的是用到的find函數,find函數的函數原型為:template<class _InIt,class _Ty> _InIt find(_InIt _First, _InIt _last, const _Ty & _val),從find函數原型能夠看出find函數是一個函數模板,函數的形參中前兩個參數為_InIt。該參數是InputIterator的縮寫。最后一個參數則是一個隨意類型變量的引用。而我們的程序在使用find函數實。傳入的實參為find(vec.begin(),vec.end(),7)。這樣我們的形參迭代器就將算法find和容器vector聯系起來了,從這個角度我們能夠非常easy理解為什么說迭代器是算法和容器的聯系的橋梁了

       為什么上面的形參是一個InputIterator,而可以接受實參vec.begin()呢?為了回答這個問題,須要從迭代器的分類說起。

        在STL中,迭代器主要分為5類。各自是:輸入迭代器、輸出迭代器、前向迭代器、雙向迭代器和隨機訪問迭代器。

        輸入迭代器:僅僅讀。支持++、==、!=;

        輸出迭代器:僅僅寫,支持++;

        前向迭代器:讀寫,支持++、==、!=。

        雙向迭代器:讀寫,支持++、--。C++的全部標准庫容器都至少在雙向迭代器的層次上。;

        隨機訪問迭代器:讀寫,支持++、--、[n]、-n、<、<=、>、>=。

        輸入迭代器和輸出迭代器是最低級的迭代器。后三種迭代器都是對該迭代器的一種派生,回到剛剛那個問題。為什么實參vec.begin()可以與形參_InIt“虛實結合”呢?我們先看以下的代碼:

#include<iostream>
using namespace std;
class A{};
class A1:public A{};//類A1繼承A
class B{};
void print(A)//僅僅需指定參數類型
{
	cout<<"This is Base class A!"<<endl;
}
void print(B)//僅僅需指定參數類型
{
	cout<<"This is Base class B!"<<endl;
}
int main()
{
	print(A());
	print(B());
	print(A1());//將一個派生類的對象傳遞過去
	return 0;
}

       從上面的代碼能夠看出,在main函數中,我們調用print(A1())。即能夠用派生類對象作為實參傳遞給以基類類型作為形參的函數。所以find函數中的vec.begin()作為實參,以輸入迭代器類型作為形參,兩者能夠達到虛實相結合的目的而不會出錯。所以說。迭代器為各類算法和和各類容器提供了一個相互溝通的平台

        接着。我們從容器本身的角度和算法本身的角度對迭代器做進一步的分析。

        容器的迭代器都是定身制作的,什么意思呢?全部容器都內置一個迭代器。該內置迭代器由容器的設計者實現。

因為現有的容器比較多,不可能每種容器的迭代器都具體介紹下。可是有一點能夠確定的是,每一個容器相應的迭代器都是依據容器的特點來實現的,以求達到最高效率。

我們所關心的問題是:哪些操作會使容器的迭代器失效呢

        迭代器失效?沒錯。就是迭代器失效。

迭代器失效指的是迭代器原來所指向的元素不存在了或者發生了移動,此時假設不更新迭代器,將無法使用該過時的迭代器。

迭代器失效的根本原因是對容器的某些操作改動了容器的內存狀態(如容器又一次載入到內存)或移動了容器內的某些元素。

        使vector迭代器失效的操作

        1.向vector容器內加入元素(push_back,insert)

           向vector容器加入元素分下面兩種情況:

            1)若向vector加入元素后,整個vector又一次載入。即前后兩次vector的capacity()的返回值不同一時候,此時該容器 內的全部元素相應的迭代器都將失效。

            2)若該加入操作不會導致整個vector容器載入,則指向新插入元素后面的那些元素的迭代器都將失效。

       2,刪除操作(erase,pop_back,clear)

           vector運行刪除操作后,被刪除元素相應的迭代器以及其后面元素相應的迭代器都將失效。

       3.resize操作:調整當前容器的size

           調整容器大小對迭代器的影響分例如以下情況討論: 

            A.若調整后size>capacity,則會引起整個容器又一次載入,整個容器的迭代器都將失效。

            B.若調整后size<capacity,則不會又一次載入,詳細情況例如以下:

                B1.若調整后容器的size>調整前容器的size,則原來vector的全部迭代器都不會失效。

                B2.若調整后容器的size<調整前容器的size,則容器中那些別切掉的元素相應的迭代器都將失效。

      4.賦值操作(v1=v2     v1.assign(v2))

          會導致左操作數v1的全部迭代器都失效,顯然右操作數v2的迭代器都不會失效。

     5.交換操作(v1.swap(v2))

          因為在做交換操作時。v1,v2均不會刪除或插入元素,所以容器內不會移動不論什么元素。故v1,v2的全部迭代器都不會失效。

     使deque迭代器失效的操作

     1.插入操作(push_front,push_back,insert)

         deque的插入操作對迭代器的影響分兩種情況:

         A.在deque容器首部或尾部插入元素不會是不論什么迭代器失效;

         B.在除去首尾的其它位置插入元素會使該容器的全部迭代器失效。

     2.刪除操作

         A.在deque首、尾刪除元素僅僅會使被刪除元素相應的迭代器失效;

         B.在其它不論什么位置的刪除操作都會使得整個迭代器失效。

       使list/map/set迭代器失效的操作

       因為list/map/set容器內的元素都是通過指針連接的。list實現的數據結構是雙向鏈表,而map/set實現的數據結構是紅黑樹,故這些容器的插入和刪除操作都只需更改指針的指向,不會移動容器內的元素。故在容器內添加元素時,不會使不論什么迭代器失效,而在刪除元素時,只會使得指向被刪除的迭代器失效。

        再回顧下find函數。find函數前兩個形參都是輸入迭代器類型。這兩個迭代器並非某個容器特有的迭代器,而是一個一般的迭代器。可將全部標准庫容器內部的迭代器視為形參所相應迭代器類的派生類。

以下我們透過stl中迭代器的實現代碼來分析迭代器的實現方法.

         

#include<iostream>
#include<cstddef>//ptrdiff_t相應的頭文件
struct input_iterator_tag{};//輸入迭代器
struct output_iterator_tag{};//輸出迭代器
struct forward_iterator_tag:public input_iterator_tag{};//前向迭代器
struct bidirectional_iterator_tag:public forward_iterator_tag{};//雙向迭代器
struct random_access_iterator_tag:public bidirectional_iterator_tag{};//隨機訪問迭代器

//std::iterator,標准迭代器的類模板
template<class Category,class T,class Distance=ptrdiff_t,
         class Pointer=T*,class Reference=T&>
struct iterator//迭代器包括五個經常使用屬性
{
	typedef Category iterator_category;//迭代器的類型,五種之中的一個
	typedef T		 value_type;//迭代器所指向的元素的類型
	typedef Distance difference_type;//兩個迭代器的差值
	typedef Pointer  pointer;//迭代器的原始指針
	typedef Reference reference;//迭代器所指向元素的引用
};

//定義iterator_traits,用於提取迭代器的屬性,該類的對象不應該讓用戶看到
template<class Iterator>
struct iterator_traits
{
	//以下的操作相當於一個遞歸的操作。用於遞歸提取原始指針的相關值
	typedef typename Iterator::iterator_category iterator_category;
	typedef typename Iterator::value_type		 value_type;
	typedef typename Iterator::difference_type   difference_type;
	typedef typename Iterator::pointer		     pointer;
	typedef typename Iterator::reference         reference;
};

//針對原始指針的偏特化版本號
template<class T>
struct iterator_traits<T*>
{
	//相當於遞歸終止條件
	typedef random_access_iterator_tag iterator_category;
	typedef T         value_type;
	typedef ptrdiff_t diffrence_type;
	typedef T*		  pointer;
	typedef T&	      reference;
};

//針對指向經常使用的原始指針設計的偏特化版本號
template<class T>
struct iterator_traits<const T*>
{
	typedef random_access_iterator_tag iterator_category;
	typedef	T		   value_type;
	typedef ptrdiff_t  diffrence_type;
	typedef const T *  pointer;//重點在這里
	typedef const T &  reference;//還有這里
};

//定義兩個迭代器的差值類型的函數distance_type
template<class Iterator>
inline typename iterator_traits<Iterator>::difference_type *
distance_type(const Iterator&)
{
	return static_cast<typename iterator_traits<Iterator>::difference_type *>(0);
}

//獲取迭代器的value_type函數
template<class Iterator>
inline typename iterator_traits<Iterator>::value_type *
value_type(const Iterator&)
{
	return static_cast<typename iterator_traits<Iterator>::value_type*>(0);
}

//求兩個一般迭代器之間的距離的函數_distance,供distance函數分類調用
template<class InputIterator>
inline typename iterator_traits<InputIterator>::difference_type
_distance(InputIterator first,InputIterator last,input_iterator_tag)
{
	typename iterator_traits<InputIterator>::difference_type n=0;
	while(first!=last)
	{
		++first;
		++n;
	}
	return n;
}
//求兩個隨機訪問迭代器之間的距離的函數_distance,供distance函數分類調用
template<class RandomAccessIterator>
inline typename iterator_traits<RandomAccessIterator>::difference_type
_distance(RandomAccessIterator first,RandomAccessIterator last,
          random_access_iterator_tag)
{
	return last-first;
}

//自適應地調用distance函數
template<class InputIterator>
inline typename iterator_traits<InputIterator>::difference_type
distance(InputIterator first,InputIterator last)
{
	typedef typename iterator_traits<InputIterator>::iterator_category category;
	//從typename能夠看出。category是一個類型
	return _distance(first,last,category());//顯示調用category類的構造函數,返回該類的一個對象
}

/*****以下的函數用於將指針移動n位的方法*/
//一般迭代器求向前移動的方法,與雙向迭代器和隨機反問迭代器不同
template<class InputIterator,class Distance>
inline void _advance(InputIterator& i,Distance n,input_iterator_tag)
{
	while(n--)
	{
		++i;
	}
}

//針對雙向迭代器移動的方法
template<class BidirectionalIterator,class Distance>
inline void _advance(BidirectionalIterator &iter,Distance n,
	                 bidirectional_iterator_tag)
{
	if(n>=0)//當n大於0時。向后移動
	{
		while(n--)
		{
			++iter;
		}
	}
	else//當n小於0時,向前移
	{
		while(n++)
		{
			--iter;
		}
	}
}

//定義隨機訪問迭代器移動的方法
template<class RandomAccessIterator,class Distance>
inline void _advance(RandomAccessIterator &iter,Distance n,
	                 random_access_iterator_tag)
{
	iter+=n;
}

//自適應的調用advance函數
template<class InputIterator,class Distance>
inline void advance(InputIterator &iter,Distance n)
{
	_advance(i,n,iterator_catetory(iter));
}
       從上面的代碼中不難發現,實現一個迭代器。須要做一下工作:

        1.定義5類迭代器的標志類,該標志類用於實現函數的差別調用(即重載),比如求兩迭代器距離函數distance(iter1,iter2,tag)。移動函數advance(iter,n,tag)。

這五個標志類分別為:input_iterator_tag,output_iterator_tag。forward_iterator_tag,bidirectional_iterator_tag,random_access_iterator_tag。

        2.對於每個iterator類,都必須包括5個屬性,分別為:iterator_category、value_type、difference_type、pointer、reference。

        3.定義一個迭代器的“屬性榨汁機”iterator_traits,用於獲取iterator的5中屬性值。

        4.定義迭代器的操作。分別為:

           1) 獲取iterator的標志----->iterator_category(iter)。

           2)獲取兩迭代器差值的類型----->distance_type(iter)。

           3)獲取迭代器的原始類型--------->value_type(iter);

           4)求兩迭代器的距離---------------->distance(iter1,iter2,tag);

           5)將迭代器移動n位------------------>advance(iter,n,tag)。

參考文獻:

[1]《C++ Primer中文版 第4版》

[2]《STL源代碼分析 侯傑》

版權聲明:本文博主原創文章。博客,未經同意不得轉載。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM