第三章-继承与派生
第三章-繼承與派生
文章目錄
- 第三章-繼承與派生
- 1.類的繼承
- 基類與派生類繼承規則(訪問權限)
- 2.類型兼容規則
- 3.派生類構造函數
- 4.派生類析構函數
- 5.同名隱藏
- 6.虛基類
- Tips
1.類的繼承
類的繼承,是新的類從已有類那里得到已有的特性;從已有類產生新類的過程就是類的派生
- 原有的類叫做基類或者父類,產生的新類叫做派生類或者子類
- 一個派生類可以有多個基類,即多繼承,只有一個基類的叫做單繼承
- 如果不顯式地給出繼承方式,默認為私有繼承
- 派生類包含了它的全部基類中除了構造函數和析構函數之外的所有成員
- 派生類成員是指除了從基類繼承來的所有成員之外,新增加的數據和函數成員
示例:
//基類 class A { public:int a;void printA(){printf("A");} }; //派生類,公有繼承 class B:public A { public: int b;void printB(){printf("B");} };基類與派生類繼承規則(訪問權限)
| 公有繼承 | 公有 | 保護 | 不可訪問 | 不可訪問 |
| 保護繼承 | 保護 | 保護 | 不可訪問 | 不可訪問 |
| 私有繼承 | 私有 | 私有 | 不可訪問 | 不可訪問 |
2.類型兼容規則
類型兼容規則是指需要基類對象的地方,都可以使用公有派生類的對象來替代
在替代之后,派生類對象可以作為基類對象使用,但只能使用從基類繼承的成員
class B{···} class D:public B{···} B b1,*pb1; D d1; (1)b1=d1; //派生類對象轉化為基類對象 (2)B &rb=d1; //d1為rb的一個引用,修改它成員變量的值,rb里面也會改變 (3)pb1=&d1; //派生類對象的地址也可以轉化為基類指針,即基類指針指向派生類對象 class A{ public:void display(){cout<<"A::display()"<<endl;} }; class B:public A{ public:void display(){cout<<"B::display()"<<endl;} }; class C:public B{ public:void display(){cout<<"C::display()"<<endl;} }; void fun(A* ptr){ptr->display(); } int main() {A a;B b;C c;fun(&a); //用 A對象的指針調用fun函數fun(&b);fun(&c); } /* 輸出結果均為 A::display() */3.派生類構造函數
構造派生類的對象時,就要對基類的成員對象和新增成員對象進行初始化
派生類構造函數的一般語法形式為:
派生類名 :: 派生類名(參數表):基類名n(參數表),成員對象名n(參數表) {其他初始化操作; }- 如果對基類初始化時,需要調用基類的帶有形參表的構造函數時,派生類就必須聲明構造函數,提供一個將參數傳遞給基類構造函數的途徑
- 如果不需要調用基類的帶參數的構造函數,也不需要調用新增的成員變量的帶參數的構造函數,派生類也可以不聲明構造函數,全部采用默認的構造函數
派生類構造函數執行的一般次序如下:
如果為派生類編寫復制構造函數,一般需要為基類相應的復制構造函數傳遞參數(這里可以使用替代兼容規則)
class A{A(A & n){} }; class B:public A{B(B & m):A(m){ //用派生類對象初始化基類的引用} };4.派生類析構函數
派生類中編寫析構函數,只需要在函數體中把派生類新增的非對象成員清理工作做好就行了,系統會自己調用基類及對象成員的析構函數來對基類及對象成員進行清理。
- 執行次序與構造函數完全相反,首先執行析構函數的函數體,然后對派生類新增的類型的成員對象進行清理,最后對所有從基類繼承而來的成員清理。
- 沒有顯式聲明析構函數的話,編譯系統會自動生成默認的析構函數
5.同名隱藏
如果派生類中聲明了一個和基類成員同名的新成員,派生類的新成員就隱藏了外層同名成員,這叫做同名隱藏
這時使用“對象名 . 成員名”只能訪問到派生類新增成員,如果想要調用基類的成員,必須用基類名和作用域符進行限定
class A{A(){x=12;}int x; }; class B:public A{B(){x=23;}void print(){cout<< A::x <<endl;cout<< x <<endl; //B類中的x}int x; }; int main(){B obj;cout<< obj.A::x <<endl;cout<< obj.B::x <<endl;//為了避免二義性,必須用基類名和作用域符 }為了不產生二義性,也可以使用using關鍵字加以澄清
將using用于基類中的函數名,派生類中定義同名但參數不同的函數,基類的函數就不會被隱藏,兩個重載的函數將會并存在于派生類作用域中
class A{ public:void fun(){···} }; class B:public A{ public:using A :: fun;void fun(int i){···} }; //使用 B的對象,既可以直接調用無參fun函數,也可以直接調用有參fun函數6.虛基類
如果某個派生類的部分或全部直接基類是從另一個共同的基類派生而來,在這些直接基類中,從上一級基類繼承來的成員就擁有相同的名稱,因此派生類中也就會產生同名現象,對這種類型的同名成員也要用作用域符來唯一標識,而且必須用直接基類進行限定
class A{int x; }; class A1:public A{···}; class A2:public A{···}; class B:public A1,public A2{···}; int main(){B b;cout<< b.A1::x <<endl; }可以將共同基類設置為虛基類,這樣從不同路徑繼承而來的同名數據成員在內存中只有一個副本,同一函數名也只有一個映射
虛基類的聲明是在派生類定義過程中進行的,語法為:
class 派生類名:virtual 繼承方式 基類名在多繼承情況下,虛基類關鍵字的作用范圍和繼承方式關鍵字相同,只對其后的基類起作用。聲明了虛基類之后,虛基類的成員在進一步的派生過程中和派生類一起維護同一個內存數據副本
class A{ public:A(){x=10;}int x; }; class B1:virtual public A{ public:void fun1(){x=23;} }; class B2:virtual public A{ public:void fun2(){x=45;} }; class C:public B1,public B2{ public:void fun3(){x=67;} }; int main() {C c;cout<<c.A::x<<","<<c.x<<endl;c.fun1();cout<<c.A::x<<","<<c.x<<endl;c.fun2();cout<<c.A::x<<","<<c.x<<endl;c.fun3();cout<<c.A::x<<","<<c.x<<endl;return 0; } /* 程序的運行結果為:10,1023,2345,4567,67 */- 如果虛基類沒有聲明構造函數,那么所有相關類使用的都是默認構造函數
- 如果虛基類聲明有帶形參的構造函數,并且沒有聲明默認模式的構造函數,那么在整個繼承關系中,直接或間接繼承虛基類的所有派生類,都必須在構造函數的成員初始化列表中列出對虛基類的初始化
建立對象時所指定的類稱為最遠派生類。
在上一個例子中,對于虛基類A而言,C為最遠派生類
建立一個對象時,如果對象中含有從虛基類繼承來的成員,虛基類的成員由最遠派生類的構造函數通過調用虛基類的構造函數進行初始化。
并且只有最遠派生類的構造函數會調用虛基類的構造函數,該派生類的其他基類(如B1、B2)對虛基類構造函數的調用會自動忽略。
Tips
構造一個類的對象的一般順序:
總結
- 上一篇: 第二章-类和对象
- 下一篇: 第四章-数据共享与保护