Overload (多載) vs. Override (覆寫) — (I)

發表於 分類為「設計模式/原則

 
Overload (多載)Override (覆寫) 為程式設計的 2 個常見性質,

物件導向程式設計 (OOP) 尤其重要。
 
或許是原文相似的關係,兩者時常令初學者搞混 😨,

不然就是對其了解只停留在:

「多個相同方法名稱」、「改寫父類別方法」…,而不懂實際運用。
 
別擔心!本篇將以簡單的例子,闡明各自用途、使用方式,

使您不再模凌兩可,並運用自如 😆。


 
[註]:
若無指明,本篇使用的語言預設為 Java。

 


 

Overload (多載)

 
在解釋 Overload (多載) 的含義之前,先來記憶名詞吧!
 

Load 」 本身有 乘載、負擔、負荷、負載 的意思。

於是,加上一個 Down,成了 Download (下載),

若加上一個 Over,則成了 — Overload (多載) 啦!

又譯為: 超載、覆載、重載、過載…「各種」載。
 
恭喜,您已學會分辨 Overload (多載)Override (覆寫) 😂。
 
overload-vs-override

 


 

其實你學過了!

Overload (多載) 的觀念其實您已見過無數次 😂!

其為 Christopher Strachey 於 1967 年提出的一 特定多型 (Ad hoc polymorphism) 機制,

簡單來說就是: 根據不同情境『 相同的模樣,擁有不同的行為 』。
 
 
還記得國小數學嗎?

當時的「+」、「」運算子,純粹就是做正數的 加法、減法
add-sub-stract-multiply-divide
 
然而上了國一,老師告訴你 負數 的觀念:「3 + 5 = 2」,

以及如何強調一個 正數:「+10」…。
 
這時你發現了:

一樣都長「+」、「」,在不同時候卻有 不同意義 (加減 vs. 正負數)。

會了 😇:

這正是 運算子多載 (Operator Overloading)

— — 將多載觀念:『相同的模樣,擁有不同的行為』,應用在運算子上。
 
沒錯,Java 串接字串使用的「+」,一樣是 運算子多載 喔!

另外,可別忘記 字串 是 immutable (永不改變的)

public class Main {

    public static void main(String[] args) {

        String name = "Jason";
        String output = "Hello, " + name + "!";

        System.out.println(output);
    }
}

/*
 * Output:
 * 
 * Hello, Jason!
 */

[註]:
不同於某些語言 (e.g., C++),

Java、C… 並不 提供『自訂的運算子多載』,詳見 此篇
 
 
需注意的是,當有人同時提及 Overload (多載)Override (覆寫) 時,

87% 指的都是 方法多載 (Method Overloading),而非 運算子多載 唷 😲!

 


 

方法多載 (Method Overloading)

方法多載 (Method Overloading) or 函式/建構子多載,顧名思義:

將觀念『相同的模樣,擁有不同的行為』,應用在 方法/函式/建構元 上。
 
運算子的 模樣 很簡單:「   +   –   *   /   &   ^   |   <<   >>…   」,

而 『方法的模樣』便是指 — 方法名稱 (method’s name)

也就是: 相同的 方法名稱,擁有不同的 實作
 
條件是:

參數串列 (parameter list)不同

( 包含不同的參數型別、數量 )

 
例如:

void test();
void test(int i);
void test(char c);
void test(String s, int i);
void test(String s, String s2);

 
[註]:
大多數 物件導向程式語言 皆支援 方法多載 (e.g., Java, C++),

而傳統的 結構化程式語言 (e.g., C 語言) 不支援。

 
 

無多載方法

直接看個栗子 🌰
 
Bob 是個愛煎牛排的固執廚師 (chef):


 
你可以跟他點餐,但你沒有選擇 (無參數),永遠只有牛排 😂:

public class Main {

    public static void main(String[] args) {

        Chef bob = new Chef();

        bob.cook(); // 牛排x1
    }
}

// 廚師 (chef)
class Chef {

    // 回傳型別: void
    // 參數型別: 無
    void cook() {
        System.out.println("準備餐點: 牛排");
    }
}

 
 

多載方法 (增加參數)

儘管 Bob 只想煎牛排,最後還是向現實屈服了:

允許顧客點餐 (增加 char 參數),

目前只提供 A號餐、B號餐,若您北爛亂點餐 Bob 會很生氣:

public class Main {

    public static void main(String[] args) {

        Chef bob = new Chef();

        bob.cook(); // 牛排x1

        bob.cook('B'); // 豬排x1

        bob.cook('C'); // 食屎
    }
}

class Chef {

    // 回傳型別: void
    // 參數型別: 無
    void cook() {
        System.out.println("準備餐點: 牛排");
    }

    // 回傳型態: void
    // 參數型態: char
    void cook(char meal) {
        switch (meal) {
            case 'A':
                System.out.println("準備 A 號餐: 牛排");
                break;
            case 'B':
                System.out.println("準備 B 號餐: 豬排");
                break;
            default:
                System.out.println("食屎吧你");
                break;
        }
    }
}

 
舉凡 遊戲 id、樂團名稱…,取名字都是一件麻煩事 😒,

方法多載 (Overload) 讓我們得以使用 相同名稱 宣告方法 😇,

而非取個 cookforOrder(char c) 或 cook2(char c)…。

感恩多載,讚嘆多載

 
 

多載方法 (多個參數)

身為一個專業的廚師,讓顧客指定數量也是很合理的 (增加 int 參數):

// 回傳型別: void
// 參數型別: char, int
void cook(char meal, int quantity) {
    if (quantity < 1)
        return;
    switch (meal) {
        case 'A':
            System.out.println("準備 A 號餐: 牛排");
            break;
        case 'B':
            System.out.println("準備 B 號餐: 豬排");
            break;
        default:
            System.out.println("食屎吧你");
            break;
    }
    // 做好一份了,剩下 quantity - 1 份
    cook(meal, quantity - 1); // 遞迴呼叫
}

[註]:
最後一行,使用 自己呼叫自己的技巧,稱為 遞迴 (Recursion)
 
執行結果:

public class Main {

    public static void main(String[] args) {

        Chef bob = new Chef();
    
        bob.cook('A'); // 牛排x1

        bob.cook('B', 3); // 豬排x3
    }
}

 
有沒有好方便!儘管呼叫相同的 方法名稱,

程式會自動找到對映的 參數型別 (或無參數) 以執行。

 
關鍵在於:

呼叫的 參數型別、順序 或 數量 不同

cook( )  vs.  cook(“A”)  vs.  cook(‘B’, 3);

 
 

方法簽章 (Method Signature)

整理一下,目前 廚師 (Chef) 類別 的多載方法:

class Chef {

    // 回傳型別: void
    // 參數型別: 無
    void cook() {
        ... 略 ...
    }

    // 回傳型別: void
    // 參數型別: char
    void cook(char meal) {
        ... 略 ...
    }

    // 回傳型別: void
    // 參數型別: char, int
    void cook(char meal, int quantity) {
        ... 略 ...
    }
}

 
Q1: 請問,是否能再擴充以下方法? (參數串列 與 cook(char meal) 衝突,但 修改回傳型態 )

boolean cook(char meal){
    ... 略 ...
}

 
 
Ans:
 
不行!

因為其 參數串列 與 void cook(char meal) 的 重複了!
 
在 Java 中:

方法簽章 (Method Signature) = 方法名稱 (method’s name) + 參數型別 (parameter types),

用以決定方法的 唯一性,其中 Signature 又譯為:外貌簽名、簽署、署名。
 

編譯器 是使用 方法簽章 (Method Signature) 區分不同方法,而非 回傳型別 (return type),

因此,不能宣告兩個具有相同簽章的方法。

[註]:
相同的觀念也出現在 C# 中:
方法的 傳回類型 不是 方法多載用途的方法簽章的一部分。
不過,在判斷委派與所指向的方法之間的相容性時,它是方法簽章的一部分。
 
 
然而,這也衍伸了一個問題:

Q2:請問,是否能再擴充以下方法? (參數串列 與 Chef 類別 衝突,但 修改回傳型態 )

boolean cook(int i){
    ... 略 ...
}

 
 
Ans:
 
可以,但 最好不要!
 
從範例中可得知,重載方法之間雖然參數串列不同,

功能是一致的 — 烹飪 (cook)。

重載方法之間,若回傳型別不同,

將使程式碼 難以維護、理解,若有新的意圖應取新的方法名稱。

— — 軟體開發大師 Kent Beck.

 
 

重構 — 重複的程式碼 (Duplicated Code)

程式碼的壞味道 (Bad Smells in Code, by Kent Beck & Martin Fowler) 中,

首當其衝的便是 重複的程式碼 (Duplicated Code) !
 
讓我們看看,截至目前為止 Chef 類別,有多少『 準備餐點 』😱:

class Chef {

    void cook() {
        System.out.println("準備 A 號餐: 牛排");
    }

    void cook(char meal) {
        switch (meal) {
            case 'A':
                System.out.println("準備 A 號餐: 牛排");
                break;
            case 'B':
                System.out.println("準備 B 號餐: 豬排");
                break;
            default:
                System.out.println("食屎吧你");
                break;
        }
    }

    void cook(char meal, int quantity) {

        if (quantity < 1)
            return;

        switch (meal) {
            case 'A':
                System.out.println("準備 A 號餐: 牛排");
                break;
            case 'B':
                System.out.println("準備 B 號餐: 豬排");
                break;
            default:
                System.out.println("食屎吧你");
                break;
        }

        // 做好一份了,剩下 (quantity - 1) 份
        cook(meal, quantity - 1); // 遞迴呼叫
    }
}

 
真的是臭到爆了 😂,而且第二個函式還 100% 出現在 第三個函式中…。


 
別擔心,由於 多載函式 大多 目的一致

大多內建『 重複的程式碼 』之解法 — 提煉函式
 
void cook() 方法,能夠 點一份牛排

void cook(char meal) 方法,能 點一份牛排 或豬排,

於是我們能夠如此重構:

void cook() {
    cook('A'); // 呼叫多載函式
}

void cook(char meal) {
    switch (meal) {
        case 'A':
            System.out.println("準備 A 號餐: 牛排");
            break;
        case 'B':
            System.out.println("準備 B 號餐: 豬排");
            break;
        default:
            System.out.println("食屎吧你");
            break;
    }
}

 
void cook(char meal) 只能 點一份 牛排或豬排,

void cook(char meal, int quantity) 能夠 點很多份 牛排或豬排!

於是我們能夠如此重構:

void cook(char meal) {
    cook(meal, 1); // 相當於數量 x1
}

 
 
最後:

class Chef {

    // 點一份 牛排
    void cook() {
        cook('A');
    }

    // 點一份 牛排 or 豬排
    void cook(char meal) {
        cook(meal, 1);
    }

    // 點很多份 牛排 or 豬排
    void cook(char meal, int quantity) {

        if (quantity < 1)
            return;

        switch (meal) {
            case 'A':
                System.out.println("準備 A 號餐: 牛排");
                break;
            case 'B':
                System.out.println("準備 B 號餐: 豬排");
                break;
            default:
                System.out.println("食屎吧你");
                break;
        }

        // 做好一份了,剩下 quantity - 1 份
        cook(meal, quantity - 1); // 遞迴呼叫
    }
}

 
是否乾淨許多呢 😆!
 
But,得注意的是:

對未包含共同程式或邏輯的 方法,可能並不適用,

例如,需依不同型別做個別的處理的 方法 (method)。

 
 

建構子多載 (Constructor Overloading)

建構子 (Constructor) 是一種特殊的方法,會在類別被 實例 (new) 時自動執行,

建構子 沒有回傳值,且 名稱必須與類別相同
 
上方的重構技巧,便常見於『 具有多個 建構子 (Constructor) 的類別 』中,

常見的做法是:『 由簡單去呼叫複雜 』。
 
例如,我們要製作一個具有 長、寬、顏色 的 長方形 (Rectangle)

且若無指定值,預設是 寬500 高309 藍色 的長方形,一開始可能寫成:

class Rectangle {

    int width;
    int height;
    int color;

    Rectangle() {
        this.width = 500;
        // 別問我為何 1.618...去問歐幾里得
        this.height = (int) (width / 1.618); 
        this.color = 0x2196F3; // 藍色
    }

    Rectangle(int width) {
        this.width = width;
        this.height = (int) (width / 1.618);
        this.color = 0x2196F3; // 藍色
    }

    Rectangle(int width, int height) {
        this.width = width;
        this.height = height;
        this.color = 0x2196F3; // 藍色
    }

    Rectangle(int width, int height, int color) {
        this.width = width;
        this.height = height;
        this.color = color;
    }
}

 

 
 
沒關係 😆,照著 廚師 (chef) 類別依樣畫葫蘆,進行重構 ❗️

class Rectangle {

    int width;
    int height;
    int color;

    Rectangle() {
        this(500); // 正確 (O)
//        Rectangle(width); 錯誤 (X) -- 得使用 this 關鍵字
    }

    Rectangle(int width) {
        this(width, (int) (width / 1.618));
    }

    Rectangle(int width, int height) {
//        int area = width * height; 錯誤 (X) -- 利用「this」呼叫建構子,需為首句敘述
        this(width, height, 0x2196F3);
    }

    // 主建構函式
    Rectangle(int width, int height, int color) {
        this.width = width;
        this.height = height;
        this.color = color;
    }
}

 

跟上節的重構非常相似! 但最大的不同在於:

建構子無法像方法一樣以名稱呼叫,得透過關鍵字 this 或 super (子類別),

且該 this 或 super 必須是該建構元的 首句敘述 (first statement)。

注意:

this() 和 this. 不一樣喔!

this() 用於 在 建構元中 呼叫 其他建構元,且需為 首句敘述 (first statement),

this. 用於存取類別中的欄位 (例 this.width),放第一萬行也沒關係!
 
 
最後,我們便能以簡單的方式建立長方形,也可以詳細的對其進行設定 😆:
 
無參數建構子:

 
三參數建構子:

 
附上 Main 程式碼:

import javax.swing.*;
import java.awt.*;

public class Main {

    public static void main(String[] args) {
        Rectangle defaultRec = new Rectangle();
        showJFrame(defaultRec);
    }

    static JFrame showJFrame(Rectangle r) {

        int width = r.width;
        int height = r.height;
        Color color = new Color(r.color);

        // 簡易的 Swing 圖形化介面
        JFrame frame = new JFrame();
        frame.setSize(width, height);
        frame.setLocationRelativeTo(null);
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.getContentPane().setBackground(color);
        frame.setVisible(true);
        return frame;
    }
}

 
以上的作法,便是 軟體開發大師 Kent Beck 提及:

將所有建構函式,轉接道一個 『 主建構函式 』,

以向未來的維護/修改者,傳達這些『 不會改變的要求 』。

 
 

子類別的多載

最後,得注意的是:

多載不只會發身在自身類別,繼承/實作 的子類別同樣能多載父類別的方法! 』

public class Main {

    public static void main(String[] args) {
        new A().test(); // A

        new B().test(); // B

        new B().test('C'); // C
    }
}

class A {
    void test() {
        System.out.println('A');
    }
}

class B extends A {

    // 覆寫 (Override)
    void test() {
        System.out.println('B');
    }

    // 多載 (Overload)
    void test(char c) {
        System.out.println(c);
    }
}

 
然而,可別忘了多載的條件是:

參數串列 (parameter list)不同

( 包含不同的參數型別、數量 )

方法簽章 (方法名稱+參數串列) 與 父類別 相同

並非多載而是 Override (覆寫) — 會將父類別的方法覆蓋掉 (改寫),

詳細觀念將於下篇提及 😄。

 


 

再談 運算子多載 (Operator Overloading)

本篇的重點雖放在 方法多載

運算子多載 同樣令許多初學者困惑。
 
因此,除了本篇開頭提及的「+」範例,

接下來將介紹幾個 C/C++ 的小坑 😂 。
 
若您只想學 Java、PHP…等語言,可以不用往下看 😄,

但…工程師…學海無涯…你懂的 😂。


 
 

範例 — 取址運算子 (&)

首先,這是一個 int 變數: i

int i = 10;

 
 
Q:

我們能否取得 i 所儲存的 記憶體位址 呢?
 
Ans:
Java 不能,但 C/C++ 可以

也就是,使用 取址運算子 (Address-of Operator)

&

 
用法: 將「 & 」放在 想取得位址的變數 ,如下所示 (C 語言):

#include <stdio.h>

int main() {

    int i = 10;

    printf("i 的 記憶體位址: %p", &i);

    return 0;
}

/*
 * Output:
 *
 * i 的 記憶體位址: 0x7fff5f2ec9f8 (每次輸出可能都不同)
 */

[註]:
格式化字串中的 %p 會以 16 進位顯示數值。
 
 
假設, &i 執行結果為 0xA0,則概念如圖:
 

 
 

AND 運算子

明顯地 取址運算子AND 運算子 為多載,如下所示 (C++):

#include <bitset>
#include <iostream>

using namespace std;

int main() {
    bitset<4> x("0110"); //  二進位數 0110
    bitset<4> y("0011"); //  二進位數 0011

    // 此處的 & 為 AND 運算子
    // 會將 x 與 y 進行 逐位元 (bitwise) AND 運算
    // 而非對 y 進行取址 
    bitset<4> result = x & y; // 二進位數 0010

    cout << "x & y: " << result << endl;
}


/*
 * Output:
 *
 * x & y: 0010
 */

 
[註]:
AND 運算: 兩個 bit 皆為 1,則輸出 1,否則為 0
 
AND
 
 

範例 — 間接運算子 (*)

接下來的範例,是初學者最畏懼的 — 星號地獄 🤣,

尤其搭配雙重指標,往往被一堆「***」搞得不要不要的,

希望本篇範例,能讓各位重拾對 指標 (Pointer) 的信心 😇。

 
 

先來了解指標吧!

若宣告一個變數 i:

int i;

 
我們得知 i 是一個『 型別: int 』的 變數
 
 
Q: 那看到這個呢? (C/C++)

int* ptr;

 
Ans:
ptr 是一個『 型別: int * 』的 變數
 
也就是說:

ptr 一樣是個 變數,但不同於 i 指向整數,

而是指向 『 整數型別 的 記憶體位址 』,此時 ptr 又稱為 指標 (Pointer) 或 指標變數。

 
於是,我們能夠使用上節的範例,

使用「&」取址,並指派給 指標

#include <stdio.h>

int main() {

    int i = 10;

    int* ptr = &i; // 用 & 取得 i 的位址後,指派給 ptr

    printf("i 的 記憶體位址: %p\n", &i);
    printf("ptr 的 記憶體位址: %p\n", &ptr);
    
    return 0;
}

/*
 * Output:
 *
 * i 的 記憶體位址: 0xA0 (每次輸出可能都不同)
 * ptr 的 記憶體位址: 0xFF (每次輸出可能都不同)
 */

 
概念如下:

 
總之:

指標 (Pointer) 是個存放 記憶體位址 的變數。

指標 (Pointer) 是個存放 記憶體位址 的變數。

指標 (Pointer) 是個存放 記憶體位址 的變數。

— 覺得很重要 o.o

 
 

縮排習慣

在程式碼中,以下的格式被稱為 指標宣告子 (Pointer declarator)

資料型別 *變數名稱;

 
Q:

哪泥!?「 * 」怎麼跑去變數名稱前面了?

不是緊跟著資料型別嗎 (e.g., int* i;)?
fear
 
A:
兩者是相同的,沒有任何區別,

然而許多人的習慣是放在變數名稱前面,

一開始的範例,是為了讓您好理解 😁。

 
 

再看 間接運算子

有了 指標 (Pointer) 後,我們不免想存取其「 指向位置的『內容』」:


 
於是,我們能將 間接運算子 (indirection operator) 或稱 解參考運算子 (dereference operator),

放在 指標變數 前面,進行 取值 或 設值 的操作。
 
間接運算子:

*

 
不是我筆誤啦!!

你會發現 指標宣告子間接宣告子 竟然長的一模模一樣樣 😱 ❗️

這是許多初學者噩夢的開始 🤣,但別擔心,我會教你如何分辨。
 
先來看看如何使用:

#include <stdio.h>

int main() {

    int i = 10;

    int* ptr = &i; // 此時的 * 為 指標宣告子
    
    // 此時的 * 為 解參考運算子,而非 指標宣告子
    printf("對 ptr 解參考: %p\n", *ptr); 
    
    return 0;
}

/*
 * Output:
 *
 * 對 ptr 解參考: 10 (i 的內容)
 */

 
圖示支援:

 
 

如何分辨?

指標宣告運算子,顧名思義,用於 宣告指標

因此只會長成這種模樣:

資料型別 *變數名稱;

 
會了 😇:

其他一律是 間接運算子 (indirection operator) !!

來看個實際範例:

#include <stdio.h>

int main() {

    int i = 10;

    int *ptr;

    ptr = &i;

    *ptr = &i; 錯誤 (X)

    return 0;
}

 
若要把 i 的位址,指派給指標變數 ptr,

應該使用 ptr = &i;,而非 *ptr = &i;
 
因為 *ptr = &i; 的「 * 」前面,沒有資料型別,因此不是宣告
 
再次強調:

我是說變數 ptr,而 不是 變數 *ptr。

 
總結 「 * 」 的多載功能:

國小的乘法、宣告指標、解參考指標,

相信您再也不會搞混了 😆。

 


 

運算元多載 (Operand Overloading)

指標 (Pointer) 的介紹,就暫告一段落,

不然可能要打個二十篇才能講完了 😂。
 
接下來介紹的是,較常被忽略的 運算元多載 (Operand Overloading)

其中又以 『 0 』最為普遍。

 
 

NULL 指標

就像 Java 物件實例 有 空值 null (小寫) 的概念,

在 C/C++ 語言中,則有所謂的 空指標 NULL (大寫),

表示 指標 目前 沒有指到記憶體中的任何位址
 
 
Q:

那麼,C 語言是如何實現『』這個抽象概念呢?
 
Ans:
空指標 (NULL Pointer) 依舊是個指標,

大部分函式庫的定義是:

((void *)0)

 
(暫不考慮 C++11 的 nullptr 關鍵字)
 
圖示支援:
 

 
其中 void * 是個通用型別的指標,

而 0 代表的便是『空』、『無』、『false』的概念。

 
 

非 0 即 True

在 C/C++ 語言中,條件判斷的準則是:『 非 0 即 True 』,

於是我們得以如此判斷 指標 是否為空:

#include <stdio.h>

int main() {

    int *ptr = NULL;

    if (ptr)
        printf("%s\n", "true");
    else
        printf("%s\n", "false"); // 空指標

    return 0;
}

 

結果是永遠的 false !

 
等價於下列程式碼:

#include <stdio.h>

int main() {
    
    if (0)
        printf("%s\n", "true");
    else
        printf("%s\n", "false"); // 執行這兒~

    return 0;
}

[註]:
Java 的條件判斷式 (if-then statement),
只能 判斷 布林代數 (boolean),不能判斷 數字 (0)。
 

許多開發者習慣將使用完畢的指標,

設為 NULL,以預防修改到非法的記憶體位址。

 
 

NUL 字元

接著,再看一個「 0 」的多載範例 😀:
 
不像 Java 與 C++ 具有方便的 字串類別 String

C 語言中,字串 是以 空字元 結尾的 字元序列 所表示。
 
空字元 (Null Character) 時常簡稱為 NUL 字元 (我沒有少打 L 喔 😆),

實際上,就是個 數值為 0 的 控制字元

 
 

‘\0’

在 C 語言中,空字元 則是以 字元常值 (char literal) '\0' 表示,

其中,反斜線『 \ 』被稱為 逸出字元 或 跳脫字元 (escape character)。
 
例如字串 “Hey”,在記憶體中實際表示為:
 

 
以 C 語言程式為例:

#include <stdio.h>
#include <stdlib.h>
#include <memory.h>

int main() {

    // [法一] -- 字元陣列
    char str[] = {'H', 'e', 'y', '\0', 'J', 'a', 'y'};
    printf("%s\n", str); // 只會印出 Hey,因為遇到 空字元 ('\0') 了

    // [法二] -- 字串常值 (string literal)
    char *str2 = "Hey"; // 會自動補上 空字元 ('\0')
    printf("%s\n", str2);

    // [法三] -- 字元指標
    char *str3 = (char *) malloc(strlen("Hey") + 1); // 需要加 1,以預留給空字元
    strcpy(str3, "Hey"); // 會自動補上 空字元 ('\0')
    printf("%s\n", str3);

    return 0;
}

 
 

看到 0 就想到 — 條件判斷!

別忘了,在 C/C++ 語言中,條件判斷的準則是:『 非 0 即 True 』:

#include <stdio.h>

int main() {

    char nul = '\0';

    if (nul)
        printf("%s\n", "true");
    else
        printf("%s\n", "false"); // 空字元

    return 0;
}

 

依然是永遠的 false !

 
 

NULL vs. NUL

既然 NULL 指標NUL 字元 都是由 0 而來,

那他們可以混用嗎?
 

答案是 不妥

儘管,這樣宣告字串可能不會出錯:

char str[] = {'H', 'e', 'y', NULL};

 
NULL 仍不應用在任何非指標的情況下

 


 

總結

如上所述,0 的意義,在不同情境 (Context),具有不同含義,

可能是 整數 0, NULL 指標 或是 NUL 字元、false…。

這便是多載的觀念:根據不同情境『 相同的模樣,擁有不同的行為 』。
 
多載 (Overlaod) 大幅地增加了程式開發的便利性,

例如,Java 的 valueOf 函式,提供了多種 多載方法,

使我們得以用相同的方法名稱,執行不同資料型別的轉型操作:
 

 
然而,Overload (多載)Override (覆寫) 其實是息息相關的,

皆是實踐 多型 (polymorphism) 的技術之一,

善用這些技巧,才能有效實作彈性、可擴充的程式,

Override (覆寫) 的觀念將在 下篇 提及 😁。
 
 
 
 



作者: 鄭中勝
喜愛音樂,但不知為何總在打程式😱
期許能重新審視、整理自身所學,幫助有需要的人。

在《Overload (多載) vs. Override (覆寫) — (I)》中有 2 則留言

發表迴響