日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪(fǎng)問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > c/c++ >内容正文

c/c++

面向对象三大特性之一:多态(C++)

發(fā)布時(shí)間:2025/3/19 c/c++ 18 豆豆
生活随笔 收集整理的這篇文章主要介紹了 面向对象三大特性之一:多态(C++) 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

目錄

多態(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)出了接口繼承。
class A { public:virtual void Func() = 0; //只聲明,不實(shí)現(xiàn) }; class A1 : public A { public:virtual void Func() //繼承后實(shí)現(xiàn){cout << "A1" << endl;} }; class A2 : public A { public:virtual void Func() //繼承后實(shí)現(xiàn){cout << "A2" << endl;} };int main() {A* a1 = new A1;A* a2 = new A2;a1->Func();a2->Func();return 0; }

?

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)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。