關鍵詞:多態
將多態之前先來說幾個概念:
-
聯編
聯編是指一個程序模塊、代碼之間互相關聯的過程。
聯編又分爲兩種:
- 靜態聯編(static binding),是程序的匹配、連接在編譯階段實現,也稱爲早期匹配。重載函數使用靜態聯編。
- 動態聯編是指程序聯編推遲到運行時進行,所以又稱爲晚期聯編(遲綁定)switch 語句和 if 語句是動態聯編的例子。
結合實際,
- C++與C相同,是靜態編譯型語言
- 在編譯時,編譯器自動根據指針的類型判斷指向的是一個什麼樣的對象;所以編譯器認爲父類指針指向的是父類對象。
- 由於程序沒有運行,所以不可能知道父類指針指向的具體是父類對象還是子類對象
從程序安全的角度,編譯器假設父類指針只指向父類對象,因此編譯的結果爲調用父類的成員函數。這種特性就是靜態聯編。
於是就有思考問題如果子類定義了與父類中原型相同的函數會發生什麼?
舉個例子:
class A
{
public:
virtual void print() //虛函數
{
cout << "AAAAAAAAAAAAAAAAAA" << endl;
}
};
class B :public A
{
public:
void print()
{
cout << "BBBBBBBBBBBBBBBBBBBB" << endl;
}
void show()
{
cout << "bbbbbbbbbbbbbb" << endl;
}
};
int main()
{
A *pa;
pa = new A; //基類指針指向基類對象
pa->print();
//pa->show(); //基類指針無法指向派生類對象,向下轉型有風險
delete pa; //基類指針指向派生類對象
pa = new B;
pa->print(); //若print()未定義爲虛函數,編譯時已經確定pa 爲A類指針,靜態聯編
//pa->show();
delete pa;
system("pause");
return 0;
}
上述代碼中,若基類中函數print()未聲明爲虛函數,編譯時已經確定pa 爲A類指針,這就是靜態聯編,其結果如下:
聲明爲虛函數後:
這就是多態,C++中通過virtual關鍵字對多態進行支持,使用virtual聲明的函數被重寫後即可展現多態特性
所以,顯而易見
多態成立的三個條件
- 要有繼承
- 要有虛函數重寫
- 用父類指針(父類引用)指向子類對象
什麼是函數重寫?
在子類中定義與父類中原型相同的函數,函數重寫只發生在父類與子類之間
重載與重寫的區別
重載:
- 同一個作用域;
- 子類無法重載父類函數,父類同名函數將被覆蓋;
- 重載是在編譯期間根據參數類型和個數決定;
重寫:
- 發生於父類、子類之間;
- 父類和子類函數有相同的函數原型;
- 使用virtual關鍵字聲明後能夠產生多態;
- 運行期間根據具體對象類型決定調用的函數。
虛析構函數
將析構函數聲明爲需析構函數的作用:可以通過基類指針釋放派生類對象
多態原理
當類中聲明虛函數時,編譯器會在類中生成一個虛函數表,虛函數表是一個存儲類成員函數指針的數據結構,由編譯器自動生成與維護的virtual成員函數會被編譯器放入虛函數表中,當存在虛函數時,每個對象中都有一個指向虛函數表的指針(C++編譯器給父類對象、子類對象提前佈局vptr指針;當進行函數調用時,C++編譯器不需要區分子類對象或者父類對象,只需要再base指針中,找vptr指針即可。)
VPTR一般作爲類對象的第一個成員
純虛函數
純虛函數是一個在基類中說明的虛函數,在基類中沒有定義,要求任何偶愛盛磊都定義自己的版本,類似於JAVA中的接口
純虛函數的說明形式:
class Sharp
{
protected:
double x, y; //在圓中,xy都表示半徑,矩形中x表示矩形的長,y表示矩形的寬
double s; //面積
public:
virtual double GetArea() = 0; //純虛函數
Sharp(double _x, double _y);
};
抽象類
一個具有純虛函數的基類稱爲抽象類,抽象類不能用於直接創建對象實例,可以聲明抽象類的指針和引用
派生類中必須實現基類中的純虛函數,否則它仍將被看作一個抽象類
幾個注意事項:
-
構造函數無法實現多態
-
不要用基類指針指向派生類數組,父類p++與子類p++步長不同
-
抽象類不能用於直接創建對象實例