zoukankan      html  css  js  c++  java
  • 自定义能够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 }

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

  • 相关阅读:
    java获取指定月份有几个星期x,获取指定月份跨了多少个星期
    linux下vim编辑器使用
    bash Shell条件测试
    grep与正则表达式
    网络基础--NAT
    网络基础-DHCP
    Python--元组(tuple)
    Python--元组(tuple)
    Linux--用户管理
    Linux--用户管理
  • 原文地址:https://www.cnblogs.com/CCQLegend/p/5110755.html
Copyright © 2011-2022 走看看