15.1 OOP概述
- 派生類通過使用類派生列表明確指出它是從哪一個(些)基類繼承而來的;形式是,首先是一個冒號,後面緊跟以逗號分隔的基類列表,其中每個基類前面可以有訪問說明符;
class Bulk_quote: public Quote, protect Quote1 { }
-
派生類必須在其內部對所有重新定義的虛函數進行聲明,派生類可以在這樣的函數前加上virtual關鍵字,但不是必須的, C++11新標準允許派生類顯式地著名它將使用哪個成員函數來改寫基類的虛函數,具體措施是在該函數的形參列表後增加一個override關鍵字;
-
動態綁定有時稱爲運行時綁定,當使用基類的引用(或指針)調用一個虛函數時將發生動態綁定,案例見527頁;
15.2 定義基類和派生類
- 作爲繼承關係中根節點的類(根基類)通常都會定義一個虛析構函數,即使該函數不執行任何實際操作也是如此;
- 基類必須將它的兩種成員函數區分開來,一種是基類希望派生類進行覆蓋的函數,通常聲明爲虛函數(virtual),一種是希望派生類直接繼承不做任何改變的函數;當使用指針或引用調用虛函數時,該調用將被動態綁定;
- 任何構造函數之外的非靜態函數都可以是虛函數,關鍵字virtual只能出現在類內部的聲明語句之前而不能用於類外部的函數定義
- 成員函數如果沒有被聲明爲虛函數,則其解析過程發生在編譯時而非運行時;
- 派生類經常覆蓋繼承的虛函數,如果沒有覆蓋,則直接繼承基類的版本;
- 編譯器會隱式地執行派生類到基類的類型轉換,當使用基類指針或引用來調用派生類的基類成員部分,但反過來基類不能隱式地轉化爲派生類;
- 派生類初始化的順序,先初始化基類的部分,然後按照聲明的順序依次初始化派生類的成員;
- 派生類的作用域嵌套在基類的作用域之內,對於派生類的成員來說,它使用派生類成員的方式與使用基類成員的方式沒什麼不同;
- 派生類的聲明要包含類名但是不包含它的派生列表;
- C++11新標準提供了一種防止繼承發生的方法,在類名後跟一個關鍵字final,表示此類不可繼承
class noDerived final {/* */ };
-
練習15.7
class limitedQuote : public Quote { private: size_t max_qty; double discount; public: limitedQuote(string&book, double sales_price, int qty, double disc): Quote(book, sales_price), max_qty(qty), discount(disc){} double net_price(size_t n) const override { if (n > max_qty) { return (n - max_qty) * price + max_qty * price * (1 - discount); } else { return n * price * (1 - discount); } } };
-
基類的指針或引用的靜態類型可能與其動態類型不一致,主要看其綁定的對象類型是基類類型還是派生類類型;
-
當用一個派生類對象給一個基類對象初始化或賦值時,只有該派生類對象中的基類部分會被拷貝、移動或賦值,它的派生類部分將被忽略掉;
-
理解具有繼承關係的類之間發生的類型轉換,有三點至關重要 :
1.從派生類向基類的類型轉換隻對指針或引用類型有效(即派生類的指針可以轉換爲基類指針) 2.基類向派生類不存在隱式類型轉換 3.派生類向基類的類型轉換可能會由於訪問受限而變得不可行
15.3 虛函數
- 必須爲每一個虛函數都提供定義而不管它是否被用到了,因爲編譯器也無法確定到底會用哪個虛函數;
- 多態(polymorphism)這個詞源於希臘語,含義是“多種形式”,我們把具有繼承關係的多個類型稱爲多態類型;引用或指針的靜態類型與動態類型不同這一事實正是C++語言支持多態性的根本所在;
- 對非虛函數的調用在編譯時進行綁定,通過對象進行的函數(無論是不是虛函數)調用在編譯時綁定,可以稱爲靜態綁定,通過指針對虛函數的調用稱爲動態綁定,僅由動態綁定時對象的動態類型纔有可能與靜態類型不同;
- 一個派生類的函數如果覆蓋了某個繼承而來的虛函數,則它的形參類型必須與它覆蓋的基類函數完全一致;
- 如果虛函數使用默認實參,則基類和派生類中定義的默認實參最好一致,因爲使用哪個默認實參由指針的類型決定;
- 迴避虛函數的機制是使用作用域運算符:: 強調使用基類或者某個派生類的虛函數,如
double undiscounted = baseP->Quote::net_price(42); //baseP爲基類指針,Quote::顯式告知使用Quote版本的net_price()虛函數
15.4 抽象基類
- 在函數體的位置(聲明語句的分號前)書寫=0就可以將一個虛函數說明爲純虛函數,其中,=0只能出現在類內部的虛函數聲明語句處;我們也可以爲純虛函數提供定義,不過函數體必須在類的外部;
- 含有純虛函數的類是抽象基類(abstract base class)。抽象基類負責定義接口,後續的其他類可以覆蓋該接口,而且我們不能(直接)創建一個抽象基類的對象;抽象基類的派生類如果沒有給純虛函數定義仍然是抽象基類;
- 練習15.17 報錯內容“E0322 不允許使用抽象類類型 "Disc_Quote" 的對象: “
15.5 訪問控制與繼承
- 派生類的成員和友元只能訪問派生類對象中的基類部分的受保護成員,對於普通的基類對象中的保護成員不具有特殊的訪問權限;
- 派生訪問說明符對於派生類的成員(及友元)能否訪問其直接基類的成員沒什麼影響,對基類成員的訪問權限只與基類中的訪問說明符有關;
- 派生訪問說明符的目的是控制派生類用戶(派生類的對象及派生類的派生類)對於基類成員的訪問權限;詳細看544頁例;
-
類成員的訪問說明 繼承方式\原訪問說明符 public protect private public public protect private protect protect protect private private private private private -
友元關係不能傳遞也不能繼承,每個類負責控制各自成員的訪問權限;
-
using可以改變個別成員的可訪問性,但是隻能爲派生類自己能訪問的名字提供聲明,詳見546頁;
-
struct關鍵字和class關鍵字定義的類的區別僅僅是默認成員訪問說明符及默認派生訪問說明符,struct默認public訪問,class默認private訪問;
-
派生類向基類轉換的可訪問性3個原則詳見544頁,結合練習15.18與15.19使用效果更佳;
15.6 繼承中的類作用域
- 派生類的成員將隱藏同名的基類成員,如果希望使用隱藏的成員,可以使用作用域運算符::;
- 聲明在內層作用域的函數並不會重載聲明在外層作用域的函數,如果派生類的成員與基類的某個成員同名,派生類將隱藏基類成員,即使形參列表不一致,基類成員也會被隱藏而不是重載;這裏重載和隱藏的區別在於如果使用對應基類形參的函數,隱藏會報錯,而重載會匹配;
- 如果基類中有同名的重載函數,那麼派生類的同名函數將覆蓋基類中重載函數的0個或多個實例,有時候只需要覆蓋基類重載集合中的部分函數,一個簡單的方法是使用using 聲明語句;
15.7 構造函數與拷貝控制
- 對於有繼承關係的基類,我們應該把它的析構函數定義爲虛析構函數,否則delete一個指向派生類對象的基類指針將產生未定義的行爲;
- 如果一個類定義了析構函數,即使它通過=default的形式使用了合成的版本,編譯器也不會爲這個類合成移動操作;
- 當派生類定義了拷貝或移動操作時,該操作負責拷貝或移動包括基類部分成員在內的整個對象,而析構函數只負責銷燬派生類自己分配的資源,派生類的基類部分的資源將由基類的析構函數部分隱式銷燬;
- 對象銷燬的順序與構造的順序相反,派生類析構函數先執行,然後是基類的析構函數;
- 如果構造函數或析構函數調用了某個虛函數,則我們應該執行與構造函數或析構函數所屬類型相對應的虛函數版本;
- 派生類不能繼承基類的默認構造函數,,方式是提供一條註明了基類名的using聲明語句,舉例見557頁,一個構造函數的using聲明不會改變該構造函數的訪問級別(普通成員會改變),而且一個using聲明語句不能指定explicit或constexpr;
15.8 容器與繼承
- 當派生類對象被賦值給基類對象時,其中的 派生類部分將被“切掉”,因此容器和存在繼承關係的類型無法兼容;
- 當我們希望在容器中存放具有繼承關係的對象時,我們實際上存放的通常是基類的指針(更好的選擇是智能指針);
15.9 文本查詢程序再探
自己實現的程序貼在下面