繼承:程序員
派生類:數組
程序猿種類有不少種,如 C/C++ 程序猿,Java 程序猿,Python 程序猿等等。那麼咱們要把程序猿設計成一個基類, 咱們則須要抽出其特有的屬性和方法。bash
全部程序猿的共同屬性(成員變量):函數
全部的程序猿都有的共同方法(成員函數):微服務
而不一樣的程序猿,又有各自不一樣的屬性和方法:post
繼承的格式以下:大數據
class 派生類名:public 基類名 {
};
複製代碼
程序猿 Coder
基類:ui
class Coder {
public:
bool isWorkOvertime(){} // 是否要加班
bool isReward(){} // 是否有獎勵
void Set(const string & name) // 設置名字 {
m_name = name;
}
...
private:
string m_name; // 姓名
string m_post; // 職位
int m_sex; // 性別
};
複製代碼
Python 程序猿 PythonCoder
派生類:人工智能
class PythonCoder : public Coder
{
public:
bool isAIField(){} // 是不是人工智能領域
bool isBigDataField(){} // 是不是大數據領域
};
複製代碼
派生類對象的大小 = 基類對象成員變量的大小 + 派生類對象本身的成員變量的大小。在派生類對象中,包含着基類對象,並且基類對象的存儲位置位於派生類對象新增的成員變量以前,至關於基類對象是頭部。spa
class CBase {
int a1;
int a2;
};
class CDerived : public CBase
{
int a3;
};
複製代碼
繼承的關係是「是」的關係:
繼承的關係是「有」的關係:
假設已經存在了 Man 類表示男人,後面須要些一個 Women 類來表示女人。Man 類和 Women 類確實是有共同之處,那麼就讓 Women 類繼承 Man 類,是否合適?
咱們先想一想繼承的邏輯要求,假設 Women 類繼承 Man 類後的邏輯就是:一個女人也是一個男人。很明顯,這顯然不成立!
因此,好的作法是歸納男人和女人的共同特色,抽象出一個 Human 類表示人,而後 Man 和 Woman 都繼承 Human 類。
假設要寫一個小區養狗管理系統:
假定狗只有一個主人,可是一個主人能夠最多有 10 條狗,應該如何設計和使用「主人」類 和「狗」類呢?咱們先看看下面幾個例子:
class CDog;
class CMaster // 主人類 {
CDog dogs[10]; // 狗類的成員對象數組
};
class CDog // 狗類 {
CMaster m; // 主人類的成員對象
};
複製代碼
例子一能夠發現是:
至關於人中有狗,狗中有人:
這樣是很差的,由於會產生循環不斷的構造,主人類構造狗對象,狗類又構造主人對象....
class CDog;
class CMaster // 主人類 {
CDog * pDogs[10]; // 狗類的對象指針數組
};
class CDog // 狗類 {
CMaster m; // 主人類的成員對象
};
複製代碼
這樣又變成狗中有人,人去指向「狗中有人」的狗,關係就會顯得很錯亂,以下圖:
class CDog;
class CMaster // 主人類 {
CDog dogs[10]; // 狗類的對象數組
};
class CDog // 狗類 {
CMaster * pm; // 主人類的對象指針
};
複製代碼
這樣就會變成,人中有狗,人裏面的狗又會指向主人,雖然關係相對好了一點,可是一樣仍是會繞暈,效果以下圖:
class CDog;
class CMaster // 主人類 {
CDog * pDogs[10]; // 狗類的對象指針數組
};
class CDog // 狗類 {
CMaster * pm; // 主人類的對象指針
};
複製代碼
這個是正確的例子,由於至關於人和主人是獨立的,而後經過指針的做用,使得狗是能夠指向一個主人,主人也能夠同時指向屬於本身的 10 個狗,這樣會更靈活。
若是不用指針對象,生成 A 對象的同時也會構造 B 對象。用指針就不會這樣,效率和內存都是有好處的。
好比:
class Car {
Engine engine; // 成員對象
Wing * wing; // 成員指針對象
};
複製代碼
定義一輛汽車,全部的汽車都有 engine,但不必定都有 wing 這樣對於沒有 wing 的汽車,wing 只佔一個指針,判斷起來也很方便。
派生類(子類)能夠定義一個和基類(父類)成員同名的成員,這叫「覆蓋」。在派生類(子類)中訪問這類成員時,默認的狀況是訪問派生類中定義的成員。要在派生類中訪問由基類定義的同名成員時,要使用做用域符號::
。
下面看具體的例子:
// 基類
class Father {
public:
int money;
void func();
};
// 派生類
class Son : public Father // 繼承
{
public:
int money; // 與基類同名成員變量
void func(); // 與基類同名成員函數
void myFunc();
};
void Son::myFunc()
{
money = 100; // 引用的是派生類的money
Father::money = 100; // 引用的是基類的money
func(); // 引用的是派生類的
Father::func(); // 引用的是基類的
}
複製代碼
至關於 Son 對象佔用的存儲空間:
咱們都知道基類的 public 成員,都是能夠被派生類成員訪問的,那麼基類的 protected、private 成員,分別能夠被派生類成員訪問嗎?帶着這個問題,咱們能夠先看下面的栗子:
class Father {
public:
int nPublic; // 公有成員
protected:
int nProtected; // 保護成員
private:
int nPrivate; // 私有成員
};
class Son : public Father
{
void func() {
nPublic = 1; // OK
nProtected = 1; // error
nPrivate =1; // ok,訪問從基類繼承的protected成員
Son a;
a.nProtected = 1; // error,a不是當前對象
}
};
int main() {
Father f;
Son s;
f.nPublic; // OK
s.nPublic; // OK
f.nProtected; // error
s.nProtected; // error
f.nPrivate; // error
s.nPrivate; // error
}
複製代碼
基類的 protected、private 成員對於派生類成員的權限說明:
基類的 protected 成員 | 基類的 private 成員 |
---|---|
派生類的成員函數能夠訪問當前對象的基類的保護成員 | 不能被派生類成員訪問 |
一般在初始化派生類構造函數時,派生類構造函數是要實現初始化基類構造函數的。那麼如何在派生類構造函數裏初始化基類構造函數呢?
class Bug {
private :
int nLegs; int nColor;
public:
int nType;
Bug (int legs, int color);
void PrintBug (){ };
};
class FlyBug : public Bug // FlyBug 是Bug 的派生類
{
int nWings;
public:
FlyBug( int legs,int color, int wings);
};
Bug::Bug( int legs, int color)
{
nLegs = legs;
nColor = color;
}
// 錯誤的FlyBug 構造函數
FlyBug::FlyBug ( int legs,int color, int wings)
{
nLegs = legs; // 不能訪問
nColor = color; // 不能訪問
nType = 1; // ok
nWings = wings;
}
// 正確的FlyBug 構造函數:
FlyBug::FlyBug ( int legs, int color, int wings):Bug( legs, color)
{
nWings = wings;
}
int main() {
FlyBug fb ( 2,3,4);
fb.PrintBug();
fb.nType = 1;
fb.nLegs = 2 ; // error. nLegs is private
return 0;
}
複製代碼
在上面代碼例子中:
第24-30行的派生類構造函數初始化基類是錯誤的方式,由於基類的私有成員是沒法被派生類訪問的,也就沒法初始化。
第33-36行代碼是正確派生類構造函數初始化基類構造函數的方式,經過調用基類構造函數來初始化基類,在執行一個派生類的構造函數 以前,老是先執行基類的構造函數。
從上面的例子中咱們也得知構造派生對象前,是先構造基類對象,那麼在析構的時候依然依據「先構造,後初始化」的原則,因此派生類析構時,會先執行派生類析構函數,再執行基類析構函數。
以下栗子:
class Base {
public:
int n;
Base(int i) : n(i)
{
cout << "Base " << n << " constructed" << endl;
}
~Base()
{
cout << "Base " << n << " destructed" << endl;
}
};
class Derived : public Base
{
public:
Derived(int i) : Base(i)
{
cout << "Derived constructed" << endl;
}
~Derived()
{
cout << "Derived destructed" << endl;
}
};
int main() {
Derived Obj(3);
return 0;
}
複製代碼
輸出結果:
Base 3 constructed
Derived constructed
Derived destructed
Base 3 destructed
複製代碼
// 基類
class Base {};
// 派生類
class Derived : public Base {};
Base b; // 基類對象
Derived d; // 派生類對象
複製代碼
b = d;
複製代碼
Base & br = d;
複製代碼
Base * pb = & d;
複製代碼
==注意:若是派生方式是 private 或 protected,則上述三條不可行==
// 基類
class Base {};
// 派生類
class Derived : protected Base {};
Base b; // 基類對象
Derived d; // 派生類對象
複製代碼
因此派生方式是 private 或 protected,則是沒法像 public 派生承方式同樣把派生類對象賦值、引用、指針給基類對象。
public 派生方式的狀況下,派生類對象的指針能夠直接賦值給基類指針
Base *ptrBase = & objDerived;
複製代碼
經過強制指針類型轉換,能夠把 ptrBase 轉換成 Derived 類的指針
Base * ptrBase = &objDerived;
Derived *ptrDerived = ( Derived * ) ptrBase;
複製代碼
程序員要保證 ptrBase 指向的是一個 Derived 類的對象,不然很容易會出錯。