2002.C++BASE-構(gòu)造函數(shù)、析構(gòu)函數(shù)

轉(zhuǎn):C++繼承中構(gòu)造函數(shù)、析構(gòu)函數(shù)調(diào)用順序及虛析構(gòu)函數(shù)

1.構(gòu)造函數(shù)

大家都知道構(gòu)造函數(shù)里就可以調(diào)用成員變量,而繼承中子類是把基類的成員變成自己的成員,那么也就是說子類在構(gòu)造函數(shù)里就可以調(diào)用基類的成員了,這就說明創(chuàng)建子類的時候必須先調(diào)用基類的構(gòu)造函數(shù),只有這樣子類才能在構(gòu)造函數(shù)里使用基類的成員,所以是創(chuàng)建子類時先調(diào)用基類的構(gòu)造函數(shù)然后再調(diào)用自己的構(gòu)造函數(shù)。通俗點說,你要用某些物品,但這些物品你沒辦法自己生產(chǎn),自然就要等別人生產(chǎn)出來,你才能拿來用。

2.析構(gòu)函數(shù)

上面說到子類是將基類的成員變成自己的成員,那么基類就會只存在子類中直到子類調(diào)用析構(gòu)函數(shù)后。做個假設(shè):假如在基類的析構(gòu)函數(shù)調(diào)用比子類的先,這樣會發(fā)生什么事呢?類成員終止了,而類本身卻還在,但是在類存在的情況下,類成員就應該還存在的,這不就產(chǎn)生矛盾了嗎?所以子類是調(diào)用自身的析構(gòu)函數(shù)再調(diào)用基類的析構(gòu)函數(shù)基類的析構(gòu)函數(shù)必須設(shè)置為虛的,而作為最終子類則可以是虛的也可以不是虛的,因為沒有其他類繼承于它不會影響最終功能。但又不是所有類的析構(gòu)函數(shù)都設(shè)置為虛的比較好,因為存在虛函數(shù)的類實例化時會額外添加一個虛表指針,浪費內(nèi)存性能

3.虛函數(shù)

virtual主要作用是在多態(tài)方面,而C++的多態(tài)最主要的是類的動態(tài)綁定,動態(tài)綁定則是指將子類的指針或引用轉(zhuǎn)換成基類,基類對象就可以動態(tài)判斷調(diào)用哪個子類成員函數(shù)。這就說明在沒有子類指針或引用轉(zhuǎn)換為基類對象的話,virtual沒有存在意義(純虛函數(shù)除外),也就是有沒有virtual都是調(diào)用其自身的成員函數(shù)。通過這些分析,對于virtual就有了眉目了。當子類指針或引用轉(zhuǎn)換為基類時,若基類中有用virtual定義的函數(shù),被子類重寫后,此基類對象就會根據(jù)子類調(diào)用子類中的重寫后的函數(shù),而不是基類中的函數(shù);反之,若是基類中沒有用virtual定義,則不管基類被賦值的是哪個子類的值,調(diào)用的都是基類的成員函數(shù)(當然指的值子類重載的基類函數(shù),不然就算要調(diào)用子類特有的成員函數(shù)也會編譯不過)。

存在虛析構(gòu)函數(shù)為什么不會存在虛構(gòu)造函數(shù)呢?

構(gòu)造函數(shù)不能是虛函數(shù),因為構(gòu)造子類時本身也是調(diào)用的子類構(gòu)造函數(shù),然后子類構(gòu)造函數(shù)會調(diào)用基類構(gòu)造函數(shù),所以虛構(gòu)造函數(shù)的存在是沒有意義的。只有在構(gòu)造完成后,對象才能成為一個類的名符其實的實例。另外,靜態(tài)成員函數(shù)和內(nèi)聯(lián)函數(shù)也不能是虛函數(shù)

虛函數(shù)是針對對象的,不是針對類的.

這一點可以從類成員函數(shù)(即靜態(tài)成員函數(shù))不能是虛函數(shù)看出來.倘若類不被實例化為對象,虛函數(shù)的存在本身也沒意義.

上面的假設(shè)我感覺并不認可,派生類中的構(gòu)造,析構(gòu)可以調(diào)用到基類的構(gòu)造析構(gòu)是由編譯器編譯中實現(xiàn)的.即:在子類構(gòu)造函數(shù)開頭自動添加默認的基類構(gòu)造函數(shù)或初始化列表中指定的基類構(gòu)造函數(shù)調(diào)用;在子類析構(gòu)函數(shù)末尾自動添加其基類析構(gòu)函數(shù)調(diào)用.

至于為什么會先調(diào)用基類構(gòu)造函數(shù)再調(diào)用子類構(gòu)造函數(shù),先調(diào)用子類析構(gòu)函數(shù)再調(diào)用基類析構(gòu)函數(shù).我認為:因為只可能出現(xiàn)子類中成員依賴基類成員的存在而存在,而不會出現(xiàn)基類中成員依賴子類成員存在.例如:子類中有一個成員是基類中一個指針成員所指向?qū)ο蟮囊?則這種情況下倘若沒有先調(diào)用基類構(gòu)造函數(shù)對其指針成員初始化創(chuàng)建對象.那子類引用初始化時便不知會指向何處.同樣析構(gòu)時倘若先調(diào)用基類將其中的對象釋放后,此時子類中引用變量在做一下善后處理時也便沒有任何意義,因而其指向?qū)ο笠呀?jīng)釋放掉了. 派生類對象中基類成員先于子類成員存在,后于子類對象消失.

不知道初始化列表中倘若基類構(gòu)造函數(shù)在其他子類成員初始化之后生成的代碼中基類構(gòu)造函數(shù)調(diào)用是否還會在其他代碼之前.這樣子在GCC中會報警,但可以編譯通過,而且感覺其生成代碼中也會按照初始化列表中順序調(diào)用.即構(gòu)造函數(shù)調(diào)用被放到了其他子類成員后面,因為代碼就是這么寫的.(這句我也不是那么確定的)

關(guān)于派生類構(gòu)造函數(shù)與基類構(gòu)造函數(shù)的調(diào)用順序問題

  • 《面向?qū)ο蟪绦蛟O(shè)計基礎(chǔ)(第二版》李師賢等,第254頁:C++語言的基本規(guī)則是:創(chuàng)建一個派生類的對象時,如果基類帶有構(gòu)造函數(shù),則先調(diào)用基類的構(gòu)造函數(shù),然后才調(diào)用派生類的構(gòu)造函數(shù)。

  • 《Thinking in C++》,劉宗田等譯,第261頁:可以看出,構(gòu)造在類層次的最根處開始,而在每一層,首先調(diào)用基類構(gòu)造函數(shù),然后調(diào)用成員對象構(gòu)造函數(shù)。

  • 《C++ Primer Plus(第四版)中文版》,孫建春等譯,第399頁:記住:創(chuàng)建派生類對象時,程序首先調(diào)用基類構(gòu)造函數(shù),然后再調(diào)用派生類構(gòu)造函數(shù)。

真的是這樣嗎?

一個類的對象在實例化時,這個類的構(gòu)造函數(shù)會被調(diào)用。如果承認這一點,就會發(fā)現(xiàn)上述論斷的矛盾之處。一個派生類的對象,在實例化時,不調(diào)用作為產(chǎn)生它的類 的構(gòu)造函數(shù),而先去調(diào)用別的類的構(gòu)造函數(shù),這符合邏輯嗎?再考慮一下基數(shù)構(gòu)造函數(shù)有參數(shù)的的時候,派生類構(gòu)造函數(shù)的定義形式,“派生類構(gòu)造函數(shù)可以使用初 始化列表機制將值傳遞給基類構(gòu)造函數(shù)”(《C++ Primer Plus(第四版)中文版》第399頁)。如果是基類的構(gòu)造函數(shù)先被調(diào)用,那么它所使用的參數(shù)從何而來?

前兩本書在說明這一規(guī)則時,毫無例外地在派生類構(gòu)造函數(shù)和基類構(gòu)造函數(shù)中使用cout輸出一些信息來表明相應的構(gòu)造函數(shù)被調(diào)用了,并以此說明構(gòu)造函數(shù)的調(diào) 用順序。在這里,我要指出的是:這一順序,僅僅是這些cout輸出的順序,并不能說明是函數(shù)調(diào)用的順序。真正調(diào)用的過程,單純依賴于C++是看不到的。

我們可以用這樣的實驗來證明這一點。選擇前兩本書關(guān)于這一規(guī)則的任何一個實例,在Visual Studio中,分別對派生類和基類的構(gòu)造函數(shù)下斷點,注意:斷點要下在函數(shù)定義函數(shù)名處,這樣才是真正函數(shù)執(zhí)行的起點,而不能下在cout語句上,那是 函數(shù)體,不能說明問題。然后調(diào)試這個程序,你會發(fā)現(xiàn)派生類構(gòu)造函數(shù)的斷點先中斷,基類的構(gòu)造函數(shù)斷點后中斷。如果你有匯編的知識,那么請打開匯編語言的開 關(guān),這之間的關(guān)系就更明顯了。

現(xiàn)在可以更確切地說明這個規(guī)則了:

  • 派生類對象在實例化時,派生類構(gòu)造函數(shù)先被執(zhí)行,在執(zhí)行過程中(在實例化派生類成員前),調(diào)用基類構(gòu)造函數(shù),然后(在基類成員實例化后)返回派生類構(gòu)造函數(shù)實例化派生類成員。

  • 析構(gòu)函數(shù)的順序問題依此類推。

4.代碼

  • 缺省構(gòu)造函數(shù)的調(diào)用關(guān)系
#include <iostream> 
#include <string> 
using namespace std;  

class CBase {
  string name;     
  int age; 
public:     
  CBase() { cout << "BASE" << endl;  }     
  ~CBase() {         cout << "~BASE" << endl;     } 
};  

class CDerive : public CBase { 
public:     
  CDerive() {cout << "DERIVE" << endl;     }     
  ~CDerive() {          cout << "~DERIVE" << endl;     } 
}; 

int main ( )  {     
  CDerive d;      
  return 0; 
}
BASE
DERIVE
~DERIVE
~BASE
(int)0
  • 有參數(shù)時的傳遞
#include <iostream>
#include <string> 
using namespace std;  

class CBase {
string name; 
public:     
  CBase(string s) : name(s) 
  { 
    cout << "BASE: " << name << endl;    
  }     
  ~CBase() {         cout << "~BASE" << endl;     } 
};

class CDerive : public CBase {
int age; 
public:     
CDerive(string s, int a) : CBase(s), age(a) {          cout << "DERIVE: " << age << endl;     }     
~CDerive() {          cout << "~DERIVE" << endl;     } };  

int main ( )  {     CDerive d("John", 27);      return 0; }
BASE: John
DERIVE: 27
~DERIVE
~BASE
(int)0
  • 祖孫三代的參數(shù)傳遞
#include <iostream> 
#include <string> 
using namespace std;  

class CBase {     
string name; 
public:     CBase(string s) : name(s) {cout << "BASE: " << name << endl;     }     
~CBase() {         cout << "~BASE" << endl;     } 
};  

class CDerive : public CBase {
 int age;
 public:     CDerive(string s, int a) : CBase(s), age(a) { cout << "DERIVE: " << age << endl;     }     
~CDerive() {          cout << "~DERIVE" << endl;     } 
};  

class CSon : public CDerive { 
string id; 
public:     
CSon(string s1, int a, string s2) : CDerive(s1, a), id(s2) {cout << "SON: " << id << endl;     }     
~CSon() {          cout << "~SON" << endl;     } 
};  

int main ( )  {     CSon s("John", 27, "8503026");      return 0; }
BASE: Jhon
DERIVE: 27
SON: 8503026
~SON
~DERIVE
~BASE
(int)0
  • 多重繼承的參數(shù)傳遞
    多重繼承時參數(shù)的傳遞方法和上面一樣,要注意的是兩個基類的順序。決定2個基類的順序是知27行。
    將27行的CBase1和CBase2的順序交換一下,其結(jié)果中BASE1和BASE2的順序也隨之改變,與第30行無關(guān)。
#include <iostream> 
#include <string> 
using namespace std;  

class CBase1 {
string name; 
public:     
CBase1(string s) : name(s) { cout << "BASE1: " << name << endl;     }     
~CBase1() {         cout << "~BASE1" << endl;     } 
};  

class CBase2 {     
int age;
 public:     CBase2(int a) : age(a) {         cout << "BASE2: " << age << endl;     }     
~CBase2() {         cout << "~BASE2" << endl;     } 
};  

class CDerive : public CBase1, public CBase2 {     
string id; 
public:     CDerive(string s1, int a, string s2) : CBase1(s1), CBase2(a), id(s2) {          cout << "DERIVE: " << id << endl;     }     
~CDerive() {          cout << "~DERIVE" << endl;     }
 };  

int main ( )  {     CDerive d("John", 27, "8503026");      return 0; }
BASE1: John
BASE2: 27
DERIVE: 8503026
~DERIVE
~BASE2
~BASE1
(int)0
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容