1. 程式人生 > >關於基類與派生類相關,以及繼承

關於基類與派生類相關,以及繼承

多型就是一個基類可以指向其任意派生類的能力,即基類的指標或者引用可以指其派生類,作用就就是希望使用派生具體的實現介面功能
1.多型性只存在類的繼承層次中
2.必須使用基類的指標或者引用才有效.
3.多型可以動態呼叫的函式必須是由virtual宣告的函式
4.多型性是執行時的動態載入,不是連結時也不是編譯時決定的
5.當出現虛擬函式時,為避免基類指標或者引用的的區域性析構,那麼基類的析構必須宣告為virtual。派生構造因為在初始化時就是呼叫派生類建構函式的,
而且構造是深度優先的,所以構造不用
(即基類指標只調用了基類的解構函式,但派生類未呼叫,故根源的基類的析構宣告為virtual,那麼就會先呼叫派生類的析構定義例項了)


一個類物件的靜態和動態型別是相同的故虛擬函式機制只在使用指標和引用時才會如預期般地起作用。

抽象基類(只宣告呼叫介面函式純虛擬函式)<-實現基類(宣告虛擬函式 + 共用功能函式)<-派生類(介面的具體實現 + 自有業務處理邏輯)
一般基類不應包括太多變數,尤其是抽象基類,基本就不應該含有變數,因為含有太多變數會導致類在構造和複製時效能和效率都會降低。


構造順序Base->Devide,析構順序Devide->Base
基類建構函式被呼叫的順序反映了派生類繼承層次結構中深度優先的遍歷過程,派生類的解構函式呼叫順序與它的建構函式呼叫順序相反
故不能基類的建構函式或者解構函式裡面呼叫虛擬函式,因為這樣會引起程式的崩潰。
對於派生類物件在基類建構函式因早於派生,基類指標會先構造基類,但如果基類建構函式中呼叫的派生類的虛擬實現,就有可能使用到派生構造的物件,故有衝突
對於派生類物件在基類解構函式中也是如此,基類指標會先析構派生類,而到基類析構裡面呼叫派生的實現,如果物件不再可能引發錯誤,派生類部分也是未定義的但是這一次不是因為它還沒有被構造而是因為它已經被銷


如果類不是基類,或者沒有多型的情況出現,就不需要宣告為virtual,但如果是繼承關係,基類的析構必須是虛擬的。

預設情況下函式是在編譯時刻被靜態解析的,在C++中通過一種被稱為虛擬函式virtual function的機制來支援動態繫結
在程序執行時刻需要解析出被呼叫的函式這個解析過程被稱為動態繫結dynamic bindng,

純虛擬函式(purl virtual):virtual T func(T arg) = 0;(例如通常使用的virtual const char* what() const throw() {};)
purl virtual函式會導致類抽象化,即該類不能被實體化,也就是不能建立一個類物件(實體變數),只能建立一個類指標;
包含一個或多個純虛擬函式的類被編譯器識別為抽象基類,試圖建立一個抽象基類的獨立類物件會導致編譯時刻錯誤;

類似地通過虛擬機制呼叫純虛擬函式也是錯誤的
繼承的派生類則需要將所有的純虛擬函式進行實現,如果沒有實質的實現,也需用通過空函式來例項化,這樣在建立物件時才不會因抽象而無法引用函式.
否則也是無法建立物件,只能通過引用或者指標進行使用;


在基類與派生類如果不想按照動態繫結則可以通過類域來指定實現方法,不指定的話就會導致基類無法使用派生類的函數了。
如果基類和派生類存在同樣函式不用virtual會怎麼樣? 那麼實現的內容具體會由指標的型別是基類還是派生類決定,
如果是基類指標指向派生類物件則仍按基類的定義例項實現, 從而失去多型效應,即不會按照對應派生的定義例項去實現功能
如果是派生類指標指向派生類則按照派生類的定義例項實現,這樣會引起隱藏基類相同名稱的函式,是hide而非override
如果想明確指定可以使用類域來實現,而不是直接訪問。


內聯解構函式可能是程式程式碼膨脹的一個源泉,因為它被插入到函式中的每個退出點,在每個return 語句之前解構函式都必須被內聯地展開, 以析構每一個活動的區域性類物件.
比如在函式中的每一個return語句都會觸發一個解構函式,解決方法是在函式中減少return語句的呼叫,儘量集中到一點使用return退出.
要麼就在實現原始碼中顯式的宣告析構為非內聯的.
這裡提醒如果析構包括太多操作,請記住應該如何控制內聯和呼叫函式內區域性物件的操作

*************************************************************************************************************
類繼承建構函式的順序
1.基類建構函式。基類一般必須含有預設的建構函式,否則會引起派生類編譯因沒有預設建構函式而無法通過。
如果有多個基類則建構函式的呼叫順序是某類在類派生表中出現的順序而不是它們在成員初始化表中的順序 
2.成員類物件建構函式。如果有多個成員類物件則建構函式的呼叫順序是物件在類中被宣告的順序而不是它們出現在成員初始化表中的順序
3.派生類物件建構函式

1

2

3

4

5

6

7

8

9

10

class C: public A, public B {

private:

G g;

public:

C():e(1), c(2), g(3) {};

 

private:

F c;

E e;

}

  

定義一個C的構造順序為:A->B->G->F->E->C


*************************************************************************************************************
虛擬繼承virtual inheritance,在虛擬繼承下只有一個共享的基類子物件被繼承而無論該基類在派生層次中出現多少次。
共享的基類子物件被稱為虛擬基類virtual base class,在虛擬繼承下基類子物件的複製及由此而引起的二義性都被消除了。

任何層次的多重繼承如果出現類域二義性都會導致編譯時刻的錯誤。

因為當中間派生類虛擬基礎基類時,作為中間派生類,所有對虛擬基類的建構函式呼叫都被自動抑制了,對於建構函式最好提供
顯式的建構函式,一個公共(public)函式作為最終派生類使用,和一個protected()最為中間派生類使用,以避免不必要的引數

虛擬繼承只是消除了多箇中間派生類會重複複製相同基類和由此引發的重複基類二義性的問題,但對於中間派生類的虛擬函式卻無法保證能消除二義性。比如

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

class A {

virtual void func();

virtual void func1();

virtual void func2(){...};

...

};

class B:public virtual A {

virtual void func(){...};

virtual void func1(){...};

...

};

class C:public virtual A {

virtual void func(){..};

...

};

 

class D:public B, public C {

void func(){A::func();}; /* must be specific */

void func1(func1();); /* B::func1() */

void func2(func2();); /* A::func2() */

...

}

 

以上虛擬基類的虛擬函式有3種情況:
1.沒有被中間派生類重新定義具體實現。A::func2()
2.中間派生類同一層次僅有一個特例。A::func1()->B::func1()
3.中間派生類同一層次出現多個派生類(>=2)定義其具體實現。A::func(), B::func(), C::func()

在非虛擬繼承基類的中間派生類下,所有非限定修飾(不用類域)的引用100%都會引起二義性的編譯錯誤,
對於虛擬繼承,
第一種為共享基類的單個定義例項,故不存在二義性,最終派生類均可直接無二義的訪問。
第二種為同一層次僅有一個定義例項,最終派生類會根據繼承層次的優先順序(派生類高於基類),取最接近最終派生類的實現例項進行訪問。
第三種為同一層次出現多個派生類的例項,直接訪問則會引起二義性的編譯錯誤。
如果想要清晰的的引用不同層次的中間派生類,最好通過類域特指訪問特定定義例項,也可以解除因第三種情況引起的二義性編譯錯誤


虛擬建構函式順序:虛擬建構函式層次(從左至右, 深度優先) -> 非虛擬建構函式層次(從左至右, 深度優先)
比如:

1

2

3

4

5

6

7

class A{...};

class B{...};

class C: public A{...};

class D: public virtual B{...};

class E: public virtual B{...};

class F:{...};

class G: public C, public D, public E, public virtual F{...};

那麼定義一個G類物件構造順序為:
B->F->A->C->D->E->G

析構則剛剛好相反。

鄭州哪家男科醫院好

鄭州婦科醫院哪好

鄭州醫院哪家看男科好

鄭州哪家醫院看男科好