我們先來(lái)看一個(gè)代碼,這是在繼承與虛函數(shù)學(xué)生過(guò)程中發(fā)生的一個(gè)錯(cuò)誤,涉及到了C++的對(duì)象內(nèi)存的知識(shí),因?yàn)檫@方面知識(shí)比較復(fù)雜,這里不做過(guò)多的介紹,只簡(jiǎn)單分析一下出錯(cuò)原因。
class Base
{
public:
void fun()
{
Cout << “Base::fun()” << endl;
}
~Base() //虛析構(gòu)函數(shù)
{
cout << ”Base::~Base” << endl;
}
};
class Child:public Base //繼承Base類(lèi)
{
Public:
virtual void fun() //虛函數(shù)
{
Cout << “Child:fun()” <<endl;
}
};
int main()
{
Base *p = new Child; //新建一個(gè)子類(lèi)的對(duì)象,賦值給一個(gè)父類(lèi)指針
p->fun();
delete p; //通過(guò)父類(lèi)指針釋放內(nèi)存
return 0;
}
VS運(yùn)行程序時(shí),發(fā)生如下錯(cuò)誤:
通過(guò)提示發(fā)現(xiàn),VS提示應(yīng)該是內(nèi)存方面的錯(cuò)誤。而且對(duì)于上面的代碼來(lái)說(shuō),父類(lèi)中的fun函數(shù)不是虛函數(shù),而子類(lèi)中的fun函數(shù)是虛函數(shù),所以p->fun也是會(huì)調(diào)用父類(lèi)的fun函數(shù)。
那么為什么會(huì)出現(xiàn)內(nèi)存釋放的錯(cuò)誤呢?重新寫(xiě)一個(gè)main函數(shù)如下:
int main()
{
Child *c = new Child; //在堆上創(chuàng)建一個(gè)子類(lèi)對(duì)象
Base *p = c; //將子類(lèi)對(duì)象指針賦值給一個(gè)父類(lèi)對(duì)象指針
cout << c << " " << p << endl; //打印信息
p->fun(); //通過(guò)父類(lèi)指針來(lái)調(diào)用fun函數(shù)
delete p; //通過(guò)父類(lèi)指針釋放內(nèi)存
return 0;
}
再次運(yùn)行以上代碼,結(jié)果如下:
我們從打印的信息可以看到,Child對(duì)象指針值為0x01393FD0,Base對(duì)象指針為0x1393FD4,通過(guò)打印信息發(fā)現(xiàn)兩個(gè)值并不一樣,這樣釋放內(nèi)存時(shí)產(chǎn)生了錯(cuò)誤。
原因如下:
-
存在虛函數(shù)的類(lèi)對(duì)象中會(huì)隱藏了一個(gè)虛指針,虛指針存放在對(duì)象內(nèi)存的開(kāi)始位置,這里子類(lèi)中有一個(gè)虛指針而父類(lèi)中沒(méi)有;
-
子類(lèi)中有一塊內(nèi)存布局和父類(lèi)對(duì)象的內(nèi)存布局是一樣的,但是這塊內(nèi)存肯定不是子類(lèi)對(duì)象的起始位置,所以將子類(lèi)對(duì)象指針賦值給一個(gè)父類(lèi)對(duì)象指針時(shí),為了操作上不產(chǎn)生錯(cuò)誤,會(huì)把這塊和父類(lèi)內(nèi)存布局相同的位置賦值給父類(lèi)指針,因而發(fā)生了內(nèi)存的偏移;
-
在堆上分配的子類(lèi)對(duì)象內(nèi)存,如上面代碼起始地址是0x01393FD0,這里是通過(guò)delete父類(lèi)指針來(lái)釋放內(nèi)存,而父類(lèi)指針的值為0x1393FD4,這樣釋放內(nèi)存中檢測(cè)不是正確的起始位置而發(fā)生了錯(cuò)誤。
解決方法:
要解決以上問(wèn)題,就要想辦法讓子類(lèi)指針賦值給父類(lèi)指針時(shí),兩個(gè)指針的值是一個(gè)樣,這里我們可以在父類(lèi)中設(shè)置任意一個(gè)虛函數(shù)(將父類(lèi)中的fun設(shè)置為虛函數(shù)或者將父類(lèi)的析構(gòu)函數(shù)設(shè)置為虛函數(shù)),這樣父類(lèi)和子類(lèi)中都有虛指針,賦值時(shí)不會(huì)發(fā)生地址的偏移。
為了防止出現(xiàn)以上錯(cuò)誤,我們代碼中一定要多加注意。盡量不要在子類(lèi)中設(shè)置虛函數(shù)和父類(lèi)中的普通函數(shù)重名,另外還有一個(gè)編碼小技巧,如果一個(gè)類(lèi)中有虛函數(shù)或者純虛函數(shù)時(shí),要將其析構(gòu)函數(shù)設(shè)置為虛析構(gòu)函數(shù),以防發(fā)生內(nèi)存泄漏等問(wèn)題。
本文版權(quán)歸黑馬程序員C/C++培訓(xùn)學(xué)院所有,歡迎轉(zhuǎn)載,轉(zhuǎn)載請(qǐng)注明作者出處。謝謝!作者:黑馬程序員C/C++培訓(xùn)學(xué)院首發(fā):http://m.409rqu1.cn/news/c.html