C++ 虛函數 從入門到精通

寫在前面

在面向對象的程序設計中爲什麼會出現虛函數這個說法呢?
萬物究其本源:
1.面向對象的三大特徵:

  • 封裝:類
  • 繼承
  • 多態:動態綁定,當使用基類的引用(或者指針)對象調用一個虛函數時,將發生動態綁定,會根據對象本身的類型調用對應的函數

2.虛函數是在繼承這一特徵上出現的
繼承指的是基類和派生類的層次關係,派生類可以直接或間接的繼承基類。

  • 基類:定義在層次關係中,所有類共同擁有的數據成員
  • 派生類:定義各自特有的成員

在基類中定義的成員函數,如果在派生類中各自定義自己的版本,則在基類中將該函數定義成虛函數;派生類中如果將繼承來的虛函數重新定義了,可以在形參列表後加上override關鍵字,來表明改寫了基類的虛函數
在這裏插入圖片描述

虛函數

虛函數是通過虛函數表來實現的,更詳細內容參考:詳細內容

類派生列表

作用:指名派生類是從哪些基類繼承而來的
位置:緊跟在派生類的類名後面,由 冒號 和 以逗號分隔的基類名組成,基類名前需指名繼承的級別(private、protected、public)
基類:
在這裏插入圖片描述
派生類:
在這裏插入圖片描述
基類和派生類詳解

  • 類內除構造函數之外的非靜態成員函數都可以定義成虛函數

  • virtual關鍵字只可以出現在類內對該函數進行聲明時,在類的外部定義該成員函數時不能出現

  • 當通過指針或者引用調用虛函數時,虛函數的解析過程發生在程序 運行時 而 不是在編譯時,與其綁定的對象有關,只有這種情況會出現對象的靜態類型和動態類型不一致的情況;普通對象調用虛函數或者非虛函數時,在編譯時綁定到該對象所屬類的函數上。

  • 派生類可以覆蓋其基類中的虛函數,也可以不覆蓋,如果不覆蓋,默認繼承基類中的版本

  • 派生類覆蓋基類中的虛函數時,可以加上virtual關鍵字,也可以不加,因爲一旦該函數聲明成虛函數,在其所有的派生類中都是虛函數

  • 派生類中虛函數的返回類型 需要 和基類中虛函數的返回類型是一致的,但是當虛函數返回是該類的指針或者引用時,上述規則就無效。例如D是從B派生來的,B中的虛函數返回的是B*,D中對應的虛函數可以返回D*,但這時候要求從D到B的類型轉化是可訪問的

  • 派生類中虛函數的形參 要和基類中對應虛函數的形參嚴格匹配。如果派生類中的虛函數和基類中的基函數名字相同,但是形參列表不同,這時候,派生類中的虛函數並沒有覆蓋基類中的虛函數,而是新定義的函數,與基類中的同名虛函數相互獨立。
    如果這種參數列表不匹配的情況,是寫錯了,編譯器並不會報錯,但是爲了讓編譯器檢查出來,可以加上override關鍵字,來指名該函數是覆蓋基類中的基函數
    在這裏插入圖片描述

  • 如果虛函數不希望被派生類覆蓋,則加上final關鍵字
    在這裏插入圖片描述

  • 如果對虛函數的調用不希望是動態綁定,則可以通過類作用域運算符來指定調用哪個類的虛函數
    在這裏插入圖片描述

純虛函數

  • 在聲明類的成員函數時,在 分號 前加上 =0 ,就表明該函數是純虛函數,=0只能出現在函數的聲明處
  • 純虛函數如果要定義的話,必須要定義在類的外部 在這裏插入圖片描述
  • 含有純虛函數的類稱爲抽象基類,不能定義該類的對象,即不能創建抽象基類的對象。在抽象基類的派生類中,必須覆蓋純虛函數,否則該派生類依然是抽象基類

實例

參考鏈接

#include <bits/stdc++.h>
#include <iostream> 
#include <stdlib.h>
using namespace std; 

class CA 
{ 
public: 
    void f() 
    { 
        cout << "CA f()" << endl; 
    } 
    virtual void ff() 
    { 
        cout << "CA ff()" << endl; 
        f(); 
    } 
}; 

class CB : public CA 
{ 
public : 
    virtual void f() 
    { 
        cout << "CB f()" << endl; 
    } 
    void ff() 
    { 
        cout << "CB ff()" << endl; 
        f(); 
        CA::ff(); 
    } 
}; 
class CC : public CB 
{ 
public: 
    virtual void f() 
    { 
        cout << "C f()" << endl; 
    } 
}; 

int main() 
{ 
    CB b; 
    CA *ap = &b; 
    CC c; 
    CB &br = c; 
    CB *bp = &c; 

    ap->f(); //CA f()
    cout << endl;

    b.f(); //CB f()
    cout << endl;

    br.f(); //C f()
    cout << endl;

    bp->f(); //C f()
    cout << endl;

    ap->ff(); //CB ff() CB f() CA ff() CA f()
    cout << endl;

    bp->ff(); //CB ff() C f() CA ff() CA f()
    cout << endl;

    return 0; 
}

運行結果:

CA f()

CB f()

C f()

C f()

CB ff()
CB f()
CA ff()
CA f()

CB ff()
C f()
CA ff()
CA f()

總結:

  • 如果指針或者引用對象所調用的函數是虛函數,那麼調用它所指向 或者 綁定 的對象所屬類的虛函數(動態綁定,在程序運行時確定所指向的類型)
  • 如果調用的是非虛函數,那麼直接調用該指針類型或者引用類型所屬類的虛函數(靜態綁定,在編譯時就確定了該指針或引用指向對象的類型)特別注意 第一行的輸出 ap->f(); //CA f()
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章