》什麼是繼承:C++語言爲了實現代碼複用最重要的手段,允許我們在原有(基類)功能下擴增,形成一個新類,這個新類稱爲派生類或者子類。
》繼承格式:class 派生類名(子類名):繼承類型 基類名(父類名)
繼承類型:基類成員在派生類中可見性(public,protected,private)
public繼承:基類的非私有成員在子類中訪問類型不變。
protected繼承:基類的非私有成員在子類中訪問類型變爲protected屬性。
private繼承:基類的非私有成員在子類中訪問類型都變爲private屬性。
不論何種繼承基類的私有成員在派生類中都是繼承下來的了只是都是不可見的。
幾點總結:
1.基類私有成員在派生類中是不可訪問的,如果想在派生類中訪問基類非私有成員,而在類外不能訪問就定義爲protected繼承。
2.public繼承是一個接口繼承保持is-a原則,每個父類可用的成員對子類也可用,每個子類對象也是一個父類對象。(即父類非私有成員皆爲子類所用)
3.protected/private繼承是一個實現繼承,基類的部分成員並非完全成爲子類接口的一部分,是 has-a 的關係原則,所以非特殊情況下不會使用這兩種繼承關係,在絕大多數的場景下使用的都是公有繼承。(即子類從父類繼承來的某些成員對外屬性是保護或者私有)
4.不管是哪種繼承方式,在派生類內部都可以訪問基類的公有成員和保護成員,基類的私有成員存在但是在子類中不可見(不能訪問)。
5.使用關鍵字class時默認的繼承方式是private,使用struct時默認的繼承方式public,不過最好顯式的寫出繼承方式。
6.在實際運用中一般使用都是public繼承,極少場景下才會使用protetced/private繼承.
》講一下繼承的調用順序:
基類構造函數->派生類中成員對象構造函數->派生類構造函數
按照繼承列表調用 按照派生類中成員對象聲明順序調用
總結:1.基類構造函數沒有缺省值,派生類必須要在初始化列表中顯示的調用基類構造函數並給出參數。
2.若基類沒有定義構造函數則派生類也不需要定義都使用默認的構造函數
3.基類定義了帶有形參表的構造函數,則派生類就必須也要定義。
舉個菱形繼承的例子:(下面會講到菱形繼承)
class A
{
public:
A()
{
cout << "A()" << endl;
}
int data1;
};
class B:public A
{
public:
B()
{
cout << "B()" << endl;
}
private:
int data2;
};
class C:public A
{
public:
C()
{
cout << "C()" << endl;
}
private:
int data3;
};
class D :public B, public C
{
public:
D()
{
cout << "C()" << endl;
}
private:
int data4;
};
int main()
{
B b;
return 0;
}
這裏程序執行結果爲:A();
B();
創建了一個B類對象,先調用父類即A的構造函數然後再調用自己的構造函數。這裏的構造函數什麼都沒做。
》繼承體系的作用域:
1.父類與子類分屬於兩個作用域(所以父類的私有成員纔不能被子類使用因爲相當於類外調用父類的私有成員)
2.子類和父類有同名成員,子類成員將屏蔽父類對自己成員的直接訪問 稱爲同名隱藏
在子類中除非使用 基類名::成員名 纔會訪問基類成員 稱爲重定義
》繼承與轉換 --賦值兼容規則(條件:必須是public繼承)
1.子類對象可以複製給父類對象 Base b=Derive d;
2.父類對象不可以複製給子類對象。會越界
3.父類的指針,引用可以指向子類對象。Base *B; B=&d;
4.子類的指針,引用不可以指向父類對象。
》友元與繼承
友元關係不能繼承:即基類友元不能訪問子類私有和保護成員。
》繼承和靜態成員:
若基類定義了一個靜態成員,則無論有派生多少個子類,僅有這麼一個靜態成員。
》三種類型的調用方法:分別是
單繼承:即保持直線繼承
class A{}; class B:public A{}; class C:public B {};
多繼承:即子類可以使用多個繼承類型繼承多個父類
class A{}; class B{}; class C:public A,public C{};
菱形繼承:
class A{}; class B:public A{}; class C:public A {}; class D:public B,public C{};
上面菱形繼承程序例子:
如果在主函數中定義D d;結果會是
A();
B();
A();
C();
D();
這樣類D中就會有重複的類A中的成員,訪問data1就會產生二義性,編譯都通不過,因爲他不知道你要調用哪一個data1,當然也可以使用d.B::data1;或者d.C::data1;來決定是哪個data1,不過我們在這塊有一個更好的方法,那就是虛基類。
》虛繼承:如果一個類有多個直接基類,且直接基類又有共同基類,則最底層的派生類中會保留這個間接共同基類的成員多份同名成員。
解決方法:1.在派生對象名前加作用域標識符使他唯一的標識一個成員
2.使用虛基類:虛基類聲明:
class 派生類名:virtual 繼承方式 類名{}
如果派生類中只存在一份間接基類的拷貝,則沒有二義性。
》虛基類的初始化:與普通多繼承的初始化在語法上是一樣的,只是構造函數的調用不一樣
1.虛基類如果定義帶有形參的構造函數,或者定義普通的構造函數,則整個繼承結構中,所有直接或者間接的派生類都必須在構造函數的成員初始化列表中對虛基類構造函數的調用,以初始化在虛基類中定義的數據成員。
2.建立一個對象時,如果這個對象含有從虛基類中繼承來的成員,則虛基類的成員是由最遠派生類的構造函數通過調用虛基類的構造函數來進行初始化的。該派生類的其它基類對虛基類構造函數的調用都忽略。
3.若同一層次中同時包含非虛基類和虛基類,應先調用基類的構造函數在調用非虛基類的構造函數。
4.對於多個虛基類,構造函數的執行順序仍然是先左後右,自上而下。
5.對於非虛基類,構造函數的執行順序仍然是先左後右,自上而下。
6.若虛基類是由非虛基類派生而來的,則仍然先調用基類構造函數,再調用派生類構造函數。
還有一個問題,virtual這個關鍵詞應該在哪加?所有派生類聲明內都加還是隻在直接派生類前加?弄清這個問題就要分析派生類的內存裏都有些什麼東西,是按照什麼順序存儲的?
class A
{
public:
A(int a=0)
:data1(a)
{
cout << "A()" << endl;
}
int data1;
};
class B : virtual public A
{
public:
B(int b=0)
:A(1)
, data2(b)
{
cout << "B()" << endl;
}
private:
int data2;
};
int main()
{
B b(2);
return 0;
}
類B內存中有:指針P-》data2-》data1;並且P指向data1.
調試程序,看到在類B內首先是一個指針,其次是類B自己的成員data2最後是類A的成員data1,並且這個指針存放的就是類A中data1相對於類A對象的偏移量。
這也就是說,子類中會保存一個地址指向虛基類的成員。
還是這個例子,只是這裏是虛繼承:
class A
{
public:
A(int a=0)
:data1(a)
{
cout << "A()" << endl;
}
int data1;
};
class B : virtual public A
{
public:
B(int b=0)
:A(1)
, data2(b)
{
cout << "B()" << endl;
}
private:
int data2;
};
class C : virtual public A
{
public:
C(int c=0)
:A(3)
, data3(c)
{
cout << "C()" << endl;
}
private:
int data3;
};
class D : virtual public B,virtual public C
{
public:
D(int d=0)
:A(2)
,data4(d)
{
cout << "D()" << endl;
}
private:
int data4;
};
菱形虛繼承:當直接子類與間接子類定義前都加virtual時:
分析類B的內存:指針P1-》data2-》data1;
類C的內存:指針P2-》data3-》data1;
類D的內存:指針P3-》data4-》data1-》P1-》data2-》P2-》data3;
P1 P2 P3
只有直接子類前有virtual時:
分析類B的內存:指針P1-》data2-》data1;
類C的內存:指針P2-》data3-》data1;
類D的內存:P1->data2-》P2-》data3-》data4-》data1;