#分享 [筆記]多型與繼承的關係|C++

2021年7月13日 14:34
多型的出現是為了要解決什麼問題?多型觸發的條件是什麼?為什麼多型要和繼承一起使用?多型能保有繼承的優點嗎?多型比繼承又多了什麼功能?特殊函式也能多型嗎? 這是一篇中山資工所 江明朝老師 物件導向程式設計課程筆記,如內容有錯,歡迎留言交流~ 還是很困惑嗎?文章裡有答案喔~😎 好讀版:
� 一、多型 (polymorphism) 多型想要解決C語言switch的問題,虛擬函式 (virtual functions) + 繼承 (inheritance) + override可以實做多型。override的前提是有多個虛擬函式簽章 (signature)一樣,才會是override,如果不是虛擬函式,則會變成redefine。另一個相近的概念overload則是要在函式名一樣,但簽章不一樣的狀況。 (一)純虛擬函式 (pure virtual functions) 純虛擬函式 (=0)在基礎類別中,不需要先給予實作內容,等到衍生類別才需要實作內容,並在後面宣告override,因為有時候我無法先給它定義;而如果是一般的虛擬函式 ,基礎類別就必須給出實作細節。 只要函式被宣告為virtual,以後的子類別的此函式都是虛擬的,即使子類別沒有顯示宣告virtual。也就是一日virtual,終生virtual。不過為了增加可讀性,建議還是都要寫virtual。 任何類別中如果有一個以上的純虛擬函式,就是扮演抽象類別 (abstract class)。抽象類別無法被直接實例化,除非能補足抽象類別所缺的實作,故抽象類別只能當作介面 (interface)的角色。直接實例化抽象類別會導致不完整的實例而失敗,但依舊可以使用抽象類別作為parameter type / return type / data member type,因為使用者會賦值 (assign)衍生類別物件給這些抽象類別,以補足抽象類別所缺乏的實作。 (二)多型與繼承關係 多型要和繼承一起使用,因為透過繼承,不同函式的scope才會一樣,scope一樣才能override。因此如果兩個沒有繼承關係的類別,有著同樣函式簽章的虛擬函式,兩者是不會有多型關係,顯然多行和繼承的關係十分密切。總之多型需要和繼承與虛擬函式一起使用,才會有多型的的效果。 1. 多型仍保有繼承優點 繼承關係:基礎類別 (b) <----- 衍生類別1 (d1) <------- 衍生類別2 (d2) <---------- 衍生類別3 (d3) 如果基礎類別中宣告一個虛擬函式(vf),基礎類別已經有給予定義,而d1已經override,d2也已經override,則這3個類別的vf是獨立的。所以如果我想在d2中重複使用d1的vf是可以的,只要使用::去明示scope即可,如:d1::vf。而如果d3沒有override,則d3的vf是繼承d2::vf,故如果沒有override就繼承上面有定義的定義。因此,在多型中,要麻繼承親代定義,要麻override重新定義。即使是override虛擬函式,仍保有繼承重複使用程式碼的特性,只不過使用上要使用base::virtual_function,已表明我是呼叫基礎的虛擬函式,而不是呼叫自己的虛擬函式。 2. 多型的功用
所以多型的override有什麼的功能呢?顯然不是只有單純重新定義虛擬函數,不然這就和redefine沒差別。它的目的是為了處理C switch的問題,如果以C switch去模擬上圖的功能,我們會用一個變數Animal存放Dog, Cat or Rat,並且用switch去偵測Animal的值是哪一個?如果是狗 Dog,則印出coin值;如果是貓 Cat,則印出luck和rainbow值;如果是鼠 Rat,則印出good和shock值。 顯然C switch的方法如果種類更多時,會很難維護。而多型也可以來實作同樣的功能,同時在維護上更方便。此時會有一個基礎類別寵物 (Pet),而旗下繼承者有狗 (Dog)、貓 (Cat)、鼠 (Rat),且每個類別都有虛擬函式getInfo(),getInfo()會印出各別獨有的成員資料。因此getInfoe()有四種版本,分別為Pet::getInfo()、Dog::getInfo()、Cat::getInfo()、Rat::getInfo()。 Pet *pet; pet = new Pet(); pet.getInfo() == pet.Pet::getInfo() pet = new Dog(); pet.getInfo()== pet.Dog::getInfo(),印出coin值 pet = new Cat(); pet.getInfo() == pet.Cat::getInfo(),印出luck和rainbow值 pet = new Rat(); pet.getInfo() == pet.Rat::getInfo(),印出good和shock值 我可以建構一個指向寵物的指標 (pet),並且分別配置狗物件、貓物件、鼠物件,在呼叫pet.getInf(),神奇的是居然不是呼叫到基礎版本Pet::getInf(),而是呼叫到衍生類別的版本。這方法有助於,如果今天我不確定pet會配置到哪個衍生類別時,但又想要呼叫衍生類別的getInfo(),這時多型就非常有用。 多型可以讓基礎類別,向下存取衍生類別的虛擬函式;而繼承可以讓衍生類別,向上存取基礎類別的函式。不過多型向下存取的前提是,基礎類別要配置衍生類別物件,因此基礎類別物件是不可能靠多型向下存取。上面pet的例子,即使pet配置狗物件,pet本身仍是Pet,Pet也無法存取Dog非虛擬函式。 (三)虛擬解構子 (virtual destructor) 有個情境一定要使用虛擬解構子: 1. 有一個沒有虛擬解構子的基礎類別 2. 有一個繼承它的衍生類別 3. 有一個基礎類別的指標指向衍生類別 例如:Pet *pet; pet = new Dog(); delete pet; 以上述Pet–Dog的例子,如果Pet::~Pet()不是virtual,則當執行delete pet時,編譯器只會呼叫Pet::~Pet(),這樣會造成記憶體遺失,因為Pet沒權利存取Dog的解構子;而如果Pet::~Pet()是virtual,執行delete pDog時,編譯器會先呼叫Pet::~Dog()再呼叫Pet::~Pet()。 編譯器會自動呼叫兩次的原因是,雖然解構子不會被繼承,但也不用在Dog::~Dog()實作中呼叫Pet:: ~Pet(),因為編譯器會自動呼叫。 虛擬解構子不能是純虛擬解構子,也就是不能宣告為=0,因為這樣base::destructor會沒有定義!故虛擬解構子只能為一般虛擬函式,虛擬解構子本體可以為空,因為編譯器會自動建立,故還是有定義。 所以結論是,如果類別中有使用到虛擬函式,建議再加上虛擬解構子,以避免記憶體遺漏。然後沒有虛擬建構子這玩意! 二、相關文章 4. [筆記]介面與實作、運算子多載、左值右值、參數傳遞、回傳多值|C++
運算子多載如何執行?int a = 10; ++a—;這串程式碼為什麼會出錯?居然跟左值又值有關!左值右值真的是一左一右嗎?參數傳遞中的傳指標和傳參考差在哪邊?C++居然無法回傳多值,那我該怎麼回傳多值? 5. [筆記]陣列與指標|C++
我以為我傳的是陣列,但居然傳的是指標?為什麼我無法回傳陣列?只能回傳指標?所以陣列和指標是什麼關係?指標運算怎麼算?釋放動態記憶體時,指標變數會不會也被釋放? 6. [筆記]類別、特殊函式、內嵌函式、函式物件|C++
程序導向的C語言也能實作物件導向?C語言要如何模擬類別?C struct與C++ struct與C++ class這三者有什麼差別?類別中有哪六個特殊函式?初始化和賦值有什麼差別?內嵌函式和巨集很像?物件居然可以拿來當成函式?為什麼C++宣告無參數物件時,不用加()? 7. [筆記]繼承模式與存取權限|C++
繼承模式和存取權限有什麼關係?private繼承不是讓所有存取權限變成private嗎?為什麼衍生類別依舊可以存取基礎類別?類別開發者和使用者有什麼區分?特殊函式的繼承會和一般函式一樣嗎? 8. [筆記]多型與繼承的關係|C++
多型的出現是為了要解決什麼問題?多型觸發的條件是什麼?為什麼多型要和繼承一起使用?多型能保有繼承的優點嗎?多型比繼承又多了什麼功能?特殊函式也能多型嗎? 9. [筆記]static / const成員資料與函式|C++
non-const static成員資料無法在類別內初始化,也無法使用初始化列,那到底該如何初始化?static成員是什麼概念?可以將自己宣告為自己的成員資料嗎?如果可以,該如何實現?如果不行,會發生什麼問題? 10. [筆記]夥伴函式與類別、不夠朋友問題|C++
夥伴函式和成員函式差在哪裡?為什麼輸入/輸出的多載運算子一定要為夥伴函式?如果A類別把B函式當作夥伴,但B函式的參數中沒有A類別,顯然這個B不把A當夥伴,就會造成main()無法找到B函式。
10
留言 2
文章資訊
85 篇文章294 人追蹤
Logo
每天有 7 則貼文
共 2 則留言
國立臺灣大學
看不懂好複雜XD
b1 看不懂這篇筆記,是正常的XDDD,因為: 1. 多型的機制本來就小複雜 2. 筆記中省略多型的基本介紹 3. 你需要先對類別、繼承、多型有基本認識 4. 我把繼承和多型混在一起比較 多型我也是反覆看三遍以上+實際實作,才比較理解😊😊