多態
多態就是指一種事物在不同時期的不同體現。
多態有兩張情況,動態多態和靜態多態
靜態多態
在編譯期間完成的,編譯器根據函數實參的類型(可能會進行隱式類型轉換),可推斷出要調用那個函數,如果有對應的函數就調用該函數,否則出現編譯錯誤。
靜態多態的實現方法有兩種:
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.在同一作用域中
2.函數名字要相同,但是參數列表不同
3.返回值可以不同重寫
1.在同一作用域中
2.函數名字,參數列表和返回值都相同,但是有個特例(協變)
3.必須有virtual關鍵字
4.訪問限定符可以不同重定義
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們可以看到,派生類的虛表是接在第一個繼承的基類的後面的。