目錄
繼承的含義:(常規->具體)
繼承意味着可以先定義並編譯常規形式的類,再定義這個類的更加具體的形式並繼承這個類的所有屬性,即一個基類創建派生類的過程,在這個過程中,我們可以根據需要添加更多的成員屬性和成員函數。
派生類:
class HourlyEmployee : public Employee { }
- HourlyEmployee:派生類
- Employee:基類
- 繼承關係:符號(:)
派生類的定義只列出新增的成員變量,基類定義的成員變量不必提及,它們會自動提供給派生類。
重定義、重載與重寫
重定義:(redefining)
繼承基類的成員函數的定義需要在派生類的定義中修改,使其在派生類中的定義有別於在基類中的定義。爲了重定義成員函數,只需在類定義中列出它,並提供新的定義,類似於在派生中新增成員函數。
- 子類重新定義父類中有相同名稱的非虛函數 (函數名相同,返回的類型相同, 參數列表可以相同,可以不同 ) 。
重載:(overload)
- 重載是函數名相同,參數列表不同
- 重載只是在類的內部存在。但是不能靠返回類型來判斷。
重寫:(override)
重寫也叫做覆蓋。子類重新定義父類中有相同名稱和參數的虛函數。函數特徵相同。但是具體實現不同,主要是在繼承關係中出現的 。
重寫需要注意:
1 被重寫的函數不能是static的。必須是virtual的
2 重寫函數必須有相同的類型,名稱和參數列表
3 重寫函數的訪問修飾符可以不同。儘管virtual是private的,派生類中重寫改寫爲public,protected也是可以的
#include <iostream> using namespace std; class A { public: A() { cout<<"this is A'contrutor."<<endl; } ~A() { cout<<"this is A'destrutor."<<endl; } //overload,兩個f1函數在Base類的內部被重載 void f1() { cout<<"A f1(null)"<<endl; } void f1(int a) { cout<<"A f1(int)"<<endl; } virtual void display() { cout<<"A display()"<<endl; } private: void say(){ cout<<"A say()"<<endl; } }; class B:public A { public: B():A() { cout<<"this is B'contrutor."<<endl; } ~B() { cout<<"this is B'destrutor."<<endl; } //重定義 void f1(int a) { cout<<"B f1(int)"<<endl; } //重寫 void display() { cout<<"B display()"<<endl; } }; int main() { int a=1; A a1; cout<<"********"<<endl; B b1; a1.f1(); a1.f1(a); b1.f1(a); a1.display(); b1.display(); return 0; }
綜上所述,總結如下:
成員函數重載特徵:
相同的範圍(在同一個類中)
函數名字相同
參數不同
virtual關鍵字可有可無
- 重寫(覆蓋)是指派生類函數覆蓋基類函數,特徵是
不同的範圍,分別位於基類和派生類中
函數的名字相同
參數相同
基類函數必須有virtual關鍵字
- 重定義(隱藏)是指派生類的函數屏蔽了與其同名的基類函數,規則如下:
a 如果派生類的函數和基類的函數同名,但是參數不同,此時,不管有無virtual,基類的函數被隱藏。
b 如果派生類的函數與基類的函數同名,並且參數也相同,但是基類函數沒有vitual關鍵字,此時,基類的函數被隱藏。
派生類的對象具有多個類型
- 在允許使用基類對象的任何地方,都能使用派生類的對象。
例如:
在函數中要求使用Employee類型的參數,就可以爲該參數傳遞HourlyEmployee類型的值,另外,可以將HourlyEmployee類的對象賦給Employee類型的變量。
- 注意:不可以將Employee對象賦給HourlyEmployee類型的變量,畢竟Employee不一定是HourlyEmployee.
不繼承的函數:
- 構造函數不繼承
- 私有成員函數和成員變量不繼承
- 析構函數不繼承
- 拷貝構造函數不繼承
- 賦值操作符不繼承
構造函數和析構函數
- 基類的構造函數不被派生類繼承,構造函數不能被繼承是有道理的,因爲即使繼承了,它的名字和派生類的名字也不一樣,不能成爲派生類的構造函數,當然更不能成爲普通的成員函數,但可在派生類構造函數的定義中調用基類的構造函數
- 析構函數也不能被繼承。與構造函數不同的是,在派生類的析構函數中不用顯式地調用基類的析構函數,因爲每個類只有一個析構函數,編譯器知道如何選擇,無需程序員干涉。
#include <iostream> using namespace std; class A { public: A() { cout<<"this is a'contrutor."<<endl; } ~A() { cout<<"this is a'destrutor."<<endl; } }; class B:public A { public: B():A() { cout<<"this is b'contrutor."<<endl; } ~B() { cout<<"this is B'destrutor."<<endl; } }; int main() { A a1; cout<<"********"<<endl; B b1; return 0; }
- 執行順序:析構函數的執行順序和構造函數的執行順序相反
創建派生類對象時,構造函數的執行順序和繼承順序相同,即先執行基類構造函數,再執行派生類構造函數。
而銷燬派生類對象時,析構函數的執行順序和繼承順序相反,即先執行派生類析構函數,再執行基類析構函數。
私有成員函數和成員變量
- 假如類的私有成員變量可在派生類的成員函數定義中訪問,這違背了其保密性,因此,除了通過基類的接口函數進行訪問,否則不能直接訪問基類的成員函數和成員變量
- 私有成員函數只應作爲輔助函數使用,所以只應在定義他們的類中使用。
賦值操作符和拷貝構造函數
- 重載的賦值操作符和拷貝構造函數不會被繼承,然而,他們可以在派生的重載賦值操作符以及拷貝構造函數的定義中使用。
- 重載賦值操作符必須定義爲類的成員函數。
- 爲什麼賦值運算符重載函數不能被繼承呢?
因爲相較於基類,派生類往往要添加一些自己的數據成員和成員函數,如果允許派生類繼承基類的賦值運算符重載函數,那麼,在派生類不提供自己的賦值運算符重載函數時,就只能調用基類的,但基類版本只能處理基類的數據成員,在這種情況下,派生類自己的數據成員怎麼辦?
參考:
protected限定符
在類的成員變量或成員函數之前,如果添加了限定符protected,那麼除了派生類之外的其他任何類或函數,它的作用等同於private,但在派生類中,可以通過名稱直接訪問這些成員變量