自定义能够for each的类,C#,Java,C++,C++/cli的实现方法
2016-01-08 01:20
846 查看
自定义类能够被for each,应该算是个老生常谈的话题了,相关的资料都很多,不过这里整理总结主流语言的不同实现方式,并比较部分细节上的差异。
第一种语言,也是实现起来最简单的Java语言。在Java里,要被for each,就须实现Iterable<T>接口。Iterable<T>接口定义有一个方法(注:Java8以后多了两个default方法,不用管他):
Iterator<T>接口下有三个方法:
细节:迭代的第一次会先调用hasNext();方法,这一点跟后面有些语言不相同。
多说几句,可能有些同学对Iterable<T>接口与Iterator<T>接口不一样的地方是,Iterable<T>是容器类所实现的,Iterator<T>是迭代器。容器类是存放数据的,迭代器是存放迭代过程中的游标(当前访问的位置),和控制游标和访问器的移动的。
实现例子:
遍历方法:
第二种语言,C#,跟JAVA相当类似,只是在迭代器的具体实现有些细节上的差异。Java是Iterable<T>,C#对应的接口叫IEnumerable<T>。IEnumerable<T>从非泛型的版本继承,有两个方法:
与IEnumerable<T>相似,IEnumerator<T>接口也是从IEnumerator继承,同时还继承了IDisposable接口。其有3个方法和2个属性:
方法和属性较多,逻辑容易乱。不用愁,我来捋一下顺序:
第一次访问:Reset()(初始化后游标被设置为空位置,不指向任何元素)->MoveNext()->Current
第二次及以后访问:MoveNext()->Current
访问退出:MoveNext()->Dispose()
具体实现:
调用方法:
第三种方法,是C++/cli,其实C++/cli与C#都是基于.net的,C++/cli也一样从IEnumerable<T>继承,只是C++/cli不支持显式接口实现,写法有些差异。并且C++/cli语法啰嗦臃肿,顺便给大家开眼界。一般不主张使用C++/cli,但是在混合使用C#和本地C++代码时会很有用。另外,C++/cli的成员可以是非托管类的指针,但不能是对象本身(可能是因为托管对象会在内存移动,使得非托管类无法对自身成员进行取地址),C++/cli的泛型参数不能使非托管的,指针也不行。
头文件:
CPP文件:(部分)
调用方法:
最后是非托管C++方式,非托管C++没有接口这个概念,所以不存在要实现哪个接口的问题。事实上,C++11之前并没有foreach(C++11里叫for range),C++11要实现for range,需要实现以下五个函数:
调用顺序:
第一次访问元素: begin() ==> end() ==> operator!=(iterator& other) ==> operator*()
第二次即以后访问元素:operator++() ==> operator!=(iterator& other) ==> operator*()
访问退出:operator++() ==> operator!=(iterator& other)
注意的是:for range的迭代器游标初始化一定是指向首元素!实现方式:
头文件:
CPP文件:(部分)
C++98的遍历方式:
C++11的遍历方式:
这里有个问题,按照要求,迭代器似乎必须知道最后一个元素,那对于只能遍历,得等到遍历到最后一个元素才能知道他的存在(没有后继),是否就没法实现了呢?也不是,可以这么变通:begin()和end()不是各返回一个迭代器么,前者的游标会动,后者不动。给迭代器设置一个成员curPos,begin()返回的迭代器curPos为0,end()返回的迭代器curPos为1,然后当begin()的迭代器迭代到没有没有后继时,把curPos设为1,然后不就能使得循环退出么?
实现方法,假设有一个读取NativePerson的读取器,他长这样的:
然后就可以这样实现:
头文件:
CPP文件:(部分)
然后就没有然后了。还有什么问题么?
第一种语言,也是实现起来最简单的Java语言。在Java里,要被for each,就须实现Iterable<T>接口。Iterable<T>接口定义有一个方法(注:Java8以后多了两个default方法,不用管他):
Iterator<T> iterator();
Iterator<T>接口下有三个方法:
boolean hasNext(); T next();
细节:迭代的第一次会先调用hasNext();方法,这一点跟后面有些语言不相同。
多说几句,可能有些同学对Iterable<T>接口与Iterator<T>接口不一样的地方是,Iterable<T>是容器类所实现的,Iterator<T>是迭代器。容器类是存放数据的,迭代器是存放迭代过程中的游标(当前访问的位置),和控制游标和访问器的移动的。
实现例子:
public class Person { private String name; public Person() {//构造函数 } public String getName() { return name; } } public class PersonSet implements Iterable<Person>{ private Person[] persons;//容器类存放数据,数组本身就可以被for each,只是这里演示如何使用Iterable<Person>接口。 public PersonSet(){ //构造函数 } @Override public Iterator<Person> iterator() { // TODO Auto-generated method stub return new Iterator<Person>() { private int index=0;//迭代器存放游标 @Override public boolean hasNext() { // TODO Auto-generated method stub return index < persons.Length; } @Override public Person next() { // TODO Auto-generated method stub return persons[index++];//别忘了访问完数据还得移动游标 } }; } }
遍历方法:
PersonSet persons=//具体初始化过程不写 for (Person person : persons) { System.out.println(person.getName()); }
第二种语言,C#,跟JAVA相当类似,只是在迭代器的具体实现有些细节上的差异。Java是Iterable<T>,C#对应的接口叫IEnumerable<T>。IEnumerable<T>从非泛型的版本继承,有两个方法:
IEnumerator<T> GetEnumerator(); IEnumerator GetEnumerator();
与IEnumerable<T>相似,IEnumerator<T>接口也是从IEnumerator继承,同时还继承了IDisposable接口。其有3个方法和2个属性:
T Current { get; } object Current { get; } void Dispose(); bool MoveNext(); void Reset();
方法和属性较多,逻辑容易乱。不用愁,我来捋一下顺序:
第一次访问:Reset()(初始化后游标被设置为空位置,不指向任何元素)->MoveNext()->Current
第二次及以后访问:MoveNext()->Current
访问退出:MoveNext()->Dispose()
具体实现:
class Person { public string Name { get; } } class PersonSet:IEnumerable<Person> { private Person[] persons; public PersonSet() {//构造函数 } public IEnumerator<Person> GetEnumerator() => new Enumerator(this); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();//显式接口实现,直接返回泛型版本就可 private class Enumerator : IEnumerator<Person> { private int index; private PersonSet parent; public Enumerator(PersonSet parent) { this.parent = parent; } public Person Current => parent.persons[index]; object IEnumerator.Current => parent.persons[index]; void IDisposable.Dispose() { } public bool MoveNext() => (++index) < parent.persons.Length; public void Reset() => index = -1;//游标一定要设为空! } }
调用方法:
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的泛型参数不能使非托管的,指针也不行。
头文件:
#pragma once ref class Person { public: property System::String^ Name { System::String^ get();} }; ref class PersonSet : System::Collections::Generic::IEnumerable<Person^> { ref class Enumerator : System::Collections::Generic::IEnumerator<Person^> { private: PersonSet^ parent; public: Enumerator(); ~Enumerator(); property Person^ Current{ virtual Person^ get(); }; property Object^ NonGenericCurrent { virtual Object^ get() final = System::Collections::IEnumerator::Current::get; //通过重命名地方法来实现C#的“显式接口调用”的效果。 } virtual bool MoveNext(); virtual void Reset(); }; array<Person^>^ persons; public: PersonSet(); virtual System::Collections::Generic::IEnumerator<Person^>^ GetEnumerator() final; virtual System::Collections::IEnumerator^ GetNonGenericEnumerator() final = System::Collections::IEnumerable::GetEnumerator; //通过重命名地方法来实现C#的“显式接口调用”的效果。 };
CPP文件:(部分)
Person^ PersonSet::Enumerator::Current::get() { return parent->persons[index]; } Object^ PersonSet::Enumerator::NonGenericCurrent::get() { return parent->persons[index]; } bool PersonSet::Enumerator::MoveNext() { return ++index < parent->persons->Length; } void PersonSet::Enumerator::Reset() { index = -1; } System::Collections::Generic::IEnumerator<Person^>^ PersonSet::GetEnumerator() { return gcnew Enumerator(this); } System::Collections::IEnumerator ^ PersonSet::GetNonGenericEnumerator() { return GetEnumerator(); }
调用方法:
PersonSet^ persons = gcnew PersonSet(); for each (auto person in persons) { Console::WriteLine(person->Name); }
最后是非托管C++方式,非托管C++没有接口这个概念,所以不存在要实现哪个接口的问题。事实上,C++11之前并没有foreach(C++11里叫for range),C++11要实现for range,需要实现以下五个函数:
iterator begin();//前两个函数是容器类的成员,iterator是自行实现的迭代器,类名任意。 iterator end(); iterator& operator++();//后三个是迭代器的成员,操作符重载。 bool operator!=(iterator& other); T operator*();//T是想要访问的元素
调用顺序:
第一次访问元素: begin() ==> end() ==> operator!=(iterator& other) ==> operator*()
第二次即以后访问元素:operator++() ==> operator!=(iterator& other) ==> operator*()
访问退出:operator++() ==> operator!=(iterator& other)
注意的是:for range的迭代器游标初始化一定是指向首元素!实现方式:
头文件:
struct NativePerson { const char* name; }; class PersonSet1 { NativePerson* const persons; const int length; public: class iterator { PersonSet1* parent; int current; public: iterator(PersonSet1* parent,int current); iterator& operator++(); bool operator!=(iterator& other); NativePerson operator*(); }; PersonSet1(NativePerson* const persons, int length); iterator begin(); iterator end(); };
CPP文件:(部分)
PersonSet1::iterator & PersonSet1::iterator::operator++() { current++; return *this; } bool PersonSet1::iterator::operator!=(iterator & other) { return current < other.current; } NativePerson PersonSet1::iterator::operator*() { return parent->persons[current]; } PersonSet1::iterator PersonSet1::begin() { return iterator(this,0); } PersonSet1::iterator PersonSet1::end() { return iterator(this, length); }
C++98的遍历方式:
PersonSet1 ps(new NativePerson[10],10); for (PersonSet1::iterator pp = ps.begin();pp != ps.end();pp++) { cout << (*p).name << endl; }
C++11的遍历方式:
PersonSet1 ps(new NativePerson[10],10); for (auto p : ps) { cout<<p.name<<endl; }
这里有个问题,按照要求,迭代器似乎必须知道最后一个元素,那对于只能遍历,得等到遍历到最后一个元素才能知道他的存在(没有后继),是否就没法实现了呢?也不是,可以这么变通:begin()和end()不是各返回一个迭代器么,前者的游标会动,后者不动。给迭代器设置一个成员curPos,begin()返回的迭代器curPos为0,end()返回的迭代器curPos为1,然后当begin()的迭代器迭代到没有没有后继时,把curPos设为1,然后不就能使得循环退出么?
实现方法,假设有一个读取NativePerson的读取器,他长这样的:
class PersonReader { public: bool next(); NativePerson get(); };
然后就可以这样实现:
头文件:
class PersonSet2 { PersonReader& reader; public: class iterator { PersonSet2& parent; CurPos curPos; public: iterator(PersonSet2& parent);//begin iterator();//end iterator& operator++(); bool operator!=(iterator& other); NativePerson operator*(); }; PersonSet2(PersonReader& reader); iterator begin(); iterator end(); };
CPP文件:(部分)
PersonSet2::iterator::iterator(PersonSet2 & parent) : parent(parent) { curPos = BEGIN; operator++();//必须调用一次operator++()保证指针处在第一个元素。 } PersonSet2::iterator::iterator() : parent(*(PersonSet2*)nullptr) { curPos = END; } PersonSet2::iterator & PersonSet2::iterator::operator++() { if (parent.reader.next()) { //把读取器的游标往后移动后要做的事 } else { curPos = END;//这样就把迭代器标记为末元素 } return *this; } bool PersonSet2::iterator::operator!=(iterator & other) { return curPos != other.curPos;//如果没读到最后一个元素,迭代器游标位置为BEGIN,否则就被设为END } NativePerson PersonSet2::iterator::operator*() { return parent.reader.get(); } PersonSet2::iterator PersonSet2::begin() { return iterator(*this); } PersonSet2::iterator PersonSet2::end() { return iterator(); }
然后就没有然后了。还有什么问题么?
相关文章推荐
- 【C++ OpenGL ES 2.0编程笔记】1: OpenGL ES 2.0 渲染管线和EGL
- 抽象的工厂方法
- 《C语言及程序设计》第31讲实践项目
- C语言编译全过程(有图有真相)
- C++折半查找法练习
- C语言实现的线程池
- 类所占内存的大小
- [转] Cz/C++中栈空间、堆空间,及内存区域的划分
- 三个C++资源链接(大量)
- [C++]拷贝构造的玄机
- 一起talk C栗子吧(第九十 二回:C语言实例--浅谈typedef)
- Vector 的使用(C++)
- c++实现反射机制
- C++编程练习——汉诺(haoni)塔问题
- C++编程练习——用递归法实现1^2+2^2+3……+.... n^2
- C++编程练习——用递归法将一个整数N转换成字符串
- C++中虚继承、虚函数
- POJ 1008_Maya Calendar
- C++11新特性应用--让你的程序更高效(右值引用避免深拷贝)
- C++11新特性应用--让你的程序更高效(右值引用避免深拷贝)