zoukankan      html  css  js  c++  java
  • C#继承机制

    很好的资料,转自博客园:http://www.cnblogs.com/huangkit/archive/2006/09/01/492023.html


    一. 继承基础知识 

      为了提高软件模块的可复用性和可扩充性,以便提高软件的开发效率,我们总是希望能够利用前人或自己以前的开发成果,同时又希望在自己的开发过程中能够有足够的灵活性,不拘泥于复用的模块。C#这种完全面向对象的程序设计语言提供了两个重要的特性--继承性inheritance 和多态性polymorphism。
      继承是面向对象程序设计的主要特征之一,它可以让您重用代码,可以节省程序设计的时间。继承就是在类之间建立一种相交关系,使得新定义的派生类的实例可以继承已有的基类的特征和能力,而且可以加入新的特性或者是修改已有的特性建立起类的新层次。

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


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

      为了用软件语言对现实世界中的层次结构进行模型化,面向对象的程序设计技术引入了继承的概念。一个类从另一个类派生出来时,派生类从基类那里继承特性。派生类也可以作为其它类的基类。从一个基类派生出来的多层类形成了类的层次结构。

      注意: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#中的继承符合下列规则:

      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 修饰符修改此嵌套类型。

  • 相关阅读:
    PAT (Advanced Level) Practice 1071 Speech Patterns (25分)
    PAT (Advanced Level) Practice 1070 Mooncake (25分)
    PAT (Advanced Level) Practice 1069 The Black Hole of Numbers (20分)
    PAT (Advanced Level) Practice 1074 Reversing Linked List (25分)
    PAT (Advanced Level) Practice 1073 Scientific Notation (20分)
    第一次冲刺个人总结01
    构建之法阅读笔记01
    人月神话阅读笔记01
    四则运算2
    学习进度条(软件工程概论1-8周)
  • 原文地址:https://www.cnblogs.com/kiwi/p/2425062.html
Copyright © 2011-2022 走看看