65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (10投票s)

2002年6月13日

8分钟阅读

viewsIcon

317467

downloadIcon

823

关于创建可枚举和可排序的自定义集合类的教程。详细介绍了 IEnumerable、IEnumerator、IComparable 和 IComparer 接口的用法

引言

集合类基本上是一系列索引对象。这意味着我们知道集合在任何时候有多少对象。我们可以逐一枚举这些对象。我们可以访问与特定索引关联的对象。通常集合类也是可排序的。对于可排序的集合类,它所包含或收集的对象必须与其同类型对象可进行比较。在本教程中,我们将看到如何设计和创建我们自己的集合类。我们将创建一个名为 Student 的类,该类实现 IComparable 接口。这将作为我们收集的对象。我们还将有一个名为 StudentAgeComparer 的嵌套类,它实现 IComparer 接口。这个类用于排序目的。然后,我们将创建名为 StudentCollection 的类,它是 Student 类的集合类,并实现 IEnumerable 接口。StudentCollection 类将包含一个名为 StudentEnumerator 的嵌套类,该类实现 IEnumerator 接口。这就是实现了使类可枚举所需的所有方法和属性的接口。顺便说一句,C# 程序员可能会对能够使用 foreach 来枚举实现 IEnumerableIEnumerator 接口的集合类感兴趣。

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 的替代排序方法,我们调用 ArrayListSort() 方法的另一个重载,其中传递一个实现 IComparer 接口的对象的实例。在这种情况下,我们传递 StudentAgeComparer 类的新实例。现在,IComparableIComparer 接口的重要性对您来说应该更加清楚了。现在,让我们看一下 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;
}
© . All rights reserved.