物件導向程式設計有4個主要特點:抽象,封裝,繼承和多態。
1.1繼承與派生的概念
在C++中可重用性是通過繼承(inheritance)這一機制來實現的。繼承是C++的一個只要組成部分.
一個新類從已有的父類那裡獲得其已有特徵,這種現象稱為類的繼承。通過繼承,一個建立子類從已有的父類那裡獲得父類的特性。
從另一個角度說,從已有的類(父類)產生一個新的子類,稱為類的派生。
類的繼承是用已有的類來簡曆專用類的編程技術.
衍生類別繼承了基類的所有資料成員和成員函數,並可以對成員做必要的增加和調整。
一個基類可以派生出多個衍生類別,每一個衍生類別又可以作為基類再派生出新的衍生類別,因此基類和衍生類別是相對而言的.
一個衍生類別不僅可以從一個基類派生,也可以從多個基類派生。一個衍生類別有兩個或者多個基類的稱為多重繼承(multiple inheritance)。
關於衍生類別和基類的關係,可以表述為:衍生類別是基類的具體化,而基類則是衍生類別的抽象。
繼承方式包括:public(公用的),private(私人的),protected(保護的).此項是可選的,如果不寫此項,則預設是private的。
衍生類別從基類接收成員,衍生類別把基類全部的成員(不包括建構函式和解構函式)接手過來,也就是說是沒有選擇的,不能選擇接收其中一部分成員,而捨棄另一部分成員.
不同繼承方式決定了基類成員在衍生類別中的訪問屬性.
1)公用繼承(public inheritance)
基類的公用成員和保護成員在衍生類別中保持原有訪問屬性,其私人成員仍為基類私人。
2)私人繼承(private inheritance)
基類的公有成員和保護成員在衍生類別中成了私人成員,其私人成員仍為基類私人。
3)受保護的繼承(protected inheritance)
基類的公用成員和保護成員在衍生類別中成了保護成員,其私人成員仍為基類私人.
公用繼承:
採用公用繼承方式時,基類的公用成員和保護成員在衍生類別中仍然保持其公用成員和保護成員的屬性,而基類的私人成員在衍生類別中並沒有成為衍生類別的私人成員,它仍然是基類的私人成員,只有基類的成員函數可以應用它,而不能被衍生類別的成員函數應用,因此就成為衍生類別中的不可訪問的成員。
私用繼承
私人基類的公用成員和保護成員在衍生類別中的訪問屬性相當於衍生類別中的私人成員,即衍生類別的成員函數能訪問他們,而在衍生類別不能訪問他們。私人基類的私人成員在衍生類別中成為不可訪問的成員,只有基類的成員函數可以應用他們.一個基類成員在基類中的訪問屬性和在衍生類別中的訪問屬性可能是不同的.
保護繼承
如果基類聲明了私人成員,那麼任何衍生類別都是不能訪問他們的,若希望在衍生類別中能訪問他們,應當把它們聲明為保護成員,如果一個類中聲明了保護成員,就意味著該類可能要用作基類,在他的衍生類別中會訪問這些成員
比較一下私人繼承和保護繼承,可以發現,在直接衍生類別中,以上兩種繼承方式的作用實際上是相同的;在類外不能訪問任何成員,而在衍生類別中可以通過成員函數訪問基類中的公用成員和保護成員。
在衍生類別中,成員有4種不同的訪問屬性:
1)公用的,衍生類別和衍生類別外都可以訪問。
2)受保護的,衍生類別內可以訪問,衍生類別外不能訪問,其下一層的衍生類別可以訪問。
3)私人的,衍生類別內可以訪問,衍生類別外不能訪問
4)不可訪問的,衍生類別內和衍生類別外都不能訪問。
衍生類別的建構函式和解構函式
使用者在聲明類時可以不定義建構函式,系統會自動化佈建一個預設的建構函式,在定義類對象時會自動調用這個預設的建構函式。這個建構函式實際上是一個空函數,不執行任何操作,如果需要對類中的資料成員初始化,應自己定義建構函式。
建構函式的主要作用是對資料成員初始化,在設計衍生類別的建構函式時,不僅要考慮衍生類別所新增的資料成員的初始化,還應當考慮基類的資料成員初始化,也就是說,希望在執行衍生類別的建構函式時,使衍生類別的資料庫成員和基類的資料成員同時都被初始化,解決這個問題的思路是:在執行衍生類別的建構函式時,調用基類的建構函式。
其一般形式為
衍生類別建構函式名(總參數列表):基類建構函式名(參數類別)
{衍生類別中新增資料成員初始化語句}
請注意:在類中對衍生類別建構函式作聲明時,不包括基類建構函式名及其參數列表,只在定義函數時才將他們列出。
實際上,在衍生類別建構函式中對基類成員初始化,就是建構函式初始化表,也就是說,不僅可以利用初始化表對建構函式的資料成員初始化,而且可以利用初始化表調用衍生類別的基類建構函式,實現對基類資料成員的初始化。也可以在同一個建構函式的定義中同時實現這兩種功能。
在建立一個對象時,執行建構函式的順序是:衍生類別建構函式先調用基類的建構函式,再執行衍生類別建構函式本身(即衍生類別建構函式的函數體)
在衍生類別對象釋放時,先執行衍生類別的解構函式,再執行基類的解構函式。
衍生類別建構函式的任務應該包括3個部分
對基類資料成員的初始化,對子物件資料成員初始化,對衍生類別資料成員初始化
程式中衍生類別建構函式首部如下:
Student1(int n,string nam,int n1,string nam1,int a,string ad):Student(n,nam),monotor(n1,nam1)
歸納起來,定義衍生類別建構函式的一般形式為
衍生類別建構函式名(總參數表列):基類建構函式名(參數表列),子物件名(參數表列)
{衍生類別中新增資料成員初始化語句}
執行衍生類別建構函式的順序是:
調用基類建構函式,對基類資料成員初始化
調用子物件建構函式,對子物件資料成員初始化
再執行衍生類別建構函式本身,對衍生類別資料成員初始化。
衍生類別建構函式的總參數表列中的參數,應當包括基類建構函式和子物件的參數列表中的參數,基類建構函式和子物件的次序可以是任意的。如上面的衍生類別建構函式首部可以寫成
Student(int n,string nam,int n1,string nam1,int a,stirng ad):monitor(n1,nam1),Student(n,nam)
如果在基類中沒有定義建構函式,或者定義了沒有參數的建構函式,那麼在定義衍生類別建構函式時可以不寫基類建構函式,因為此時衍生類別建構函式沒有向基類建構函式傳遞參數的任務。調用衍生類別建構函式時系統自動首先調用基類的預設建構函式。如果基類和子物件類型的聲明中都沒有定義帶參數的建構函式,而且也不需對衍生類別自己的資料成員初始化,則可以不必顯示的定義衍生類別建構函式。因為此時衍生類別建構函式既沒有像基類建構函式和子物件建構函式傳遞參數的任務,也沒有對衍生類別資料成員初始化的任務。在建立衍生類別對象時,系統會自動調用系統提供的衍生類別的預設建構函式,並在執行衍生類別預設建構函式的過程中,調用基類的預設建構函式和子物件類型預設建構函式。
如果在基類或者子物件類型的聲明中定義了才參數的建構函式,那麼就必選顯示的定義衍生類別建構函式,並在衍生類別建構函式中寫出基類或子物件類型的建構函式及其參數。
如果在基類中既定義無參的建構函式,又定義了有參的建構函式(建構函式重載),則在定義衍生類別建構函式時,既可以包含基類建構函式及其參數,也可以不包含基類建構函式,在調用衍生類別建構函式時,根據建構函式的內容決定調用基類的有參建構函式還是無參建構函式。
衍生類別的解構函式
在執行衍生類別的解構函式時,系統會自動調用基類的解構函式和子物件的解構函式,對基類和子物件進行清理。調用的順序和建構函式正好相反;先執行衍生類別自己的解構函式,對衍生類別新增加的成員進行清理,然後調用子物件的解構函式,對子物件進行清理,最後調用基類的解構函式,對基類進行清理.
聲明多重繼承的方法
如果已聲明了類A,類B和類C,可以聲明多重繼承的衍生類別D:
class D:public A,private B,protected C
{類D新增加的成員}
多重繼承衍生類別的建構函式形式於單繼承時的建構函式形式基本相同,只有在初始表中包含多個基類建構函式
如
衍生類別建構函式名(總參數列表):基類1建構函式(參數類別),基類2建構函式(參數列表),基類3建構函式(參數列表)
{衍生類別中新增成員初始化}
各基類的排列順序任意。衍生類別建構函式同樣為:先調用基類的建構函式,再執行衍生類別建構函式的函數體.調用基類建構函式的順序是按照聲明衍生類別時基類出現的順序.
多重繼承一起的二義性問題
虛基類
虛基類的作用: 如果一個衍生類別有多個直接基類,而這些直接基類又有一個共同的基類,則在最終的衍生類別中會保留該間接共同基類資料成員的多份同名成員,在引用這些同名成員時,必選在衍生類別對象名後增加直接基類名,以避免產生二義性,使其唯一地標識一個成員.
在一個類中 保留間接共同基類的多份同名成員,這種現象是人們不希望出現的。
C++提供虛基類(virtual base class)的方法,使得在繼承間接共同基類時只保留一份成員.
現在,將類A聲明為虛基類,方法如下
class A//聲明基類A
{......};
class B:virtual public A//聲明類B是類A的公用衍生類別,A是B的虛基類
{......};
class C:virtual public A//聲明類C是類A的公用衍生類別,A是C的虛基類
{......};
注意:虛基類並不是聲明基類時聲明的,而是在聲明衍生類別時,指定繼承方式時聲明的。因為一個基類可以在產生一個衍生類別時作為虛基類,而在聲明另一個衍生類別時不作為虛基類,聲明虛基類的一般形式為
class 衍生類別名:virtual 繼承方式 基類名
經過這樣的聲明後,當基類通過多條派生路徑被一個衍生類別繼承時,該衍生類別只繼承該基類一次。
需要注意:為了保證虛基類在衍生類別中只繼承一次,應當在該基類的所有直接衍生類別中聲明為虛基類,否則仍然會出現對基類的多次繼承.
虛基類的初始化
C++編譯系統只執行最後的衍生類別對虛基類的建構函式的調用,而忽略虛基類的其他衍生類別(如類B和類C)對虛基類的建構函式的調用,這就保證了虛基類的資料成員不會被多次初始化.
注意:在定義衍生類別的建構函式時,與以往使用的方法有所不同。規定:在最後的衍生類別中不僅僅要負責對其直接基類進行初始化,還要負責對虛基類初始化
使用多重繼承時要十分小心,經常會出現二義性問題。許多專業人員認為:不要提倡在程式中使用多重繼承,只有在比較簡單和不易出現二義性的情況下或實在必要時才使用多重繼承,能用單一繼承解決的問題不要使用多重繼承,也是由於這個原因,有些面向多謝的程式設計語言(如java,smalltalk)並不支援多重繼承.
基類與衍生類別的轉換
基類於衍生類別對象之間有賦值相容關係,由於衍生類別中包含從基類繼承的成員,因此可以將衍生類別的值賦給基類對象,在用到基類對象的時候可以其子類對象代替.
1)衍生類別對象可以向基類對象賦值。
如
A a1;//定義基類A對象a1
B b1;//定義類A的公用衍生類別B的對象b1
a1 = b1;//用衍生類別B對象b1對基類對象a1賦值
在賦值時捨棄衍生類別自己的成員,實際上,所謂賦值只是對資料成員賦值,對成員函數不存在賦值問題。
請注意:賦值後不能企圖通過對象a1去訪問衍生類別對象b1的成員,因為b1的成員於a1的成員是不同的。
應當注意,只能用子類對象對其基類對象賦值,而不能用基類對象對其之類對象賦值,理由是顯然的,因為基類對象不包括衍生類別的成員,無法對衍生類別的成員賦值
2)衍生類別對象可以提到基類對象向基類對象的引用進行賦值或者初始化.
如已定義了基類A對象a1,可以定義a1的引用變數
A a1;//定義基類A對象a1
B b1;//定義公用衍生類別B對象b1
A &r = a1;//定義基類A對象的引用變數r,並哦那個a1對其初始化
這時,引用變數r是a1的別名,r和a1共用同一段儲存單元。也可以用子類對象初始化引用變數r,將上面最後一行改為:
A &r = b1;//定義基類A對象的引用變數r,並用衍生類別B對象b1對其初始化
或者保留上面第3行"A &r = a1;",而對r重新賦值:
r = b1;//用衍生類別B對象b1對a1的引用變數r賦值
3)如果函數的參數是基類對象或者基類對象的引用,相應的實參可以用子類對象
4)衍生類別對象的地址可以賦給指向基類對象的指標變數,也就是說,指向基類對象的指標變數也可以指向衍生類別對象.
用指向基類對象的指標變數指向子類對象是合法的,安全的,不會出現遍以上的錯誤,但在應用上卻不能完全滿足人們的希望,人們有時希望通過使用基類指標能夠基類和子類對象的成員。在下一章就要解決這個問題。辦法是使用虛函數和多態性