? ? ? 自定義類能夠被for each,應該算是個老生常談的話題了,相關的資料都很多,不過這里整理總結主流語言的不同實現方式,并比較部分細節上的差異。
? ? ? 第一種語言,也是實現起來最簡單的Java語言。在Java里,要被for each,就須實現Iterable<T>接口。Iterable<T>接口定義有一個方法(注:Java8以后多了兩個default方法,不用管他):
1 Iterator<T> iterator();
? ? ??Iterator<T>接口下有三個方法:
1 boolean hasNext(); 2 T next();
? ? ? 細節:迭代的第一次會先調用hasNext();方法,這一點跟后面有些語言不相同。
? ? ? 多說幾句,可能有些同學對Iterable<T>接口與Iterator<T>接口不一樣的地方是,Iterable<T>是容器類所實現的,Iterator<T>是迭代器。容器類是存放數據的,迭代器是存放迭代過程中的游標(當前訪問的位置),和控制游標和訪問器的移動的。
? ? ? 實現例子:
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>從非泛型的版本繼承,有兩個方法:
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();
? ? ? 方法和屬性較多,邏輯容易亂。不用愁,我來捋一下順序:
? ? ?第一次訪問: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 }
然后就沒有然后了。還有什么問題么?
?