取一个对象的地址(或指针或引用),并看作基类的地址,这被称为向上类型转换(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。