自定义能够for each的类,C#,Java,C++,C++/cli的实现方法

      自定义类能够被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 }

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

 

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/272477.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SQL Server 2008 R2:快速清除日志文件的方法

本例&#xff0c;快速清理“students”数据库的日志&#xff0c;清理后日志文件不足1M。USE [master] GO ALTER DATABASE students SET RECOVERY SIMPLE WITH NO_WAIT GO ALTER DATABASE students SET RECOVERY SIMPLE GO USE students GO--此处需要注意&#xff…

linux网络编程之字节序

进程间通信 特点&#xff1a;依赖于内核&#xff0c;造成缺陷——无法实现多机通信。 网络编程 地址&#xff1a;由IP地址和端口号构成&#xff0c;端口号用来判断客户端接入哪个服务器。 数据的交流&#xff1a;涉及到协议&#xff08;http&#xff0c;tcp&#xff0c;udp&…

Oracle查看表空间和表空间中的对象

select * from user_tables;--查询所有用户表 select username,default_tablespace from user_users;--查询当前表空间select tablespace_name from dba_tablespaces;--查询所有表空间select tablespace_name, sum(bytes)/1024/1024 from dba_data_files group by tablespace_n…

C#中DateTime.Ticks属性及Unix时间戳转换

DateTime.Ticks&#xff1a;表示0001 年 1 月 1 日午夜 12:00:00 以来所经历的 100 纳秒数&#xff0c;即Ticks的属性为100纳秒&#xff08;1Ticks 0.0001毫秒&#xff09;。Unix时间戳&#xff1a;是从1970年1月1日&#xff08;UTC/GMT的午夜&#xff09;开始所经过的秒数&am…

WebBrowser控件的常用方法、属性和事件

1. 属性属性说明Application如果该对象有效&#xff0c;则返回掌管WebBrowser控件的应用程序实现的自动化对象(IDispatch)。如果在宿主对象中自动化对象无效&#xff0c;这个程序将返回WebBrowser 控件的自动化对象Parent返回WebBrowser控件的父自动化对象&#xff0c;通常是一…

二维码高亮

// 二维码高亮。http://blog.sina.com.cn/s/blog_a843a8850102uy6w.html 转载于:https://www.cnblogs.com/muyushifang07/p/5114667.html

socket 网络 编程

网络编程场景 自己是客户端站在5栋楼前&#xff0c;自己要找到5栋楼中的一座并进入某一间房间&#xff0c;这时第二座楼上有人在用汉语&#xff08;tcp/udp&#xff09;说话,我的ip地址&#xff08;楼号&#xff09;是…&#xff0c;我的端口号&#xff08;房间号&#xff09;是…

7个免费的Linux FTP客户端工具

在Dropbox、YouSendIt、idrive以及许多这样云存储和共享工具的帮助下&#xff0c;我们在互联网上发送和共享大型文件变得容易起来。所有这些网站都可以帮助你在互联网上传送文件&#xff0c;但如果你要分享庞大的数据&#xff0c;这依然是很复杂的事情。所以&#xff0c;你需要…

树莓派的几种登录方式及树莓派的网络配置

&#xff08;1&#xff09;HDMI 视频线 连接到显示器 &#xff08;2&#xff09;串口 设备破解&#xff1a; 默认情况下,树莓派的串口和蓝牙连接&#xff0c;把串口用来数据通信。 修改系统配置&#xff0c;启用串口登录树莓派 1.打开SD卡根目录的"config.txt"文件…

C语言之常量与变量

1.常量 1.1整型常量:短整型(short int),整型(int),长整型(long int).短整型和长整型都可省虑后面的int,三者唯一的区别就是内存大小的区别,从小到大依次为short < int < long. int a;short int b;long int c;  printf("%d,%d",a,b);  printf("%ld&quo…

【收集】11款Linux数据恢复工具

如果你使用的是Linux操作系统&#xff0c;那么你一定想知道一旦硬盘崩溃的话又该如何保存和恢复数据。其实&#xff0c;现在有很多Linux数据恢复工具可以让我们摆脱数据安全的困扰。小编已经为各位准备好了一些最好的Linux数据恢复工具&#xff0c;欢迎大家品鉴。KnoppixKnoppi…

VIM更新

1、可以用以下指令 sudo apt-get install vim2、默认的是国外的源&#xff0c;apt-get 安装失败的时候&#xff0c;我们更换成国内的源。 &#xff08;1&#xff09; 编辑sources.list 打开终端输入 sudo nano /etc/apt/sources.list用#注释或直接删除原有的内容&#xff0c…

svn 常用操作命令

检出svn co svn://xxxxx/svn/ios --username jm --password 123 通常情况下&#xff0c;命令svn add *会忽略所有已经在版本控制之下的目录&#xff0c;有时候&#xff0c;你会希望添加所有工作拷贝的未版本化文件&#xff0c;包括那些隐藏在深处的文件&#xff0c;可以使用svn…

8款适合Linux用户使用的数据库管理工具

从内容管理系统到简单的表格&#xff0c;数据库是每一个开发项目的一部分。这就是为什么开发者们如此强调使用正确类型的数据库工具。下面这些可能对您有所帮助&#xff01;1. AutotablaAutotabla是一个你的程序的SQL数据表的CGI管理界面。只需要提供你数据库架构的XML描述&…

linux库引入之分文件编程

分文件编程好处 将main函数和其他功能性函数放在不同的文件中&#xff0c;分模块的编程思想&#xff0c;分工明确&#xff0c;查找错误比较容易&#xff0c;责任可以划分清楚&#xff0c;程序也方便调试&#xff0c;并且主函数比较简洁。 将文件从同一目录下的另一个文件夹拷贝…

为什么Chrome浏览器特爱吃内存

微软用惯用的手法——改名——给 IE 被黑的一生画上了句号。还好&#xff0c;它在技术段子圈里早就有了接班人&#xff1a;Chrome。Chrome 很好很强大&#xff0c;速度极快、功能很多。但同时它也是你的电脑内存不足或者耗电太快的罪魁祸首。没办法&#xff0c;Chrome 太爱吃内…

nyoj--120--校园网络(scc+缩点)

校园网络 时间限制&#xff1a;3000 ms | 内存限制&#xff1a;65535 KB难度&#xff1a;5描述南阳理工学院共有M个系&#xff0c;分别编号1~M,其中各个系之间达成有一定的协议&#xff0c;如果某系有新软件可用时&#xff0c;该系将允许一些其它的系复制并使用该软件。但该允…

SQL的四种连接用法整理

1、内联接&#xff08;典型的联接运算&#xff0c;使用像 或 <> 之类的比较运算符&#xff09;。包括相等联接和自然联接。 内联接使用比较运算符根据每个表共有的列的值匹配两个表中的行。例如&#xff0c;检索 students和courses表中学生标识号相同的所有行。 …

简述 OAuth 2.0 的运作流程

本文将以用户使用 github 登录网站留言为例&#xff0c;简述 OAuth 2.0 的运作流程。 假如我有一个网站&#xff0c;你是我网站上的访客&#xff0c;看了文章想留言表示「朕已阅」&#xff0c;留言时发现有这个网站的帐号才能够留言&#xff0c;此时给了你两个选择&#xff1a;…

linux库引入之动态库静态库(生成和使用)

库&#xff1a; 库是一种可执行代码的二进制形式&#xff0c;可以被操作系统载入内存执行。就是将源代码转化为二进制格式的源代码&#xff0c;相当于进行了加密&#xff0c;别人可以使用库&#xff0c;但是看不到库中的内容。 如何使用 用户需要同时具有头文件和库。 头文件…