1 / 49

Derived Class

Derived Class. 前言 衍生類別的定義 單一繼承 public, protected, 和 privated 基底類別 virtual 成員函式 RTTI (Run-time Type Information). 前言. C++ 提供類別繼承的機制來擴充或更改現有類別的功能。我們可以利用此機制來達到以下兩個目的: 程式碼的再利用( code reuse ) 物件導向的設計( object-oriented design ). class B. class D. 類別繼承的圖示. 衍生類別的定義.

Télécharger la présentation

Derived Class

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Derived Class • 前言 • 衍生類別的定義 • 單一繼承 • public, protected, 和 privated 基底類別 • virtual 成員函式 • RTTI (Run-time Type Information)

  2. 前言 • C++ 提供類別繼承的機制來擴充或更改現有類別的功能。我們可以利用此機制來達到以下兩個目的: • 程式碼的再利用(code reuse) • 物件導向的設計(object-oriented design)

  3. class B class D 類別繼承的圖示 衍生類別的定義 • 假定 B 是一個類別。我們可以用底下的格式來定義一個 B 的衍生類別 D: • class D : public B • { • // members of D • }; • 我們稱:D 繼承 B、B 是 D 的 base class(基底類別)或 superclass(父類別)、以及 D 是 B 的 derived class(衍生類別)或 subclass(子類別)。

  4. 類別繼承通常是用來表達 kind-of 關係(或稱 is-a 關係):衍生類別是基底類別的一種。譬如: Manager(經理)也是 Employee(員工),所以我們先規劃好 Employee 類別,然後把 Manager 定義成 Employee 的一個衍生類別: • class Employee { • // other members • private: • string first_name, family_name; • char middle_initial; • Date hiring_date; • short department; • }; • class Manager : public Employee { • // other members • private: • set<Employee *> group; • short level; • };

  5. 衍生類別除了本身的資料成員以外,也具有基底類別的資料成員。譬如:衍生類別除了本身的資料成員以外,也具有基底類別的資料成員。譬如: Employee 的資料成員: string first_name, family_name; char middle_initial; Date hiring_date; short department; Manager 的資料成員: string first_name, family_name; char middle_initial; Date hiring_date; short department; set<Employee *> group; short level;

  6. 由於衍生類別是基底類別的子類別,因此衍生類別的物件可視為基底類別的物件;基底類別型態的指標也可用於衍生類別的物件,而不須要經過型態轉換。但反過來就不成立了。譬如:由於衍生類別是基底類別的子類別,因此衍生類別的物件可視為基底類別的物件;基底類別型態的指標也可用於衍生類別的物件,而不須要經過型態轉換。但反過來就不成立了。譬如: • void foo (Manager mm, Employee ee) • { • Employee *pe = &mm; // ok • Manager *pm = &ee; // error • pe->level = 2; // error: Employee doesn‘t have • // data member:level • pm = static_cast<Manager *> (pe); // ok: explicit type casting • pm->level = 2; // ok: since pe points to a • // Manager object • }

  7. 衍生類別及其朋友(friends)可以直接使用基底類別的 public 或 protected 成員,但是不能使用基底類別的 private 成員。譬如: • class D : public B { • friend void k(D); • void a() { f(); … } // ok • void b() { g(); … } // ok • void c() { _x = 0; } // ok • void d() { h(); … } // error • void e() { _y = 0; } // error • }; • void k (D obj) • { • obj.g(); // ok • obj.f(); // ok • obj._x = 0; // ok • obj._y = 0; // error • } • class B { • public: • void f(); • protected: • void g(); • int _x; • private: • void h(); • int _y; • };

  8. 本身與其朋友 衍生類別與其朋友 外界 public protected private x x x 存取控制 我們用下表來總結類別成員的存取控制:

  9. 如果基底類別的成員函式不符合所需的話,我們可以在衍生類別中重新改寫(override)。譬如:如果基底類別的成員函式不符合所需的話,我們可以在衍生類別中重新改寫(override)。譬如: • class Employee { • public: • void print(); • // other members • }; • void Employee::print() • { • cout << first_name << ‘ ’ • << middle_initial << ‘ ’ • << family_name; • } • class Manager : public Employee { • public: • void print(); • // other members • }; • void Manager ::print() • { • Employee::print(); • cout << ‘ ’ << level; • } 這項改寫的機制讓 code reuse 可以很容易地達成。

  10. 衍生類別的建構函式必須呼叫基底類別的建構函式(如果它存在的話),而且前者的參數必須包含後者的參數。譬如:衍生類別的建構函式必須呼叫基底類別的建構函式(如果它存在的話),而且前者的參數必須包含後者的參數。譬如: • class Employee { • public: • Employee (const string & n, int d) • : family_name(n), department(d) { } • // other members • }; • class Manager : public Employee { • public: • Manager (const string & n, int d, int lvl) • : Employee(n, d), level(lvl) { } • // other members • };

  11. 衍生類別的物件建構順序如下: • 1. 執行基底類別的建構函式。 • 2. 執行衍生類別資料成員的建構函式。 • 3. 執行衍生類別的建構函式。 • 衍生類別的物件解構順序則恰恰相反,即: • 1. 執行衍生類別的解構函式。 • 2. 執行衍生類別資料成員的解構函式。 • 3. 執行基底類別的解構函式。 資料成員和基底類別是按照宣告的順序來建構,解構則按照相反的順序。

  12. 拷貝衍生類別物件至基底類別物件時,只拷貝基底類別的資料成員。拷貝衍生類別物件至基底類別物件時,只拷貝基底類別的資料成員。 • class Employee { • Employee (const Employee &); • Employee& operator=(const Employee &); • // … • }; • void f (const Manager &m) • { • Employee e = m; // construct e from Employee part of m • e = m; // assign Employee part of m to e • }

  13. class CPoint2D { • public: • CPoint2D (int x = 0, int y = 0) : _x(x), _y(y) { } • int x () { return _x; } • int y () { return _y; } • void setX (int x) { _x = x; } • void setY (int y) { _y = y; } • void set (int x, int y) { _x = x; _y = y; } • bool isZero () { return _x == 0 && _y == 0; } • double distance () { return sqrt(_x * _x + _y * _y); } • protected: • int _x, _y; • }; 我們利用 CPoint2D 類別來定義 CPoint3D 類別。首先我們把 CPoint2D 的 private 成員改成 protected 成員,讓 CPoint3D 可以使用它們。 範 例

  14. class CPoint3D : public CPoint2D { • public: • CPoint3D (int x = 0, int y = 0, int _z = 0) • : CPoint2D(x, y), _z(z) { } • int z() { return _z; } • void setZ (int z) { _z = x; } • void set (int x, int y, int z) { set(x, y); setZ(z); } • bool isZero () { return _x == 0 && _y == 0 && _z == 0; } • double distance () { return sqrt(_x * _x + _y * _y + _z * _z); } • private: • int _z; • }; CPoint3D 改寫(override) CPoint2D 的 isZero() 和 distance() 函式。

  15. 我們可以為 CPoint3D 類別定義輸出運算子 << 如下: • #include <iostream> • using namespace std; • #include “CPoint3D.h” • ostream& operator<< (ostream &os, CPoint3D p) • { • os << ‘(‘ << p.x() << “, “ << p.y() << “, “ << p.z() << ‘)’; • return os; • }

  16. 測試程式 • #include “CPoint3D.h” • int main () • { • CPoint3D p(1, 2, 3); • cout << p << endl; • CPoint2D q(5, 6); • q = p; • cout << q << endl; • p = q; // error • } 輸出結果 (1, 2, 3) (1, 2)

  17. 我們利用標準的 string 類別來定義 big5string 類別。 big5string 類別提供了一些專門用來處理中文字串的成員函式。註:中文 Big5 碼中的中文字元是雙位元組,其中高位元組的範圍是 0x81  0xFE、低位元組的範圍是 0x40  0x7E 與 0xA1  0xFE。 範 例 • typedef unsigned short big5char; • class big5string : public string { • public: • bool is_big5char (int idx); • big5char next_char(int idx); • int big5_length(); • private: • bool in (char c, int min, int max) { return min <= c && c <= max; } • bool is_big5hiByte (char c) { return in(c, 0x81, 0xFE); } • bool is_big5loByte (char c) • { return in(c, 0x40, 0x7E) || in(c, 0xA1, 0xFE); } • };

  18. bool big5string::is_big5char (int idx) • { • assert(idx >= 0 && idx < length() ); • char *cp = c_str(); • if (cp[idx] < 128 || idx == length() -1) • return false; • if (is_big5hiByte(cp[idx]) && is_big5loByte(cp[idx+1]) • return true; • else • return false; • }

  19. big5char big5string::next_char (int idx) • { • char *cp = c_str(); • return is_big5char(idx)? cp[idx]*256+cp[idx] : cp[idx]; • } • int big5string::big5_length () • { • int len = 0, k = 0; • while (k < length()) { • k = is_big5char(k) ? k+2 : k+1; • len++; • } • return len; • }

  20. big5string 物件除了可使用 big5string 的功能以外,也可使用 string 所提供的各項功能。 測試程式 • #include <iostream> • #include <string> • #include “big5string.h” • int main () • { • string s1(“Hi, “); • big5string s2; • s2 = s1 + “好久不見”; • cout << s2 << endl; • cout << “# of characters is: “ << s2.big5_length() << endl; • return 0; • } 輸出結果 Hi, 好久不見 # of characters is: 8

  21. Employee Manager Director 單一繼承 • 類別可隨需要而建立層層的繼承關係。譬如: • class Employee { … }; • class Manager : public Employee { … }; • class Director : public Manager { … }; • Director 是 Manager 的衍生類別、 Manager • 又是 Employee 的衍生類別。因此 Director 是 Manager 的子類別,也是 Employee 的子類別。之前所說 Manager 和Employee 間的關係同樣適用於 Director 和 Employee 之間。譬如: Director 包含 Employee 所有的資料成員、也可以直接使用 Employee 的 public 和 protected 成員、等等。

  22. B D1 D2 Dn 衍生類別也可以作為其他類別的基底類別。如此一來,類別的繼承就形成如右圖所示的線性結構。D1, D2, …, Dn 都是基底類別 B 的衍生類別(或子類別),其中 D1 稱為 B 的「直接衍生類別」、 D2, …, Dn 稱為 B 的「間接衍生類別」。此外,D1, D2, …, Dn 型態的指標都可以轉換成 B 型態的指標。

  23. root class 有些時候,單一繼承的類別會形成如圖所示的階層狀(樹狀)的結構。其中最上層的類別稱為「根類別(root class)」。其他的類別都是根類別的子類別。

  24. public, protected, 和 privated 基底類別 • 基底類別可以指定成 public、protected、或 private。譬如: • class X : public B { … }; • class Y : protected B { … }; • class Z : privated B { … }; • 如果省略這些指定,則 class 的基底類別預設為 private、而 struct 的基底類別預設為 public 。譬如: • class X : B { … }; // B is a private base • struct X : B { … }; // B is a public base

  25. 這三種的差別在於以下的限制對間接衍生類別有所不同:這三種的差別在於以下的限制對間接衍生類別有所不同: • 基底類別 public 和 protected 成員的存取; • 把指標和參照從衍生類別的型態轉換成基底類別的型態。 • 假定 D 是 B 的一個直接衍生類別。 • B 是一個 private 基底類別 • B 的 protected 和 public 成員在 D 中變成 private 成員。這使得只有 D 的成員和朋友可以使用它們,而其他函式(包含 D 的衍生類別之成員和朋友)則不能使用它們。 • 此外,只有 D 的成員和朋友可以把 D* 轉換成 B*。

  26. class B { • public: • void foo (); • protected: • void bar (); • }; • class D1 : private B { • void f () { bar(); … } // ok • }; • class D2 : public D1 { • void g () { bar(); … } // error • void h () { foo(); … } // error • }; • void func () • { • B b; • D1 d1; • D2 d2; • b.foo(); // ok • b.bar(); // error • d1.foo(); // error • d1.bar(); // error • d2.foo(); // error • d2.bar(); // error • B *bp = &D1; // error • }

  27. B 是一個 protected 基底類別 • B 的 protected 和 public 成員在 D 中變成 protected 成員,使得只有 D 及其衍生類別的成員和朋友可以使用它們,而其他函式則不能使用它們。 • 此外,只有 D 及其衍生類別的成員和朋友可以把 D* 轉換成 B*。 • B 是一個 public 基底類別 • B 的 protected 和 public 成員在 D 中仍維持相同的存取模式。此外,任何函式都可以把 D* 轉換成 B*。

  28. class B { • public: • void foo (); • protected: • void bar (); • }; • class D1 : protected B { • void f () { bar(); … } // ok • }; • class D2 : public B { • void g () { bar(); … } // ok • void h () { foo(); … } // ok • }; • void func () • { • B b; • D1 d1; • D2 d2; • b.foo(); // ok • b.bar(); // error • d1.foo(); // error • d1.bar(); // error • d2.foo(); // error • d2.bar(); // error • B *bp = &D1; // error • }

  29. class B { • public: • void foo (); • protected: • void bar (); • }; • class D1 : public B { • void f () { bar(); … } // ok • }; • class D2 : public B { • void g () { bar(); … } // ok • void h () { foo(); … } // ok • }; • void func () • { • B b; • D1 d1; • D2 d2; • b.foo(); // ok • b.bar(); // error • d1.foo(); // ok • d1.bar(); // error • d2.foo(); // ok • d2.bar(); // error • B *bp = &D1; // ok • }

  30. 使用 B 的 public 和 protected 成員 D 的成員 D 及其衍生類別 其他函式 與朋友 的成員與朋友 private B protected B public B public but not protected x public but not protected public but not protected

  31. 把 D* 轉換成 B* D 的成員 D 及其衍生類別 其他函式 與朋友 的成員與朋友 private B protected B public B x x x

  32. 從以上的比較我們得知:對基底類別成員的存取,以 public 的限制最少、protected 次之、而 private 最多。此外,只有 public 的繼承方式允許在非成員的函式中,把衍生類別型態的指標轉換成基底類別型態的指標。由於這些原因,public 的繼承方式是最常用來定義基底類別的子類型(subtype)。如果我們想把基底類別當成 implementation 內部的一個類別,不希望外界直接地使用它,最好使用protected 和 private 的繼承方式,其中又以 private 的隔離效果比 protected 來得大。

  33. Virtual Functions • class Employee { • public: • void print (); • // … • }; • class Manager : public Employee { • public: • void print (); • // … • }; • class Director : Manager { • public: • void print (); • // … • }; 假定我們有如右邊所示的類別繼承關係。由於資料成員多寡不一,每個類別因此各自定義了一個 print() 成員函式,用來列印相關的員工資料。

  34. 假定你想寫一個函式能夠列印任何一類員工的資料。由於 Manager 和 Director 都屬於 Employee 類別,因此你可能認為以下的函式就可以達到這個目的: • void print_emp (Employee *e) • { • e->print(); • } 其實不然。原因是:e 是 Employee 型態的指標,所以不論傳進來的引數型態是 Employee、 Manager、或 Director,e->print() 永遠是呼叫 Employee 的成員函式 print()。

  35. 你可以在 Employee 類別中,加入一個儲存員工類型的資料成員來解決前述的問題。譬如: • class Employee { • public: • enum emp_type {EMPLOYEE, MANAGER, DIRECTOR}; • void print (); • emp_type type() { return _type; } • // … • private: • emp_type _type; • }; • 並在三個類別的建構函式中,加入資料成員 type 的設定。經過這些加工之後,你就可以寫出下一頁的列印函式。

  36. void print_emp (Employee *e) • { • switch (e->type()) { • case Employee::EMPLOYEE: • e->print(); • break; • case Employee::MANAGER: • static_cast<Manager *>(e)->print(); // Manager’s print() • break; • case Employee::DIRECTOR: • static_cast<Director *>(e)->print(); // Director’s print() • break; • } • }

  37. 上述的解決方案有下面兩個缺點: 就如同 print_emp() 函式所示,程式設計師必須判斷物件的類別,然後採取適當的型態轉換。這種作法不僅增加程式撰寫的負擔,也容易造成錯誤。 print_emp() 函式只能列印 Employee、Manager、和 Director 三種類別的物件。如果其他人用繼承的方式定義另一種員工的類別,如 Secretary,則已經寫死的 print_emp() 函式將無法用來列印這個新類別的物件。 • class Secretary : public Employee { • public: • void print (); • // … • };

  38. 宣告 virtual 函式 為了解決上述的問題,C++ 提供一種稱為 virtual 函式的特別成員函式。你只要在成員函式的宣告之前加上關鍵字 virtual,就可以把它變成 virtual 函式,即 virtualreturn_type func_name (parameter list) 當類別含有 virtual 成員函式時,C++ 編譯器會為它產生一個 virtual function table,其中包含此類別所有 virtual 成員函式的位址。此外,屬於此類別的物件,除了儲存資料成員外,會另外儲存一個指向此 virtual function table的指標。

  39. a _x _x _y _y X::vf1() _ _vptr_ _X _ _vptr_ _X X::vf1() virtual table for class X b • 舉例來說,假定類別 X 的宣告如下: • class X { • public: • virtual void vf1 (); • virtual void vf2 (); • void f (); • private: • int _x, _y; • }; • X a, b; • 則 X 類別的物件結構將如右圖所示。

  40. class Base { • virtual void foo (int); • // other members • }; • class Derived : public Base • { • virtual void foo (int); • // other members • }; override 衍生類別的 virtual 函式會覆蓋(override)基底類別的同名同參數列的 virtual 函式。

  41. class Employee { • public: • virtual void print (); • // … • }; • class Manager : public Employee { • public: • virtual void print (); • // … • }; • class Director : Manager { • public: • virtual void print (); • // … • }; 若我們把前述 Employee 類別和它衍生類別中的 print() 成員函式都改成 virtual(如左圖所示),則函式 print_emp() 就變得簡單多了,而且也克服前述的一些缺點。

  42. 利用 virtual 函式的 print_emp() 定義如下: • void print_emp (Employee *e) • { • e->print(); • } • 則 e->print() 會呼叫傳進來物件所定義的 print() 成員函式。譬如:若傳進來 Emploee 型態的物件時,e->print() 等同於e->Emploee::print();若是 Manager 型態的物件時,e->print() 等同於 e-> Manager ::print()。

  43. 型態相同的物件(都是 Employee)卻具有不同的行為(不同的 print() 功能),稱之為 polymorphism(多型)。具有 virtual 成員函式的類別稱為多型型態(polymorphic type)。 • 在 C++ 中,若要使用多型,你必須: • 把一些成員函式定義成 virtual。 • 透過物件指標或參照來呼叫這些 virtual 成員函式。 • 若透過物件直接呼叫 virtual 成員函式,因為編譯時會固定呼叫的對像,所以會達不到多型的效果。譬如: • Employee e; • e.print(); // 一定呼叫 Employee::print()

  44. Pure Virtual Functions • 如果基底類別的 virtual 成員函式只是用來規定衍生類別應該具備的使用介面(interface),而且基底類別也不用來定義物件的話,我們可以用以下的格式把 virtual 成員函式設定無定義的函式: • virtual return_type fucn_name (parameter_list) = 0 • 這樣的函式稱為 pure virtual function。

  45. Abstract Classes • 本身擁有或繼承但不改變 pure virtual functions 的類別稱為抽象類別(abstract class)。由於 pure virtual functions 是沒有定義的函式,因此抽象類別不可用來定義物件。譬如底下的 Abstract_Base 和Abstract_Derived 是抽象類別,而 Concrete_Derived 就不再是了: • class Abstract_Base { • public: • virtual void foo () = 0; • }; • class Abstract_Derived : public Abstract_Base { /* … */ } • class Concrete_Derived : public Abstract_Base { • public: • virtual void foo () {…}; // no longer pure • }

  46. 抽象類別通常製訂介面(interface)用來規範衍生類別的基本功能。譬如:抽象類別通常製訂介面(interface)用來規範衍生類別的基本功能。譬如: • class Shape { • public: • // 所有 Shape 類別物件都必須提供下列的功能 • virtual void rotate (int) = 0; • virtual void draw () = 0; • virtual void is_closed (int) = 0; • }; • class Circle : public Shape { • public: • virtual void rotate (int) { /* function definition */ } • virtual void draw () { /* function definition */ } • virtual void is_closed (int) { /* function definition */ } • // other members • }

  47. RTTI (Run-time Type Information) • C++ 的 RTTI 的機制提供下面兩個功能: • dynamic_cast • typeid

  48. p 如果指標 p所指的物件型態是 T或其基底類別是 T。 dynamic_cast<T *>(p) = 所有其他情形 0 dynamic_cast 假定 Base 是 Derived 的基底類別,Other 是與它們無任何繼續關係的類別 • void f (Derived *p) • { • Base *q0 = p; // ok • Base *q1 = dynamic_cast<Base *>( p); // ok • Other *q2 = p; // compile error • Other *q3 = dynamic_cast<Other *>( p); // ok: q3 is 0 • }

  49. typeid • typeid 運算子可用來取得物件的型態資訊: • typeid(obj); // 傳回物件 obj的型態資訊 • typeid(*objptr); // 傳回物件指標 objptr 所指物件的型態資訊 • 我們可以從型態資訊中取得型態的名稱,如: • typeid(obj).name(); //物件 obj所屬類別的名稱 • 也可以比較兩個物件的型態是否相同,如 • typeid(obj1) == typeid(obj2) // true 若兩物件的類別相同 • typeid(obj1) != typeid(obj2) // true 若兩物件的類別不相同

More Related