精品主页 | 软件下载 | 系统下载 | 精品导航| 精彩图片 | 转帖工具 | 版主申请 | 影视下载
发新话题
打印

实例解析C /CLI中的接口与泛型

实例解析C /CLI中的接口与泛型

  


                    接口



  某些时候,让不相关的类分享一组公有成员,以便产生相同的行为,是非常有用的。一个最基本的方法可能是通过一个公共的基类来定义它们,但这种方法太受局限,因为它要求这些类通过继承而互相关联,另外,它们也许还有着各自的基类,且CLI类型只支持单一类继承。



  C  /CLI提供了一种方法,可利用多个类实现一组通用的功能,这就是我们通称的"接口",而一个接口则是一组成员函数的声明。要注意,这些函数只是声明,没有定义,也就是说,一个接口定义了一个由抽象函数组成的类型--这些函数实际上是纯虚函数,且在适当的时候,这些函数由客户类来实现。一个接口可允许不相关的类用同一名称和类型,实现同一功能,而无须要求这些类分享公共基类。在例1中演示了怎样定义一个接口。



  例1:












using namespace System;

public interface class ICollection

{

 void Put(Object^ o); //隐式public abstract

 Object^ Get(); //隐式public abstract

};

  一个接口的定义看上去非常像一个类,除了用interface取代了ref或value,所有的函数都没有函数体,且均隐式为public和abstract。按照通常的约定,一个接口名带有起始字母I,后再接一个大写字母。(接口类与接口结构是等价的。)与类相似,一个接口也能有public或private访问可见性。



  一个接口能有一个或多个"基接口",在这种情况下,它将继承这些接口中的所有抽象函数,例如,在例2中,接口I2显式继承自I1,而I3显式继承自I1与I2,并通过I2隐式继承自I1。



  例2:












interface class I1 { /* ... */ };

interface class I2 : I1 { /* ... */ };

interface class I3 : I1, I2 { /* ... */ };

  一个类可像从基类继承时那样,来实现一个接口,见例3。



  例3:












public ref class List : ICollection

{

 public:

  void Put(Object^ o)

  {

   // ...

  }

  Object^ Get()

  {

   // ...

  }

  // ...

};




  一个类能实现一个以上的接口,在这种情况下,必须使用逗号来分隔接口列表,顺序倒不是很重要。当然,一个类在实现一个或多个接口时,也能显式地带有一个基类,在这种情况下,基类通常(但不是必须)写在最前面。



  如果一个类实现了一个接口,但没有定义接口中所有的函数,这个类就必须声明为abstract。当然了,任何从抽象类继承而来的类也是抽象类,除非定义了之前的这些抽象函数。


  


                  





  接口不提供多重继承,与此相比,一个CLI类也只能有一个基类,然而,接口却提供某种与多重类继承相似的功能,但概念与之完全不同,例如,一个类不能从接口中继承函数定义;接口继承体系是独立于类继承体系的--实现同一接口的类也许会、但也许不会通过类继承体系相互关联。



  例4演示了一个类:Queue,其与List无关联(但除了这个外,两者都是从Object继承而来的),两者都实现了同一接口。



  例4:












public ref class Queue : ICollection

{

 public:

  void Put(Object^ o)

  {

   // ...

  }

  Object^ Get()

  {

   // ...

  }

  // ...

};

  现在,可用它来编写处理参数为List或Queue的函数了,如例5。



  例5:












ref class Item { /* ... */ };

void ProcessCollection(ICollection^ c);

int main()

{

 List^ myList = gcnew List;

 Queue^ myQueue = gcnew Queue;

 ProcessCollection(myList);

 ProcessCollection(myQueue);

}

void ProcessCollection(ICollection^ c)

{

 Item^ x = gcnew Item();

 /*1*/ c->Put(x);

 /*2*/ x = static_cast<Item^>(c->Get());

}

  在标号1与2中,为访问底层的List或Queue,使用了一个指向接口的句柄c,由此,你可传递给ProcessCollection一个指向任意对象的句柄,只要它的类实现了这个接口,或者它是从实现了这个接口的类继承而来的。



  例6演示了一个包含只读属性X、只写属性Y、读写属性Z的接口,对读写属性来说,get与set声明的顺序并不重要。



  例6:












public interface class IProperties

{

 property int X { int get(); }

 property String^ Y { void set(String^ value); }

 property Object^ Z { Object^ get(); void set(Object^ value); }

};

  一个接口的成员,可以为静态数据成员、实例或静态函数、静态构造函数、实例或静态属性、实例或静态事件、操作符函数、或任意的嵌套类型。



  一般来说,我们会用for each语句来枚举集合中的所有元素,要对集合中的每个元素逐个进行操作,可使用如下语法:



   for each (表达式形式的类型标识符)

    嵌入语句



  表达式类型必须为一个"集合类型",如果要成为一个集合类型,这个类型必须实现接口System::Collections::IEnumerable,如例7中所定义。



  例7:












public interface class IEnumerable

{

 IEnumerator^ GetEnumerator();

};

  正如大家所见,GetEnumerator返回一个指向IEnumerator的句柄,如例8中所定义。



  例8:












public interface class IEnumerator

{

 bool MoveNext();

 void Reset();

 property Object^ Current { Object^ get(); }

};

  System::Array为一个集合类型,因为所有的CLI数组类型都继承自System::Array,所以,任何数组类型表达式都可以作为for each语句中的表达式。在例9的标号1中,for each用于遍历一个int数组,标号2中的处理过程也一样,但直接使用了枚举器。



  例9:












using namespace System;

using namespace System::Collections;

int main()

{

 array<int>^ ary = gcnew array<int>{10, 20, 30, 40};

 /*1*/ for each (int i in ary)

 {

  Console::Write([/img] }

 Console::WriteLine();

 /*2*/ IEnumerator^ ie = ary->GetEnumerator();

 while (ie->MoveNext())

 {

  Console::Write([/img] }

 Console::WriteLine();

}


  


                  





  泛型



  就像函数能用一个或多个类型表示符来定义一样,类型也可以这样来定义。假如有这样一种情况,某种类型建模了一个"数组",其可使用下标来访问每个元素,这样的类型往往被称为"向量",实现一个向量之后,可以保存一组int、一组double、或一组用户自定义类型的元素。然而,正是因为每种类型实现的代码对类型中的元素来说,都是唯一的,因此,可使用泛型机制来定义一个向量类型,并创建特定类型的实例。例10就是这样的一个例子。



  例10:












generic <typename T>

public ref class Vector

{

 int length;

 /*1*/ array<T>^ vector;

 public:

  property int Length

  {

   int get() { return length; }

   private:

    void set(int value) { length = value; }

  }

  /*2*/ property T default[int]

  {

   T get(int index) { return vector[index]; }

   void set(int index, T value) { vector[index] = value; }

  }

  Vector(int vectorLength, T initValue)

  {

   Length = vectorLength;

   vector = gcnew array<T>(Length);

   for (int i = 0; i < Length;   i)

   {

    /*3*/ this = initValue;

   }

   /*4*/ //for each (T element in vector)

   //{

    // element = initValue;

   //}

  }

  Vector(int vectorLength)

  {

   Length = vectorLength;

   vector = gcnew array<T>(Length);

   for (int i = 0; i < Length;   i)

   {

    /*5*/ this = T();

   }

  }

  Vector()

  {

   Length = 0;

   /*6*/ vector = gcnew array<T>(0);

  }

  ~Vector() //多余的

  {

   /*7*/ vector = nullptr;

  }

  virtual String^ ToString() override

  {

   String^ s = "[";

   int i;

   for (i = 0; i < Length - 1;   i)

   {

    /*8*/ s = String::Concat(s, this, ":");

   }

   /*9*/ s = String::Concat(s, this, "]");

   return s;

  }

  virtual bool Equals(Object^ obj) override

  {

   if (obj == nullptr)

   {

    return false;

   }

   if (this == obj) //在测试自身吗?

   {

    return true;

   }

   /*10*/ if (GetType() == obj->GetType())

   {

    Vector<T>^ v = static_cast<Vector^>(obj);

    if (Length != v->Length) //是否向量有不同的长度?

    {

     return false;

    }

    for (int i = 0; i < Length;   i)

    {

     /*11*/ //if (this != v)

     if (this->Equals(v) == false)

     {

      return false;

     }

    }

    return true;

   }

   return false;

  }

  /*12*/ virtual int GetHashCode() override

  {

   return 0;

  }

};

  如同泛型函数一样,泛型的定义一般由generic <typename t1, ..., typename tn>打头,意思是通过tn引入了类型参数t1,这些参数的作用范围,可从类型定义的结束直到它们所依附的对象。在本例中,只有一个类型参数T。



  在标号1中可见,一个Vector是作为一个类型为T的元素数组存储的。



  在标号2中,定义了一个默认的索引属性,以便可用一个int下标值来访问一个Vector。当然了,在运行时,我们可以存取类型为T的元素,而不用管它实际上是什么类型。



  与其直接访问私有成员,倒不如通过公有属性来进行访问,比如说,用Length来取代length;使用下标来访问当前的Vector(标号3)时,我们使用了this。在此,很有可能会想到使用for each循环来取代一般的老式for循环,如标号4,但是,在此使用for循环是行不通的。在for each循环中,通过命名在循环结构中的局部变量,可为每个元素都产生一个副本,也就是说,变量已经不是原来的元素了,因此,修改它的值不会改变元素的值。



  在标号5中,需要把每个元素设为默认值,幸运的是,标准C  要求每种类型都有一个默认的构造函数--甚至对不是类的类型也是这样,一般表示为类型名后紧跟空的圆括号。




  


                  





  程序在构造函数没有参数的情况下(标号6),分配了一个零元素数组。(注意,指向零元素数组的句柄,不等同于包含nullptr的句柄。)这样做的目的是为了让成员函数可以正常工作,甚至对空Vector也是如此。



  为了完整起见,还定义了一个析构函数,以把句柄设为nullptr,目的是为了显式地告之垃圾回收器,我们对这个对象及与之关联内存的工作已经完成。



  使用在标号8及9中的this还是真有点让人糊涂,那它真正的目的是什么呢?不像等价的模板类,泛型类Vector是在类型T未知的情况下编译为一个程序集,如果编译器不知道T的类型,它也不知道this的类型,因此,它该怎样生成代码,才能把这个表达式转换为Concat所期望的类型呢?其实它不必知道,Concat的其中一个重载版本会要求它的第二个参数为Object^类型,且因为this有类型T,无论类型T是否处在运行时,它都是从System::Object继承来的,因此,它与参数类型相兼容。更进一步来说,因为System::Object有一个名为ToString的虚函数,对此虚函数的调用将产生一个String^,假定类型T已覆盖了这个函数,那么就可返回正确的字符串了。



  函数Equals非常简单明了,但有一件事需要指出,请看标号11,从直觉上来说,我们一般会在此写成!=操作符,但是,这却通不过编译。请记住,类Vector是在对类型T一无所知的情况下编译的,除了它是继承自System::Object,同样地,唯一它允许对T调用的成员函数是那些由System::Object提供的函数,而这些类型未定义!=操作符,幸运的是,它提供了Equals函数,因此可使用它来达到同样的目的;接着,类型T覆盖了这个函数以执行对两个T的相等性判别,例11是主程序。



  例11:












int main()

{

 /*1*/ Vector<int>^ iv1 = gcnew Vector<int>(4);

 /*2*/ Console::WriteLine("iv1: {0}", iv1);

 /*3*/ Vector<int>^ iv2 = gcnew Vector<int>(7, 2);

 Console::WriteLine("iv2: {0}", iv2);

 iv2[1] = 55;

 iv2[3] -= 17;

 iv2[5] *= 3;

 Console::WriteLine("iv2: {0}", iv2);

 /*4*/ Vector<String^>^ sv1 = gcnew Vector<String^>(3);

 Console::WriteLine("sv1: {0}", sv1);

 /*5*/ Vector<String^>^ sv2 = gcnew Vector<String^>(5, "X");

 Console::WriteLine("sv2: {0}", sv2);

 sv2[1] = "AB";

 sv2[3] = String::Concat(sv2[4], "ZZ");

 Console::WriteLine("sv2: {0}", sv2);

 /*6*/ Vector<DateTime>^ dv1 = gcnew Vector<DateTime>(2);

 Console::WriteLine("dv1: {0}", dv1);

 /*7*/ Vector<DateTime>^ dv2 = gcnew Vector<DateTime>(3, DateTime::Now);

 Console::WriteLine("dv2: {0}", dv2);

 for (int i = 0; i < dv2->Length;   i)

 {

  Thread::Sleep(1100);

  dv2 = DateTime::Now;

 }

 Console::WriteLine("dv2: {0}", dv2);

 /*8*/ Vector<Vector<int>^>^ viv = gcnew Vector<Vector<int>^>(3);

 Console::WriteLine("viv: {0}", viv);

 viv[0] = gcnew Vector<int>(2, 1);

 viv[1] = gcnew Vector<int>(4, 2);

 viv[2] = gcnew Vector<int>(3, 5);

 Console::WriteLine("viv: {0}", viv);

 /*9*/ Vector<int>^ iv3 = gcnew Vector<int>(4,3);

 Vector<int>^ iv4 = gcnew Vector<int>(4,3);

 Vector<int>^ iv5 = gcnew Vector<int>(4,2);

 Vector<int>^ iv6 = gcnew Vector<int>(5,6);

 Console::WriteLine("iv3->Equals(iv4) is {0}", iv3->Equals(iv4));

 Console::WriteLine("iv3->Equals(iv5) is {0}", iv3->Equals(iv5));

 Console::WriteLine("iv3->Equals(iv6) is {0}", iv3->Equals(iv6));

}

  为创建Vector的一个特定类型,可在尖括号中作为类型参数指定一个类型,如标号1、3所示。记住,int是值类System::Int32的同义词。(如果泛型有多个类型参数,需要以逗号把它们分隔开。)



  在标号4、5中,定义了一个String^类型的Vector--一个引用类型;在标号6、7中,定义了一个DateTime类型的Vector--一个值类型;在标号8中,定义了一个Vector,而其的元素则为int类型的Vector;最后,用不同的length及Vector int值测试了Equals函数,插1是程序的输出:



  插1:例11的输出












iv1: [0:0:0:0]

iv2: [2:2:2:2:2:2:2]

iv2: [2:55:2:-15:2:6:2]

sv1: [::]

sv2: [X:X:X:X:X]

sv2: [X:AB:X:XZZ:X]

dv1: [1/1/0001 12:00:00 AM:1/1/0001 12:00:00 AM]

dv2: [4/9/2005 3:30:40 PM:4/9/2005 3:30:40 PM:4/9/2005 3:30:40 PM]

dv2: [4/9/2005 3:30:41 PM:4/9/2005 3:30:42 PM:4/9/2005 3:30:43 PM]

viv: [::]

viv: [[1:1]:[2:2:2:2]:[5:5:5]]

iv3->Equals(iv4) is True

iv3->Equals(iv5) is False

iv3->Equals(iv6) is False

  实际上,不但可以定义泛型类,还可以定义泛型接口与代理。



  在前面,也指出了调用泛型成员函数时的一些限制,默认情况下,一个类型参数被限定为从System::Object继承来的任意类型,也就是任意CLI类型。对泛型或函数中的类型参数,如果使用where子句,还可以施加一个或多个限定条件,见例12。



  例12:












generic <typename T>

where T : ValueType

public ref class Vector

{ ... };

value class C {};



/*1*/ Vector<int>^ iv;

/*2*/ Vector<String^>^ sv; //错误

/*3*/ Vector<DateTime>^ dv;

/*4*/ Vector<Vector<int>^>^ viv; //错误

/*5*/ Vector<C>^ cv;

  在本例中,编译器将允许任意有着System::ValueType类型的类型参数,或从它继承来的类型被传递给泛型。假设此时又想定义某种特定的类型,如标号2和3所示,就会出错,因为String与Vector<int>不是值类型。同样地,如果换成例13中的限定条件,那么标号5就会被拒绝,因为值类型C没有实现接口System::IComparable,而System::Int32和System::DateTime却实现了。



  例13:












generic <typename T>

where T : ValueType

public ref class Vector, IComparable

{ ... };

  一旦编译器知道T可以被限定为比System::Object更明确的类型,就会允许调用这些类型中的成员函数,而这些类型则可包含一个基类型或任意顺序、任意数目的接口类型。

TOP

发新话题