Presentation is loading. Please wait.

Presentation is loading. Please wait.

1/56 多型與虛擬函數 Do at Rome as the Romans do. 《 Answer from St. Ambrose to St. Augustine 》 多型 (polymorphism) 可以讓程式有更好 的架構及可讀性,減少使用繼承以定義 新類別的困難。在 C++ 的語法中,多型.

Similar presentations


Presentation on theme: "1/56 多型與虛擬函數 Do at Rome as the Romans do. 《 Answer from St. Ambrose to St. Augustine 》 多型 (polymorphism) 可以讓程式有更好 的架構及可讀性,減少使用繼承以定義 新類別的困難。在 C++ 的語法中,多型."— Presentation transcript:

1 1/56 多型與虛擬函數 Do at Rome as the Romans do. 《 Answer from St. Ambrose to St. Augustine 》 多型 (polymorphism) 可以讓程式有更好 的架構及可讀性,減少使用繼承以定義 新類別的困難。在 C++ 的語法中,多型 需要使用虛擬函數來實現。 20

2 2/56 多型與虛擬函數 20.1 多型的基本概念 20.2 晚期聯結與虛擬函數 20.3 VPTR 和 VTABLE 20.4 純虛擬函數與抽象類別 20.5 重載虛擬函數 20.6 虛擬解構函數

3 3/56 多型的基本概念 沒有封裝 (encapsulation) 和資料抽象化 (data abstraction) 就談不上繼承。 如果沒有了繼承也就沒有多型 (polymorphism) 。 「 polymorphism 」的字源是希臘字,「很 多形態 (many shapes) 」的意思。

4 4/56 Cast (資料型態轉換) int N = 5; float(N) int (&N) // &N 預設的表達方式為十六進位 (float) N (int)&N

5 5/56 Upcast (向上轉換資料型態) (1/2) Upcast 只發生在有繼承關係的類別之間。 Shape 和 Circle 之間的類別繼承關係

6 6/56 Upcast (向上轉換資料型態) (2/2) Circle C2; Shape* pS = &C2; // Shape 指標指向物件 C1 Shape& S1 = C2; // 定義 C1 的參照 S1 這兩個運算就是典型的「 upcast 」。 這些運算將衍生物件的地址傳給了基礎物 件的指標;或將基礎物件做為衍生物件的 參照,都是一種「向上轉型」的動作。 Upcast 的目的之一就在於能夠沿用既有的介 面。

7 7/56 多型帶來的便利 使用 upcasted 指標呼叫衍生類別內的同名 成員函數。 使用參照呼叫同名的成員函數。 間接呼叫同名的成員函數。 Upcasted 參照和指標必需配合下一節即將 介紹的 虛擬函數 (virtual function) 才能有多型的 效果。

8 8/56 多型的範例 (an example of inherited class)

9 9/56 使用 upcasted 指標呼叫衍生類別內的 同名成員函數 Circle C2; // 定義 Circle 物件 C2. Shape* pS = &C2; // 定義 Shape 指標 pS , // 並指向 Circle 物件 C2. 可以直接以下述方式呼叫到正確版本的 Draw() : pS->Draw(); // 希望呼叫 Circle::Draw() 也就是說,多型允許基礎類別和衍生類別共用指標。

10 10/56 使用參照呼叫同名的成員函數 讓基礎類別和衍生類別可以共用外部定義的函數。 例如: void Remove(Shape& S1) {S1.Erase();} 可以由基礎類別 Shape 和衍生類別 Circle 共用: Shape S0; // 定義 Shape 物件 S0. Circle C2; // 定義 Circle 物件 C2. Remove(S0); // 呼叫 Shape::Erase() Remove(C2); // 呼叫 Circle::Erase()

11 11/56 間接呼叫同名的成員函數 如果 ObjectSize() 是定義在基礎類別內的成員函數, 它又間接呼叫了 Area() : pS->ObjectSize(); // 希望間接呼叫 Circle::Area() S0.ObjectSize(); // 希望間接呼叫 Shape::Area() C2.ObjectSize(); // 希望間接呼叫 Circle::Area() 基礎類別是由現有的程式庫定義,即使它又間接 呼叫了其它的成員函數,我們依然可以直接沿用, 不需在衍生類別內重新定義。

12 12/56 範例程式 檔案 Upcast.cpp #include using namespace std; //---- 宣告類別 Shape ---------------------------- class Shape { private: int i; public: Shape(): i(7){} ~Shape() {} virtual void Draw() {cout<<" 畫個圖.\n";} virtual void Area() {cout<<" 圖的面積.\n";} virtual void Erase() {cout<<" 將圖清除.\n";} void ObjectSize() {Area();} };

13 13/56 //---- 宣告類別 Circle --------------------------- class Circle : public Shape { private: int r; public: Circle(): r(5) {} Circle(int N): r(N) {} ~Circle() {} void Draw() {cout<<" 畫一個圓形.\n";} void Area() {cout<<" 圓形的面積為 PI*R*R.\n";} void Erase() {cout<<" 把圓形清除.\n";} }; void Remove(Shape& S1) {S1.Erase();}

14 14/56 // ---- 主程式 ----------------------------------- int main() { Circle C2; Shape* pS; pS = &C2; cout 執行「 pS->Draw(); 」之後 :" << endl; pS->Draw(); cout 執行「 pS->ObjectSize(); 」之後 :" << endl; pS->ObjectSize(); cout 執行「 Remove(C2); 」之後 :" << endl; Remove(C2); return 0; }

15 15/56 程式執行結果 執行「 pS->Draw(); 」之後 : 畫一個圓形. 執行「 pS->ObjectSize(); 」之後 : 圓形的面積為 PI*R*R. 執行「 Remove(C2); 」之後 : 把圓形清除.

16 16/56 晚期聯結與虛擬函數 將函數的呼叫與函數本體間建立起關係叫做聯結 (binding) 。 如果這個聯結的動作在編譯階段就已完成,稱為早期聯結 (early binding) 。 執行時才進行的聯結稱為晚期聯結 (late binding) 、執行期聯結 (runtime binding) ,或動態聯結 (dynamic binding) 。 如果要在執行的時候才臨時進行聯結,就必需將基礎類別中的相 關成員函數宣告為虛擬函數 (virtual function) 。例如: virtual void Draw(){cout<<" 畫個圖.\n";} virtual void Area(){cout<<" 圖的面積.\n";} virtual void Erase(){cout<<" 將圖清除.\n";}

17 17/56 程式 Poly.cpp: 五個類別之間的繼承 關係 在類別 Shape 中,兩個成員函數 Draw() 和 Erase() 都宣告為虛擬函數。 圖 20.1.3 五個類別之間的繼承關係

18 18/56 程式 Poly.cpp 示範在有繼承關係的情況下,如何使用多型以呼叫到各 衍生類別內的正確成員函數 Draw( ) 和 Erase( ) 。 這個效果可以擴展到以下的所有衍生類別之中,包括類 別 Shape 的三個衍生類別 Square , Triangle ,和 Circle ,以及在類別 Circle 下的衍生類別 Cylinder 。 這就是「多型」 (polymorphism) 所帶來的效果。

19 19/56 程式 Poly.cpp // Poly.cpp #include using namespace std; //---- 宣告類別 Shape ------------------- class Shape { private: int i; public: Shape(): i(7){} ~Shape(){} virtual void Draw() {cout<<" 畫個圖 \n";} virtual void Erase() {cout<<" 將圖清除 \n";} };

20 20/56 //---- 宣告類別 Circle ------------------- class Circle : public Shape { private: int r; public: Circle(): r(5) {} Circle(int N): r(N) {} ~Circle() {} void Draw() {cout<<" 畫一個圓形 \n";} void Erase() {cout<<" 把圓形清除 \n";} };

21 21/56 //---- 宣告類別 Square ------------------- class Square : public Shape { private: int a; public: Square(): a(2) {} Square(int N): a(N) {} ~Square() {} void Draw() {cout<<" 畫一個正方形 \n";} void Erase() {cout<<" 把正方形清除 \n";} };

22 22/56 //---- 宣告類別 Triangle ----------------- class Triangle : public Shape { private: int a, b, c; public: Triangle(): a(1), b(1), c(1) {} Triangle(int L, int M, int N): a(L), b(M), c(N) {} ~Triangle() {} void Draw() {cout<<" 畫一個三角形 \n";} void Erase() {cout<<" 把三角形清除 \n";} };

23 23/56 //---- 宣告類別 Cylinder ------------------ class Cylinder : public Circle { private: int r, h; public: Cylinder(): r(5), h(1) {} Cylinder(int M, int N): r(M), h(N) {} ~Cylinder() {} void Draw() {cout<<" 畫一個圓柱形 \n";} void Erase() {cout<<" 把圓柱形清除 \n";} }; void Remove(Shape& S1) {S1.Erase();}

24 24/56 // ---- 主程式 ------------------------------- int main() { Circle C2; Cylinder CyL3; Triangle T2; Square Sq2; Shape* pS; cout << " 執行 「 pS=&C2 」之後, " << endl; pS=&C2; cout Draw() 」的結果是 : "; pS->Draw(); cout << " 執行 「 pS=&CyL3 」之後, " << endl; pS=&CyL3; cout Draw() 」的結果是 : ";

25 25/56 pS->Draw(); cout << " 執行 「 pS=&T2 」之後, " << endl; pS=&T2; cout Draw() 」的結果是 : "; pS->Draw(); cout << " 執行 「 pS=&Sq2 」之後, " << endl; pS=&Sq2; cout Draw() 」的結果是 : "; pS->Draw(); cout << endl; cout << " 執行 「 Remove(C2) 」的結果是 : " << endl; Remove(C2); cout << " 執行 「 Remove(CyL3) 」的結果是 : " << endl; Remove(CyL3); return 0; }

26 26/56 程式執行結果 執行 「 pS=&C2 」之後, 「 pS->Draw() 」的結果是 : 畫一個圓形 執行 「 pS=&CyL3 」之後, 「 pS->Draw() 」的結果是 : 畫一個圓柱形 執行 「 pS=&T2 」之後, 「 pS->Draw() 」的結果是 : 畫一個三角形 執行 「 pS=&Sq2 」之後, 「 pS->Draw() 」的結果是 : 畫一個正方形 執行 「 Remove(C2) 」的結果是 : 把圓形清除 執行 「 Remove(CyL3) 」的結果是 : 把圓柱形清除

27 27/56 物件分割( object slicing ) 在程式 Poly.cpp 中,我們定義了一個稱為 Remove() 的函數: void Remove(Shape& S1) {S1.Erase();} // S1 為參照 如果我們把這個函數改為傳物件的值: void Remove(Shape S1) {S1.Erase();} 則執行結果沒有多型的效果。 這是因為 upcast 時只容得下基礎類別的成員。 這種現象叫做物件分割 (object slicing) 。

28 28/56 重新定義( redefinition )與覆蓋 ( overriding ) 重新定義 (redefinition): 在衍生類別中定義 與基礎類別同名的成員函數。 覆蓋 (overriding): 重新定義的對象是虛擬 函數。

29 29/56 VPTR 和 VTABLE 如果基礎類別中有虛擬函數時,編譯器會自動為每一個由該基礎類別及其 衍生類別所定義的物件加上一個叫做 v-pointer 的指標,簡稱 VPTR 。 編譯器還為每個類別加上了一個叫做 v-table 的表,簡稱 VTABLE 。 VPTR 指向 VTABLE 開頭的地方。 在各 VTABLE 中記錄的是所有宣告為 virtual 的成員函數位址,包括該類別 以及該類別的基礎類別之內的 virtual 成員函數。 如果在衍生類別中沒有覆蓋基礎類別的成員函數,則在 VTABLE 中記錄的 是基礎類別成員函數的地址。 如果在衍生類別中有覆蓋到基礎類別的成員函數,則在 VTABLE 中記錄的 是衍生類別成員函數的地址。這些地址都在 VTABLE 內按照它們在基礎函 數內的次序排列。

30 30/56 多型的基礎:晚期聯結 (late binding) 內部運作的原理 圖 20.3.1 VPTR 和 VTABLE 之間的關係

31 31/56 當基礎類別含有虛擬函數時編譯器自 動進行的動作 1. 為所有的基礎類別和每個衍生類別都設定一個 VTABLE 和一個 VPTR 。 2. 對所有的 VPTR 進行初始化的動作:將各個 VPTR 指向正確 VTABLE 的第一個欄位。 3. 對每個 upcast 的虛擬函數呼叫加入專用的程式碼, 以備動態設定基礎類別指標,指向正確的 VPTR 。 在效率的考量下,所有的虛擬函數都不應該以 inline 函數的方式存在。

32 32/56 純虛擬函數( pure virtual function ) 阻止使用基礎類別來定義物件,可以將 virtual 函數改為純虛擬函數。 只要將 inline 形式的 virtual 函數本體部份簡 化為「 = 0; 」即可。例如: virtual void Rotate() = 0; virtual void Erase() = 0;

33 33/56 抽象類別( abstract class ) 含有純虛擬函數的類別稱為抽象類別。 如果類別內的所有虛擬函數都是純虛擬函數,則 此基礎類別稱為純抽象類別 (pure abstract class) 。 如果我們想要使用基礎類別宣告物件,就會在編 譯時收到:「無法使用抽象類別創造實例 (cannot create instance of abstract class) 」的錯誤訊息。

34 34/56 範例程式 檔案 PolyPure.cpp // PolyPure.cpp #include using namespace std; //---- 宣告類別 Shape -------- class Shape { private: int i; public: Shape(): i(7){} ~Shape(){} virtual void Draw() =0; // 純虛擬函數 virtual void Erase()=0; // 純虛擬函數 };

35 35/56 //---- 宣告類別 Circle-------- class Circle : public Shape { private: int r; public: Circle(): r(5) {} Circle(int N): r(N) {} ~Circle() {} void Draw() {cout<<" 畫一個圓形.\n";} void Erase() {cout<<" 把圓形清除.\n";} };

36 36/56 //---- 宣告類別 Cylinder-------- class Cylinder : public Circle { private: int r, h; public: Cylinder(): r(5), h(1) {} Cylinder(int M, int N): r(M), h(N) {} ~Cylinder() {} void Draw() {cout<<" 畫一個圓柱形.\n";} void Erase() {cout<<" 把圓柱形清除.\n";} }; void Make(Shape& S1) {S1.Draw();} // ---- 主程式 --------------------------- int main() { Circle C1;

37 37/56 Cylinder CyL; Shape* pS; cout << " 「 Make(C1)) 」 : "; Make(C1); cout << " 「 Make(CyL)) 」 : "; Make(CyL); pS = &C1; cout << " 執行「 pS=&C1 」之後," << endl; cout Erase() 」的結果是 : "; pS->Erase(); pS = &CyL; cout << " 執行「 pS=&CyL 」之後," << endl; cout Erase() 」的結果是 : "; pS->Erase(); return 0; }

38 38/56 程式執行結果 「 Make(C1)) 」 : 畫一個圓形. 「 Make(CyL)) 」 : 畫一個圓柱形. 執行「 pS=&C1 」之後, 「 pS->Erase() 」的結果是 : 把圓形清除. 執行「 pS=&CyL 」之後, 「 pS->Erase() 」的結果是 : 把圓柱形清除

39 39/56 純虛擬函數的定義 (1/2) 由 pure virtual 函數的標準語法,它本身是沒有內容的。 在某些情況下,我們既要阻止使用基礎類別來宣告物件, 同時又要有實質的內容可以使用。這時候,可以在基礎 類別的宣告外,加上純虛擬函數的定義。例如,假如 Shape 是基礎類別,它有一個純虛擬函數 Center() : class Shape { //... 其他敘述 virtual void Center()=0; } 則純虛擬函數 Center() 的定義可以寫成 void Shape::Center() { //...Center() 的實體敘述 }

40 40/56 純虛擬函數的定義 (2/2) 在所有 Shape 的 ( 直接 ) 衍生類別的宣告中, 都要加入以下的定義: void Center() {Shape::Center();} 這時候,在 Shape 的 VTABLE 中,屬於 Center() 的欄位依舊是空的,但有個名叫 Center() 的函數實體可以從衍生類別中呼 叫。

41 41/56 純虛擬函數的定義 : 呼叫共同的成員 函數 例如,假設 Circle 是 Shape 的衍生類別, Cylinder 是 Circle 的衍生類別: Circle C1; Cylinder CyL; 就可以使用下列敘述呼叫共同的成員函數 Center() : Shape* pS; C1.Center(); CyL.Center(); pS = &C1; pS->Center(); pS = &CyL; pS->Center();

42 42/56 純虛擬函數的定義 : 使用基礎物件的參照做為 參數來定義函數 void MakeCenter(Shape& S1) {S1.Center();} 再使用下述的語法來叫用 Center() : MakeCenter(C1); MakeCenter(CyL);

43 43/56 範例程式 檔案 PureDef.cpp // PureDef.cpp #include using namespace std; //---- 宣告類別 Shape ---------------------------- class Shape { private: int i; public: Shape(): i(7){} ~Shape(){} virtual void Rotate()=0; virtual void Erase()=0; virtual void Center()=0; };

44 44/56 //--- 定義純虛擬函數 Center() ---------------------- void Shape::Center() {cout << "Center point\n";} //---- 宣告類別 Circle-------- class Circle : public Shape { private: int r; public: Circle(): r(5) {} Circle(int N): r(N) {} ~Circle() {} void Rotate() {cout<<" 將圓形迴轉 \n";} void Erase() {cout<<" 把圓形清除 \n";} void Center() {Shape::Center();} };

45 45/56 //---- 宣告類別 Cylinder ----------------------- class Cylinder : public Circle { private: int r, h; public: Cylinder(): r(5), h(1) {} Cylinder(int M, int N): r(M), h(N) {} ~Cylinder() {} void Rotate() {cout<<" 將圓柱形迴轉 \n";} void Erase() {cout<<" 把圓柱形清除 \n";} }; //--- 定義函數 MakeCenter() ------------------- void MakeCenter(Shape &S1) {S1.Center();}

46 46/56 // ---- 主程式 --------------------------------- int main() { Circle C1; Cylinder CyL; Shape* pS; pS = &C1; cout << " 執行 「 pS=&C1 」之後, " << endl; cout Center() 」的結果是 : "; pS->Center(); pS = &CyL; cout << " 執行 「 pS=&CyL 」之後, " << endl; cout Center() 」的結果是 : "; pS->Center(); cout << " 「 MakeCenter(C1) 」 : "; MakeCenter(C1); cout << " 「 MakeCenter(CyL) 」 : "; MakeCenter(CyL); return 0; }

47 47/56 程式執行結果 執行 「 pS=&C1 」之後, 「 pS->Center() 」的結果是 : Center point 執行 「 pS=&CyL 」之後, 「 pS->Center() 」的結果是 : Center point 「 MakeCenter(C1) 」 : Center point 「 MakeCenter(CyL) 」 : Center point

48 48/56 找出所有沒有覆蓋 virtual 函數的相關 衍生類別的技巧 例如,如果暫時移除衍生類別 Circle 裹面下列的定義 敘述: void Center(){Shape::Center();} 在編譯時將會發生以下的錯誤訊息 Cannot create instance of abstract class ‘Circle’ 意思是:「無法產生抽象類別 ‘Circle’ 的實例」。上 述的技巧一次只能清查一代的繼承關係。

49 49/56 名稱隱藏 (name hiding) 如果基礎類別中的成員函數有重載 (overloaded) 的現象時,在衍生類別中重新定義 (redefine) 其中任一個成員函數都會讓其它同名的成員函 數無法從衍生類別取用。

50 50/56 重載虛擬函數( overloaded virtual functions ) 如果重載的成員函數是虛擬函數時,固然名稱隱藏的效 應還是存在,但經由 upcast 情況會不一樣。 假設衍生類別 Cylinder 繼承 Circle ,而 Circle 繼承自基礎類 別 Shape ,且其中的成員函數 Rotate() 有如下的關係: ( 純虛擬函數 Rotate(int) 有加上定義。 ) ShapeCircleCylinder virtual Rotate()Rotate() virtualRotate(int)

51 51/56 名稱隱藏 如果我們使用 Circle 和 Cylinder 分別定義了 C1 和 CyL 兩 個物件: Circle C1; Cylinder CyL; CyL.Rotate(5); // 由於名稱隱藏的緣 // 故無法通過編譯。

52 52/56 使用晚期聯結以完全避免名稱隱藏的 現象 : 「多型」的附屬特性 利用 upcast ,分別定義指標 pS 和參照 S1 : Shape *pS = &C1; Shape &S1 = CyL; 則 pS->Rotate(); S1.Rotate(); pS->Rotate(2); S1.Rotate(4); 能正確執行,完全避免名稱隱藏的現象。

53 53/56 範例程式 檔案 VOverload.cpp // VOverload.cpp #include using namespace std; //---- 宣告類別 Shape ------------------------- class Shape { private: int i; public: Shape(): i(7){} ~Shape(){} virtual void Rotate() {cout<<" 將圖迴轉 \n";} virtual void Rotate(int N) {cout<<" 將圖迴轉 "<< N << " 單位 \n";} virtual void Erase() {cout<<" 將圖清除 \n";} };

54 54/56 //---- 宣告類別 Circle ------------------------ class Circle : public Shape { private: int r; public: Circle(): r(5) {} Circle(int N): r(N) {} ~Circle() {} void Rotate() {cout<<" 將圓形迴轉 \n";} void Erase() {cout<<" 把圓形清除 \n";} };

55 55/56 //----- 宣告類別 Cylinder ------------------ class Cylinder : public Circle { private: int r, h; public: Cylinder(): r(5), h(1) {} Cylinder(int M, int N): r(M), h(N) {} ~Cylinder() {} void Rotate(){cout<<" 將圓柱形迴轉 \n";} void Erase() {cout<<" 把圓柱形清除 \n";} };

56 56/56 // ---- 主程式 ------------------------------ int main() { Circle C1; Cylinder CyL; Shape *pS = &C1; Shape &S1 = CyL; cout Rotate() 」 : "; pS->Rotate(); cout Rotate(2) 」 : "; pS->Rotate(2); cout << " 「 S1.Rotate() 」 : "; S1.Rotate(); cout << " 「 S1.Rotate(4) 」 : "; S1.Rotate(4); return 0; }

57 57/56 程式執行結果 「 pS->Rotate() 」 : 將圓形迴轉 「 pS->Rotate(2) 」 : 將圖迴轉 2 單位 「 S1.Rotate() 」 : 將圓柱形迴轉 「 S1.Rotate(4) 」 : 將圖迴轉 4 單位

58 58/56 20.6 虛擬解構函數( virtual destructor ) 在動態配置 (dynamic memory allocation) 物件,並 伴隨指標的 upcasting 時,必需將解構函數改成虛 擬解構函數 (virtual destructor) 才能順利完整的回 收記憶體資源。 將解構函數虛擬化後,才能夠引進 VTABLE 和 VPTR ,依序執行所有基礎類別和衍生類別的解構 函數,完全釋放所有的記憶空間。。 一般而言,如果在基礎類別內宣告虛擬成員函數, 就必需同時宣告虛擬解構函數。 建構函數不需要虛擬化。

59 59/56 虛擬解構函數 例如,假設衍生類別 Cylinder 繼承自 Circle ,而 Circle 繼承自基礎類別 Shape : Shape* pS1 = new Circle; Shape* pS2 = new Cylinder; 如果不再使用時可以用 delete 敘述回收: delete pS1; delete pS2; 為了完整回收,必需將基礎類別的解構函數改成 虛擬解構函數: virtual ~Shape() { //... 解構函數本體 }

60 60/56 範例程式 檔案 VDestructor.cpp // VDestructor.cpp #include using namespace std; //---- 宣告類別 Shape ------------------------------- class Shape { private: int i; public: Shape(): i(7) {cout << " Shape 建構函數 \n";} virtual ~Shape() {cout << " Shape 解構函數 \n";} virtual void Rotate() {cout << " 將圖迴轉 \n";} virtual void Erase() {cout << " 將圖清除 \n";} };

61 61/56 //---- 宣告類別 Circle ------------------------------ class Circle : public Shape { private: int r; public: Circle(): r(5) {cout << " Circle 建構函數 \n";} Circle(int N): r(N) {cout << " Circle 建構函數 \n";} ~Circle() {cout << " Circle 解構函數 \n";} void Rotate() {cout<<" 將圓形迴轉 \n";} void Erase() {cout<<" 把圓形清除 \n";} };

62 62/56 //---- 宣告類別 Cylinder ---------------------------- class Cylinder : public Circle { private: int r, h; public: Cylinder(): r(5), h(1) {cout << " Cylinder 建構函數 \n";} Cylinder(int M, int N): r(M), h(N) {cout << " Cylinder 建構函數 \n";} ~Cylinder() {cout << " Cylinder 解構函數 \n";} void Rotate(){cout<<" 將圓柱形迴轉 \n";} void Erase() {cout<<" 把圓柱形清除 \n";} };

63 63/56 // ---- 主程式 -------------------------------------- main() { cout << "(1) 定義一個 Circle:\n"; Shape* pS1 = new Circle; cout << "(2) 定義一個 Cylinder:\n"; Shape* pS2 = new Cylinder; cout << "(3) 清除 pS1:\n"; delete pS1; cout << "(4) 清除 pS2:\n"; delete pS2; }

64 64/56 程式執行結果 (1) 定義一個 Circle: Shape 建構函數 Circle 建構函數 (2) 定義一個 Cylinder: Shape 建構函數 Circle 建構函數 Cylinder 建構函數 (3) 清除 pS1: Circle 解構函數 Shape 解構函數 (4) 清除 pS2: Cylinder 解構函數 Circle 解構函數 Shape 解構函數

65 65/56 記憶體泄漏 (memory leak) 如果不在類別 Shape 中使用虛擬解構函數 ( 詳見本書所附 CD- ROM 檔案 VDestructor2.cpp) ,則結果變成: 程式執行結果 衍生類別 Circle 和 Cylinder 的解構函數都沒有被呼叫。這種情 況將造成記憶體資源不斷泄漏損失,稱為記憶體泄漏。 (1) 定義一個 Circle: Shape 建構函數 Circle 建構函數 (2) 定義一個 Cylinder: Shape 建構函數 Circle 建構函數 Cylinder 建構函數 (3) 清除 pS1: Shape 解構函數 (4) 清除 pS2: Shape 解構函數


Download ppt "1/56 多型與虛擬函數 Do at Rome as the Romans do. 《 Answer from St. Ambrose to St. Augustine 》 多型 (polymorphism) 可以讓程式有更好 的架構及可讀性,減少使用繼承以定義 新類別的困難。在 C++ 的語法中,多型."

Similar presentations


Ads by Google