C++多態(tài)的全面講解
1.多態(tài)的定義和實現(xiàn)
多態(tài)的淺層理解
多態(tài),即多種形態(tài),也就是說,不同的對象在完成某個行為時會產(chǎn)生不同的狀態(tài)。
舉個例子,買火車票時,普通人正常買票,學(xué)生半價買票,軍人優(yōu)先買票。
在C++中,多態(tài)就是對于同一個函數(shù),當調(diào)用的對象不同,他的操作也不同。
多態(tài)的構(gòu)成條件
多態(tài)是繼承體系中的一個行為,如果要在繼承體系中構(gòu)成多態(tài),需要滿足兩個條件:
1. 必須通過基類的指針或者引用調(diào)用虛函數(shù)
2. 被調(diào)用的函數(shù)必須是虛函數(shù),并且派生類必須要對繼承的基類的虛函數(shù)進行重寫
解釋1:因為子類和父類的虛表各自一份,倘若能夠通過對象傳遞的方式同時傳遞虛表的話,那么父類就可能拿到子類的虛表,不合理。
解釋2:有虛函數(shù)就有虛函數(shù)表,對象當中就會存放一個虛基表指針,通過虛基表指針指向的內(nèi)容來訪問對應(yīng)的函數(shù)。若子類沒有重寫父類的虛函數(shù)內(nèi)容,則子類也會調(diào)用父類的函數(shù)。
2.虛函數(shù)
虛函數(shù)就是被virtual修飾的類成員函數(shù)(這里的virtual和虛繼承的virtual雖然是同一個關(guān)鍵字,但是作用不一樣)
class Person { public: virtual void func() { cout << "普通人->正常買票" << endl; } };
虛函數(shù)的重寫規(guī)則
虛函數(shù),即被virtual關(guān)鍵字重寫的類成員函數(shù)。
重寫(覆蓋):派生類中有一個跟基類中完全相同的虛函數(shù)(三同:即派生類虛函數(shù)與基類虛函數(shù)的返回值類型、函數(shù)名字、參數(shù)列表完全相同,也有例外!),這樣則稱子類重寫了父類的虛函數(shù)。
示例代碼如下:
class Person { public: virtual void func() { cout << "普通人->正常買票" << endl; } }; class Student : public Person { public: //子類必須重寫父類的虛函數(shù) virtual void func() { cout << "學(xué)生->半價買票" << endl; } }; //必須是父類的指針或引用去調(diào)用虛函數(shù) //這里的參數(shù)類型不能是對象,否則是一份臨時拷貝,則無法構(gòu)成多態(tài) void F(Person& ps) { ps.func(); } int main() { Person ps; Student st; F(ps); F(st); return 0; }
筆試選擇題??键c(選自Effective C++):
如果不滿足虛函數(shù)重寫的條件,例如參數(shù)不同則會變成重定義。
思考如下代碼輸出結(jié)果:
#include <iostream> class Base{ public: virtual void Show(int n = 10)const{ //提供缺省參數(shù)值 std::cout << "Base:" << n << std::endl; } }; class Base1 : public Base{ public: virtual void Show(int n = 20)const{ //重新定義繼承而來的缺省參數(shù)值 std::cout << "Base1:" << n << std::endl; } }; int main(){ Base* p1 = new Base1; p1->Show(); return 0; }
輸出的是Base1:10,
因為如果子類重寫了缺省值,此時的子類的缺省值是無效的,使用的還是父類的缺省值。原因是因為多態(tài)是動態(tài)綁定,而缺省值是靜態(tài)綁定。對于P1,他的靜態(tài)類型也就是這個指針的類型是Base,所以這里的缺省值是Base的缺省值,而動態(tài)類型也就是指向的對象是Base1,所以這里調(diào)用的虛函數(shù)則是Base1中的虛函數(shù),所以這里就是Base1中的虛函數(shù),Base中的缺省值,也就是Base1:10。
簡單概括就是:虛函數(shù)的重寫只重寫函數(shù)實現(xiàn),不重寫缺省值。
虛函數(shù)重寫條件的兩個例外
1.協(xié)變(返回值不同)
當基類和派生類的返回值類型不同時,如果基類對象返回基類對象的引用或者指針,派生類對象也返回的是派生類對象的引用或者指針時,就會引起協(xié)變。協(xié)變也能完成虛函數(shù)的重寫
例1:指針
class A {}; class B :public A {}; class Person { public: virtual A* func() { cout << "virtual A* func()" << endl; return new A; } }; class Student : public Person { public: virtual B* func() { cout << "virtual B* func()" << endl; return new B; } };
例2:引用
class Human { public: virtual Human& print() { cout << "i am a human" << endl; return *this; } }; class Student : public Human { public: virtual Student& print() { cout << "i am a student" << endl; return *this; } };
2.析構(gòu)函數(shù)的重寫(函數(shù)名不同)
析構(gòu)函數(shù)雖然函數(shù)名不同,但是也能構(gòu)成重寫,因為站在編譯器的視角,他所調(diào)用的析構(gòu)函數(shù)名稱都叫做destructor
為什么編譯器要通過這種方式讓析構(gòu)函數(shù)也能構(gòu)成重寫呢?
假設(shè)我用一個基類指針或者引用指向派生類對象,如果不構(gòu)成多態(tài)會怎樣?
class Human { public: ~Human() { cout << "~Human()" << endl; } }; class Student : public Human { public: ~Student() { cout << "~Student()" << endl; } }; int main() { Human* h = new Student; delete h; return 0; }
上述代碼只會調(diào)用Human的析構(gòu)函數(shù),即如果不構(gòu)成多態(tài),那么指針是什么類型的,就會調(diào)用什么類型的析構(gòu)函數(shù),這也就導(dǎo)致了一種情況,如果派生類的析構(gòu)函數(shù)中有資源釋放,而這里卻沒有釋放掉那些資源,就會導(dǎo)致內(nèi)存泄漏的問題。
所以為了防止這種情況,必須要將析構(gòu)函數(shù)定義為虛函數(shù)。這也就是編譯器將析構(gòu)函數(shù)重命名為destructor的原因
class Human { public: virtual ~Human() { cout << "~Human()" << endl; } }; class Student : public Human { public: virtual ~Student() { cout << "~Student()" << endl; } }; int main() { Human* h = new Student; delete h; return 0; }
3.C++11 override 和 final
override
override關(guān)鍵字是用來檢測派生類虛函數(shù)是否構(gòu)成重寫的關(guān)鍵字。
如基類虛函數(shù)沒有virtual或者派生類虛函數(shù)名拼錯等問題,這些問題不會被編譯器檢查出來,發(fā)生錯誤時也很難一下子鎖定,所以C++增添了override這一層保險,當修飾的虛函數(shù)不構(gòu)成重寫時就會編譯錯誤。
class A { public: virtual void func() {} }; class B : public A { public: //未重寫則報錯 virtual void func() override {}; };
final
使用final修飾的虛函數(shù)不能被重寫。
如果某一個虛函數(shù)不想被派生類重寫,就可以用final來修飾這個虛函數(shù)
class Human { public: virtual void print() final { cout << "i am a human" << endl; } }; class Student : public Human { public: virtual void print() { cout << "i am a student" << endl; } };
重載對比重定義(隱藏)與重寫(覆蓋)
4.抽象類
虛函數(shù)后面加上=0就是純虛函數(shù),包含純虛函數(shù)的類即為抽象類(接口類)。抽象類不能實例化出對象,派生類繼承抽象類后若沒有重寫純虛函數(shù)那么仍為抽象類,亦不能實例化出對象。純虛函數(shù)規(guī)范了派生類必須重寫虛函數(shù),并且更加體現(xiàn)出了接口繼承。
抽象類就像是一個藍圖,為派生類描述好一個大概的架構(gòu),派生類必須實現(xiàn)完這些架構(gòu),至于要在這些架構(gòu)上面做些什么,增加什么,就屬于派生類自己的問題。
class Human { public: virtual void print() = 0; }; class Student : public Human { public: virtual void print() { cout << "i am a student" << endl; } }; class Teacher : public Human { public: virtual void print() { cout << "i am a teacher" << endl; } };
接口繼承與實現(xiàn)繼承
普通函數(shù)的繼承就是接口繼承,派生類可以使用基類的函數(shù);而虛函數(shù)的重寫則是實現(xiàn)繼承,派生類繼承的僅僅是基類的函數(shù)接口,目的是為了重寫基類虛函數(shù)的函數(shù)體,達成多態(tài)。因此如果不實現(xiàn)多態(tài),則不要將函數(shù)定義為虛函數(shù)。
到此這篇關(guān)于C++多態(tài)的全面講解的文章就介紹到這了,更多相關(guān)C++多態(tài)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Qt出現(xiàn)假死凍結(jié)現(xiàn)象的原因及解決方法
應(yīng)用程序出現(xiàn)假死或凍結(jié)現(xiàn)象通常是由于一些常見問題所導(dǎo)致的,本文主要介紹了Qt出現(xiàn)假死凍結(jié)現(xiàn)象的原因及解決方法,具有一定的參考價值,感興趣的可以了解一下2023-10-10c語言?數(shù)據(jù)存儲與原碼?反碼?補碼詳細解析
不知道你是否和我一樣好奇,學(xué)習(xí)編程語言的同時想,各個數(shù)據(jù)類型是怎樣在我們的內(nèi)存中儲存的呢,如果你仔細深入了解的話,你會了解其中的樂趣,了解科學(xué)家們的偉大,了解c語言2022-02-02關(guān)于嘗試開發(fā)PHP的MYSQL擴展的使用
本篇文章小編將為大家介紹,關(guān)于嘗試開發(fā)PHP的MYSQL擴展的使用,需要的朋友可以參考一下2013-04-04