六 繼承與物件導向設計條款32 34

2022-08-16 02:36:11 字數 3562 閱讀 3704

is-a即「是一種」的關係,比如drive繼承自base,那麼我們有:

例如,student繼承自乙個person類,那麼:

void eat(person &p)

void study(student &s)

person p;

student s;

eat(s); // 正確呼叫,每個s都是p的物件

study(p); // 錯誤呼叫,p物件不能代表s!

可以明顯看到,person類不能傳遞給乙個student類。

一般來說,我們的基類都是比較廣泛的乙個類,子類是更具體化的類,它繼承了基類的特性,並且豐富了自己特有的性質。

但是世上的事沒有那麼的絕對,比如我有乙個鳥類作為基類,鳥會飛,於是我把飛行動作作為乙個虛函式,讓以後的子類重寫它:

class bird

};

問題來了:企鵝是鳥類,但是企鵝會飛嗎?明顯不能,那我們就不該將fly宣告為虛函式!

基於此問題的一般解決方法如下有兩種:

一、提供乙個執行期的錯誤

class penguin : public bird

};

我們重寫必要的虛函式,但是實現的內部我們只用乙個error提供錯誤資訊,這樣在執行的時候我們就可以明確知道這個pneguin;類是無法提供fly操作的。

二、在編譯期就解決問題

這是作者比較推崇的方法,能在編譯期解決的事情就不要留在執行期。

這時候我們就要修改我們class的設計了。既然鳥類也有不會飛的,那麼我們繼承鳥類的時候要提供乙個會飛的鳥基類,在這個類中提供飛行動作,讓會飛的去繼承這個即可,其它的直接繼承鳥類。

class bird

;// 提供飛行函式,會飛的鳥的基類

class flyingbird : public bird

};// 不會飛的鳥直接繼承bird

class penguin : public bird

};

將我們的設計宣告為如此形式就可以在編譯期解決問題。

綜合上面的情況,我們要明確乙個思想,不要把其它領域(如數學)的直覺施加到程式上面來,這樣有時候可能並不奏效。**可以通過編譯,但是不代表它的邏輯、結果都是正確的呀!

「public繼承」意味著is-a.適用於base classes身上的每一件事情一定也適用於derived classes身上,因為每乙個derive class物件也是乙個base class物件。

繼承而來的函式如果被重寫,那個base類的過載函式也不可見了:

class base

;class derive : public base

;

進行如下呼叫:

derive d;

int x;

d.f1(); // 正確。呼叫derive::f1

d.f1(x); // 錯誤。被遮掩了。

d.f2(); // 正確。呼叫base::f2

d.f3(); // 正確。呼叫derive::f3

d.f3(x); // 錯誤。被遮掩了。

通過這幾個呼叫可以看到,即便base類中有多個過載函式,但是一旦derive類重寫了乙個繼承而來的同名函式,那麼其他幾個過載的也不會被繼承了。如f3,base類中有兩個f3函式,但是derive中重寫了f3,那麼帶引數的f3在derive類中就不再可見了。

如果要讓他們可見的話,把兩行using base::的注釋去掉,就可以正常訪問了。又或者使用轉交函式的方法指定作用域。比如:

virtual void f1()

derived classes內的名稱會遮掩base classes內的名稱。在public繼承下從來沒有人希望如此。

為了讓被遮掩的名稱再見天日,可使用using宣告式或轉交函式。

當我們繼承的時候,public的成員函式總是會被繼承,包括它們的實現。在繼承體系中,我們一般宣告為虛函式,這樣才能為多型提供保證:

接下來運用書上的例子,來看看怎麼恰當的使用impure/pure virtual函式。

情景某航空公司有a和b型兩種飛機,它們都採用一樣的飛行方式,寫成類:

// 目的地機場

class airport

;class airplane

...};class modela : public airplane

;class modelb : public airplane

;

基於當前情景,現在的設計還是乙個好的設計。兩種飛機都採用同一種飛行方式,那麼我們就直接繼承自基類的飛行方式,這樣就不會a和b都另外寫乙份同樣的**,顯得冗餘,避免了**重複。

現在該公司又生產了新型飛機c。採取不一樣的飛行方式,但是我們卻忘記在modelc中給出我們新的fly函式,那麼就會導致我們會呼叫基類的飛行方式,顯然那不是我們想要的。

沒錯,是忘記。理論上我們只需要記得在modelc裡面重寫這個虛函式就可以保證正確執行。但是實際上我們真的可能忘記。

class airplane

...};class modela : public airplane

...};class modelb : public airplane

...};

這個設計雖然不能完美解決問題,因為我們還是可能因為複製黏貼而呼叫錯誤,但至少我們手動再呼叫一次會比之前的設計更好。

總結如下:

(1) 將fly函式寫成pure virtual,就是只提供介面。

(2) 寫乙個protected的預設飛行函式。如果是老式飛機就在繼承的fly函式中呼叫此函式。新型飛機就自己寫實現方式。

這樣就提供了更好的一層保障。

該怎麼宣告取決於實際情況

宣告non-virtual函式的目的是為了令derived classes繼承函式的介面以及乙份強制性實現。

pure virtual,impure virtual,non-virtual函式之間的差異在於你要精確指定你想要derived classes繼承的東西:只繼承介面還是繼承介面和乙份預設實現?或是乙份繼承介面和乙份強制性實現?

介面繼承是實現繼承不同。在public繼承下,derived classes總是繼承base class的介面。

pure virtual函式只具體指定介面繼承。

impure vitual函式具體指定介面繼承及預設實現繼承。

non-virtual函式具體指定介面繼承及強制性實現繼承。

物件導向設計 談談繼承與合成

合成 vs 繼承 關聯class的兩種基本途徑的對比 出處 http www.artima.com designtechniques compoinh.html 摘要這是我的design techniques的一部分,這裡我分析了兩者的構成 flexibility 和執行牽連 performance...

學習PHP物件導向(六)繼承

先看乙個例子 複製 如下 class pupil public function testing class graduate public function testing 從上面的例子可以看出,當多個類有很多共同屬性和方法時,的復用性不高,冗餘,思考css中的處理方法 解決方法 繼承 複製 如下...

Metatable與物件導向 繼承

lua是個面向過程的語言,但通過metatable可以模擬出物件導向的樣子.其關鍵就在於 index這個域.他提供了表的索引值入口.這很像重寫c 中的索引器,當表要索引乙個值時如table key lua會首先在table本身中查詢key的值,如果沒有並且這個table存在乙個帶有 index屬性的...