天天看點

【C++】(二十七)派生類成員的通路 |派生類的構造和析構函數

文章目錄

  • 派生類成員的通路
    • 1 類的保護成員
    • 2 派生類成員的通路權限
    • 3 指派相容規則
  • 派生類的構造和析構函數
    • 1 派生類的構造函數
      • 1.1 派生類構造函數的定義
      • 1.2 組合關系的派生類的構造函數
    • 2 派生類的析構函數

對派生類來說,成員分為兩大類:

(1)一類是從基類繼承過來的成員;

(2)一類是自己新生成的成員。

如果沒有繼承,一個類隻有兩種類型的通路者:類成員和類使用者。将類劃分為private和public通路級别反映對通路者的通路權限:

  • 類使用者隻能通路公有成員,
  • 類成員和友元既可以通路公有成員也可以通路私有成員。

有了繼承,就有了類的第三種通路者:派生類成員。

派生類通常需要通路(一般為私有的)基類成員,

為了允許這種通路而仍然禁止外部對基類的一般通路

,可以使用protected通路标号。

類的protected部分仍然不能被類使用者通路,但可以被派生類通路

隻有基類類成員及其友元可以通路基類的private部分,

派生類不能通路基類的私有成員

類的保護成員用protected通路标号聲明,

可以認為protected通路标号是private和public的混合

①像私有成員一樣,

保護成員不能被類使用者通路

②像公有成員一樣,

保護成員可以被該類的派生類通路

如果基類聲明了私有成員,那麼任何派生類都是不能通路它們的,若希望在派生類中能通路它們,應當把它們聲明為保護成員。

是以如果在一個類中聲明了保護成員,就意味着該類可能要用作基類,在它的派生類中會通路這些成員。

【C++】(二十七)派生類成員的通路 |派生類的構造和析構函數

派生類中包含繼承來的成員和自己新增的成員,因而産生了這兩部分成員的關系和通路屬性的問題。

對基類成員和派生類自身的成員是按不同的原則處理的,需要考慮以下6種情形:

(1)基類的成員和友元通路基類成員;

(2)派生類的成員和友元通路派生類自己新增的成員;

對于第1種和第2種的情形,可以按以下規則處理,即:

  • 基類的類成員和友元可以通路基類成員,
  • 派生類的類成員和友元可以通路派生新增的類成員。
  • 私有成員隻能被同一類中的類成員通路,公有成員可以被類使用者通路。

(3)基類的成員通路派生類新增的成員;

第3種的情形,基類的成員不能直接通路派生類的成員(因為有基類的時候尚未有派生類),但可以通過虛函數間接通路派生類的成員。

(4)類使用者通路派生類的成員;

第4種的情形,比較明确,類使用者可以通路派生類的公有成員,不能通路派生類任何私有的或保護的成員。

(5)派生類新增的成員通路基類的類成員;

(6)類使用者通路派生類的基類成員。

第5種和第6種的情形比較複雜,其通路形式實際是兩種形式:

  • ①内部通路:由派生類中新增成員對基類繼承來的成員的通路。
  • ②對象通路:在派生類外部,通過派生類的對象對從基類繼承來的成員的通路。

不同的繼承方式決定了基類成員在派生類中的通路屬性。

(1)公有繼承(public inheritance)

基類的公有成員和保護成員在派生類中保持原有通路屬性,私有成員仍為基類私有。

(2)私有繼承(private inheritance)

基類的所有成員在派生類中為私有成員。

(3)保護繼承(protected inheritance)

基類的公有成員和保護成員在派生類中成了保護成員,私有成員仍為基類私有。

【C++】(二十七)派生類成員的通路 |派生類的構造和析構函數

無論采用何種繼承方式得到的派生類,派生類成員及其友元都不能通路基類的私有成員,派生類外部的使用者隻能通路公有屬性的成員

多級派生的情況下,保護繼承和私有繼承會進一步地将基類的通路權限隐蔽成不可通路的。

一般地,

保護繼承與私有繼承在實際程式設計中是極少使用的,它們隻在技術理論上有意義

【C++】(二十七)派生類成員的通路 |派生類的構造和析構函數
【C++】(二十七)派生類成員的通路 |派生類的構造和析構函數

指派相容規則是指

在需要基類對象的任何地方,都可以使用公有派生類的對象來替代

通過公有繼承,派生類得到了基類中除構造函數、析構函數之外的所有成員。這樣,公有派生類實際就具備了基類的所有功能,

凡是基類能解決的問題,公有派生類都可以解決

指派相容規則中所指的替代包括以下的情況:

①派生類的對象可以指派給基類對象;

②派生類的對象可以初始化基類的引用;

③派生類對象的位址可以賦給指向基類的指針。

例:

class Base { }; //基類
class Derive : public Base { }; //公有派生類
Base b,
*pb; //定義基類對象、指針
Derive d; //定義派生類對象
           

這時,支援下面三種操作:

b=d; //派生類對象指派給基類,複制基類繼承部分
Base &rb=d; //基類引用到派生類對象
pb=&d; //基類指針指向派生類對象
           

指派相容規則是C++多态性的重要基礎之一。

在定義派生類時,

派生類并沒有把基類的構造函數和析構函數繼承下來

。是以,對繼承的基類成員初始化的工作要由派生類的構造函數承擔,同時基類的析構函數也要被派生類的析構函數來調用。

在執行派生類的構造函數時,使派生類的資料成員和基類的資料成員同時都被初始化。其定義形式如下:

派生類名(形式參數清單) : 基類名(基類構造函數實參清單),派生類初始化清單
{
	派生類初始化函數體
}
           

“基類名(基類構造函數實參清單)”即是調用基類構造函數,而

派生類新增加的資料成員可以在“派生類初始化清單”(盡量在此)初始化

,也可以在“派生類初始化函數體”中初始化。

派生類構造函數的調用順序是:

調用基類構造函數

如下面例子的Point(a,b);

②執行派生類初始化清單;

③執行派生類初始化函數體;

例如:

class Point { int x,y;
public: Point(int a,int b):x(a),y(b) { } //構造函數
};
class Rect : public Point { int h,w;
public: Rect(int a,int b,int c,int d):Point(a,b),h(c),w(d) { } //派生類構造函數
};
           

假定派生類A和類B的關系是組合關系,類A中有類B的子對象。

如果類B有預設構造函數,或者參數全是預設參數的構造函數,或者有無參數的構造函數,那麼類A的構造函數中可以不用顯式初始化子對象

。編譯器總是會自動調用B的構造函數進行初始化。

可以在一個類的構造函數中顯式地初始化其子對象,初始化式隻能在構造函數初始化清單中,形式為:

類名(形式參數清單) : 子對象名(子對象構造函數實參清單),類初始化清單
{
	類初始化函數體
}
           

調用順序為:

①調用基類構造函數;

②調用子對象構造函數,各個子對象時按其聲明的次序先後調用;

③執行派生類初始化清單;

④執行派生類初始化函數體;

說明:

(1)如果在基類和子對象所屬類的定義中都沒有定義帶參數的構造函數,而且也不需要對派生類自己的資料成員初始化,那麼可以不必顯式地定義派生類構造函數。派生類會合成一個預設構造函數,并在調用派生類構造函數時,會自動先調用基類的預設構造函數和子對象所屬類的預設構造函數。

class A { }; //合成預設構造函數
class B { }; //合成預設構造函數
class D: public B { A a; }; //派生類合成預設構造函數
           

(2)如果在基類中沒有定義構造函數,或定義了沒有參數的構造函數,那麼,在定義派生類構造函數時可以不顯式地調用基類構造函數。在調用派生類構造函數時,系統會自動先調用基類的無參數構造函數或預設構造函數。

例如:

class B { public: B() { } }; //無參數構造函數
class D: public B {
	D() { } //派生類構造函數不必顯式調用基類構造函數
};
           

(3)如果在基類或子對象所屬類的定義中定義了帶參數的構造函數,那麼就必須顯式地定義派生類構造函數,并在派生類構造函數中顯式地調用基類或子對象所屬類的構造函數。

class A { public: A(int) { } }; //有參數構造函數
class B { public: B(int) { } }; //有參數構造函數
class D: public B {
	D(int x) : a(x),B(x) { } //顯式調用基類或子對象構造函數
	A a;
};
           

(4)如果在基類中既定義了無參數的構造函數,又定義了有參的構造函數(構造函數重載),則在定義派生類構造函數時,既可以顯式調用基類構造函數,也可以不調用基類構造函數。

在調用派生類構造函數時,根據構造函數的内容決定調用基類的有參數的構造函數還是無參數的構造函數

可以根據派生類的需要決定采用哪一種方式。

在派生時,派生類是不能繼承基類的析構函數的,也

需要通過派生類的析構函數去調用基類的析構函數

派生類中可以根據需要定義自己的析構函數,用來

對派生類中所增加的成員進行清理工作

。基類的清理工作仍然由基類的析構函數負責。

在執行派生類的析構函數時,系統會自動調用基類的析構函數和子對象的析構函數,對基類和子對象進行清理。

析構函數調用的順序與構造函數正好相反:先執行派生類自己的析構函數,對派生類新增加的成員進行清理,然後調用子對象的析構函數,對子對象進行清理,最後調用基類的析構函數,對基類進行清理。