1. 程式人生 > >c++中的多型和多型物件模型

c++中的多型和多型物件模型

1.什麼是多型?

多型顧名思義,就是“多種形態”

在c++中,我們是通過虛擬函式來實現多型的

那麼什麼是虛擬函式呢?

虛擬函式就是在類的成員函式的前面加上virtual關鍵字,那麼該成員函式就是虛擬函式

在c++中多型形成的條件是什麼呢?

1.虛擬函式的重寫;

2.父類的指標或引用呼叫重寫的虛擬函式。

class A
{
public:
	
        virtual void f1()
	{
		cout<<"A::f1()"<<endl;
	}
};

class B : public A
{
public:
	virtual void f1()
	{
		cout<<"B::f1()"<<endl;
	}
};

void Test()
{
	B b;//b是一個子類物件
	A *p = &b;//此時形成了多型,這個時候呼叫函式與型別無關,只與指向物件有關,指向誰就呼叫誰
}
在這裡我們進行了虛擬函式的重寫,什麼是虛擬函式的重寫呢?

當子類定義了一個和父類完全相同的虛擬函式(返回值、函式名、可變引數列表)時,則稱為重寫。

特例:協變 A類虛擬函式的返回值可定義為A類的指標或引用 B類也是可以定義為B類的。(可以看做是切片型別)

//特例
class A
{
public:
	A* fun()
	{

	}
};
class B : public A
{
public:
	B* fun()
	{

	}
	//此時也構成了重寫
};
2.單繼承&多繼承

1. 單繼承:一個類只有一個直接父類時稱這個繼承關係為單繼承
2. 多繼承:一個類有兩個或以上直接父類時稱這個繼承關係為多繼承

在這裡需要了解一下虛表

虛表就是存了虛擬函式的表

虛擬函式能夠實現多型的重要原因是有一個虛表指標指向了虛擬函式表,這樣就可以通過指標來找到xuhans

單繼承

//列印虛表
typedef void(*V_FUNC)(); //定義一個函式指標
void PrintVtable(int* vtable)
{
	printf("vtable 0x%p\n",vtable);
	int** ppvtable = (int**)vtable;
	for(size_t i=0; ppvtable[i]!=0; i++)
	{
		printf("vatable[%u]::0x%p->",i,ppvtable[i]);
		V_FUNC f = (V_FUNC)ppvtable[i];
		f();
	}
}



//單繼承
class Base
{
public:
	virtual void fun1()
	{
		cout<<"Base::fun1()"<<endl;
	}
	virtual void fun2()
	{
		cout<<"Base::fun2()"<<endl;
	}
private:
	int _a;
};
class Derive : public Base
{
public:
	virtual void fun1()
	{
		cout<<"Derive::fun1()"<<endl;
	}
	virtual void fun3()
	{
		cout<<"Derive::fun1()"<<endl;
	}
};

int main()
{
	Base b;
	Derive d;
	PrintVtable(*((int**)&b));
	PrintVtable(*((int**)&d));
	return 0;
}
通過除錯監視視窗,我們可以發現單繼承的物件模型


多繼承

//列印虛表
typedef void(*V_FUNC)(); //定義一個函式指標
void PrintVtable(int* vtable)
{
	printf("vtable 0x%p\n",vtable);
	int** ppvtable = (int**)vtable;
	for(size_t i=0; ppvtable[i]!=0; i++)
	{
		printf("vatable[%u]::0x%p->",i,ppvtable[i]);
		V_FUNC f = (V_FUNC)ppvtable[i];
		f();
	}
}
//多繼承
class Base1
{
public:
	void virtual Fun1()
	{
		cout<<"Base1::Fun1()"<<endl;
	}
	void virtual Fun2()
	{
		cout<<"Base1::Fun2()"<<endl;
	}
private:
	int _b;
};
class Base2
{
public:
	void virtual Fun1()
	{
		cout<<"Base2::Fun1()"<<endl;
	}
	void virtual Fun3()
	{
		cout<<"Base2::Fun3()"<<endl;
	}
private:
	int _b;
};
class Derive:public Base1,public Base2
{
public:
	void virtual Fun1()
	{
		cout<<"Derive::Fun1()"<<endl;//覆蓋
	}
	void virtual Fun3()
	{
		cout<<"Derive::Fun3()"<<endl;
	}
private:
	int _d;
};

int main()
{
	Base1 b1;
	Base2 b2;
	Derive d;
	PrintVtable(*((int**)&b1));
	PrintVtable(*((int**)&b2));
	PrintVtable(*((int**)&d));
	PrintVtable(*((int**)((char*)&d+sizeof(Base1))));
	return 0;
}
多繼承及其物件模型


3.菱形虛擬繼承

//列印虛表
typedef void(*V_FUNC)(); //定義一個函式指標
void PrintVtable(int* vtable)
{
	printf("vtable 0x%p\n",vtable);
	int** ppvtable = (int**)vtable;
	for(size_t i=0; ppvtable[i]!=0; i++)
	{
		printf("vatable[%u]::0x%p->",i,ppvtable[i]);
		V_FUNC f = (V_FUNC)ppvtable[i];
		f();
	}
}
//菱形繼承
class Base
{
public:
	void virtual Fun1()
	{
		cout<<"Base::Fun1()"<<endl;
	}
	void virtual Fun2()
	{
		cout<<"Base::Fun2()"<<endl;
	}
public:
	int _b;
};
class Derive1:virtual public Base
{
public:
	void virtual Fun1()
	{
		cout<<"Derive1::Fun1()"<<endl;
	}
	void virtual Fun3()
	{
		cout<<"Derive1::Fun3()"<<endl;
	}
public:
	int _d1;
};
class Derive2:virtual public Base
{
public:
	void virtual Fun1()
	{
		cout<<"Derive2::Fun1()"<<endl;
	}
	void virtual Fun4()
	{
		cout<<"Derive2::Fun4()"<<endl;
	}
public:
	int _d2;
};
class Derive:public Derive1,public Derive2
{
public:
	void virtual Fun1()
	{
		cout<<"Derive::Fun1()"<<endl;//覆蓋
	}

	void virtual Fun5()
	{
		cout<<"Derive::Fun5()"<<endl;
	}
public:
	int _d;
};
int main()
{
	Derive d;
	d._b = 2;
	d._d1 = 3;
	d._d2 = 4;
	d._d = 5;
	PrintVtable(*((int**)&d));
	return 0;

}

菱形虛擬繼承的物件模型

這裡寫圖片描述

在我們的菱形虛擬繼承中,又有虛表也有虛基表,那麼就需要我們在編譯程式碼中看看記憶體中的狀況,在圖中我們可以看到,我們把基類的物件存為共有的,因此需要用虛基表用偏移量找到他。這裡注意虛擬函式的重寫,因為看的是Derive的物件模型,所以在繼承的時候,有的函式進行了重寫。