C++ 继承解析
?
繼承
?
1、概念:
繼承(inheritance)機制是面向對象程序設計使代碼可以復用的最重要的手段,它允許程序員在保持原有類特性的基礎上進行擴展,增加功能。這樣產生新的類,稱派生類。繼承呈現了面向對象程序設計的層次結構,體現了由簡單到復雜的認知過程。
一個新類從已有的類中獲得其已有的特性稱為繼承,被繼承的稱為父類(Base class)或基類,新產生的類稱為派生類或子類。
?
?
2、訪問限定符與繼承關系
?
類的成員有三種訪問限定符:public(公有)、protected(保護)、
private(私有)
?
繼承的方式:public(公有繼承)、protected(保護繼承)、
Private(私有繼承)
?
3、繼承的方式:
繼承權限規則表
| 繼承方式 | 基類的public成員 | 基類的protected成員 | 基類的private成員 | 繼承引起的訪問控制變化 |
| Public | Public | Protected | 不可見 | 基類成員在派生類中訪問權限不變 |
| Protected | Protected | Protected | 不可見 | 基類的非私有成員成為子類的保護成員 |
| Private | Private | Private | 不可見 | 基類的所有成員成為子類的私有成員 |
?
?
基類:
class Base
{
public:
????? Base()
?????????? :pub(1)
?????????? ,pro(2)
?????????? ,pri(3)
????? {}
?
public:
????? int pub;
?
protected:
????? int pro;
?
private:
????? int pri;
?
};
?
(1)公有繼承:采用公有繼承時,基類成員的訪問權限在派生類中不變
class Derived1:public Base
{
public:
?????? Derived1()
????????????? :d_pub(4)
????????????? ,d_pro(5)
????????????? ,d_pri(6)
?????? {}
?????? void SetData()
?????? {
????????????? pub = 0;
????????????? pro = 0;
????????????? pri = 0;?? //子類無法訪問基類的私有成員
?????? }
?
public:
?????? int d_pub;
protected:
?????? int d_pro;
private:
?????? int d_pri;
};
?
void FunTest()
{
?????? Base b;
?????? b.pub = 1;
?????? b.pro = 2;???
?????? b.pri = 3;???? ?? //類外無法訪問類的保護、私有成員
?
?????? Derived1 d1;
?????? d1.pub = 1;
?????? d1.pro = 2;
?????? d1.pri = 3;?? //子類對象無法訪問基類的保護、私有成員
?
?????? d1.d_pub = 4;
?????? d1.d_pro = 4;
?????? d1.d_pri = 4;???? //類外無法訪問類的保護、私有成員
}
?
(2)保護繼承:基類的公有、保護成員成為派生類的保護成員。
?
class Derived2:protected Base
{
public:
?????? Derived2()
????????????? :d_pub(4)
????????????? ,d_pro(5)
????????????? ,d_pri(6)
?????? {}
?????? void SetData()
?????? {
????????????? pub = 0;? //成為子類的保護成員
????????????? pro = 0;
????????????? pri = 0;?? //子類無法訪問基類的私有成員
?????? }
?
public:
?????? int d_pub;
protected:
?????? int d_pro;
private:
?????? int d_pri;
};
?
void FunTest()
{
?????? Base b;
?????? b.pub = 1;
?????? b.pro = 2;???
?????? b.pri = 3;???? ?? //類外無法訪問類的保護、私有成員
?
?????? Derived2 d2;
?????? d2.pub = 1; ? //成為子類的保護成員,無法訪問
?????? d2.pro = 2;
?????? d2.pri = 3;? //子類對象無法訪問基類的保護、私有成員
?
?????? d2.d_pub = 4;
?????? d2.d_pro = 4;
?????? d2.d_pri = 4;???? //類外無法訪問類的保護、私有成員
}
(3)私有繼承
class Derived3:private Base
{
public:
?????? Derived3()
????????????? :d_pub(4)
????????????? ,d_pro(5)
????????????? ,d_pri(6)
?????? {}
?????? void SetData()
?????? {
????????????? pub = 0;? //成為子類的私有成員
????????????? pro = 0;
????????????? pri = 0;?? //子類無法訪問基類的私有成員
?????? }
?
public:
?????? int d_pub;
protected:
?????? int d_pro;
private:
?????? int d_pri;
};
?
void FunTest()
{
?????? Base b;
?????? b.pub = 1;
?????? b.pro = 2;???
?????? b.pri = 3;???? ?? //類外無法訪問類的保護、私有成員
?
?????? Derived3 d3;
?????? d3.pub = 1; ? //成為子類的私有成員,無法訪問
?????? d3.pro = 2;
?????? d3.pri = 3;?? //子類對象無法訪問基類的保護、私有成員
?
?????? d3.d_pub = 4;
?????? d3.d_pro = 4;
?????? d3.d_pri = 4;???? //類外無法訪問類的保護、私有成員
}
?
?
?
2、?總結:
?
(1)??????類的保護成員和私有成員在類外部都不可訪問,基類的私有成員在派生類都不可訪問。
?
?
(2)???public繼承是一個接口繼承,保持is-a原則,每個父類可用的成員對子類也可用,因為每個子類對象也都是一個父類對象。
?
例如:有一個Horse類可以保存關于馬的所有信息,身高體重等等,那么我們就可以從Horse類中派生出白馬類,白馬類包含所有Horse類的成員,在白馬類中可以新增關于白馬的成員,這個成員通常不用于Horse類。
?????????? class Horse
{
public:
??? int Tall;
??? int Weight;
};
classWhiteHorse:public Horse
{
public:
??? int Color;
};
WhiteHorse是Horse的一個子類,包括了Horse類的所有成員,is_a是一種關系
?
?
(3)???protetced/private繼承是一個實現繼承,基類的部分成員并非完全成為子類接口的一部分,是 has-a 的關系原則,所以非特殊情況下不會使用這兩種繼承關系,在絕大多數的場景下使用的都是公有繼承。
?
實現繼承的主要目標是代碼重用,我們發現類B和類C存在同樣的代碼,因此我們設計了一個類 A,用于存放通用的代碼,基于這種思路的繼承稱為實現繼承。?
基類的成員在派生類中是私有的,基類方法將不會成為派生對象公有接口的一部分,但可以在派生類的成員函數中使用它們,基類方法將不會成為派生對象公有接口的一部分,但可以在派生類的成員函數中使用它們。
?
?class Banana
{..};
class Lauch
{
private:
classBanana; ......
};
?
(4)???基類的 private 成員在派生類中是不能被訪問的,如果基類成員不想在類外直接被訪問,但需要在派生類中能訪問,就定義為 protected 。可以看出保護成員限定符是因繼承才出現的。
?
(5)???使用關鍵字class時默認的繼承方式是private,使用struct時默認的繼承方式是public,不過最好顯示的寫出繼承方式。
?
3、?派生類的默認成員函數
在繼承關系里面,在派生類中如果沒有顯示定義這六個成員函數,編譯系統則會默認合成這六個默認
的成員函數。
?
(1)???構造函數調用順序:
進入子類的構造函數——>調用基類構造函數——>執行子類構造函數
說明:
1)、基類沒有缺省構造函數,派生類必須要在初始化列表中顯式給出基類名和參數列表。
2)、基類沒有定義構造函數,則派生類也可以不用定義,全部使用缺省構造函數。
3)、基類定義了帶有形參表構造函數,派生類就一定定義構造函數
?
(2)??????析構函數調用順序:
派生類析構函數——>派生類包含成員對象的析構函數——>基類析構函數
class Base
{
public:
?????? Base()
?????? {
????????????? ?cout<<"Base()"<<endl;
?????? }
?????? Base(int b, int r, int i)
????????????? :pub(1)
????????????? ,pro(2)
????????????? ,pri(3)
?????? {
????????????? cout<<"Base(int , int , int)"<<endl;
?????? }
?????? ~Base()
?????? {
????????????? cout<<"~Base()"<<endl;
?????? }
public:
?????? int pub;
protected:
?????? int pro;
private:
?????? int pri;
};
class Derived3:public Base
{
public:
?????? Derived3()
?????? {
????????????? ??cout<<"Derived3()"<<endl;
?????? }
?????? Derived3(int d1, int d2, int d3)
????????????? :Base(d1, d2, d3)
?????? {
????????????? d_pub = d1;
????????????? cout<<"Derived3()???----"<<"d_pub="<<d_pub<<endl;
?????? }
?????? ~ Derived3()
?????? {
????????????? cout<<"~Derived3()"<<endl;
?????? }
public:
?????? int d_pub;
protected:
?????? int d_pro;
private:
?????? int d_pri;
};
void FunTest()
{
?????? Derived3 d0;
?????? Derived3 d3(1, 2, 3);
}
?
?
?
4、即成體系中的作用域:
(1)在繼承體系中基類和派生類是兩個不同作用域。
(2) 子類和父類中有同名成員,子類成員將屏蔽父類對成員的直接訪問。(在子類成員函數中,可以使用 基類::基類成員 訪問)--隱藏 --重定義
(3) 注意在實際中在繼承體系里面最好不要定義同名的成員
?
5、繼承與轉換--賦值兼容規則:(public繼承)
(1)子類對象可以賦值給父類對象(切割/切片)
(2)父類對象不能賦值給子類對象
(3)父類的指針/引用可以指向子類對象
(4)子類的指針/引用不能指向父類對象(可以通過強制類型轉換完成)
class Base
{
public:
?????? Base()
?????? {
????????????? ?cout<<"Base()"<<endl;
?????? }
?????? Base(int b, int r, int i)
????????????? :pub(1)
????????????? ,pro(2)
????????????? ,pri(3)
?????? { cout<<"Base(int , int , int)"<<endl; }
?????? ~Base()
?????? {cout<<"~Base()"<<endl;}
?
public:
?????? int pub;
?
protected:
?????? int pro;
?
private:
?????? int pri;
};
class Derived:public Base
{
public:
?????? Derived()
?????? { cout<<"Derived3()"<<endl; }
?????? Derived(int d1, int d2, int d3)
????????????? :Base(d1, d2, d3)
?????? {
????????????? d_pub = d1;
????????????? cout<<"Derived()"<<endl;
?????? }
?????? ~ Derived()
?????? { cout<<"~Derived()"<<endl; }
public:
?????? int d_pub;
protected:
?????? int d_pro;
private:
?????? int d_pri;
};
?
void FunTest()
{
?????? Base b0;
?????? Base b1(1, 2, 3);
?????? Base* pBase;
??????
?
?????? Derived d0;
?????? Derived d1(4, 5, 6);
?????? ?Derived* pDerived;
?
?????? b0 = d0;?//子類對象可以賦值給父類對象
?????? b1 = d1;
?
?????? d0 = bo; //? 父類對象不能賦值給子類對象
?????? d1 = b1;
?
?????? pBase = &d0;???? //父類的指針/引用可以指向子類對象
?????? pBase = &d1;
?
?????? Base& _b0 = d0;
?????? Base& _b1 = d1; //父類的指針/引用可以指向子類對象
?
?????? pDerived = &b0;
?????? Derived& = b1;//子類的指針/引用不能指向父類對象
?
?????? pDerived = (Derived *)&b1;?????? //可以使用的強制類型轉換使子類的指針/引用指向父類對象
?
}
?
6、友元與繼承
友元關系不能繼承,也就是說基類友元不能訪問子類私有和保護成員。
class Base
{
public:
?????? Base()
?????? {
????????????? pub = 10;
????????????? pro = 10;
????????????? pri = 10;
????????????? ?cout<<"Base()"<<endl;
?????? }
?????? ~Base()
?????? {
????????????? cout<<"~Base()"<<endl;
?????? }
public:
?????? int pub;
protected:
?????? int pro;
private:
?????? int pri;
?friend void?????? show();
};
class Derived:public Base
{
public:
?????? Derived()
?????? {
????????????? d_pri= 10 ;
????????????? cout<<"Derived3()"<<endl;
?????? }
private:
?????? int d_pri;
};
void?????? show()
{
?????? Base b;
?????? cout<<b.pri<<endl; //友元函數可以訪問基類的私有成員
?????? Derived d;
?????? cout<<d.d_pri<<endl;?? //友元函數不能繼承,基類的友元函數不可以訪問子類的私有成員
}
?
7、繼承的類型
(1)單繼承:
class A
{
public:
?????? int data1;
} ;
class B? :public A
{
public:
?????? int data2;
} ;
(2)多繼承:
class A
{
public:
?????? int data1;
} ;
class B?
{
public:
?????? int data2;
}? ;
?class C :public A ,public B
{
public:
?????? int data3;
} ;
?
?
(3)???菱形繼承:
??????? class A
{
public:
?????? int data1;
} ;
class B: public A??????
{
public:
?????? int data2;
}? ;
?class C:public A
{
public:
?????? int data3;
} ;
class D:public B,public C
{
public:
?????? int data4;
} ;
?
?
?
?
?
D類的對象中存在兩份A對象成員,菱形繼承存在二義性和數據冗余問題
?
?
8、虛繼承--解決菱形繼承的二義性和數據冗余的問題
?
?
?
class A
{
public:
??? int data1;
?
} ;
?
class B: virtual public A??
{
public:
??? int data2;
}? ;
?class C: virtual public A
{
public:
??? int data3;
} ;
?
?class D:public B,public C
{
public:
??? int data4;
} ;
?
?void FunTest()
?{
??? ?D d;
??? ?d.data1 = 1;
??? ?d.data2 = 2;
??? ?d.data3?= 3;
??? ?d.data4?= 4;
??? ?cout<<sizeof(B)<<endl;//12
??? ?cout<<sizeof(D)<<endl;//24
//0x0098F73C?000ecc88 00000002 000ecc94 00000003 00000004 00000001 ??? ?
?
?}
?
(1)虛繼承解決了在菱形繼承體系里面子類對象包含多份父類對象的數據冗余&浪費空間的問題。
(2)虛繼承體系看起來好復雜,在實際應用我們通常不會定義如此復雜的繼承體系。一般不到萬不得已都不要定義菱形結構的虛繼承體系結構,
因為使用虛繼承解決數據冗余問題也帶來了性能上的損耗。
?就寫到這了,有什么不對的大家指正哈!!!
?
?
?
總結
- 上一篇: python Socket 客户端
- 下一篇: C++调用约定