深入解析C++中多態(tài)性和虛函數(shù)使用原理
1.為什么需要virtual
按照J(rèn)ava的思維方式,在有了繼承和向上類型轉(zhuǎn)換(upcasting)之后,就可以實現(xiàn)多態(tài)性了。但是在C++中似乎并不能orz??紤]這種情況:
#include<iostream> using std::cout; using std::endl; class A{ public: void f() const{ cout<<"class A's function"<<endl; } }; class B : public A{ public: void f() const{ cout<<"class B's function"<<endl; } }; int main(){ B b; A *ptr_a = &b; A &ref_a = b; ptr_a->f();//print: class A's function ref_a.f();//print: Class A's function }
在使用基類指針或引用調(diào)用一個派生類對象的函數(shù)時,我們發(fā)現(xiàn)程序仍然在調(diào)用基類的函數(shù),要想解決這種情況,就要引入virtual關(guān)鍵字,將上面代碼里的class A修改如下,main中的輸出就變成類B中f()的輸出了。
class A{ public: virtual void f() const{ cout<<"class A's function"<<endl; } };
那么為什么Java不需要呢?因為virtual關(guān)鍵字實現(xiàn)功能的同時,會增加該類一些操作的時間和空間占用,C++將這部分占用的優(yōu)化決定權(quán)交給了程序員,以實現(xiàn)可能的效率提高;而Java內(nèi)置了virtual的機制,沒有提高效率的選擇,但是簡化了編程。(關(guān)于virtual的具體機制,建議參考Thinking in C++)
有兩點需要注意的:
- 第一、當(dāng)使用基類指針指向派生類時,無法通過基類指針直接調(diào)用派生類中增加的函數(shù)(基類中沒有同名虛函數(shù)),除非將基類指針強制類型轉(zhuǎn)換為派生類指針。
- 第二、只能通過基類指針或者引用來調(diào)用派生類對象,如果我們將一個派生類對象通過值傳遞的方式傳遞給基類對象,這個對象被真的切成一個基類對象,而不具有任何派生類的內(nèi)容。
2.純虛函數(shù)和抽象類
在類設(shè)計時,常常希望基類僅僅作為派生類的一個接口,被繼承實現(xiàn),而不會去創(chuàng)建基類對象,這時,可以在基類中定義純虛函數(shù),使其成為一個抽象類。定義純虛函數(shù)語法是在一個虛函數(shù)聲名的基礎(chǔ)上,加上=0。例如:virtual void f() = 0;
注意:當(dāng)繼承一個抽象類時,必須實現(xiàn)其所有的純虛函數(shù),否則繼承出的類也是一個抽象類。
一般情況下,在基類中我們不會對純虛函數(shù)進(jìn)行實現(xiàn),但是C++提供了實現(xiàn)純虛函數(shù)的機制,這種方法可以讓我們定義一段公共代碼,使派生類可以公用。
class A{ public: virtual void do() = 0; }; /* *純虛函數(shù)不能作為inline函數(shù)實現(xiàn),要放在類外! */ void A::do(){ //一些公共代碼 } class B : public A{ public: void do() { A::do(); //其他代碼 } };
3.構(gòu)造函數(shù)與虛函數(shù)
如上文所說,定義一個虛函數(shù)時,需要做一些額外的工作,完成這些工作的代碼其實被秘密插入到類構(gòu)造函數(shù)的開頭部分。那么就有一個問題,如果我們在構(gòu)造函數(shù)中調(diào)用虛函數(shù)會發(fā)生什么現(xiàn)象?答案是,會調(diào)用這個虛函數(shù)的本地版本,即虛函數(shù)機制在構(gòu)造函數(shù)中不工作。
另外,構(gòu)造函數(shù)也不能被定義為虛函數(shù)。
4.虛析構(gòu)函數(shù)與純虛析構(gòu)函數(shù)
構(gòu)造函數(shù)不能被定義為虛函數(shù),而析構(gòu)函數(shù)可以,并且經(jīng)常被定義為虛函數(shù)。
#include<iostream> using namespace std; class Base1{ public: ~Base1(){cout<<"~Base1()"<<endl;} }; class Base2{ public: virtual ~Base2(){cout<<"~Base2()"<<endl;} }; class Derived1 : public Base1{ public: ~Derived1(){cout<<"~Derived1()"<<endl;} }; class Derived2 : public Base2{ public: ~Derived2(){cout<<"~Derived2()"<<endl;} }; int main(){ Base1* pd1 = new Derived1(); Base2* pd2 = new Derived2(); delete pd1; delete pd2; }
上面代碼的控制臺輸出:
~Base1()
~Derived2()
~Base2()
上面的代碼暴露出在使用多態(tài)性時,不把析構(gòu)函數(shù)定義成虛函數(shù)所帶來的影響。這種錯誤不會立刻使程序崩潰,但是它不知不覺中使內(nèi)存泄漏。
5.純虛析構(gòu)函數(shù)的應(yīng)用
在一些時候,我們需要定義一個抽象類,但是剛好沒有其他純虛函數(shù),這時候我們不妨將析構(gòu)函數(shù)定義為純虛的,因為作為基類的析構(gòu)函數(shù)本來就要求為虛函數(shù),將其進(jìn)一步定義為純虛函數(shù)并無太大不同。唯一需要注意的是,定義純虛析構(gòu)函數(shù)時必須為其提供函數(shù)體,如下。
class A{ public: virtual ~A() = 0; }; A::~A(){ } class B:public A{ //不一定需要重定義析構(gòu)函數(shù),根據(jù)需要 }
還要注意一點,在析構(gòu)函數(shù)中,虛機制也是不存在的,可通過下面的代碼體會。
#include<iostream> using namespace std; class Base{ public: virtual ~Base(){cout<<"~Base()"<<endl;f();} virtual void f(){cout<<"Base::f()"<<endl;} }; class Derived : public Base{ public: ~Derived(){cout<<"~Derived()"<<endl;} void f(){cout<<"Derived::f()"<<endl;} }; int main(){ Base * pb = new Derived(); delete pb; }
控制臺輸出為:
~Derived()
~Base()
Base::f()
以上就是深入解析C++中多態(tài)性和虛函數(shù)使用原理的詳細(xì)內(nèi)容,更多關(guān)于C++ 多態(tài)性虛函數(shù)的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C語言使用mciSendString實現(xiàn)播放音樂功能
mciSendString?支持?mp3、wma、wav、mid?等多種媒體格式,使用非常簡單。這篇文章就來為大家介紹一下C語言如何使用mciSendString實現(xiàn)播放音樂功能,需要的可以參考一下2023-02-02Qt?TCP網(wǎng)絡(luò)通信學(xué)習(xí)
用于數(shù)據(jù)傳輸?shù)牡蛯泳W(wǎng)絡(luò)協(xié)議,多個物聯(lián)網(wǎng)協(xié)議都是基于TCP協(xié)議的,這篇文章為大家介紹了Qt?TCP網(wǎng)絡(luò)通信,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-08-08Ubuntu 20.04 下安裝配置 VScode 的 C/C++ 開發(fā)環(huán)境(圖文教程)
這篇文章主要介紹了Ubuntu 20.04 下安裝配置 VScode 的 C/C++ 開發(fā)環(huán)境,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-05-05