12 虛擬函數 12.1 多載與超載 12-2 12.1.1 多載函數 12-2 12.1.2 超載函數 12-4 12 虛擬函數 12.1 多載與超載 12-2 12.1.1 多載函數 12-2 12.1.2 超載函數 12-4 12.2 虛擬函數 12-6 12.2.1 同名異式 12-7 12.2.2 虛擬函數 12-9 12.2.3 基礎類別指標 12-11 12.2.4 虛擬物件指標 12-13 12.3 抽象類別 12-15 12.3.1 虛擬類別繼承 12-15 12.3.2 純虛擬函數 12-18 12.3.3 抽象基礎類別 12-19
12.1 多載與超載 多載(overloading)是在同一類別中,重複定義二個或多個名稱相同,但參數各數不同或參數型態不同的函數。 12.1 多載與超載 多載(overloading)是在同一類別中,重複定義二個或多個名稱相同,但參數各數不同或參數型態不同的函數。 超載(overriding)則是在衍生類別中,重新定義一個與基礎類別名稱相同,但參數個數或參數型態可能相同也可能不同的函數。
12.1 多載與超載 (續) 呼叫同一個類別的多載函數時,可以因不同的參數個數或不同的參數型態,而自動執行對應的多載函數。 12.1 多載與超載 (續) 呼叫同一個類別的多載函數時,可以因不同的參數個數或不同的參數型態,而自動執行對應的多載函數。 可是呼叫基礎類別或衍生類別的多載函數時,卻不會自動執行對應的多載函數,而必須使用範圍運算符號加以限制呼叫的範圍。因此,這種多載函數(overloading function)實際只是超載函數(overriding function)的功能。
12.1.1 多載函數 建立Base類別 class Base //宣告基礎類別 { public: 12.1.1 多載函數 建立Base類別 class Base //宣告基礎類別 { public: void show(char str1[], char str2[]) { cout << str1 << '\t' << str2 << endl; } };
12.1.1 多載函數 (續) 建立Derived類別以public型態繼承Base類別 12.1.1 多載函數 (續) 建立Derived類別以public型態繼承Base類別 class Derived: public Base //宣告衍生類別 { public: void show(char str3[]) { //衍生類別多載方法 cout << str3 << endl; } };
12.1.1 多載函數 (續) 建立Derived物件d並呼叫Derived類別的show函數 int main() { 12.1.1 多載函數 (續) 建立Derived物件d並呼叫Derived類別的show函數 int main() { Derived d; //建立Derived物件d char s1[] = "基礎參數1"; //定義並起始s1字串 char s2[] = “基礎參數2”; //定義並起始s2字串 char s3[] = "衍生參數"; //定義並起始s3字串 d.show(s1, s2); //呼叫Derived多載方法錯誤 d.Base::show(s1, s2); //呼叫Base方法 d.show(s3); //呼叫Derived多載方法 return 0; //正常結束程式 }
12.1.2 超載函數 建立Base類別 class Base //宣告基礎類別 { public: 12.1.2 超載函數 建立Base類別 class Base //宣告基礎類別 { public: void show() { //基礎類別方法 cout << "基礎類別show函數" << endl; } };
12.1.2 超載函數 (續) 建立Derived類別 class Derived: public Base //宣告衍生類別 { 12.1.2 超載函數 (續) 建立Derived類別 class Derived: public Base //宣告衍生類別 { public: void show() { //衍生類別多載方法 Base::show(); //呼叫基礎類別show函數 cout << "衍生類別show函數" << endl; } };
12.1.2 超載函數 (續) 建立Derived物件d,並呼叫Derived類別的show函數 int main() { 12.1.2 超載函數 (續) 建立Derived物件d,並呼叫Derived類別的show函數 int main() { Derived d; //建立Derived物件d d.show(); //呼叫Derived多載方法 return 0; //正常結束程式 }
12.2 虛擬函數 一般而言,C++ 在編譯過程會自動將同一類別的函數結合在一起(稱為靜態結合),因此當衍生類別含有基礎類別的超載函數,則執行時將自動呼叫同一類別的同名異式函數。 例如若此呼叫發生在基礎類別,則被呼叫的是基礎類別的同名異式函數;若此呼叫發生在衍生類別,則被呼叫的是衍生類別的同名異式函數。
12.2 虛擬函數 (續) 若以virtual宣告同名異式的函數,則C++ 在編譯過程會建立一個虛擬函數表(virtual function table),供執行時將同一物件的函數結合在一起(稱為動態結合),因此當執行時呼叫繼承體系中的同名異式,將呼叫物件所屬類別的同名異式函數。 例如若執行呼叫的是以基礎類別建立的物件,則被呼叫的是基礎類別的同名異式函數,若執行呼叫的是以衍生類別建立的物件,則被呼叫的是衍生類別的同名異式函數。
12.2.1 同名異式 同名異式(polymorphism)是在基礎類別與衍生類別中多載相同名稱但不同功能的public成員函數,且類別體系中的某個函數呼叫此同名的多載函數,因此當類別物件呼叫此函數時,此函數將呼叫同一類別的多載函數。 發生這種情形是因C++ 編譯器在類別成員函數呼叫時執行靜態結合,所謂靜態結合(static binding)又稱前期結合(early binding)表示C++ 在編譯過程自動結合同一類別的函數呼叫。
12.2.1 同名異式 (續)
12.2.2 虛擬函數 若將此同名異式的成員宣告為virtual函數,則編譯時C++ 會給予此同名異式函數不同的指標,因此執行時會依據類別指標存取適當的函數。 這種情形是因C++ 編譯器對virtual函數執行動態結合,所謂動態結合(dynamic binding)又稱為後期結合(late binding)是C++ 在執行過程自動結合同一物件的函數呼叫。
12.2.2 虛擬函數 (續)
12.2.3 基礎類別指標 如果將基礎類別物件的位址或是衍生類別物件的位址存入基礎類別物件的指標中,然後以此指標呼叫同名異式的函數時,則此指標都指向基礎類別,而且都是呼叫基礎類別的同名函數。
12.2.3 基礎類別指標 (續) 建立Base類別,再建立Derived類別 class Base { public: 12.2.3 基礎類別指標 (續) 建立Base類別,再建立Derived類別 class Base { public: void show() {cout << "基礎類別\n";} //宣告Base::show() }; class Derived: public Base { void show() {cout << "衍生類別\n";} //宣告Derived::show()
12.2.3 基礎類別指標 (續) 建立Base物件b與Derived物件d,然後以b或d物件呼叫show函數 int main() { 12.2.3 基礎類別指標 (續) 建立Base物件b與Derived物件d,然後以b或d物件呼叫show函數 int main() { Base *ptr; Base b; Derived d; ptr = &b; //ptr指向物件b位址 ptr->show(); //顯示"基礎類別" ptr = &d; //ptr指向物件d位址 return 0; }
12.2.4 虛擬物件指標 若將此同名異式的成員宣告為virtual函數,則編譯時C++ 會給予此同名異式函數不同的指標,因此執行時會依據物件指標存取適當的函數。
12.2.4 虛擬物件指標 (續) 建立Base類別,再建立Derived類別 class Base { public: 12.2.4 虛擬物件指標 (續) 建立Base類別,再建立Derived類別 class Base { public: virtual void show() {cout << “基礎類別\n”;} //宣告Base::show() }; class Derived: public Base { virtual void show() {cout << "衍生類別\n";} //宣告Derived::show()
12.2.4 虛擬物件指標 (續) 建立Base物件b與Derived物件d,然後以b與d物件呼叫show函數 int main() { 12.2.4 虛擬物件指標 (續) 建立Base物件b與Derived物件d,然後以b與d物件呼叫show函數 int main() { Base *ptr; Base b; Derived d; ptr = &b; //ptr指向物件b位址 ptr->show(); //顯示"基礎類別" ptr = &d; //ptr指向物件d位址 return 0; }
12.3 抽象類別 抽象類別是在基礎類別中宣告純虛擬函數,也就是宣告函數的原型,並令該函數原型等於0,而不定義該函數的功能。 12.3 抽象類別 抽象類別是在基礎類別中宣告純虛擬函數,也就是宣告函數的原型,並令該函數原型等於0,而不定義該函數的功能。 然後衍生類別必須在類別中實現純虛擬函數的功能。這相當於在基礎類別中保留一個存取基礎類別成員的介面,但讓衍生類別去實現此介面的功能。
12.3.1 虛擬類別繼承 如下圖,若衍生類別1與衍生類別2繼承基礎類別時,沒有宣告為虛擬(virtual)繼承,則C++ 將配置二個不同位址給衍生類別1與衍生類別2的基礎類別,所以衍生類別3存取基礎類別的成員時,C++ 無法確定其路徑為「基礎類別─衍生類別1─衍生類別3」或「基礎類別─衍生類別2─衍生類別3」?因此編譯時將出現ambiguous(模擬兩可)的錯誤訊息。
12.3.1 虛擬類別繼承 (續)
12.3.1 虛擬類別繼承 (續) 範例 class Base { public: int i; }; 12.3.1 虛擬類別繼承 (續) 範例 class Base { public: int i; }; class Derived1: virtual public Base int j;
12.3.1 虛擬類別繼承 (續) 範例續 class Derived2: virtual public Base { public: 12.3.1 虛擬類別繼承 (續) 範例續 class Derived2: virtual public Base { public: int k; }; class Derived3: public Derived1, public Derived2 int sum = i + j + k;
12.3.1 虛擬類別繼承 (續) 範例續 void main() { Derived3 d3; //建立Derived3物件d3 12.3.1 虛擬類別繼承 (續) 範例續 void main() { Derived3 d3; //建立Derived3物件d3 d3.i = 10; //設定Base資料成員 d3.j = 20; //設定Derived1資料成員 d3.k = 30; //設定Derived2資料成員 Derived2 d2; //建立Derived2物件d2 d2.i = 15; //設定Base資料成員 d2.k = 45; //設定Derived2資料成員 }
12.3.2 純虛擬函數 virtual 傳回型態 函數名稱(參數列) = 0; 12.3.2 純虛擬函數 virtual 傳回型態 函數名稱(參數列) = 0; 存虛擬函數(pure virtual function)只宣告函數並且令虛擬函數等於0,但未定義虛擬函數的本體。
12.3.3 抽象基礎類別 抽象類別(abstract class)是包含一個或多個純虛擬成員函數的類別。因此,若衍生類別繼承了抽象類別後,必須在衍生類別中超載(override)與實現(implements)純虛擬函數。 範例 class Base { public: virtual void show() = 0; //show()為虛擬函數 };
12.3.3 抽象基礎類別 (續) 範例續 class Derived1: public Base { public: 12.3.3 抽象基礎類別 (續) 範例續 class Derived1: public Base { public: void show() {cout << "衍生類別1\n";}//宣告Derived1::show() }; class Derived2: public Base { void show() {cout << "衍生類別2\n";}//宣告Derived2::show()
12.3.3 抽象基礎類別 (續) 範例續 void main() { Base *list[2]; Derived1 d1; 12.3.3 抽象基礎類別 (續) 範例續 void main() { Base *list[2]; Derived1 d1; Derived2 d2; list[0] = &d1; //指標[0]指向d1 list[1] = &d2; //指標[1]指向d2 list[0]->show(); //顯示"衍生類別1" list[1]->show(); //顯示"衍生類別2" }