虛函數和抽象函數


虛函數是有代碼的並明確允許子類去覆蓋,但子類也可不覆蓋,就是說可以直接用,不用重寫   
抽象函數是沒有代碼,子類繼承后一定要重寫

在一個類中用虛函數:   
是因為在超類中的有實際代碼的方法,但明確允許子類可以作重寫   
而且當子類重寫后,可以用子類實例超類;如果這樣,超類變量調用虛函數時,執行的是子類的方法   
    
在一個類中用抽象函數   
是在寫超類時不確定函數的代碼,讓子類去實現   
抽象函數沒有方法體。

 

 

簡單來說虛函數(Virtual)已經包含了也必須包含默認的實現,所以在派生類中可以重新實現也可以不實現這些虛函數。 
抽象函數(abstract)沒有提供默認實現,所以在派生類中必須實現這些抽象函數。 
接口中的函數類似於抽象函數,也不提供默認實現,實現接口的類也必須實現這些函數。 
但接口可用於多繼承,即,類只能從一個類繼承,但可同時實現多個接口。

http://blog.csdn.net/rfb0204421/article/details/3548929

 

虛函數(virtual):可由子類繼承並重寫的函數,后期綁定
抽像函數(abstract):規定其非虛子類必須實現的函數,必須被重寫(override實現)。抽象類不能被實例化,抽象方法必須是空方法,由派生類去實現它(重載)
接口(interface):必須重寫(實現接口必須重寫接口中的所有方法,也就是要實現接口中的所有方法)


要想實現多態的方法之一就是使用到虛函數。抽象出一個高內聚、低偶合,易於維護和擴展的模型。

但是在抽象過程中我們會發現很多事物的特征不清楚,或者很容易發生變動,怎么辦呢?比如飛禽都有飛這個動作,但是對於不同的鳥類它的飛的動作方式是不同的,有的是滑行,有的要顫抖翅膀,雖然都是飛的行為,但具體實現卻是千差萬別,在我們抽象的模型中不可能把一個個飛的動作都考慮到,那么怎樣為以后留下好的擴展,怎樣來處理各個具體飛禽類千差萬別的飛行動作呢?比如我現在又要實現一個類“鶴”,它也有飛禽的特征(比如飛這個行為),如何使我可以只用簡單地繼承“飛禽”,而不去修改“飛禽”這個抽象模型現有的代碼,從而達到方便地擴展系統呢?

因此面向對象的概念中引入了虛函數來解決這類問題。

使用虛函數就是在父類中把子類中共有的但卻易於變化或者不清楚的特征抽取出來,作為子類需要去重新實現的操作(override)。而虛函數也是OOP中實現多態的關鍵之一。

下面引舉一個例子:(用C#描述)

class 飛禽
{
       public string wing;           // 翅膀
       public string feather;        // 羽毛
       ……                 // 其它屬性和行為

       public virtual bool Fly()     // 利用關鍵字virtual來定義為虛函數,這是一個熱點
       {
           // 空下來讓子類去實現
       }
}

class 麻雀 : 飛禽             // 麻雀從飛禽繼承而來
{
       …… // 定義麻雀自己特有的屬性和行為
       public override bool Fly()    // 利用關鍵字override重載飛翔動作,實現自己的飛翔
       {
           …… // 實現麻雀飛的動作
       }
}

class 鶴 : 飛禽               // 鶴從飛禽繼承而來
{
…… // 定義鶴自己的特有的屬性和行為
       public override bool Fly()    // 利用關鍵字override重載實現鶴的飛翔
       {
           …… // 實現鶴飛的動作
       }
}

這樣我們只需要在抽象模型“飛禽”里定義Fly()這個行為,表示所有由此“飛禽”派生出去的子類都會有Fly()這個行為,而至於Fly()到底具體是怎么實現的,那么就由具體的子類去實現就好了,不會再影響“飛禽”這個抽象模型了。

比如現在我們要做一個飛禽射擊訓練的系統,我們就可以這樣來使用上面定義的類:
// 如何來使用虛函數,這里同時也是一個多態的例子.
// 定義一個射擊飛禽的方法
// 注意這里聲明傳入一個“飛禽”類作為參數,而不是某個具體的“鳥類”。好處就是以后不管再出現多少
// 種鳥類,只要是從飛禽繼承下來的,都照打不誤:)(多態的方式)

void ShootBird(飛禽 bird)
{
       // 當鳥在飛就開始射擊
       if(bird.Fly())
       {
           …… // 射擊動作
       }
}
static void main()
{
       / /打麻雀
       ShootBird(new 麻雀());
       // 打鶴
       ShootBird(new 鶴());
       // 都是打鳥的過程,我只要實現了具體某個鳥類(從“飛禽”派生而來)的定義,就可以對它
       // 進行射擊,而不用去修改ShootBird函數和飛禽基類
       ShootBird(new 其它的飛禽());
}

虛函數從C#的程序編譯的角度來看,它和其它一般的函數有什么區別呢?一般函數在編譯時采用先期綁定編譯到了執行文件中,其相對地址在程序運行期間是不發生變化的。而虛函數在編譯期間采用的是后期綁定,它的相對地址是不確定的,它會根據運行時期對象實例來動態判斷要調用的函數,其中那個聲明時定義的類叫聲明類,那個執行時實例化的類叫實例類。
(     如:飛禽 bird = new 麻雀();
那么飛禽就是聲明類,麻雀是實例類。       

具體的檢查的流程如下:
(1)當調用一個對象的函數時,系統會直接去檢查這個對象聲明定義的類,即聲明類,看所調用的函數是否為虛函數;
(2)如果不是虛函數,那么它就直接執行該函數。而如果有virtual關鍵字,也就是一個虛函數,那么這個時候它就不會立刻執行該函數了,而是轉去檢查對象的實例類。
(3)在這個實例類里,他會檢查這個實例類的定義中是否重新實現了該虛函數(通過override關鍵字),如果是,則執行該實例類中的這個重新實現的函數。而如果沒有的話,系統就會不停地往上找實例類的父類,並對父類重復剛才在實例類里的檢查,直到找到第一個重寫了該虛函數的父類為止,然后執行該父類里重寫后的函數。
知道這點,就可以理解下面代碼的運行結果了:

class A
{
       protected virtual Func()      // 注意virtual,表明這是一個虛函數
       {
           Console.WriteLine("Func In A");
       }
}

class B : A                   // 注意B是從A類繼承,所以A是父類,B是子類
{
       protected override Func()     // 注意override ,表明重新實現了虛函數
       {
           Console.WriteLine("Func In B");
       }
}

class C : B                   // 注意C是從A類繼承,所以B是父類,C是子類
{

}

class D : A                   // 注意D是從A類繼承,所以A是父類,D是子類
{
       protected new Func()          // 注意new ,表明覆蓋父類里的同名類,而不是重新實現
       {
           Console.WriteLine("Func In D");
       }
}

static void main()
{
       A a;                 // 定義一個a這個A類的對象.這個A就是a的聲明類
       A b;                 // 定義一個b這個A類的對象.這個A就是b的聲明類
       A c;                 // 定義一個c這個A類的對象.這個A就是b的聲明類
       A d;                 // 定義一個d這個A類的對象.這個A就是b的聲明類

       a = new A();             // 實例化a對象,A是a的實例類
       b = new B();             // 實例化b對象,B是b的實例類
       c = new C();             // 實例化b對象,C是b的實例類
       d = new D();             // 實例化b對象,D是b的實例類

       a.Func() ;
       // 執行a.Func:1.先檢查聲明類A 2.檢查到是虛方法 3.轉去檢查實例類A,就為本身 4.執行實例類A中的方法 5.輸出結果 Func In A
       b.Func() ;
       // 執行b.Func:1.先檢查聲明類A 2.檢查到是虛方法 3.轉去檢查實例類B,有重載的 4.執行實例類B中的方法 5.輸出結果 Func In B
       c.Func() ;
       // 執行c.Func:1.先檢查聲明類A 2.檢查到是虛方法 3.轉去檢查實例類C,無重載的 4.轉去檢查類C的父類B,有重載的 5.執行父類B中的Func方法 5.輸出結果 Func In B
       d.Func();
       // 執行d.Func:1.先檢查聲明類A 2.檢查到是虛方法 3.轉去檢查實例類D,無重載的(這個地方要注意了,雖然D里有實現Func(),但沒有使用override關鍵字,所以不會被認為是重載) 4.轉去檢查類D的父類A,就為本身 5.執行父類A中的Func方法 5.輸出結果 Func In A
       D d1 = new D()
       d1.Func();      // 執行D類里的Func(),輸出結果 Func In D
}

 

http://blog.sina.com.cn/s/blog_82dde7b6010149cu.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM