生活随笔
收集整理的這篇文章主要介紹了
C/C++学习之路: 继承
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
C/C++學習之路: 繼承
目錄
繼承概述派生類訪問控制繼承中的構造和析構繼承中同名函數的處理方法非自動繼承的函數繼承中靜態成員特性多繼承
1. 繼承概述
1. 繼承基本概念
c++最重要的特征是代碼重用,通過繼承機制可以利用已有的數據類型來定義新的數據類型,新的類不僅擁有舊類的成員,還擁有新定義的成員。一個B類繼承于A類,或稱從類A派生類B。這樣的話,類A成為基類(父類), 類B成為派生類(子類)。派生類中的成員,包含兩大部分: 一類是從基類繼承過來的,一類是自己增加的成員。 從基類繼承過過來的表現其共性,而新增的成員體現了其個性。
2. 派生類定義
派生類定義格式:
Class 派生類名
: 繼承方式 基類名
{}
三種繼承方式: public : 公有繼承private : 私有繼承protected : 保護繼承 從繼承源上分: 單繼承:指每個派生類只直接繼承了一個基類的特征多繼承:指多個基類派生出一個派生類的繼承關系,多繼承的派生類直接繼承了不止一個基類的特征
2. 派生類訪問控制
派生類繼承基類,派生類擁有基類中全部成員變量和成員方法(除了構造和析構之外的成員方法),但是在派生類中,繼承的成員并不一定能直接訪問,不同的繼承方式會導致不同的訪問權限。派生類的訪問權限規則如下:
class A {
public:int mA
;
protected:int mB
;
private:int mC
;
};
class B : public A {
public:void PrintB() {cout
<< mA
<< endl
; cout
<< mB
<< endl
; }
};class SubB : public B {void PrintSubB() {cout
<< mA
<< endl
; cout
<< mB
<< endl
; }
};void test01() {B b
;cout
<< b
.mA
<< endl
;
}
class C : private A {
public:void PrintC() {cout
<< mA
<< endl
; cout
<< mB
<< endl
; }
};class SubC : public C {void PrintSubC() {}
};void test02() {C c
;
}
class D : protected A {
public:void PrintD() {cout
<< mA
<< endl
; cout
<< mB
<< endl
; }
};class SubD : public D {void PrintD() {cout
<< mA
<< endl
; cout
<< mB
<< endl
; }
};void test03() {D d
;
}
3. 繼承中的構造和析構
子類對象在創建時會首先調用父類的構造函數,父類構造函數執行完畢后,才會調用子類的構造函數當父類構造函數有參數時,需要在子類初始化列表(參數列表)中顯示調用父類構造函數析構函數調用順序和構造函數相反
class A {
public:A() {cout
<< "A類構造函數!" << endl
;}~A() {cout
<< "A類析構函數!" << endl
;}
};class B : public A {
public:B() {cout
<< "B類構造函數!" << endl
;}~B() {cout
<< "B類析構函數!" << endl
;}
};class C : public B {
public:C() {cout
<< "C類構造函數!" << endl
;}~C() {cout
<< "C類析構函數!" << endl
;}
};void test() {C c
;
}
4. 繼承中同名函數的處理方法
當子類成員和父類成員同名時,子類依然從父類繼承同名成員如果子類有成員和父類同名,子類訪問其成員默認訪問子類的成員(本作用域,就近原則)在子類通過作用域::進行同名成員區分(在派生類中使用基類的同名成員,顯示使用類名限定符)
class Base {
public:Base() : mParam(0) {}void Print() { cout
<< mParam
<< endl
; }public:int mParam
;
};class Derived : public Base {
public:Derived() : mParam(10) {}void Print() {cout
<< Base
::mParam
<< endl
;cout
<< mParam
<< endl
;}int &getBaseParam() { return Base
::mParam
; }public:int mParam
;
};int main() {Derived derived
;cout
<< derived
.mParam
<< endl
; derived
.Print();derived
.getBaseParam() = 100;cout
<< "Base:mParam:" << derived
.getBaseParam() << endl
;return EXIT_SUCCESS
;
}
任何時候重新定義基類中的一個重載函數,在新類中所有的其他版本將被自動隱藏(非覆蓋,可通過類作用域運算符調用)
class Base{
public:void func1(){cout
<< "Base::void func1()" << endl
;};void func1(int param
){cout
<< "Base::void func1(int param)" << endl
;}void myfunc(){cout
<< "Base::void myfunc()" << endl
;}
};class Derived1 : public Base{};
class Derived2 : public Base{
public:void myfunc(){cout
<< "Derived2::void myfunc()" << endl
;}
};
class Derived3 : public Base{
public:void func1(int param1
, int param2
){cout
<< "Derived3::void func1(int param1,int param2)" << endl
;};
};
class Derived4 : public Base{
public:int func1(int param
){Base::func1(10);cout
<< "Derived4::int func1(int param)" << endl
;return 0;}
};
void test01(){Derived1 derived1
;derived1
.myfunc();Derived2 derived2
;derived2
.myfunc();
}
void test02(){Derived3 derived3
;derived3
.func1(10,20);Derived4 derived4
;derived4
.func1(10);
}
5. 非自動繼承的函數
不是所有的函數都能自動從基類繼承到派生類中。構造函數和析構函數用來處理對象的創建和析構操作,構造函數和析構函數不能被繼承,必須為每一個特定的派生類分別創建。另外operator=也不能被繼承,因為它完成類似構造函數的行為。盡管我們知道如何由=右邊的對象如何初始化=左邊的對象的所有成員,但是這個并不意味著對其派生類依然有效。在繼承的過程中,如果沒有創建這些函數,編譯器會自動生成它們。
6. 繼承中靜態成員特性
靜態成員函數和非靜態成員函數的共同點: 他們都可以被繼承到派生類中。如果重新定義一個靜態成員函數,所有在基類中的其他重載函數會被隱藏。如果我們改變基類中一個函數的特征,所有使用該函數名的基類版本都會被隱藏。 靜態成員函數不能是虛函數(virtual function)
class Base {
public:static int getNum() { return sNum
; }static int getNum(int param
) {return sNum
+ param
;}public:static int sNum
;
};int Base
::sNum
= 10;class Derived : public Base {
public:static int sNum
;
#if 0static int getNum(int param1
, int param2
){return sNum
+ param1
+ param2
;}
#elsestatic void getNum(int param1
, int param2
) {cout
<< sNum
+ param1
+ param2
<< endl
;}#endif
};int Derived
::sNum
= 20;
7. 多繼承
1. 多繼承基本概念
我們可以從一個類繼承,我們也可以能同時從多個類繼承,這就是多繼承。但是從多個類繼承可能會導致函數、變量等同名導致較多的歧義。解決方法就是顯示指定調用那個基類的版本。
class Base1 {
public:void func1() { cout
<< "Base1::func1" << endl
; }
};class Base2 {
public:void func1() { cout
<< "Base2::func1" << endl
; }void func2() { cout
<< "Base2::func2" << endl
; }
};
class Derived : public Base1, public Base2 {
};int main() {Derived derived
;derived
.func2();derived
.Base1::func1();derived
.Base2::func1();return EXIT_SUCCESS
;
}
2. 菱形繼承和虛繼承
兩個派生類繼承同一個基類而又有某個類同時繼承者兩個派生類,這種繼承被稱為菱形繼承,或者鉆石型繼承。
這種繼承所帶來的問題: 羊繼承了動物的數據和函數,駝同樣繼承了動物的數據和函數,當草泥馬調用函數或者數據時,就會產生二義性。草泥馬繼承自動物的函數和數據繼承了兩份,這份數據我們只需要一份就可以。
class BigBase {
public:BigBase() { mParam
= 0; }void func() { cout
<< "BigBase::func" << endl
; }public:int mParam
;
};class Base1 : public BigBase {
};class Base2 : public BigBase {
};class Derived : public Base1, public Base2 {
};int main() {Derived derived
;cout
<< "derived.Base1::mParam:" << derived
.Base1
::mParam
<< endl
;cout
<< "derived.Base2::mParam:" << derived
.Base2
::mParam
<< endl
;cout
<< "Derived size:" << sizeof(Derived
) << endl
; return EXIT_SUCCESS
;
}
對于這種菱形繼承所帶來的兩個問題,c++為我們提供了一種方式,采用虛基類。
class BigBase {
public:BigBase() { mParam
= 0; }void func() { cout
<< "BigBase::func" << endl
; }public:int mParam
;
};class Base1 : virtual public BigBase {
};class Base2 : virtual public BigBase {
};class Derived : public Base1, public Base2 {
};int main() {Derived derived
;derived
.func();cout
<< derived
.mParam
<< endl
;cout
<< "Derived size:" << sizeof(Derived
) << endl
;return EXIT_SUCCESS
;
}
以上程序Base1 ,Base2采用虛繼承方式繼承BigBase,那么BigBase被稱為虛基類。通過虛繼承解決了菱形繼承所帶來的二義性問題。
3. 虛繼承實現原理
BigBase 菱形最頂層的類,內存布局圖沒有發生改變。Base1和Base2通過虛繼承的方式派生自BigBase,這兩個對象的布局圖中可以看出編譯器為我們的對象中增加了一個vbptr (virtual base pointer),vbptr指向了一張表,這張表保存了當前的虛指針相對于虛基類的首地址的偏移量。Derived派生于Base1和Base2,繼承了兩個基類的vbptr指針,并調整了vbptr與虛基類的首地址的偏移量。使得這種菱形問題在繼承時候能只繼承一份數據,并且也解決了二義性的問題。當使用虛繼承時,虛基類是被共享的,也就是在繼承體系中無論被繼承多少次,對象內存模型中均只會出現一個虛基類的子對象(這和多繼承是完全不同的)。即使共享虛基類,但是必須要有一個類來完成基類的初始化(因為所有的對象都必須被初始化,哪怕是默認的),同時還不能夠重復進行初始化C++標準中選擇在每一次繼承子類中都必須書寫初始化語句(因為每一次繼承子類可能都會用來定義對象),但是虛基類的初始化是由最后的子類完成,其他的初始化語句都不會調用。
總結
以上是生活随笔為你收集整理的C/C++学习之路: 继承的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。