第15章 多态性和虚函数(一)
向上类型转换
取一个对象的地址(或指针或引用),并看作基类的地址,这被称为向上类型转换(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。