繼承基礎


  1. 範例程式:
    1. 請參考 school.cc 並與 school.c 比較.
    2. veh_inh.cc: 簡單繼承範例.
    3. veh_vf.cc: virtual function 範例
  2. 基本術語: Base class 或稱 parent class 為基本的類別; derived class 或稱 child class 為衍生的類別; 後者 inherit (繼承) 前者裡面所有的 attributes (幾乎啦). 或者說後者 is derived from 前者 (從前者衍生而來). Child class 所描述的, 通常都是 parent class 這個大族群當中一小群.
  3. 公開繼承 (public) 的 base class 與 derived class 之間的關係
    1. Base class 裡面所有的 attributes, 在 derived class 裡面都可以找得到. (想像: 每個 derived class 物件比較大, 屬性比較多)
    2. 需要 base class 物件當做 r-value 的地方, 都可以餵 derived class 物件給它吃. (想像: 天底下所有可以當做 base class 物件來用的集合, 包含所有 derived class 物件)
    3. base class 物件可以接收 derived class 物件的資料. (多餘的部分就丟掉不要了); 本應指到 base class 物件的指標可以令它指到 derived 物件去 (多餘的部分不要去看它就好了);
  4. 不繼承的成員函數:
    1. Constructor: 先遞迴執行所有 base class(es) 的建構子, 最後再執行 derived class 的建構子.
    2. Destructor: 先執行 derived class 的除構子, 再遞迴執行所有 base class(es) 的除構子.
    3. Copy constructor: 內定動作為: 呼叫所有 base classes 及 data members 的複製建構子.
    4. Assignment operator: 內定動作為: 呼叫所有 base classes 及 data members 的 operator =().
    不用記, 要想想看身為一個使用者, 你自己會期望 C++ 怎麼做? 這些都是大家所公認, 符合一般使用者期望的規定. Q: 如果你用 new 與 delete 的方式定義一個矩陣類別 matrix, 又定義一個太空船類別 spaceship 當中有一個 data member 為 "matrix orientation;" 那麼 spaceship 的這四個特殊成員函數應如何實作才不會有 memory leak 等問題? 結論: C++ 當中這些特殊規則的設計, 就是為了要降低程式設計師的負擔, 讓我們不必知道細節就可以放心寫程式.
  5. dominance (overriding): 如果 child class 當中出現與 parent class 當中相同名字的成員函數, 那麼不論 signature 是否相同, parent class 當中的所有這些成員函數全部被覆蓋掉. 亦即 child class 的使用者只能看到 child class 內定義的版本; 完全看不見 parent class 當中的任何一個版本.
  6. forwarding: 要實作自己這個類別的某個成員函數, 但其中要做的事情其實可以藉由呼叫資料成員或 parent class 內的相關成員函數來完成. 例如 veh_inh.cc 當中的 vehicle::move. [繼承的思考方式] 圖案
  7. 繼承的思考方式: 把一件工作 (例如 draw) 按照 層次 (而不是按照 部分) 來分割. 如果要勉強做個比喻, 以 "頭-軀幹-手-腳" 的方式來分工建構一個機器人就是按照 部分 來分割; 而以 "骨架-電路-包裝" 的方式則是按照 層次 來分割. 第一層做完時, 已經是一個機器人; 第二層做完時是一個有功能的機器人; 第三層做完時, 是一個漂亮的機器人.
  8. virtual function: 請見 veh_vf.cc.
    1. 目的: 讓其他程式設計師 (非本類別的作者) 可以寫具有一般性的程式 (例如 race), 對任何 base class 物件 (例如 vehicle) 都適用; 但是當這個程式作用在特定的 child class 物件 (例如 ambulance) 上時, 我們又希望還可以看到 child class 物件的特色 (例如救護車上頭的燈應該要畫出來). 這樣的效果叫做 polymorphism.
    2. 類別設計者的工作: 把繼承關係畫出來 (通常是一個 tree), 先找出有那些 member functions 出現在有直系血親關係的類別當中. (例如 draw, clear, move 都出現在好幾個類別當中, 而這些類別之間都有繼承關係.) 這表示在 descendant classes 當中, 這些 member functions 重新定義, override 掉 ancestor classes 當中的定義, 也就是說使用者會認為每個這種出現在不同層次的 member function (例如 object::draw(), vehicle::draw(), ambulance::draw(), jet_ambulance::draw()) 雖然代表同一個觀念, 但應隨著一個 object 的真正類別不同而有不同的行為. 於是把出現在最老的祖先當中的那個版本 (例如 object::draw()) 宣告成 virtual.
    3. 類別使用者的工作: 把你寫的函數當中, 需要有 "child classes 各自展現特色" 效果的所有參數 以指標或以 reference 方式傳遞. 為什麼? 因為以值傳遞會把 descendant class 物件當中它的特色部分砍掉.
    4. (僅供參考: C 也可以做到相同的效果: 把每個 virtual functions 都宣告成結構當中一個 "指向函數指標" 的欄位. 從這裡可以看出為什麼只要在最老的祖先當中宣告 virtual 就可以了.)
    5. pure virtual: 有時候最老的祖先類別 (例如 object) 實在是太 general 了, 可以用的資訊太少了, 你的 virtual function 真不知從何寫起. 與其寫成空的, 不如用 "... = 0" 直接告訴 compiler. 如果等一下不小心 (或其他人) 想要宣告這種類別的 instance, 顯然是一種錯誤, 而 compiler 就可以幫你檢查出來.
    6. 凡是有 virtual function 的類別, 都應該考慮將它的 destructor 也宣告成 virtual! 因為這表示這個類別 (和它的子孫) 有可能被拿來在 polymorphism 的場合當中使用...
  9. 繼承不是萬靈丹!
    1. 考慮用包含/合成 (containmaint/composition, 即結構內包含結構) 取代私下繼承 (private inheritance);
    2. 考慮用一個欄位分辨 "類別" (但有些時候用繼承加上 virtual function 可能是較好的解決方案)
  10. protected: derived class 可以使用, 外人無法使用的屬性或 parent class.
  11. 作業: 修改 veh_inh.cc 與 veh_vf.cc, 增加 ambulance::clear() 與 jet_ambulance::clear() 兩個函數, 把螢幕真的清除乾淨.