C++類繼承
C++ Primer Plus讀書筆記
類繼承
1.概念:類繼承機制只需要提供新特性,甚至不需要訪問原始碼就可以派生出類。
2.寫法:在類名稱後加冒號和需要繼承的類名。
class jicheng: public ZhuMeng()
{
};
- 上面寫的被繼承類是ZhuMeng(),public表示是使用ZhuMeng是公有基類,這種派生方式叫做公有派生。
- 使用公有派生時:
- 基類的公有成員將稱為派生類的公有成員;
- 基類的私有成員也成為派生類的一部分,但是隻能通過基類的公有和保護方法訪問。
3.繼承類的新特性
派生類需要自己的建構函式;
建構函式的細節:
- 派生類不能直接訪問基類的私有成員,而必須通過基類方法進行訪問。派生類建構函式必須使用基類建構函式。
- 建立派生類物件時,首先應當建立基類物件。這就意味著基類物件應當在進入派生類建構函式之前被建立。因此,就需要使用成員初始化列表來完成這項工作。
- 如果不進行成員初始化,則會執行預設的基類建構函式,除非使用預設的建構函式,否則應顯式呼叫正確的基類建構函式。
//派生類頭函式 #ifndef JICHENG_H_ #define JICHENG_H_ #include"ZhuMeng.h" #include<iostream> class jicheng : public
//派生類建構函式 #include"jicheng.h" jicheng::jicheng(int shi1, int fen1, int miao1) : ZhuMeng(shi1, fen1, miao1) { //這裡的冒號後面部分是成員初始化列表,它是可執行的程式碼 }
//基類頭函式 #ifndef ZHUMENG_H_ #define ZHUMENG_H_
- 派生類可以根據需要新增額外的資料成員和成員函式。
- 綜合上面兩條所述,派生類建構函式的要點:
- 首先建立基類物件
- 派生類的建構函式應通過成員初始化列表將基類資訊傳遞給基類建構函式
- 派生類建構函式應初始化派生類新增的資料成員。
- 當派生類物件過期時,程式將首先呼叫派生類解構函式,然後在呼叫基類解構函式。
4.派生類和基類之間的特殊關係
- 派生類物件可以使用基類的方法(方法不是私有即可)
jicheng jicheng_name1(1,2,3);
jicheng_name1.printt();//呼叫基類的方法
- 基類的指標可以在不進行顯式型別轉換的情況下指向派生類的物件;基類引用可以在不進行顯式型別轉換的情況下引用派生類的物件。基類指標或引用只能用於呼叫基類方法。
- 如果允許基類引用隱式地引用派生類物件,則可以使用基類引用為派生類物件呼叫基類方法,因為派生類繼承了基類的方法,因此這樣做不會有什麼問題。
ZhuMeng * pointer1 = &jicheng_name1;
ZhuMeng & reference1 = jicheng_name1;
- 如果允許基類引用隱式地引用派生類物件,則可以使用基類引用為派生類物件呼叫基類方法,因為派生類繼承了基類的方法,因此這樣做不會有什麼問題。
- 不可以將基類物件和地址賦給派生類引用和指標。
- 如果可以將基類物件賦給派生類引用,則派生類引用可以為基類物件呼叫派生類方法,這樣會出現錯誤。
ZhuMeng jilei_name1(1,2,3);
jicheng * jicheng_pointer = &jilei_name1;//錯誤
jicheng & jicheng_reference = jilei_name1;//錯誤
- 如果可以將基類物件賦給派生類引用,則派生類引用可以為基類物件呼叫派生類方法,這樣會出現錯誤。
對於形參為指向基類的指標的函式,可以使用基類物件的地址或派生類物件的地址作為實參。
ZhuMeng jilei_name2(1,2,3); jicheng jicheng_name2(1,2,3); void func(const ZhuMeng * pt); func(jilei_name2); func(jicheng_name2);
- 將基類物件初始化為派生類的物件,需要匹配建構函式,也可以使用預設的匹配建構函式。
jicheng(const ZhuMeng &);
- 將基類物件初始化為派生類的物件,需要匹配建構函式,也可以使用預設的匹配建構函式。
5.繼承:is-a關係
派生類和基類之間的特殊關係是基於C++繼承的底層模型的,實際上,繼承有三種方式:
公有繼承
,私有繼承
,保護繼承
三種方式。公有繼承:
- 建立一種
is-a
關係,即派生類物件也是一個基類物件,可以對基類物件執行任何操作,也可以對派生類物件執行。因為派生類可以新增特性,因此將這種關係稱為is-a-kind-of
(是一種)關係也是可以的。派生類 is a 基類。 - 公有繼承不建立
has-a
關係,比如說A has-a B
,A負責構建和銷燬B並在它的生存期使用它。 - 公有繼承不建立
is-like-a
關係,它不採用明喻,假設說律師像鯊魚,但律師並不是鯊魚,鯊魚可以在水下生活,而律師不能,因此律師並不繼承鯊魚,因為繼承不能刪去基類的屬性。在有些情況下可以設計一個包含公有特徵的類,然後使用is-a或者has-a關係,在這個類的基礎上定義相關的類。 - 公有繼承不建立
is-implemented-as-a
(作為……來實現)關係。 - 公有繼承不建立
uses-a
關係,比如計算機和印表機之間的關係。
- 建立一種
6.多型公有繼承
- 如果希望同一個方法在派生類和基類中的行為不同,則需要使用用於實現多型的公有繼承。
7.虛擬函式
接著上面的多型公有繼承問題
如果方法是通過引用或指標而不是物件呼叫的,它將確定使用哪一種方法。如果沒有使用關鍵字
virtual
,程式將根據引用型別或指標型別選擇方法。如果使用了關鍵字virtual
,程式將根據引用或指標指向的物件的型別來選擇方法。例如:
//View()函式在ZhuMeng和jicheng兩個類中都存在 ZhuMeng a1(1,2,3); jicheng a2(1,2,3); jicheng & ref_1 = a1; jicheng & ref_2 = a2; //如果View()函式是virtual ref_1.View();//呼叫ZhuMeng::View() ref_2.View();//呼叫jicheng::View() //如果View()函式不加virtual ref_1.View();//呼叫jicheng::View() ref_2.View();//呼叫jicheng::View()
- 也就是說,如果加virtual,則根據被引用物件型別來呼叫多型函式,如果不加,則根據引用物件的型別來呼叫多型函式。
- 如果某個方法在基類中被宣告為虛的,則它在派生類中將自動稱為虛方法。
8.派生類的資料訪問方式
在派生類中不能直接訪問基類的private資料,而必須使用基類的公有方法才能訪問這些資料。訪問的方式取決於方法。
非建構函式不能使用成員初始化列表語法,但是派生類方法可以呼叫公有的基類方法。
9.虛的解構函式
- 如果解構函式不是虛的,則將只調用對應於指標型別的解構函式,如果解構函式是虛的,則會呼叫相應物件的解構函式,因此使用虛的解構函式可以確保正確的解構函式序列被呼叫。
10.靜態聯編和動態聯編
將原始碼中的函式呼叫解釋為執行特定的函式程式碼塊被稱為函式名聯編binding。
在編譯過程中進行聯編叫做靜態聯編,又稱為早起聯編,然而虛擬函式使得這項工作變得更困難。因為使用哪一個函式是不能在編譯時確定的,因為編譯器不知道使用者將選擇哪種型別的物件。所以,編譯器必須生成能夠在程式執行時選擇正確的虛方法的程式碼,這被稱為動態聯編或晚期聯編。
指標和引用型別的相容性:
- 在C++中,動態聯編與通過指標和引用呼叫方法相關。通常不允許將某一個型別的地址賦給另一種型別的指標。
- 但是指向基類的引用或指標可以引用派生類物件,這叫做向上強制轉換。
- 相反的,指向派生類的指標或引用用來指向基類,叫做向下強制轉換,如果沒有顯式定義是不被允許的。
編譯器對非虛方法使用靜態聯編。這種方法會增加程式的額外開銷,如果一個類不用做基類的話,是不需要動態聯編的,同時,如果派生類不重新定義基類的任何方法,則也是不需要動態聯編的,這些情況下使用靜態聯編是高效、合理的方法。
11.虛擬函式的實現
- 通常虛擬函式的實現是編譯器的任務,編譯器處理虛擬函式的方法是:給每個物件新增一個隱藏成員,隱藏成員中儲存了一個指向函式地址陣列的指標。這種陣列稱為虛擬函式表,虛擬函式表中儲存了為類物件進行宣告的虛擬函式的地址。
- 舉個例子,例如基類物件包含一個指標,該指標指向基類中所有虛擬函式的地址表,派生類物件將包含一個指向獨立地址表的指標。如果派生類提供了虛擬函式的新定義,該虛擬函式將儲存新函式地址,如果派生類中沒有提供新的虛擬函式,則會儲存函式原版的地址。
- 總之,使用虛擬函式時,在記憶體和執行速度上都會有一定的成本。
- 每個物件都增大,增大量為儲存地址的空間
- 對於每個類,編譯器都建立一個虛擬函式地址表
- 對於每個函式呼叫,都需要執行一項額外的操作,即到表中查詢地址。
12.虛擬函式
建構函式:建構函式不能是虛擬函式,建立派生類物件的時候呼叫派生類建構函式,而不是基類的建構函式,然後派生類的建構函式將會使用基類的某個建構函式。但是派生類並不會繼承基類的建構函式
解構函式:解構函式應當是虛擬函式,除非這個類不用做基類。同時還要注意,基類和派生類都需要虛解構函式。
友元:友元不能是虛擬函式,因為友元不是類成員,而只有成員才能是虛擬函式,但是可以通過讓友元函式使用虛成員函式。
注意:重新定義並不會生成函式的兩個版本,而是隱藏了基類中的版本。也就是說,重新定義繼承的方法並不是過載。
13.protected
這是一種訪問控制模式,它與private類似,在類外只能用公有類成員來訪問protected部分中的類成員。
private和protected的區別為:派生類的成員可以直接訪問基類的protected而不能直接訪問private成員,也就是說:對於外部世界來說,protected和private類似,但是對於派生類來說,protected和public類似。
14.抽象基類ABC(abstract base class)
有時候兩個類並不適合繼承的is-a關係,將兩個類的公共特性放到一個抽象基類中,這樣就可以使用基類指標來同時管理兩個物件。就是類裡定義了純虛成員函式的類。純虛擬函式只提供了介面,並沒有具體實現。抽象類不能被例項化(不能建立物件),通常是作為基類供子類繼承,子類中重寫虛擬函式,實現具體的介面。簡言之,ABC描述的是至少使用一個純虛擬函式的介面,從ABC派生出的類將根據派生類的具體特徵,使用常規虛擬函式來實現這種介面。
下面說一下純虛擬函式的概念:純虛擬函式是可以不提供實現的,其實現由具體的派生類來進行。要求宣告的時候結尾加上=0,如下
virtual func() =0;
- 如果是const的成員函式,則再加上const,放在一起就是const=0。
15.繼承與動態記憶體分配
- 如果在派生類中使用了new來分配空間,則需要在相應的解構函式中將這些記憶體給釋放掉。
- 一般來說,解構函式,複製建構函式和賦值運算子都需要進行重寫,但是在派生類中是訪問不了基類的內容的,這就需要顯式地呼叫這些運算子來將派生類物件傳遞給基類,基類自己選取自己的成員進行賦值。