面向对象三大特性之一:多态(C++)
目錄
多態(tài)的定義及實(shí)現(xiàn)
抽象類(lèi)
多態(tài)的原理
單繼承和多繼承關(guān)系的虛函數(shù)表
多態(tài)的定義及實(shí)現(xiàn)
1、什么是多態(tài)?
當(dāng)不同的對(duì)象去完成某個(gè)行為時(shí),會(huì)產(chǎn)生出不同的結(jié)果。多態(tài)是:不同繼承關(guān)系的類(lèi)對(duì)象去調(diào)用同一函數(shù)時(shí),產(chǎn)生了不同的行為。
例如:Student類(lèi)繼承了Person類(lèi)。 Person對(duì)象買(mǎi)票全價(jià),Student對(duì)象買(mǎi)票半價(jià)。這就是多態(tài)行為。
2、構(gòu)成多態(tài)的兩個(gè)必要條件
- 調(diào)用函數(shù)的對(duì)象必須是指針或者引用。?若不用指針或引用,則傳一個(gè)對(duì)象給基類(lèi)類(lèi)型(切片),父類(lèi)將自己的虛表改為子類(lèi)的虛表,不合理。
- 被調(diào)用的函數(shù)必須是虛函數(shù),且完成了虛函數(shù)的重寫(xiě)。
虛函數(shù):在類(lèi)的成員函數(shù)前面加關(guān)鍵字virtual。
虛函數(shù)的重寫(xiě):派生類(lèi)中有一個(gè)跟基類(lèi)的完全相同虛函數(shù),他們的函數(shù)名、參數(shù)、返回值都相同,我們就稱(chēng)子類(lèi)的虛函數(shù)重寫(xiě)了基類(lèi)的虛函數(shù)。另外虛函數(shù)的重寫(xiě)也叫作虛函數(shù)的覆蓋。
實(shí)現(xiàn)一個(gè)簡(jiǎn)單的多態(tài)例子:
class Person { public:virtual void BuyTicket(){cout << "買(mǎi)票全價(jià)" << endl;} }; class Student : public Person { public:virtual void BuyTicket(){cout << "半價(jià)買(mǎi)票" << endl;} };void Func(Person& people) {people.BuyTicket(); }void Test() {Person p;Func(p);Student s;Func(s); }3、重寫(xiě)
虛函數(shù)的重寫(xiě)中,派生類(lèi)中重寫(xiě)的成員函數(shù)可以不加virtual關(guān)鍵字,因?yàn)槔^承后基類(lèi)的虛函數(shù)被繼承下來(lái)了在派生類(lèi)依舊保持虛函數(shù)屬性,我們只是重寫(xiě)了它。但這是非常不規(guī)范的,我們平時(shí)不要這樣使用。
虛函數(shù)重寫(xiě)有一個(gè)例外:重寫(xiě)的虛函數(shù)的返回值可以不同,但必須分別是基類(lèi)指針和派生類(lèi)指針或者基類(lèi)引用和派生類(lèi)引用。這種行為叫做?協(xié)變。
//協(xié)變舉例 class A{}; class B : public A{};class Person { public:virtual A* f(){return new A;} }; class Student : public Person { public:virtual B* f(){return new B;} };析構(gòu)函數(shù)的重寫(xiě)問(wèn)題:若基類(lèi)的析構(gòu)函數(shù)被寫(xiě)成了虛函數(shù),那么繼承下來(lái)的派生類(lèi)中是否重寫(xiě)了析構(gòu)函數(shù)?這里他們看起來(lái)函數(shù)名不相同,違背了重寫(xiě)的規(guī)則,但其實(shí)可以理解為編譯器對(duì)析構(gòu)函數(shù)的重寫(xiě)進(jìn)行了特殊處理,編譯后析構(gòu)函數(shù)的名稱(chēng)統(tǒng)一處理成destructor。這也說(shuō)明了基類(lèi)的析構(gòu)函數(shù)最好寫(xiě)成虛函數(shù)。
class Person { public: virtual ~Person() { cout << "~Person()" << endl; } }; class Student : public Person { public: virtual ~Student(){ cout << "~Student()" << endl;} };// 只有派生類(lèi)Student的析構(gòu)函數(shù)重寫(xiě)了Person的析構(gòu)函數(shù),下面的delete對(duì)象調(diào)用析構(gòu)函數(shù),才能構(gòu)成多態(tài),才能保證p1和p2指向的對(duì)象正確的調(diào)用析構(gòu)函數(shù)。 int main() { Person* p1 = new Person; Person* p2 = new Student;delete p1; //這里希望通過(guò)對(duì)象調(diào)析構(gòu),所以用虛函數(shù)delete p2;//結(jié)果為:// ~Person()// ~Student()return 0; }為什么將析構(gòu)函數(shù)寫(xiě)成虛函數(shù)?如果析構(gòu)函數(shù)不使用virtual,使用動(dòng)態(tài)綁定,則在析構(gòu)的時(shí)候就會(huì)忽略掉派生類(lèi)的部分。若我們?cè)谂缮?lèi)中進(jìn)行了空間的開(kāi)辟,而在派生類(lèi)的析構(gòu)中對(duì)其進(jìn)行釋放,如過(guò)不調(diào)用派生類(lèi)析構(gòu),會(huì)造成內(nèi)存泄漏。
//基類(lèi)的析構(gòu)函數(shù)不是虛函數(shù)時(shí),兩個(gè)析構(gòu)函數(shù)沒(méi)有構(gòu)成多態(tài). //在析構(gòu)的時(shí)候,是根據(jù)類(lèi)型析構(gòu),而不是根據(jù)對(duì)象析構(gòu),忽略了派生類(lèi)的部分,會(huì)造成內(nèi)存泄漏。 class Person { public: ~Person() { cout << "~Person()" << endl; } }; class Student : public Person { public: ~Student(){ cout << "~Student()" << endl;} };int main() { Person* p1 = new Person; Person* p2 = new Student;delete p1; delete p2;//結(jié)果為:// ~Person()// ~Person() //只對(duì)基類(lèi)不部分進(jìn)行了析構(gòu),而并沒(méi)有對(duì)派生類(lèi)部分進(jìn)行析構(gòu)return 0; }4、接口繼承和實(shí)現(xiàn)繼承
實(shí)現(xiàn)繼承:普通函數(shù)的繼承是一種實(shí)現(xiàn)繼承,派生類(lèi)繼承了基類(lèi)函數(shù),可以使用函數(shù),繼承的是函數(shù)的實(shí)現(xiàn)。
接口繼承:虛函數(shù)的繼承是一種接口繼承,派生類(lèi)繼承的是基類(lèi)虛函數(shù)的接口,目的是為了重寫(xiě),達(dá)成多態(tài),繼承的是接口。所以如果不實(shí)現(xiàn)多態(tài),不要把函數(shù)定義成虛函數(shù)。
繼承達(dá)到的目的就是實(shí)現(xiàn)繼承,多態(tài)就是接口繼承。
5、重載、重寫(xiě)(覆蓋)、重定義(隱藏)的對(duì)比?
重載:
- 兩個(gè)函數(shù)在同一個(gè)作用域中。
- 函數(shù)名相同,參數(shù)個(gè)數(shù)或類(lèi)型不同。
重寫(xiě)(覆蓋):
- 兩個(gè)函數(shù)在兩個(gè)不同的作用域中。
- 函數(shù)名,參數(shù),返回值都相同(協(xié)變除外)。
- 兩個(gè)函數(shù)都必須是虛函數(shù)。
重定義(隱藏):
- 兩個(gè)函數(shù)在兩個(gè)不同的作用域中。
- 函數(shù)名相同。
- 上述條件成立后,如果不是重寫(xiě)就一定構(gòu)成了隱藏。
?
抽象類(lèi)
純虛函數(shù):在虛函數(shù)的后面寫(xiě)上 =0 ,則這個(gè)函數(shù)為純虛函數(shù)。只對(duì)函數(shù)進(jìn)行聲明,不實(shí)現(xiàn)。在派生類(lèi)中才對(duì)于函數(shù)進(jìn)行實(shí)現(xiàn)。
純虛函數(shù)的目的:強(qiáng)制重寫(xiě)虛函數(shù)。
抽象類(lèi):包含純虛函數(shù)的類(lèi)叫做抽象類(lèi)(也叫接口類(lèi))。
- 抽象類(lèi)不能實(shí)例化出對(duì)象,派生類(lèi)繼承后也不能實(shí)例化出對(duì)象,
- 當(dāng)派生類(lèi)中將繼承的抽象類(lèi)的純虛函數(shù)都重寫(xiě)實(shí)現(xiàn)了,才可以實(shí)例化出對(duì)象。
- 更體現(xiàn)出了接口繼承。
?
C++11還提供了override 和 ?nal 來(lái)修飾虛函數(shù):
override:
虛函數(shù)的意義就是實(shí)現(xiàn)多態(tài),如果沒(méi)有重寫(xiě),虛函數(shù)就沒(méi)有意義。所以C++11中使用了 純虛函數(shù) + override?的方式來(lái)強(qiáng)制重寫(xiě)虛函數(shù)。
override 修飾的派生類(lèi)虛函數(shù)沒(méi)有重寫(xiě)會(huì)編譯報(bào)錯(cuò)。
final:
final 修飾基類(lèi)的虛函數(shù)不能被派生類(lèi)重寫(xiě) 。
?
多態(tài)的原理
1、虛函數(shù)表(虛表)
虛函數(shù)表本質(zhì)是一個(gè)存放 虛函數(shù)指針 的指針數(shù)組,這個(gè)數(shù)組最后面放了一個(gè)nullptr。虛函數(shù)指針就是類(lèi)中虛函數(shù)的地址,這些虛函數(shù)的地址存放在這個(gè)指針數(shù)組中。
類(lèi)對(duì)象中有著一個(gè)隱藏的成員指針_vfptr,我們叫做虛函數(shù)表指針,這個(gè)指針指向虛函數(shù)表。
派生類(lèi)的虛表生成:
? ? ? a.先將基類(lèi)中的虛表內(nèi)容拷貝一份到派生類(lèi)虛表中
? ? ? b.如果派生類(lèi)重寫(xiě)了基類(lèi)中某個(gè)虛函數(shù),用派生類(lèi)自己的虛函數(shù)覆蓋虛表中基類(lèi)的虛函數(shù)
? ? ? c.派生類(lèi)自己新增加的虛函數(shù)按其在派生類(lèi)中的聲明次序增加到派生類(lèi)虛表的最后
虛函數(shù)存在哪??
? ? ? 虛函數(shù)和普通函數(shù)一樣的,都是存在代碼段的,只是他的指針又存到了虛表中。
虛表存在哪?
? ? ? 在vs下驗(yàn)證后發(fā)現(xiàn)虛表存在代碼段。
虛函數(shù)表指針存在哪?
? ? ? 虛函數(shù)指針是存放在對(duì)象中,所以虛函數(shù)指針的位置是跟著對(duì)象的位置走的對(duì)象在棧上被創(chuàng)建,虛函數(shù)指針存在于棧上;對(duì)象被創(chuàng)建在堆上,虛函數(shù)指針就存在于堆上。
2、多態(tài)的原理
實(shí)際上的多態(tài)就是不同的對(duì)象,在調(diào)用時(shí)查找其虛函數(shù)表,找到要調(diào)用的函數(shù)。
在派生類(lèi)中,派生類(lèi)的虛函數(shù)表已完成了重寫(xiě),所以盡管調(diào)用的是同一個(gè)函數(shù),但虛表卻不同,完成的是不同的動(dòng)作,展現(xiàn)出不同的形態(tài)。
滿(mǎn)足多態(tài)后的函數(shù)調(diào)用,不是在編譯時(shí)確定的,是運(yùn)行起來(lái)以后到對(duì)象的中去找的。不滿(mǎn)足多態(tài)的函數(shù)調(diào)用,是編譯時(shí)確認(rèn)好的。
3、動(dòng)態(tài)綁定與靜態(tài)綁定?
1)?靜態(tài)綁定(前期綁定/早綁定):在程序編譯期間確定了程序的行為,也稱(chēng)為靜態(tài)多態(tài),比如:函數(shù)重載
2)?動(dòng)態(tài)綁定(后期綁定/晚綁定):是在程序運(yùn)行期間,根據(jù)具體拿到的類(lèi)型確定程序的具體行為,調(diào)用具體的函數(shù),也稱(chēng)為動(dòng)態(tài)多態(tài)。
?
單繼承和多繼承關(guān)系的虛函數(shù)表
1、單繼承下的虛函數(shù)表
- 派生類(lèi)的虛函數(shù)表先存放拷貝自基類(lèi)的虛函數(shù)表,并用重寫(xiě)的函數(shù)將基類(lèi)對(duì)應(yīng)的函數(shù)覆蓋。在表的后面,按派生類(lèi)自己的聲明順序,加入自己的虛函數(shù)地址。
- 在vs的編譯器下監(jiān)視的窗口中在虛函數(shù)表中無(wú)法看到派生類(lèi)自己的虛函數(shù),可以通過(guò)打印虛函數(shù)表看到。
- 虛函數(shù)表指針存放在對(duì)象的前四個(gè)字節(jié),以nullptr結(jié)尾,相當(dāng)于一個(gè)函數(shù)指針數(shù)組 。
2、多繼承下的虛函數(shù)表
- 多繼承派生類(lèi)的未重寫(xiě)的虛函數(shù)放在第一個(gè)繼承基類(lèi)部分的虛函數(shù)表中
?
?
?
?
?
?
?
?
?
總結(jié)
以上是生活随笔為你收集整理的面向对象三大特性之一:多态(C++)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Linux——进程信号(总结)
- 下一篇: s3c2440移植MQTT