13.多態、虛函數

多態與虛函數:

什麼是虛函數:

用virtual關鍵字聲明的函數都是虛函數。虛函數存在的唯一目的,就是爲了實現多態(動態綁定/運行時綁定)。

virtual 關鍵字只在類定義中的成員函數聲明處使用,不能在類外部寫成員函數體時使用。所有派生類中具有覆蓋關係的同名函數都將自動成爲虛函數。

靜態成員函數不能是虛函數。

再說簡單點:有virtual聲明的函數都是虛函數。如果沒有virtual,那麼派生類中的同名函數會把基類中的同名函數隱藏了。如果有,那麼派生類的同名函數(同參同返回)會在虛函數表中將基類的同名函數覆蓋掉。

什麼是多態:

        多態性可以簡單概括爲“一個接口,多種行爲”。

動態綁定(運行階段)是多態的基礎。

基類指針或引用,指向一系列的派生類對象, 調用派生類對象的同名覆蓋方法(也就是那個與基類虛函數同名同參同返回的函數),指針指向誰,就會調用誰的方法。

多態分爲兩種:

        (1)編譯時多態(也叫靜態的多態):主要通過函數的重載和模板來實現。

        (2)運行時多態(也叫動態的多態):主要通過虛函數來實現。

覆蓋:

       基類的某個成員函數爲虛函數,派生類又定義一個成員函數,函數名、形參、返回類型都與基類的成員函數相同。那麼就會用派生類的函數覆蓋掉基類的虛函數


說一下多態是如何實現的:

在代碼編譯階段產生一張虛函數表vftable。運行的時候,會載入內存,加載到數據段。一個類的虛函數表中列出了該類的全部虛函數地址。

如果成員裏有虛函數,在成員變量裏只會多一個虛函數指針(對象的前4個字節),指向虛函數表vftable()存放虛函數地址。虛函數的個數只會影響表的大小。不會影響對象的大小。

基類有虛函數,派生類如果有同名同參數列表同返回值的函數,派生類的函數會自動變成虛函數,在虛函數表中派生類會覆蓋掉原有的函數。

舉個栗子:

    #include <iostream>
    using namespace std;
    class A
    {
    public:
        int i;
        virtual void func() {}
        virtual void func2() {}
    };
    class B : public A
    {
        int j;
        void func() {}
    };
    int main()
    {
        cout << sizeof(A) << ", " << sizeof(B);
        return 0;
    }

 設 pa 的類型是 A*,則 pa->func() 這條語句的執行過程如下:

1) 取出 pa 指針所指位置的前 4 個字節,也就是虛函數指針。如果 pa 指向的是類 A 的對象,則這個地址就是類 A 的虛函數表的地址;類 B 同

2) 根據虛函數指針找到虛函數表,在其中查找要調用的虛函數的地址。

如果 pa 指向的是類 A 的對象,自然就會在類 A 的虛函數表中查出 A::func 的地址;類 B 同

類 B 沒有自己的 func2 函數,因此在類 B 的虛函數表中保存的是 A::func2 的地址,這樣,即便 pa 指向類 B 的對象,pa->func2();這條語句在執行過程中也能在類 B 的虛函數表中找到 A::func2 的地址。

3) 根據找到的虛函數的地址調用虛函數。

由以上過程可以看出,只要是通過基類指針或基類引用調用虛函數的語句,就一定是多態的,也一定會執行上面的查表過程,哪怕這個虛函數僅在基類中有,在派生類中沒有。


動態綁定與靜態綁定:

綁定就是函數調用。

在使用的時候,用一個基類的指針指向了一個派生類的對象,如果調用這個虛函數,會先找虛函數指針,再找虛函數表,再再找虛函數地址。而這個綁定過程就是動態綁定(運行時期)。

如果你不用指針調用,而是用對象本身調用函數,不論是否是虛函數,都是靜態綁定(編譯時期)。

用指針調用如果是虛函數,指針識別的就是運行時期的類型;如果調用的是一般的函數,指針識別就是在編譯時期。

  • 沒有virtual -> 靜態綁定
  • 有 virtual 用引用或指針  ->   動態綁定
  • 有 virtual 但用對象調用->   動態綁定

純虛函數:

一般情況下,基類是不希望定義對象的。基類只是爲了將共有的屬性統一起來。

爲了實現這一目的:在基類提供的一個虛函數,爲所有派生類提供統一的虛函數接口,具體實現讓派生類自己去重寫的。

virtual void show() = 0;  // 在虛函數後面加   =  0    就是純虛函數,不用去實現。

純虛函數實際上是不存在的,引入純虛函數就是爲了便於實現多態。

擁有純虛函數的類叫做抽象類。抽象類不能實例化。一般基類都應該實現爲抽象類。

那麼問題來了:

1、基類在沒有更多方法的時候,把誰實現成純虛函數呢?-------->  析構函數

首先明確一個函數想要成爲虛函數     1、它得有地址;2、得依賴對象

1)構造函數能不能是虛函數?

構造函數不依賴對象,構造函數執行完纔有對象,有對象纔有虛函數指針,所以不能是虛函數。

2)static成員方法能不能是虛函數?  virtual static

靜態函數也不依賴對象,可以直接使用類名調用,也不能是虛函數。

3)inline函數能不能是虛函數?  =>  virtual inline  

內聯函數直接在程序內展開,沒有地址,無法往虛函數表放。也不能是虛函數。

4)析構函數能不能實現成虛函數?

析構函數依賴對象,有地址。可以寫成虛函數

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章