C#學習基礎------繼承

繼承

任何面向對像的程序設計語言都必須提供兩個重要的特性:繼承性(inheritance)多態性(polymorphism).繼承的引入,就是在類之間建立一种相交關系,使得新定議的派生類的實例可以繼承已有的基類的特征和能力,而且可以加入新的特性或者是修改已有的特性,建立起類的層次.同一操作作用於不同的對像,可以有不同的解釋,產生不同的執行結果,這就是多態性.多態性通過派生類重載基類中的虛函數型方法來實現.

注意:
C#中,派生類只能從一個類中繼承.

C#中,派生類從它的直接基類中繼承成員:方法,域,屬性,事件,索引指示器.
除了構造函數和析構函數,派生類隱式地繼承了直接基類的所有成員.

下面是一個關於車輛的例子
using System;
class Vehicle  //定義汽車類
{
  int wheels;  //公有成員,輪子個數.
  protected float weight;  //保掮成員,重量.
  public Vehicle() {;}
  public Vehicle(int w,float g)
  {
    wheels=w;
    weight=g;
  }
  public 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;
  }
}
Vehicle作為基類,體現了"汽車"這個實體有的公共性質;汽車都有輪子和重量,Car類繼承了Vehicle的這些性質,並且添加了自己身的特性:可以搭載乘客.

覆蓋
類的成員聲明中,可以聲明與繼而來的成員同名的成員.這時我們稱派生類的成員覆蓋

(hide)了基類的成員.這种情況下,編譯器不會報告錯誤,但會給出一個警告.對派生類的成

員使用new關鍵字,可以關閉這個警告.
前面的汽車類的例子中,類Car繼承了Vehicle的Speak()方法.我們可以給Car類也聲明一個

Speak()方法,覆蓋Vehicle中的Speak,見下面的代碼.
using System;
class Vehicle  //定義汽車類
{
  int wheels;  //公有成員,輪子個數.
  protected float weight;  //保掮成員,重量.
  public Vehicle() {;}
  public Vehicle(int w,float g)
  {
    wheels=w;
    weight=g;
  }
  public 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;
  }
  new public void Speak()
  {
    Console.WriteLine("Di-Di");
  }
}
注意:
如果在成員聲明中加上了new關鍵字修飾,而該成員事實上並沒有覆蓋繼承的成員,編譯器將

會給出警告,在一個成員聲明同時使用new和override則編譯器會報告錯誤.

base保留字
base關鍵字主要是為派生類調用基類成員提供一個簡寫的方法.我們先看一個例子程序代碼
class A
{
  public void F()
  {
  //.........
  }
  public int this[int nIndex]
  {
    get{};
    set{};
  }
}
class B:A
{
  public void G()
  {
    int x=base[0];
    base.F();
  }
}
類B從類A中繼承,B的方法G中調用了A的方法F和索引指示器.方法F在進行編譯時等價於:
public void G()
{
  int x=(A (this))[0];
  (A (this)).F();
}
使用base關鍵字對基類成員的訪問格式為:
base . indentifier
base [expression-list]

多態性

在面向對像的系統中,多態性是一個非常重要的概念,它允許客戶對一個對像進行操作,由對

像來完成一系列的動作,具體實現哪個動作,如何實現由系統負責解釋.

C#支持兩種類型的多態性
編譯時的多態性
編譯時的多態性是通過重載來實現的.
運行時的多態性
運行時的多態性就是指直到系統運行時,才根據實際情況決定實現何种操作.C#中,運行時的

多態性通過虛成員實現.

編譯時的多態性為我們提供了運行速度快的特點,而運行時的多態多性則帶來了高度靈活和

抽像的特點.

虛方法
當類中的方法聲明前加上了virtual修飾符,我們稱之為虛方法,反之為非虛.使用了virtual

修飾符後,不允許再有static,abstract或override修飾符.
對於非虛的方法,無論被其所在類的實例調用,還是被這個類的派生類的實例調用,方法的執

行方式不變.而對於虛方法,它的執行方式可以被派生類改變,這种改變是通過方法的重載來

實現的.
下面的例子說明了虛方法與非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
B.F
B.G
B.G
例子中,A類提供了兩個方法;非虛的F和虛方法G.類B則提供了一個新的非虛的方法F(),從而

覆蓋了繼承的F:類B同時還重載了繼承的方法G
注意到本例中,方法a.G()實際調用了B.G,而不是A.G.這是因為編譯時值為A,但運行時值為

B,所以B完成了對方法的實際調用.

在派生類中對虛方法進行重載

下面用汽車的例子來說明多態性的實現.
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!");
    }
}
class Test
{
    public static void Main()
    {
        Vehicle v1 = new Vehicle(1,2);
        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();
    }
}
程序運行結果:
the w 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類中的Speak方法被聲明為虛方法,那麼在派生類中就可以重新定義此方法.
在派生類Car和Truck中分別重載了Speak方法,派生類中的方法原型和基類中的方法原型必

須完全一致.
在Test類中,創建了Vehicle類的實例v1,並且先後指向Car類的實例c1和Truck類的實例t1.
這里,Vehicle類的實例v1先後被賦予Car類的實例c1,以及Truck類的實例t1的值.在執行過

程中,v1先後指代不同的類的實例,從而調用不同的版本.這里v1的Speak方法實現了多態性,

並且v1.Speak()究竟執行哪個版本,不是在程序編譯時確定的,而是在程序的動態運行時,根

據v1某一時刻的指代類型來確定的,所以還體現了動態的多態性.


抽像與密封
抽像類
有時候,基類並不與具體的事物相聯系,而是只表達一种抽像的概念,用以為它的派生類提供

了一個公共的界面.為此,C#中引入了抽像類(abstract class)的概念.
抽像類使用abstract修飾符,對抽像類的使用有以下幾點規定:
抽像類只能作為其它類的基類,它不能直接被實例化,而且對抽像類不能使用new操作符.抽

像類如果含有抽像的變量或值,則它們要麼是null類型,要麼包含了對非抽像類的實例的引

用.
抽像類允許包含抽像成員,雖然這不是必須的.
抽像類不能同時又是密封的.
如果一個非抽像類從抽像類中派生,則其必須通過重載來實現所有繼承而來的抽像成員.請

看下面的示例:
abstract class A
{
  public abstract void F();
}
abstract class B:A
{
  public void G(){}
}
class C:B
{
  public override void F(){}
}
抽像類A提供了一個抽像方法F.類B從抽像類A中繼承,並且又提供了一個方法G;因為B中並沒

有包含對F的實現,所以B也必須是抽像類.類C從類B中繼承,類中重載了抽像方法F,並且提供

了對F的具體實現,則類C允許是非抽像的.
讓我們繼續研究汽車類的例子,我們從"交通工具"這個角度來理確Vehicle類的話,它應該表

達一种抽像的概念,我們可以把它定議為抽像類,由轎車類Car和卡車類Truck來繼承這個抽

像類,它們作為可以實例化的類.

using System;
abstract 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.WirteLine("The truck is speaking:Ba-ba!");
  }
}

抽像方法
具體的實現交給派生類通過重載來實現.
一個方法聲明中如果加上了abstract修飾符,我們稱該方法為抽像方法(abstract method)
如果一個方法被聲明也是抽像的,那麼該方法默認也是一個虛方法.事實上,抽像方法是一個

新的虛方法,它不提供具體的方法實現代碼.非虛的派生類要求通過重載為繼承的虛方法提

供自己的實現,而抽像方法則不包含具體的實現內容,所以方法聲明的執行體中只有一個分

號";".
只能在抽像類中聲明抽像方法.對抽像方法,不能再使用static或virtual修飾符,而且方法

不能有任何可執行代碼,哪怕只是一對大括號中間加一個一個分號"{;}"都不允許出現,只需

要給出方法的原型就可以了.
"交通工具"的"鳴笛"這個方法實上是沒有什麼意義的,接下來我們利用抽像方法的概念繼續

改寫這個類的例子:
using System;
abstract class Vehicle
{
  public int wheels;
  protected float weight;
  public Vehicle(int w,float g)
  {
    wheels=w;
    weight=g;
  }
  public abstract void Speak();
}
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.WirteLine("The truck is speaking:Ba-ba!");
  }
}
還要注意,抽像方法在派生類中不能使用base關鍵字來進行訪問.例如下面的寫法在編譯時

會發生錯誤:
class A
{
  public abstract void F();
}
class B:A
{
  public override void F()
  {
    base.F();  //錯誤,base.F是抽像方法
  }
}
我們還可以利用抽像方法來重載基類的虛方法,這時基類中的虛方法的執行代碼就被"撋截"

了.下面的例子說明了這一點:
class A
{
  public virtual void F()
  {
    Console.WriteLine("A.F");
  }
}
abstract class B:A
{
  public abstract override void F();
}
class C:B
{
  public override void F()
  {
    Console.WriteLine("C.F");
  }
}
類A聲明了一個虛方法F,派生類B使用抽像方法重載了F,這樣B的派生類C就可以重載F並提供

自己的實現.

密封類
密封類在聲明中使用sealed修飾符,這樣就可以防止該類被其它類繼承.如果試圖將一個密

封類作為其它類的基類,C#將示出錯.再所當然,密封類不能同時又是抽像類,因為抽像總是

希望被繼承的.
在哪些場合下使用密封類呢?密封類可以阻止其它程序員在無意中繼承該類,而且密封可以

起到運行時優化的效果.實際上,密封類中不可能有派生類,如果密封類實例中存在虛成員函

數可以轉化為非虛的,函數修飾符virtual不再生效,看看下面這個例子.
abstract class A()
{
  public abstract void F();
}
sealed class B:A
{
  public override void F()
  {
  /////////////////
  }
}
如果我們嘗試寫下面的代碼:
class C:B{}
C#會指出這個錯誤,告訴你B是一個密封類,不能試圖從B中派生任何類.
密封方法
C#還提出了密封方法(sealed method)的概念,以防止在方法所在類的派生類中對該方法的

重載.對方法可以使用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的派生類中,可以重載方法G,但不能重載方法F.


繼承中關於屬性的一些問題

和類的成員方法一樣,我們也可以定議屬性的重載,虛屬性,抽像屬性以及密封屬性的概念.與類和方法一樣,屬性的修飾符也應符合下列規則:

從上面可以看出,屬性的這些規則與方法十分類似,對於屬性的訪問器,我們可以把get訪問

器看成是一個與屬性修飾符相同,沒有參數,返回值為屬性的值類型的方法,把set訪問器看

成是一個與屬性修飾符相同,僅含有一個value慘數,返回類型為void的方法.下面我們用客

戶住宿的例子來說明屬性在繼承中的一些問題.
using System;
public enum sex
{
  woman,
  man,
};
abstract public class People
{
  private string s_name;
  public virtual string Name
  {
    get{return s_name;}
  }
  private sex m_sex;
  public virtual sex Sex
  {
    get{return m_sex;}
  }
  protected string s_card;
  public abstract string Card
  {
    get;set;
  }
}
上面的例子中聲明了"人"這個類,人的姓名Name和性別Sex是兩個只讀的虛屬性;身份證號

Card是一個抽像屬性,允許讀寫.因為類People中包含了抽像屬性Card,所以People必須聲明

是抽像的.下面我們為住宿的客人編寫一個類,類從Peple中繼承.
class Customer:People
{
  string s_no;
  int i_day;
  public string No
  {
    get {return s_no;}
    set
    {
      if(s_no!=value)
      {
        s_no=value;
      }
    }
  }
  public int Day
  {
    get {return i_day;}
    set
    {
      if(i_day!=value)
      {
        i_day=value;
      }
    }
  }
  public override string Name
  {
    get {return base.Name;}
  }
  public override sex Sex
  {
    get {return base.Sex;}
  }
  public override string Card
  {
    get {return s_card;}
    set {s_card=value;}
  }
}
在類Customer中,屬性Name,Sex和Card的聲明都加上了override修飾符,屬性的聲明都與基

類People中保持一致.Name和Sex的get訪問器,Card的get和set訪問器都使用了base關鍵字

來訪問基類People中的訪問器.屬性Card的聲明重載了基類People中抽像訪問器.這樣,在

Customer類中沒有抽像成員的存在,Customer可以是非虛的.

 

posted @ 2007-11-02 14:38  Athrun  阅读(989)  评论(0编辑  收藏  举报