多態與多態對象模型

      這裏簡單介紹下什麼是多態,多態的構成條件,多態原理以及多態的對象模型。在介紹多態之前,先簡單的介紹下什麼是虛函數。

虛函數

    類的成員函數前面加virtual關鍵字,則這個成員函數稱爲虛函數。

    注:1. 除靜態成員函數   2. 內聯函數不能定義爲虛函數

   虛函數重寫:

    當在子類的定義了一個與父類完全相同的虛函數時,則稱子類的這個函數重寫(也稱覆蓋)了父類的這個虛函數。

   純虛函數

   在成員函數的形參後面寫上=0,則成員函數爲純虛函數。包含純虛函數的類叫做抽象類(也叫接口類),抽象類不能實例化出對象。純虛函數在派生類中重新定義以後,派生類才能實例化出對象。

   純虛函數強制重寫虛函數。

多態

    1.定義

       多態就是多形態。

       靜態多態就是重載,因爲是在編譯期決議確定的,所以稱爲靜態多態。

       動態多態就是通過繼承重寫基類的虛函數實現多態,因爲是在運行時決議的,所以稱爲冬天多態。

        當使用基類的指針或引用調用重寫的虛函數時,當指向父類調用的就是父類的虛函數,當指向子類調用的就是子類的虛函數。

     注:普通調用與類型有關,多態調用與對象有關。

    2.條件

        1.虛函數重寫  2.父類的指針和引用(指向父類調用父類,指向子類調用子類)

eg:

class Base
{
public :
	virtual void func1()
	{
		cout<<"Base::func1" <<endl;
	}

	virtual void func2()
	{
		cout<<"Base::func2" <<endl;
	}

public:
	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;
	}

public:
	int b ;
};

 

    這是一種運行期多態,即父類指針唯有在程序運行時才能知道所指的真正類型是什麼。這種運行期決議,是通過虛函數表來實現的。

注:

      1). 派生類重寫基類的虛函數實現多態,要求函數名、參數列表、返回值完全相同。

      2). 在基類中定義類虛函數,在該函數中虛函數始終保持虛函數的特性。

      3).四個默認成員函數,除了析構,都不要定義爲虛函數。析構函建議定義爲虛函數(能保證正確調用對應虛函數)。

why? 另外析構函數比較特殊,因爲派生類的析構函數跟基類的析構函數名稱不一樣,但是構成覆蓋,這裏是因爲編譯器做了特殊處理)因爲父類的指針可能指向父類對象也可能指向子類對象,爲了讓指向誰調用誰的析構,所用定義爲多態,否則會造成內存泄露。

     4). 靜態成員還是那戶不能定義爲虛函數。

多態對象模型

    使用指針訪問虛表

   eg:

class Base
{
public :
	virtual void func1()
	{
		cout<<"Base::func1" <<endl;
	}

	virtual void func2()
	{
		cout<<"Base::func2" <<endl;
	}

public:
	int a ;
};

typedef void(*V_FUNC)();

void PrintVTable(int** vtable)
{
	cout<<"===================================="<<endl;

	printf("虛函數表:%p\n", vtable);
	for (size_t i = 0; vtable[i] != 0; ++i)
	{
		printf("vfunc[%d]:%p->", i, vtable[i]);
		V_FUNC f = (V_FUNC)vtable[i];
		f();
	}
	cout<<"===================================="<<endl;
}

void Test()
{
	Base b;
	PrintVTable((int**)(*((int**)&b)));
}

 

分析:

 

 

單繼承對象模型

寫一個類繼承Base

eg:

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;
	}

public:
	int _b ;
};

 

分析:

    在C++對象模型中,對於一般繼承(這個一般是相對於虛擬繼承而言),若子類重寫(overwrite)了父類的虛函數,則子類虛函數將覆蓋虛表中對應的父類虛函數(注意子類與父類擁有各自的一個虛函數表);若子類並無overwrite父類虛函數,而是聲明瞭自己新的虛函數,則該虛函數地址將擴充到虛函數表最後(在vs中無法通過監視看到擴充的結果,不過我們通過取地址的方法可以做到,子類新的虛函數確實在父類子物體的虛函數表末端)。

  多繼承對象模型(非菱形繼承)

eg:

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(*V_FUNC)();

void PrintVTable(int** vtable)
{
	cout<<"===================================="<<endl;

	printf("虛函數表:%p\n", vtable);
	for (size_t i = 0; vtable[i] != 0; ++i)
	{
		printf("vfunc[%d]:%p->", i, vtable[i]);
		V_FUNC f = (V_FUNC)vtable[i];
		f();
	}
	cout<<"===================================="<<endl;
}

void Test()
{
	Derive d;
	PrintVTable((int**)(*((int**)&d)));
	PrintVTable((int**)(*(int**)((char*)&d+sizeof(Base1))));

}

 

分析:

 

    單繼承中(一般繼承),子類會擴展父類的虛函數表。在多繼承中,子類的虛函數被放在聲明的第一個基類的虛函數表中。

    注:

      1.C++中,編譯時多態主要是通過函數重載和運算符重載實現的。運行時多態性主要通過虛函數重寫實現。

      2.C++動態決議的虛擬機制中,使用的vtable就是一個用來保存虛成員函數的地址的函數指針數組。

      3. 同類型的對象虛表相同(共享同一塊),拷貝時只拷貝成員變量不拷貝虛表。

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章