C++学习之继承篇
今天通過(guò)對(duì)實(shí)驗(yàn)二繼承,重載,覆蓋的學(xué)習(xí),讓我更深一步理解了這些概念的區(qū)別。
首先來(lái)明確一個(gè)概念,函數(shù)名即地址,也就是說(shuō)函數(shù)名就是個(gè)指針。
編譯階段,編譯器為每個(gè)函數(shù)的代碼分配一個(gè)地址空間并編譯函數(shù)代碼到這個(gè)空間中,函數(shù)名就指向這個(gè)地址空間。
也即每個(gè)函數(shù)名都有自己唯一的代碼空間。
同理,類的成員函數(shù)也是如此。
但是,有一點(diǎn)大家一定要記住,C++編譯器編譯CPP文件時(shí),會(huì)根據(jù)"C++編譯器的函數(shù)名修飾規(guī)則" 對(duì)函數(shù)名進(jìn)行修飾。
(修飾規(guī)則大家自己去搜吧,我就不敘述了),前面講到函數(shù)名稱的作用是指向函數(shù)真實(shí)代碼的指針。
知道了以上規(guī)則,那么我們對(duì)函數(shù)覆蓋便不難理解了。
首先來(lái)看看百度百科中函數(shù)覆蓋的中文描述是:
函數(shù)覆蓋發(fā)生在父類與子類之間,其函數(shù)名、參數(shù)類型、返回值類型必須同父類中的相對(duì)應(yīng)被覆蓋的函數(shù)嚴(yán)格一致,
覆蓋函數(shù)和被覆蓋函數(shù)只有函數(shù)體不同,當(dāng)派生類對(duì)象調(diào)用子類中該同名函數(shù)時(shí)會(huì)自動(dòng)調(diào)用子類中的覆蓋版本,
而不是父類中的被覆蓋函數(shù)版本,這種機(jī)制就叫做函數(shù)覆蓋。
我們來(lái)寫(xiě)一段函數(shù)覆蓋的代碼
class father
{
public:
void fun()
{cout<<"father's fun"<<endl;}
};
class son:public father
{
public:
void fun()
{cout<<"son's fun"<<endl;}
};
void main()
{
father Father,*pFather;
son Son,*pSon;
int i = sizeof(Father); //此行代碼過(guò)后,i=1,此行代碼無(wú)意義只是讓大家知道普通成員函數(shù)不占用類的實(shí)例空間。
// Father.fun(); //此行注釋與下行注釋是正常的調(diào)用函數(shù)覆蓋相信大家都能理解,所以不在解釋
// Sun.fun();
pSon = (son*)&Father; //子類指針指向父類實(shí)例是危險(xiǎn)的,此例中并沒(méi)涉及到任何越界,并且為了展示區(qū)別
//所以才這樣使用,但大家要明白,這樣做是危險(xiǎn)的。
pFather = (father*)&Son;
pSon->fun(); //函數(shù)調(diào)用執(zhí)行了father's fun
pFather->fun(); //函數(shù)調(diào)用執(zhí)行了son's fun
}
此時(shí)有些人可能就不能理解為什么會(huì)出現(xiàn)這種調(diào)用結(jié)果了,那么大家是否還記得我上面曾提到的"C++編譯器的函數(shù)名修飾規(guī)則"?
我們來(lái)根據(jù)"C++編譯器的函數(shù)名修飾規(guī)則"再來(lái)想想原因。
根據(jù)規(guī)則,編譯器把父類father中的fun函數(shù)名編譯為"?fun@father@@QAEZ",子類son中的fun函數(shù)名編譯為"?fun@son@@QAEZ"。
當(dāng)pSon->fun();調(diào)用時(shí),編譯器會(huì)把pSon所存地址值的類型轉(zhuǎn)化成當(dāng)前指針類型,而pSon的當(dāng)前類型為son(這句話不多余,
因?yàn)轭愋褪强梢噪S意轉(zhuǎn)換的),
所以表達(dá)式"pSon->fun()"全部展開(kāi)以后得到的函數(shù)名稱即"?fun@father@@QAEZ"
同理表達(dá)式"pFather->fun()"全部展開(kāi)以后得到的函數(shù)名稱即"?fun@son@@QAEZ",
既然得出了函數(shù)名,那么也就可以根據(jù)函數(shù)名稱,跳轉(zhuǎn)到真實(shí)的函數(shù)代碼實(shí)體位置了。
根據(jù)以上分析,我覺(jué)得"函數(shù)覆蓋(英文名不知到叫什么只能用中文了)"這個(gè)詞匯真的容易把人帶入歧途,
我的語(yǔ)文又不好,所以還希望哪位語(yǔ)文好的兄弟,來(lái)重新翻譯下"函數(shù)覆蓋"這個(gè)詞匯。
呃,真費(fèi)勁啊,函數(shù)覆蓋算是講完,不知道大家有每有看懂,如果還看不懂的話,我是真沒(méi)招了。
下面在來(lái)講個(gè)虛函數(shù)吧。
?
有了前面的基礎(chǔ),大家應(yīng)該對(duì)函數(shù)有了充分的了解。
那么問(wèn)大家一個(gè)問(wèn)題,為什么一個(gè)類實(shí)力化后普通成員函數(shù)不影響實(shí)例的大小?
呵呵,如果一個(gè)新手能回答出來(lái),那我這篇東西就不算白寫(xiě)。
正確答案嘛,是因?yàn)椴恍枰鼇?lái)影響實(shí)例大小,因?yàn)榫幾g器會(huì)根據(jù)"C++編譯器的函數(shù)名修飾規(guī)則"與"表達(dá)式的地址類型",
自動(dòng)的把成員函數(shù)展開(kāi)成完整的函數(shù)名,也就找到了函數(shù)的真實(shí)地址,所以普通成員函數(shù)是不影響實(shí)力大小的。
我再來(lái)問(wèn)大家一個(gè)問(wèn)題,你們認(rèn)為虛函數(shù)需不需要影響類的實(shí)例大小?
哈哈,這次的答案是需要。
這次我們來(lái)寫(xiě)一段虛函數(shù)的代碼瞧瞧 因?yàn)橹挥性趯?shí)例內(nèi)部添加一個(gè)指針,才能夠完成例如,
class father
{
public:
virtual void fun()
{cout<<"father's fun"<<endl;}
};
class son:public father
{
public:
void fun()
{cout<<"son's fun"<<endl;}
};
void main()
{
father Father,*pFather;
son Son,*pSon;
int i = sizeof(Father); //此行代碼過(guò)后,i=4,此行代碼無(wú)意義只是讓大家知道虛函數(shù)占用類的實(shí)例空間。
Father.fun(); //函數(shù)執(zhí)行結(jié)果 "father's fun"
Son.fun(); //函數(shù)執(zhí)行結(jié)果 "son's fun" 這句簡(jiǎn)單點(diǎn)說(shuō),就是很多人說(shuō)的動(dòng)態(tài)綁定,我們下面會(huì)具體分析。
pSon = (son*)&Father; //再次強(qiáng)調(diào),子類指針指向父類實(shí)例是危險(xiǎn)的,一旦越界操作,就會(huì)引發(fā)異常。
pFather = (father*)&Son;
pSon->fun(); //函數(shù)執(zhí)行結(jié)果 "father's fun"
pFather->fun(); //函數(shù)執(zhí)行結(jié)果 "son's fun" 這兩句也是動(dòng)態(tài)綁定,相信還是有不少人不理解,下面具體分析。
}
恩,大家需要調(diào)式一下上面的程序,對(duì)Father添加監(jiān)視,你會(huì)發(fā)現(xiàn)Father實(shí)例中莫名其妙的多了一個(gè)vftable類型指針對(duì)象vfptr。
是了,虛函數(shù)的實(shí)現(xiàn)靠的就是這個(gè)東西了。
IED幫我們?cè)谖覀兊膶?shí)例外部實(shí)例化了一個(gè)vftable對(duì)象(我知道這句話很繞,但是我不知道該怎么更好的解釋了),
同時(shí)為我們的實(shí)例Father添加了一個(gè)指向vftable對(duì)象的指針vfptr。
我們繼續(xù)把監(jiān)視中的指針vfptr展開(kāi),可以看到一個(gè)叫[0]的函數(shù)指針(別問(wèn)我這名為啥長(zhǎng)成這樣,我也沒(méi)搞清楚),
哈哈,找到了,這里存儲(chǔ)了一個(gè)地址,這個(gè)地址就是一個(gè)函數(shù)真實(shí)地址(如果用的VS2010編譯器,你會(huì)直觀的看到這個(gè)地址
所對(duì)應(yīng)的是father::fun這個(gè)函數(shù))。
然后,我再來(lái)明確一個(gè)虛函數(shù)規(guī)則,就是當(dāng)你的實(shí)例調(diào)用虛函數(shù)時(shí),最終調(diào)用的就是這個(gè)vftable類型的成員[0]所存儲(chǔ)地址。
那么好了,我們知道子類是完全繼承父類的,所以那個(gè)vftable類型指針對(duì)象vfptr也同時(shí)被繼承了下來(lái)。
IDE同樣為我們實(shí)例化一個(gè)vftable對(duì)象,讓vfptr來(lái)指向這個(gè)vftable對(duì)象。
而如果我們的子類重寫(xiě)了虛函數(shù),那么IDE在實(shí)例化vftable對(duì)象時(shí),就會(huì)把[0]這個(gè)指針重寫(xiě)為新的子類中那個(gè)虛函數(shù)地址。
如果我們的子類沒(méi)有重寫(xiě)這個(gè)虛函數(shù),那么IDE就會(huì)找到距離這個(gè)子類關(guān)系最近的一個(gè)實(shí)現(xiàn)了虛函數(shù)的父類,
把這個(gè)父類中的虛函數(shù)地址,寫(xiě)入到子類的[0]中。
這樣子類在調(diào)用虛函數(shù)的時(shí)候,就可以實(shí)現(xiàn)動(dòng)態(tài)綁定了。
另外父類指針指向子類實(shí)例時(shí),因?yàn)橛辛藇fptr指針占位,所以當(dāng)父類指針調(diào)用虛函數(shù)時(shí),尋址到的vfptr是子類實(shí)例的。
而子類的vfptr指向子類自己的vftable對(duì)象,所以父類最終調(diào)用的會(huì)是子類對(duì)象的中[0],所以[0]中存的是哪個(gè)函數(shù)地址。
父類指針最終調(diào)用的就會(huì)是哪個(gè)函數(shù)了。
轉(zhuǎn)載于:https://www.cnblogs.com/luoyunjian/p/4480712.html
總結(jié)
- 上一篇: Java 基本类型相互转换
- 下一篇: 如何创建C++程序