C++面試常見題目5_面向物件的三大特性(封裝,繼承,多型)
-
面向物件的三大特性
-
封裝
-
定義:將資料和對該資料進行合法操作的函式封裝在一起作為一個類的定義,即用類進行資料抽象。
-
繼承
-
定義:用類派生從一個類繼承另一個類,派生類繼承基類的成員。
-
訪問控制與繼承
訪問方式 |
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函式時,該怎麼辦?由於抽象類總是會被作為基類用於派生的,而基類就該有一個虛的解構函式,並且由純虛擬函式可以導致抽象類。所以常常把基類的解構函式宣告為純虛解構函式。又由於所有物件析構時,最後都會呼叫其基類的解構函式,所以基類的解構函式必須有定義。純虛解構函式也不例外。
本系列文章目的為個人準備面試的簡單總結,文中多有不足,敬請批評指正!