一.多態概念
同樣是動物叫的?個?為(函數),傳貓對象過去,就是”(>ω<)喵“,傳狗對象過去,就是"汪汪"。
1.根據對象不同類型,調用不同函數,這就叫做運行時多態(動態多態)
2.編譯時多態(靜態多態)主要就是我們前?講的函數重載和函數模板,他們傳不同類型的參數就可以調?不同的函數,通過參數不同達到多種形態,之所以叫編譯時多態,是因為他們實參傳給形參的參數匹配是在編譯時完成的,我們把編譯時?般歸為靜態
———————————————————————————————
二.實現多態還有兩個必須重要條件:
? 必須是通過基類的指針或者引?調?虛函數
? 被調?的函數必須是虛函數,并且完成了虛函數重寫/覆蓋。
原因分析:多態實現必須條件分析
———————————————————————————————
三.重寫/覆蓋
1.重寫意味這,通過對父類成員虛函數的聲明,然后重新寫函數實現(聲明采用基類,實現采用自己寫的)
2.函數名,參數,返回值相同
3.由于派生類重寫時的聲明采用的是,基類成員虛函數的聲明因此,在基類中已經寫過virtual在成員函數前,派生類成員函數不用再寫virtual,不過建議寫上
———————————————————————————————
四.靜態/動態多態
1.編譯時多態(靜態多態),函數重載和函數模板,編譯時傳入參數匹配,生成函數
2.運行時多態(動態多態),編譯階段無法知道,實際調用哪個版本的函數是在運行時根據對象的實際類型決定的
———————————————————————————————
五.析構函數的重寫
基類的析構函數為虛函數,此時派?類析構函數只要定義(看上面重寫分析),?論是否加virtual關鍵字,都與基類的析構函數構成重寫,雖然基類與派?類析構函數名字不同看起來不符合重寫的規則,實際上編譯器對析構函數的名稱做了特殊處理,編譯后析構函數的名稱統?處理成destructor,所以基類的析構函數加了vialtual修飾,派?類的析構函數就構成重寫。
delete的原理delete詳解剖析
- 在空間上執行析構函數,完成對象中資源的清理工作
- 調用operator delete函數釋放對象的空間
class A
{public:virtual ~A(){cout << "~A()" << endl;}
};
class B : public A
{public:~B(){cout << "~B()->delete:"<<_p<< endl;delete _p;}
protected:
int* _p = new int[10];
};
// 只有派?類Student的析構函數重寫了Person的析構函數,下?的delete對象調?析構函數,才能
//構成多態,才能保證p1和p2指向的對象正確的調?析構函數。
int main()
{A* p1 = new A;A* p2 = new B;delete p1;delete p2;return 0;
}
上面的代碼我們可以看到,如果~A(),不加virtual,那么delete p2時只調?的A的析構函數,沒有調用B的析構函數,就會導致內存泄漏問題,因為~B()中在釋放資源。
注意:這個問題?試中經常考察,?家?定要結合類似下?的樣例才能講清楚,為什么基類中的析構函數建議設計為虛函數。
———————————————————————————————
六.重載 重寫/覆蓋 隱藏 三個概念的對比辨析
———————————————————————————————
七.純虛函數和抽象類
在虛函數聲明時在函數簽名末尾添加 = 0
,則這個函數為純虛函數,純虛函數不需要定義實現(實現沒啥意義因為要被派?類重寫,但是語法上可以實現),只要聲明即可。包含純虛函數的類叫做抽象類,抽象類不能實例化出對象,如果派?類繼承后不重寫純虛函數,那么派?類也是抽象類。純虛函數某種程度上強制了派?類重寫虛函數,因為不重寫實例化不出對象
抽象類就類似,car和各種具體車的品牌,car作為抽象類 ,Benz和BMW作為派生類
———————————————————————————————
八.多態底層原理 多態實現必須條件分析
對不滿?多態條件(指針或者引?+調?虛函數)的函數調?是在編譯時綁定,也就是編譯時確定調?函數的地址,叫做靜態綁定。
? 滿?多態條件的函數調?是在運?時綁定,也就是在運?時到指向對象的虛函數表中找到調?函數的地址,也就做動態綁定。
虛函數表
? 基類對象的虛函數表中存放基類所有虛函數的地址。同類型的對象共?同?張虛表,不同類型的對象各?有獨?的虛表,所以基類和派?類有各?獨?的虛表。
? 派?類由兩部分構成,繼承下來的基類和??的成員,?般情況下,繼承下來的基類中有虛函數表指針,??就不會再?成虛函數表指針。但是要注意的這?繼承下來的基類部分虛函數表指針和基類對象的虛函數表指針不是同?個,就像基類對象的成員和派?類對象中的基類對象成員也獨?的。
? 派?類中重寫的基類的虛函數,派?類的虛函數表中對應的虛函數就會被覆蓋成派?類重寫的虛函數地址。
? 派?類的虛函數表中包含,(1)基類的虛函數地址,(2)派?類重寫的虛函數地址完成覆蓋,派?類??的虛函數地址三個部分。
? 虛函數表本質是?個存虛函數指針的指針數組,?般情況這個數組最后?放了?個0x00000000標記。(這個C++并沒有進?規定,各個編譯器??定義的,vs系列編譯器會再后?放個0x00000000標記,g++系列編譯不會放)
? 虛函數存在哪的?虛函數和普通函數?樣的,編譯好后是?段指令,都是存在代碼段的,只是虛函數的地址?存到了虛表中。
? 虛函數表存在哪的?這個問題嚴格說并沒有標準答案C++標準并沒有規定,我們寫下?的代碼可以對?驗證?下。vs下是存在代碼段(常量區)
注意:同類型對象共有同一張虛函數表
———————————————————————————————
八.指針數組/數組指針
1.指針數組:void (*pf)()
2.數組指針:int (*pa)[10]
——————————————————————————————
九.
派生類對象析構時會自動在結束時自動增加析構基類部分,目的是保證先子后父
——————————————————————————————
繼承
一.讓一個類不能被繼承
1.構造函數私有化(派生類在構造時調用不到基類構造函數)
2.關鍵字final class Base final{ };
二.
1.友元關系不能繼承
2.虛繼承(虛繼承(Virtual Inheritance)是C++中處理多重繼承時避免二義性問題的一種機制,特別是當一個類從多個基類繼承,而這些基類又有一個共同的基類時。通過使用虛繼承,可以確保共同基類的成員在派生類中只有一個副本,從而避免了重復繼承帶來的數據冗余和訪問沖突.)
三.繼承語法
四.切片特性
public繼承的派?類對象 可以賦值給 基類的指針 / 基類的引?。這?有個形象的說法叫切?或者切割。寓意把派?類中基類那部分切出來,基類指針或引?指向的是派?類中切出來的基類那部分。
五.隱藏
- 在繼承體系中基類和派?類都有獨?的作?域。
- 派?類和基類中有同名成員,派?類成員將屏蔽基類對同名成員的直接訪問,這種情況叫隱藏。(在派?類成員函數中,可以使? 基類::基類成員 顯?訪問)
- 需要注意的是如果是成員函數的隱藏,只需要函數名相同就構成隱藏。
- 注意在實際中在繼承體系??最好不要定義同名的成員。