因為魚的呼吸是吐泡泡,和一般動物的呼吸不太一樣,所以我們在fish類中重新定義breathe方法。我們希望如果對象是魚,就調用fish類的breathe()方法,如果對象是動物,那麼就調用animal類的breathe()方法。程序代碼如例2-16所示(EX08.CPP)。
例2-16
#include <iOStream.h>
class animal
{
public:
void eat()
{
cout<<"animal eat"<<endl;
}
void sleep()
{
cout<<"animal sleep"<<endl;
}
void breathe()
{
cout<<"animal breathe"<<endl;
}
};
class fish:public animal
{
public:
void breathe()
{
cout<<"fish bubble"<<endl;
}
};
void fn(animal *pAn)
{
pAn->breathe();
}
void main()
{
animal *pAn;
fish fh;
pAn=&fh;
fn(pAn);
}
我們在fish類中重新定義了breathe()方法,采用吐泡泡的方式進行呼吸。接著定義了一個全局函數fn(),指向animal類的指針作為fn()函數的參數。在main()函數中,定義了一個fish類的對象,將它的地址賦給了animal類的指針變量pAn,然後調用fn()函數。看到這裡,我們可能會有些疑惑,照理說,C++是強類型的語言,對類型的檢查應該是非常嚴格的,但是,我們將fish類的對象fh的地址直接賦給指向animal類的指針變量,C++編譯器居然不報錯。這是因為fish對象也是一個animal對象,將fish類型轉換為animal類型不用強制類型轉換,C++編譯器會自動進行這種轉換。反過來,則不能把animal對象看成是fish對象,如果一個animal對象確實是fish對象,那麼在程序中需要進行強制類型轉換,這樣編譯才不會報錯。
讀者可以猜想一下例2-16運行的結果,輸出的結果應該是“animal breathe”,還是“fish bubble”呢?
運行這個程序,你將看到如圖2.12所示的結果。
圖2.12 EX09程序的運行結果(一)
為什麼輸出的結果不是“fish bubble”呢?這是因為在我們將fish類的對象fh的地址賦給pAn時,C++編譯器進行了類型轉換,此時C++編譯器認為變量pAn保存就是animal對象的地址。當在fn函數中執行pAn->breathe()時,調用的當然就是animal對象的breathe函數。
為了幫助讀者更好地理解對象類型的轉換,我們給出了fish對象內存模型,如圖2.13所示。
當我們構造fish類的對象時,首先要調用animal類的構造函數去構造animal類的對象,然後才調用fish類的構造函數完成自身部分的構造,從而拼接出一個完整的fish對象。當我們將fish類的對象轉換為animal類型時,該對象就被認為是原對象整個內存模型的上半部分,也就是圖2.13中的“animal的對象所占內存”。當我們利用類型轉換後的對象指針去調用它的方法時,自然也就是調用它所在的內存中的方法。因此,出現如圖2.12所示的結果,也就順理成章了。
現在我們在animal類的breathe()方法前面加上一個virtual關鍵字,結果如例2-17所示。
例2-17
#include <iOStream.h>
class animal
{
public:
void eat()
{
cout<<"animal eat"<<endl;
}
void sleep()
{
cout<<"animal sleep"<<endl;
}
virtual void breathe()
{
cout<<"animal breathe"<<endl;
}
};
class fish:public animal
{
public:
void breathe()
{
cout<<"fish bubble"<<endl;
}
};
void fn(animal *pAn)
{
pAn->breathe();
}
void main()
{
animal *pAn;
fish fh;
pAn=&fh;
fn(pAn);
}
用virtual關鍵字申明的函數叫做虛函數。運行例2-17這個程序,結果調用的是fish類的呼吸方法:
圖2.14 EX08程序的運行結果(二)
這就是C++中的多態性。當C++編譯器在編譯的時候,發現animal類的breathe()函數是虛函數,這個時候C++就會采用遲綁定(late binding)技術。也就是編譯時並不確定具體調用的函數,而是在運行時,依據對象的類型(在程序中,我們傳遞的fish類對象的地址)來確認調用的是哪一個函數,這種能力就叫做C++的多態性。我們沒有在breathe()函數前加virtual關鍵字時,C++編譯器在編譯時就確定了哪個函數被調用,這叫做早期綁定(early binding)。
C++的多態性是通過遲綁定技術來實現的,關於遲綁定技術,讀者可以參看相關的書籍,在這裡,我們就不深入講解了。
C++的多態性用一句話概括就是:在基類的函數前加上virtual關鍵字,在派生類中重寫該函數,運行時將會根據對象的實際類型來調用相應的函數。如果對象類型是派生類,就調用派生類的函數;如果對象類型是基類,就調用基類的函數。