博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
自定义能够for each的类,C#,Java,C++,C++/cli的实现方法
阅读量:4309 次
发布时间:2019-06-06

本文共 8848 字,大约阅读时间需要 29 分钟。

      自定义类能够被for each,应该算是个老生常谈的话题了,相关的资料都很多,不过这里整理总结主流语言的不同实现方式,并比较部分细节上的差异。

      第一种语言,也是实现起来最简单的Java语言。在Java里,要被for each,就须实现Iterable<T>接口。Iterable<T>接口定义有一个方法(注:Java8以后多了两个default方法,不用管他):

1 Iterator
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
{10 private Person[] persons;//容器类存放数据,数组本身就可以被for each,只是这里演示如何使用Iterable
接口。11 public PersonSet(){12 //构造函数13 }14 @Override15 public Iterator
iterator() {16 // TODO Auto-generated method stub17 return new Iterator
() {18 private int index=0;//迭代器存放游标19 @Override20 public boolean hasNext() {21 // TODO Auto-generated method stub22 return index < persons.Length;23 }24 25 @Override26 public Person next() {27 // TODO Auto-generated method stub28 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
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
6 { 7 private Person[] persons; 8 public PersonSet() 9 {
//构造函数10 }11 public IEnumerator
GetEnumerator() => new Enumerator(this);12 13 IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();//显式接口实现,直接返回泛型版本就可14 private class Enumerator : IEnumerator
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
8 { 9 ref class Enumerator : System::Collections::Generic::IEnumerator
10 {11 private:12 PersonSet^ parent;13 public:14 Enumerator();15 ~Enumerator();16 property Person^ Current{ virtual Person^ get(); };17 property Object^ NonGenericCurrent18 {19 virtual Object^ get() final = System::Collections::IEnumerator::Current::get;20 //通过重命名地方法来实现C#的“显式接口调用”的效果。21 }22 virtual bool MoveNext();23 virtual void Reset();24 };25 array
^ persons;26 public:27 PersonSet();28 virtual System::Collections::Generic::IEnumerator
^ GetEnumerator() final;29 virtual System::Collections::IEnumerator^ GetNonGenericEnumerator() final30 = 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
^ 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 iterator11     {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<
<

     这里有个问题,按照要求,迭代器似乎必须知道最后一个元素,那对于只能遍历,得等到遍历到最后一个元素才能知道他的存在(没有后继),是否就没法实现了呢?也不是,可以这么变通:begin()和end()不是各返回一个迭代器么,前者的游标会动,后者不动。给迭代器设置一个成员curPos,begin()返回的迭代器curPos为0,end()返回的迭代器curPos为1,然后当begin()的迭代器迭代到没有没有后继时,把curPos设为1,然后不就能使得循环退出么?

     实现方法,假设有一个读取NativePerson的读取器,他长这样的:

1 class PersonReader2 {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);//begin11         iterator();//end12         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     else18     {19         curPos = END;//这样就把迭代器标记为末元素20     }21     return *this;22 }23 24 bool PersonSet2::iterator::operator!=(iterator & other)25 {26     return curPos != other.curPos;//如果没读到最后一个元素,迭代器游标位置为BEGIN,否则就被设为END27 }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 }

然后就没有然后了。还有什么问题么?

 

转载于:https://www.cnblogs.com/CCQLegend/p/5110755.html

你可能感兴趣的文章
应该是引用了项目本身!从引用中册除对所在项目的引用就可以了!
查看>>
通过脚本自动安装探针
查看>>
ubuntu 11.04 可用源更新
查看>>
微信服务号获取openId流程(订阅号)
查看>>
struts 与 jquery 整合
查看>>
日期处理 给定日期,获取是星期几
查看>>
回车与换行的区别
查看>>
安装hadoop2.6.0伪分布式环境
查看>>
LoadRunner 如何设置关联
查看>>
正则表达式中的?=,?!,?<=,?<!(预查)解释小栗子
查看>>
zookeeper3.4.13几个参数
查看>>
Memcached学习(二)--命令解析
查看>>
Python 集合
查看>>
几本关于PHP安全的书
查看>>
学习记录--HooKSystemCall
查看>>
使用apache设置绑定多个域名或网站
查看>>
bzoj2194: 快速傅立叶之二
查看>>
2018-2019-2 20189206 《密码与安全新技术专题》 第四次作业
查看>>
CentOS7如何设置静态IP及开放DNS端口
查看>>
精密V / I 转换电路
查看>>