溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

C++中多態(tài)的示例分析

發(fā)布時間:2021-05-11 14:03:10 來源:億速云 閱讀:135 作者:小新 欄目:開發(fā)技術(shù)

小編給大家分享一下C++中多態(tài)的示例分析,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!

1. 多態(tài)概念

1.1 概念

  • 多態(tài)的概念:通俗來說,就是多種形態(tài),具體點(diǎn)就是去完成某個行為,當(dāng)不同的對象去完成時會產(chǎn)生出不同的狀態(tài)。

  • 舉個栗子:比如買票,當(dāng)普通人買票時,是全價買票;學(xué)生買票時,是半價買票;軍人買票時是優(yōu)先買票。同一個事情針對不同的人或情況有不同的結(jié)果或形態(tài)。

2. 多態(tài)的定義及實(shí)現(xiàn)

2.1 多態(tài)的構(gòu)成條件

多態(tài)是在不同繼承關(guān)系的類對象,去調(diào)用同一函數(shù),產(chǎn)生了不同的行為。比如Student繼承了Person。

Person對象買票全價,Student對象買票半價。

注意:那么在繼承中要構(gòu)成多態(tài)還有兩個條件:

  • 必須通過基類的指針或者引用調(diào)用虛函數(shù)。

  • 被調(diào)用的函數(shù)必須是**虛函數(shù),且派生類必須對基類的虛函數(shù)進(jìn)行重寫。

2.2 虛函數(shù)

虛函數(shù):即被virtual修飾的類成員函數(shù)稱為虛函數(shù)。

class Person {
public:
	virtual void BuyTicket() { cout << "買票-全價" << endl;}
};

2.3 虛函數(shù)的重寫

  • 虛函數(shù)的重寫(覆蓋):派生類中有一個跟基類完全相同的虛函數(shù)(即派生類虛函數(shù)與基類虛函數(shù)的返回值類型、函數(shù)名字、參數(shù)列表完全相同),稱子類的虛函數(shù)重寫了基類的虛函數(shù)。

注意:

  • 在重寫基類虛函數(shù)時,派生類的虛函數(shù)在不加virtual關(guān)鍵字時,雖然也可以構(gòu)成重寫(因?yàn)槔^承后基類的虛函數(shù)被繼承下來了在派生類依舊保持虛函數(shù)屬性),但是該種寫法不是很規(guī)范,不建議這樣使用。

class Person {
public:
	virtual void BuyTicket() { cout << "買票-全價" << endl; }
};
class Student : public Person {
public:
	void BuyTicket() { cout << "買票-半價" << endl; }
};

2.4 代碼示例

2.4.1 沒構(gòu)成重寫

C++中多態(tài)的示例分析

2.4.2 構(gòu)成重寫

C++中多態(tài)的示例分析

2.5 虛函數(shù)重寫的兩個例外

 2.5.1 協(xié)變

派生類重寫基類虛函數(shù)時,與基類虛函數(shù)返回值類型不同。即基類虛函數(shù)返回基類對象的指針或者引用,派生類虛函數(shù)返回派生類對象的指針或者引用時,稱為協(xié)變。

C++中多態(tài)的示例分析

 2.5.2 析構(gòu)函數(shù)的重寫

如果基類的析構(gòu)函數(shù)為虛函數(shù),此時派生類析構(gòu)函數(shù)只要定義,無論是否加virtual關(guān)鍵字,都與基類的析構(gòu)函數(shù)構(gòu)成重寫,即使基類與派生類析構(gòu)函數(shù)名字不同。雖然函數(shù)名不相同,看起來違背了重寫的規(guī)則,其實(shí)不然,這里可以理解為編譯器對析構(gòu)函數(shù)的名稱做了特殊處理,編譯后析構(gòu)函數(shù)的名稱統(tǒng)一處理成destructor。

C++中多態(tài)的示例分析

注意:析構(gòu)函數(shù)在編譯以后函數(shù)名會統(tǒng)一成destructor。如果不加virtual則會造成重定義(隱藏),如上圖代碼如果不構(gòu)成析構(gòu)函數(shù)的重寫,則在析構(gòu)p2時只會析構(gòu)Student,因?yàn)榇藭r成了重定義所以不會自動調(diào)用Person里面的析構(gòu),造成資源泄漏。

2.6 C++11 override 和 final

  • C++對函數(shù)重寫的要求比較嚴(yán)格,但是有些情況下由于疏忽,可能會導(dǎo)致函數(shù)名字母次序?qū)懛炊鵁o法構(gòu)成重載,而這種錯誤在編譯期間是不會報出的,只有在程序運(yùn)行時沒有得到預(yù)期結(jié)果才來debug會得不償失。

  • 因此:C++11提供了override和final兩個關(guān)鍵字,可以幫助用戶檢測是否重寫。

1. final:修飾虛函數(shù),表示該虛函數(shù)不能再被繼承

C++中多態(tài)的示例分析

2. override: 檢查派生類虛函數(shù)是否重寫了基類某個虛函數(shù),如果沒有重寫編譯報錯。

C++中多態(tài)的示例分析

2.7 重載、覆蓋(重寫)、隱藏(重定義)的對比

C++中多態(tài)的示例分析

C++中多態(tài)的示例分析

3. 抽象類

3.1 概念

在虛函數(shù)的后面寫上 =0 ,則這個函數(shù)為純虛函數(shù)。包含純虛函數(shù)的類叫做抽象類(也叫接口類),抽象類不能實(shí)例化出對象。派生類繼承后也不能實(shí)例化出對象,只有重寫純虛函數(shù),派生類才能實(shí)例化出對象。純虛函數(shù)規(guī)范了派生類必須重寫,另外純虛函數(shù)更體現(xiàn)出了接口繼承。

C++中多態(tài)的示例分析 

3.2 接口繼承和實(shí)現(xiàn)繼承

  • 普通函數(shù)的繼承是一種實(shí)現(xiàn)繼承,派生類繼承了基類函數(shù),可以使用函數(shù),繼承的是函數(shù)的實(shí)現(xiàn)。

  • 虛函數(shù)的繼承是一種接口繼承,派生類繼承的是基類虛函數(shù)的接口,目的是為了重寫,達(dá)成多態(tài),繼承的是接口。所以如果不實(shí)現(xiàn)多態(tài),不要把函數(shù)定義成虛函數(shù)。

4.多態(tài)的原理

4.1虛函數(shù)表

C++中多態(tài)的示例分析

通過觀察測試我們發(fā)現(xiàn)b對象是8bytes,除了_b成員,還多一個__vfptr放在對象的前面(注意有些平臺可能會放到對象的最后面,這個跟平臺有關(guān)),對象中的這個指針我們叫做虛函數(shù)表指針(v代表virtual,f代表function)。一個含有虛函數(shù)的類中都至少都有一個虛函數(shù)表指針,因?yàn)樘摵瘮?shù)的地址要被放到虛函數(shù)表中,虛函數(shù)表也簡稱虛表。

C++中多態(tài)的示例分析

虛函數(shù)表指針(簡稱虛表指針)

虛函數(shù)表本質(zhì)是一個指針數(shù)組(指針是一個虛函數(shù)指針),(虛基表->菱形繼->存的偏移量)。

C++中多態(tài)的示例分析

基類和派生類中的虛函數(shù)表。

C++中多態(tài)的示例分析

總結(jié):

  • 基類b對象和派生類d對象虛表是不一樣的,這里我們發(fā)現(xiàn)Func1完成了重寫,所以d的虛表中存的是重寫的Derive::Func1,所以虛函數(shù)的重寫也叫作覆蓋,覆蓋就是指虛表中虛函數(shù)的覆蓋。重寫是語法的叫法,覆蓋是原理層的叫法。

  • 另外Func2繼承下來后是虛函數(shù),所以放進(jìn)了虛表,F(xiàn)unc3也繼承下來了,但是不是虛函數(shù),所以不會放進(jìn)虛表。

  • 虛函數(shù)表本質(zhì)是一個存虛函數(shù)指針的指針數(shù)組,這個數(shù)組最后面放了一個nullptr。

  • 派生類的虛表生成:
    a.先將基類中的虛表內(nèi)容拷貝一份到派生類虛表中
    b.如果派生類重寫了基類中某個虛函數(shù),用派生類自己的虛函數(shù)覆蓋虛表中基類的虛函數(shù)
    c.派生類自己新增加的虛函數(shù)按其在派生類中的聲明次序增加到派生類虛表的最后。

  • 虛函數(shù)存在哪的?虛表存在哪的?

  • 虛函數(shù)在代碼段,虛函數(shù)表也在代碼段。虛函數(shù)表中存放的是虛函數(shù)地址。對象里面是虛函數(shù)表的指針。

4.2多態(tài)的原理

C++中多態(tài)的示例分析

C++中多態(tài)的示例分析

總結(jié)

  • 這樣就實(shí)現(xiàn)出了不同對象去完成同一行為時,展現(xiàn)出不同的形態(tài)。

  • 反過來思考我們要達(dá)到多態(tài),有兩個條件,一個是虛函數(shù)覆蓋,一個是對象的指針或引用調(diào)用虛函數(shù)?
    虛函數(shù)覆蓋是為了構(gòu)成多態(tài)時不同的對象通過調(diào)用對應(yīng)類中的虛函數(shù)表時通過虛函數(shù)的地址去找到對應(yīng)的虛函數(shù)。而指針或者引用是因?yàn)樵谡覍?yīng)的虛函數(shù)時是在虛函數(shù)表中通過地址查找的。

  • 通過匯編代碼分析,看出滿足多態(tài)以后的函數(shù)調(diào)用,不是在編譯時確定的,是運(yùn)行起來以后到對象中去找的。不滿足多態(tài)的函數(shù)調(diào)用時編譯時確認(rèn)好的。即不滿足多態(tài)的函數(shù)地址編譯時已經(jīng)確定,而構(gòu)成多態(tài)的虛函數(shù)在運(yùn)行時會通過地址去call。

4.3 動態(tài)綁定與靜態(tài)綁定

  1. 靜態(tài)綁定又稱為前期綁定(早綁定),在程序編譯期間確定了程序的行為,也稱為靜態(tài)多態(tài),比如:函數(shù)重載。

  2. 動態(tài)綁定又稱后期綁定(晚綁定),是在程序運(yùn)行期間,根據(jù)具體拿到的類型確定程序的具體行為,調(diào)用具體的函數(shù),也稱為動態(tài)多態(tài)。

5.單繼承和多繼承關(guān)系的虛函數(shù)表

5.1 單繼承中的虛函數(shù)表

C++中多態(tài)的示例分析

通過代碼打印出虛表中的函數(shù):

#include<iostream>
using namespace std;

class Base {
public:
	virtual void func1() { cout << "Base::func1" << endl; }
	virtual void func2() { cout << "Base::func2" << endl; }
private:
	int a;
};
class Derive :public Base {
public:
	virtual void func1() { cout << "Derive::func1" << endl; }
	virtual void func3() { cout << "Derive::func3" << endl; }
	virtual void func4() { cout << "Derive::func4" << endl; }
private:
	int b;
};
typedef void(*VFPTR) ();
void PrintVTable(VFPTR vTable[])
{
	// 依次取虛表中的虛函數(shù)指針打印并調(diào)用。調(diào)用就可以看出存的是哪個函數(shù)
	cout << " 虛表地址>" << vTable << endl;
	for (int i = 0; vTable[i] != nullptr; ++i)
	{
		printf(" 第%d個虛函數(shù)地址 :0X%x,->", i, vTable[i]);
		VFPTR f = vTable[i];
		f();
	}
	cout << endl;
}

int main()
{
	Base b;
	Derive d;
	// 思路:取出b、d對象的頭4bytes,就是虛表的指針,前面我們說了虛函數(shù)表本質(zhì)是一個存虛函數(shù)指針的指針數(shù)組,這個數(shù)組最后面放了一個nullptr
	// 1.先取b的地址,強(qiáng)轉(zhuǎn)成一個int*的指針
	// 2.再解引用取值,就取到了b對象頭4bytes的值,這個值就是指向虛表的指針
	// 3.再強(qiáng)轉(zhuǎn)成VFPTR*,因?yàn)樘摫砭褪且粋€存VFPTR類型(虛函數(shù)指針類型)的數(shù)組。
	// 4.虛表指針傳遞給PrintVTable進(jìn)行打印虛表
	// 5.需要說明的是這個打印虛表的代碼經(jīng)常會崩潰,因?yàn)榫幾g器有時對虛表的處理不干凈,虛表最后面沒有放nullptr,導(dǎo)致越界,這是編譯器的問題。我們只需要點(diǎn)目錄欄的 - 生成 - 清理解決方案,再編譯就好了。
	VFPTR * vTableb = (VFPTR*)(*(int*)&b);
	PrintVTable(vTableb);
	VFPTR* vTabled = (VFPTR*)(*(int*)&d);
	PrintVTable(vTabled);
	return 0;
}

C++中多態(tài)的示例分析

5.2 多繼承中的虛函數(shù)表

#include<iostream>
using namespace std;

class Base1 {
public:
	virtual void func1() { cout << "Base1::func1" << endl; }
	virtual void func2() { cout << "Base1::func2" << endl; }
private:
	int b1;
};
class Base2 {
public:
	virtual void func1() { cout << "Base2::func1" << endl; }
	virtual void func2() { cout << "Base2::func2" << endl; }
private:
	int b2;
};
class Derive : public Base1, public Base2 {
public:
	virtual void func1() { cout << "Derive::func1" << endl; }
	virtual void func3() { cout << "Derive::func3" << endl; }
private:
	int d1;
};
typedef void(*VFPTR) ();
void PrintVTable(VFPTR vTable[])
{
	cout << " 虛表地址>" << vTable << endl;
	for (int i = 0; vTable[i] != nullptr; ++i)
	{
		printf(" 第%d個虛函數(shù)地址 :0X%x,->", i, vTable[i]);
		VFPTR f = vTable[i];
		f();
	}
	cout << endl;
}
int main()
{
	Derive d;
	VFPTR* vTableb1 = (VFPTR*)(*(int*)&d);
	PrintVTable(vTableb1);
	VFPTR* vTableb2 = (VFPTR*)(*(int*)((char*)&d + sizeof(Base1)));
	PrintVTable(vTableb2);
	return 0;
}

C++中多態(tài)的示例分析

6. 相關(guān)題目

1. inline函數(shù)可以是虛函數(shù)嗎?

答:能,virtual函數(shù)可以寫成inline函數(shù),不會造成語法錯誤。虛函數(shù)是在運(yùn)行的時候才決定調(diào)用基類或者子類的對應(yīng)函數(shù),inline函數(shù)是在編譯期間來決定展開與否。虛函數(shù)是在運(yùn)行的時候才決定調(diào)用基類或者子類的對應(yīng)函數(shù),inline函數(shù)是在編譯期間來決定展開與否。虛函數(shù)是在運(yùn)行的時候才決定調(diào)用基類或者子類的對應(yīng)函數(shù),inline函數(shù)是在編譯期間來決定展開與否。

2. 靜態(tài)成員可以是虛函數(shù)嗎?

答:不能,因?yàn)殪o態(tài)成員函數(shù)沒有this指針,使用類型::成員函數(shù)的調(diào)用方式無法訪問虛函數(shù)表,所以靜態(tài)成員函數(shù)無法放進(jìn)虛函數(shù)表。

3. 構(gòu)造函數(shù)可以是虛函數(shù)嗎?

答:不能,因?yàn)閷ο笾械奶摵瘮?shù)表指針是在構(gòu)造函數(shù)初始化列表階段才初始化的。

4. 析構(gòu)函數(shù)可以是虛函數(shù)嗎?什么場景下析構(gòu)函數(shù)是虛函數(shù)?

答:可以,并且最好把基類的析構(gòu)函數(shù)定義成虛函數(shù)。

5. 對象訪問普通函數(shù)快還是虛函數(shù)更快?

答:首先如果是普通對象,是一樣快的。如果是指針對象或者是引用對象,則調(diào)用的普通函數(shù)快,因?yàn)闃?gòu)成多態(tài),運(yùn)行時調(diào)用虛函數(shù)需要到虛函數(shù)表中去查找。

6. 虛函數(shù)表是在什么階段生成的,存在哪的?

答:虛函數(shù)表是在編譯階段就生成的,一般情況下存在代碼段(常量區(qū))的。

以上是“C++中多態(tài)的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識,歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

c++
AI