溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

C++ 多態(tài)與虛函數(shù)、與構(gòu)造函數(shù)和析構(gòu)函數(shù)有什么聯(lián)系

發(fā)布時(shí)間:2021-07-19 10:12:25 來源:億速云 閱讀:166 作者:chen 欄目:互聯(lián)網(wǎng)科技

這篇文章主要講解了“C++ 多態(tài)與虛函數(shù)、與構(gòu)造函數(shù)和析構(gòu)函數(shù)有什么聯(lián)系”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“C++ 多態(tài)與虛函數(shù)、與構(gòu)造函數(shù)和析構(gòu)函數(shù)有什么聯(lián)系”吧!

多態(tài)與虛函數(shù)

面向?qū)ο缶幊讨?,多態(tài)的含義是“一個接口,多種實(shí)現(xiàn)”。
多態(tài)分為靜態(tài)多態(tài)和動態(tài)多態(tài)。靜態(tài)多態(tài)是通過模板化和重載技術(shù)來實(shí)現(xiàn),在編譯的時(shí)候確定。動態(tài)多態(tài)通過虛函數(shù)和繼承關(guān)系來實(shí)現(xiàn),執(zhí)行動態(tài)綁定,在運(yùn)行的時(shí)候確定。
C++中運(yùn)行時(shí)的多態(tài)是指根據(jù)對象的實(shí)際類型來調(diào)用相應(yīng)的函數(shù)。如果對象類型是派生類,就調(diào)用派生類的函數(shù);如果對象類型是基類,就調(diào)用基類的函數(shù)。
C++多態(tài)性是通過虛函數(shù)來實(shí)現(xiàn)的,虛函數(shù)允許子類重新定義成員函數(shù),而子類重新定義父類方法稱為覆蓋或重寫(override)。

虛函數(shù):
用virtual關(guān)鍵字申明的函數(shù)叫做虛函數(shù),虛函數(shù)肯定是類的成員函數(shù)。
虛函數(shù)的作用就是實(shí)現(xiàn)動態(tài)綁定,也就是在程序的運(yùn)行階段動態(tài)地選擇合適的成員函數(shù)。
具體的實(shí)現(xiàn)方式,在基類中定義了虛函數(shù)后,可以在基類的派生類中對虛函數(shù)重新定義,在派生類中重新定義的函數(shù)應(yīng)與虛函數(shù)具有相同的形參個數(shù)和形參類型,以實(shí)現(xiàn)統(tǒng)一的接口,不同定義過程。
如果在派生類中沒有對虛函數(shù)重新定義,則它繼承其基類的虛函數(shù),此時(shí)派生類也為抽象類,不能實(shí)例化對象。

虛函數(shù)表:
當(dāng)一個類含有一個乃至多個虛函數(shù)的時(shí)候,將在全局?jǐn)?shù)據(jù)區(qū)(靜態(tài)區(qū))中存儲該類相應(yīng)的虛函數(shù)表vtbl,編譯器給類的每個對象添加一個相同隱藏的成員——虛函數(shù)表指針vptr,指向自身類的虛函數(shù)表。
虛函數(shù)表的大小在編譯時(shí)確定,不必動態(tài)分配內(nèi)存空間進(jìn)行存儲,因此不虛函數(shù)表不存在堆中,根據(jù)以上特征,虛函數(shù)表類似于類中靜態(tài)成員變量。靜態(tài)成員變量也是全局共享,大小確定,所以虛函數(shù)表和靜態(tài)成員變量一樣,存放在全局?jǐn)?shù)據(jù)區(qū)。
虛函數(shù)表中保存了類對象進(jìn)行聲明的虛函數(shù)的地址,即虛函數(shù)表的元素是指向類成員函數(shù)的指針。也就是說我們可以通過vptr訪問虛函數(shù)表,進(jìn)而訪問被聲明的虛函數(shù)的的地址,從而調(diào)用相應(yīng)的虛函數(shù)。
同一個類的不同對象的vptr實(shí)際上指向同一張?zhí)摵瘮?shù)表。vptr的設(shè)定和重置都由每一個類的構(gòu)造函數(shù),析構(gòu)函數(shù)和拷貝賦值運(yùn)算符自動完成。一般來說,將在構(gòu)造函數(shù)中進(jìn)行虛表的創(chuàng)建和虛表指針的初始化。
基類的虛函數(shù)表和派生類的虛函數(shù)表分別為保存在不同位置的兩個獨(dú)立數(shù)組,也就是說基類的隱藏成員和派生類的隱藏成員指向不同的地址。
如果派生類沒有重新定義基類的某個虛函數(shù)A,則派生類的虛函數(shù)表vtbl將保存基類的虛函數(shù)A的原始地址(此時(shí)派生類和基類的虛函數(shù)表中保存的虛函數(shù)A的地址是一樣的)。
如果派生類重寫了基類的某個虛函數(shù)B,則派生類的虛函數(shù)表vtbl將保存新的虛函數(shù)B的地址(此時(shí)的虛函數(shù)B其實(shí)有兩個版本,分別被基類和派生類的虛函數(shù)表分開保存)。

純虛函數(shù):
純虛函數(shù)是在基類中聲明的虛函數(shù),它在基類中沒有定義,要求任何派生類都要定義自己的實(shí)現(xiàn)方法。在基類中實(shí)現(xiàn)純虛函數(shù)的方法是在函數(shù)原型后加“=0”,即virtual void funtion1() = 0;
含有純虛函數(shù)的類為抽象類,不能聲明對象,只是作為基類為派生類服務(wù)。除非在派生類中完全實(shí)現(xiàn)基類的所有虛函數(shù),否則派生類也是抽象類,不能實(shí)例化對象。
抽象類不能定義對象,但是可以作為指針或引用類型使用。

不能聲明虛函數(shù):
常見的不能聲明為虛函數(shù)的有:普通函數(shù)(非成員函數(shù));靜態(tài)成員函數(shù);內(nèi)聯(lián)成員函數(shù);構(gòu)造函數(shù);友元函數(shù)。
普通函數(shù)(非成員函數(shù))只能被overload,不能被override,聲明為虛函數(shù)也沒有意義。
靜態(tài)成員函數(shù)對于每個類來說只有一份代碼,所有的對象都共享這一份代碼,也沒有要動態(tài)綁定的必要性。
內(nèi)聯(lián)函數(shù)在編譯時(shí)被展開,從而可減少函數(shù)調(diào)用花費(fèi)的代價(jià),虛函數(shù)是在運(yùn)行時(shí)才進(jìn)行動態(tài)綁定,從而使得繼承對象能夠準(zhǔn)確的執(zhí)行自己的動作。
構(gòu)造函數(shù)目的是為了生成對象時(shí)進(jìn)行對象初始化,虛函數(shù)目的是在不同類型的對象中調(diào)用不同的方法以產(chǎn)生不同的動作,當(dāng)對象還沒有生成時(shí),虛函數(shù)是沒有意義的,而構(gòu)造函數(shù)是為了在對象還沒有生成時(shí)實(shí)例化對象。
友元函數(shù)不支持繼承,對于沒有繼承特性的函數(shù)就沒有虛函數(shù)的說法。

早綁定和晚綁定:
c++編譯器在編譯的時(shí)候,要確定每個對象調(diào)用的函數(shù)(非虛函數(shù))的地址,這稱為早期綁定,當(dāng)我們將Son類對象的地址賦給指針pFather時(shí),C++編譯器進(jìn)行了類型轉(zhuǎn)換,此時(shí)C++編譯器認(rèn)為指針變量pFather保存的就是Father對象的地址,當(dāng)在main函數(shù)中執(zhí)行pFather->Say(),調(diào)用的是Father對象的Say函數(shù)。
從內(nèi)存角度看:

C++ 多態(tài)與虛函數(shù)、與構(gòu)造函數(shù)和析構(gòu)函數(shù)有什么聯(lián)系

前面輸出的結(jié)果是因?yàn)榫幾g器在編譯的時(shí)候,就已經(jīng)確定了對象調(diào)用的函數(shù)地址,要解決這個問題就要使用晚綁定,當(dāng)編譯器使用晚綁定時(shí)候,就會在運(yùn)行時(shí)再去確定對象的類型以及正確的調(diào)用函數(shù),而要讓編譯器采用晚綁定,就要在基類中聲明函數(shù)時(shí)使用virtual關(guān)鍵字.一旦某個函數(shù)在基類中聲明為virtual,那么在所有的派生類中該函數(shù)都是virtual,而不需要再顯式地聲明為virtual。

編譯器為每個對象提供了一個虛表指針(即vptr),這個指針指向了對象所屬類的虛表,在程序運(yùn)行時(shí),根據(jù)對象的類型去初始化vptr,從而讓vptr正確的指向了所屬類的虛表,從而在調(diào)用虛函數(shù)的時(shí)候,能夠找到正確的函數(shù)。由于pFather實(shí)際指向的對象類型是Son,因此vptr指向的Son類的vtable,當(dāng)調(diào)用pFather->Son()時(shí),根據(jù)虛表中的函數(shù)地址找到的就是Son類的Say()函數(shù)。
從內(nèi)存角度看:

在這里插入圖片描述

構(gòu)造函數(shù)與虛函數(shù)

構(gòu)造函數(shù)不能為虛函數(shù)。
從C++之父Bjarne的回答我們應(yīng)該知道C++為什么不支持構(gòu)造函數(shù)是虛函數(shù)了,簡單講就是沒有意義。
虛函數(shù)的作用在于通過子類的指針或引用來調(diào)用父類的那個成員函數(shù)。而構(gòu)造函數(shù)是在創(chuàng)建對象時(shí)自己主動調(diào)用的,不可能通過子類的指針或引用去調(diào)用。

構(gòu)造函數(shù)目的是為了生成對象時(shí)進(jìn)行對象初始化,虛函數(shù)目的是在不同類型的對象中調(diào)用不同的方法以產(chǎn)生不同的動作,當(dāng)對象還沒有生成時(shí),內(nèi)存中不存在虛函數(shù)指針和虛函數(shù)表,此時(shí)虛函數(shù)是沒有意義的,而構(gòu)造函數(shù)是為了在對象還沒有生成時(shí)實(shí)例化對象,如果構(gòu)造函數(shù)被聲明為虛函數(shù),內(nèi)存空間還沒有虛函數(shù)表,該構(gòu)造函數(shù)將變得沒有意義,所以構(gòu)造函數(shù)不能是虛函數(shù)。

析構(gòu)函數(shù)與虛函數(shù)

當(dāng)派生類指針指向用new運(yùn)算符生成的派生類對象時(shí),delete派生類指針,將執(zhí)行派生類的析構(gòu)函數(shù),再執(zhí)行基類的析構(gòu)函數(shù)。因?yàn)樵趯?shí)例化派生類對象時(shí),先實(shí)例了基類對象。
當(dāng)基類指針指向用new運(yùn)算符生成的派生類對象時(shí),delete基類指針,因?yàn)榫幾g器又進(jìn)行了類型轉(zhuǎn)換,默認(rèn)為基類指針指向基類對象的地址,根據(jù)早綁定中內(nèi)存的關(guān)系,如果基類析構(gòu)函數(shù)沒有聲明為虛函數(shù),將只執(zhí)行基類的構(gòu)造函數(shù),如果基類析構(gòu)函數(shù)聲明為虛函數(shù),盡管進(jìn)行類型轉(zhuǎn)換,根據(jù)晚綁定中內(nèi)存的關(guān)系,不管基類對象還是派生類對象都有相應(yīng)的虛函數(shù)表指針,因此析構(gòu)時(shí)會先調(diào)用派生類的析構(gòu)函數(shù)(vptr指向自身的析構(gòu)函數(shù)),再調(diào)用基類的析構(gòu)函數(shù)(聲明派生類對象先實(shí)例了基類對象)。

#include<iostream>
using namespace std;

class Base {
public:
    Base()
    { 
        cout<<"Base::Base()"<<endl;
        fun();
    }
    virtual ~Base()
    {
        cout<<"Base::~Base()"<<endl;
        fun();
    }
    virtual void fun()
    {
        cout<<"Base::fun() virtual"<<endl;
    }

};

class Derived:public Base
{
public:
    Derived()
    {
        cout<<"Derived::Derived()"<<endl;
        fun();;
    }
    ~Derived()
    {
        cout<<"Derived::~Derived()"<<endl;
        fun();
    }
    virtual void fun()
    {
        cout<<"Derived::fun() virtual"<<endl;
    }
};

int main()
{
	//basa
    Base *b = new Base();
    delete b;
    cout<<endl;
    /*
    Base::Base()
    Base::fun() virtual
    Base::~Base()
    Base::fun() virtual
    */ 

    
	//Derived
    Derived *d = new Derived();
    delete d;
    cout<<endl;
    /*
    Base::Base()
    Base::fun() virtual //派生類還不存在
    Derived::Derived()
    Derived::fun() virtual //派生類已存在
    Derived::~Derived()
    Derived::fun() virtual //派生類還存在
    Base::~Base()
    Base::fun() virtual //派生類不存在
    */


	//Base* Derived,父類指針指向子類對象的實(shí)現(xiàn)原理
    Base *bd = new Derived();
    delete bd;
    cout<<endl;
    /*
    Base::Base()
    Base::fun() virtual //派生類不存在
    Derived::Derived()
    Derived::fun() virtual //派生類已存在
    //當(dāng)基類析構(gòu)函數(shù)沒用聲明為虛函數(shù)時(shí),將不調(diào)用派生類的析構(gòu)函數(shù)
    Derived::~Derived()
    Derived::fun() virtual //派生類還存在
    Base::~Base()
    Base::fun() virtual //派生類不存在
    */

    system("pause");
    return 0;
}

感謝各位的閱讀,以上就是“C++ 多態(tài)與虛函數(shù)、與構(gòu)造函數(shù)和析構(gòu)函數(shù)有什么聯(lián)系”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對C++ 多態(tài)與虛函數(shù)、與構(gòu)造函數(shù)和析構(gòu)函數(shù)有什么聯(lián)系這一問題有了更深刻的體會,具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識點(diǎn)的文章,歡迎關(guān)注!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

c++
AI