今天收到盛大的面試,問我一個問題,關於派生類中如何初始化基類對象,我在想派生類對於構造函數不都是先構造基類對象,然后在構造子類對象,但是如果我們在成員初始化列表先初始化派生類的私有成員,在函數內去調用基類的構造函數,能編譯通過嗎?或者當我們定義了基類的默認構造函數,而沒有去在派生類的構造函數中顯示的去調用基類的構造函數,會出現什么狀況,我想派生類肯定會自動去調用基類的默認構造函數,那么析構函數又怎么樣呢?我們都知道派生類的析構函數會先被調用,然后基類的析構函數后被調用,但是我不知道我們是否需要在派生類的析構函數中顯示的去調用基類的析構函數嗎?這個有待我去驗證。
代碼一:在派生類中成員初始化列表先初始化派生類的私有成員,不顯示的調用基類的構造函數
#include <iostream> using namespace std; class Base { private: int n; public: Base(int m):n(m){ cout<<"constructor is called\n";} ~Base(){} }; class Derive:public Base { private: int n; public: Derive(int m):n(m) { } ~Derive(){} }; int main() { Derive* a = new Derive(10);return 0; }
結果:編譯錯誤,error C2512: “Base”: 沒有合適的默認構造函數可用
代碼二:在派生類中成員初始化列表先初始化派生類的私有成員,顯示的調用基類的構造函數
#include <iostream> using namespace std; class Base { private: int n; public: Base(){ cout<<"default constructor is called\n"; n = 8;} Base(int m):n(m){ cout<<"constructor is called\n";} ~Base(){} }; class Derive:public Base { private: int n; public: Derive(int m):Base(m),n(m) { } ~Derive(){} }; int main() { Derive* a = new Derive(10);return 0; }
運行結果:
代碼三:在派生類中成員初始化列表先初始化派生類的私有成員,不顯示的調用基類的構造函數,則會調用默認的構造函數
#include <iostream> using namespace std; class Base { private: int n; public: Base(){ cout<<"default constructor is called\n"; n = 8;} Base(int m):n(m){ cout<<"constructor is called\n";} ~Base(){} }; class Derive:public Base { private: int n; public: Derive(int m):n(m) { } ~Derive(){} }; int main() { Derive* a = new Derive(10); return 0; }
運行結果:
代碼四:派生類析構函數的調用過程中會不會自動去調用基類的析構函數呢?答案是,肯定的,所以千萬不要在派生類的析構函數中再去調用基類的析構函數,這種去釋放已經釋放的內存,系統是不允許的。
#include <iostream> using namespace std; class Base { private: int n; public: Base(){ cout<<"default constructor is called\n"; n = 8;} Base(int m):n(m){ cout<<"constructor is called\n";} ~Base(){ cout<<"Base distructor is called\n"; } }; class Derive:public Base { private: int n; public: Derive(int m):Base(m),n(m) { } ~Derive(){ cout<<"Derive distructor is called\n"; } }; int main() { Derive* a = new Derive(10); delete a; return 0; }
運行結果:
代碼5:如果我們去試試在派生類的析構函數中去調用基類的析構函數,看看結果如何?當我想這么做的時候,我突然發現這個代碼我寫出來,因為我們都知道,對於C++的一個對象要么將對象分配在棧中,要么將對象分配在堆中,而對於分配在棧中的對象我們過程結束后,自動調用類的析構函數,而分配在堆中的對象,得我們去delete,但是你必須拿到指向對象在堆中內存的句柄,也就是指針,但是我發現不可能拿得到,除非你在派生類的構造函數中去new基類對象,但是又有個問題,在派生類構造函數中去new出這個基類對象,那么基類對象是派生類的局部變量,還是派生類繼承而來的呢?我發現肯定是派生類的局部變量,那么也就是說,如果new出一個派生類對象,那么派生類本身的私有成員是在堆中,而繼承而來的屬性,也就是基類的東西分配的棧中,好吧,這樣,難道派生對象在內存上竟然不是連續的?
#include <iostream> using namespace std; class Base { private: int n; public: Base(){ cout<<"default constructor is called\n"; n = 8;} Base(int m):n(m){ cout<<"constructor is called\n";} ~Base(){ cout<<"Base distructor is called\n"; } }; class Derive:public Base { private: int n; public: Derive(int m):Base(m),n(m) // 在這里構造繼承屬性,即派生類的基類部分 { new Base(m); //這個僅僅在派生類中創建了一個基類的變量而已 } ~Derive(){ cout<<"Derive distructor is called\n"; } }; int main() { Derive* a = new Derive(10); delete a; return 0; }
運行結果如下:
構造兩次基類,一次構造派生類中的基類成分,還有一次僅僅是派生類的變量而已。同時析構派生類,自動調用基類析構函數。
代碼六:在派生類構造函數中用new分配一個基類對象,然后析構掉,在main函數中去調用基類的成員函數,發現仍可調度,說明在派生類構造函數中用new分配基類對象,不是派生類的基類成分。
#include <iostream> using namespace std; class Base { private: int n; public: Base(){ cout<<"default constructor is called\n"; n = 8;} Base(int m):n(m){ cout<<"constructor is called\n";} void get(){ cout<<"get() is called\n"; } ~Base(){ cout<<"Base distructor is called\n"; } }; class Derive:public Base { private: int n; public: Derive(int m):Base(m),n(m) // 在這里構造繼承屬性,即派生類的基類部分 { Base* a =new Base(m); //這個僅僅在派生類中創建了一個基類的變量而已 delete a; } ~Derive(){ cout<<"Derive distructor is called\n"; } }; int main() { Derive* a = new Derive(10); a->get(); delete a; return 0; }
運行如下:
下面我就有一個疑問了,派生類中只能將基類成分分配在棧中嗎?而如果去new出派生類對象,那么豈不內存不連續了,這是個問題?我驗證了一下,與我的想法並非一致,內存還是連續的。只不過在派生類構造基類的過程中(new出派生類方式),只是在堆中去分配基類的成分,雖然使用非new來創建基類對象。
代碼如下:
#include <iostream> using namespace std; class Base { public: int n; public: Base(){ cout<<"default constructor is called\n"; n = 8;} Base(int m):n(m){ cout<<"constructor is called\n";} void get(){ cout<<"get() is called\n"; } ~Base(){ cout<<"Base distructor is called\n"; } }; class Derive:public Base { private: int n; public: Derive(int m):Base(m),n(m) // 在這里構造繼承屬性,即派生類的基類部分 { cout<<"Base address: "<<&(Base::n)<<endl; //地址 cout<<"Derive address: "<<&n<<endl; //地址 } ~Derive(){ cout<<"Derive distructor is called\n"; } }; int main() { Derive* a = new Derive(10); delete a; return 0; }
運行結果如下:
以上可以看出地址連續,說明派生類中基類成分和派生類成分地址是連續的。