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

詳解C++虛函數的工作原理

 更新時間:2020年06月22日 11:34:41   作者:讓我思考一下  
這篇文章主要介紹了C++虛函數的工作原理的的相關資料,文中講解非常細致,代碼幫助大家更好的理解和學習,感興趣的朋友可以了解下

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

討論靜態(tài)綁定與動態(tài)綁定,首先需要理解的是綁定,何為綁定?函數調用與函數本身的關聯,以及成員訪問與變量內存地址間的關系,稱為綁定。 理解了綁定后再理解靜態(tài)與動態(tài)。

  • 靜態(tài)綁定:指在程序編譯過程中,把函數調用與響應調用所需的代碼結合的過程,稱為靜態(tài)綁定。發(fā)生在編譯期。
  • 動態(tài)綁定:指在執(zhí)行期間判斷所引用對象的實際類型,根據實際的類型調用其相應的方法。程序運行過程中,把函數調用與響應調用所需的代碼相結合的過程稱為動態(tài)綁定。發(fā)生于運行期。

C++中動態(tài)綁定

在C++中動態(tài)綁定是通過虛函數實現的,是多態(tài)實現的具體形式。而虛函數是通過虛函數表實現的。這個表中記錄了虛函數的地址,解決繼承、覆蓋的問題,保證動態(tài)綁定時能夠根據對象的實際類型調用正確的函數。這個虛函數表在什么地方呢?C++標準規(guī)格說明書中說到,編譯器必須要保證虛函數表的指針存在于對象實例中最前面的位置(這是為了保證正確取到虛函數的偏移量)。也就是說,我們可以通過對象實例的地址得到這張?zhí)摵瘮当?,然后可以遍歷其中的函數指針,并調用相應的函數。

虛函數的工作原理

要想弄明白動態(tài)綁定,就必須弄懂虛函數的工作原理。C++中虛函數的實現一般是通過虛函數表實現的(C++規(guī)范中沒有規(guī)定具體用哪種方法,但大部分的編譯器廠商都選擇此方法)。類的虛函數表是一塊連續(xù)的內存,每個內存單元中記錄一個JMP指令的地址。編譯器會為每個有虛函數的類創(chuàng)建一個虛函數表,該虛函數表將被該類的所有對象共享。 類的每個虛成員占據虛函數表中的一行。如果類中有N個虛函數,那么其虛函數表將有N*4字節(jié)的大小。

虛函數(virtual)是通過虛函數表來實現的,在這個表中,主要是一個類的虛函數的地址表,這張表解決了繼承、覆蓋的問題,保證其真實反映實際的函數。這樣,在有虛函數的類的實例中分配了指向這個表的指針的內存(位于對象實例的最前面),所以,當用父類的指針來操作一個子類的時候,這張?zhí)摵瘮当砭惋@得尤為重要,指明了實際所應調用的函數。它是如何指明的呢?后面會講到。

JMP指令是匯編語言中的無條件跳轉指令,無條件跳轉指令可轉到內存中任何程序段。轉移地址可在指令中給出,也可以在寄存器中給出,或在儲存器中指出。

首先我們定義一個帶有虛函數的基類

class Base
{
public:
	virtual void fun1(){
		cout<<"base fun1!\n";
	}
	virtual void fun2(){
		cout<<"base fun2!\n";
	}
	virtual void fun3(){
		cout<<"base fun3!\n";
	}

	int a;
};

我們可以看到在Base類的內存布局上,第一個位置上存放虛函數表指針,接下來才是Base的成員變量。另外,存在著虛函數表,該表里存放著Base類的所有virtual函數。

既然虛函數表指針通常放在對象實例的最前面的位置,那么我們應該可以通過代碼來訪問虛函數表,通過下面這段代碼加深對虛函數表的理解:

#include "stdafx.h"
#include<iostream>
using namespace std;

class Base
{
public:
	virtual void fun1(){
		cout<<"base fun1!\n";
	}
	virtual void fun2(){
		cout<<"base fun2!\n";
	}
	virtual void fun3(){
		cout<<"base fun3!\n";
	}

	int a;
};

int _tmain(int argc, _TCHAR* argv[])
{
	typedef void(*pFunc)(void);
	Base b;
	cout<<"虛函數表指針地址:"<<(int*)(&b)<<endl;

	//對象最前面是指向虛函數表的指針,虛函數表中存放的是虛函數的地址
	pFunc pfun;
	pfun=(pFunc)*((int*)(*(int*)(&b))); //這里存放的都是地址,所以才一層又一層的指針
	pfun();
	pfun=(pFunc)*((int*)(*(int*)(&b))+1);
	pfun();
	pfun=(pFunc)*((int*)(*(int*)(&b))+2);
	pfun();

	system("pause");
	return 0;
}

運行結果:

通過這個例子,對虛函數表指針,虛函數表這些有了足夠的理解。下面再深入一些。C++又是如何利用基類指針和虛函數來實現多態(tài)的呢?這里,我們就需要弄明白在繼承環(huán)境下虛函數表是如何工作的。目前只理解單繼承,至于虛繼承,多重繼承待以后再理解。

單繼承代碼如下:

class Base
{
public:
	virtual void fun1(){
		cout<<"base fun1!\n";
	}
	virtual void fun2(){
		cout<<"base fun2!\n";
	}
	virtual void fun3(){
		cout<<"base fun3!\n";
	}

	int a;
};

class Child:public Base
{
public:
	void fun1(){
		cout<<"Child fun1\n";
	}
	void fun2(){
		cout<<"Child fun2\n";
	}
	virtual void fun4(){
		cout<<"Child fun4\n";
	}
};

內存布局對比:

通過對比,我們可以看到:

  • 在單繼承中,Child類覆蓋了Base類中的同名虛函數,在虛函數表中體現為對應位置被Child類中的新函數替換,而沒有被覆蓋的函數則沒有發(fā)生變化。
  • 對于子類自己的虛函數,直接添加到虛函數表后面。

另外,我們注意到,類Child和類Base中都只有一個vfptr指針,前面我們說過,該指針指向虛函數表,我們分別輸出類Child和類Base的vfptr:

int _tmain(int argc, _TCHAR* argv[])
{
	typedef void(*pFunc)(void);
	Base b;
	Child c;
	cout<<"Base類的虛函數表指針地址:"<<(int*)(&b)<<endl;
	cout<<"Child類的虛函數表指針地址:"<<(int*)(&c)<<endl;

	system("pause");
	return 0;
}

運行結果:

可以看到,類Child和類Base分別擁有自己的虛函數表指針vfptr和虛函數表vftable。

下面這段代碼,說明了父類和基類擁有不同的虛函數表,同一個類擁有相同的虛函數表,同一個類的不同對象的地址(存放虛函數表指針的地址)不同。

int _tmain(int argc, _TCHAR* argv[])
{
	Base b;
	Child c1,c2;
	cout<<"Base類的虛函數表的地址:"<<(int*)(*(int*)(&b))<<endl;
	cout<<"Child類c1的虛函數表的地址:"<<(int*)(*(int*)(&c1))<<endl;	//虛函數表指針指向的地址值
	cout<<"Child類c2的虛函數表的地址:"<<(int*)(*(int*)(&c2))<<endl;

	system("pause");
	return 0;
}

在定義該派生類對象時,先調用其基類的構造函數,然后再初始化vfptr,最后再調用派生類的構造函數( 從二進制的視野來看,所謂基類子類是一個大結構體,其中this指針開頭的四個字節(jié)存放虛函數表頭指針。執(zhí)行子類的構造函數的時候,首先調用基類構造函數,this指針作為參數,在基類構造函數中填入基類的vfptr,然后回到子類的構造函數,填入子類的vfptr,覆蓋基類填入的vfptr。如此以來完成vfptr的初始化)。也就是說,vfptr指向vftable發(fā)生在構造函數期間完成的。

動態(tài)綁定例子:

#include "stdafx.h"
#include<iostream>
using namespace std;

class Base
{
public:
	virtual void fun1(){
		cout<<"base fun1!\n";
	}
	virtual void fun2(){
		cout<<"base fun2!\n";
	}
	virtual void fun3(){
		cout<<"base fun3!\n";
	}

	int a;
};

class Child:public Base
{
public:
	void fun1(){
		cout<<"Child fun1\n";
	}
	void fun2(){
		cout<<"Child fun2\n";
	}
	virtual void fun4(){
		cout<<"Child fun4\n";
	}
};


int _tmain(int argc, _TCHAR* argv[])
{
	Base* p=new Child;
	p->fun1();
	p->fun2();
	p->fun3();

	system("pause");
	return 0;
}

運行結果:

結合上面的內存布局:

其實,在new Child時構造了一個子類的對象,子類對象按上面所講,在構造函數期間完成虛函數表指針vfptr指向Child類的虛函數表,將這個對象的地址賦值給了Base類型的指針p,當調用p->fun1()時,發(fā)現是虛函數,調用虛函數指針查找虛函數表中對應虛函數的地址,這里就是&Child::fun1。調用p->fun2()情況相同。調用p->fun3()時,子類并沒有重寫父類虛函數,但依舊通過調用虛函數指針查找虛函數表,發(fā)現對應函數地址是&Base::fun3。所以上面的運行結果如上圖所示。

到這里,你是否已經明白為什么指向子類實例的基類指針可以調用子類(虛)函數?每一個實例對象中都存在一個vfptr指針,編譯器會先取出vfptr的值,這個值就是虛函數表vftable的地址,再根據這個值來到vftable中調用目標函數。所以,只要vfptr不同,指向的虛函數表vftable就不同,而不同的虛函數表中存放著對應類的虛函數地址,這樣就實現了多態(tài)的”效果“。

以上就是詳解C++虛函數的工作原理的詳細內容,更多關于C++虛函數的資料請關注腳本之家其它相關文章!

相關文章

  • windows下安裝QT及visual studio 2017搭建開發(fā)環(huán)境

    windows下安裝QT及visual studio 2017搭建開發(fā)環(huán)境

    這篇文章主要介紹了windows下安裝QT及visual studio 2017搭建開發(fā)環(huán)境,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-03-03
  • C語言數據結構之擴展字符詳解

    C語言數據結構之擴展字符詳解

    掌握C語言數據結構的關鍵在于理解其核心概念,擴展字符作為其中的重要一環(huán),對于編程人員來說至關重要,本指南將為您深入剖析擴展字符的相關知識,帶您輕松掌握C語言數據結構,讓我們一起探索這個令人著迷的領域吧!
    2024-03-03
  • C/C++?活動預處理器詳解

    C/C++?活動預處理器詳解

    預處理器是一些指令,指示編譯器在實際編譯之前所需完成的預處理,預處理的作用就是在代碼被編譯前對代碼做某些替換,這篇文章主要介紹了C/C++?活動預處理器,需要的朋友可以參考下
    2022-11-11
  • 從匯編看c++中引用與指針的使用分析

    從匯編看c++中引用與指針的使用分析

    在c++中,引用和指針具有相同的作用,都可以用來在函數里面給變函數外面對象或者變量的值,下面就來看他們的原理
    2013-05-05
  • c語言如何實現兩數之和

    c語言如何實現兩數之和

    這篇文章主要介紹了c語言如何實現兩數之和,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-07-07
  • c語言大小端(數據在內存中的存儲)

    c語言大小端(數據在內存中的存儲)

    大小端是內存存儲字節(jié)的兩種方式,一個是大端存儲,一個是小端存儲,本文主要介紹了c語言大小端,具有一定的參考價值,感興趣的可以了解一下
    2023-09-09
  • c++10進制轉換為任意2-16進制數字的實例

    c++10進制轉換為任意2-16進制數字的實例

    下面小編就為大家?guī)硪黄猚++10進制轉換為任意2-16進制數字的實例。小編覺得挺不錯的,現在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-06-06
  • C++?Qt開發(fā)之使用QNetworkAccessManager實現Web網頁訪問

    C++?Qt開發(fā)之使用QNetworkAccessManager實現Web網頁訪問

    Qt?是一個跨平臺C++圖形界面開發(fā)庫,利用Qt可以快速開發(fā)跨平臺窗體應用程序,本文主要介紹了如何運用QNetworkAccessManager組件實現Web網頁訪問,需要的可以參考下
    2024-03-03
  • C++詳解哈夫曼樹的概念與實現步驟

    C++詳解哈夫曼樹的概念與實現步驟

    給定N個權值作為N個葉子結點,構造一棵二叉樹,若該樹的帶權路徑長度達到最小,稱這樣的二叉樹為最優(yōu)二叉樹,也稱為哈夫曼樹(Huffman?Tree)。哈夫曼樹是帶權路徑長度最短的樹,權值較大的結點離根較近
    2022-04-04
  • C++中CString string char* char 之間的字符轉換(多種方法)

    C++中CString string char* char 之間的字符轉換(多種方法)

    在寫程序的時候,我們經常遇到各種各樣的類型轉換,比如 char* CString string 之間的互相轉換,這里簡單為大家介紹一下,需要的朋友可以參考下
    2017-09-09

最新評論