【C++ 筆記】多型(Polymorphism) - part 23
【C++ 筆記】多型(Polymorphism) - part 23
很感謝你點進來這篇文章。
你好,我並不是什麼 C++、程式語言的專家,所以本文若有些錯誤麻煩請各位鞭大力一點,我極需各位的指正及指導!!本系列文章的性質主要以詼諧的口吻,一派輕鬆的態度自學程式語言,如果你喜歡,麻煩留言說聲文章讚讚吧!
OOP 四大特性
再次強調 OOP(物件導向程式設計)的四大特性:
封裝(Encapsulation)(已學)繼承(Inheritance)(已學)- 多型(Polymorphism)
- 抽象(Abstraction)
多型(Polymorphism)
多型也稱為多態。
多型簡單來說,就是「同一個動作,因物件不同而產生不同結果」。想像我們現在有一個遙控器(介面),上面有一個「開啟」按鈕。當按下這個按鈕去控制不同設備時,會發生不同的事:
- 對電視按「開啟」,電視會顯示畫面。
- 對電風扇按「開啟」,風扇會開始轉動。
- 對電燈按「開啟」,燈會亮起來。
同一個「開啟」指令(介面),因為控制的物件(電視、風扇、電燈)不同,產生不同的行為,這就是多型。
在程式設計中,多型可讓我們用統一的方式(像是遙控器的按鈕)去控制不同類型的物件,卻能得到各自其專屬的結果,就像「網站上的連結被點擊」後,YouTube 會播放影片、Netflix 會顯示影集頁面,而教育網站可能顯示課程資料。
:::info
多型的具體定義:
相同的操作介面,對於不同的資料型別或物件,能夠產生不同的行為表現。
:::
當類別之間存在層次結構,並且類別之間是透過繼承關聯時,就會使用到多態。
From 菜鳥教程
順便說說他的英文涵義:
The word polymorphism means having many forms.
polymorphism 這個單字的意思是有著多種形式。
From GeeksForGeeks
至於 C++ 的多型可以分為兩種:
- Compile-time Polymorphism(編譯期多型)
- Run-time Polymorphism(執行期多型)

Image Source:https://www.geeksforgeeks.org/cpp-polymorphism/
Compile-time Polymorphism
也被稱為早期繫結(early binding)、靜態多型(static polymorphism)。
In compile-time polymorphism, the compiler determines how the function or operator will work depending on the context. This type of polymorphism is achieved by function overloading or operator overloading.
From GeeksForGeeks
在編譯期多型中,編譯器會根據上下文來決定函數或運算子的行為方式。這種類型的多型是透過函數多載(function overloading)或運算子多載(operator overloading)來實現的。
Run-time Polymorphism
也被稱為晚期繫結(late binding)、動態多型(dynamic polymorphism)。
The function call in runtime polymorphism is resolved at runtime in contrast with compile time polymorphism, where the compiler determines which function call to bind at compilation. Runtime polymorphism is implemented using function overriding with virtual functions.
From GeeksForGeeks
與編譯期多型不同,執行期多型中的函數呼叫是在執行期間才被解析,而非在編譯期間由編譯器決定綁定哪一個函數呼叫。
執行期多型是透過使用虛擬函數(virtual functions)來進行函數覆寫(function overriding)實現的。
Compare with both
| 類型 | 名稱 | 決定時機 | 技術 | 效率 |
|---|---|---|---|---|
| Compile-time Polymorphism | 編譯時多型 | 編譯時 | 函式多載(Overloading)、運算子多載(Operator Overloading)、模板(Templates) | 高(編譯時確定) |
| Run-time Polymorphism | 執行時多型 | 執行時 | 虛擬函式(Virtual Functions)與繼承 | 低(需虛擬表 vtable 支援) |
Compile-time Polymorphism
因為是「編譯期」多型,所以也稱靜態多型。
函數多載(Function Overloading)
這是 OOP 的特性,他可以有兩個或是更多的同名函數,但對於參數資料型態與數量而表現得有所不同。
啥意思?假設 add(1, 2) 跟 add(1.0, 4.4),裡面的兩參數要做相加,但是他們資料型態不同,所輸出的結果也當然不同,前者是 3,後者為 5.4。
筆者寫了一個小例題:
1 |
|
輸出結果:
1 | 整數:123 |
上面這個例子展示了同名函數,但是參數的資料型態不同,而有不同結果,如:輸入 123 就輸出一個整數型態的 123,輸入 “this is 123” 就輸出一個字串型態的 “this is 123”。
以下再舉一個當參數數量不同的例子:
1 |
|
輸出結果:
1 | 矩形面積: 50 |
可以看到 area 這個同名函數,有不同的參數數量,平面的矩形只要 長*寬,因此需要兩個參數,長方體需要 長*寬*高,所以需要三個參數,就這樣。
運算子多載(Operator Overloading)
可以自定義物件去使用 C++ 中的運算子。
C++ has the ability to provide the operators with a special meaning for particular data type, this ability is known as operator overloading.
From GeeksForGeeks
C++ 能夠為運算子提供針對特定資料型態的特殊意義,這叫做運算子多載。
而要多載一個運算子,具體格式如下:
1 | 類別名 operator"放要多載的運算子"(const 類別名&); |
舉個例子:
1 | myclass operator+(const myclass&); |
以下是個完整的運算子多載範例:
1 |
|
輸出結果:
1 | (4, 6) |
這支程式是在對兩點的 x y 進行相加。
而至於為什麼要加上:
1 | ostream& operator<<(ostream& os, const Point& p) { |
這行程式碼,是因為 Point 類別沒有提供對 ostream 的支援,也就是說 C++ 標準輸出串流沒有定義 operator<< 對應 Point 類別,會讓 C++ 不知道怎麼輸出這個物件。因此需要我們自己撰寫一個全域函數來重載 operator<<。
由於此 Point 類別是 public 的存取修飾詞,不然要在裡面加上 friend ostream& operator<<(ostream& os, const Point& pt); 讓函數去存取這些成員。
而要做 return os; 的動作是為了支援鏈式呼叫,讓每次 operator<< 都回傳 ostream&,讓下一個 << 能接續呼叫。如:cout << a << b << c;。
更完善範例:加入其他可多載的運算子
1 |
|
另外提供一個範例,較為直覺一點(兩個體積相加):
1 |
|
來源:菜鳥教程
可多載與不可多載的運算子
可多載的運算子:
| 運算子之係屬 | 運算子 |
|---|---|
| 二元算術運算子 | + - * / |
| 關係運算子 | == != < > <= >= |
| 邏輯運算符 | && ! |
| 一元運算子 | +(正) -(負) *(指標) &(取位址) |
| 遞增、遞減運算子 | ++ -- |
| 位元運算子 | & ~ ^ << >> |
| 指定運算子 | = += -= *= /= %= &= ^= <<= >>= |
| 記憶體空間新增與釋放 | new delete new[] delete[] |
| 其他運算子 | ()(函數呼叫) ->(箭頭運算子) ,(逗號) [](索引運算子) |
註:可多載的運算子還有邏輯運算子的 ||(OR)、位元運算子的 | (OR 位元運算子),跟指定運算子 |=。
以下是不可多載的運算子:
.(成員存取運算子).*->*(成員指標存取運算子)::(範圍解析運算子)sizeof? :(三元運算子)#(預處理符號)
來源:菜鳥教程
Run-time Polymorphism
因為是「執行期」多型,所以也稱為動態多型。
Virtual Functions
達成執行期多型的必備條件:
- 使用繼承(Inheritance)
- 基類別(Base Class)中的函數必須使用 virtual 關鍵字
- 透過指標或參考呼叫函數
在這邊要先解釋一下虛函數和純虛函數:
:::info
虛函數(Virtual Functions):
- 在基類別中宣告一個函數為虛擬函數,使用關鍵字 virtual。
- 衍生類別可以覆寫(override)這個虛擬函數。
- 呼叫虛函數時,會根據物件的實際型態來決定呼叫哪個版本的函數。
:::
至於上面的第三項,他的意思是當我們用一個指標或參考來呼叫函數時,系統會根據「這個指標實際指向的物件是哪一種型態」來決定執行哪個版本的函數,而不是根據變數表面上的型態。
做一個比喻:
假設你有一張「動物」身分證(Animal 為基類),上面寫著這是一隻動物。但其實這張身分證是借給一隻狗(Dog 是衍生類別)用的。
讓這隻「動物」叫一聲時(呼叫 speak() 函數),如果這個函數是虛函數(virtual),那牠會發出狗的叫聲(Dog 的 speak()),因為牠實際上是狗,不是真的只有「動物」那麼簡單。
:::info
純虛函數(Pure Virtual Functions):
- 一個包含純虛函數的類別被稱為抽象類別(Abstract Class),它不能被直接實例化。
- 純虛函數沒有函數體,宣告時使用 = 0。
- 它強制衍生類別提供具體的實作。
:::
來源:菜鳥教程
純虛函數宣告例子:
1 | class Shape { |
Function Overriding
至於虛函數的部分講到了 override 的部分,這也是在執行期多型時很重要的概念:
:::info
C++ 的函數覆寫(function overriding)僅能在衍生類別(derived class)中使用,是為了重新定義基類別中的虛函數,以實現多型(polymorphism)。
:::
在 function overriding 的過程中,需要具備以下三項基本條件:
- 基類別中的函數必須是 virtual 函數。
- 衍生類別中定義的函數必須具有相同的函數簽章(signature)。
- (於 C++11 之後)加上 override 關鍵字來明確表示此為覆寫行為。
至於函數簽章是啥?
:::info
函數簽章(function signature),又名為函數原型(function prototype),在 C++ 中,是對函數的一種宣告,用來告訴編譯器該函數的名稱、回傳型態、參數型態(與順序),但不包含函數體內部的內容。
:::
語法形式:data_type function_name(parameters, ...)。
其實就是以下這樣(宣告一個函數而已,並未進行明確定義):
1 |
|
Run-time Polymorphism 的例題 1
1 |
|
輸出結果:
1 | Derived class function |
From:GeeksForGeeks
在 Base 類別中定義了一個虛擬函數 display(),然後他會在 Derived 類別(繼承自 Base)中被 override。(符合執行期多型的前兩個必備條件)
接下來在主函數這邊,最後用到指標去呼叫函數,也符合條件,所以這是一個執行期多型(呼叫的函數會根據實際物件的型態,而不是指標的型態來決定最終執行哪個版本的函數)。
而在這邊的 basePtr 就是上面講虛函數時比喻的動物,但隨著他指向的物件不同,在呼叫一個虛函數(假設 virtual speak())時,C++ 會在執行期去查這隻「動物」實際是誰,然後執行「狗的版本」(衍生類)的 speak()。
Run-time Polymorphism 的例題 2
簡單的小小範例:
1 |
|
輸出結果:
1 | 汪!汪! |
總結
多型(Polymorphism)概述
- 定義:同一操作介面,對不同物件或資料型態產生不同行為。「多型」意為「多種形式」。
- 比喻:如同有一個遙控器「開啟」按鈕,對不同設備(電視、風扇、電燈)執行不同動作。
- 應用:透過統一介面控制不同物件,實現其專屬行為(如網站連結點擊後的不同反應)。
- 前提:通常需有繼承關係的類別層次結構。
多型分類
在 C++ 中,多型分為兩種:
- 編譯期多型(Compile-time Polymorphism):靜態多型,編譯時決定行為。
- 執行期多型(Run-time Polymorphism):動態多型,執行時決定行為。
編譯期多型(Compile-time Polymorphism)
- 別名:早期繫結(early binding)、靜態多型。
- 實現方式:
- 函數多載(Function Overloading):同名函數根據參數型態或數量不同,執行不同行為。
- 運算子多載(Operator Overloading):自定義運算子行為。
- 特點:編譯時確定,效率高。
執行期多型(Run-time Polymorphism)
- 別名:晚期繫結(late binding)、動態多型。
- 實現方式:
- 虛擬函數(Virtual Functions):基類中使用
virtual關鍵字,衍生類別覆寫(override)。- 條件:
- 使用繼承。
- 基類函數為虛擬函數。
- 透過指標或參考呼叫。
- 純虛函數:宣告為
virtual void func() = 0,函數體內無內容,衍生類別必須實作內容,包含純虛函數的類別為抽象類別。
- 條件:
- 函數覆寫(Function Overriding):衍生類重新定義基類虛函數,需相同函數簽章(名稱、回傳型態、參數)。
- C++11 後可用
override關鍵字明確標示。
- C++11 後可用
- 虛擬函數(Virtual Functions):基類中使用
- 特點:執行時需透過虛擬表(vtable),效率較低但靈活。
編譯期與執行期多型比較
| 類型 | 名稱 | 決定時機 | 技術 | 效率 |
|---|---|---|---|---|
| Compile-time | 編譯期多型 | 編譯時 | 函數多載、運算子多載、模板 | 高(靜態確定) |
| Run-time | 執行期多型 | 執行時 | 虛擬函數、函數覆寫 | 低(需 vtable) |
參考資料
Operator Overloading in C++ | GeeksforGeeks
Function Overloading in C++ | GeeksforGeeks
Functions in C++ | GeeksforGeeks
C++ Polymorphism | GeeksforGeeks
Function Overriding in C++ | GeeksforGeeks
TypeScript 初學者也能看的學習指南 09 - Function Overloads 函式重載 - iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天


