欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C/C++多態(tài)深入探究原理

 更新時(shí)間:2022年06月28日 09:43:00   作者:溫逗死  
多態(tài)按字面的意思就是多種形態(tài)。當(dāng)類之間存在層次結(jié)構(gòu),并且類之間是通過(guò)繼承關(guān)聯(lián)時(shí),就會(huì)用到多態(tài)。C++?多態(tài)意味著調(diào)用成員函數(shù)時(shí),會(huì)根據(jù)調(diào)用函數(shù)的對(duì)象的類型來(lái)執(zhí)行不同的函數(shù)

多態(tài)

面向?qū)ο缶幊逃腥筇匦裕豪^承、封裝和多態(tài)。

其中,多態(tài)又分為編譯時(shí)多態(tài)和運(yùn)行時(shí)多態(tài)。編譯多態(tài)是通過(guò)重載函數(shù)體現(xiàn)的,運(yùn)行多態(tài)是通過(guò)虛函數(shù)體現(xiàn)的。

多態(tài)是如何實(shí)現(xiàn)的呢?下面舉個(gè)例子:

#include <iostream>
using namespace std;
class Base {
public:
	virtual void fun() {
		cout << " Base::func()" << endl;
	}
	void fun1(int a) {
		cout << "Base::func1()" << endl;
	}
	void fun2(int a, int b) {
		cout << "Base::func2()" << endl;
	}
};
class Son1 : public Base {
public:
	virtual void fun() override {
		cout << " Son1::func()" << endl;
	}
};
class Son2 : public Base {
};
int main()
{
	cout << "編譯時(shí)多態(tài)" << endl;
	Base* base1 = new Base;
	base1->fun1(1);
	base1->fun2(1,1);
	cout << "運(yùn)行時(shí)多態(tài)" << endl;
	Base* base = new Son1;
	base->fun();
	base = new Son2;
	base->fun();
	delete base;
	base = NULL;
	return 0;
}

結(jié)果:

在例子中

  • 由于Base類中 fun1 和 fun2 函數(shù)簽名不同(其中,函數(shù)后面是否有const 也是簽名的一部分),從結(jié)果分析實(shí)現(xiàn)重載,體現(xiàn)了多態(tài)性。
  • Base為基類,其中的函數(shù)為虛函數(shù)。子類1繼承并重寫了基類的函數(shù),子類2繼承基類但沒(méi)有重寫基類的函數(shù),從結(jié)果分析子類體現(xiàn)了多態(tài)性。

那么為什么會(huì)出現(xiàn)多態(tài)性,其底層的原理是什么?這里需要引出一些相關(guān)的概念來(lái)進(jìn)行解釋。

虛表和虛表指針

  • 虛表:虛函數(shù)表的縮寫,類中含有virtual關(guān)鍵字修飾的方法時(shí),編譯器會(huì)自動(dòng)生成虛表
  • 虛表指針:在含有虛函數(shù)的類實(shí)例化對(duì)象時(shí),對(duì)象地址的前四個(gè)字節(jié)存儲(chǔ)的指向虛表的指針

父類對(duì)象模型:

子類對(duì)象模型:

上圖中展示了虛表和虛表指針在基類對(duì)象和派生類對(duì)象中的模型,下面闡述實(shí)現(xiàn)多態(tài)的過(guò)程:

(1)編譯器在發(fā)現(xiàn)基類中有虛函數(shù)時(shí),會(huì)自動(dòng)為每個(gè)含有虛函數(shù)的類生成一份虛表,該表是一個(gè)一維數(shù)組,虛表里保存了虛函數(shù)的入口地址

(2)編譯器會(huì)在每個(gè)對(duì)象的前四個(gè)字節(jié)中保存一個(gè)虛表指針,即vptr,指向?qū)ο笏鶎兕惖奶摫?。在?gòu)造時(shí),根據(jù)對(duì)象的類型去初始化虛指針vptr,從而讓vptr指向正確的虛表,從而在調(diào)用虛函數(shù)時(shí),能找到正確的函數(shù)

(3)所謂的合適時(shí)機(jī),在派生類定義對(duì)象時(shí),程序運(yùn)行會(huì)自動(dòng)調(diào)用構(gòu)造函數(shù),在構(gòu)造函數(shù)中創(chuàng)建虛表并對(duì)虛表初始化。在構(gòu)造子類對(duì)象時(shí),會(huì)先調(diào)用父類的構(gòu)造函數(shù),此時(shí),編譯器只“看到了”父類,并為父類對(duì)象初始化虛表指針,令它指向父類的虛表;當(dāng)調(diào)用子類的構(gòu)造函數(shù)時(shí),為子類對(duì)象初始化虛表指針,令它指向子類的虛表

(4)當(dāng)派生類對(duì)基類的虛函數(shù)沒(méi)有重寫時(shí),派生類的虛表指針指向的是基類的虛表;當(dāng)派生類對(duì)基類的虛函數(shù)重寫時(shí),派生類的虛表指針指向的是自身的虛表;當(dāng)派生類中有自己的虛函數(shù)時(shí),在自己的虛表中將此虛函數(shù)地址添加在后面這樣指向派生類的基類指針在運(yùn)行時(shí),就可以根據(jù)派生類對(duì)虛函數(shù)重寫情況動(dòng)態(tài)的進(jìn)行調(diào)用,從而實(shí)現(xiàn)多態(tài)性。

下面在VS2019環(huán)境下,通過(guò)程序展現(xiàn):

代碼部分:

#include <iostream>
using namespace std;
class A {
public:
	virtual void vfunc1() {
		cout << "A::vfunc1() -> ";
	}
	virtual void vfunc2() {
		cout << "A::vfunc2() -> " ;
	}
	void func1() {
		cout << "A::func1() -> " ;
	}
	void func2() {
		cout << "A::func2() -> " ;
	}
	int m_data1, m_data2;
};
class B : public A {
public:
	virtual void vfunc1() {
		cout << "B::vfunc1() -> " ;
	}
	void func2() {
		cout << "B::func2() -> " ;
	}
	int m_data3;
};
class C : public B {
public:
	virtual void vfunc1() {
		cout << "C::vfunc1() -> " ;
	}
	void func2() {
		cout << "C::func2() -> " ;
	}
	int m_data1, m_data4;
};
int main()
{
	//  這里指針操作比較混亂,在此稍微解析下:

	//  *****printf("虛表地址:%p\n", *(int *)&b); 解析*****:
	//  1.&b代表對(duì)象b的起始地址
	//  2.(int *)&b 強(qiáng)轉(zhuǎn)成int *類型,為了后面取b對(duì)象的前四個(gè)字節(jié),前四個(gè)字節(jié)是虛表指針
	//  3.*(int *)&b 取前四個(gè)字節(jié),即vptr虛表地址
	//

	//  *****printf("第一個(gè)虛函數(shù)地址:%p\n", *(int *)*(int *)&b);*****:
	//  根據(jù)上面的解析我們知道*(int *)&b是vptr,即虛表指針.并且虛表是存放虛函數(shù)指針的
	//  所以虛表中每個(gè)元素(虛函數(shù)指針)在32位編譯器下是4個(gè)字節(jié),因此(int *)*(int *)&b
	//  這樣強(qiáng)轉(zhuǎn)后為了后面的取四個(gè)字節(jié).所以*(int *)*(int *)&b就是虛表的第一個(gè)元素.
	//  即f()的地址.
	//  那么接下來(lái)的取第二個(gè)虛函數(shù)地址也就依次類推.  始終記著vptr指向的是一塊內(nèi)存,
	//  這塊內(nèi)存存放著虛函數(shù)地址,這塊內(nèi)存就是我們所說(shuō)的虛表.
	cout << "class A 成員函數(shù)、成員變量的地址::" << endl;
	A a;
	cout << "A::vptr 地址 :" << *(int*)&a << endl;
	cout << "A::vtbl 地址 :" << *(int*)*(int*)&a << endl;
	cout << "A::vtbl 地址 :" << *((int*)*(int*)(&a) + 1) << endl;
	union {
		void* pv;
		void(A::* pfn)();
	} u;
	u.pfn = &A::vfunc1;
	(a.*u.pfn)();
	cout << u.pv << endl;
	u.pfn = &A::vfunc2;
	(a.*u.pfn)();
	cout << u.pv << endl;
	u.pfn = &A::func1;
	(a.*u.pfn)();
	cout << u.pv << endl;
	u.pfn = &A::func2;
	(a.*u.pfn)();
	cout << u.pv << endl;
	cout << "class B 成員函數(shù)、成員變量的地址::" << endl;
	B b;
	cout << "B::vptr 地址 :" << *(int*)&b << endl;
	cout << "B::vtbl 地址 :" << *(int*)*(int*)&b << endl;
	cout << "B::vtbl 地址 :" << *((int*)*(int*)(&b) + 1) << endl;
	union {
		void* pv;
		void(B::* pfn)();
	} m;
	m.pfn = &B::vfunc1;
	(b.*m.pfn)();
	cout << m.pv << endl;
	m.pfn = &B::vfunc2;
	(b.*m.pfn)();
	cout << m.pv << endl;
	m.pfn = &B::func1;
	(b.*m.pfn)();
	cout << m.pv << endl;
	m.pfn = &B::func2;
	(b.*m.pfn)();
	cout << m.pv << endl;
	cout << "class C 成員函數(shù)、成員變量的地址::" << endl;
	C c;
	cout << "C::vptr 地址 :" << *(int*)&c << endl;
	cout << "C::vtbl 地址 :" << *(int*)*(int*)&c << endl;
	cout << "C::vtbl 地址 :" << *((int*)*(int*)(&c) + 1) << endl;
	union {
		void* pv;
		void(C::* pfn)();
	} n;
	n.pfn = &C::vfunc1;
	(c.*n.pfn)();
	cout << n.pv << endl;
	n.pfn = &C::vfunc2;
	(c.*n.pfn)();
	cout << n.pv << endl;
	n.pfn = &C::func1;
	(c.*n.pfn)();
	cout << n.pv << endl;
	n.pfn = &C::func2;
	(c.*n.pfn)();
	cout << n.pv << endl;
}	

運(yùn)行結(jié)果:

整個(gè)程序圖示:

通過(guò)圖示我們可以看出,函數(shù)在構(gòu)造后,通過(guò)vptr尋找到vtbl,進(jìn)而得到所對(duì)應(yīng)的成員函數(shù)。而它是怎么做到尋找到所需要的是父類還是子類的成員函數(shù)呢?

這里就要提到另一個(gè)隱藏的指針,this指針。

this指針是隱藏在類里面的一個(gè)指針,它指向當(dāng)前對(duì)象,通過(guò)它可以訪問(wèn)當(dāng)前對(duì)象的所有成員。

如程序中如果出現(xiàn):

    C c;
    c.vfunc1();

其實(shí)編譯器會(huì)對(duì)其進(jìn)行處理,從直觀上可以將 vfunc1() 看作是下面形式(不知編譯器是否這樣轉(zhuǎn)換):

    c.A::vfunc1(&c);

其中,&c就是隱藏的this指針,通過(guò)this指針,進(jìn)而得到c對(duì)象需要的成員函數(shù)。

同時(shí),這里面還包括另一個(gè)C++語(yǔ)法:動(dòng)態(tài)綁定和靜態(tài)綁定

  • 靜態(tài)綁定:綁定的是靜態(tài)類型,所對(duì)應(yīng)的函數(shù)或?qū)傩砸蕾囉趯?duì)象的靜態(tài)類型,發(fā)生在編譯期;
  • 動(dòng)態(tài)綁定:綁定的是動(dòng)態(tài)類型,所對(duì)應(yīng)的函數(shù)或?qū)傩砸蕾囉趯?duì)象的動(dòng)態(tài)類型,發(fā)生在運(yùn)行期;

從上面的定義也可以看出,非虛函數(shù)一般都是靜態(tài)綁定,而虛函數(shù)都是動(dòng)態(tài)綁定(如此才可實(shí)現(xiàn)多態(tài)性)。

所以,我們?cè)谏厦娲a中加入一些代碼如下:

    B bb;
    A aa = (A)bb;
    aa.vfunc1();

同時(shí),加入斷點(diǎn),進(jìn)行調(diào)試,通過(guò)vs2019窗口查看反匯編代碼,我們得到如下代碼:

    B bb;
00B63237  lea         ecx,[bb]  
00B6323D  call        B::B (0B6129Eh)  
    A aa = (A)bb;
00B63242  lea         eax,[bb]  
00B63248  push        eax  
00B63249  lea         ecx,[aa]  
00B6324F  call        A::A (0B6128Ah)  
    aa.vfunc1();
00B63254  lea         ecx,[aa]  
00B6325A  call        A::vfunc1 (0B6111Dh)  

由于,aa是一個(gè)A的對(duì)象而非指針,即使a內(nèi)容是B對(duì)象強(qiáng)制轉(zhuǎn)換而來(lái),aa.vfunc1()調(diào)用的是靜態(tài)綁定的A::vfunc1()。同時(shí),在匯編中我們得到,在調(diào)用時(shí),直接call xxxx,call后面是一個(gè)固定的地址,從這里依舊可以看出是靜態(tài)綁定。

同時(shí),我們繼續(xù)運(yùn)行下面代碼:

    A* pa = new B;
    pa->vfunc1();

    pa = &b;
    pa->vfunc1();

得到如下反匯編:

    A* pa = new B;
00B6325F  push        10h  
00B63261  call        operator new (0B6114Fh)  
00B63266  add         esp,4  
00B63269  mov         dword ptr [ebp-174h],eax  
00B6326F  cmp         dword ptr [ebp-174h],0  
00B63276  je          __$EncStackInitStart+68Fh (0B6328Bh)  
00B63278  mov         ecx,dword ptr [ebp-174h]  
00B6327E  call        B::B (0B6129Eh)  
00B63283  mov         dword ptr [ebp-17Ch],eax  
00B63289  jmp         __$EncStackInitStart+699h (0B63295h)  
00B6328B  mov         dword ptr [ebp-17Ch],0  
00B63295  mov         eax,dword ptr [ebp-17Ch]  
00B6329B  mov         dword ptr [pa],eax  
    pa->vfunc1();
00B632A1  mov         eax,dword ptr [pa]  
00B632A7  mov         edx,dword ptr [eax]  
00B632A9  mov         esi,esp  
00B632AB  mov         ecx,dword ptr [pa]  
00B632B1  mov         eax,dword ptr [edx]  
00B632B3  call        eax  
00B632B5  cmp         esi,esp  
00B632B7  call        __RTC_CheckEsp (0B61316h)    //并非固定地址

    pa = &b;
00B632BC  lea         eax,[b]  
00B632BF  mov         dword ptr [pa],eax  
    pa->vfunc1();
00B632C5  mov         eax,dword ptr [pa]  
00B632CB  mov         edx,dword ptr [eax]  
00B632CD  mov         esi,esp  
00B632CF  mov         ecx,dword ptr [pa]  
00B632D5  mov         eax,dword ptr [edx]  
00B632D7  call        eax  
00B632D9  cmp         esi,esp  
00B632DB  call        __RTC_CheckEsp (0B61316h)  

在下面這段程序中,我們可以看到,指針pa指向一個(gè)B對(duì)象,有一個(gè)向上轉(zhuǎn)型操作,可以確定,這應(yīng)該是動(dòng)態(tài)綁定。同時(shí),在匯編代碼中,call后面并不是一個(gè)固定的地址,從這里我們也可以看出pa調(diào)用了B::vfunc1()。

到此這篇關(guān)于C/C++多態(tài)深入探究原理的文章就介紹到這了,更多相關(guān)C語(yǔ)言多態(tài)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • C語(yǔ)言實(shí)現(xiàn)逆波蘭式實(shí)例

    C語(yǔ)言實(shí)現(xiàn)逆波蘭式實(shí)例

    這篇文章介紹了C語(yǔ)言實(shí)現(xiàn)逆波蘭式實(shí)例,有需要的朋友可以參考一下
    2013-09-09
  • C語(yǔ)言中判斷兩個(gè)IPv4地址是否屬于同一個(gè)子網(wǎng)的代碼

    C語(yǔ)言中判斷兩個(gè)IPv4地址是否屬于同一個(gè)子網(wǎng)的代碼

    這篇文章主要介紹了C語(yǔ)言中判斷兩個(gè)IPv4地址是否屬于同一個(gè)子網(wǎng)的代碼,需要的朋友可以參考下
    2017-09-09
  • C 語(yǔ)言環(huán)境設(shè)置詳細(xì)講解

    C 語(yǔ)言環(huán)境設(shè)置詳細(xì)講解

    本文主要介紹C 語(yǔ)言環(huán)境設(shè)置,在不同的系統(tǒng)平臺(tái)上,C語(yǔ)言的環(huán)境設(shè)置不同,這里幫大家整理了Liunx, UNIX,Windows 上安裝C語(yǔ)言環(huán)境,有開(kāi)始學(xué)習(xí)C語(yǔ)言的朋友可以參考下
    2016-08-08
  • C++實(shí)現(xiàn)簡(jiǎn)單的計(jì)算器小功能

    C++實(shí)現(xiàn)簡(jiǎn)單的計(jì)算器小功能

    這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)簡(jiǎn)單的計(jì)算器小功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-02-02
  • C++ 流插入和流提取運(yùn)算符的重載的實(shí)現(xiàn)

    C++ 流插入和流提取運(yùn)算符的重載的實(shí)現(xiàn)

    這篇文章主要介紹了C++ 流插入和流提取運(yùn)算符的重載的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-12-12
  • C++模版函數(shù)詳解

    C++模版函數(shù)詳解

    C++中的模版總體可以分為兩大類:模版函數(shù)、模版類。本篇文章先寫模版函數(shù),需要的朋友可以參考下
    2017-02-02
  • Pipes實(shí)現(xiàn)LeetCode(195.第十行)

    Pipes實(shí)現(xiàn)LeetCode(195.第十行)

    這篇文章主要介紹了Pipes實(shí)現(xiàn)LeetCode(195.第十行),本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-08-08
  • C語(yǔ)言實(shí)現(xiàn)宿舍管理系統(tǒng)課程設(shè)計(jì)

    C語(yǔ)言實(shí)現(xiàn)宿舍管理系統(tǒng)課程設(shè)計(jì)

    這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)宿舍管理系統(tǒng)課程設(shè)計(jì),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • 利用C語(yǔ)言解決八皇后問(wèn)題以及解析

    利用C語(yǔ)言解決八皇后問(wèn)題以及解析

    這篇文章主要給大家介紹了關(guān)于利用C語(yǔ)言解決八皇后問(wèn)題以及解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2018-12-12
  • C++中static修飾符的詳解及其作用介紹

    C++中static修飾符的詳解及其作用介紹

    這篇文章主要介紹了C++中static修飾符的詳解及其作用介紹,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-09-09

最新評(píng)論