1. 程式人生 > >(七)、c++多型的實現過程

(七)、c++多型的實現過程

一、多型的實現機制

          當一個類 A 中有虛擬函式時,A 這個類會自動生成一張虛擬函式表記錄該 A 類所擁有的虛擬函式,並用一個指標 __vfptr 指向該表。當 A  類被當作父類讓子類 B 繼承時,顯然子類 B 也會繼承父類 A 的虛擬函式表指標  __vfptr。當子類 B 修改了虛擬函式,那麼父類虛擬函式表指標 __vfptr 所指向的內容就會被子類 B 修改。具體體現為,當父類 A 虛擬函式表中記錄了一個虛擬函式入口地址為0x11111111,在子類 B 中修改了這個虛擬函式,子類的這個虛擬函式入口地址為0x22222222, 那麼父類 A 的虛表指標當中的0x11111111

就會被修改為 0x22222222。這樣就達到了父類可以呼叫子類方法的神奇過程。當然子類新加的虛擬函式父類是無法呼叫的,子類會有自己的虛擬函式表,用於被他人繼承。

          當子類繼承父類時,也會把自身新加的虛擬函式加入到父類的虛擬函式表 中,但父類無法呼叫子類新加的虛擬函式,其原因在於父類當中並沒有子類新加虛擬函式的宣告,你雖然有函式入口地址,但通過父類去呼叫時,父類並不認識這些入口地址對應的函式。

         當子類繼承多個父類時,只會在繼承宣告的第一個父類的虛表中新增子類新加的虛擬函式。而子類對父類虛擬函式的修改會對所有父類虛表生效。

二、證明虛擬函式表指標的存在

// 64位系統下的測試
// 不加 virtual 關鍵字,該類大小為8,加了之後為16
#include <iostream>
using namespace std;

class Test
{
public:
	virtual void show()
	{
	}
private:
	int data;
};
int main()
{
	cout<<"Test 類的大小"<<sizeof (Test)<<"位元組"<<endl;
}

三、虛擬函式表的繼承機制:測試時觀察類中虛表的變化

// 如何測試:
// 1、在構造父類物件時,觀察this指標裡的虛表
// 2、觀察子類構造完成之後,子類繼承的虛表變化
#include <iostream>

using namespace std;

class Base
{
public:
	// 虛擬函式放到虛擬函式表裡 
	virtual void show()
	{
		cout<<"this is Base show()"<<endl;
	}
	virtual void print()
	{
		cout<<"this is Base print()"<<endl;
	}
};


class Test:public Base
{
public:
	// 多型覆蓋虛表 
	void show()
	{
		cout<<"this is Test show()"<<endl;
	}
	
};


int main(void)
{
	Test myTest;
	
	// 相容賦值
	Base *myBase = &myTest;
	Base &youBase = myTest;
	
	// 以下都會呼叫子類方法 
	myBase->show();
	youBase.show(); 
	
	// 沒有重寫虛擬函式,那麼就繼續呼叫父類自身方法
	myBase->print();
	youBase.print(); 
} 

四、純虛擬函式

           首先明白一點虛擬函式都是針對於類來說的。在實際生活中,我們永遠無法通過不具體的動物生出新的動物物件,只有當動物具體到某一物種時才可以產生新的物件。就比如說你說動物生了一個動物,這句話是毫無意義的,你不知道新生動物的外形,習慣等等有用資訊,但你說狗生了一隻狗,那麼你就可以想象新生的狗的樣子,你也會知道新生的狗喜歡吃屎這個特點。          但是所有的物種都是由動物派生而來,但是我們又不能把動物的特點確定下來。為了解決這類問題,純虛擬函式就應運而生。

            純虛擬函式寫法:virtual 返回值 函式名(引數列表) = 0;

測試程式碼

#include <iostream>

using namespace std;

class Animal
{
public:
	// 純虛擬函式相當於佔位置,使虛表內的函式地址入口為NULL
	// 那麼就意味著,只要該函式地址入口為 NULL 就不能通過他構造物件 
	virtual void eat() = 0;
	virtual void sleep() = 0;
};

class Dog: public Animal
{
public:
	void eat()
	{
		cout<<"this is Dog eat()"<<endl;
	}
	void sleep()
	{
		cout<<"this is Dog sleep()"<<endl;
	}
};

class Pig: public Animal
{
public:
	void eat()
	{
		cout<<"this is Pig eat()"<<endl;
	}
	void sleep()
	{
		cout<<"this is Pig sleep()"<<endl;
	}
};

// 通用程式碼 
void fun(Animal *pt)
{
	pt->eat();
	pt->sleep();
}


int main(void)
{
	// Animal myAnimal;  // 無法通過動物產生新的動物
	                     // 因為新生的動物不具有現實意義 
	Pig myPig;
	Dog myDog;
	
	// 根據不同物種呼叫相應的方法 
	fun(&myPig);
	fun(&myDog);
}