虛函數讀書筆記

成員函數與繼承
派生類可以繼承其基類的成員,然而當遇到與類型相關的操作時,派生類必須對其重新定義。換句話說,派生類需要對這些操作提供自己的新定義以覆蓋(override)從基類繼承而來的舊定義。
在C++語言中,基類必須將它的兩種成員函數分開來:一種是基類希望其派生類進行覆蓋的函數;另一種是基類希望派生類直接繼承而不要改變的函數。對於前者,基類通常將其定義爲虛函數(virtual)。當我們使用指針或引用調用虛函數時,該調用將被動態綁定。根據引用或指針所綁定的對象類型不同,該調用可能執行基類的版本,也可能執行某個派生類的版本。
基類通過在其成員函數的聲明語句之前加上virtual使得該函數執行動態綁定。任何構造函數之外的非靜態函數都可以是虛函數關鍵字virtual只能出現在類內部的聲明語句之前而不能用於類外部的函數定義如果基類把一個函數聲明成虛函數,則該函數在派生類中隱式地也是虛函數
成員函數如果沒被聲明爲虛函數,則其解析過程發生在編譯時而非運行時。對虛函數的調用可能在運行時才被解析。當某個虛函數通過指針或引用調用時,編譯器產生的代碼知道運行時才能確定應該調用哪個版本的函數。被調用的函數是與綁定到指針或引用上的對象的動態類型相匹配的那一個。
必須要搞清楚的一點是,動態綁定只有當我們通過指針或引用調用虛函數時纔會發生。當我們通過一個具有普通類型(非引用非指針)的表達式調用虛函數時,在編譯時就會將調用的版本確定下來。

關鍵概念:C++的多態性
OOP的核心思想是多態性(polymorphism)。我們把具有繼承關係的多個類型稱爲多態類型,因爲我們使用這些類型的“多種形式”而無須在意它們的差異。引用或指針的靜態類型與動態類型不同這一事實正是C++支持多態性的根本所在。
當我們使用基類的引用或指針調用基類中的定義的一個函數時,我們並不知道該函數真正作用的對象是什麼類型,因爲它可能是一個基類的對象也可能是一個派生類的對象。如果該函數是虛函數,則只到運行時纔會決定到底執行哪個版本,判斷的依據是引用或指針所綁定的對象的真實類型。
另一方面,對非虛函數的調用在編譯時進行綁定。類似的,通過對象進行的函數(虛函數或非虛函數)調用也在編譯時綁定。對象的類型是確定不變的,我們無論如何都不可能令對象的動態類型與靜態類型不一致。因此,通過對象進行的函數調用將在編譯時綁定到該對象所屬類中的函數版本上。
當且晉檔對通過指針或引用調用虛函數時,纔會在運行時解析該調用,也只有在這種情況下對象的動態類型纔有可能與靜態類型不同。

派生類中的虛函數
派生類經常(不總是)覆蓋它的繼承的虛函數。如果派生類沒有覆蓋其基類中的某個虛函數,則該虛函數的行爲類似於其他的普通成員,派生類會直接繼承其在基類中版本。
派生類可以在他覆蓋的函數前使用virtual關鍵字,但不是非得這麼做。派生類如果定義一個函數與基類中虛函數的名字相同但是形參列表不同,這仍然是合法的行爲。編譯器將認爲新定義的這個函數與基類中原有的函數是相互獨立的。這時,派生類的函數並沒有覆蓋掉基類中的版本。就實際的變成習慣而言,這種聲明往往意味着發生了錯誤,因爲我們可能原本希望派生類能覆蓋基類中的虛函數,但是一不小心把形參列表弄錯了。
要想調試並發現這樣的錯誤顯然非常困難。在C++11中我們可以使用override關鍵字來說明派生類中的虛函數。這麼做的好處是在使得程序員的意圖更加清晰的同時讓編譯器可以爲我們發現一些錯誤,後者在編程實際中顯得更加重要。如果使用override標記某個函數,但該函數並沒有覆蓋已存在的虛函數,此時編譯器將報錯。
我們還能把某個函數指定爲final,如果我們已經把函數定義成final,則之後任何嘗試覆蓋該函數的操作都將引發錯誤。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章