使用 MC++ 实现自定义集合类





5.00/5 (10投票s)
2002年6月13日
8分钟阅读

317467

823
关于创建可枚举和可排序的自定义集合类的教程。详细介绍了 IEnumerable、IEnumerator、IComparable 和 IComparer 接口的用法
引言
集合类基本上是一系列索引对象。这意味着我们知道集合在任何时候有多少对象。我们可以逐一枚举这些对象。我们可以访问与特定索引关联的对象。通常集合类也是可排序的。对于可排序的集合类,它所包含或收集的对象必须与其同类型对象可进行比较。在本教程中,我们将看到如何设计和创建我们自己的集合类。我们将创建一个名为 Student
的类,该类实现 IComparable
接口。这将作为我们收集的对象。我们还将有一个名为 StudentAgeComparer
的嵌套类,它实现 IComparer
接口。这个类用于排序目的。然后,我们将创建名为 StudentCollection
的类,它是 Student
类的集合类,并实现 IEnumerable
接口。StudentCollection
类将包含一个名为 StudentEnumerator
的嵌套类,该类实现 IEnumerator
接口。这就是实现了使类可枚举所需的所有方法和属性的接口。顺便说一句,C# 程序员可能会对能够使用 foreach
来枚举实现 IEnumerable
和 IEnumerator
接口的集合类感兴趣。
IEnumerable 接口
IEnumerable
是一个接口,如果集合类的成员要被枚举,则该集合类会实现它。foreach
C# 关键字只能枚举实现 IEnumerable
的类。它的唯一目的是公开用于按顺序枚举集合中各种对象的枚举器对象。这个枚举器对象只不过是一个实现了 IEnumerator
接口的类的实例的指针。通常,当有一个类实现 IEnumerable
接口时,我们会有一个内部类作为该类的枚举器类。内部枚举器类将实现 IEnumerator
。
IEnumerable
接口仅定义一个名为 GetEnumerator
的方法,该方法简单地返回类的枚举器。下面展示了一个典型的场景,我们有一个名为 AbcCollection 的集合类,它实现 IEnumerable
,还有一个名为 AbcCollectionEnumerator 的嵌套内部类,它实现 IEnumerator
。使实现 IEnumerator
的类成为 IEnumerable
实现类的嵌套内部类的优点是,枚举器可以访问集合类的私有字段和方法。
__gc class AbcCollection : public IEnumerable
{
public:
IEnumerator* GetEnumerator()
{
...
}
__gc class AbcCollectionEnumerator : public IEnumerator
{
...
};
};
IEnumerator 接口
允许枚举其收集项的任何集合类都必须实现一个实现 IEnumerator
接口的枚举器。枚举器最初定位在第一个项目之前。有一个名为 MoveNext
的方法,它将枚举器推进到集合中的下一个对象。如果枚举器成功前进,MoveNext
返回 true。如果 MoveNext
由于枚举器已到达集合末尾而失败,则返回 false。如果枚举器实例化后集合已修改,则 MoveNext
将抛出 InvalidOperationException
异常。
IEnumerator
接口有一个名为 Current
的属性,它返回枚举器指向的集合中的当前对象。如果枚举器定位在集合的第一个元素之前,或者枚举器已经枚举到集合的边缘之外,则会抛出 InvalidOperationException
异常。如果枚举器实例化后集合已修改,也会抛出 InvalidOperationException
异常。IEnumerator
实现的第三个也是最后一个成员是 Reset
方法,它会将枚举器重置到集合中第一个对象之前的位置。通常,实现 IEnumerator
接口的类有一个接受参数的构造函数。这个参数的类型将是集合类的类型,该类将被枚举。还将有一个集合类型的私有成员,并且构造函数将用传递给它的参数对其进行初始化。
__gc class AbcCollectionEnumerator : public IEnumerator
{
AbcCollectionEnumerator(AbcCollection* abc)
{
m_privmember = abc;
...
}
...
};
IComparable 接口
任何需要排序的类都需要实现 IComparable
。严格来说,这不是绝对正确的,因为可以使用另一个类作为我们类的对象的比较器,并且这个比较器类需要实现 IComparer
接口。但我们稍后会介绍这一点。总之,IComparable
只定义一个成员,即一个名为 CompareTo
的方法。CompareTo
将接受一个与实现 IComparable
的类类型相同的对象,并返回一个 int
。如果对象等于当前实例,则返回的 int
为 0;如果当前实例小于对象,则为负数;如果当前实例大于对象,则为非零正数。实现 IComparable
的对象的数组或数组列表是可排序的。
IComparer 接口
IComparer
接口提供了一种比较两个对象的方法,用于对集合类进行排序。Sort()
函数的一个重载接受 IComparer
对象作为参数,并将使用此 IComparer
来比较要排序的对象。IComparer
接口仅定义一个名为 Compare
的方法。与 IComparable
接口的 CompareTo
方法类似,如果两个对象相等,则返回 0。如果第一个对象小于第二个对象,则返回负数;如果第一个对象大于第二个对象,则返回非零正数。典型的实现是通过比较要比较的对象的成员的一个或多个的 CompareTo
方法来完成的。
Student 类
我们将实现的 Student
类将实现 IComparable
,以便 Student
对象可以进行比较和排序。此外,我们还将有一个名为 StudentAgeComparer
的内部嵌套类,它提供了一种基于默认标准以外的标准进行排序的替代方法。Student
类下面以不完整的方式列出,以节省空间。要查看完整的列表,您可以查看文章提供的源代码或文章末尾提供的完整代码列表。
__gc class Student : public IComparable
{
public:
Student(String* sname, String* srollnum, int sage)
{
...
}
void Display()
{
...
}
int CompareTo(Object* obj)
{
Student* tmp = dynamic_cast<Student*>(obj);
return m_name->CompareTo(tmp->m_name);
}
__gc class StudentAgeComparer : public IComparer
{
...
};
private:
String* m_name;
String* m_rollnum;
int m_age;
};
正如你所看到的,我们在 CompareTo
方法中没有做任何花哨的事情。我们只是通过调用对象 m_name
成员(这是一个 String
)的 CompareTo
来委托给较低级别进行比较。String
类实现了 IComparable
,因此我们节省了一些编码,这很棒。因此,默认情况下,Student
对象集合将根据 Student
对象的 m_name
成员进行排序。现在,假设有人说他们想按年龄对 Student
对象集合或数组进行排序。为了应对这种情况,我们有一个名为 StudentAgeComparer
的内部类,它实现了 IComparer
接口。我们将在下面看到它的实现。
__gc class StudentAgeComparer : public IComparer
{
public:
int Compare(Object* x,Object* y)
{
Student* x1 = dynamic_cast<Student*>(x);
Student* y1 = dynamic_cast<Student*>(y);
return x1->m_age.ToString()->CompareTo(y1->m_age.ToString());
}
};
我们再次将比较委托给较低级别。这次,我们通过调用 m_age
成员的 ToString()
方法返回的 String
来调用 CompareTo
函数。我们可以直接对其调用 int
,但那样我们就必须自己进行装箱,记住这是 MC++,而不是 C#。我认为最好使用 String
形式进行比较。稍后,当我们实现集合时,我们将看到如何实现一个替代排序,该排序使用 StudentAgeComparer
按学生的年龄而不是默认的学生姓名进行排序。
StudentCollection 类
StudentCollection
类是 Student
对象的集合类,并实现 IEnumerable
。它还有一个名为 StudentEnumerator
的内部类,它充当 StudentCollection
类的枚举器。StudentCollection
类有一个私有的 ArrayList
成员,用于存储各种 Student
对象。我选择 ArrayList
而不是 Array
是因为 ArrayList
是动态可调整大小的。因此,我不需要指定默认大小。当我不停地添加对象时,ArrayList
会不断增长。StudentCollection
列在下面,和以前一样,列表只是部分列表。有关完整的代码列表,请参阅文章附带的压缩源代码或文章末尾的完整列表。
__gc class StudentCollection : public IEnumerable
{
public:
StudentCollection()
{
m_students = new ArrayList();
m_dirty = false;
}
__gc class StudentEnumerator : public IEnumerator
{
...
};
IEnumerator* GetEnumerator()
{
m_dirty = false;
return dynamic_cast<IEnumerator*>(new StudentEnumerator(this));
}
int Add(Object* value)
{
m_dirty = true;
return m_students->Add(value);
}
void Sort()
{
m_dirty=true;
m_students->Sort();
}
void SortByAge()
{
m_dirty=true;
m_students->Sort(new Student::StudentAgeComparer());
}
private:
ArrayList* m_students;
bool m_dirty;
};
GetEnumerator()
方法返回 StudentEnumerator
类的新实例。因此,每次调用 GetEnumerator()
都会创建一个新的枚举器。如果对集合进行了任何更改,我们会将一个脏标志设置为 true,以指示枚举器集合在枚举器创建后已被更改。提供了两个 Sort 方法。第一个 Sort
方法只是调用 ArrayList
类的零参数 Sort
重载方法。在这种情况下,可以对 Student
对象进行比较和排序,因为它实现了 IComparable
。还有一个称为 SortByAge
的替代排序方法,我们调用 ArrayList
的 Sort()
方法的另一个重载,其中传递一个实现 IComparer
接口的对象的实例。在这种情况下,我们传递 StudentAgeComparer
类的新实例。现在,IComparable
和 IComparer
接口的重要性对您来说应该更加清楚了。现在,让我们看一下 StudentEnumerator
类。
__gc class StudentEnumerator : public IEnumerator
{
public:
StudentEnumerator(StudentCollection* sc)
{
m_sc = sc;
index = -1;
}
Object* get_Current()
{
if(index < 0 || index >= m_sc->m_students->Count || m_sc->m_dirty)
throw new InvalidOperationException();
return m_sc->m_students->Item[index];
}
void Reset()
{
m_sc->m_dirty = false;
index = -1;
}
bool MoveNext()
{
if(m_sc->m_dirty)
throw new InvalidOperationException();
index++;
return (index < m_sc->m_students->Count);
}
private:
int index;
StudentCollection* m_sc;
};
枚举器类有一个 StudentCollection
类型的私有成员,在构造函数中,我们将传递的 StudentCollection
对象赋给这个私有成员。我们还有一个索引,默认设置为 -1,因为 ArrayList
的索引从 0 开始。当调用 Reset
方法时,我们只需将索引重置为 -1 并将脏标志设置为 false。在 MoveNext
()
方法中,如果脏标志已设置,我们将抛出 InvalidOperationException
异常。否则,我们递增索引,如果我们没有超出集合的末尾,则返回 true
,否则返回 false
。类似地,在 Current
属性的 get
实现中,我们检查索引是否小于 0,或者是否已超出集合的限制。在任一情况下,或者如果脏标志已设置,都将抛出 InvalidOperationException
类型的异常。否则,将返回 ArrayList
中具有当前索引的 Student
对象。
枚举我们的集合
StudentCollection* sc = new StudentCollection();
//Populate the collection
IEnumerator* iEnum = sc->GetEnumerator();
while(i->MoveNext())
{
Student* stmp = dynamic_cast<Student*>(iEnum->Current);
stmp->Display();
}
示例屏幕截图
完整源代码列表
#include "stdafx.h"
#using <mscorlib.dll>
#include <tchar.h>
using namespace System;
using namespace System::Collections;
__gc class Student : public IComparable
{
public:
Student(String* sname, String* srollnum, int sage)
{
m_name = sname;
m_rollnum = srollnum;
m_age=sage;
}
void Display()
{
Console::WriteLine("{0}{1}{2}",
m_name->PadRight(30),
m_rollnum->PadRight(15),m_age.ToString());
}
int CompareTo(Object* obj)
{
Student* tmp = dynamic_cast<Student*>(obj);
return m_name->CompareTo(tmp->m_name);
}
__gc class StudentAgeComparer : public IComparer
{
public:
int Compare(Object* x,Object* y)
{
Student* x1 = dynamic_cast<Student*>(x);
Student* y1 = dynamic_cast<Student*>(y);
return x1->m_age.ToString()->CompareTo(
y1->m_age.ToString());
}
};
private:
String* m_name;
String* m_rollnum;
int m_age;
};
__gc class StudentCollection : public IEnumerable
{
public:
StudentCollection()
{
m_students = new ArrayList();
m_dirty = false;
}
__gc class StudentEnumerator : public IEnumerator
{
public:
StudentEnumerator(StudentCollection* sc)
{
m_sc=sc;
index = -1;
}
Object* get_Current()
{
if(index <0 || index >= m_sc->m_students->Count ||
m_sc->m_dirty)
throw new InvalidOperationException();
return m_sc->m_students->Item[index];
}
void Reset()
{
m_sc->m_dirty = false;
index=-1;
}
bool MoveNext()
{
if(m_sc->m_dirty)
throw new InvalidOperationException();
index++;
return (index < m_sc->m_students->Count);
}
private:
int index;
StudentCollection* m_sc;
};
IEnumerator* GetEnumerator()
{
m_dirty = false;
return dynamic_cast<IEnumerator*>(
new StudentEnumerator(this));
}
int Add(Object* value)
{
m_dirty = true;
return m_students->Add(value);
}
void Sort()
{
m_dirty=true;
m_students->Sort();
}
void SortByAge()
{
m_dirty=true;
m_students->Sort(new Student::StudentAgeComparer());
}
private:
ArrayList* m_students;
bool m_dirty;
};
int _tmain(void)
{
Student *s1 = new Student("Sally Smith","GF/45/112",22);
Student *s2 = new Student("Alice Brown","GF/45/117",27);
Student *s3 = new Student("Linda Mathews","GF/45/120",23);
StudentCollection* sc = new StudentCollection();
sc->Add(s1);
sc->Add(s2);
sc->Add(s3);
IEnumerator* iEnum = sc->GetEnumerator();
while(iEnum->MoveNext())
{
Student* stmp = dynamic_cast<Student*>(iEnum->Current);
stmp->Display();
}
sc->Sort();
iEnum->Reset();
Console::WriteLine();
while(iEnum->MoveNext())
{
Student* stmp = dynamic_cast<Student*>(iEnum->Current);
stmp->Display();
}
sc->SortByAge();
iEnum->Reset();
Console::WriteLine();
while(iEnum->MoveNext())
{
Student* stmp = dynamic_cast<Student*>(iEnum->Current);
stmp->Display();
}
return 0;
}