c++ iterator(迭代器)分類及其使用


前言:

  以下的內容為我閱讀c++沉思錄18,19,20章的筆記以及自己的想法.

正文:

  總所周知,c++的stl中提出了iterator的概念,這是C所沒有的.在一般的使用中,iterator的行為很像c內建的指針.而在java和c#中索性就直接取消了指針,而采用類似iterator的做法來代替了指針.很多編程人員在使用iterator的時候也僅僅把他當作了指針的一個變體而沒有多加注意.

  不過既然是學習,那我們在使用的時候也要知道其存在的原因,其分類以及用法吧.

  首先是問題的提出:

  很多人會覺得,既然C++沿用了C的指針這么強大的東西了,為什么還要iterator這么一群類來工作呢?

  我們知道,在我們編寫模板的時候,對於使用了iterator做為參數的函數,往往該函數對於iterator有些特定的操作.比如下列2個函數

      

template<class P,class T>
P find(P start,P beyond,const T& x)
{
while( start != beyond && * start != x)
++start;
return start;
}

template<class P, class T>
void reverse(P start, P beyond)
{
while(start != beyond) {
--beyond;
if (start != beyond) {
T t = *start;
*start = *beyond;
*beyond = t;
++ start;
}
}
}

  我們可以看到,這兩個函數都對模板參數P做了一定要求,在find中,我們要求P必須允許 != ,++和*(P)這三個運算符的操作,而對於reverse函數來說,其要求更多,運算符++,--,*(),!=都必須支持.問題就這么出來了,我們怎么讓所有人都遵守這一要求呢?或者說,后續采用這個模板的使用者怎么能在不知道實現細節的情況下了解並遵守這些要求呢?顯然,我們需要一個分類方法來知道如何界定不同種類的迭代器.

  不過這里還是沒有解釋一個疑惑,即這兩個函數改成T的指針也能完成的很好,我們還要iterator做什么?

  答案很簡單,不是所有數據結構都是線性的!對於一個鏈表來說,如果要實現上述的功能,我們必須重新寫一個幾乎一樣僅僅是把++start變成start = start->next的函數.顯然,這樣重復的事情我們是不希望碰到的,尤其如果函數還非常大的情況下.而這時候,iterator的作用就出來了.對於鏈表,我們的鏈表的iterator(list_iterator)僅僅只要把operator++()重寫成operator++(){now = now->next;},就能正常的使用find函數了.完全不需要重新編寫.這就減少了我們算法的編寫量.

  現在,既然我們知道了iterator的好處了之后,接下來我們就想知道之前提到的分類法是怎么回事了.經常聽說輸入迭代器,輸出迭代器,前向迭代器,雙向迭代器,隨機存取迭代器是怎么回事.

  迭代器的分類:

  1.輸入迭代器(input iterator)

        input iterator就像其名字所說的,工作的就像輸入流一樣.我們必須能

  • 取出其所指向的值  
  • 訪問下一個元素
  • 判斷是否到達了最后一個元素
  • 可以復制

  因此其支持的操作符有  *p,++p,p++,p!=q,p == q這五個.凡是支持這五個操作的類都可以稱作是輸入迭代器.當然指針是符合的.

  2.輸出迭代器(output iterator)

    output iterator工作方式類似輸出流,我們能對其指向的序列進行寫操作,其與input iterator不相同的就是*p所返回的值允許修改,而不一定要讀取,而input只允許讀取,不允許修改.

    支持的操作和上頭一樣,支持的操作符也是 *p,++p,p++,p!=q,p == q.

 

  使用Input iterator和output iterator的例子:

 

 1 template<class In,class Out>
2 void copy(In start,In beyond, Out result)
3 {
4 while(start != beyond) {
5 *result = *start; //result是輸出迭代器,其*result返回的值允許修改
6 ++result;
7 ++start;
8 }
9 }
10
11 //簡寫
12 template<class In,class Out>
13 void copy(In start,In beyond, Out result)
14 {
15 while(start != beyond)
16 *result++ = *start++;//這個應該能看懂的...
17 }


   3.前向迭代器(forward iterator)

     前向迭代器就像是輸入和輸出迭代器的結合體,其*p既可以訪問元素,也可以修改元素.因此支持的操作也是相同的.

   4.雙向迭代器(bidirectional iterator)

     雙向迭代器在前向迭代器上更近一步,其要求該種迭代器支持operator--,因此其支持的操作有  *p,++p,p++,p!=q,p == q,--p,p--

   5. 隨機存取迭代器(random access iterator)

    即如其名字所顯示的一樣,其在雙向迭代器的功能上,允許隨機訪問序列的任意值.顯然,指針就是這樣的一個迭代器.

    對於隨機存取迭代器來說, 其要求高了很多:

  • 可以判斷是否到結尾(  a==b or a != b)
  • 可以雙向遞增或遞減( --a or ++a)
  • 可以比較大小( a < b or a > b or a>=b ...etc)
  • 支持算術運算( a + n)
  • 支持隨機訪問( a[n] )
  • 支持復合運算( a+= n)

  結構圖:

  上面即為我們所討論到的幾個迭代器的層次.看起來很像繼承是麽?

  但是實際上在STL中,這些並不是通過繼承關聯的.這些只不過是一些符合條件的集合.這樣的好處是:少去了一個特殊的基類(input iterator),其次,使得內建類型指針可以成為iterator.

      其實只要明白iterator是滿足某些特別的功能的集合(包括類和內建類型),就不會覺得是繼承了.

   

  Iterator使用:

  一個ostream_iteartor的例子:

  

 1 #include <iostream>
2
3 using namespace std;
4
5 template<class T>
6 class Ostream_iterator {
7 public:
8 Ostream_iterator(ostream &os,const char* s):
9 strm(&os), str(s){}
10 Ostream_iterator& operator++() {return *this;}
11 Ostream_iterator& operator++(int) {return *this;}
12 Ostream_iterator& operator*() {return *this;}
13 Ostream_iterator& operator=(const T& t)
14 {
15 *strm << t << str;
16 return *this;
17 }
18
19 private:
20 ostream* strm;
21 const char *str;
22 };
23
24 template<class In,class Out>
25 Out Copy(In start,In beyond,Out dest)
26 {
27 while(start != beyond)
28 *dest++ = *start++;
29 return dest;
30 }
31
32 int main()
33 {
34 Ostream_iterator<int> oi(cout, " \n");
35
36 int a[10];
37 for (int i = 0;i!=10;++i)
38 a[i] = i+1;
39 Copy(a,a+10,oi);
40
41 return 0;
42 }

  在這個例子中,我們簡單的構造了一個ostream_iterator,並且使用了copy來輸出..這個ostream_iterator和STL差別還是很大,不過功能上已經差不多了.我想讀者們應該也能看懂代碼吧,所以就不用多做什么解釋了.
   第二個例子中,我們講構造一個istream_iterator.

  對於一個istream_iterator來說,我們必須要存的是T buffer和istream *strm.不過由於istream的特殊性,我們必須知道buffer是否滿,以及是否到達尾部,因此,我們的結構是這樣的 

 

1 template <class T>
2 class Istream_Iterator{
3
4 private:
5 istream *strm;
6 T buffer;
7 int full;
8 int eof;
9 };

  對於構造函數來說,因為有eof的存在,因此,我們自然想到,我們的默認構造函數就應該把eof設為1.而對於有istream的iterator,都應該為0.

  

1 Istream_iterator(istream &is):
2 strm(&is),full(0),eof(0){}
3 Istream_iterator():strm(0),full(0),eof(1){}

  對於我們的版本的istream_iterator來說,我們需要完成的功能有

  • 進行取值操作(dereference),既*(p)
  • 自增操作
  • 比較

  自增操作:

  我們知道,在istream中,一旦獲取到一個值了之后,我們將不在訪問這個值.我們的iterator也要符合這樣的要求,因此,我們通過full來控制.

 

1     Istream_iterator& operator++(){
2 full = 0;
3 return *this;
4 }
5 Istream_iterator operator++(int){
6 Istream_iterator r = *this;
7 full = 0;
8 return r;
9 }

  Dereference:

  對於解除引用操作來說,我們需要將流中緩存的值存入buffer內.同時,我們要判斷讀取是否到結尾,到了結尾則應當出錯.

  在這里我們寫了一個私有函數fill(),這個將在比較的時候用到

  

 T operator*() {
fill();
assert(eof);//我們斷定不應該出現eof
return buffer;
}
void fill(){
if (!full && !eof) {
if (*strm >> buffer)
full = 1;
else
eof = 1;
}
}

  比較:

  對於比較來說,只有兩個istream_iterator都同一個對象或者都處於文件尾時,這兩個istream_iterator才相等.因此這里其比較對象為istream_iterator而不是const istream_iterator

template<class T>
int operator==(Istream_iterator<T> &p,Istream_iterator<T>& q)
{
if (p.eof && q.eof)
return 1;
if (!p.eof && !q.eof)
return &p == &q;
p.fill();q.fill();
return p.eof == q.eof;
}

最后的測試例子,讀者可以把之前的copy和ostream_iterator拷下來

int main()
{
Ostream_iterator<int> o(cout,"\t");
Istream_iterator<int> i(cin);
Istream_iterator<int> eof;

Copy(i,eof,o);
return 0;
}

結論:

  iterator是STL實現所有算法已經其通用型的基礎.通過對iterator分類,使得算法的使用者在使用時不需要知道具體實現就可知道算法對於參數的要求,形成一個通用的體系.


 


免責聲明!

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



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