C++是一門可面向對象可面向過程編程的語言,當然它和C語言比起的優勢就是面向對象,面向對象是C++的一大特點,那麼,上一講將了面向對象的第一要素,那麼這一講的內容就是面向對象的第二要素——繼承。
為什麼要有繼承?
如果沒有繼承,那麼類僅僅只是具有一些相關行為的數據結構,所以,這僅僅只是對過程語言的一大改進,而繼承的引入,則開闢完全不同的新天地,那麼繼承能夠做些什麼?
使用繼承構建類。
使用繼承擴展功能。
使用繼承實現多態(多態的實現不只是繼承,還有虛函數,兩者缺一不可)。
那麼什麼時候可以使用繼承?簡單點說有一個原則,就是當我們認為“是一個”的時候就可以考慮繼承了,關於“是一個”的概念可能有點模糊,簡單點說就是當我們認為某個東西屬於某類時就可以使用該原則了,比如貓屬於動物,學生也是人,所以這時就可以使用繼承啦。在C++裡面,有“是一個”的概念,同時也有“有一個”的概念,“有一個”和“是一個”這兩個概念可能有時候會有些模糊,當我們理解不清的時候可能會套錯了模型,“是一個”的概念決定我們使用繼承,而且是共有繼承,而“有一個”的概念我們不應該使用共有繼承,更多的時候我們選擇使用複合,當然有時候我們可以使用私有繼承,當然在私有繼承和複合類型之間怎麼決策又有一些技巧性,這裡大家可以通過《C++ Effective》一書進行了解。
使用繼承構建類。
如果按照上面我們的“是一個”的原則去寫代碼可能我們會發現代碼沒法往下寫,因為很多時候“是一個”並不是那麼容易被看透,否則也就沒有多重繼承這種難以理解的語法存在,所以很多時候我們我們使用繼承沒別的想法,僅僅只是想要複用現有的代碼而已。
//+--------------------------
class Super{
public:
Super(){}
virtual ~Super(){}
void doSomeThing(){}
};
class Sub : public Super{
public:
Sub()
void doOtherThing(){}
};
//+---------------------------
我們可以認為Sub就是一個Super,這沒毛病,也合情合理,但是當出現多重繼承的時候,比如:
//+----------------------------
class Super2{
public:
Super2(){}
virtual void ~Super2(){}
void doSomeOtherThing(){}
};
class Sub : public Super,public Super2{
public:
Sub(){}
void doOtherThing(){}
};
//+---------------------------
如果我們現在還在認為Sub是一個Super的話就有點難理解啦,但它確確實實具有Super和Super2的功能,他們確確實實也滿足“是一個”的原則(否則多態也就沒有意義),但是這裡我們似乎要澄清一件事,這裡並非是因為Sub滿足“是一個”才使用繼承(當然或許我們設計之初就是這麼考慮的),而是因為被繼承才被是一個,嗯,好吧,理解起來有些拗口,所以我們才這麼認為繼承可以用來構建類,是一個或許只是它附加的一個功能。
和使用繼承構建類比起,使用繼承擴展功能似乎更好理解一些,因為它是實實在在的是一個,我們要的也就是這個是一個原則,比如我們手裡有一個處理字符串的類——String,而現有的接口都是該String的引用作為參數,而我們想要在新的開發中使用更加便捷的String,但同時有需要使用原有依賴該String的一些接口功能,所以我們不可能重新實現一個String,我們應該做的就是擴展這個String,子類化一個類出來,他提供有我們需要的功能,同時他還是一個String,那些使用String引用作為參數的接口依然不受任何任何影響:
//+--------------------------
class MyString : public String{
public:
……
using String::append;// 加入基類的append只能接受字符串
template
void append(const T& val){
std::ostringstream os;
os< String::append(os.str()); } …… }; void testFun(const String& str){ std::cout< } int main(){ MyString str; str.append(123); // 調用子類的append str.append(","); // 調用基類的append str.append(128.82);// 調用子類的append std::cout< testFun(str); return 0; } //+---------------------------- 這就是典型的使用繼承去擴展現有功能的例子啦,那麼我們下面看看繼承的一些高級用法,我們將思路回到前面的點上,我們不再去考慮擴展功能這件事,我們只想如何做好一件事,比如想要編寫一個類,他具有比較大小,判斷是否相等的操作,那麼我們可以如下: //+--------------------------- class MObj{ public: MObj(){} virtual ~MObj(){} friend bool operator friend bool operator>(const MObj& obj,const MObj& obj2); friend bool operator==(const MObj& obj,const MObj& obj2); friend bool operator!=(const MObj& obj,const MObj& obj2); …… }; //+--------------------------- 如果我們上面所見,當我們想要實現這些功能,我們需要完成四個函數的編寫,如果我們需要再編寫一個類也需要這些操作,那麼我們又得重新再來一遍,這……如果一個兩個還好,要是我們經常這麼幹一定很不爽,所以這就是我們這裡要說的重點,我們只需要完成一部分操作就能夠實現全部操作即可,比如我們規定,當我們提供operator,同時提供operator==以及提供operator!=等其他操作。 //+--------------------------- template class CmpOrder{ public: friend bool operator>(const T& left,const T& right){ return !(left < right); } friend bool operator == (const T& left, const T& right){ return !(left < right) && !(left > right); } friend bool operator!=(const T& left, const T& right){ return !(left == right); } }; //+----------------------------- 這是一個通用的比較基類,所以只需要我們的類繼承至該類而且實現operator //+----------------------------- class MInt : public CmpOrder private: int mVal; public: MInt(int v) :mVal(v){} friend bool operator return left.mVal < right.mVal; } }; int main(){ MInt val1(7); MInt val2(8); std::cout << (val1 < val2) << std::endl; std::cout << (val1 > val2) << std::endl; std::cout << (val1 == val2) << std::endl; std::cout << (val1 != val2) << std::endl; system("pause"); return 0; } //+------------------------------- 當然這種操作手法有一個奇怪的名字,叫奇特的遞歸模板模式,他的模式: //+------------------------------- class Sub : public Super{……}; //+------------------------------- 他使用模板加繼承的手法實現一些強大的功能,當然還有一些更奇怪的繼承方法我們後續在實踐中慢慢說,比如: //+------------------------------- template class Sub : public Sub template<> class Sub{} //+-------------------------------- 這種繼承手法能夠實現一些功能強大的組件,比如我們可以以此為基礎實現一個數據庫。 好吧,繼承我們就暫時介紹到這裡,接下來還有多態等著說呢。 每天會更新論文和視頻,還有如果想學習c++知識在晚上8.30免費觀看這個直播:https://ke.qq.com/course/131973#tuin=b52b9a80
閱讀更多 IT布丁老師 的文章