這裏簡單介紹下什麼是多態,多態的構成條件,多態原理以及多態的對象模型。在介紹多態之前,先簡單的介紹下什麼是虛函數。
虛函數
類的成員函數前面加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. 同類型的對象虛表相同(共享同一塊),拷貝時只拷貝成員變量不拷貝虛表。