C++純虛函數、虛函數、實函數、抽象類,重載、重寫、重定義


  首先,面向對象程序設計(object-oriented programming)的核心思想是數據抽象、繼承、動態綁定。通過數據抽象,可以使類的接口與實現分離,使用繼承,可以更容易地定義與其他類相似但不完全相同的新類,使用動態綁定,可以在一定程度上忽略相似類的區別,而以統一的方式使用它們的對象。

  虛函數的作用是實現多態性(Polymorphism),多態性是將接口與實現進行分離,采用共同的方法,但因個體差異而采用不同的策略。純虛函數則是一種特殊的虛函數。虛函數聯系到多態,多態聯系到繼承。

一、虛函數

1 . 定義

  C++的虛函數主要作用是運行時多態,父類中提供虛函數的實現,為子類提供默認的函數實現

  子類可以重寫父類的虛函數實現子類的特殊化

  如下就是一個父類中的虛函數:

class A
{
public:
    virtual void out2(string s)
    {
        cout<<"A(out2):"<<s<<endl;
    }
};

  當我們在派生類中覆蓋某個函數時,可以在函數前加virtual關鍵字。然而這不是必須的,因為一旦某個函數被聲明成虛函數,則所有派生類中它都是虛函數。任何構造函數之外的非靜態函數都可以是虛函數。派生類經常(但不總是)覆蓋它繼承的虛函數,如果派生類沒有覆蓋其基類中某個虛函數,則該虛函數的行為類似於其他的普通成員,派生類會直接繼承其在基類中的版本。

2 . 動態綁定

  當我們使用基類的引用(或指針)調用一個虛函數時將發生動態綁定(dynamic binding)。因為我們直到運行時才能知道到底調用了哪個版本的虛函數,可能是基類中的版本也可能是派生類中的版本,判斷的依據是引用(或指針)所綁定的對象的真實類型。與非虛函數在編譯時綁定不同,虛函數是在運行時選擇函數的版本,所以動態綁定也叫運行時綁定(run-time binding)。

3 . 靜態類型與動態類型

  靜態類型指的是變量聲明時的類型或表達式生成的類型,它在編譯時總是已知的;動態類型指的是變量或表達式表示的內存中的對象的類型,它直到運行時才可知。當且僅當通過基類的指針或引用調用虛函數時,才會在運行時解析該調用,也只有在這種情況下對象的動態類型才有可能與靜態類型不同。如果表達式既不是引用也不是指針,則它的動態類型永遠與靜態類型一致。

二、純虛函數

1 . 定義

  C++中包含純虛函數的類,被稱為是“抽象類”。抽象類不能使用new出對象,只有實現了這個純虛函數的子類才能new出對象

  C++中的純虛函數更像是“只提供申明,沒有實現”,是對子類的約束,是“接口繼承”。

  C++中的純虛函數也是一種“運行時多態”。

  如下面的類包含純虛函數,就是“抽象類”:

class A
{
public:
    virtual void out1(string s)=0;
    virtual void out2(string s)
    {
        cout<<"A(out2):"<<s<<endl;
    }
};

  請注意,純虛函數應該只有聲明,沒有具體的定義,即使給出了純虛函數的定義也會被編譯器忽略。

2.引入原因:
      1) 為了方便使用多態特性,我們常常需要在基類中定義虛擬函數。
      2) 在很多情況下,基類本身生成對象是不合情理的。例如,動物作為一個基類可以派生出老虎、孔雀等子類,但動物本身生成對象明顯不合常理。
     為了解決上述問題,引入了純虛函數的概念,將函數定義為純虛函數(方法:virtual ReturnType Function()= 0;),則編譯器要求在派生類中必須予以重載以實現多態性。同時含有純虛擬函數的類稱為抽象類,它不能生成對象。這樣就很好地解決了上述兩個問題。

三、普通函數(no-virtual)

  普通函數是靜態編譯的,沒有運行時多態,只會根據指針或引用的“字面值”類對象,調用自己的普通函數

  普通函數是父類為子類提供的“強制實現”。

  因此,在繼承關系中,子類不應該重寫父類的普通函數,因為函數的調用至於類對象的字面值有關。

四、重載、重寫、重定義

   重載overload:是 函數名相同,參數列表不同。重載只是在類的內部存在。但是不能靠返回類型來判斷。
   重寫override:也叫做覆蓋。 子類重新定義父類中有相同名稱和參數的虛函數函數特征相同。但是具體實現不同,主要是在繼承關系中出現的。
  重寫需要注意:
  1 被重寫的函數不能是static的。必須是virtual的
  2  重寫函數必須有相同的類型,名稱和參數列表
  3  重寫函數的訪問修飾符可以不同。例如:盡管virtual是private的,派生類中重寫改寫為public,protected也是可以的

  重定義 (redefining)也叫做隱藏:

  子類重新定義父類中有相同名稱的非虛函數 ( 參數列表可以不同 ) 。

  如果一個類,存在和父類相同的函數,那么,這個類將會覆蓋其父類的方法,除非你在調用的時候,強制轉換為父類類型,否則試圖對子類和父類做類似重載的調用是不能成功的。
 

五、 虛析構函數

虛析構函數: 在析構函數前面加上關鍵字virtual進行說明,稱該析構函數為虛析構函數。雖然構造函數不能被聲明為虛函數,但析構函數可以被聲明為虛函數。

一般來說,如果一個類中定義了虛函數, 析構函數也應該定義為虛析構函數。

六、 抽象基類

  含有(或者未經覆蓋直接繼承)純虛函數的類叫抽象基類(abstract base class)。抽象基類負責定義接口,而后續的其他類可以覆蓋該接口。如果派生類中沒有重新定義純虛函數,而只是繼承基類的純虛函數,則這個派生類仍然還是一個抽象基類。因為抽象基類含有純虛函數(沒有定義),所以我們不能創建一個抽象基類的對象,但可以聲明指向抽象基類的指針或引用。

  之所以要存在抽象類,最主要是因為它具有不確定因素。我們把那些類中的確存在,但是在父類中無法確定具體實現的成員函數稱為純虛函數。純虛函數是一種     特殊的虛函數,它只有聲明,沒有具體的定義。 抽象類中至少存在一個純虛函數;存在純虛函數的類一定是抽象類。存在純虛函數是成為抽象類的充要條件。
  
#include <iostream>
using namespace std;

class A
{
public:
    virtual void out1()=0;  ///由子類實現
    virtual ~A(){};
    virtual void out2() ///默認實現
    {
        cout<<"A(out2)"<<endl;
    }
    void out3() ///強制實現
    {
        cout<<"A(out3)"<<endl;
    }
};

class B:public A
{
public:
    virtual ~B(){};
    void out1()
    {
        cout<<"B(out1)"<<endl;
    }
    void out2()
    {
        cout<<"B(out2)"<<endl;
    }
    void out3()
    {
        cout<<"B(out3)"<<endl;
    }
};

int main()
{
    A *ab=new B;
    ab->out1();
    ab->out2();
    ab->out3();
    cout<<"************************"<<endl;
    B *bb=new B;
    bb->out1();
    bb->out2();
    bb->out3();
   bb->A::out3();
delete ab; delete bb; return 0; }

 

out3()是一個實函數的重定義.
調用ab->out3();會去調A類中的out3(),它是在我們寫好代碼的時候就會定好的。因為out3()不是虛函數,不會動態綁定,也就是根據它是由A類定義的,這樣就調用這個類的函數。 
out2()是虛函數。調用ab->out2();會調用bb中保存的對象中對應的這個函數。這是由於new的B對象。
out1()與out2()一樣,只是在基類中不需要寫函數實現。
同時,還可以通過作用域運算符來實現在子類中調用父類的虛函數。

 

總結:

①.虛函數必須實現,不實現編譯器會報錯。

②.父類和子類都有各自的虛函數版本。由多態方式在運行時動態綁定。

③.通過作用域運算符可以強行調用指定的虛函數版本。

④.純虛函數聲明如下:virtual void funtion()=0; 純虛函數無需定義。包含純虛函數的類是抽象基類,抽象基類不能創建對象,但可以聲明指向抽象基類的指針或引用。

⑤.派生類實現了純虛函數以后,該純虛函數在派生類中就變成了虛函數,其子類可以再對該函數進行覆蓋。

⑥.析構函數通常應該是虛函數,這樣就能確保在析構時調用正確的析構函數版本

 

 c++虛函數表:

   C++虛函數表詳細解釋及實例分析     

 


免責聲明!

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



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