C#继承与多态

一、继承的基础知识

    为了提高软件模块的可复用性和可扩充性,以便提高软件的开发效率,我们总是希望能够利用前人或自己以前的开发成果,同时又希望在

自己的开发过程中能够有足够的灵活性,不拘泥于复用的模块。C#这种完全面向对象的程序设计语言提供了两个重要的特性--

继承性inheritance 和多态性polymorphism。

  继承是面向对象程序设计的主要特征之一,它可以让您重用代码,可以节省程序设计的时间。继承就是在类之间建立一种相交关系,使得

新定义的派生类的实例可以继承已有的基类的特征和能力,而且可以加入新的特性或者是修改已有的特性建立起类的新层次。

  现实世界中的许多实体之间不是相互孤立的,它们往往具有共同的特征也存在内在的差别。人们可以采用层次结构来描述这些实体之间的

相似之处和不同之处。

                                             图1 类图

  上图反映了交通工具类的派生关系。最高层的实体往往具有最一般最普遍的特征,越下层的事物越具体,并且下层包含了上层的特征。

它们之间的关系是基类与派生类之间的关系。

  为了用软件语言对现实世界中的层次结构进行模型化,面向对象的程序设计技术引入了继承的概念。一个类从另一个类派生出来时,

派生类从基类那里继承特性。派生类也可以作为其它类的基类。从一个基类派生出来的多层类形成了类的层次结构。

  注意:C#中,派生类只能从一个类中继承。这是因为,在C++中,人们在大多数情况下不需要一个从多个类中派生的类。从多个基类中

派生一个类这往往会带来许多问题,从而抵消了这种灵活性带来的优势。

    C#中,派生类从它的直接基类中继承成员:方法、域、属性、事件、索引指示器。除了构造函数和析构函数,派生类隐式地继承了直接

基类的所有成员。看下面示例:

 

using System ;

class Vehicle //定义交通工具(汽车)类

{

     protected int wheels ; //公有成员:轮子个数

     protected float weight ; //保护成员:重量

     public Vehicle( ){;}

     public Vehicle(int w,float g)

     {

        wheels = w ;

        weight = g ;

     }

     public void Speak( )

     {

        Console.WriteLine( "交通工具的轮子个数是可以变化的! " ) ;

     }

} ;

class Car:Vehicle //定义轿车类:从汽车类中继承

{

    int passengers ; //私有成员:乘客数

    public Car(int w , float g , int p) : base(w, g)

    {

        wheels = w ;

        weight = g ;

        passengers=p ;

    }

  Vehicle 作为基类,体现了"汽车"这个实体具有的公共性质:汽车都有轮子和重量。Car 类继承了Vehicle 的这些性质,并且添加了自身

的特性:可以搭载乘客。

二、C#中类继承的实现机制

C#中的继承符合下列规则:

  1、继承是可传递的。如果C从B中派生,B又从A中派生,那么C不仅继承了B中声明的成员,同样也继承了A中的成员。Object 类作为所有类

       的基类。

  2、派生类应当是对基类的扩展。派生类可以添加新的成员,但不能除去已经继承的成员的定义。

  3、构造函数和析构函数不能被继承。除此以外的其它成员,不论对它们定义了怎样的访问方式,都能被继承。基类中成员的访问方式

       只能决定派生类能否访问它们。

  4、派生类如果定义了与继承而来的成员同名的新成员,就可以覆盖已继承的成员。但这并不因为这派生类删除了这些成员,只是不能再

       访问这些成员。

  5、类可以定义虚方法、虚属性以及虚索引指示器,它的派生类能够重载这些成员,从而实现类可以展示出多态性。

  6、派生类只能从一个类中继承,可以通过接吕实现多重继承。

  下面的代码是一个子类继承父类的例子:

  using System ;

   public class ParentClass

   {

      public ParentClass( )

      {

          Console.WriteLine("父类构造函数。"); }

          public void print( )

          {

             Console.WriteLine("I'm a Parent Class。") ;

          }

       }

   }

   public class ChildClass : ParentClass

   {

       public ChildClass( )

       {

            Console.WriteLine("子类构造函数。") ;

       }

       public static void Main( )

       {

           ChildClass child = new ChildClass( ) ;

           child.print( ) ;

       }

    } 

  程序运行输出:

  父类构造函数。子类构造函数。I'm a Parent Class。

  上面的一个类名为ParentClass, main函数中用到的类名为ChildClass。要做的是创建一个使用父类ParentClass现有代码的子类

ChildClass。

  1.首先必须说明ParentClass是ChildClass的基类。

  这是通过在ChildClass类中作出如下说明来完成的:"public class ChildClass : ParentClass"。在派生类标识符后面,用分号":" 来

表明后面的标识符是基类。C#仅支持单一继承。因此,你只能指定一个基类。

  2.ChildClass的功能几乎等同于ParentClass。

  因此,也可以说ChildClass "就是" ParentClass。在ChildClass 的Main( )方法中,调用print( ) 方法的结果,就验证这一点。该子类

并没有自己的print( )方法,它使用了ParentClass中的 print( )方法。在输出结果中的第三行可以得到验证。

  3.基类在派生类初始化之前自动进行初始化。ParentClass 类的构造函数在ChildClass的构造函数之前执行。

 

 

 三、访问与隐藏基类成员

  (1) 访问基类成员

  通过base 关键字访问基类的成员:

   调用基类上已被其他方法重写的方法。

   指定创建派生类实例时应调用的基类构造函数。

   基类访问只能在构造函数、实例方法或实例属性访问器中进行。

   从静态方法中使用 base 关键字是错误的。

  示例:下面程序中基类 Person 和派生类 Employee 都有一个名为 Getinfo 的方法。通过使用 base 关键字,可以从派生类中调用基类上

的 Getinfo 方法。

 

using System ;

public class Person

{

   protected string ssn = "111-222-333-444" ;

   protected string name = "张三" ;

   public virtual void GetInfo()

   {

       Console.WriteLine("姓名: {0}", name) ;

       Console.WriteLine("编号: {0}", ssn) ;

   }

}

class Employee: Person

{

   public string id = "ABC567EFG23267" ;

   public override void GetInfo()

   {

     // 调用基类的GetInfo方法:

     base.GetInfo();

     Console.WriteLine("成员ID: {0}", id) ;

   }

}

class TestClass

{

   public static void Main()

   {

      Employee E = new Employee() ;

      E.GetInfo() ;

   }

  程序运行输出:

   姓名: 张三

   编号: 111-222-333-444

   成员ID: ABC567EFG23267

   示例:派生类同基类进行通信。

 

using System ;

public class Parent

{

   string parentString;

   public Parent( )

   {

       Console.WriteLine("Parent Constructor.") ;

   }

   public Parent(string myString)

   {

       parentString = myString;

       Console.WriteLine(parentString) ;

   }

   public void print( )

   {

       Console.WriteLine("I'm a Parent Class.") ;

   }

}

public class Child : Parent

{

   public Child( ) : base("From Derived")

   {

      Console.WriteLine("Child Constructor.") ;

   }

   public void print( )

   {

      base.print( ) ;

      Console.WriteLine("I'm a Child Class.") ;

   }

   public static void Main( )

   {

      Child child = new Child( ) ;

      child.print( ) ;

      ((Parent)child).print( ) ;

   }

  程序运行输出:

From Derived

Child Constructor.

I'm a Parent Class.

I'm a Child Class.

I'm a Parent Class.

  说明:

  1.派生类在初始化的过程中可以同基类进行通信。

   上面代码演示了在子类的构造函数定义中是如何实现同基类通信的。分号":"和关键字base用来调用带有相应参数的基类的构造函数。

     输出结果中,第一行表明:基类的构造函数最先被调用,其实在参数是字符串"From Derived"。

  2.有时,对于基类已有定义的方法,打算重新定义自己的实现。

    Child类可以自己重新定义print( )方法的实现。Child的print( )方法覆盖了Parent中的 print 方法。结果是:除非经过特别指明,

      Parent类中的print方法不会被调用。

  3.在Child类的 print( ) 方法中,我们特别指明:调用的是Parent类中的 print( ) 方法。

   方法名前面为"base",一旦使用"base"关键字之后,你就可以访问基类的具有公有或者保护权限的成员。 Child类中的print( )方法的

     执行结果出现上面的第三行和第四行。

  4.访问基类成员的另外一种方法是:通过显式类型转换。

    在Child类的Main( )方法中的最后一条语句就是这么做的。记住:派生类是其基类的特例。这个事实告诉我们:可以在派生类中进行数据

      类型的转换,使其成为基类的一个实例。上面代码的最后一行实际上执行了Parent类中的 print( )方法。

 

  

(2) 隐藏基类成员

  想想看,如果所有的类都可以被继承,继承的滥用会带来什么后果?类的层次结构体系将变得十分庞,大类之间的关系杂乱无章,对类的

   理解和使用都会变得十分困难。有时候,我们并不希望自己编写的类被继承。另一些时候,有的类已经没有再被继承的必要。C#提出了一个

   密封类(sealed class)的概念,帮助开发人员来解决这一问题。

  密封类在声明中使用sealed 修饰符,这样就可以防止该类被其它类继承。如果试图将一个密封类作为其它类的基类,C#将提示出错。

    理所当然,密封类不能同时又是抽象类,因为抽象总是希望被继承的。

  在哪些场合下使用密封类呢?密封类可以阻止其它程序员在无意中继承该类。而且密封类可以起到运行时优化的效果。实际上,密封类中

    不可能有派生类。如果密封类实例中存在虚成员函数,该成员函数可以转化为非虚的,函数修饰符virtual 不再生效。

  让我们看下面的例子:

  bstract class A

   {

       public abstract void F( ) ;

   }

   sealed class B: A

   {

       public override void F( )

       { // F 的具体实现代码 }

   }

  如果我们尝试写下面的代码

    class C: B{ }

  C#会指出这个错误,告诉你B 是一个密封类,不能试图从B 中派生任何类。

  (3) 密封方法

  我们已经知道,使用密封类可以防止对类的继承。C#还提出了密封方法(sealedmethod) 的概念,以防止在方法所在类的派生类中对该

    方法的重载。对方法可以使用sealed 修饰符,这时我们称该方法是一个密封方法。

  不是类的每个成员方法都可以作为密封方法密封方法,必须对基类的虚方法进行重载,提供具体的实现方法。所以,在方法的声明中,

    sealed 修饰符总是和override 修饰符同时使用。请看下面的例子代码:

 

using System ;

class A

{

   public virtual void F( )

   {

       Console.WriteLine("A.F") ;

   }

   public virtual void G( )

   {

       Console.WriteLine("A.G") ;

   }

}

class B: A

{

    sealed override public void F( )

    {

        Console.WriteLine("B.F") ;

    }

    override public void G( )

    {

        Console.WriteLine("B.G") ;

    }

}

class C: B

{

   override public void G( )

   {

       Console.WriteLine("C.G") ;

   }

  类B 对基类A 中的两个虚方法均进行了重载,其中F 方法使用了sealed 修饰符,成为一个密封方法。G 方法不是密封方法,所以在B 的

    派生类C 中,可以重载方法G,但不能重载方法F。

  (4) 使用 new 修饰符隐藏基类成员

  使用 new 修饰符可以显式隐藏从基类继承的成员。若要隐藏继承的成员,请使用相同名称在派生类中声明该成员,并用 new 修饰符修饰

    它。

  请看下面的类:

public class MyBase

{

     public int x ;

     public void MyVoke() ;

}

  在派生类中用 MyVoke名称声明成员会隐藏基类中的 MyVoke方法,即:

 

public class MyDerived : MyBase

{

   new public void MyVoke ();

}

  但是,因为字段 x 不是通过类似名隐藏的,所以不会影响该字段。

  通过继承隐藏名称采用下列形式之一:

   a、引入类或结构中的常数、指定、属性或类型隐藏具有相同名称的所有基类成员。

   b、引入类或结构中的方法隐藏基类中具有相同名称的属性、字段和类型。同时也隐藏具有相同签名的所有基类方法。

   c、引入类或结构中的索引器将隐藏具有相同名称的所有基类索引器。

  注意:在同一成员上同时使用 new 和 override 是错误的。同时使用 new 和 virtual 可保证一个新的专用化点。在不隐藏继承成员的

          声明中使用 new 修饰符将发出警告。

  示例1:在该例中,基类 MyBaseC 和派生类 MyDerivedC 使用相同的字段名 x,从而隐藏了继承字段的值。该例说明了 new 修饰符的使用。

          同时也说明了如何使用完全限定名访问基类的隐藏成员。

 

using System ;

public class MyBase

{

    public static int x = 55 ;

    public static int y = 22 ;

}

public class MyDerived : MyBase

{

    new public static int x = 100; // 利用new 隐藏基类的x

    public static void Main() 

    {

        // 打印x:

        Console.WriteLine(x);

        //访问隐藏基类的 x:

        Console.WriteLine(MyBase.x);

        //打印不隐藏的y:

        Console.WriteLine(y);

    }

}

  输出: 100 55 22

  如果移除 new 修饰符,程序将继续编译和运行,但您会收到以下警告:

 

The keyword new is required on 'MyDerivedC.x' because it hides inherited member 'MyBaseC.x'.

  如果嵌套类型正在隐藏另一种类型,如下例所示,也可以使用 new 修饰符修改此嵌套类型。

 

 

四、多级继承

  一些面向对象语言允许一个类从多个基类中继承,而另一些面向对象语言只允许从一个类继承,但可以随意从几个接口或纯抽象类中继承。

  只有C++支持多级继承,许多程序员对此褒贬不一。多级继承常会引起继承来的类之间的混乱,继承而来的方法往往没有唯一性,所以C#中

    类的继承只可以是一个,即子类只能派生于一个父类,而有时你必须继承多个类的特性,为了实现多重继承必须使用接口技术,下面是对接

    口的多重继承进行介绍:

using System ;

//定义一个描述点的接口

interface IPoint

{

   int x

   {

       get ;

       set ;

   }

   int y

   {

       get ;

       set ;

   }

}

interface IPoint2

{

   int y

   {

       get ;

       set ;

   }

}

//在point中继承了两个父类接口,并分别使用了两个父类接口的方法

class Point:IPoint,IPoint2

{

   //定义两个类内部访问的私有成员变量

   private int pX ;

   private int pY ;

   public Point(int x,int y)

   {

       pX=x ;

       pY=y ;

   }

   //定义的属性,IPoint接口方法实现

   public int x

   {

      get

      {

          return pX ;

      }

      set

      {

          pX =value ;

      }

   }

 

   //IPoint1接口方法实现

   public int y

   {

       get

       {

           return pY ;

       }

       set

       {

           pY =value ;

       }

   }

}

class Test

{

    private static void OutPut( IPoint p )

    {

        Console.WriteLine("x={0},y={1}",p.x,p.y) ;

    }

    public static void Main( )

    {

        Point p =new Point(15,30) ;

        Console.Write("The New Point is:") ;

        OutPut( p ) ;

        string myName =Console.ReadLine( ) ;

        Console.Write("my name is {0}", myName) ;

    }

 

五、多态

   

1、什么是多态

   面向对象程序设计中的另外一个重要概念是多态性。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。可以把一组对象

   放到一个数组中,然后调用它们的方法,在这种场合下,多态性作用就体现出来了,这些对象不必是相同类型的对象。当然,如果它们都

   继承自某个类,你可以把这些派生类,都放到一个数组中。如果这些对象都有同名方法,就可以调用每个对象的同名方法。

   同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果,这就是多态性。多态性通过派生类重载基类中的虚函数型方法来

   实现。

   在面向对象的系统中,多态性是一个非常重要的概念,它允许客户对一个对象进行操作,由对象来完成一系列的动作,具体实现哪个动作、

   如何实现由系统负责解释。

   “多态性”一词最早用于生物学,指同一种族的生物体具有相同的特性。在C#中,多态性的定义是:同一操作作用于不同的类的实例,不同

   的类将进行不同的解释,最后产生不同的执行结果。C#支持两种类型的多态性:

   ● 编译时的多态性

      编译时的多态性是通过重载来实现的。对于非虚的成员来说,系统在编译时,根据传递的参数、返回的类型等信息决定实现何种操作。

   ● 运行时的多态性

      运行时的多态性就是指直到系统运行时,才根据实际情况决定实现何种操作。C#中,运行时的多态性通过虚成员实现。

      编译时的多态性为我们提供了运行速度快的特点,而运行时的多态性则带来了高度灵活和抽象的特点。

2、实现多态

   多态性是类为方法(这些方法以相同的名称调用)提供不同实现方式的能力。多态性允许对类的某个方法进行调用而无需考虑该方法所提供

   的特定实现。例如,可能有名为 Road 的类,它调用另一个类的 Drive 方法。这另一个类 Car 可能是 SportsCar 或 SmallCar,但二者都

   提供 Drive 方法。虽然 Drive 方法的实现因类的不同而异,但 Road 类仍可以调用它,并且它提供的结果可由 Road 类使用和解释。

   可以用不同的方式实现组件中的多态性:

   ● 接口多态性。

   ● 继承多态性。

   ● 通过抽象类实现的多态性。

   接口多态性

   多个类可实现相同的“接口”,而单个类可以实现一个或多个接口。接口本质上是类需要如何响应的定义。接口描述类需要实现的方法、

   属性和事件,以及每个成员需要接收和返回的参数类型,但将这些成员的特定实现留给实现类去完成。

   组件编程中的一项强大技术是能够在一个对象上实现多个接口。每个接口由一小部分紧密联系的方法、属性和事件组成。通过实现接口,

   组件可以为要求该接口的任何其他组件提供功能,而无需考虑其中所包含的特定功能。这使后续组件的版本得以包含不同的功能而不会干扰

   核心功能。其他开发人员最常使用的组件功能自然是组件类本身的成员。然而,包含大量成员的组件使用起来可能比较困难。可以考虑将

   组件的某些功能分解出来,作为私下实现的单独接口。

   根据接口来定义功能的另一个好处是,可以通过定义和实现附加接口增量地将功能添加到组件中。优点包括:

  1.简化了设计过程,因为组件开始时可以很小,具有最小功能;之后,组件继续提供最小功能,同时不断插入其他的功能,并通过实际使用

    那些功能来确定合适的功能。

  2.简化了兼容性的维护,因为组件的新版本可以在添加新接口的同时继续提供现有接口。客户端应用程序的后续版本可以利用这些接口的

    优点。

  通过继承实现的多态性

  多个类可以从单个基类“继承”。通过继承,类在基类所在的同一实现中接收基类的所有方法、属性和事件。这样,便可根据需要来实现

  附加成员,而且可以重写基成员以提供不同的实现。请注意,继承类也可以实现接口,这两种技术不是互斥的。

  C# 通过继承提供多态性。对于小规模开发任务而言,这是一个功能强大的机制,但对于大规模系统,通常证明会存在问题。过分强调继承

  驱动的多态性一般会导致资源大规模地从编码转移到设计,这对于缩短总的开发时间没有任何帮助。

  何时使用继承驱动的多态性呢?使用继承首先是为了向现有基类添加功能。若从经过完全调试的基类框架开始,则程序员的工作效率将大大

  提高,方法可以增量地添加到基类而不中断版本。当应用程序设计包含多个相关类,而对于某些通用函数,这些相关类必须共享同样的实现

  时,您也可能希望使用继承。重叠功能可以在基类中实现,应用程序中使用的类可以从该基类中派生。抽象类合并继承和实现的功能,这在

  需要二者之一的元素时可能很有用。

  通过抽象类实现的多态性

  抽象类同时提供继承和接口的元素。抽象类本身不能实例化,它必须被继承。该类的部分或全部成员可能未实现,该实现由继承类提供。已

  实现的成员仍可被重写,并且继承类仍可以实现附加接口或其他功能。

  抽象类提供继承和接口实现的功能。抽象类不能示例化,必须在继承类中实现。它可以包含已实现的方法和属性,但也可以包含未实现的

  过程,这些未实现过程必须在继承类中实现。这使您得以在类的某些方法中提供不变级功能,同时为其他过程保持灵活性选项打开。抽象类

  的另一个好处是:当要求组件的新版本时,可根据需要将附加方法添加到基类,但接口必须保持不变。

  何时使用抽象类呢?当需要一组相关组件来包含一组具有相同功能的方法,但同时要求在其他方法实现中具有灵活性时,可以使用抽象类。

  当预料可能出现版本问题时,抽象类也具有价值,因为基类比较灵活并易于被修改。

           

示例:实现多态性的程序

using System ;

public class DrawingBase

{

   public virtual void Draw( )

   {

     Console.WriteLine("I'm just a generic drawing object.") ;

   }

}

public class Line : DrawingBase

{

   public override void Draw( )

   {

       Console.WriteLine("I'm a Line.") ;

   }

}

public class Circle : DrawingBase

{

   public override void Draw( )

   {

      Console.WriteLine("I'm a Circle.") ;

   }

}

public class Square : DrawingBase

{

   public override void Draw( )

   {

     Console.WriteLine("I'm a Square.") ;

   }

}

public class DrawDemo

{

   public static int Main(string[] args)

   {

       DrawingBase [] dObj = new DrawingBase [4];

       dObj[0] = new Line( ) ;

       dObj[1] = new Circle( ) ;

       dObj[2] = new Square( ) ;

       dObj[3] = new DrawingBase( ) ;

       foreach (DrawingBase drawObj in dObj)

       drawObj.Draw( ) ;

       return 0;

   }

}

说明:上面程序演示了多态性的实现。在DrawDemo类中的Main( )方法中,创建了一个数组,数组元素是DrawingBase类的对象。该数组名为

dObj,是由四个DrawingBase类型的对象组成。接下来,初始化dObj数组,由于Line,Circle和Square类都是DrawingBase类的派生类,所以

这些类可以作为dObj数组元素的类型。如果C#没有这种功能,你得为每个类创建一个数组。继承的性质可以让派生对象当作基类成员一样用,

这样就节省了编程工作量。 一旦数组初始化之后,接着是执行foreach循环,寻找数组中的每个元素。在每次循环中,dObj 数组的每个元素

(对象)调用其Draw( )方法。多态性体现在:在运行时,各自调用每个对象的Draw( )方法。尽管dObj 数组中的引用对象类型是DrawingBase,

这并不影响派生类重载DrawingBase类的虚方法Draw( )。 在dObj 数组中,通过指向DrawingBase基类的指针来调用派生类中的重载的Draw( )

方法。

输出结果是:

I'm a Line.

I'm a Circle.

I'm a Square.

I'm just a generic drawing object.

在DrawDemo 程序中,调用了每个派生类的重载的Draw( )方法。 最后一行中,执行的是DrawingBase类的虚方法Draw( )。这是因为运行到

最后,数组的第四个元素是DrawingBase类的对象。

3、虚方法

   当类中的方法声明前加上了virtual 修饰符,我们称之为虚方法,反之为非虚。使用了virtual 修饰符后,不允许再有static, abstract,

   或override 修饰符。

示例1:带有虚方法的类

using System ;

public class DrawingBase

{

    public virtual void Draw( )

    {

        Console.WriteLine("这是一个虚方法!") ;

    }

}

说明:这里定义了DrawingBase类。这是个可以让其他对象继承的基类。该类有一个名为Draw( )的方法。Draw( )方法带有一个virtual修饰符,

该修饰符表明:该基类的派生类可以重载该方法。DrawingBase类的 Draw( )方法完成如下事情:输出语句"这是一个虚方法!"到控制台。

示例:带有重载方法的派生类

using System ;

public class Line : DrawingBase

{

    public override void Draw( )

    { 

        Console.WriteLine("画线.") ;

    }

}

public class Circle : DrawingBase

{

    public override void Draw( )

    {

        Console.WriteLine("画圆.") ;

    }

}

public class Square : DrawingBase

{

    public override void Draw( )

    {

       Console.WriteLine("画正方形.") ;

    }

}

说明:上面程序定义了三个类。这三个类都派生自DrawingBase类。每个类都有一个同名Draw( )方法,这些Draw( )方法中的每一个都有一个重

载修饰符。重载修饰符可让该方法在运行时重载其基类的虚方法,实现这个功能的条件是:通过基类类型的指针变量来引用该类。

对于非虚的方法,无论被其所在类的实例调用,还是被这个类的派生类的实例调用,方法的执行方式不变。而对于虚方法,它的执行方式可以

被派生类改变,这种改变是通过方法的重载来实现的。

下面的例子说明了虚方法与非虚方法的区别。

using System ;

class A

{

   public void F( )

   {

       Console.WriteLine("A.F") ;

   }

   public virtual void G( )

   {

       Console.WriteLine("A.G") ;

   }

}

class B: A

{

   new public void F( )

   {

       Console.WriteLine("B.F") ;

   }

   public override void G( )

   {

       Console.WriteLine("B.G") ;

   }

}

class Test

{

   static void Main( )

   {

      B b = new B( ) ;

      A a = b;

      a.F( ) ;

      b.F( ) ;

      a.G( ) ;

      b.G( ) ;

   }

}

例子中,A 类提供了两个方法:非虚的F 和虚方法G 。类B 则提供了一个新的非虚的方法F, 从而覆盖了继承的F; 类B 同时还重载了继承的

方法G 。那么输出应该是:A.F B.F B.G B.G

注意到本例中,方法a.G( ) 实际调用了B.G,而不是A.G,这是因为编译时值为A,但运行时值为B ,所以B 完成了对方法的实际调用。

在派生类中对虚方法进行重载

先让我们回顾一下普通的方法重载,普通的方法重载指的是:类中两个以上的方法(包括隐藏的继承而来的方法),取的名字相同,只要使用的

参数类型或者参数个数不同,编译器便知道在何种情况下应该调用哪个方法。

而对基类虚方法的重载是函数重载的另一种特殊形式。在派生类中重新定义此虚函数时,要求的是方法名称,返回值类型、参数表中的参数

个数、类型顺序都必须与基类中的虚函数完全一致。在派生类中声明对虚方法的重载,要求在声明中加上override 关键字,而且不能有new,

static 或virtual 修饰符。

看一个用汽车类的例子来说明多态性的实现的程序:

using System ;

class Vehicle//定义汽车类

{

   public int wheels; //公有成员轮子个数

   protected float weight; //保护成员重量

   public Vehicle(int w,float g)

   {

       wheels = w;

       weight = g;

   }

   public virtual void Speak( )

   {

      Console.WriteLine( " the w vehicle is speaking!" ) ;

   }

};

class Car:Vehicle //定义轿车类

{

   int passengers; //私有成员乘客数

    public Car(int w,float g,int p) : base(w,g)

    {

        wheels = w;

        weight = g;

        passengers = p;

    }

    public override void Speak( )

    {

       Console.WriteLine( " The car is speaking:Di-di!" ) ;

    }

}

class Truck:Vehicle //定义卡车类

{

    int passengers; //私有成员乘客数

    float load; //私有成员载重量

    public Truck (int w,float g,int p, float l) : base(w,g)

    {

        wheels = w;

        weight = g;

        passengers = p;

        load = l;

    }

    public override void Speak( )

    {

        Console.WriteLine( " The truck is speaking:Ba-ba!" ) ;

    }

    public static void Main( )

    {

        Vehicle v1 = new Vehicle(0,0 ) ;

        Car c1 = new Car(4,2,5) ;

        Truck t1 = new Truck(6,5,3,10) ;

        v1.Speak( ) ;

        v1 = c1;

        v1.Speak( ) ;

        c1.Speak( ) ;

        v1 = t1;

        v1.Speak( ) ;

        t1.Speak( ) ;

    }

}

分析上面的例子我们看到:

● Vehicle 类中的Speak 方法被声明为虚方法,那么在派生类中就可以重新定义此方法。

● 在派生类Car 和Truck 中分别重载了Speak 方法,派生类中的方法原型和基类中的方法原型必须完全一致。

● 在Test 类中,创建了Vehicle 类的实例v1, 并且先后指向Car 类的实例c1 和Truck 类的实例t1。

运行该程序结果应该是:

The Vehicle is speaking!

The car is speaking:Di-di!

The car is speaking:Di-di!

The truck is speaking:Ba-ba!

The truck is speaking:Ba-ba!

这里,Vehicle 类的实例v1 先后被赋予Car 类的实例c1, 以及Truck 类的实例t1的值。在执行过程中,v1 先后指代不同的类的实例,从而

调用不同的版本。这里v1 的Speak 方法实现了多态性,并且v1.Speak 究竟执行哪个版本,不是在程序编译时确定的,而是在程序的动态运行

时,根据v1 某一时刻的指代类型来确定的,所以还体现了动态的多态性。

六、练习题

1、将下列程序补充完整,使之能正确运行。

 class people

{

       protected string name = "";

       protected int age = 0;

       public people(string _name,int _age)

       {

              this.name = _name;

              this.age = _age;

       }      

       public void say(string text)

       {

              if(text != "") Console.Write(name + " 说 : 我今年 " + age.ToString() + " 岁了! "+text);

       }

}

class teacher : people

{

 

}

 

class run

{

       static void Main()

       {

              teacher anson = new teacher("anson",28);

              anson.say("你好 !");

       }

}

2、实现下图中的Shape层次结构,每个TwoDimensionalShape都应该包含方法Area,用于计算二维图形的面积。每个ThreeDimensionalShape应该包含方法Area和方法Volume,来分别计算三维图形的表面积和体积。使用层次结构中每个实体类的对象的Shape的引用的数组编写一个程序。这个程序应该输出数组中每个对象的String表示。同样,在一个循环中处理数组中的所有图形,判断每个图形是二维的还是三维的。如果一个图形是二维的,显示它的Area,如果一个图形是三维的,显示它的Area和Volume。

TwoDimensionalShape

Shape

ThreeDimensionalShape

Circle

Sphere

Cylinder

Cube

Square

Triangle

    Shape类的层次结构

posted @ 2011-04-06 18:38  似水流年-johnhuo  阅读(2085)  评论(1编辑  收藏  举报