35
15 15 第第第第第第第 第第第第第第第 15-1 15-1 第第第第第 第第第第第 15-2 15-2 第第第第第第第第第第第 第第第第第第第第第第第 15-3 15-3 第第第第第第第第第第 第第第第第第第第第第 15-4 15-4 第第第第 第第第第 15-5 15-5 第第第第第 第第第第第 15-6 15-6 第第第第第第第第第第 第第第第第第第第第第

第 15 章 繼承與多重繼承

  • Upload
    ivria

  • View
    69

  • Download
    0

Embed Size (px)

DESCRIPTION

第 15 章 繼承與多重繼承. 15-1 繼承的基礎 15-2 覆寫與隱藏父類別的成員 15-3 子類別的建構與解構子 15-4 多重繼承 15-5 軟體工程與繼承 15-6 類別的型態轉換與檢查. 15-1 繼承的基礎. 15-1-1 類別繼承的基礎 15-1-2 實作繼承 15-1-3 父類別的存取控制. 15-1-1 類別繼承的基礎 - 圖例. 「繼承」( Inheritance )是物件導向程式設計的一種進階觀念,繼承就是物件的再利用,當定義好一個類別後,其他類別可以繼承這個類別的成員資料和函數。 - PowerPoint PPT Presentation

Citation preview

第第 1515 章 章 繼承與多重繼承繼承與多重繼承 15-1 15-1 繼承的基礎繼承的基礎 15-2 15-2 覆寫與隱藏父類別的成員覆寫與隱藏父類別的成員 15-3 15-3 子類別的建構與解構子子類別的建構與解構子 15-4 15-4 多重繼承多重繼承 15-5 15-5 軟體工程與繼承軟體工程與繼承 15-6 15-6 類別的型態轉換與檢查類別的型態轉換與檢查

15-1 15-1 繼承的基礎繼承的基礎 15-1-1 15-1-1 類別繼承的基礎類別繼承的基礎 15-1-2 15-1-2 實作繼承實作繼承 15-1-3 15-1-3 父類別的存取控制父類別的存取控制

15-1-1 15-1-1 類別繼承的基礎類別繼承的基礎 -- 圖例圖例 「繼承」(「繼承」( InheritanceInheritance )是)是

物件導向程式設計的一種進階物件導向程式設計的一種進階觀念,繼承就是物件的再利用,觀念,繼承就是物件的再利用,當定義好一個類別後,其他類當定義好一個類別後,其他類別可以繼承這個類別的成員資別可以繼承這個類別的成員資料和函數。料和函數。

類別繼承也是在模擬真實世界,類別繼承也是在模擬真實世界,例如:學生和老師都是人,我例如:學生和老師都是人,我們可以先定義們可以先定義 PersonPerson 類別來類別來模擬人類,然後擴充模擬人類,然後擴充 PersonPerson類別建立類別建立 StudentStudent 類別來模擬類別來模擬學生,如右圖所示:學生,如右圖所示:

15-1-1 15-1-1 類別繼承的基礎類別繼承的基礎 -- 圖例說明圖例說明 PersonPerson 類別是類別是 StudentStudent 類別的「父類類別的「父類

別」(別」( SuperclassSuperclass )或「基礎類別」)或「基礎類別」(( Base ClassBase Class ),反之),反之 StudentStudent 類別是類別是PersonPerson 類別的「子類別」(類別的「子類別」( SubclassSubclass ))或「延伸類別」(或「延伸類別」( Derived ClassDerived Class )。)。

UMLUML 類別圖的繼承是使用空心的箭頭線來類別圖的繼承是使用空心的箭頭線來標示兩個類別間的關係。標示兩個類別間的關係。

15-1-1 15-1-1 類別繼承的基礎類別繼承的基礎 -- 類別架構類別架構 繼承不只可以多個子類別繼承同一個父類別,還繼承不只可以多個子類別繼承同一個父類別,還

可以擁有很多層的繼承,如果將整個類別關聯性可以擁有很多層的繼承,如果將整個類別關聯性(( RelationshipsRelationships )的樹狀結構都繪出來,稱為)的樹狀結構都繪出來,稱為「類別架構」(「類別架構」( Class HierarchyClass Hierarchy ),如下圖所),如下圖所示:示:

15-1-1 15-1-1 類別繼承的基礎類別繼承的基礎 -- 兄弟類別兄弟類別 TruckTruck、、 CarCar和和 MotorcycleMotorcycle 類別是「兄類別是「兄

弟類別」(弟類別」( Sibling ClassesSibling Classes ),因為擁有),因為擁有相同相同 VehicleVehicle 父類別。當然我們可以繼續父類別。當然我們可以繼續繼承類別繼承類別 CarCar ,類別,類別 SportsCarSportsCar和和 JeepJeep也是類別也是類別 VehicleVehicle 的子類別,不過並不是的子類別,不過並不是直接繼承的子類別。直接繼承的子類別。

簡單的說,簡單的說, CarCar 類別是類別是 SportsCarSportsCar和和JeepJeep 的直接父類別(的直接父類別( Direct Base ClasDirect Base Classs ),), VehicleVehicle 類別則是類別則是 SportsCarSportsCar和和JeepJeep 的間接父類別(的間接父類別( Indirect Base Indirect Base ClassClass )。)。

15-1-2 15-1-2 實作繼承實作繼承 -- 父類別的宣告父類別的宣告 父類別父類別 vehiclevehicle 定義車輛的基本資料,如下所示:定義車輛的基本資料,如下所示:class vehicle {class vehicle {private:private: int engineNo;int engineNo; string owner;string owner;public:public: void setNumber(int no) { engineNo = no; }void setNumber(int no) { engineNo = no; } void setOwner(string owner) { this->owner void setOwner(string owner) { this->owner

= owner; }= owner; } void printVehicle() { }void printVehicle() { }};};

15-1-2 15-1-2 實作繼承實作繼承 -- 子類別的宣告子類別的宣告(( 語法語法 ))

類別如果是繼承自存在的其他類別,其宣告語法,類別如果是繼承自存在的其他類別,其宣告語法,如下所示:如下所示:

class class 子類別名稱 子類別名稱 : : 存取修飾子 父類別名稱 存取修飾子 父類別名稱 {{

// // 擴充的成員資料和函數擴充的成員資料和函數};}; 上述類別宣告使用「上述類別宣告使用「 :: 」運算子後跟著父類別,」運算子後跟著父類別,

表示擴充父類別的宣告,在父類別前方的存取修表示擴充父類別的宣告,在父類別前方的存取修飾子可以定義繼承父類別的存取範圍,其值可以飾子可以定義繼承父類別的存取範圍,其值可以是是 privateprivate、、 protectedprotected和和 publicpublic ,詳細說明,詳細說明請參閱下一節。請參閱下一節。

15-1-2 15-1-2 實作繼承實作繼承 -- 子類別的宣告子類別的宣告(( 範例範例 ))

因為車輛可以分成很多種,例如:卡車、機車和因為車輛可以分成很多種,例如:卡車、機車和轎車等,以轎車轎車等,以轎車 carcar 子類別為例的類別宣告,如子類別為例的類別宣告,如下所示:下所示:

class car : public vehicle {class car : public vehicle {private:private: int doors;int doors;public:public: car(string owner, int no, int doors) { }car(string owner, int no, int doors) { } void printCar() { }void printCar() { }};};

15-1-2 15-1-2 實作繼承實作繼承 -UML-UML 類別圖類別圖 UMLUML 類別圖,如下圖所示:類別圖,如下圖所示:

15-1-2 15-1-2 實作繼承實作繼承 -- 繼承的成員種類繼承的成員種類 在在 C++C++ 語言宣告的子類別可以繼承父類別語言宣告的子類別可以繼承父類別

的所有成員資料和函數,但是並不包含:的所有成員資料和函數,但是並不包含:• 父類別的建構子和解構子。父類別的建構子和解構子。• 父類別的朋友關係。父類別的朋友關係。• 父類別的指定運算子父類別的指定運算子 == 。。

15-1-3 15-1-3 父類別的存取控制父類別的存取控制 -- 說明說明 在在 C++C++ 語言的子類別使用哪一種存取控制來繼承父類別,語言的子類別使用哪一種存取控制來繼承父類別,

將影響成員的存取範圍,如下所示:將影響成員的存取範圍,如下所示:父類別名稱 父類別名稱 {{private:private: …… ……protected:protected: …… ……public:public: …… ……};};class class 子類別名稱 子類別名稱 : : 存取修飾子 父類別名稱 存取修飾子 父類別名稱 {{ …… ……};};

15-1-3 15-1-3 父類別的存取控制父類別的存取控制 -- 父類別父類別的存取控制的存取控制

在子類別是使用存取修飾子在子類別是使用存取修飾子privateprivate、、 protectedprotected和和 publicpublic 來繼承父來繼承父類別。父類別各種存取控制的說明,如下類別。父類別各種存取控制的說明,如下表所示:表所示: public修飾子 protected修飾子 private修飾子public區塊 public區塊(子) protected區塊(子) private區塊(子)protected區塊 protected區塊(子) protected區塊(子) private區塊(子)private區塊 不能存取 不能存取 不能存取

15-1-3 15-1-3 父類別的存取控制父類別的存取控制 -- 子類別子類別的存取控制的存取控制

子類別是否能夠存取父類別指定區塊的成子類別是否能夠存取父類別指定區塊的成員函數和資料,需視它屬於類別的員函數和資料,需視它屬於類別的 publicpublic、、protectedprotected和和 privateprivate 成員而定,筆者整成員而定,筆者整理如下表所示:理如下表所示:存取種類 public區塊 protected區塊 private區塊同類別的成員 可 可 可子類別的成員 可 可 不可非類別的成員 可 不可 不可

15-2 15-2 覆寫與隱藏父類別的成員覆寫與隱藏父類別的成員 15-2-1 15-2-1 覆寫父類別的成員函數覆寫父類別的成員函數 15-2-2 15-2-2 隱藏父類別的成員資料隱藏父類別的成員資料

15-2-1 15-2-1 覆寫父類別的成員函數覆寫父類別的成員函數 -- 說說明明

在父類別的成員函數如果不符合需求,子在父類別的成員函數如果不符合需求,子類別可以宣告同名、同參數列和傳回值的類別可以宣告同名、同參數列和傳回值的函數來取代父類別的成員函數,稱為「覆函數來取代父類別的成員函數,稱為「覆寫」(寫」( OverrideOverride )。)。

15-2-1 15-2-1 覆寫父類別的成員函數覆寫父類別的成員函數 -- 父父類別類別

在父類別在父類別 vehiclevehicle 擁有一個靜態成員函數和成員擁有一個靜態成員函數和成員函數需要覆寫,如下所示:函數需要覆寫,如下所示:

class vehicle {class vehicle {private:private: ……… ………public:public: static void showBrand() { }static void showBrand() { } ……… ……… void printVehicle(int index) { }void printVehicle(int index) { }};}; 上述上述 showBrand()showBrand()和和 printVehicle()printVehicle() 是需要覆是需要覆

寫的成員函數。寫的成員函數。

15-2-1 15-2-1 覆寫父類別的成員函數覆寫父類別的成員函數 -- 子子類別類別

子類別子類別 carcar 使用使用 publicpublic 繼承父類別繼承父類別 vehiclevehicle ,如,如下所示:下所示:

class car : public vehicle {class car : public vehicle {private:private: ……… ………public:public: ……… ……… static void showBrand() { }static void showBrand() { } void printVehicle(int no) { }void printVehicle(int no) { }};}; 在程式碼呼叫在程式碼呼叫 carcar 物件的成員函數時,就是呼叫物件的成員函數時,就是呼叫

子類別子類別 carcar 的函數,而不是父類別的同名函數。的函數,而不是父類別的同名函數。

15-2-2 15-2-2 隱藏父類別的成員資料隱藏父類別的成員資料 -- 說說明明

除了父類別的成員函數外,子類別也可以除了父類別的成員函數外,子類別也可以隱藏父類別成員資料的變數,只需變數名隱藏父類別成員資料的變數,只需變數名稱相同,就算變數型態不同也一樣可以隱稱相同,就算變數型態不同也一樣可以隱藏。藏。

15-2-2 15-2-2 隱藏父類別的成員資料隱藏父類別的成員資料 -- 父父類別類別

例如:父類別例如:父類別 vehiclevehicle 的成員變數的成員變數 ownerowner 是宣告是宣告在在 publicpublic 區塊的區塊的 stringstring 字串型態,如下所示:字串型態,如下所示:

class vehicle {class vehicle {

private:private:

……… ………

public:public:

string owner;string owner;

……… ………

};};

15-2-2 15-2-2 隱藏父類別的成員資料隱藏父類別的成員資料 -- 子子類別類別

在子類別在子類別 carcar 使用使用 publicpublic 存取修飾子繼承父類別存取修飾子繼承父類別vehiclevehicle ,如下所示:,如下所示:

class car : public vehicle {class car : public vehicle {private:private: int owner;int owner; ……… ………public:public: ……… ………};}; carcar 物件的成員變數物件的成員變數 ownerowner 是整數是整數 intint ,不再是,不再是

stringstring 字串,原來父類別字串,原來父類別 publicpublic 區塊的區塊的 ownerowner成員變數被隱藏起來。成員變數被隱藏起來。

15-3 15-3 子類別的建構與解構子子類別的建構與解構子 15-3-1 15-3-1 子類別的建構與解構順序子類別的建構與解構順序 15-3-2 15-3-2 子類別傳遞參數給父類別子類別傳遞參數給父類別

15-3-1 15-3-1 子類別的建構與解構順子類別的建構與解構順序序

當建立子類別的物件呼叫子類別的建構子當建立子類別的物件呼叫子類別的建構子時,它會先初始化父類別的成員,也就是時,它會先初始化父類別的成員,也就是呼叫父類別的建構子。如果子類別沒有建呼叫父類別的建構子。如果子類別沒有建構子,在建立物件時,預設建構子構子,在建立物件時,預設建構子(( Default ConstructorDefault Constructor )就會呼叫父類)就會呼叫父類別的預設建構子。別的預設建構子。

簡單的說,在呼叫子類別的建構子前,會簡單的說,在呼叫子類別的建構子前,會先呼叫父類別的建構子,而解構子剛好與先呼叫父類別的建構子,而解構子剛好與建構子是相反順序,也就是子類別的建構建構子是相反順序,也就是子類別的建構子是在父類別的建構子之前呼叫。子是在父類別的建構子之前呼叫。

15-3-2 15-3-2 子類別傳遞參數給父類子類別傳遞參數給父類別別

當父類別擁有建構子時,在子類別可以傳遞參數當父類別擁有建構子時,在子類別可以傳遞參數給父類別的建構子。例如:給父類別的建構子。例如: carcar 類別是繼承自類別是繼承自vehiclevehicle ,此時,此時 carcar 子類別的建構子,如下所示:子類別的建構子,如下所示:

car(int owner, int no, int doors) : car(int owner, int no, int doors) : vehicle(owner, no) {vehicle(owner, no) {

this->doors = doors;this->doors = doors;}} 上述建構子的「上述建構子的「 :: 」運算子後是傳遞給父類別」運算子後是傳遞給父類別

vehiclevehicle 建構子的參數,如果父類別不只一個,建構子的參數,如果父類別不只一個,請使用「請使用「 ,, 」號分隔。」號分隔。

其中傳遞給父類別參數的值,就是傳入子類別建其中傳遞給父類別參數的值,就是傳入子類別建構子的參數值,以此例,構子的參數值,以此例, ownerowner和和 nono 也是子類也是子類別建構子的參數。別建構子的參數。

15-4 15-4 多重繼承多重繼承 -- 說明說明 「多重繼承」(「多重繼承」( Multiple InheritanceMultiple Inheritance ))

是指一個類別能夠繼承多個父類別,如下是指一個類別能夠繼承多個父類別,如下圖所示:圖所示:

15-4 15-4 多重繼承多重繼承 -- 語法語法 多重繼承。其宣告語法如下所示:多重繼承。其宣告語法如下所示:class class 子類別名稱 子類別名稱 : : 存取修飾子 父類別名存取修飾子 父類別名稱稱 , , 存取修飾子 父類別名稱存取修飾子 父類別名稱…… .. {.. {

…… …… // // 額外的成員資料和函數額外的成員資料和函數};}; 上述類別和繼承類別的語法相似,因為繼上述類別和繼承類別的語法相似,因為繼

承類別不只一個,需要使用「承類別不只一個,需要使用「 ,, 」逗號來分」逗號來分隔。隔。

15-4 15-4 多重繼承多重繼承 -- 範例範例 例如:繼承自例如:繼承自 trucktruck和和 driverdriver 類別的子類類別的子類別別 driven_tuckdriven_tuck ,如下所示:,如下所示:

class driven_truck : public truck, public class driven_truck : public truck, public driver {driver {

public:public: driven_truck(float pl, float fl, float pay, int driven_truck(float pl, float fl, float pay, int

pd):pd): truck(pl, fl), driver( pay, pd) { }truck(pl, fl), driver( pay, pd) { } float costPerTom( float cost_of_gas ) { }float costPerTom( float cost_of_gas ) { } void printData() { } void printData() { } };};

15-5 15-5 軟體工程與繼承軟體工程與繼承 -- 圖例圖例 軟體工程的繼承是類別關聯性(軟體工程的繼承是類別關聯性( RelationshipRelationship

ss ),它是指不同類別間的關係。例如:繼承是),它是指不同類別間的關係。例如:繼承是Is-aIs-a 類別關聯性,稱為一般關係類別關聯性,稱為一般關係(( GereralizationGereralization )。成品和零件()。成品和零件( Whole-Whole-PartPart )的類別關聯性,即)的類別關聯性,即 Part-ofPart-of和和 Has-aHas-a 關係,關係,如下圖所示:如下圖所示:

15-5 15-5 軟體工程與繼承軟體工程與繼承 -- 圖例說明圖例說明 Part-ofPart-of和和 Has-aHas-a 關係的說明,如下所示:關係的說明,如下所示:

• Part-ofPart-of 關係:指此類別是其他類別的零件,關係:指此類別是其他類別的零件,以上圖為例以上圖為例 WheelWheel 車輪和車輪和 DoorDoor 車門是車門是 CarCar車類別的零件。車類別的零件。

• Has-aHas-a 關係:相反於關係:相反於 Part-ofPart-of 關係,關係, CarCar 類別類別Has-aHas-a 擁有擁有 WheelWheel和和 DoorDoor 類別。類別。

在在 UMLUML 的上述關係稱為「聚合關係」的上述關係稱為「聚合關係」(( AggregationAggregation ),或是另一種更強調),或是另一種更強調Whole-partWhole-part 關係稱為「組成關係」關係稱為「組成關係」(( CompositionComposition )。)。

15-6 15-6 類別的型態轉換與檢查類別的型態轉換與檢查 -- 說明說明 在在 ANSI-C++ANSI-C++ 定義定義

reinterpret_castreinterpret_cast、、 static_caststatic_cast、、 dynamic_cdynamic_castast和和 const_castconst_cast 四種新的型態轉換運算子,可四種新的型態轉換運算子,可以使用在類別型態的轉換,其基本語法如下所示:以使用在類別型態的轉換,其基本語法如下所示:

static_cast<static_cast< 類別類別 **>(>( 運算式運算式 ););const_cast<const_cast< 類別類別 **>(>( 運算式運算式 ););dynamic_cast<dynamic_cast< 類別類別 **>(>( 運算式運算式 ););reinterpret_cast<reinterpret_cast< 類別類別 **>(>( 運算式運算式 );); ANSI-C++ANSI-C++ 還定義全新還定義全新 typeidtypeid 運算子,可以檢運算子,可以檢

查指定運算式或類別的型態,其語法如下所示:查指定運算式或類別的型態,其語法如下所示:typeid(typeid( 運算式運算式 ))

15-6 15-6 類別的型態轉換與檢查類別的型態轉換與檢查 --static_caststatic_cast 運算子運算子

C++C++的的 static_caststatic_cast 運算子可以將類別型態指標運算子可以將類別型態指標轉換成其他類別型態的指標,只需是編譯程式能轉換成其他類別型態的指標,只需是編譯程式能夠自動轉型的型態迫換,都可以使用夠自動轉型的型態迫換,都可以使用 static_caststatic_cast運算子來明確表示所需的型態轉換,如下所示:運算子來明確表示所需的型態轉換,如下所示:

class A {};class A {};class B: public A {};class B: public A {};A *a = new A;A *a = new A;B *b = static_cast<B*>(a);B *b = static_cast<B*>(a); 上述程式碼使用上述程式碼使用 static_cast<B*>static_cast<B*> 運算子,將父運算子,將父

類別類別 AA 物件指標物件指標 aa ,型態轉換成子類別,型態轉換成子類別 BB 的物件的物件指標指標 bb 。。

15-6 15-6 類別的型態轉換與檢查類別的型態轉換與檢查 --const_castconst_cast 運算子運算子

C++C++的的 const_castconst_cast 運算子可以取消常數運算子可以取消常數物件指標成為一般物件指標,如下所示:物件指標成為一般物件指標,如下所示:

class C {};class C {};

const C *c1 = new C;const C *c1 = new C;

C *c2 = const_cast<C*>(c1);C *c2 = const_cast<C*>(c1); 上述程式碼使用上述程式碼使用 const_cast<C*>const_cast<C*> 運算子,運算子,

將常數物件指標將常數物件指標 c1c1 型態轉換成非常數物件型態轉換成非常數物件指標指標 c2c2 。。

15-6 15-6 類別的型態轉換與檢查類別的型態轉換與檢查 --dynamic_castdynamic_cast 運算子運算子

C++C++的的 dynamic_castdynamic_cast 運算子主要是在類別架運算子主要是在類別架構中,安全的進行型態轉換,構中,安全的進行型態轉換, static_caststatic_cast和和dynamic_castdynamic_cast 運算子的差異,在於運算子的差異,在於dynamic_castdynamic_cast 會進行檢查,這是在執行期檢查會進行檢查,這是在執行期檢查型態轉換的指標是否指向合法的需求型態,如果型態轉換的指標是否指向合法的需求型態,如果不是,就傳回不是,就傳回 NULLNULL 指標,如下所示:指標,如下所示:

class D { virtual void dummy() {}; };class D { virtual void dummy() {}; };class E: public D {};class E: public D {};D *d1 = new E;D *d1 = new E;D *d2 = new D;D *d2 = new D;E *e1 = dynamic_cast<E*>(d1);E *e1 = dynamic_cast<E*>(d1);E *e2 = dynamic_cast<E*>(d2); // E *e2 = dynamic_cast<E*>(d2); // 傳回傳回 NULLNULL 指標指標

15-6 15-6 類別的型態轉換與檢查類別的型態轉換與檢查 --reinterpret_castreinterpret_cast 運算子運算子

C++C++的的 reinterpret_castreinterpret_cast 運算子可以在完全無運算子可以在完全無關的類別型態間進行轉換,其操作是二進位複製,關的類別型態間進行轉換,其操作是二進位複製,從一個指標複製至另一個指標,如下所示:從一個指標複製至另一個指標,如下所示:

class F {};class F {};

class G {};class G {};

F *f = new F;F *f = new F;

G *g = reinterpret_cast<G*>(f);G *g = reinterpret_cast<G*>(f); 上述程式碼使用上述程式碼使用 reinterpret_cast<G*>reinterpret_cast<G*> 運算子,運算子,

將類別將類別 FF 物件指標物件指標 ff ,型態轉換成類別,型態轉換成類別 GG 的物件的物件指標指標 gg 。。

15-6 15-6 類別的型態轉換與檢查類別的型態轉換與檢查 --typeidtypeid 運算子運算子

C++C++的的 typeidtypeid 運算子可以在執行期傳回運算式運算子可以在執行期傳回運算式的型態資訊,我們可以使用關係運算子「的型態資訊,我們可以使用關係運算子「 ==== 」」和「和「 !=!= 」來比較其傳回值,如下所示:」來比較其傳回值,如下所示:

class H {};class H {};H *h1;H *h1;H h2;H h2;if ( typeid(h1) != typeid(h2) ) {if ( typeid(h1) != typeid(h2) ) { cout << "h1cout << "h1與與 h2h2 型態不同型態不同 \n";\n";}} 上述程式碼使用上述程式碼使用 typeidtypeid 運算子檢查物件變數運算子檢查物件變數 h2h2

和物件指標變數和物件指標變數 h1h1 是否是相同型態。是否是相同型態。