動態多態以及多態調用過程

        多態分爲靜態多態與動態多態。靜態多態包括函數重載,泛型編程。動態是虛函數的使用。

        靜態多態是指編譯器在編譯期間完成的,編譯器根據函數實參的類型(可能會進行隱式類型轉換),可推斷出要調用的那個函數,如果有對應的函數就調用該函數,否則會出現編譯錯誤。

        動態多態,我們在這裏主要說明的是動態多態。

        動態綁定:在程序執行期間(非編譯器)判斷所引用對象的實際類型,根據其實際類型調用相應的方法。使用virtual關鍵字修飾類的成員函數時,指明該函數爲虛函數,派生類需要重新實現,編譯器將實現動態綁定。動態綁定的條件是基類中的函數必須是虛函數,且派生類一定要重寫基類的虛函數;第二點是通過基類類型的引用或者指針調用虛函數。

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

         eg: virtual void Display() = 0;

       繼承體系同名成員函數的關係

        重載:在同一作用域,函數名相同,參數不同,返回值可以不同。

        重寫(覆蓋):函數名相同,參數相同,返回值相同(協變除外);基類函數必須有virtual關鍵字;訪問修飾符可以不同。

        重定義(隱藏):在不同的作用域中(分別在基類和派生類);函數名相同;在基類和派生類中只要不構成重寫就是重定義。

        總結(以下是有關動態多態的總結)

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

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

        3.只有類的非靜態成員函數才能定義是虛函數,靜態成員函數不能定義爲虛函數。

        4.如果在類外定義虛函數,只能在聲明函數時加virtual關鍵字,定義時不用加。

        5.構造函數不能定義爲虛函數,雖然可以將operator=定義爲虛函數,但最好不要這麼做,使用時容易混淆。

        6.不要再構造函數和析構中調用虛函數,在析構函數和構造函數中對象是不完整的,可能會出現未定義的行爲。

        7.最好將基類的析構函數聲明爲虛函數。

        8.虛表是所有類對象實例共用的。

        多態實現原理

        帶有虛函數的類比正常的類多了四個字節,裏面保存了一個指針。如下圖所示:

      

        派生類虛函數表的創建過程是要創建一個派生類的對象先要調用基類的構造函數,此時虛表指針爲基類的虛表指針,基類構造函數調用完成後,再把基類的虛表指針替換爲派生類的虛表指針,從而創建派生類對象。在派生類構建虛函數表時,可以看成將基類的虛函數拷貝了一份,若在派生類中虛函數重寫,則基類的虛函數被派生類中重寫的虛函數所替換掉,若沒有構成重寫,將不會被覆蓋,且沒有重寫的虛函數在派生類虛函數表中的位置不變,若派生類中存在新的虛函數,則按照定義順序跟在已經被覆蓋的基類虛函數表的後面,此時派生類的虛函數創建成功。

        代碼實現:

 

class Base
{
	virtual void Funtest1()
	{
		cout << "Base::Funtest1()" << endl;
	}
	virtual void Funtest2()
	{
		cout << "Base::Funtest2()" << endl;
	}
	int _b;
};
class Derived:public Base
{
	void Funtest1()
	{
		cout << "Derived::Funtest1()" << endl;
	}
	void Funtest2()
	{
		cout << "Derived::Funtest2()" << endl;
	}
	void Funtest3()
	{
		cout << "Derived::Funtest3()" << endl;
	}
	virtual void Funtest4()
	{
		cout << "Derived::Funtest4()" << endl;
	}
	int _d;
};

typedef void (*Fun)();

void Printvpf(Base& b)
{
	int* addr = (int*)*((int*)&b);
	Fun* pFun = (Fun*)addr;
	while (*pFun)
	{
		(*pFun)();
		pFun = (Fun*)++addr;
	}
}

int main()
{
	Base b;
	Derived d;
	Printvpf(b);
	Printvpf(d);
	return 0;
}

        如下圖所示:

        

         普通虛擬繼承的對象模型:

              

         在這裏,若派生類中沒有自己新加的虛函數,則不會創建第二個虛表指針。

         多繼承的虛擬繼承與上面的普通虛擬繼承對象模型類似,如果派生類中新加了虛函數,則新加的虛函數會放在第一個繼承基類虛表的後面。

         菱形虛擬繼承:

class B
{
public :
    virtual void FunTest1()
    {
        cout<<"B::FunTest1()"<<endl;
    }
    int _b;
};
class C1:virtual public  B
{
public :
    virtual void FunTest1()//重寫B
    {
        cout<<"C1::FunTest1()"<<endl;
    }
    virtual void FunTest2()//新加的虛函數
    {
        cout<<"C1::FunTest2()"<<endl;
    }
    int _c1;
};
class C2:virtual public B
{
public :
    virtual void FunTest1()//重寫B中虛函數
    {
        cout<<"C2::FunTest1()"<<endl;
    }
    virtual void FunTest3()//新加虛函數
    {
        cout<<"C2::FunTest3()"<<endl;
    }
    int _c2;
};
class D:public C1,public C2
{
public :
    virtual void FunTest1()//重寫C1和C2中的虛函數
    {
        cout<<"D::FunTest1()"<<endl;
    }
    virtual void FunTest2()//重寫C1中的虛函數
    {
        cout<<"D::FunTest2()"<<endl;
    }
    virtual void FunTest3()//重寫C2中的虛函數
    {
        cout<<"D::FunTest3()"<<endl;
    }
    int _d;
};
void Test1()
{
    D d;
    d._b = 1;
    d._c1 = 2;
    d._c2 = 3;
    d._d = 4;
}
int main()
{
    Test1();
    return 0;
}


              

         上圖則爲菱形虛擬繼承的對象模型。

          

          

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