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

第一种语言,也是实现起来最简单的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 = -;//游标一定要设为空! }
}

调用方法:

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 = -;
} 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,);
} PersonSet1::iterator PersonSet1::end()
{
return iterator(this, length);
}

C++98的遍历方式:

 PersonSet1 ps(new NativePerson[],);
for (PersonSet1::iterator pp = ps.begin();pp != ps.end();pp++)
{
cout << (*p).name << endl;
}

C++11的遍历方式:

 PersonSet1 ps(new NativePerson[],);
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();
}

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

自定义能够for each的类,C#,Java,C++,C++/cli的实现方法的更多相关文章

  1. 12 自定义标签/JSTL标签库/web国际化/java web之设计模式和案例

    EL应用      自定义一个标签,实现两个字符串的相加 1回顾      1.1servlet生命周期           init(ServletConfig)           service ...

  2. java能不能自己写一个类叫java.lang.System/String正确答案

    原文: http://www.wfuyu.com/php/22254.html 未做测试 ! 最近学习了下java类加载相干的知识.然后看到网上有1道面试题是 能不能自己写个类叫java.lang.S ...

  3. 根据异常自定义处理逻辑(【附】java异常处理规范)

    ▄︻┻┳═一『异常捕获系列』Agenda: ▄︻┻┳═一有关于异常捕获点滴,plus我也揭揭java的短 ▄︻┻┳═一根据异常自定义处理逻辑([附]java异常处理规范) ▄︻┻┳═一利用自定义异常来 ...

  4. 集合-强大的集合工具类:java.util.Collections中未包含的集合工具

    任何对JDK集合框架有经验的程序员都熟悉和喜欢java.util.Collections包含的工具方法.Guava沿着这些路线提供了更多的工具方法:适用于所有集合的静态方法.这是Guava最流行和成熟 ...

  5. MIME类型和Java类型

    MIME类型和Java类型 类型转换Spring Cloud Stream提供的开箱即用如下表所示:“源有效载荷”是指转换前的有效载荷,“目标有效载荷”是指转换后的“有效载荷”.类型转换可以在“生产者 ...

  6. JVM 自定义类加载器在复杂类情况下的运行分析

    一.自定义类加载器在复杂类情况下的运行分析 1.使用之前创建的类加载器 public class MyTest16 extends ClassLoader{ private String classN ...

  7. [Google Guava] 2.3-强大的集合工具类:java.util.Collections中未包含的集合工具

    原文链接 译文链接 译者:沈义扬,校对:丁一 尚未完成: Queues, Tables工具类 任何对JDK集合框架有经验的程序员都熟悉和喜欢java.util.Collections包含的工具方法.G ...

  8. [Google Guava] 强大的集合工具类:java.util.Collections中未包含的集合工具

    转载的,有问题请联系我 原文链接 译文链接 译者:沈义扬,校对:丁一 尚未完成: Queues, Tables工具类 任何对JDK集合框架有经验的程序员都熟悉和喜欢java.util.Collecti ...

  9. 使用 Arrays 类操作 Java 中的数组

    Arrays 类是 Java 中提供的一个工具类,在 java.util 包中.该类中包含了一些方法用来直接操作数组,比如可直接实现数组的排序.搜索等(关于类和方法的相关内容在后面的章节中会详细讲解滴 ...

随机推荐

  1. vector的 emplace 和 insert 以及使用vector进行iterator遍历 且 erase的时候注意事项

    vector<int> first;//Size()==2 first.push_back(); first.push_back(); //first.insert(2); vector& ...

  2. java中获取比毫秒更为精确的时间

    所以这里提醒做非常精确的时间统计的朋友,谨慎使用System.currentTimeMillis() . 在Java中可以通过System.currentTimeMillis()或者System.na ...

  3. Python LDAP中的时间戳转换为Linux下时间

    (Get-ADUser zhangsan -Properties badpasswordtime).badpasswordtime返回值为:131172610187388712131172610187 ...

  4. 创建一个Table View

    在本课程中,您将创建应用程序FoodTracker的主屏幕.您将创建第二个,表视图为主场景,列出了用户的菜谱.你会设计定制表格单元格显示每一个菜谱,它是这样的: 学习目标 在课程结束时,你将能够: 创 ...

  5. Aapache status / apache2ctl status 总是403

    默认apache2ctl status访问的是http://localhost:80/server_status 所以得搞定default这个站点,放歌html就可以了. 在default的配置里加入 ...

  6. ARM中C和汇编混合编程及示例(转)

    在嵌入式系统开发中,目前使用的主要编程语言是C和汇编,C++已经有相应的编译器,但是现在使用还是比较少的.在稍大规模的嵌入式软件中,例如含有OS,大部分的代码都是用C编写的,主要是因为C语言的结构比较 ...

  7. Android 5.0 双卡信息管理分析

    首先,如前面的博文所讲的,Android5.0开始支持双卡了.另外,对于双卡的卡信息的管理,也有了实现,尽管还不是完全彻底完整,如卡的slot id, display name,iccid,color ...

  8. Winform下richtextbox截图实现

    #region 根据richtextbox创建GDI+ private void DrawGDI(RichTextBox rich,Panel panl,PictureBox p2) { rich.U ...

  9. Struts2返回json

    Action怎么返回json类型数据?方法1,使用struts2的插件struts2-json-plugin-2.3.8.jar(在下载的strut2库文件夹中). 在struts2.xml中对要返回 ...

  10. 用户控件的设计要点 System.Windows.Forms.UserControl

    用户控件的设计要点 最近的项目中有一个瀑布图(彩图)的功能,就是把空间和时间上的点量值以图的形式呈现出来,如下图: X坐标为空间,水平方向的一个像素代表一个空间单位(例如50米) Y坐标为时间,垂直方 ...