之所以寫這篇文章,主要是為了回答網友 zhancaihua123同學的下面幾個問題:
father* p=new son;
p->disp(...);
father是父類,son是子類。disp是一個子類重定義過的虛函數。
問題一:p->disp(...);是不是可以寫成p->disp(p,...);
問題二:p的類型是不是father*
問題三:子類disp函數的this指針是不是son*類型的
問題四:如果第三的問題回答“是”請回答第四題,回答“不是”的,請回避!
那么如果disp函數的this指針是 son*類型的,用p調用disp的時候,我們將p的值傳遞給disp的this指針,那么既然p是father*類型的,而this是son*類型的,則上述操作就相當於把父類的指針轉換為子類指針,這不是與父類指針不能自動(隱式)轉換為子類指針矛盾么?
看回答,發現有好多人this指針的本質有錯誤的認識,估計不少人一說起this指針,腦袋立即反應出:那個類里使用的this指針,那么這個指針就是那個類類型咯。其實事實根本不是這樣子的,這里修正對this指針的錯誤理解:
首先,zhancaihua123同學,我在這里重申一點:“this”不是變量,是關鍵字,意味着this指針並不是哪個真實存在的符號/儲存空間。所以,this指針沒有C++語言范疇里的變量類型。所以我曾說的“子類disp函數的this指針是son*類型”這個說法是錯的。this指針即不能說是son*類型也不能說是"father"類型。
當然,可能不少同學因此反駁,我在IDE里敲入“this->”后會自動彈出類的成員變量啊,怎么就說this沒有類型呢?確實,this指針多多少少跟所在類的類型有關。因此,我也對this指針使用“類型”一詞,但是這里“類型”包含下面的兩層含義:
- 起始地址(嚴格來說應該是0偏移地址)。
- 涵蓋范圍/尋址范圍。
因為在機器層面,變量(其實壓根就沒有什么變量,該叫操作數)除了整型、浮點型之分外,是沒有類型的,更沒有類成員一說,只有尋址。據此,再進一步說明之前先介紹兩個基本常識:(一下全部在X86-64機器VC++編譯器范圍類討論)
======================================基本常識分界線=====================================
基本常識1:
類成員是如何訪問的?(注:沒有特別說明,都不包括靜態成員)
當C++被編譯成二進制代碼后,不考慮什么導出符號、未定義符號之類的,什么變量名就統統消失,那么要訪問這些變量,就得通過機器層面的方式:如以某個地址作為標准/基准(起始地址),通過距離這個基准一定量來定位要訪問的變量。對於類成員來說,一般是對象首地址+偏移。比如以下這個類:
class A { int a; int b; virtual void F(); }
他的一個對象aa在內存中的分布如下:(為什么起始地址為F0H?沒有為什么,隨便定的)
那么假設對象aa的首地址(F0H)保存在寄存器rax中,那么成員a的訪問方式為:
rax+8h
同理,成員b的訪問方式:
rax+Ch
基本常識2:
什么是繼承?
比如如下定義:
struct IA { int a; virtual void F(); void FF(); }; struct B { int b; virtual void G()=0; }; class Derived:public B,public IA { public: int d; virtual void F(); void FF(); virtual void G(); };
那么假設有Derived d,d對象的內存分布圖如下:
好了,簡介完兩個基本常識,開始講this指針的“類型”。
========================================this指針的類型=================================
this指針跟不少人想象的不一樣,它的類型由被調用函數決定。它的類型遵循着這兩點規則:(途中打勾為首地址。)
1.對於非虛函數,this指針的基准地址為函數定義所在層級對象的首地址,范圍為該層級對象始末。
如:IA::FF();
this指針類型是以D0H為首地址,范圍是從首地址開始到DFH為止。(其實里面有內存空洞,我們不去糾結這個)
Derived::FF();
this指針類型是C0H為首地址,范圍是從首地址到E8H為止。
2.對於虛函數,this指針的基准地址為函數首先聲明者的首地址,范圍為實現者的始末。
如:IA::F();
其this指針類型是以D0H為首地址,范圍是從首地址開始到DFH為止。
Derived::F();
其this指針類型是以D0H為首地址,范圍是從C0H到E8H為止。
那么當有IA* a=new Derived();后,
a->F();便是這么訪問b成員的了。假設首地址D0H保存在寄存器rax里,
rax-8
========================================結案陳詞======================================
好了,這里可以最后總結回答原問題三及其新問的“如果用對象指針調用函數,那么到底是指針傳遞給this還是&object傳遞給this?”
之所以有 zhancaihua123同學所疑惑的“this指針到底是father*還是son*”,秘密就在這里。之所以覺得它像是father*,是因為形如IA* a=new Derived();a所存放的地址是D0H,通過a所能直接訪問的范圍限於上面的橙色區域,並且當調用F()時,所傳的地址、this指針的值就是a的值D0H。而又覺得它是son*,是因為在F()內部,通過this指針可以訪問的范圍值整個子類對象。於是乎就讓人覺得有父類指針隱式轉換為子類指針之嫌。
順便解答了 zhancaihua123同學第二個問題,就是傳給this指針的肯定是a的值即對象指針的值。
=========================================鳴謝========================================
有人說,提出好的問題,等於解決了問題的一半。有些問題,我們沒留意到,沒認真想過,所以通常都是想當然。正是 zhancaihua123同學所發問,引起我的思考,才去寫代碼檢驗自己的想法。所以,最該感謝的,就是提出問題的zhancaihua123同學。