漫談C 程式設計中的多型與new關鍵字 C 教程

2021-04-15 11:53:37 字數 4567 閱讀 8367

1. 你通常怎樣用多型?

假設我有乙個類,裡面有乙個 printstatus 方法,用於列印例項的當前狀態,我希望該類的派生類都帶有乙個 printstatus 方法,並且這些方法都用於列印其實例的當前狀態。那麼我會這樣表達我的願望:

// code #01

class base

} 於是我可以寫乙個這樣的方法:

// code #02

public void displaystatusof(base bs)

} bs 中可能包含著不同的 base 的派生類,但我們卻可以忽略這些「個性」而使用一種統一的方式來處理某事。在 .net 2.0 中,xmlreader 的 create 有這樣乙個版本:

public static xmlreader create(stream input);

你可以向 create 傳遞任何可用的「流」,例如來自檔案的「流」(filestream)、來自記憶體的「流」(memorystream)或來自網路的「流」(networkstream)等。雖然每一中「流」的工作細節都不同,但我們卻使用一種統一的方式來處理這些「流」。

2. 假如有人不遵守承諾...

displaystatusof 隱含著這樣乙個假設:bs 中如果存在派生類的例項,那麼該派生類應該重寫 printstatus,當然必須加上 override 關鍵字:

// code #03

class derived1 : base

} 你可以把這看作一種承諾、約定,直到有人沉不住氣...

// code #04

class derived2 : base

} 假設我們有這樣乙個陣列: // code #05

base bs = new base

; 把它傳遞給 displaystatusof,則輸出是:

// output #01

// public virtual void printstatus() in base

// public override void printstatus() in derived1

// public virtual void printstatus() in base

從輸出結果中很容易看出 derived2 並沒有按照我們期望的去做。但你無需驚訝,這是由於 derived2 的設計者沒有「遵守約定」的緣故。

3. new:封印咒術

從 output #01 中我們可以看到,new 只是把 base.printstatus 封印起來而不是消滅掉,你可以解除封印然後進行訪問。對於 derived2 的使用者,解封的方法是把 derived2 的例項轉換成 base 型別:

// code #06

base d2 = new derived2();

d2.printstatus();

// output #02

// public virtual void printstatus() in base

而在 derived2 內部,你可以透過 base 來訪問:

// code #07

base.printstatus();

這種方法是針對例項成員的,如果被封印的成員是靜態成員的話,就要透過類名來訪問了。

4. 假如 base.printstatus 是某個介面的隱式實現...

假如 base 實現了乙個 iface 介面:

// code #08

inte***ce iface

class base : iface

} 我們只需要讓 derived2 重新實現 iface:

// code #09

class derived2 : base, iface

} derived1 保持不變。則把:

// code #10

iface fs = new iface

傳遞給:

// code #11

public void displaystatusof(iface fs)

} 輸出結果是:

// output #03

// public virtual void printstatus() in base

// public override void printstatus() in derived1

// public new void printstatus() in derived2

從輸出結果中,我們可以看到,雖然 derived2.printstatus 應用了 new,但卻依然參與動態繫結,這是由於 new 只能割斷 derived2.printstatus 和 base.printstatus 的聯絡,而不能割斷它與 iface.printstatus 的聯絡。我在 derived2 的定義中重新指定實現 iface,這將使得編譯器認為 derived2.printstatus 是 iface.printstatus 的隱式實現,於是,在動態繫結時 derived2.printstatus 就被包括進來了。

5. 誰的問題?

我必須指出,如果 base(code #01)和 derived2(code #04)同時存在的話,它們倆其中乙個存在著設計上的問題。為什麼這樣說呢?base 的設計者在 printstatus 上應用 virtual 說明了他希望派生類能透過重寫這一方法來參與動態繫結,即多型性;而 derived2 的設計者在 printstatus 上應用 new 則說明了他希望割斷 derived2.printstatus 和 base.printstatus 之間的聯絡,這將使得 derived2.printstatus 無法參與到 base 的設計者所期望的動態繫結中。如果在 base.printstatus 上應用 virtual(即對多型性的期望)是合理的話,那麼 derived2.printstatus 應該換用另外乙個名字了;如果在 derived2.printstatus 上應用 new(即否決參與動態繫結)是合理的,那麼 base.printstatus 應該考慮是否去掉 virtual 了,否則就會出現一些奇怪的行為,例如 output #01 的第三行輸出。

假如繼承體系中多型性行為的期望是合理的話,那麼更實際的做法應該是把 base 定義成這樣:

// code #12

abstract class base

而原來 base 中的實現應該下移到乙個派生類中: // code #13

class derived3 : base

} 這樣,derived2.printstatus 將使得編譯無法完成,從而迫使其設計者要麼更改方法的名字,要麼換用 override 修飾。這種強制使得 derived2 的設計者不得不重新考慮其設計的合理性。

假如繼承體系中多型性行為的期望不總是合理呢?例如 stream 有這樣乙個方法:

public abstract long seek(long offset, seekorigin origin);

現在假設我有乙個方法在處理輸入流時需要用到 stream.seek:

// code #14

public void resume(stream input, long offset)

當我們向 resume 傳遞乙個 networkstream 的例項,resume 將會丟擲乙個 notsupportedexception,因為 networkstream 不支援 seek。那麼這是否說明 stream 的設計有問題呢?

// code #15

public void resume(stream input, long offset)

else

} 如果 canseek 為 false,那就只好從頭來過了。

實際上,我們並不能保證任何 stream 的派生類都能夠支援某個(些)操作,我們甚至不能保證來自同乙個派生類的所有例項都支援某個(些)操作。你可以設想有這樣乙個 prioritystream,它能夠根據當前登入賬號的許可權來決定是否提供寫操作,這使得擁有足夠許可權的人才能修改資料。或許 stream 的設計者已經預料到這類情況的發生,所以 canread、canseek 和 canwrite 就被加入到 stream 裡了。

值得注意的是,code #07 的 derived2 可能是乙個很糟糕的設計,也可能是乙個很實用的設計。在本文,它是乙個很糟糕的設計,如果你足夠細心,你會察覺到 derived2 的設計者希望 derived2.printstatus 繞過 base.printstatus 而直接和 iface.printstauts 進行關聯,表面上這沒什麼不妥,但實質上 base.printstatus 和 iface.printstauts 在約定上是同質的,這意味著如果與 iface.printstauts 進行關聯就等於承認自己和 base.printstatus 是同質的,這樣的話,為什麼不直接在 derived2 裡重寫 printstatus 呢?在《基類與介面混合繼承的宣告問題》中,我示範了乙個實用的設計,用 new 和介面重新實現(inte***ce reimplementation)來糾正非預期的多型行為。

6. 最後...

當我的朋友拿著問題來找我時,我通常都不會直接給出我的答案,而是盡我的能力向他提供足夠多的可用資訊,以便他能夠根據他所面臨的實際情況作出處理,畢竟,我不會比他更了解他的問題,而他也應該形成他自己的關於他的問題的思考。我希望浪子能用自己的答案回答他所提出的問題,因為只有這樣,那些知識才真正屬於他,並且我也相信本文已經提供了足夠多的可用資訊。

漫談C 程式設計中的多型與new關鍵字

void printstatus in base 我們只需要讓 derived2 重新實現 iface code 09 class derived2 base,iface derived1 保持不變。則把 code 10 iface fs new iface 傳遞給 code 11 public v...

漫談C 程式設計中的多型與new關鍵字 C 教程

1.你通常怎樣用多型?假設我有乙個類,裡面有乙個 printstatus 方法,用於列印例項的當前狀態,我希望該類的派生類都帶有乙個 printstatus 方法,並且這些方法都用於列印其實例的當前狀態。那麼我會這樣表達我的願望 code 01 class base 於是我可以寫乙個這樣的方法 co...

C 程式設計中的 New 關鍵詞的幾種用法

前段時間乙個朋友問到c 的new關鍵字有幾種用法,雖說在日常程式設計中經常用到這個小傢伙,但它到底有幾種用法還真沒有留意過,現將從網上總結出的資料記下以供同仁學習。1 new 運算子 用於建立物件和呼叫建構函式。2 new 修飾符 用於隱藏基類成員的繼承成員。3 new 約束 用於在泛型宣告中約束可...