一步一步認識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//auto_ptr的頭文件 void f() { auto_ptr p(new int (42)); }
通過以上的分析,我們便對智能指針有了一定的了解。迭代器iterator就是一種智能指針,它對原始指針進行了封裝,並且提供一些等價於原始指針的操作,做到既方便又安全。
一提到STL,必須要馬上想到其主要的6個組成部件,分別是:容器、算法、迭代器、仿函數、適配器和空間分配器,本文主要介紹迭代器。迭代器是連接容器和算法的一種重要橋梁。為什麼呢?我們舉個例子來說明原因。
#include#include//用到find函數 #include using namespace std; int main() { vector vec; int i; for(i=0;i<10;i++) { vec.push_back(i); } if(vec.end()!=find(vec.begin(),vec.end(),7)) { cout<<"find!"< 上述代碼中,值得注意的是用到的find函數,find函數的函數原型為:template
_InIt find(_InIt _First, _InIt _last, const _Ty & _val),從find函數原型可以看出find函數是一個函數模板,函數的形參中前兩個參數為_InIt,該參數是InputIterator的縮寫,最後一個參數則是一個任意類型變量的引用。而我們的程序在使用find函數實,傳入的實參為find(vec.begin(),vec.end(),7),這樣我們的形參迭代器就將算法find和容器vector聯系起來了,從這個角度我們可以很容易理解為什麼說迭代器是算法和容器的聯系的橋梁了。 為什麼上面的形參是一個InputIterator,而能夠接受實參vec.begin()呢?為了回答這個問題,需要從迭代器的分類說起。
在STL中,迭代器主要分為5類,分別是:輸入迭代器、輸出迭代器、前向迭代器、雙向迭代器和隨機訪問迭代器。
輸入迭代器:只讀,支持++、==、!=;
輸出迭代器:只寫,支持++;
前向迭代器:讀寫,支持++、==、!=;
雙向迭代器:讀寫,支持++、--,C++的所有標准庫容器都至少在雙向迭代器的層次上。;
隨機訪問迭代器:讀寫,支持++、--、[n]、-n、<、<=、>、>=;
輸入迭代器和輸出迭代器是最低級的迭代器,後三種迭代器都是對該迭代器的一種派生,回到剛剛那個問題,為什麼實參vec.begin()能夠與形參_InIt“虛實結合”呢?我們先看下面的代碼:
#includeusing namespace std; class A{}; class A1:public A{};//類A1繼承A class B{}; void print(A)//只需指定參數類型 { cout<<"This is Base class A!"< 從上面的代碼可以看出,在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
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從上面的代碼中不難發現,實現一個迭代器,需要做一下工作:#include //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 struct iterator//迭代器包含五個常用屬性 { typedef Category iterator_category;//迭代器的類型,五種之一 typedef T value_type;//迭代器所指向的元素的類型 typedef Distance difference_type;//兩個迭代器的差值 typedef Pointer pointer;//迭代器的原始指針 typedef Reference reference;//迭代器所指向元素的引用 }; //定義iterator_traits,用於提取迭代器的屬性,該類的對象不應該讓用戶看到 template 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 struct iterator_traits { //相當於遞歸終止條件 typedef random_access_iterator_tag iterator_category; typedef T value_type; typedef ptrdiff_t diffrence_type; typedef T* pointer; typedef T& reference; }; //針對指向常用的原始指針設計的偏特化版本 template struct iterator_traits { 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 inline typename iterator_traits ::difference_type * distance_type(const Iterator&) { return static_cast ::difference_type *>(0); } //獲取迭代器的value_type函數 template inline typename iterator_traits ::value_type * value_type(const Iterator&) { return static_cast ::value_type*>(0); } //求兩個一般迭代器之間的距離的函數_distance,供distance函數分類調用 template inline typename iterator_traits ::difference_type _distance(InputIterator first,InputIterator last,input_iterator_tag) { typename iterator_traits ::difference_type n=0; while(first!=last) { ++first; ++n; } return n; } //求兩個隨機訪問迭代器之間的距離的函數_distance,供distance函數分類調用 template inline typename iterator_traits ::difference_type _distance(RandomAccessIterator first,RandomAccessIterator last, random_access_iterator_tag) { return last-first; } //自適應地調用distance函數 template inline typename iterator_traits ::difference_type distance(InputIterator first,InputIterator last) { typedef typename iterator_traits ::iterator_category category; //從typename可以看出,category是一個類型 return _distance(first,last,category());//顯示調用category類的構造函數,返回該類的一個對象 } /*****下面的函數用於將指針移動n位的方法*/ //一般迭代器求向前移動的方法,與雙向迭代器和隨機反問迭代器不同 template inline void _advance(InputIterator& i,Distance n,input_iterator_tag) { while(n--) { ++i; } } //針對雙向迭代器移動的方法 template 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 inline void _advance(RandomAccessIterator &iter,Distance n, random_access_iterator_tag) { iter+=n; } //自適應的調用advance函數 template 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源碼剖析 侯捷》