天天看點

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

繼續閱讀