C++多態(tài)的全面講解
1.多態(tài)的定義和實(shí)現(xiàn)
多態(tài)的淺層理解
多態(tài),即多種形態(tài),也就是說,不同的對(duì)象在完成某個(gè)行為時(shí)會(huì)產(chǎn)生不同的狀態(tài)。
舉個(gè)例子,買火車票時(shí),普通人正常買票,學(xué)生半價(jià)買票,軍人優(yōu)先買票。
在C++中,多態(tài)就是對(duì)于同一個(gè)函數(shù),當(dāng)調(diào)用的對(duì)象不同,他的操作也不同。
多態(tài)的構(gòu)成條件
多態(tài)是繼承體系中的一個(gè)行為,如果要在繼承體系中構(gòu)成多態(tài),需要滿足兩個(gè)條件:
1. 必須通過基類的指針或者引用調(diào)用虛函數(shù)
2. 被調(diào)用的函數(shù)必須是虛函數(shù),并且派生類必須要對(duì)繼承的基類的虛函數(shù)進(jìn)行重寫
解釋1:因?yàn)樽宇惡透割惖奶摫砀髯砸环荩热裟軌蛲ㄟ^對(duì)象傳遞的方式同時(shí)傳遞虛表的話,那么父類就可能拿到子類的虛表,不合理。
解釋2:有虛函數(shù)就有虛函數(shù)表,對(duì)象當(dāng)中就會(huì)存放一個(gè)虛基表指針,通過虛基表指針指向的內(nèi)容來訪問對(duì)應(yīng)的函數(shù)。若子類沒有重寫父類的虛函數(shù)內(nèi)容,則子類也會(huì)調(diào)用父類的函數(shù)。
2.虛函數(shù)
虛函數(shù)就是被virtual修飾的類成員函數(shù)(這里的virtual和虛繼承的virtual雖然是同一個(gè)關(guān)鍵字,但是作用不一樣)
class Person { public: virtual void func() { cout << "普通人->正常買票" << endl; } };
虛函數(shù)的重寫規(guī)則
虛函數(shù),即被virtual關(guān)鍵字重寫的類成員函數(shù)。
重寫(覆蓋):派生類中有一個(gè)跟基類中完全相同的虛函數(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é)生->半價(jià)買票" << endl; } }; //必須是父類的指針或引用去調(diào)用虛函數(shù) //這里的參數(shù)類型不能是對(duì)象,否則是一份臨時(shí)拷貝,則無法構(gòu)成多態(tài) void F(Person& ps) { ps.func(); } int main() { Person ps; Student st; F(ps); F(st); return 0; }
筆試選擇題??键c(diǎn)(選自Effective C++):
如果不滿足虛函數(shù)重寫的條件,例如參數(shù)不同則會(huì)變成重定義。
思考如下代碼輸出結(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,
因?yàn)槿绻宇愔貙懥巳笔≈?,此時(shí)的子類的缺省值是無效的,使用的還是父類的缺省值。原因是因?yàn)槎鄳B(tài)是動(dòng)態(tài)綁定,而缺省值是靜態(tài)綁定。對(duì)于P1,他的靜態(tài)類型也就是這個(gè)指針的類型是Base,所以這里的缺省值是Base的缺省值,而動(dòng)態(tài)類型也就是指向的對(duì)象是Base1,所以這里調(diào)用的虛函數(shù)則是Base1中的虛函數(shù),所以這里就是Base1中的虛函數(shù),Base中的缺省值,也就是Base1:10。
簡(jiǎn)單概括就是:虛函數(shù)的重寫只重寫函數(shù)實(shí)現(xiàn),不重寫缺省值。
虛函數(shù)重寫條件的兩個(gè)例外
1.協(xié)變(返回值不同)
當(dāng)基類和派生類的返回值類型不同時(shí),如果基類對(duì)象返回基類對(duì)象的引用或者指針,派生類對(duì)象也返回的是派生類對(duì)象的引用或者指針時(shí),就會(huì)引起協(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)成重寫,因?yàn)檎驹诰幾g器的視角,他所調(diào)用的析構(gòu)函數(shù)名稱都叫做destructor
為什么編譯器要通過這種方式讓析構(gòu)函數(shù)也能構(gòu)成重寫呢?
假設(shè)我用一個(gè)基類指針或者引用指向派生類對(duì)象,如果不構(gòu)成多態(tài)會(huì)怎樣?
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; }
上述代碼只會(huì)調(diào)用Human的析構(gòu)函數(shù),即如果不構(gòu)成多態(tài),那么指針是什么類型的,就會(huì)調(diào)用什么類型的析構(gòu)函數(shù),這也就導(dǎo)致了一種情況,如果派生類的析構(gòu)函數(shù)中有資源釋放,而這里卻沒有釋放掉那些資源,就會(huì)導(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)鍵字是用來檢測(cè)派生類虛函數(shù)是否構(gòu)成重寫的關(guān)鍵字。
如基類虛函數(shù)沒有virtual或者派生類虛函數(shù)名拼錯(cuò)等問題,這些問題不會(huì)被編譯器檢查出來,發(fā)生錯(cuò)誤時(shí)也很難一下子鎖定,所以C++增添了override這一層保險(xiǎn),當(dāng)修飾的虛函數(shù)不構(gòu)成重寫時(shí)就會(huì)編譯錯(cuò)誤。
class A { public: virtual void func() {} }; class B : public A { public: //未重寫則報(bào)錯(cuò) virtual void func() override {}; };
final
使用final修飾的虛函數(shù)不能被重寫。
如果某一個(gè)虛函數(shù)不想被派生類重寫,就可以用final來修飾這個(gè)虛函數(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; } };
重載對(duì)比重定義(隱藏)與重寫(覆蓋)
4.抽象類
虛函數(shù)后面加上=0就是純虛函數(shù),包含純虛函數(shù)的類即為抽象類(接口類)。抽象類不能實(shí)例化出對(duì)象,派生類繼承抽象類后若沒有重寫純虛函數(shù)那么仍為抽象類,亦不能實(shí)例化出對(duì)象。純虛函數(shù)規(guī)范了派生類必須重寫虛函數(shù),并且更加體現(xiàn)出了接口繼承。
抽象類就像是一個(gè)藍(lán)圖,為派生類描述好一個(gè)大概的架構(gòu),派生類必須實(shí)現(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; } };
接口繼承與實(shí)現(xiàn)繼承
普通函數(shù)的繼承就是接口繼承,派生類可以使用基類的函數(shù);而虛函數(shù)的重寫則是實(shí)現(xiàn)繼承,派生類繼承的僅僅是基類的函數(shù)接口,目的是為了重寫基類虛函數(shù)的函數(shù)體,達(dá)成多態(tài)。因此如果不實(shí)現(xiàn)多態(tài),則不要將函數(shù)定義為虛函數(shù)。
到此這篇關(guān)于C++多態(tài)的全面講解的文章就介紹到這了,更多相關(guān)C++多態(tài)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(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)象的原因及解決方法,具有一定的參考價(jià)值,感興趣的可以了解一下2023-10-10c語言?數(shù)據(jù)存儲(chǔ)與原碼?反碼?補(bǔ)碼詳細(xì)解析
不知道你是否和我一樣好奇,學(xué)習(xí)編程語言的同時(shí)想,各個(gè)數(shù)據(jù)類型是怎樣在我們的內(nèi)存中儲(chǔ)存的呢,如果你仔細(xì)深入了解的話,你會(huì)了解其中的樂趣,了解科學(xué)家們的偉大,了解c語言2022-02-02關(guān)于嘗試開發(fā)PHP的MYSQL擴(kuò)展的使用
本篇文章小編將為大家介紹,關(guān)于嘗試開發(fā)PHP的MYSQL擴(kuò)展的使用,需要的朋友可以參考一下2013-04-04