C 回顧 繼承和組合

2021-08-25 11:21:24 字數 3306 閱讀 9714

一、組合語法

實際上,我們一直在用組合建立類,只不過是在用內部資料型別或已存在類的物件組合新類。

二、繼承語法

在**中和原來一樣給出該類的名字,但在類的左括號的前面,加乙個冒號和基類的名字(對於多重繼承,要給出多個基類名,它們之間用逗號分開)。當做完這些時,將會自動地得到基類中的資料成員和成員函式。

在繼承時,基類中所有的成員都是被預設為私有的,所以如果基類的前面沒有public,這意味著基類的所有公有成員將在派生類中變為私有的;而繼承時通過使用關鍵字public,則基類中的所有公有成員在派生類中仍是公有的。

派生類可以直接訪問所有基類的公有函式;如果派生類中重寫了基類的函式,將會使用派生類的重定義版本,如果仍想呼叫基類的函式,則必須使用作用域運算子來顯式地標明基類名。

三、建構函式的初始化表示式表

建構函式的初始化表示式表的形式模仿繼承活動,將子物件建構函式的呼叫語句放在建構函式參數列和冒號之後,在函式體的左括號之前,如果在初始化表示式表中有多個建構函式的呼叫,則用逗號隔開。

建構函式的初始化表示式表允許我們顯式地呼叫成員物件的建構函式,它的主要思想是:在進入新類的建構函式體之前呼叫所有其他的建構函式,這樣,對子物件的成員函式所做的任何呼叫總是轉到了這個初始化的物件中。即使編譯器可以隱藏地呼叫預設的建構函式,但在沒有對所有的成員物件和基類物件的建構函式進行呼叫之前,就沒有辦法進入該建構函式體。這是c++的乙個強化機制,它確保了如果沒有呼叫物件的建構函式,就別想向下進行。

為了使語法一致,可以把內部型別看做只有乙個取單個引數的建構函式,而這個引數與正在初始化的變數的型別相同。但要記住,這些並不是真正的建構函式,如果沒有顯式地呼叫偽建構函式,初始化是不會執行的。如下:

class x 

};

構造是從類層次的最根處開始,而在每一層,首先會呼叫基類建構函式,然後呼叫成員物件建構函式(對於成員物件,構造函式呼叫的次序完全不受建構函式的初始化表示式表中的次序影響,該次序是由成員物件在類中宣告的次序所決定的,否則就會對兩個不同的建構函式有兩種不同的呼叫順序,而析構函式將不知道如何相應逆序執行析構,這就產生了相關性問題)。呼叫析構函式則嚴格按照建構函式相反的次序。

四、名字隱藏

如果繼承乙個類並且對它的成員函式重新進行定義,可能會出現兩種情況:1)正如在基類中所進行的定義一樣,在派生類的定義中明確地定義操作和返回型別,這稱為對普通成員函式的重定義;2)如果基類的成員函式是虛函式,則稱為重寫。

任何時候如果重新定義了基類中的乙個過載函式,在新類之中所有其他的版本則被自動地隱藏了。如果通過修改基類中乙個成員函式的操作或返回型別來改變了基類的介面,我們就沒有使用繼承所提供的功能,而是按另一種方式來重用了該類(由於繼承的最終目的是為了實現多型性,如果我們改變了函式特徵或返回型別,實際上便改變了基類的介面)。

五、非自動繼承的函式

不是所有的函式都能自動地從基類繼承到派生類中的。建構函式和析構函式用來處理物件的建立和析構操作,但它們只知道對它們的特定層次上的物件做些什麼,所以,建構函式和析構函式不能被繼承,必須為每乙個特定的派生類分別建立。

operator=也不能被繼承,因為它完成類似於建構函式的活。除了賦值運算子以外,其餘的運算子可以自動地繼承到派生類中。

在繼承過程中,如果不親自建立這些函式,編譯器就會生成它們。被生成的建構函式使用成員方式的初始化,被生成的operator=使用成員方式的賦值,生成的operator=僅僅作用於同種型別物件。如果想把一種型別賦於另一種型別,則必須自己寫operator=。

一旦決定寫自己的拷貝建構函式和賦值運算子,編譯器就會假定我們已制定所做的一切,並且不再像在生成的函式中那樣自動地呼叫基類版本。如果想呼叫基類版本,就必須顯式地呼叫它們。

靜態成員函式和非靜態成員函式的共同點:

1)它們均可被繼承到派生類中;

2)如果我們重新定義了乙個靜態成員,所有在基類中的其他過載函式會被隱藏;

3)如果我們改變了基類中的乙個函式的特徵,所有使用該函式名字的基類版本都將會被隱藏。

不同點:

靜態成員函式不能是虛函式。

六、組合與繼承的選擇

組合通常是在希望新類內部具有已存在類的功能時使用,而不是希望已存在類作為它的介面。也就是說,嵌入乙個物件用以實現新類的功能,而新類的使用者看到的是新定義的介面而不是來自老類的介面。

希望新類與已存在的類有著嚴格相同的介面,能在已經用過這個已存在類的任何地方使用這個新類,這就必須使用繼承。

通過在基類表中去掉public或通過顯式地宣告private,可以私有地繼承基類。建立的新類具有基類的所有資料和功能,但這些功能是隱藏的,該類的使用者訪問不到這些內部功能,並且新類的物件不能看做是這個基類的例項。通常情況不使用private繼承,偶然有這種情況(可能想產生像基類介面一樣的介面部分,而不允許該物件的處理像乙個基類物件,private繼承提供了這個功能)。

當私有繼承時,基類的所有public成員都變成了private,如果希望它們中的任何乙個是可視的,在派生類的public部分宣告它們的名字即可,例如:

class

afloat fnumber() const

float fnumber(int) const

};class

b : a ;

這樣,如果想要隱藏基類的部分功能,則private繼承是有用的,如果給出乙個過載函式的名字將使基類中所有它的過載版本公有化。

在實際專案中,有時希望某些東西隱藏起來,但仍允許其派生類的成員訪問,此時protected就派上了用場,它的意思是「就這個類的使用者而言,它是private的,但它可被從這個類繼承來的任何類使用」。最好讓資料成員是private,因為我們應該保留改變內部實現的權利,然後才能通過protected成員函式控制對該類的繼承者的訪問。

保護繼承的派生類意味著對其他類來說是「照此實現」,但它對於派生類和友元是「is-a」,它是不常用的。

確定應當用組合還是繼承,最清楚的方法之一是詢問是否需要從新類向上型別轉換。

七、向上型別轉換

繼承最重要的方面不是它為新類提供了成員函式,而是它是基類與新類之間的關係,這種關係可被描述為:「新類屬於原有類的型別」。從派生類到基類的型別轉換,稱為向上型別轉換(upcasting),這種轉換是安全的(從更專門的型別到更一般的型別)。

如果允許編譯器為派生類生成拷貝建構函式,它將首先自動地呼叫基類的拷貝建構函式,然後再是各成員物件的拷貝建構函式(或在內部型別上執行位拷貝),因此可以得到正確的操作。然而,如果自己寫派生類的拷貝建構函式,並且出現錯誤,這時將會為派生類的基類部分呼叫預設的建構函式,這是在沒有其他的建構函式可供選擇呼叫的情況下,編譯器回溯搜尋的結果。所以在建立自己的拷貝建構函式時,要正確地呼叫基類拷貝建構函式(這是向上型別轉換的一種情況)。

向上型別轉換還能出現在對指標或引用簡單賦值期間。任何向上型別轉換都會損失物件的型別資訊。

c 繼承和組合

當建立乙個物件時,編譯器總是確保呼叫了所有的子物件的建構函式,如果子物件有自己的預設建構函式,那麼編譯器可以自動地呼叫它們。但是,如果子物件沒有預設建構函式,或者想改變建構函式的某個預設引數,這就會出現問題,因為這個新類的建構函式沒有權利訪問這個子物件的私有資料成員,所有不能直接對它們初始化。解決的...

c 繼承,組合

1 什麼是繼承 a繼承b,說明a是b的一種,並且b的所有行為對a都有意義 eg a woman b human a 鴕鳥 b 鳥 不行 因為鳥會飛,但是鴕鳥不會。2.什麼是組合 若在邏輯上a是b的 一部分 a part of 則不允許b從a派生,而是要用a和其它東西組合出b。例如眼 eye 鼻 no...

組合繼承和寄生式組合繼承

組合繼承綜合了原型鏈和盜用建構函式,解決了原型內引用值共享的問題,以及子類在例項化時不能給父類建構函式傳參的問題。缺點 呼叫了兩次父類建構函式影響效率,而且子類的原型物件上也擁有了不必要也用不上的屬性,即父類建構函式的例項屬性。這樣的話子類的例項物件如果刪除某個屬性,這個屬性仍然可以訪問到,因為它可...