-
面向對象的三大特性
-
封裝
-
定義:將數據和對該數據進行合法操作的函數封裝在一起作爲一個類的定義,即用類進行數據抽象。
-
繼承
-
定義:用類派生從一個類繼承另一個類,派生類繼承基類的成員。
-
訪問控制與繼承
訪問方式 |
private |
protected |
public |
同一個類和友元類 |
可以 |
可以 |
可以 |
派生類 |
不可以 |
可以 |
可以 |
外部類 |
不可以 |
不可以 |
可以 |
繼承方式 | private繼承 | protected繼承 | public繼承 |
基類的private成員 | 不可見 | 不可見 | 不可見 |
基類的protected成員 | 變爲private成員 | 仍爲protected成員 | 仍爲protected成員 |
基類的public成員 | 變爲private成員 | 變爲protected成員 | 仍爲public成員 |
-
構造函數與析構函數的調用:
- 構造派生類對象:a.調用派生類的構造函數;b.在派生類構造函數初始化列表處調用基類的構造函數;c.完成基類構造函數;d.返回並完成派生類構造函數剩下部分
- 析構派生類對象:a.調用派生類的析構函數;b.執行派生類析構函數的函數體; c.調用基類的析構函數;d.執行基類析構函數函數體後返回
-
派生類的默認成員函數
-
在繼承關係裏,派生類中如果沒有顯示定義這六個成員函數,編譯系統則會默認合成六個成員函數,即構造函數、拷貝構造函數、析構函數、賦值操作符重載、取地址操作符重載、const修飾的取地址操作符重載。
-
轉換與繼承
- 派生類對象可以賦值或初始化基類對象。(public)
- 基類對象不可以賦值給派生類對象。(public)
- 基類對象的指針和引用可以指向派生類對象。(public)
- 派生類對象的指針和引用不能指向基類對象,但是可以通過強制轉化完成。(public)
-
繼承與靜態成員
- 基類定義了static成員,則整個繼承體系裏面只有一個這樣的成員。無論派生出多少個子類,都只有一個static成員實例。(所有類對象共享一個static類成員,static類對象必須要在類外進行初始化。)
- static成員遵循常規訪問控制與繼承
-
繼承與友元關係
- 友元關係不能繼承。基類的友元對派生類的成員沒有特殊的訪問權限。
-
繼承情況下的類作用域
- 在繼承體系中基類和派生類都有獨立的作用域。當子類和父類中有同名成員,子類成員將屏蔽父類成員的直接訪問。
-
多態
-
定義:編譯或運行時決定使用基類中定義的函數還是使用派生類中定義的函數。“一個接口,多種方法”
-
靜態多態與動態多態
-
區別:在什麼時候將函數實現和函數調用關聯起來,是在編譯時期還是運行時期。
-
靜態多態是指在編譯期間就可以確定函數的調用地址,並生產代碼,這就是靜態的,也就是說地址是早早綁定的,靜態多態也往往被叫做靜態聯編。
-
靜態多態往往通過函數重載來實現,調用速度快,效率高但缺乏靈活性。
-
動態多態則是指函數調用的地址不能在編譯器期間確定,必須需要在運行時才確定,這就屬於晚綁定,動態多態也往往被叫做動態聯編。
-
動態多態通過虛函數來實現,虛函數允許派生類重新定義成員函數,而派生類重新定義基類的做法稱爲覆蓋或稱爲重寫。
-
重載(overload)與覆蓋(重寫override)
-
重載要求函數名相同,但是參數列表必須不同,返回值可以相同也可以不同。
-
覆蓋要求函數名、參數列表、返回值必須相同。
-
在類中重載是同一個類中不同成員函數之間的關係。
-
在類中覆蓋則是子類和基類之間不同成員函數之間的關係。
-
重載函數的調用是根據參數列表來決定調用哪一個函數。
-
覆蓋函數的調用是根據對象類型的不同決定調用哪一個。
-
在類中對成員函數重載是不能夠實現多態。
-
在子類中對基類虛函數的覆蓋可以實現多態。
-
虛函數
-
定義:虛函數是在基類中使用關鍵字virtual聲明的函數。在派生類中重新定義基類中定義的虛函數時,會告訴編譯器不要靜態鏈接到該函數。
-
純虛函數
-
定義:純虛函數是一種特殊的虛函數,在許多情況下,在基類中不能對虛函數給出有意義的實現,而把它聲明爲純虛函數,它的實現留給該基類的派生類去做。純虛函數在聲明虛函數時被“初始化”爲0的函數。在成員函數的形參後面寫上=0,包含純虛函數的類叫做抽象類(也叫接口類),抽象類不能實例化出對象。純虛函數在派生類中重新定義以後,派生類才能實例化出對象
-
虛函數表
-
定義:C++ 中的虛函數(Virtual Function)是通過一張虛函數表(Virtual Table)來實現的,簡稱爲V-Table。虛表是屬於類的,而不是屬於某個具體的對象,一個類只需要一個虛表即可。同一個類的所有對象都使用同一個虛表。
-
虛表指針
-
定義:爲了指定對象的虛表,對象內部包含一個虛表的指針,來指向自己所使用的虛表。爲了讓每個包含虛表的類的對象都擁有一個虛表指針,編譯器在類中添加了一個指針,*__vptr,用來指向虛表。這樣,當類的對象在創建時便擁有了這個指針,且這個指針的值會自動被設置爲指向類的虛表。
-
虛函數與構造函數(構造函數爲什麼一般不定義爲虛函數)
-
若構造函數聲明爲虛函數,那麼由於對象還未創建,還沒有內存空間,更沒有虛函數表地址用來調用虛函數來構造函數了
-
虛函數可以調用在派生類中的函數,如果構造函數定義爲虛函數,那麼構造函數可能操作沒有被初始化的成員,導致出錯。(虛函數只需要”部分“信息就可以自動調用,特別地,這種機制允許我們在只知道接口,不知道具體對象的類型的情況下調用函數。而實例化一個對象需要完整的信息,特別地,必須知道實例化對象的確切類型。)
-
如果構造函數使用虛機制,他將只產生通過它自己的V-Table的調用,而不是最後派生的VTABLE。
-
虛函數與析構函數(析構函數爲什麼一般定義爲虛函數)
-
對象已經被構造,它的*__vptr已經被初始化了,所以可以發生虛函數調用。
-
析構函數不設爲虛函數,可能只調用基類的析構函數,而沒有調用派生類的析構函數,容易造成內存泄露。
-
純虛析構函數
-
虛析構函數是爲了讓通過基類指針或引用可以正確釋放派生類對象。有時候如果想讓基類成爲一個抽象類,也就是不能被實例化,可以爲類引入一個純虛函數。但如果手上沒有任何pure virtual函數時,該怎麼辦?由於抽象類總是會被作爲基類用於派生的,而基類就該有一個虛的析構函數,並且由純虛函數可以導致抽象類。所以常常把基類的析構函數聲明爲純虛析構函數。又由於所有對象析構時,最後都會調用其基類的析構函數,所以基類的析構函數必須有定義。純虛析構函數也不例外。
本系列文章目的爲個人準備面試的簡單總結,文中多有不足,敬請批評指正!
參考:
https://blog.csdn.net/m0_38121874/article/details/76147303
http://www.runoob.com/cplusplus/cpp-inheritance.html
https://blog.csdn.net/chenkaixin_1024/article/details/59489817
https://blog.csdn.net/xy913741894/article/details/52939323
https://blog.csdn.net/eagle_1036077338/article/details/53186171
https://blog.csdn.net/tennysonsky/article/details/49252679