天天看点

第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。

继续阅读