點擊上方“C語言與CPP編程”,選擇“關注/置頂/星標公眾號”
干貨福利,第一時間送達!
最近有小伙伴說沒有收到當天的文章推送,這是因為微信更改了推送機制,導致沒有星標公眾號的小伙伴刷不到當天推送的文章,無法接收到一些比較實用的知識和資訊。所以建議大家加個星標??,以后就能第一時間收到推送了。
條款32、確定你的public繼承塑模出is-a關系
public繼承代表“is-a”關系,即派生類是一種基類。適用于基類的每件事情一定適用于派生類,因為每個派生類對象都是一個基類對象。
例如假設矩形類的規則包括寬度可獨立于高度,那么正方形類便無法public繼承矩形類。
條款33、避免遮掩繼承而來的名稱
對于下面這樣的代碼,編譯器為了估算f1指什么,首先查找局部作用域(即Derived::f1),隨后查找外圍作用域(即Derived類),隨后查找Base類,隨后查找Base類所在的命名空間,最后查找全局作用域,直到找到f2的聲明式。
void Derived::f1(){...f2();...}
然而繼承時,無論是不是虛函數,派生類中的函數會遮掩所有基類中同名的函數,這導致重載時出現問題。
classBase{public:void f1();void f1(int);};class Derived: publicBase{public:void f1();};Derived d;d.f1(); //調用Derived::f1()d.f1(0); //錯誤,Base::f1(int)被遮掩
解決辦法有兩個。
1、使用using聲明式。
classDerived: publicBase{public:using Base::f1;void f1();};Derived d;d.f1(); //調用Derived::f1()d.f1(0); //調用Base::f1(int)
2、使用轉交函數
class Derived: public Base{public:void f1(){ Base::f1(); }};Derived d;d.f1(); //調用Base::f1()d.f1(0); //錯誤
條款34、區分接口繼承和實現繼承
public繼承實際上由函數接口繼承和函數實現繼承組成,其中派生類總是繼承基類的接口。
身為class設計者,有時你希望派生類只繼承接口,有時希望派生類繼承接口和可覆寫的實現,有時希望派生類繼承接口和不可覆寫的實現:
1、希望只繼承接口時,使用純虛函數。
2、希望繼承接口和可覆寫的實現時,使用普通虛函數或有實現的純虛函數;后者迫使用戶覆寫,但又保留了可以調用的默認實現用以減少代碼冗余。
classBase{public:virtual void f() = 0;};classDerived: publicBase{public:virtual void f() override { Base::f(); }};void Base::f(){...}
3、希望繼承接口和不可覆寫的實現時,使用非虛函數。
條款35、考慮virtual函數以外的其他選擇
有時可以考慮virtual函數的替代方案。
1、使用non-virtual interface手法,以public非虛成員函數包裹較低訪問性的virtual函數;其優勢在于可以在虛函數調用前設定好適當場景,并在調用結束后清理場景。
2、將虛函數替換為函數指針成員變量或者std::function成員變量,以調用的function的不同替代虛函數的不同;其優勢在于同一類可以有多種function,并且function可以在運行時更改。
classWidget;intdefault_health_calc(const Widget &w);classWidget{public:typedef std::function<int(const Widget &)> HealthCalc;explicit Widget(HealthCalc hc = default_health_calc) : health_calc(hc){}inthealth_value()const{ return health_calc(*this); }private:HealthCalc health_calc;}
3、使用傳統的Strategy設計模式,將繼承體系內的虛函數替換為另一個繼承體系內的虛函數。
條款36、絕不重新定義繼承而來的non-virtual函數
絕對不要重新定義繼承而來的非虛函數,因為這違反了“is-a”關系。如果真的需要在派生類中實現不同的函數,那么該函數應該聲明為虛函數。
條款37、絕不重新定義繼承而來的缺省參數值
由于禁止重新定義繼承而來的非虛函數,所以只需討論繼承而來的帶缺省參數的虛函數。
原因很簡單:虛函數是動態綁定,而缺省參數值是靜態綁定。如果以基類指針指向派生類對象來使用虛函數,運行的是派生類的虛函數,但缺省參數是基類的缺省參數。
classBase {public:virtualvoidf(int n = 3){ cout << n; }};classDerived :public Base {public:virtualvoidf(int n = 10) override { cout << n; }};intmain(){Base *p = new Derived;p->f(); //輸出3}
條款38、通過復合塑模出has-a或“根據某物實現出”
復合指某種類型的對象內含其他類型的對象。從應用的角度來講,復合意味著has-a;從實現的角度來講,復合意味著根據某物實現出。
要將復合和public繼承區分。例如如果用list來實現set,set并不滿足list中可以有重復元素的規則,只是用list來實現,所以不能是public繼承,而是復合。
條款39、明智而審慎地使用private繼承
private繼承意味著“根據某物實現出”。通常優先使用復合而非private繼承,除了需要使用基類的protected成員或者需要重新定義虛函數時。
不過private繼承有空基類優化。這一點對于庫開發者來說可能很重要。
條款40、明智而審慎地使用多重繼承
多重繼承比單一繼承更復雜,也會導致歧義,例如從多個基類繼承相同名稱時必須明確指出調用的是哪個基類的函數。
當出現鉆石型繼承時C++默認的做法是執行復制。例如假設InputFile、OutputFile都繼承File,IOFile繼承InputFille、OutputFile,那么IOFile中有兩份File的成員變量。

如果不希望這樣,可以使用虛基類,令所有直接繼承它的類采用虛繼承。那么上面的例子中最終IOFile中只有一個File。
class File { ... };classInputFile: virtualpublicFile { ... };class OutputFile: virtual publicFile { ... };class IOFile: public InputFile, public OutputFile { ... };
然而虛繼承會帶來大小、速度、初始化復雜度等成本,因此盡量避免虛繼承,并且使用時虛基類不要帶任何成員變量。
多重繼承的正當用途之一是用public繼承某個接口類、用private繼承某個協助實現的類。

C++ 校招 / 社招跳槽逆襲!從0到1打造高含金量項目,導師1v1輔導,助你斬獲大廠offer!
很多同學準備校招時最焦慮的問題就是:“簡歷沒項目,怎么打動面試官?”
為了解決這個痛點,我們推出了C++訓練營,來了!

在這里,你可以:
我們不只是教你寫代碼,更帶你走一遍完整的項目流程: 從需求分析、架構設計、編譯調試,到版本管理、測試發布,全流程掌握!
項目配套資料齊全,遇到問題還有導師幫你答疑,不怕卡殼!
項目準備好了,你只差一次出發。
相信我,這些項目絕對能夠讓你進步巨大!下面是其中某三個項目的說明文檔

訓練營適用人群:
不適合人群:

1、C++訓練營,來了!
2、HarmonyOS 學習資料分享(無套路免費分享)
我組建了一些社群一起交流,群里有大牛也有小白,如果你有意可以一起進群交流。
歡迎你添加我的微信,我拉你進技術交流群。此外,我也會經常在微信上分享一些計算機學習經驗以及工作體驗,還有一些內推機會。
加個微信,打開另一扇窗
感謝你的分享,點贊,在看三連