C++程式是個物件導向的程式, 而繼承則是物件導向中的重要角色, 以下是最近採用的新寫法, 若以基礎類別宣告的指標, 指向衍生類別的物件, 若基礎類別與衍生類別有相同名稱的函式, 也就是說衍生類別複寫了該函式, 配合虛擬函式宣告, 它可以形成動態繫結(binding), 因此會依據函式本身去選用對應的函式, 而非直接採用指標型別所屬函式
範例程式碼1
#include <iostream>
using namespace std;
class mammal {
public:
mammal () { cout << "Class mammal constructor\n"; }
virtual ~mammal () { cout << "Class mammal destructor\n"; }
void virtual speak() const { cout << "This is mammal's speaking\n"; }
};
class dog: public mammal {
public:
dog () { cout << "Class dog constructor\n"; }
virtual ~dog () { cout << "Class dog destructor\n"; }
void virtual speak() const { cout << "This is dog's speaking\n"; }
};
int main() {
mammal *pm[2];
// mammal
pm[0] = new mammal;
pm[0]->speak();
delete pm[0];
// dog
pm[1] = new dog;
pm[1]->speak();
delete pm[1];
return 0;
};
說明1: 宣告一個哺乳類(mammal)類別, 它除了建構子與解構子外, 只有提供speak函式
說明2: 宣告一個狗類(dog)類別, 它公開繼承mammal, 另外它還複寫了speak函式
說明3: 宣告mammal類別的解構子與speak函式為虛擬函式(virtual)
說明4: 主程式中, 以mammal宣告指標, 當指標指往mammal物件時, 並呼叫speak, 它會啟動mammal版本的speak; 若指向dog物件時, 並呼叫speak, 它會自動切換至dog版本的speak
說明5: 若取消mammal版本speak函式的虛擬宣告, 則同樣的程式碼, 即使指向dog物件, 仍舊啟動mammal版本的speak
說明6: 若取消mammal版本解構子的虛擬宣告, 則在delete pm[1]時, 會發現只有啟用mammal版本的解構子, 而依照順序由dog->mammal依次解構
說明7: 解構時會依據繼承的順序, 從衍生類別解構子開始呼叫起, 逐步往基本類別解構子移動; 而相反地, 建構時則與解構相反
這樣的寫法, 可以讓程式碼變成用同樣的名字來呼叫不同功能的函式, 使得較大規模程式開發, 可以更加模組化, 更有架構...
這樣的寫法裡面, 用基本類別的指標去呼叫基本類別的專屬函式, 並沒有問題...只是今天的問題是, 若是衍生類別有不同的函式(非複寫), 則會變成基礎類別的指標無法使用衍生類別的擴充函式, 以下列出兩種修改方式
修改1
#include <iostream>
using namespace std;
class mammal {
public:
mammal () { cout << "Class mammal constructor\n"; }
virtual ~mammal () { cout << "Class mammal destructor\n"; }
void virtual speak() const { cout << "This is mammal's speaking\n"; }
void method01() const { cout << "This is method01\n"; }
void virtual method02() const { cout << "This is method02\n"; }
};
class dog: public mammal {
public:
dog () { cout << "Class dog constructor\n"; }
virtual ~dog () { cout << "Class dog destructor\n"; }
void virtual speak() const { cout << "This is dog's speaking\n"; }
void method02() const { cout << "This is method02\n"; }
};
int main() {
mammal *pm[2];
// mammal
pm[0] = new mammal;
pm[0]->speak();
pm[0]->method01();
delete pm[0];
// dog
pm[1] = new dog;
pm[1]->speak();
pm[1]->method01();
pm[1]->method02();
delete pm[1];
return 0;
};
修改2
#include <iostream>
using namespace std;
class mammal {
public:
mammal () { cout << "Class mammal constructor\n"; }
virtual ~mammal () { cout << "Class mammal destructor\n"; }
void virtual speak() const { cout << "This is mammal's speaking\n"; }
void method01() const { cout << "This is method01\n"; }
};
class dog: public mammal {
public:
dog () { cout << "Class dog constructor\n"; }
virtual ~dog () { cout << "Class dog destructor\n"; }
void virtual speak() const { cout << "This is dog's speaking\n"; }
void method02() const { cout << "This is method02\n"; }
};
int main() {
mammal *pm[2];
// mammal
pm[0] = new mammal;
pm[0]->speak();
pm[0]->method01();
delete pm[0];
// dog
pm[1] = new dog;
pm[1]->speak();
pm[1]->method01();
static_cast(pm[1])->method02();
delete pm[1];
return 0;
};
修改一則是直接在基本類別內加上一個虛擬函式, 使得整個狀況直接變成動態繫結的問題, 這樣的缺點是那基本類別的函式會多如牛毛; 修改二則是在呼叫時, 直接將基本類別的指標強制轉型為衍生類別的指標, 感覺上這個邏輯上比較正確...