面向對象程序設計中最重要的一個概念是繼承。繼承允許我們依據另一個類來定義一個類,這使得創建和維護一個應用程序變得更容易。這樣做,也達到了重用代碼功能和提高執行時間的效果。
當創建一個類時,您不需要重新編寫新的數據成員和成員函數,只需指定新建的類繼承了一個已有的類的成員即可。這個已有的類稱爲基類,新建的類稱爲派生類。
繼承有三種繼承方式:公有繼承, 保護繼承,私有繼承。如下圖所示:
一個簡單的公有繼承:
class Person//父類(基類)
{
public:
Person()
{
cout << "Person" << endl;
}
private:
int _a;
};
class Student : public Person//子類(派生類)
{
public:
Student()
{
cout << "Student" << endl;
}
private:
int _b;
};
總結:
1. 基類的私有成員在派生類中是不能被訪問的,如果一些基類成員不想被基類對象直接訪問,但需要在派生類中能訪問,就定義爲保
護成員。可以看出保護成員限定符是因繼承纔出現的。
2. public繼承是一個接口繼承,保持is-a原則,每個父類可用的成員對子類也可用,因爲每個子類對象也都是一個父類對象。
3. protetced/private繼承是一個實現繼承,基類的部分成員並未完全成爲子類接口的一部分,是has-a 的關係原則,所以非特殊情
況下不會使用這兩種繼承關係,在絕大多數的場景下使用的都是公有繼承。
4. 不管是哪種繼承方式,在派生類內部都可以訪問基類的公有成員和保護成員,但是基類的私有成員存在但是在子類中不可見(不能
訪問)。
5. 使用關鍵字class時默認的繼承方式是private,使用struct時默認的繼承方式是public,不過最好顯示的寫出繼承方式。
6. 在實際運用中一般使用都是public繼承,極少場景下才會使用protetced/private繼承。
繼承與轉換-賦值兼容規則(公有繼承):
1、子類的對象可以賦值給父類(切片)
2、父類的對象不可以賦值給子類
3、父類的指針和引用賦值給子類
4、子類的指針和引用不可以賦值給父類(可以強制類型轉換)
class Person//父類(基類)
{
public:
Person()
{
cout << "Person" << endl;
}
private:
int _a;
};
class Student : public Person//子類(派生類)
{
public:
Student()
{
cout << "Student" << endl;
}
private:
int _b;
};
void test()
{
Person p;
Student s;
p = s;//子類可以賦值給父類
//s = p;//父類不能賦值給子類
Person* p1 = &s;//父類的指針和引用可以賦值給子類
Person& p2 = s;
//Student* s1 = &p;//子類的指針和引用不可以賦值給父類
//Student& s2 = p;
Student* s3 = (Student*)&p;//但是可以強制類型轉換賦值給父類
Student& s4 = (Student&)p;
}
我們用下圖來解釋切片到底是一個什麼東西:
隱藏:
子類和父類中有同名成員,子類成員將屏蔽父類對成員的直接訪問。(在子類成員函數中,可以使用基類::基類成員訪問)–隱藏(重定義)
簡單明瞭來說就是就是一個在基類一個在派生類中,函數名相同,不構成重寫就構成隱藏。不過這裏的重寫下面提到,這裏先這樣寫着。
下面我們再來說說派生類的六大默認成員函數:
在我們的派生類中,調用構造函數時,會有一個神奇的事情發生,它先會對在基類繼承下來的成員進行初始化,然後再對派生類的成員進行初始化。其他四個和構造函數一樣先調用父類的相對應的函數,然後派生類的,不過的是,析構函數有點區別,析構函數先對派生類的成員進行清理,在對父類的成員進行清理,畢竟棧上的規則爲後進先出。
拿一個簡答的構造函數舉個例子吧:
#include<string>
class Person
{
public:
Person(const char* a)
:_a(a)
{}
Person(const Person& p)
:_a(p._a)
{}
protected:
string _a;
};
class Student : public Person
{
public:
Student(const char* a,int b)
:Person(a)
,_b(b)
{}
Student(const Student& s)
:Person(s)
,_b(s._b)
{}
private:
int _b;
};