類的繼承與派生
類的組合
- 在一個類中以另一個類的對象作爲數據成員(子對象),稱爲類的組合。
組合是橫向的。
類的繼承
繼承是在一個已存在的類的基礎上,建立一個新的類。
已存在的類稱爲“基類(base class)”或“父類(father class)”。
新建立的類稱爲“派生類(derived class)”或“子類(child class)”。
單繼承
如果一個派生類只從一個基類派生,稱爲單繼承(single inheritance), 這種繼承關係所形成的層次是一個樹形結構 。
如果一個派生類有兩個或多個基類,稱爲多重繼承(multiple inheritance)
-
派生類的成員
①繼承的基類的數據成員或成員函數。
派生類把基類全部的成員(不包括構造函數和析構函數)接收過來,不能只選擇接收其中一部分成員,而捨棄另一部分成員。
②新增加的數據成員和成員函數。
在聲明派生類時,一般還應當定義派生類的新的成員,包括構造函數和析構函數,因爲構造函數和析構函數是不能從基類繼承的。
③重新定義的基類的成員函數。
覆蓋基類的同名成員。 -
派生類的聲明方式
class Derived: <Access> Base1,<Access> Base2,……
{
private:
……;
public:
……
protected:
……;
};
有三種繼承方式:公有派生、保護派生、私有派生(默認)。
public
: 表示公有派生private
:表示私有派生(默認)protected
:表示保護派生
不同繼承方式的影響主要體現在訪問權限:
派生類成員函數對基類成員的訪問權限;
派生類對象對基類成員的訪問權限;
- 公有派生
公有派生時,基類中所有成員在派生類中保持各個成員的訪問權限。
class A1 : public A
{
……
};
基類成員屬性 | 派生類中的成員函數 | 派生類外的派生類的對象 |
---|---|---|
public | 可以引用 public | 可以引用 |
protected | 可以引用 protected | 不可引用 |
private | private 不可引用 | 不可引用 |
- 私有派生
class A1 : private A
{
……
};
私有派生時,基類中公有成員和保護成員在派生類中均變爲私有的,在派生類中仍可直接使用這些成員;基類中的私有成員,在派生類中不可直接使用。
基類成員屬性 | 派生類中的成員函數 | 派生類外的派生類的對象 |
---|---|---|
public | 可以引用 private | 不可引用 |
protected | 可以引用 private | 不可引用 |
private | 不可引用 private | 不可引用 |
- 保護派生
class A1 : protected A
{
……
};
保護派生時,基類中公有成員和保護成員在派生類中均變爲保護的,在派生類中仍可直接使用這些成員;基類中的私有成員,在派生類中不可直接使用。
基類成員屬性 | 派生類中的成員函數 | 派生類外的派生類的對象 |
---|---|---|
public | 可以引用 protected | 不可引用 |
protected | 可以引用 protected | 不可引用 |
private | 不可引用 private | 不可引用 |
- protected 成員的特點與作用
對建立其所在類對象的模塊來說,它與 private 成員的性質相同。
對於其派生類來說,它與 public 成員的性質相同。
既實現了數據隱藏,又方便繼承,實現代碼重用。
多繼承
class Derived: <Access> Base1,<Access> Base2,……
{
private:
……;
public:
……
protected:
……;
};
- 注意:每一個“繼承方式”,只用於限制對緊隨其後之基類的繼承。
派生類的構造函數和析構函數
- 構造函數不能被繼承。
設計派生類的構造函數時,不僅要考慮派生類所增加的數據成員的初始化,還應當考慮基類的數據成員初始化。在執行派生類的構造函數時,使派生類的數據成員和基類的數據成員同時都被初始化。
解決方法: 在執行派生類的構造函數時,調用基類的構造函數來初始化基類成員和子對象。
派生類構造函數名(總參數表列): 基類構造函數名(參數表列) ,子對象名(參數表列) { 派生類中新增數據成員初始化語句 }
先用單繼承來說明:
demo
class C:public A
{
int y;
B b; //自對象
public:
C(int a,int b,int c):A(a),B(b){y = c}
};
當撤銷派生類對象時,析構函數的調用正好相反。
多重繼承時派生類的構造函數
派生類構造函數名(總參數表列) :
基類1構造函數(參數表列) , 基類2構造函數(參數表列) , 基類3構造函數(參數表列)
……{派生類中新增數成員據成員初始化語句}
派生類構造函數的執行順序:先按照聲明派生類時基類出現的順序調用基類的構造函數,再執行派生類構造函數。
-
派生類的複製構造函數
- 派生類未定義複製構造函數的情況
編譯器會在需要時生成一個隱含的複製構造函數;
先調用基類的複製構造函數;
再爲派生類新增的成員執行復制。 - 派生類定義了複製構造函數的情況
基類的複製構造函數形參類型是基類對象的引用,實參可以是派生類對象的引用 C::C(const C &c1):B1(c1),B2(c1){……}
- 派生類未定義複製構造函數的情況
-
派生類的析構函數
-
析構函數不被繼承,派生類如果需要,要自行聲明析構函數。
-
不需要顯式地調用基類的析構函數,系統會自動隱式調用。
先執行派生類析構函數,再調用基類的析構函數
class A{
~A(){}
};
class B: public A{
~B(){} //系統會自動隱式調用A的析構函數
};
demo
#include <iostream>
using namespace std;
class Base1 {
public:
Base1(int i)
{ cout << "Constructing Base1 " << i << endl; }
~Base1() { cout << "Destructing Base1" << endl; }
};
class Base2 {
public:
Base2(int j)
{ cout << "Constructing Base2 " << j << endl; }
~Base2() { cout << "Destructing Base2" << endl; }
};
class Base3 {
public:
Base3() { cout << "Constructing Base3 *" << endl; }
~Base3() { cout << "Destructing Base3" << endl; }
};
class Derived: public Base2, public Base1, public Base3 {
public:
Derived(int a, int b, int c, int d): Base1(a), member2(d), member1(c), Base2(b)
{ }
private:
Base3 member3;
Base1 member1;
Base2 member2;
};
int main() {
Derived obj(1, 2, 3, 4);
return 0;
}
Constructing Base2 2
Constructing Base1 1
Constructing Base3 *
Constructing Base3 *
Constructing Base1 3
Constructing Base2 4
Destructing Base2
Destructing Base1
Destructing Base3
Destructing Base3
Destructing Base1
Destructing Base2
- 多重繼承中的二義性衝突
如果從不同基類繼承了同名成員,但是在派生類中沒有定義同名成員,“派生類對象名”、“引用名.成員名”、“派生類指針->成員名”訪問成員存在二義性問題
解決方式:用類作用域符限定類名::
class A{
int x;
};
class B{
int x;
}
class C:public A,public B{
};
C c;
c.x; //二義性
c.A::x;
c.B::x;
- 派生類與基類同名成員的支配規則
當派生類中新增加的數據或函數與基類中原有的同名時,若不加限制,則優先調用派生類中的成員(同名隱藏)。
虛基類
class A{
int x;
};
class B:public A{
};
class C:public A{
};
class D:public B,public C{
}; //這樣,類D中就有兩份類A的拷貝
D d;
d.x = 1; //模糊引用
同一個公共的基類在派生類中產生多個拷貝,不僅多佔用了存儲空間,而且可能會造成多個拷貝中的數據不一致和模糊的引用。
在多重派生的過程中,若使公共基類在派生類中只有一個拷貝,可將公共基類說明爲虛基類。
在派生類的定義中,在基類的類名前加上關鍵字virtual
,將基類說明爲虛基類。
要注意:
virtual的修飾對象、修飾位置及修飾時機
demo
class A{
int x;
};
class B:virtual public A{
};
class C:virtual public A{
};
class D:public B,public C{
}; //這樣,類D中就有兩份類A的拷貝
- 用虛基類進行多重派生時,若虛基類沒有缺省的構造函數,則在每一個派生類的構造函數中都必須有對虛基類構造函數的顯示調用 (且首先調用)。
class A{
public:
int x;
A(int x){this->x = x;}
};
class B:virtual public A{
public:
int y;
B(int x,int y):A(x){this->y = y;}
};
class C:virtual public A{
public:
int z;
C(int x,int z):z(z),A(x){}
};
class D:public B,public C{
public:
int dx;
D(int a1,int b,int c,int d,int a2):B(a1,b),C(a1,c),A(a2){dx=d;}
}; //這樣,類D中就有兩份類A的拷貝
int main(){
D d(1,2,3,4,5);
cout<<d.B::x<<endl;
cout<<d.C::x<<endl;
cout<<d.x<<endl;
return 0;
}
由於virtual
的作用,D的對象中的x只有一份,所以即使所以用B::
、C::
來限定,仍然不能獲取B,C的x值,獲得只是那唯一的一份x的值。
5
5
5
在這個例子中,基類A中沒有無參構造函數,必須顯示調用A(a2)
。
使用多重繼承時要十分小心,經常會出現二義性問題。
建議: 不要在程序中過度使用多重繼承,只有在比較簡單、不易出現二義性的情況或實在必要時才使用多重繼承,能用單一繼承解決的問題就不要使用多重繼承。