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

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

        「指針對所有C/C++的程序猿來講,一點都不陌生。ios

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

       既然「指針」的使用者一不當心就可能致使內存泄漏,那麼咱們怎樣可使得指針的使用變得更安全呢?從C++面向對象的角度分析,咱們有沒有可能將「指針」封裝起來,使得用戶不直接接觸指針,而使用一個封裝後的對象來替代指針的操做呢?安全

        答案是顯然的,「智能指針」(smart pointer)正解決這類問題,尤爲是在防止內存泄漏方面作得很突出。數據結構

C++標準庫std中提供了一種「智能指針類」名爲"auto_ptr",先看如下的代碼:dom

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

       經過以上的分析。咱們便對智能指針有了必定的瞭解。spa

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

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

迭代器是鏈接容器和算法的一種重要橋樑。code

爲何呢?咱們舉個樣例來講明緣由。

#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源代碼分析 侯傑》

相關文章
相關標籤/搜索