C++之多態

多態

多態就是指一種事物在不同時期的不同體現。

多態有兩張情況,動態多態靜態多態

靜態多態

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

靜態多態的實現方法有兩種:

1.重載
#include <iostream>

using namespace std;

int add(int left, int right)
{
    return left + right;
}

float add(float left, float right)
{
    return left + right;
}

int main()
{
    cout << add(10, 20) << endl;
    cout << add(3.13f, 2.14f) << endl;
    system("pause");
    return 0;
}
2.泛型編程

關於泛型編程我還沒有了解太多,會在以後的學習中瞭解並總結。

動態多態

動態綁定:在程序執行期間(非編譯期)判斷所引用對象的實際類型,根據其實際類型調用相應的方法。

使用virtual關鍵字修飾類的成員函數時,指明該函數爲虛函數,派生類需要重新實現,編譯器將實現動態綁定。
#include <iostream>
#include <Windows.h>

using namespace std;

class WashRoom
{
public:
    void GoToManWashRoom()
    {
        cout << "Man-->Please Left" << endl;
    }
    void GoToWomanWashRoom()
    {
        cout << "Woman-->Please Right" << endl;
    }
};
class Person
{
public:
    virtual void GoToWashRoom(WashRoom & washRoom) = 0;
};
class Man :public Person
{
public:
    virtual void GoToWashRoom(WashRoom & washRoom)
    {
        washRoom.GoToManWashRoom();
    }
};
class Woman :public Person
{
public:
    virtual void GoToWashRoom(WashRoom & washRoom)
    {
        washRoom.GoToWomanWashRoom();
    }
};
void FunTest()
{
    WashRoom washRoom;
    for (int iIdx = 1; iIdx <= 10; ++iIdx)
    {
        Person* pPerson;
        int iPerson = rand() % iIdx;
        if (iPerson & 0x01)
            pPerson = new Man;
        else
            pPerson = new Woman;
        pPerson->GoToWashRoom(washRoom);
        delete pPerson;
        pPerson = NULL;
        Sleep(1000);
    }
}
int main()
{
    FunTest();
    system("pause");
    return 0;
}

但是想要實現多態綁定必須滿足幾點條件:

1.通過基類類型的引用或者指針調用虛函數

對象的類型

靜態類型:對象聲明時的類型,在編譯期間確定。

動態類型:目前所指對象的類型,在運行期間確定。
class CBase
{};
class CDe1:public CBase
{};

class CDe2:public CBase
{};



int main()
{
    //pd1的靜態類型是CDe1*,動態類型是CDe1
    CDe1* pd1 = new CDe1;
    //pb的靜態類型是CBase*,動態類型是CDe1
    CBase* pb = pd1;
    CDe2* pd2 = new CDe2;
    //pb的動態類型現在是CDe2
    pb = pd2;
    system("pause");
    return 0;
}

2.必須是虛函數並且派生類一定要對虛函數進行重寫

class Base
{
public :
   virtual void FunTest1( int _iTest)
   {
      cout <<"Base::FunTest1()" << endl;
   }
   void FunTest2(int _iTest)
   {
      cout <<"Base::FunTest2()" << endl;
   }
   virtual void FunTest3(int _iTest1)
   {
      cout <<"Base::FunTest3()" << endl;
   }
   virtual void FunTest4( int _iTest)
   {
      cout <<"Base::FunTest4()" << endl;
    }
};

class Derived :public Base
{
public :
   //正確
   virtual void FunTest1(int _iTest)
   {
      cout <<"Derived::FunTest1()" << endl;
   }
   //錯誤,基類中不是虛函數,
   virtual void FunTest2(int _iTest)
   {
      cout <<"Derived::FunTest2()" << endl;
   }
   //錯誤,不是虛函數
   void FunTest3(int _iTest1)
   {
   cout <<"Derived::FunTest3()" << endl;

   }
   //錯誤,參數列表不一樣
   virtual void FunTest4(int _iTest1,int _iTest2)
   {
      cout<<"Derived::FunTest4()"<<endl;
   }
};

在靜態多態的實現中有一個重載,在動態多態中有一個重寫,我們也就對繼承體系中同名成員函數的關係來個小的總結

  1. 重載

     1.在同一作用域中
    

    2.函數名字要相同,但是參數列表不同
    3.返回值可以不同

  2. 重寫

      1.在同一作用域中
    

    2.函數名字,參數列表和返回值都相同,但是有個特例(協變)
    3.必須有virtual關鍵字
    4.訪問限定符可以不同

  3. 重定義

    1.在不同作用域中
    2.函數名字相同
    3.在基類和派生類中,只要不構成重寫的就是重定義。

純虛函數

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

在一開始我們定義的washroom類中就有純虛函數

class Person
{
public:
    //純虛函數
    virtual void GoToWashRoom(WashRoom & washRoom) = 0;
};

剖析虛函數列表

#include <iostream>

using namespace std;

class C
{
public:
    C()
    {
        i = 10;
        cout << "this = " << this << endl;
    }
    virtual ~C(){};
private:
    int i;
};

int main()
{
    C c;
    cout << sizeof(c) << endl;
    system("pause");
    return 0;
}

如果在類中我們定義的不是虛擬析構函數,是普通析構函數的話,類的大小應該爲4,就是類C的成員變量i的大小,

這裏寫圖片描述

從圖中我們可以看到,此類的大小爲8,
在類成員變量的上面多出了4個字節的內容,這和我們之前解決菱形問題二義性引入虛擬繼承的類似

 對於有虛函數的類,編譯器都會維護一張虛函數表(虛表),對象的前四個字節是指向虛表的指針(虛表指針)

CTest類的內存佈局:

這裏寫圖片描述

這裏寫圖片描述

內存2中的00 00 00 00爲虛表的結束標誌

單繼承下的虛表列表

class Base
{
public:
virtual void FunTest0(){cout<<"Base::FunTest0()";}
virtual void FunTest1(){cout<<"Base::FunTest1()";}
virtual void FunTest2(){cout<<"Base::FunTest2()";}
};
class Derived:public Base
{
public:
virtual void FunTest4(){cout<<"Derived::FunTest4()" ;}
virtual void FunTest5(){cout<<"Derived::FunTest5()" ;}
virtual void FunTest6(){cout<<"Derived::FunTest6()" ;}
};
typedef void (*VirtualFunPtr)();
void PrintVirtualTable(Base& b, const string& strInfo)
{
cout<< strInfo<<endl;
VirtualFunPtr* pVirtualFun = (VirtualFunPtr*)((*( int *)&b));
while(*pVirtualFun)
{
(*pVirtualFun)();
cout<< ": "<<pVirtualFun<<endl;
pVirtualFun++;
}
cout<<endl;
}
int main()
{
Base b;
Derived d;
PrintVirtualTable(b, "Base Vpf:");
PrintVirtualTable(d, "Derived Vpf:");
return 0;
}

這裏寫圖片描述
基類內存佈局
這裏寫圖片描述
基類的虛表
這裏寫圖片描述

派生類的內存佈局
這裏寫圖片描述

派生類的虛表
這裏寫圖片描述

由圖可知,虛函數是按照其聲明順序存在於虛表中的
在派生類中,前面是基類的虛函數,後面是派生類的虛函數。

這是在沒有發生重寫的情況下。

下面就來看看重寫的情況

lass Base
{
public:
    virtual void FunTest0(){ cout << "Base::FunTest0()"; }
    virtual void FunTest1(){ cout << "Base::FunTest1()"; }
    virtual void FunTest2(){ cout << "Base::FunTest2()"; }
};
class Derived :public Base
{
public:
    virtual void FunTest4(){ cout << "Derived::FunTest4()"; }
    virtual void FunTest1(){ cout << "Derived::FunTest1()"; }
    virtual void FunTest6(){ cout << "Derived::FunTest6()"; }
};
typedef void(*VirtualFunPtr)();
void PrintVirtualTable(Base& b, const string& strInfo)
{
    VirtualFunPtr* pVirtualFun = (VirtualFunPtr*)((*(int *)&b));
    while (*pVirtualFun)
    {
        (*pVirtualFun)();
        cout << ": " << pVirtualFun << endl;
        pVirtualFun++;
    }
    cout << endl;
}
int main()
{
    Base b;
    Derived d;
    PrintVirtualTable(b, "Base Vpf:");
    PrintVirtualTable(d, "Derived Vpf:");
    system("pause");
    return 0;
}

這裏寫圖片描述

多繼承的情況


using namespace std;

class Base1
{
public:
    Base1(){ _b1 = 1; }
    virtual void PrintBase1(){ cout << "Base1::PrintBase1()"; }
    int _b1;
};
class Base2
{
public:
    Base2(){ _b2 = 2; }
    virtual void PrintBase2(){ cout << "Base2::PrintBase2()"; }
    int _b2;
};
class Derived :public Base1, public Base2
{
public:
    Derived(){ _d = 3; }
    virtual void PrintDdrived(){ cout << "Derived::PrintDerived()"; }
    int _d;
};
typedef void(*VirtualFunPtr)();
void PrintVt(Base1& b, const string& strInfo)
{
    VirtualFunPtr* pVirtualFun = (VirtualFunPtr*)((*(int *)&b));
    while (*pVirtualFun)
    {
        (*pVirtualFun)();
        cout << ": " << pVirtualFun << endl;
        pVirtualFun++;
    }
    cout << endl;
}
int main()
{
    Base1 b1;
    Base2 b2;
    Derived d;
    PrintVt(b1, "Base Vpf:");
    PrintVt(d, "Derived Vpf:");
    return 0;
}

其內存佈局

這裏寫圖片描述

這裏寫圖片描述

從圖中我i們可以看到,派生類的虛表是接在第一個繼承的基類的後面的。

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