[C++ Primer Note14] 面向對象程序設計

  1. 對于某些函數,基類希望它的派生類各自定義適合自身的版本,此時基類就將這些函數聲明成虛函數(virtual function)
class Quote{
public:
    string isbn() const;
    virtual double net_price(size_t n) const;
}
  1. 派生類必須通過使用類派生列表明確指出它是從哪個基類繼承而來的。類派生列表的形式是:首先是一個冒號,后面緊跟著以逗號分隔的基類列表,其中每個基類可以有訪問說明符
  2. 派生類必須在其內部對所有重新定義的虛函數進行聲明。派生類可以在這樣的函數之前加上virtual關鍵字,但并不是非得這么做。
  3. 當我們使用基類的引用或指針調用一個虛函數時將發生動態綁定,即根據實際對象類型來選擇函數版本。
  4. 基類通常都應該定義一個虛析構函數,即使該函數什么都不做
  5. 在C++中,基類必須把它的兩種成員函數區分開來:一種是基類希望其派生類進行覆蓋的函數;另一種是基類希望派生類直接繼承而不要改變的函數。
  6. 關鍵字virtual智能出現在類內部的聲明語句之前而不能用于類外部的函數定義。如果基類把一個函數聲明成虛函數,則該函數在派生類中隱式地也是虛函數
  7. 派生類可以繼承定義在基類中的成員,但是派生類的成員函數不一定有權訪問從基類繼承而來的成員。派生類能訪問公有成員,而不能訪問私有成員。不過某些時候有一種成員基類希望派生類有權訪問,而其他用戶禁止訪問,我們用protected訪問運算符說明這些成員。
  8. 類派生列表中用到的訪問說明符可以是:publicprotected或者private中的一個。它的作用是控制派生類從基類繼承而來的成員是否對派生類的用戶可見
  9. 如果派生類沒有覆蓋基類中的某個虛函數,則該虛函數的行為類似于其他普通成員,派生類會直接繼承其在基類中的版本。C++11標準允許派生類顯式地注明它覆蓋了虛函數,具體做法是在形參列表后(const,引用限定符后)添加一個關鍵字override
  10. 一個派生類對象包含多個組成部分:一個含有派生類自己定義的成員的子對象,以及一個與該派生類繼承的基類對應的子對象,因為在派生類對象含有與其基類對應的組成部分,所以我們能把派生類對象當成基類對象來使用,而且我們也能將基類的指針或引用綁定到派生類對象中的基類部分:
Father father;
Son son;
Father *p=&father;
p=&son;
Father &r=son;

這種轉換通常稱為派生類到基類的(derived-to-base)類型轉換,和其他類型轉換一樣,編譯器會隱式地執行這種轉換。
我們可以把派生類對象或者派生類對象的引用用在需要基類引用的地方;也可以把派生類對象的指針用在需要基類指針的地方。

  1. 盡管在派生類對象中含有從基類繼承的成員,但是派生類并不能直接初始化成員,派生類必須使用基類的構造函數來初始化它的基類部分
  2. 派生類對象的基類部分和派生類對象自己的數據成員都是在構造函數的初始化階段執行初始化操作的。派生類構造函數同樣通過初始化列表來將實參傳遞給基類構造函數的,比如:
Bulk_quote(const string &book,double p,size_t pty,double disc):
                  Quote(book,p), min_qty(qty),discount(disc){ }

首先初始化基類的部分,然后按照聲明的順序依次初始化派生類的成員。

  1. 如果基類定義了一個靜態成員,則在整個繼承體系中只存在該成員的唯一定義
  2. 派生類的聲明與其他類差別不大,聲明中包含類名但是不包含派生列表。如果我們想將某個類用作基類,則該類必須已經定義而非僅僅聲明。
  3. C++11標準提供了一種防止繼承發生的方法,即在類名后跟一個關鍵字final
  4. 因為一個基類的對象可能是派生類對象的一部分,也可能不是,所以不存在從基類到派生類的自動類型轉換,除此以外即使一個基類指針或引用綁定在一個派生類對象上,我們也不能執行基類到派生類的轉換
Son son;
Father *p=&son;   //正確,動態類型是Son
Son *ps=p;   //錯誤,不能將基類轉換成派生類
  1. 當我們用一個派生類對象為一個基類對象初始化或賦值時,只有該派生類對象的基類部分會被拷貝,移動或賦值,它的派生類部分將被忽略。
  2. 我們必須為每一個虛函數都提供定義,不管它是否被使用,因為編譯器無法確定到底會使用哪個虛函數。
  3. 當且僅當對通過指針或引用調用虛函數時,才會在運行時解析該調用,也只有在這種情況下對象的動態類型才有可能與靜態類型不同。
  4. 一個派生類的函數如果覆蓋了某個繼承而來的虛函數,則它的形參類型必須與被它覆蓋的基類函數完全一致,同時返回類型也必須相匹配,但如果類的虛函數返回類型是類本身的指針或引用時,規則無效。
    如果D由B派生得到,則B的虛函數可以返回B*而派生類可以返回D*,只不過要求從D到B的類型轉換時可訪問的。
  5. 派生類如果定義了一個函數與基類中虛函數的名字相同但是形參列表不同,這仍然是合法的行為,但這有時候可能是一種錯誤。我們可以通過override關鍵字來讓編譯器為我們發現一些錯誤。
  6. 我們還能把某個函數指定為final的,這樣之后任何嘗試覆蓋此函數的操作都將引發錯誤:
struct D2:B{
    void fi(int) const final;
};
  1. 和其他函數一樣,虛函數也可以擁有默認實參,如果某次函數調用使用了默認實參,則該實參值由本次調用的靜態類型決定,所以基類和派生類中定義的默認實參最好一致。
  2. 我們可以通過作用域運算符來讓對虛函數的調用不要進行動態綁定,而是強迫其執行虛函數的某個特定版本:
double undiscounted=baseP->Quote::net_price(42);

這種機制一般用在派生類的虛函數體內調用基類虛函數版本時,如果沒有使用作用域運算符,則會導致無限遞歸

  1. 我們可以定義純虛函數告訴用戶當前這個函數沒有實際意義。一個純虛函數無需定義,我們通過在函數體的位置(聲明語句的分號前)書寫=0就可以將一個虛函數說明為純虛函數,其中=0只能出現在類內部的聲明語句處。我們也可以為純虛函數提供定義,不過函數體必須在類的外部。
  2. 含有(或者未經覆蓋直接繼承)純虛函數的類是抽象基類(abstract base class),我們不能直接創建一個抽象基類的對象
  3. protected成員對于派生類的成員友元是可訪問的,但只能通過派生類對象來訪問,派生類對于一個基類對象中的protected成員沒有任何訪問特權
  4. 派生列表中的訪問說明符對于派生類成員(友元)能否訪問其直接基類的成員沒什么影響。對基類成員的訪問權限只與基類中的訪問說明符有關。派生列表訪問說明符的目的是控制派生類用戶(包括派生類的派生類)對于基類成員的訪問權限
  5. 只有當公有繼承時,用戶代碼才能使用派生類向基類的轉換
  6. 友元關系不能繼承
  7. 有時我們需要改變派生類繼承的某個名字的訪問級別,通過使用using聲明可以達到這一目的:
class Base{
public:
    size_t size() const { return n;}
protected:
    size_t n;
};
class Derived: private Base{
public:
    using Base::size;
protected:
    using Base::n;
};

using聲明語句中名字的訪問權限由之前的訪問說明符決定

  1. 我們曾經介紹過structclass具有不同的默認訪問說明符。類似的,默認派生運算符也由定義派生類所用的關鍵字來決定。默認情況下,使用class定義的派生類是私有繼承的,struct則是公有繼承的。實際上,這兩點也是class和struct的唯二區別了。
  2. 當存在繼承關系時,派生類的作用域嵌套在其基類的作用域之內。
  3. 一個對象,引用或指針的靜態類型決定了該對象的哪些成員是可見的,我們能使用哪些成員是由靜態類型決定的。比如我們不能用基類引用調用派生類獨有的函數。
  4. 派生類的成員將隱藏同名的基類成員。我們可以通過作用域運算符來使用被隱藏的基類成員。
  5. 聲明在內層作用域的函數并不會重載聲明在外層作用域的函數。因此,定義在派生類的函數也不會重載基類的同名成員,而只會隱藏
  6. 繼承關系對基類拷貝控制最直接的影響是基類通常應該定義一個虛析構函數,這樣我們就能動態分配繼承體系中的對象了。因為這樣我們確保delete基類指針時能運行正確的析構函數版本,如果沒有定義虛析構函數,將產生未定義的行為。
  7. 如果一個類定義了析構函數,即使通過=default的形式使用了合成的版本,編譯器也不會為這個類合成移動操作
  8. 基類或派生類的合成拷貝控制成員與其他合成的構造函數,賦值運算符或析構函數類似:它們對類本身的成員一次進行初始化,賦值或銷毀。此外,還負責使用直接基類中對應的操作對一個對象的直接基類部分進行相應的操作
  9. 默認情況下,基類默認構造函數初始化派生類對象的基類部分。如果我們想拷貝(或移動)基類部分,則必須在派生類的構造函數初始值列表中顯式地使用基類的拷貝(或移動)構造函數。同樣的,派生類的賦值運算符也必須顯式地為基類部分賦值
  10. 如果構造函數或析構函數調用了某個虛函數,則我們應該執行與構造函數或析構函數所屬類型相對應的虛函數版本。
  11. 當派生類對象被賦值給基類對象時,其中的派生類部分將被切掉,因此容器和存在繼承關系的類型無法兼容。當我們希望在容器中存放具有繼承關系的對象時,我們實際上存放的通常是基類指針(更好的選擇是智能指針)。
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,406評論 6 538
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,034評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,413評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,449評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,165評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,559評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,606評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,781評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,327評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,084評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,278評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,849評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,495評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,927評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,172評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,010評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,241評論 2 375

推薦閱讀更多精彩內容