1. 程式人生 > >多型與虛擬函式

多型與虛擬函式

1.何為多型???多型的作用??

多型的概念:
一個介面,多種方法

封裝的作用:
封裝可以是得程式碼模組化;繼承可以擴充套件已經存在的程式碼,都是為了代程式碼重用;
多型的目的:介面重用

2.靜態聯編和動態聯編分別表示什麼?

在編譯的時候能夠確定物件所呼叫的成員函式的地址則為靜態聯編;
動態聯編:指的是在程式執行的時候動態地進行,根據當時的情況來確定呼叫哪個同名函式,父類指標指向哪個子類,就呼叫哪個子類的同名函式,實際上是在執行的時候虛擬函式的實現;

3.類中有虛擬函式的時候,類有什麼變化?

當類中存在虛擬函式的時候,這個類大小就增加4個位元組,這個4位元組是虛表指標,存放的是虛擬函式表的地址;
虛擬函式表其實是一個指標陣列,它裡面存放的其實是虛擬函式的地址;

虛擬函式的幾個知識點

先看一個例子:

#include "stdafx.h"
#include <ostream>
#include <iostream>
using namespace std;

class CClassA {
public:
    virtual void fun_a() {
        cout << "fun:cclassA,這是類A裡面的函式" << endl;
    }
};

class CClassB:public CClassA {
public:
    virtual void fun_a() {
        cout
<< "fun:cclassB,這是類B裡面的函式" << endl; } }; int main() { CClassA objA, *pobjA; CClassB objB; pobjA = &objA; pobjA->fun_a(); pobjA = &objB;//父類指標指向物件B,下面這個函式就是B裡面的物件!!! pobjA->fun_a(); return 0; }

這裡寫圖片描述

幾個比較重要的知識點

一個空類:一個位元組
一個空類裡面只有一個整型,此類大小:4位元組

1.定義基類的公有派生類,基類的公有派生類中過載該函式,過載該虛擬函式不是一般地過載,它要求函式名,返回型別,引數個數,引數型別和順序完全相同,由於對虛擬函式進行了過載,派生類中的虛擬函式前的virtual關鍵字可以省略
2.父類虛表和子類虛表是2個獨立的表(由於兩個類之間有繼承關係),故子類的虛表裡面也有父類的虛擬函式指標,但是如果子類中有和父類中一樣的函式名,那麼子類中的虛擬函式就覆蓋了子類虛表中所繼承的父類虛擬函式地址;
3.如果父類中有虛擬函式,子類中無虛擬函式,則子類也會生成一個虛表,(因為子類要繼承父類的虛擬函式,但是虛擬函式又不能隨便放,只能生成一個虛表出來
4.純虛擬函式是一種特殊的虛擬函式,是一種沒有具體實現的虛擬函式

例子:
class  cclassA
{
virtual <函式型別><函式名>(<引數表>)=0;
}

含有純虛擬函式的類是抽象類,抽象類是不能定義物件的(不能例項化)
含有純虛擬函式的類—->抽象類——->不能定義物件!!!!!

解構函式為什麼要推薦設計為虛擬函式

首先,先了解一下建構函式的呼叫順序:
基類建構函式–>資料成員的建構函式->派生類建構函式
執行派生類的解構函式,也需要呼叫基類以及子物件的解構函式,析構順序如下:
派生類解構函式–>資料成員類解構函式—–>基類的解構函式
正常情況下(沒有虛析構的情況下),一個子類被釋放的時候,會主動呼叫其父類解構函式;使用父類指標指向子類物件的時候,只會析構掉父類物件,如果此時子類裡有堆空間記憶體,則會造成記憶體洩露!(記憶體洩露是指使用malloc或者new申請記憶體空間之後,沒有freeh或者delete掉,此時申請的那塊記憶體仍然處於佔用狀態,稱為記憶體洩露!)
進一步解釋:如果說你建立了一個類物件,並且也呼叫了它的建構函式,就相當於開闢了一段空間;如果說這段空間沒有free或者說沒有被delete掉,那麼就會造記憶體洩漏!
其實出了函式之後,區域性變數將自動銷燬!
可以看下面這個例子(對上面例子的改動,把虛擬函式去掉了)來理解:

#include "stdafx.h"
#include <ostream>
#include <iostream>
using namespace std;

class CClassA {
public:
     void fun_a() {
        cout << "fun:cclassA,這是類A裡面的函式" << endl;
    }
};

class CClassB :public CClassA {
public:
     void fun_a() {
        cout << "fun:cclassB,這是類B裡面的函式" << endl;
    }
};

int main()
{
    CClassA objA, *pobjA;
    CClassB objB;
    pobjA = &objA;
    pobjA->fun_a();
    pobjA = &objB;//父類指標指向物件B,下面這個函式就是B裡面的物件!!!
    pobjA->fun_a();
    return 0;
}

這裡寫圖片描述

故必須將解構函式定義為虛擬函式,這樣釋放父類指標的時候便會呼叫子類的解構函式,也會正常釋放掉子類的堆空間!
看下面一個例子:

#include "stdafx.h"
class Base
{
public:
    Base() {
        printf("base 父類構造\n");

    };
    virtual ~Base() {                   //1th在此行加不加virtual有一定區別,而對於一般成員函式,基類中有虛擬函式,則子類中對應的成員函式不一定宣告為虛擬函式,因為子類繼承了父類 
        printf("Base 父類析構\n");
    };
    void fun() {                        //2th
        printf("父類base-fun\n");
    };
};
class son :public Base
{
public:
    son() {
        printf("son 子類構造\n");
    };
     ~son() {                                        //**
        printf("son 子類析構\n");
    }
    void fun() {                         //3th
        printf("子類son-fun\n");
    };
};


int main()
{
    Base *p; //父類指標

    son *pobj = new son; //走到這一步,會先呼叫父類構造,然後再呼叫子類構造,new 出子類物件指標,從堆空間中分配出來的,一般建立物件是在棧空間裡面

    p = pobj;   //父類指標指向子類物件            
    p->fun();          //注意,這個例子裡面函式不是虛擬函式,所以呼叫的函式皆為父類裡面的函式!!!!!
    delete pobj; //通過父類指標釋放子類物件


    return 0;
}

這裡寫圖片描述
**處不管有沒有virtual這個關鍵詞,結果都如下:
(驗證了上面所說的:如果父類中有虛擬函式,子類中無虛擬函式,則子類也會生成一個虛表)
對上面例子進行改造:

#include "stdafx.h"
class Base
{
public:
    Base() {
        printf("base 父類構造\n");

    };
     ~Base() {                   //1th在此行加不加virtual有一定區別,而對於一般成員函式,基類中有虛擬函式,則子類中對應的成員函式不一定宣告為虛擬函式,因為子類繼承了父類 
        printf("Base 父類析構\n");
    };
    void fun() {                        //2th
        printf("父類base-fun\n");
    };
};
class son :public Base
{
public:
    son() {
        printf("son 子類構造\n");
    };
    ~son() {                                        //**
        printf("son 子類析構\n");
    }
    void fun() {                         //3th
        printf("子類son-fun\n");
    };
};


int main()
{
    Base *p; //父類指標

    son *pobj = new son; //走到這一步,會先呼叫父類構造,然後再呼叫子類構造,new 出子類物件指標,從堆空間中分配出來的,一般建立物件是在棧空間裡面

    p = pobj;   //父類指標指向子類物件            
    p->fun();          //注意,這個例子裡面函式不是虛擬函式,所以呼叫的函式皆為父類裡面的函式!!!!!
    delete pobj; //通過父類指標釋放子類物件


    return 0;
}

1th有了變化:
這裡寫圖片描述
上面兩個例子沒什麼變化

下面要注意:

將上面的例子中的1th改了:
Base *pobj = new son

#include "stdafx.h"
class Base
{
public:
    Base() {
        printf("base 父類構造\n");

    };
    virtual  ~Base() {                   //1th在此行加不加virtual有一定區別,而對於一般成員函式,基類中有虛擬函式,則子類中對應的成員函式不一定宣告為虛擬函式,因為子類繼承了父類 
        printf("Base 父類析構\n");
    };
    void fun() {                        //2th
        printf("父類base-fun\n");
    };
};
class son :public Base
{
public:
    son() {
        printf("son 子類構造\n");
    };
    ~son() {                                        //**
        printf("son 子類析構\n");
    }
    void fun() {                         //3th
        printf("子類son-fun\n");
    };
};


int main()
{
    Base *p; //父類指標

    Base *pobj = new son; //走到這一步,會先呼叫父類構造,然後再呼叫子類構造,new 出子類物件指標,從堆空間中分配出來的,一般建立物件是在棧空間裡面

    p = pobj;   //父類指標指向子類物件            
    p->fun();          //注意,這個例子裡面函式不是虛擬函式,所以呼叫的函式皆為父類裡面的函式!!!!!
    delete pobj; //通過父類指標釋放子類物件


    return 0;
}

這裡寫圖片描述
對上面進行一些改動(在1th那邊):


#include "stdafx.h"
class Base
{
public:
    Base() {
        printf("base 父類構造\n");

    };
      ~Base() {                   //1th在此行加不加virtual有一定區別,而對於一般成員函式,基類中有虛擬函式,則子類中對應的成員函式不一定宣告為虛擬函式,因為子類繼承了父類 
        printf("Base 父類析構\n");
    };
    void fun() {                        //2th
        printf("父類base-fun\n");
    };
};
class son :public Base
{
public:
    son() {
        printf("son 子類構造\n");
    };
    ~son() {                                        //**
        printf("son 子類析構\n");
    }
    void fun() {                         //3th
        printf("子類son-fun\n");
    };
};


int main()
{
    Base *p; //父類指標

    Base *pobj = new son; //走到這一步,會先呼叫父類構造,然後再呼叫子類構造,new 出子類物件指標,從堆空間中分配出來的,一般建立物件是在棧空間裡面

    p = pobj;   //父類指標指向子類物件            
    p->fun();          //注意,這個例子裡面函式不是虛擬函式,所以呼叫的函式皆為父類裡面的函式!!!!!
    delete pobj; //這裡沒有釋放子類物件,就是因為這個沒有虛析構!

return 0;
}

這裡寫圖片描述

虛基類

其的目的是為了解決二義性問題,使用公共基類在其派生類物件中只產生一個基類子物件!!!!!
自己的一個帖子殼參考參考:
有有關虛基類的知識

有關繼承的一句話總結:

  • 公有繼承方式:
    基類中的每個成員在派生類中保持同樣的訪問許可權

  • 私有繼承方式
    基類中的每個成員在派生類中都是private成員,而且它們不能在被派生的子類所訪問

  • 保護繼承方式
    基類中的public成員和protect成員在派生類中都是protect成員,private成員在派生類中仍然為private成員.

1.不管是什麼繼承方式,派生類的成員函式和友元函式都可以訪問基類中的公有成員和保護成員,但是不能訪問私有成員
2.在公有繼承時,派生類的物件只能訪問公有成員,在保護和私有類的繼承時,派生類的物件不能訪問基類任何成員