虛擬函式的呼叫原理

weixin_45442331發表於2020-10-10

虛擬函式以及虛擬函式表

引言

​ 如果父類與子類有同名的方法,當父類指標指向子類物件時,通過該指標呼叫同名方法,會直接呼叫父類的方法,將父類中該方法定義為虛擬函式則可以解決這個問題,程式碼如下:

#include<iostream>

using namespace std;

class father
{
public:
	father()
	{
		cout << "呼叫father建構函式" << endl;
	}

	void eat()
	{
		cout << "我喜歡吃牛肉" << endl;
	}

};

class son:public father
{
public:
	son()
	{
		cout << "呼叫son類的建構函式" << endl;
	}

	void eat()
	{
		cout << "我喜歡吃羊肉" << endl;
	}
};
int main()
{
	father* father1;
	father1 = new son();
	father1->eat();
	return 0;
}

​ 執行結果如下:

在這裡插入圖片描述
​ 可以看出當father類指標指向son類物件時呼叫了同名的eat()方法,執行的是father的eat()方法,那麼怎麼樣才能執行son類的eat()方法呢?

​ 實現方法是在father類的eat()方法前加一個virtual關鍵字,使得該函式成為虛擬函式,程式碼如下:

virtual void eat()
	{
		cout << "我喜歡吃牛肉" << endl;
	}

​ 再次執行,結果如下:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-a7N1xjxa-1602216812578)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20201008231757152.png)]

​ 可以看到此時執行了子類的eat()方法,

虛擬函式的呼叫原理

繼承一個基類的情況

class father
{
public:
	virtual void func1() { cout << "呼叫func1" << endl; }
	virtual void func2() { cout << "呼叫func2" << endl; }
	virtual void func3() { cout << "呼叫func3" << endl; }
public:
	int x = 0;
	int y = 2;
};

class son :public father
{
public:
	void func1()override { cout << "呼叫son1" << endl; }
	virtual void son3() { cout << "呼叫son3" << endl; }

public:
	int i = 10;
	int j = 20;
};

​ 定義一個子類物件,子類物件的記憶體分佈如下圖:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-iCkjRVzZ-1602216812580)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20201008235755391.png)]

​ 根據上圖,子類物件son1的第一個成員存放了虛擬函式表的指標,因此可以根據son1的地址取出虛表指標來呼叫虛擬函式,程式碼實現如下:

int* pSon1 = (int*)*(int*)&son1;
	cout <<endl<< "呼叫第一個虛擬函式表" << endl;
	for (int i = 0; i < 4; i++)
	{
		(*((func_t*)pSon1+i))();
	}

​ 程式碼執行結果(完整程式碼最後附上):

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-cK39LPKE-1602216812584)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20201009000630235.png)]

多重繼承的情況

class father
{
public:
	virtual void func1() { cout << "呼叫func1" << endl; }
	virtual void func2() { cout << "呼叫func2" << endl; }
	virtual void func3() { cout << "呼叫func3" << endl; }
public:
	int x = 0;
	int y = 2;
};

class mother
{
public:
	virtual void handle1() { cout << "呼叫handle1" << endl; }
	virtual void handle2() { cout << "呼叫handle2" << endl; }
	virtual void handle3() { cout << "呼叫handle3" << endl; }
public:
	int m = 5;
	int n = 8;
};

class son :public father, public mother
{
public:
	void func1()override { cout << "呼叫son1" << endl; }
	void handle2()override { cout << "呼叫son2" << endl; }
	virtual void son3() { cout << "呼叫son3" << endl; }

public:
	int i = 10;
	int j = 20;
};

​ 在多重繼承的情況下,子類物件的記憶體分佈情況如下圖:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-LhbCuCco-1602216812585)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20201009003143015.png)]

​ 根據上圖可以由物件son1的地址來呼叫虛擬函式表2,程式碼如下:

cout << endl << "呼叫第二個虛擬函式表" << endl;
	int *pSon2 = (int*)*((int*)(&son1) + 3);
	for (int i = 0; i < 3; i++)
	{
		(*((func_t*)pSon2 + i))();
	}

​ 程式碼執行結果如下圖:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-YqsaJ59q-1602216812587)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20201009003205600.png)]

結論

​ 1.在多重繼承的情況下,子類物件會儲存多個虛擬函式表指標(取決於繼承了多少個父類),而且存放順序是根據繼承順序來的

​ 2.子類單獨定義的虛擬函式存放在第一個虛擬函式表的最後一個位置,同樣子類的資料成員也存放在最後

原始碼:https://github.com/1137185761/virtual-function.git

相關文章