自定義可以for each的類,C#,Java,C++,C++/cli的實現方法

      自定義類可以被for each,應該算是個老生常談的話題了,相關的資料都不少,不過這裏整理總結主流語言的不一樣實現方式,並比較部分細節上的差別。數組

      第一種語言,也是實現起來最簡單的Java語言。在Java裏,要被for each,就須實現Iterable<T>接口。Iterable<T>接口定義有一個方法(注:Java8之後多了兩個default方法,不用管他):ide

1 Iterator<T> iterator();

      Iterator<T>接口下有三個方法:函數

1 boolean hasNext();
2 T next();

      細節:迭代的第一次會先調用hasNext();方法,這一點跟後面有些語言不相同。this

      多說幾句,可能有些同窗對Iterable<T>接口與Iterator<T>接口不同的地方是,Iterable<T>是容器類所實現的,Iterator<T>是迭代器。容器類是存放數據的,迭代器是存放迭代過程當中的遊標(當前訪問的位置),和控制遊標和訪問器的移動的。spa

      實現例子:.net

 1 public class Person {
 2     private String name;
 3     public Person() {//構造函數
 4     }
 5     public String getName() {
 6         return name;
 7     }
 8 }
 9 public class PersonSet implements Iterable<Person>{
10     private Person[] persons;//容器類存放數據,數組自己就能夠被for each,只是這裏演示如何使用Iterable<Person>接口。
11     public PersonSet(){
12         //構造函數
13     }
14     @Override
15     public Iterator<Person> iterator() {
16         // TODO Auto-generated method stub
17         return new Iterator<Person>() {
18             private int index=0;//迭代器存放遊標
19             @Override
20             public boolean hasNext() {
21                 // TODO Auto-generated method stub
22                 return index < persons.Length;
23             }
24 
25             @Override
26             public Person next() {
27                 // TODO Auto-generated method stub
28                 return persons[index++];//別忘了訪問完數據還得移動遊標
29             }
30         };
31     }
32 }

      遍歷方法:指針

1 PersonSet persons=//具體初始化過程不寫
2 for (Person person : persons)
3 {
4     System.out.println(person.getName());
5 }

      第二種語言,C#,跟JAVA至關相似,只是在迭代器的具體實現有些細節上的差別。Java是Iterable<T>,C#對應的接口叫IEnumerable<T>。IEnumerable<T>從非泛型的版本繼承,有兩個方法:code

1 IEnumerator<T> GetEnumerator();
2 IEnumerator GetEnumerator();

      與IEnumerable<T>類似,IEnumerator<T>接口也是從IEnumerator繼承,同時還繼承了IDisposable接口。其有3個方法和2個屬性:對象

1 T Current { get; }
2 object Current { get; }
3 void Dispose();
4 bool MoveNext();
5 void Reset();

      方法和屬性較多,邏輯容易亂。不用愁,我來捋一下順序:blog

     第一次訪問:Reset()(初始化後遊標被設置爲空位置,不指向任何元素)->MoveNext()->Current

     第二次及之後訪問:MoveNext()->Current

     訪問退出:MoveNext()->Dispose()

      具體實現:

 1 class Person
 2 {
 3     public string Name { get; }
 4 }
 5 class PersonSet:IEnumerable<Person>
 6 {
 7     private Person[] persons;
 8     public PersonSet()
 9     {//構造函數
10     }
11     public IEnumerator<Person> GetEnumerator() => new Enumerator(this);
12 
13     IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();//顯式接口實現,直接返回泛型版本就可
14     private class Enumerator : IEnumerator<Person>
15     {
16         private int index;
17         private PersonSet parent;
18         public Enumerator(PersonSet parent)
19         {
20             this.parent = parent;
21         }
22         public Person Current => parent.persons[index];
23 
24         object IEnumerator.Current => parent.persons[index];
25         
26         void IDisposable.Dispose() { }
27 
28         public bool MoveNext() => (++index) < parent.persons.Length;
29 
30         public void Reset() => index = -1;//遊標必定要設爲空!
31         
32     }
33 }

     調用方法:

PersonSet persons=//具體初始化過程不寫。
foreach(var person in persons)
{
    Console.WriteLine(person.Name);
}    

      第三種方法,是C++/cli,其實C++/cli與C#都是基於.net的,C++/cli也同樣從IEnumerable<T>繼承,只是C++/cli不支持顯式接口實現,寫法有些差別。而且C++/cli語法囉嗦臃腫,順便給你們開眼界。通常不主張使用C++/cli,可是在混合使用C#和本地C++代碼時會頗有用。另外,C++/cli的成員能夠是非託管類的指針,但不能是對象自己(多是由於託管對象會在內存移動,使得非託管類沒法對自身成員進行取地址),C++/cli的泛型參數不能使非託管的,指針也不行。

頭文件:

 1 #pragma once
 2 ref class Person
 3 {
 4 public:
 5     property System::String^ Name { System::String^ get();}
 6 };
 7 ref class PersonSet : System::Collections::Generic::IEnumerable<Person^>
 8 {
 9     ref class Enumerator : System::Collections::Generic::IEnumerator<Person^>
10     {
11     private:
12         PersonSet^ parent;
13     public:
14         Enumerator();
15         ~Enumerator();
16         property Person^ Current{ virtual Person^ get(); };
17         property Object^ NonGenericCurrent
18         {
19             virtual Object^ get() final = System::Collections::IEnumerator::Current::get;
20             //經過重命名地方法來實現C#的「顯式接口調用」的效果。
21         }
22         virtual bool MoveNext();
23         virtual void Reset();
24     };
25     array<Person^>^ persons;
26 public:
27     PersonSet();
28     virtual System::Collections::Generic::IEnumerator<Person^>^ GetEnumerator() final;
29     virtual System::Collections::IEnumerator^ GetNonGenericEnumerator() final
30         = System::Collections::IEnumerable::GetEnumerator;
31     //經過重命名地方法來實現C#的「顯式接口調用」的效果。
32 };

CPP文件:(部分)

 1 Person^ PersonSet::Enumerator::Current::get()
 2 {
 3     return parent->persons[index];
 4 }
 5 Object^ PersonSet::Enumerator::NonGenericCurrent::get()
 6 {
 7     return parent->persons[index];
 8 }
 9 
10 bool PersonSet::Enumerator::MoveNext()
11 {
12     return ++index < parent->persons->Length;
13 }
14 
15 void PersonSet::Enumerator::Reset()
16 {
17     index = -1;
18 }
19 
20 System::Collections::Generic::IEnumerator<Person^>^ PersonSet::GetEnumerator()
21 {
22     return gcnew Enumerator(this);
23 }
24 
25 System::Collections::IEnumerator ^ PersonSet::GetNonGenericEnumerator()
26 {
27     return GetEnumerator();
28 }

 

       調用方法:

1 PersonSet^ persons = gcnew PersonSet();
2 for each (auto person in persons)
3 {
4     Console::WriteLine(person->Name);
5 }

       最後是非託管C++方式,非託管C++沒有接口這個概念,因此不存在要實現哪一個接口的問題。事實上,C++11以前並無foreach(C++11裏叫for range),C++11要實現for range,須要實現如下五個函數:

1 iterator begin();//前兩個函數是容器類的成員,iterator是自行實現的迭代器,類名任意。
2 iterator end();
3 iterator& operator++();//後三個是迭代器的成員,操做符重載。
4 bool operator!=(iterator& other);
5 T operator*();//T是想要訪問的元素

      調用順序:

     第一次訪問元素: begin() ==> end() ==> operator!=(iterator& other)  ==> operator*()

     第二次即之後訪問元素:operator++() ==> operator!=(iterator& other)  ==> operator*() 

     訪問退出:operator++() ==> operator!=(iterator& other)

      注意的是:for range的迭代器遊標初始化必定是指向首元素!實現方式:

頭文件:

 1 struct NativePerson
 2 {
 3     const char* name;
 4 };
 5 class PersonSet1
 6 {
 7     NativePerson* const persons;
 8     const int length;
 9 public:
10     class iterator
11     {
12         PersonSet1* parent;
13         int current;
14     public:
15         iterator(PersonSet1* parent,int current);
16         iterator& operator++();
17         bool operator!=(iterator& other);
18         NativePerson operator*();
19     };
20     PersonSet1(NativePerson* const persons, int length);
21     iterator begin();
22     iterator end();
23 };

CPP文件:(部分)

 1 PersonSet1::iterator & PersonSet1::iterator::operator++()
 2 {
 3     current++;
 4     return *this;
 5 }
 6 
 7 bool PersonSet1::iterator::operator!=(iterator & other)
 8 {
 9     return current < other.current;
10 }
11 
12 NativePerson PersonSet1::iterator::operator*()
13 {
14     return parent->persons[current];
15 }
16 
17 PersonSet1::iterator PersonSet1::begin()
18 {
19     return iterator(this,0);
20 }
21 
22 PersonSet1::iterator PersonSet1::end()
23 {
24     return iterator(this, length);
25 }

      C++98的遍歷方式:

1 PersonSet1 ps(new NativePerson[10],10);
2 for (PersonSet1::iterator pp = ps.begin();pp != ps.end();pp++)
3 {
4     cout << (*p).name << endl;
5 }

     C++11的遍歷方式:

1 PersonSet1 ps(new NativePerson[10],10);
2 for (auto p : ps)
3 {
4     cout<<p.name<<endl;
5 }

     這裏有個問題,按照要求,迭代器彷佛必須知道最後一個元素,那對於只能遍歷,得等到遍歷到最後一個元素才能知道他的存在(沒有後繼),是否就無法實現了呢?也不是,能夠這麼變通:begin()和end()不是各返回一個迭代器麼,前者的遊標會動,後者不動。給迭代器設置一個成員curPos,begin()返回的迭代器curPos爲0,end()返回的迭代器curPos爲1,而後當begin()的迭代器迭代到沒有沒有後繼時,把curPos設爲1,而後不就能使得循環退出麼?

     實現方法,假設有一個讀取NativePerson的讀取器,他長這樣的:

1 class PersonReader
2 {
3 public:
4     bool next();
5     NativePerson get();
6 };

     而後就能夠這樣實現:

     頭文件:

 1 class PersonSet2
 2 {
 3     PersonReader& reader;
 4 public:
 5     class iterator
 6     {
 7         PersonSet2& parent;
 8         CurPos curPos;
 9     public:
10         iterator(PersonSet2& parent);//begin
11         iterator();//end
12         iterator& operator++();
13         bool operator!=(iterator& other);
14         NativePerson operator*();
15     };
16     PersonSet2(PersonReader& reader);
17     iterator begin();
18     iterator end();
19 };

CPP文件:(部分)

 1 PersonSet2::iterator::iterator(PersonSet2 & parent) : parent(parent)
 2 {
 3     curPos = BEGIN;
 4     operator++();//必須調用一次operator++()保證指針處在第一個元素。
 5 }
 6 PersonSet2::iterator::iterator() : parent(*(PersonSet2*)nullptr)
 7 {
 8     curPos = END;
 9 }
10 
11 PersonSet2::iterator & PersonSet2::iterator::operator++()
12 {
13     if (parent.reader.next())
14     {
15         //把讀取器的遊標日後移動後要作的事
16     }
17     else
18     {
19         curPos = END;//這樣就把迭代器標記爲末元素
20     }
21     return *this;
22 }
23 
24 bool PersonSet2::iterator::operator!=(iterator & other)
25 {
26     return curPos != other.curPos;//若是沒讀到最後一個元素,迭代器遊標位置爲BEGIN,不然就被設爲END
27 }
28 
29 NativePerson PersonSet2::iterator::operator*()
30 {
31     return parent.reader.get();
32 }
33 
34 PersonSet2::iterator PersonSet2::begin()
35 {
36     return iterator(*this);
37 }
38 
39 PersonSet2::iterator PersonSet2::end()
40 {
41     return iterator();
42 }

而後就沒有而後了。還有什麼問題麼?

相關文章
相關標籤/搜索