C++学习之路—继承与派生(四)拓展与总结
(根據《C++程序設計》(譚浩強)整理,整理者:華科小濤,@http://www.cnblogs.com/hust-ghtao轉載請注明)
??? 1??? 拓展部分
本節主要由兩部分內容組成,分別是(1)基類與派生類的轉換和(2)繼承與組合
1.1??? 基類與派生類的轉換
??? 在前幾篇博客中,可以看到在3種繼承方式中,只有公有繼承能較好的保留基類的特征,它保留了除構造函數和析構函數以外所有的基類成員,基類的公有或保護成員的訪問權限在派生類中全部保留了下來,在派生類外可以調用基類的公有成員函數以訪問基類的私有成員。而非公用派生類不能實現基類的全部功能(例如在派生類外不能調用基類的公有成員函數訪問基類的私有成員)。因此,只有公有派生類才是基類真正的子類型,它完整的繼承了基類的功能。
??? 如果不同類型數據之間可以自動轉換和賦值,成為賦值兼容。基類與公用派生類之間具有賦值兼容關系,由于派生類中包含從基類繼承的成員,因此可以將派生類的值賦給基類對象,在用到基類對象的時候可以用其子對象代替。基類與(公用)派生類之間的賦值兼容表現在以下4個方面:
(1)派生類對象可以向基類對象賦值。若類B是類A的公用派生類,則可進行以下操作:
1: A a1 ; //定義基類A對象a1 2: B b1 ; //定義公用派生類B的對象b1 3: a1 = b1 ; //用派生類B的對象b1對基類對象a1賦值在進行賦值時舍棄派生類自己新增加的成員,所謂賦值只是對數據成員賦值,對成員函數不存在賦值問題。賦值后不能試圖通過對象a1去訪問派生類對象b1新增的成員,假設age是派生類B中新增加的成員,則:
1: a1.age = 23 ; //錯誤,a1中不包含派生類中增加的成員 2: b1.age = 21 ; //正確,b1中包含派生類中增加的成員?
總結:只能用子類對象對基類對象賦值,而不能用基類對象對其子類對象賦值。同一基類的不同派生類對象之間也不能賦值。
(2)派生類對象可以代替基類對象向基類對象的引用進行賦值或初始化。
如果已經定義了基類A對象a1,可以定義a1的引用變量:
1: A a1 ; //定義基類A對象a1 2: B b1 ; //定義公用派生類B對象b1 3: A& r = a1 ; //定義基類A對象的引用r,并用a1對其初始化 4: //也可以將上面最后一行改為 5: A& r = b1 ; //定義基類A對象的引用r,并用派生類B對象b1對其初始化此時r并不是b1的別名,也不是與b1共享同一段存儲單元。它只是b1中基類部分的別名,r與b1中基類部分共享同一段存儲單元,r與b1具有相同的起始地址。
(3)如果函數的參數是基類對象或基類對象的引用,相應的實參可以用子類對象。例如:
1: void fun ( A& r ) //形參是A類對象的引用 2: { 3: cout << r.num << endl ; //輸出該引用中的數據成員num 4: } 5: B b1 ; //定義公用派生類的對象b1 6: fun( b1 ) ; //輸出B對象b1的基類的數據成員num的值在fun函數中只能輸出派生類中基類成員的值。
(4)派生類對象的地址可以賦給指向基類對象的指針變量,即指向基類對象的指針變量可以指向派生類對象。示例程序如下:
1: #include <iostream> 2: #include <string> 3: using namespace std ; 4:? 5: class Student //聲明Student類 6: { 7: public: 8: Student ( int , string , float ) ; //聲明構造函數 9: void display(); //聲明輸出函數 10: private: 11: int num ; 12: string name ; 13: float score ; 14: }; 15: Student::Student( int n , string nam , float s ) //定義構造函數 16: { 17: num = n ; 18: name = nam ; 19: score = s ; 20: } 21: void Student::display() //定義輸出函數 22: { 23: cout << endl << "num:" << num << endl ; 24: cout << "name:" << name << endl ; 25: cout << "score:" << score << endl ; 26: } 27:? 28: class Graduate : public Student //聲明公用派生類Graduate 29: { 30: public: 31: Graduate( int , string , float , float ) ; //聲明構造函數 32: void display() ; //聲明輸出函數 33: private: 34: float wage ; 35: }; 36: Graduate::Graduate( int n , string nam , float s , float w ) : Stident( n , nam , s ) 37: , wage( w ) {} //定義構造函數 38: void Graduate::display() //定義輸出函數 39: { 40: Student::display() ; 41: cout << "wage:" << wage << endl ; 42: } 43:? 44: int main() 45: { 46: Student stud1( 1001 , "Li" , 87.5 ) ; //定義Student類對象stud1 47: Graduate grad1( 2001 , "wang" , 98.5 , 1000 ) ; //定義Graduate類對象grad1 48: Student *pt = &stud1 ; //定義指向基類的指針并指向stud1 49: pt->display() ; //調用stud1.display函數 50: pt = &grad1 ; //指針指向grad1 51: pt->display() ; //調用grad1.display函數 52:? 53: return 0 ; 54: }先看一下程序運行結果,再進行具體的分析:
分析:有很多讀者會認為,在派生類中有兩個同名的display成員函數,根據同名覆蓋的規則,第二次被調用的應當是派生類Graduate對象的display函數,在執行Graduate::display函數過程中調用Student::display函數,輸出num,name,score,然后再輸出wage的值。很明顯與上述結果不符,why?問題在于pt是指向Student類對象的指針變量,即使讓它指向了grad1,但實際上pt指向的是從grad1從基類繼承的部分。通過指向基類對象的指針,只能訪問派生類中的基類成員,而不能訪問派生類增加的成員。
??? 通過本例可以看到,用指向基類對象的指針變量指向子類對象是合法的、安全的,不會出現編譯上的錯誤。但人們更希望通過使用基類指針能夠調用基類和子類對象的成員,要解決這個問題,就要用到以后會講到的多態性和虛函數。
1.2?? 繼承與組合
在一個類中以另一個類的對象作為數據成員的,稱為類的組合。例如,聲明Professor類是Teacher類的派生類,另有一個類BirthDate,包含year,month,day等數據成員。我們可以將教授生日的信息加入到Professor類的聲明中。如:
1: class Teacher //聲明教師類 2: { 3: public: 4: ... 5: private: 6: int num ; 7: string name ; 8: char sex ; 9: }; 10:? 11: class Birthdate //聲明生日類 12: { 13: public: 14: ... 15: private: 16: int year ; 17: int month ; 18: int day ; 19: }; 20:? 21: class Professor : public Teacher //聲明教授類 22: { 23: public: 24: ... 25: private: 26: Birthdate birthday ; //Birthdate類的對象作為數據成員 27: };?
?
???? 類的組合和類的繼承一樣,都是有效地利用已有類的資源。但二者的概念和用法不同。通過繼承建立了派生類與基類的關系,這是一種“is-a”的關系,如“白貓是貓”,派生類是基類的具體化的實現,是基類的一種。通過組合則建立了成員類和組合類的關系,它們之間是“has-a”的關系。不能說Professor是一個Birthdate,只能說教授有一個Birthdate的屬性。
?
?? 2?? 繼承在軟件開發中的重要意義
??? 縮短軟件開發過程的關鍵是鼓勵軟件的重用。繼承機制在很大一部分上解決了這個問題。編寫面向對象的程序時要把注意力放在實現對自己有用的類上面,對已有的類加以整理和分類,進行剪裁和修改,并在此基礎上集中精力編寫派生類新增加的部分。
??? 人們為什么這么看重繼承,要求在軟件開發中使用繼承機制,盡可能的通過繼承建立一批新的類,有以下幾個原因:
(1)有許多基類是被程序的其他部分或其他程序使用的,這些程序要求保持原有的基類不受破壞。
(2)用戶往往得不到基類的源代碼。如果使用的類庫,用戶無法知道成員函數的代碼,因此也就無法對基類進行修改,保證了基類的安全。
(3)在類庫中,一個基類可能已被指定與用戶所需的多種組建有聯系,因此類庫不允許被修改。
(4)許多類是專門被設計為基類的,并沒有什么獨立的功能,只是一個框架,或者說是抽象類。設計這些通用的類目的是建立通用的數據結構,以便用戶在此基礎上添加各種功能建立派生類。
轉載于:https://www.cnblogs.com/hust-ghtao/p/3500928.html
總結
以上是生活随笔為你收集整理的C++学习之路—继承与派生(四)拓展与总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 到底什么是极简主义?
- 下一篇: 再谈原型和原型链