/*************************************************/
/*多肽與繼承的測試*/
/*************************************************/
#include <iostream>
using namespace std;
class A
{
public:
A() { cout<<"Create A"<<endl; }
virtual ~A() { cout<<"Destroy A"<<endl; }
virtual void Print() { cout<<"Print A"<<endl; }
}; // 注意這裏要有分號,我就犯了這個錯誤。
class B : public A
{
public:
B() { cout<<"Create B"<<endl; }
~B() { cout<<"Destroy B"<<endl; }
virtual void Print() { cout<<"Print B"<<endl; }
};
int main(int argc, char* argv[])
{
B *a = new B(); // [1]或者 A *a = new B();
// a->Print(); // [2]
delete(a); // [3]
a = NULL; // [4]
system("pause");
return 1;
}
先看上面的代碼:
在寫下面的內容之前,我覺得首先需要提出:
學習時不要怕犯低級錯誤,每次犯錯,都是一次積累。譬如,在上面代碼裏,我曾就在定義了class A和class B之後忘記了加分號,導致編譯不通過,還找了一會兒才發現這個問題。再如,我在[1]這裏創建對象時,曾經寫成:
B b = new A();
這裏一共犯了兩個錯誤,這是JAVA的寫法。
(1).在c++裏面,new返回的是指針,所以B b應該是B *b纔對。
(2).第二個錯誤,編譯器會告訴我,不能將對象A轉換成對象B,B繼承了A,如果通過new A()當然不能創建對象B,只能通過B來創建A,可以這樣來理解,因爲B包括了A的所有東西,所以通過類A來創建B,必然會導致B的一些屬性殘缺。正確的應該是: A *a = new B();
我想要分享的重點在於以下三點:
1.關於子類和父類構造與虛構調用順序一句話原則:
構造調用是由上至下(先父類後子類),析構的調用是由下至上(先子類後父類)。
2.虛析構的意義。
3.遇到不能理解的東西,最好的辦法實踐。
----------------------------------------------------------------------------------------------------------------------------------------------
運行上面的代碼,得到的結果如下:
這驗證了第一點:構造的調用順序總是先調用基類,後調用派生類。對象由底層開始創建,由上層開始析構。所以析構的調用順序和構造調用順序是嚴格逆反的。
如果我們把代碼裏class A的析構前的 virtual 關鍵字去掉,再運行一下,結果是這樣的:
沒看錯!沒有發生變化。哈哈,別急,接下來,我們把代碼裏[1]這段代碼替換一下:
B *a = new B(); ----> A *a = new B();
然後再運行一下:
這下不一樣了!是的,派生類B的析構函數沒有被調用!這樣很可能出現:因爲沒有正確析構而導致內存泄露的問題。
我們再看看百度百科裏,對於虛析構函數的介紹:
這裏:A *a = new B(); a是指向基類A的指針,再析構時,只調用了基類的析構函數,但沒有調用派生類B的析構函數,加入了關鍵字 virtual 後,才能保證只想基類的指針能正確被刪除。
好了。這次想要分享的內容差不多就這些了。最後一條是我的切身體會,很多看不懂的東西,拿來實踐一番,一下就明白了。希望這些東西能給大家帶來幫助!
另外分享一點別人寫的關於虛析構的說明,從實現上說明了原因:
虛函數和普通成員函數的區別,是虛函數放在虛函數表中,通過對象的this指針找到該類的虛函數表,然後調用。C++即採用此機制實現多態。如果是普通函數,每個函數的地址是死的。所以用A類的對象調用析構函數時只能調到A的析構。如果是虛函數,則會通過指針找到B的析構函數,而B繼承自A,還會調用A的析構函數。
——摘自:http://blog.csdn.net/cffy625/article/details/5225064