向上類型轉換
取一個對象的位址(或指針或引用),并看作基類的位址,這被稱為向上類型轉換(upcasting),因為繼承樹是以基類為頂點的。
我們還看到一個問題出現,這表現在下面的代碼中:
//: C15:Instrument2.cpp
// From Thinking in C++, 2nd Edition
// Available at http://www.BruceEckel.com
// (c) Bruce Eckel 2000
// Copyright notice in Copyright.txt
// Inheritance & upcasting
#include <iostream>
using namespace std;
enum note { middleC, Csharp, Eflat }; // Etc.
class Instrument {
public:
void play(note) const {
cout << "Instrument::play" << endl;
}
};
// Wind objects are Instruments
// because they have the same interface:
class Wind : public Instrument {
public:
// Redefine interface function:
void play(note) const {
cout << "Wind::play" << endl;
}
};
void tune(Instrument& i) {
// ...
i.play(middleC);
}
int main() {
Wind flute;
tune(flute); // Upcasting
} ///
運作程式輸出為:
Instrument::play
顯然這不是所希望的輸出,因為我們知道這個對象實際上是Wind而不隻是一個Instrument。這個調用應當輸出Wind::play。為此,由Instrument派生的任何對象無論它處于什麼位置都應當使用它的play版本。
函數調用綁定
上面程式中的問題是早捆綁引起的,因為編譯器在隻有Instrument位址時它不知道正确的調用函數。
解決方法被稱為晚捆綁(late binding),這意味着捆綁在運作時發生,基于對象的類型。晚捆綁又稱為動态捆綁(dynamic binding)或運作時捆綁(runtime binding)。當一個語言實作晚捆綁時,必須有一種機制在運作時确定對象的類型和合适的調用函數。這就是,編譯器還不知道實際的對象類型,但它插入能找到和調用正确函數體的代碼。晚捆綁機制因語言而異,但可以想象,一些種類的類型資訊必須裝在對象自身中。稍後将會看到它是如何工作的。
虛函數
對于特定的函數,為了引起晚捆綁,C++要求在基類中聲明這個函數時使用virtual關鍵字。晚捆綁隻對virtual起作用,而且隻發生在我們使用一個基類的位址時,并且這個基類中有virtual函數,盡管它們也可以在更早的基類中定義。
僅僅在函數聲明時需要使用關鍵字virtual,定義時并不需要。如果一個函數在基類中被聲明為virtual,那麼所有的派生類中它都是virtual。在派生類中virtual函數的重定義通常稱為重寫(override)。
注意:僅需要在基類中聲明一個函數為virtual。調用所有比對基類聲明行為的派生類函數都将使用虛機制。雖然可以在派生類相應函數聲明前使用關鍵字virtual(這也是無害的),但這樣會使程式段顯得備援和混亂。
//: C15:Instrument3.cpp
// From Thinking in C++, 2nd Edition
// Available at http://www.BruceEckel.com
// (c) Bruce Eckel 2000
// Copyright notice in Copyright.txt
// Late binding with the virtual keyword
#include <iostream>
using namespace std;
enum note { middleC, Csharp, Cflat }; // Etc.
class Instrument {
public:
virtual void play(note) const {//隻在這裡聲明了virtual
cout << "Instrument::play" << endl;
}
};
// Wind objects are Instruments
// because they have the same interface:
class Wind : public Instrument {
public:
// Override interface function:
void play(note) const {//這裡不用再聲明virtual,當然聲明也不會錯
cout << "Wind::play" << endl;
}
};
void tune(Instrument& i) {
// ...
i.play(middleC);
}
int main() {
Wind flute;
tune(flute); // Upcasting
system("pause");
} ///:
這個檔案除了增加了virtual關鍵字之外,一切與Instrument2.cpp相同,但結果明顯不一樣。現在的輸出是Wind::play。