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