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<class>
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>
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<mint>{/<mint>
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>
class Sub : public Sub<args...>{……}/<args...>
template<>
class Sub{}
//+--------------------------------
這種繼承手法能夠實現一些功能強大的組件,比如我們可以以此為基礎實現一個數據庫。
好吧,繼承我們就暫時介紹到這裡,接下來還有多態等著說呢。
每天會更新論文和視頻,還有如果想學習c++知識在晚上8.30免費觀看這個直播:https://ke.qq.com/course/131973#tuin=b52b9a80