自定義能夠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.pswp.cn/news/272477.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/272477.shtml
英文地址,請注明出處:http://en.pswp.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;但是看不到庫中的內容。 如何使用 用戶需要同時具有頭文件和庫。 頭文件…