C++:派生類的構造函數和析構函數的調用順序


一、派生類

在C++編程中,我們在編寫一個基類的派生類時,大致可以分為四步

• 吸收基類的成員:不論是數據成員還是函數成員,派生類吸收除基類的構造函數和析構函數之外的全部成員。

• 改造基類函數:在派生類中聲明一個或多個與其(某個)基類中的成員函數同名的成員函數,並將它(們)根據新的需求進行重寫

• 發展新的成員:在派生類中添加新的成員變量和成員函數,其中新添加的成員要求必須和基類中的成員不同名,並且應當保證新添加的成員會使派生類在功能上相比其基類有所發展

• 重寫派生類的構造函數和析構函數

特別注意:

在重寫派生類的構造函數時,通常將其基類們的構造函數變成新構造函數的一部分

語法:派生類名::派生類名(參數總表):基類名1(參數表1),基類名2(參數表2),......,新增成員名1(參數1),新增成員名2(參數2){}

示例:

 1 #include<iostream>
 2 using namespace std;
 3 class Father{ //基類 
 4 public: 5 Father()=default; //默認構造函數 6 Father(int value):father_value(value){} //帶參數的構造函數 7 ~Father(){} //析構函數 8 void show(){ 9 cout<<"這是基類Father"<<endl; 10 } 11 void father_func(){ 12 cout<<"這是基類Father的方法father_func:"<<endl; 13 cout<<"\t成員father_value的值為:"<<father_value<<endl; 14 } 15 private: 16 int father_value; 17 }; 18 
19 class Son:public Father{ //Father類的派生類Son 20 public: 21 /*步驟一:吸收Father類中除構造函數和析構函數之外的所有成員 22 void show(){ 23 cout<<"這是基類Father"<<endl; 24 } 25 void father_func(){ 26 cout<<"這是基類Father的方法father_func:"<<endl; 27 cout<<"\t成員father_value的值為:"<<father_value<<endl; 28 } 29 */ 30 //步驟二:改造基類Father中的成員 31 void show(){ 32 cout<<"這是派生類Son"<<endl; 33 } 34 //步驟三:發展新的成員 35 void son_func(){ 36 cout<<"這是派生類Son的方法son_func: "<<endl; 37 cout<<"\t成員son_value的值為:"<<son_value<<endl; 38 } 39 //步驟四:重寫構造函數和析構函數 40 Son()=default; 41 Son(int value):Father(value),son_value(value){} 42 ~Son(){} 43 private: 44 /*步驟一:吸收Father類中除構造函數和析構函數之外的所有成員 45  int father_value; 46 */ 47 //步驟三:發展新的成員 48 int son_value; 49 }; 50 int main(){
51     Father father(10);
52     father.show();
53     father.father_func();
54     cout<<"------------分界線--------------------"<<endl;
55     Son son(20);
56     son.show();
57     son.father_func();
58     son.son_func(); 
59     return 0;
60 }

 

二、派生類的構造函數的調用順序

我們先來看一個示例:

 1 #include<iostream>
 2 using namespace std;
 3 class Father1{ //基類1 
 4 public:
 5     Father1(){
 6         cout<<"這是Father1類的構造函數"<<endl;  7     } 
 8 };
 9 
10 class Father2{ //基類2
11 public:
12     Father2(){
13         cout<<"這是Father2類的構造函數"<<endl; 14     } 
15 }; 
16 
17 class Father3{ //基類3
18 public:
19     Father3(){
20         cout<<"這是Father3類的構造函數"<<endl; 21     } 
22 };
23 
24 class Son:public Father1,public Father2,public Father3{ //派生類
25 public:
26     Son(){
27         cout<<"這是Son類的構造函數"<<endl; 28     } 
29 };
30 
31 int main(){
32     Son s;
33     return 0;
34 }

由上面的例子可以看出,派生類在創建對象時會先調用其基類們的構造函數,然后才會調用自己的構造函數。下面是類Son的對象s在內存中的存放形式:

那么派生類調用基類的構造函數的順序又是如何確定的呢?我們在來看一個例子:

 1 #include<iostream>
 2 using namespace std;
 3 class Father1{ //基類1 
 4 public:
 5     Father1(){
 6         cout<<"這是Father1類的構造函數"<<endl;
 7     } 
 8 };
 9 
10 class Father2{ //基類2
11 public:
12     Father2(){
13         cout<<"這是Father2類的構造函數"<<endl;
14     } 
15 }; 
16 
17 class Father3{ //基類3
18 public:
19     Father3(){
20         cout<<"這是Father3類的構造函數"<<endl;
21     } 
22 };
23 
24 class Son:public Father3,public Father1,public Father2{ //派生類
25 public:
26     Son(){
27         cout<<"這是Son類的構造函數"<<endl;
28     } 
29 private:
30 Father1 father1; 31 Father2 father2; 32 Father3 father3; 33 };
34 
35 int main(){
36     Son s;
37     return 0;
38 }

由此可見,派生類在創建對象時其調用構造函數的順序是:

先按照派生類對基類的繼承順序調用基類的構造函數。上例中由語句“class Son:public Father3,public Father1,public Father2”可知類Son先繼承基類Father3,然后繼承基類Father1,最后繼承基類Father2,因此其調用基類的構造函數的順序也是先Fathe3,再Father1,最后Father2。

•( 若派生類的成員變量中存在其基類的對象 )接着按照基類的對象在派生類定義中聲明的先后順序調用基類的構造函數。上例中按照成員對象的定義順序依次調用Father1、Father2和Father3的構造函數。

• 最后調用派生類自己的構造函數

特別注意:

1.如果基類中沒有定義默認構造函數或帶有缺省值的構造函數而只有帶參數的構造函數時,派生類的構造函數中必須顯式的給出基類名和參數表,否則編譯器將報錯

 1 #include<iostream>
 2 using namespace std;
 3 class Father1{ //基類1 
 4 public:
 5     Father1(int v):value1(v){
 6         cout<<"這是Father1類的構造函數"<<endl;
 7     } 
 8 private:
 9     int value1;
10 };
11 
12 class Father2{ //基類2
13 public:
14     Father2(int v):value2(v){
15         cout<<"這是Father2類的構造函數"<<endl;
16     } 
17 private:
18     int value2;
19 }; 
20 
21 class Father3{ //基類3
22 public:
23     Father3(int v):value3(v){
24         cout<<"這是Father3類的構造函數"<<endl;
25     } 
26 private:
27     int value3;
28 };
29 
30 class Son:public Father1,public Father2,public Father3{ //派生類
31 public:
32     Son(int v):Father1(v),Father2(v),Father3(v),value4(v){ //派生類的構造函數中必須顯式的給出基類名和參數表 33         cout<<"這是Son類的構造函數"<<endl;
34     } 
35 private:
36     int value4;
37 };
38 
39 int main(){
40     Son s(10);
41     return 0;
42 }

2.如果基類中沒有定義構造函數,這派生類也可以不定義構造函數,系統會自動在類中添加默認的構造函數的

3.如果基類中定義了帶有參數表的構造函數時,派生類就應當定義相應的構造函數

 

QUESTION:基類在派生類的構造函數的初始化列表中的順序是否會影響派生類的構造函數調用順序?

ANSWER: 我們先來看一個示例:

 1 #include<iostream>
 2 using namespace std;
 3 class Father1{ //基類1 
 4 public:
 5     Father1(int v):value1(v){
 6         cout<<"這是Father1類的構造函數"<<endl;
 7     } 
 8 private:
 9     int value1;
10 };
11 
12 class Father2{ //基類2
13 public:
14     Father2(int v):value2(v){
15         cout<<"這是Father2類的構造函數"<<endl;
16     } 
17 private:
18     int value2;
19 }; 
20 
21 class Father3{ //基類3
22 public:
23     Father3(int v):value3(v){
24         cout<<"這是Father3類的構造函數"<<endl;
25     } 
26 private:
27     int value3;
28 };
29 
30 class Son:public Father3,public Father1,public Father2{ //派生類
31 public:
32     Son(int v):Father1(v),Father2(v),Father3(v),value4(v){
33         cout<<"這是Son類的構造函數"<<endl;
34     } 
35 private:
36     int value4;
37 };
38 
39 int main(){
40     Son s(10);
41     return 0;
42 }

 

上面的例子中派生類Son是以順序Father3、Father1、Father2來繼承基類的,但在其構造函數中基類的順序確實Father1、Father2、Father3。最終的結果表明類Son仍以其繼承基類的順序來調用基類的構造函數,而非基類在派生類構造函數中的順序。因此可見基類在派生類的構造函數的初始化列表中的順序不會影響派生類的構造函數調用順序

 

三、派生類的析構函數的調用順序

• 在派生類中,其析構函數只需要關心新增的一般成員的“善后工作”。而對於新增的成員對象和基類的“善后工作”,系統會自己調用成員對象和基類的析構函數來完成,而不需要用戶來關心。

• 在派生類中,析構函數各部分的執行順序與其構造函數的調用順序剛好相反,即派生類的析構函數先對其新增的一般成員進行析構,然后對新增的成員對象進行析構,最后按照與其繼承基類的相反順序來調用基類的析構函數

 1 #include<iostream>
 2 using namespace std;
 3 class Father1{ //基類1 
 4 public:
 5     Father1(){
 6         cout<<"這是Father1類的構造函數"<<endl;
 7     } 
 8     ~Father1(){
 9         cout<<"這是Father1類的析構函數"<<endl;
10     }
11 };
12 
13 class Father2{ //基類2
14 public:
15     Father2(){
16         cout<<"這是Father2類的構造函數"<<endl;
17     } 
18     ~Father2(){
19         cout<<"這是Father2類的析構函數"<<endl;
20     }
21 }; 
22 
23 class Father3{ //基類3
24 public:
25     Father3(){
26         cout<<"這是Father3類的構造函數"<<endl;
27     } 
28     ~Father3(){
29         cout<<"這是Father3類的析構函數"<<endl;
30     }
31 };
32 
33 class Son:public Father3,public Father1,public Father2{ //派生類
34 public:
35     Son(){
36         cout<<"這是Son類的構造函數"<<endl;
37     } 
38     ~Son(){
39         cout<<"這是Son類的析構函數"<<endl;
40     }
41 private:
42     Father1 father1;
43     Father2 father2;
44     Father3 father3;
45 };
46 
47 int main(){
48     Son s;
49     return 0;
50 }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM