網(wǎng)站怎么做白色字蘇州吳中區(qū)seo關(guān)鍵詞優(yōu)化排名
今天我們來談?wù)凜++的繼承與多態(tài)😊😊😊,本篇的關(guān)鍵內(nèi)容如下:
- 繼承的本質(zhì)及其原理
- 派生類的構(gòu)造和析構(gòu)過程
- 重載、隱藏和覆蓋
- 類的向下或向上轉(zhuǎn)型
- 靜態(tài)綁定與動(dòng)態(tài)綁定
- 虛函數(shù)對類的影響
- 虛析構(gòu)函數(shù)
下面,我們將對這幾個(gè)有關(guān)于C++繼承與多態(tài)的關(guān)鍵內(nèi)容進(jìn)行詳述的論述
淺談C++的繼承與多態(tài)
- 一、繼承的本質(zhì)及其原理
- 二、派生類的構(gòu)造和析構(gòu)過程
- 三、重載、隱藏和覆蓋
- 四、類的向下或向上轉(zhuǎn)型
- 五、靜態(tài)綁定與動(dòng)態(tài)綁定
- **一個(gè)問題:thinking::thinking::thinking::一個(gè)類聲明了虛函數(shù)后,當(dāng)調(diào)用該虛函數(shù)時(shí),一定是動(dòng)態(tài)綁定嗎?** **不一定 !**
- 六、虛析構(gòu)函數(shù)
- **什么時(shí)候把基類的析構(gòu)函數(shù)聲明為虛函數(shù)呢?**
- 七、虛函數(shù)對類的影響
一、繼承的本質(zhì)及其原理
在C++中,繼承是一種面向?qū)ο缶幊趟枷氲母拍?#xff0c;允許一個(gè)類繼承另一個(gè)類的屬性和方法,同時(shí)也可以在此基礎(chǔ)上添加新的屬性和方法。究其本質(zhì),即:
- 代碼的復(fù)用
- 在基類中給所有派生類提供一個(gè)統(tǒng)一的接口,讓派生類重寫,滿足開閉原則
二、派生類的構(gòu)造和析構(gòu)過程
在定義的派生類中,由于派生類是從基類的基礎(chǔ)上繼承過來的,對應(yīng)繼承而來的成員的初始化和清理是由基類的構(gòu)造函數(shù)和析構(gòu)函數(shù)負(fù)責(zé),而派生類的構(gòu)造和析構(gòu)函數(shù)則負(fù)責(zé)初始化和清理派生類中特定的部分,所以在派生類調(diào)用構(gòu)造函數(shù)需要初始化基類里的成員變量時(shí),不能直接指定,而是需要通過基類的構(gòu)造函數(shù)來進(jìn)行初始化,如下:
class Base
{
public:Base(int d) :ma(d) { cout << "Base()" << endl; }~Base() { cout << "~Base()" << endl; }
protected:int ma;
};class Derive :public Base
{
public://Derive(int d = 20) :ma(d), mb(d) {cout << "Derive()" << endl;}//此句會出現(xiàn)報(bào)錯(cuò):"ma" 不是類 "Derive" 的非靜態(tài)數(shù)據(jù)成員或基類Derive(int d = 20) :Base(d), mb(d) {cout << "Derive()" << endl;}~Derive() { cout << "~Derive()" << endl; }
private:int mb;
};
這里我們給出一段關(guān)于派生類構(gòu)造和析構(gòu)過程的主要描述:
- 派生類調(diào)用基類的構(gòu)造函數(shù),初始化從基類繼承而來的成員
- 調(diào)用派生類自己的構(gòu)造函數(shù),初始化派生類自己特有的成員
- 調(diào)用派生類的析構(gòu)函數(shù),釋放派生類成員可能占用的外部資源(堆空間、文件等)
- 調(diào)用基類的構(gòu)造函數(shù),釋放派生類內(nèi)存中從基類繼承而來的成員可以占用的外部資源(堆空間、文件等)
int main()
{Derive m(20); //可以自己允許一遍,查看程序允許結(jié)果return 0;
}
三、重載、隱藏和覆蓋
重載: 一組函數(shù)重載,必須處在同一個(gè)作用域中,且函數(shù)名相同,參數(shù)列表不同
隱藏: 隱藏其實(shí)就是隱藏作用域,即,在繼承結(jié)構(gòu)中,派生類的同名成員,把基類的同名成員隱藏掉了
覆蓋: 如果派生類中定義的方法與在基類繼承而來的方法,在返回值、函數(shù)名、參數(shù)列表都相同的前提下,且基類的實(shí)現(xiàn)方法為virtual
虛函數(shù),那么派生類的這個(gè)方法就會被自動(dòng)的處理成虛函數(shù),本質(zhì)上是虛函數(shù)表上的虛函數(shù)地址的覆蓋,詳見后文!
若我們在上面的Base
類和Derive
中添加方法 void show()
,在派生類調(diào)用show
函數(shù)時(shí)會優(yōu)先在自己類中尋找對應(yīng)的函數(shù)方法,即會打印Derive::show()
,基類的show
方法被隱藏了
class Base
{...void show() { cout << "Base::show()" << endl; }...
};
class Derive :public Base
{void show() { cout << "Derive::show()" << endl; }...
};int main()
{Derive m(20);m.show(); //優(yōu)先找派生類自己show成員return 0;
}
四、類的向下或向上轉(zhuǎn)型
在C++中,派生類對象轉(zhuǎn)化為基類對象、基類對象轉(zhuǎn)化為派生類對象的情形可以描述為:
- 向上轉(zhuǎn)型: 派生類對象(指針) -> 基類對象(指針) YES
- 向下轉(zhuǎn)型: 基類對象(指針) -> 派生類對象(指針) NO
由于派生類是由基類繼承而來的,其給它分配地址空間大于基類的地址空間,故可以將派生類對象(指針)轉(zhuǎn)化為基類對象(指針),即Base* pb = &m
,而不可以可以將基類類對象(指針)轉(zhuǎn)化為派生類對象(指針),即Derive* pd = &m;
,如下圖所示:
對于 派生類 -》 基類: 相當(dāng)于切片,紅色部分內(nèi)存在基類指針訪問不了,也不會被訪問
對于 基類 -》 派生類: 派生類申請的地址空間大于基類,一旦派生類對象/指針訪問紅色部分時(shí),由于基類對象是沒有這塊空間的,直接會發(fā)生非法訪問問題
五、靜態(tài)綁定與動(dòng)態(tài)綁定
靜態(tài)綁定和動(dòng)態(tài)綁定是C++中兩種不同的綁定方式
- 靜態(tài)綁定: 在編譯時(shí)進(jìn)行的綁定。它根據(jù)函數(shù)調(diào)用時(shí)的靜態(tài)類型來確定要調(diào)用的函數(shù)。靜態(tài)綁定適用于非虛函數(shù),它會默認(rèn)綁定到函數(shù)定義中的相應(yīng)代碼。
- 動(dòng)態(tài)綁定: 在運(yùn)行時(shí)進(jìn)行的綁定。它根據(jù)函數(shù)調(diào)用時(shí)的動(dòng)態(tài)類型來確定要調(diào)用的函數(shù)。動(dòng)態(tài)綁定適用于虛函數(shù),它允許在運(yùn)行時(shí)根據(jù)實(shí)際對象類型來調(diào)用相應(yīng)的函數(shù)。這樣可以實(shí)現(xiàn)多態(tài)性,即不同對象調(diào)用同名函數(shù)時(shí)可以執(zhí)行不同的操作。
#include <iostream>class Base {
public:void print() {std::cout << "Base class" << std::endl;}virtual void display() {std::cout << "Base class" << std::endl;}
};class Derived : public Base {
public:void print() {std::cout << "Derived class" << std::endl;}void display() {std::cout << "Derived class" << std::endl;}
};int main() {Base* d = new Derived();d->print(); // 靜態(tài)綁定,調(diào)用Base類的print函數(shù)/*mov eax, dword ptr[pb]mov ecx, dword ptr[eax]call ecx;(虛函數(shù)地址) 動(dòng)態(tài)(運(yùn)行時(shí)期)的綁定(函數(shù)的調(diào)用)*/d->display(); // 動(dòng)態(tài)綁定,調(diào)用Derived類的display函數(shù)return 0;
}
d->print()
調(diào)用的是靜態(tài)綁定,因?yàn)?print() 函數(shù)在基類中不是虛函數(shù)。即使 d 指向的對象實(shí)際類型是 Derived 類,編譯器也會根據(jù)指針類型(Base*)來靜態(tài)綁定調(diào)用基類的 print() 函數(shù)。所以輸出為 Base class
d->display()
這里調(diào)用的是動(dòng)態(tài)綁定,因?yàn)?display() 函數(shù)是虛函數(shù)。在運(yùn)行時(shí),實(shí)際調(diào)用的是指向的對象的類型的版本,即RTTI (run-time type information)
,即 Derived 類中的 display() 函數(shù)。所以輸出為 Derived class
一個(gè)問題🤔🤔🤔:一個(gè)類聲明了虛函數(shù)后,當(dāng)調(diào)用該虛函數(shù)時(shí),一定是動(dòng)態(tài)綁定嗎? 不一定 !
動(dòng)態(tài)綁定的實(shí)現(xiàn)通常涉及虛函數(shù)表(vtable)的使用。每個(gè)包含虛函數(shù)的類都會有一個(gè)虛函數(shù)表,其中包含了指向虛函數(shù)的指針。子類會繼承父類的虛函數(shù)表,并在其自己的虛函數(shù)表中重寫父類的虛函數(shù)指針。這樣,當(dāng)通過指針或引用調(diào)用虛函數(shù)時(shí),會根據(jù)對象的實(shí)際類型找到對應(yīng)的虛函數(shù)指針,并調(diào)用正確的函數(shù)。
然而,如果直接通過對象調(diào)用虛函數(shù),編譯器會根據(jù)對象的靜態(tài)類型來進(jìn)行綁定,即根據(jù)對象的聲明類型來調(diào)用虛函數(shù),而這就是靜態(tài)綁定,因?yàn)榫幾g器在編譯時(shí)已經(jīng)確定了應(yīng)該調(diào)用哪個(gè)函數(shù),不需要在運(yùn)行時(shí)根據(jù)對象的實(shí)際類型進(jìn)行判斷。因此,在通過指針或引用調(diào)用虛函數(shù)時(shí),會進(jìn)行動(dòng)態(tài)綁定,根據(jù)對象的實(shí)際類型確定調(diào)用哪個(gè)函數(shù);而在直接通過對象調(diào)用虛函數(shù)時(shí),會進(jìn)行靜態(tài)綁定,根據(jù)對象的聲明類型確定調(diào)用哪個(gè)函數(shù)
六、虛析構(gòu)函數(shù)
虛析構(gòu)函數(shù)是一種特殊的析構(gòu)函數(shù),用于實(shí)現(xiàn)多態(tài)性。它允許在基類指針指向派生類對象時(shí),使用基類指針來調(diào)用派生類對象的析構(gòu)函數(shù),從而正確地釋放派生類對象的資源,我們先來看看下面幾個(gè)問題
我們來看看虛函數(shù)所依賴的條件:
- 虛函數(shù)能產(chǎn)生地址,存儲在vftable虛函數(shù)表中
- 對象必須存在(vfptr -> vftable -> 虛函數(shù)地址)
哪些函數(shù)不能聲明為虛函數(shù)?
- 構(gòu)造函數(shù):構(gòu)造函數(shù)在對象創(chuàng)建和銷毀的過程中是特殊的,在構(gòu)造函數(shù)中的所有函數(shù)都是靜態(tài)綁定的,不能聲明為virtual
- 靜態(tài)成員函數(shù)static:虛函數(shù)是通過對象訪問的,而靜態(tài)成員函數(shù)沒有 this 指針,是直接通過類名訪問的,不能被繼承和覆蓋
- 內(nèi)聯(lián)函數(shù):內(nèi)聯(lián)函數(shù)在編譯時(shí)會直接將函數(shù)體嵌入到函數(shù)調(diào)用點(diǎn),沒有函數(shù)調(diào)用的開銷。而虛函數(shù)是通過虛函數(shù)表來確定的,無法進(jìn)行內(nèi)聯(lián)
什么時(shí)候把基類的析構(gòu)函數(shù)聲明為虛函數(shù)呢?
當(dāng)基類指針(或引用)指向在堆上通過new創(chuàng)建的派生類對象時(shí),派生類對象也會分配一份外部空間。如果基類的析構(gòu)函數(shù)沒有聲明為虛函數(shù),在準(zhǔn)備使用delete刪除基類指針時(shí),會發(fā)生靜態(tài)綁定。這樣,基類對象會調(diào)用基類的析構(gòu)函數(shù),而派生類對象則無法調(diào)用其自己的析構(gòu)函數(shù),導(dǎo)致內(nèi)存泄漏。
class Base { public:Base(int d) :ma(d) { cout << "Base()" << endl; }~Base() { cout << "~Base()" << endl; }virtual void show() { cout << "Base::show()" << >endl; } protected:int ma; };class Derive :public Base { public:Derive(int d) :Base(d), mb(d), ptr(new int(d)){cout << "Derive()" << endl;}// 基類的析構(gòu)函數(shù)是virtual,那么派生類的析構(gòu)>函數(shù)自動(dòng)變成virtual~Derive(){delete ptr;cout << "~Derive()" << endl;}void show() { cout << "Derive::show()" << endl; }private:int mb;int* ptr; };int main() {Base* pb = new Derive(10);pb->show();delete pb;return 0; }
在將基類的析構(gòu)函數(shù)聲明為虛函數(shù)后,當(dāng)使用delete刪除基類指針時(shí),由于基類的析構(gòu)函數(shù)是虛函數(shù),會發(fā)生動(dòng)態(tài)綁定。這樣,派生類的析構(gòu)函數(shù)會自動(dòng)成為虛析構(gòu)函數(shù)。在執(zhí)行delete時(shí),通過虛函數(shù)表指針(vfptr)找到虛函數(shù)表(vftable),將基類在虛函數(shù)表上的虛析構(gòu)函數(shù)覆蓋為派生類的虛析構(gòu)函數(shù)。這樣就會先調(diào)用派生類的析構(gòu)函數(shù),再調(diào)用基類的虛構(gòu)函數(shù)進(jìn)行釋放。
七、虛函數(shù)對類的影響
-
一個(gè)類里面定義了虛函數(shù),那么編譯階段,編譯器會給這個(gè)類生成唯一的虛函數(shù)表
vftable
主要存放的是RTTI指針
和虛函數(shù)地址
,當(dāng)程序運(yùn)行時(shí),每一張?zhí)摵瘮?shù)表都會加載到內(nèi)存的.rodate
區(qū)(只讀,不能寫) -
一個(gè)類中定義了虛函數(shù),那么這個(gè)類定義的對象在程序運(yùn)行時(shí),內(nèi)存中開始的部分多存儲一個(gè)
vfptr虛函數(shù)指針
,指向相同類型的虛函數(shù)表vftable
,一個(gè)類型定義的n個(gè)對象,它們的vfptr
指向的都是同一張?zhí)摵瘮?shù)表 -
一個(gè)類里的虛函數(shù)個(gè)數(shù),不影響對象內(nèi)存的大小(vfptr),影響的是虛函數(shù)表的大小
-
如果派生類中的方法和基類繼承而來的某個(gè)方法的返回值、函數(shù)名、參數(shù)列表相同,而基類的方法是virtual虛函數(shù),那么派生類的這個(gè)方法會自動(dòng)被處理成虛函數(shù),將虛函數(shù)表中的原來的虛函數(shù)地址覆蓋成派生類的虛函數(shù)地址
🌻🌻🌻以上就是有關(guān)淺淺談C++的繼承與多態(tài)(靜態(tài)綁定、動(dòng)態(tài)綁定和虛函數(shù)等)的內(nèi)容,如果聰明的你瀏覽到這篇文章并覺得文章內(nèi)容對你有幫助,請不吝動(dòng)動(dòng)手指,給博主一個(gè)小小的贊和收藏 🌻🌻🌻