虛函數、普通成員函數訪問類的資料成員:
class A
{
protected:
int m_data;
public:
A(int data = 0){ m_data = data; }
int GetData(){ return doGetData();}
virtual int doGetData(){ return m_data;/*m_data =0 */}
};
class B:public A
{
protected:
int m_data;
public:
B(int data = 1){ m_data = data; }
//這裡A 中的m_data = 0 ,B中的m_data = 1
int doGetData(){ return m_data ;/*m_data =1 */} //實現介面
};
class C:public B
//C繼承了A&B類的方法&屬性,且未從新定義介面,故介面還是B類中定義的
{
protected:
int m_data;
public:
C(int data = 2){ m_data = data; }
//這裡A 中的m_data = 0 ,B中的m_data = 1,C 類中的m_data = 2
};
int main()
{
C c( 10);
cout<< c.GetData()<< endl; //輸出為:1
cout<< c.A::GetData()<< endl;//輸出為:1
cout<< c.B::GetData()<< endl; //輸出為:1
cout<< c.C::GetData()<< endl; //輸出為:1
cout<< c.doGetData()<< endl; //輸出為:1
cout<< c.A::doGetData()<< endl; //輸出為:0
cout<< c.B::doGetData()<< endl; //輸出為:1
cout<< c.C::doGetData()<< endl; //輸出為:1
system("pause");
return 0;
}
分析:GetData()為普通成員函數,doGetData()為虛函數。而且,虛函數也像普通成員函數一樣,在最終調用時都要加this指標,
如A類的
int doGetData(){ return m_data ; }其實加this指標後,實際為:
virtual int doGetData( A* const this){ return this->m_data ; },
如果不習慣用參數this,可以換成別的參數:
virtual int doGetData( A* const pa){ return pa->m_data ; }―――(代碼1)
而A類的普通成員函數GetData()也有this指標,即A類的
int GetData(){ return doGetData();} 實際為:
int GetData(A* const pa){ return
pa->doGetData();}――――(代碼2)
//注意,pa->doGetData()執行的是虛函數,有多態。
(1)c.GetData():C類未定義GetData(),則調用父類B的GetData(),B類中也未定義,故調用祖父類A的GetData(),即GetData( A* const pa),其中pa指向C類的對象c(實際上相當於調用GetData( C* const pc),但由於C未定義GetData(),故無GetData( C* const pc),則調用父類的GetData(),即GetData( B* const pb),B類中也未定義,故調用祖父類A的GetData(),即GetData(
A* const pa),即按GetData( C* const pc)、GetData( B* const pb)、GetData( A* const pa)的順序尋找調用的函數)。
然後執行(代碼2)中的pa->doGetData();由於doGetData()是虛函數,故執行虛函數表中的doGetData(),即會有多態,即調用C::doGetData(),而C::doGetData()未定義,故調用父類B的doGetData(),即
virtual int doGetData( B* const pb){ return pb->m_data ; }―――(代碼3)
其中pb指向C類的對象c。
即訪問的是B類中的m_data,即為1。
註:訪問類的虛函數有多態,而資料成員則沒有多態,比如對(代碼3):
virtual int doGetData( B* const pb){ return pb->m_data; },由於pb是B*類型,不管pb在記憶體中實際指向的是B對象,還是B的派生對象(如c),pb->m_data訪問的始終是B類中的m_data,即為1。
虛函數有多態,是因為虛函數表中的函數已被實際記憶體對象的函數覆蓋。而資料成員沒有多態,是因為訪問資料成員是訪問從指標地址開始,大小為sizeof(指標宣告類型)的記憶體區間(如A* p;則p->m_data是訪問從p開始,大小為sizeof(A)的區間;而B* p;則p->m_data是訪問從p開始,大小為sizeof(B)的區間)。
(2)c.A::GetData():因為A中的GetData()可能被隱藏,所以如要訪問A中的GetData(),則需用c.A::GetData(),注意c.A::GetData()中A::只是範圍,表明c調用的是A類的GetData()函數,this指向c,即調用GetData( A* const pa),其中pa指向C類的對象c。這與(1)的效果一樣,輸出也為1。
註:c.A::GetData()即調用GetData( A* const pa),其中pa指向C類的對象c。
(3)c.B::GetData():調用B中的GetData(),this指向c,即調用GetData( B* const pb),其中pb指向C類的對象c。由於B類未覆蓋GetData(),即調用A類的GetData(),即調用調用GetData( A* const pa),其中pa指向C類的對象c,則執行(代碼2),這與(1)的效果也一樣,輸出仍為1。
(4)c.C::GetData()與(3)同理。
(5)c.doGetData():因為C未覆蓋doGetData(),故執行B::doGetData(),即實際調用doGetData( B* const pb),其中pb指向C類的對象c;即執行(代碼3),訪問的仍是B類中的m_data,即為1。
(6)c.A::doGetData():調用A類的doGetData(),this指向c,即調用doGetData( A* const pa) ,其中pa指向C類的對象c,即執行(代碼1),即訪問的是A類的m_data,即為0。
(7)c.B::doGetData()和(8)c.C::doGetData()亦同理。
總之:記住下面二點:
《1》普通成員函數和虛函數都在參數列表中加入this指標。(參見《深入探索C++物件模型》P147)
《2》虛函數有多態,而資料成員則沒有多態。
《3》普通成員函數是根據this指標類型(如A*,或B*,或C*),虛函數則是根據指向指標實際指向的記憶體對象。
虛函數調用本質:M* p, p->VF()。在記憶體中定位地址p,調用p處的虛表指標指向的虛表中的VF(N* const pn)。如C c; A* p= &c; p-> doGetData()則是在記憶體中定位地址p,並調用doGetData( A* const pa),其中pa為c的起始地址。
其實也不是那麼高深,對A* p= &c,則相當於知道了一個A對象a0(它其實是屬於C類的,但編譯器不管它是否屬於C類,只把它當普通的A對象,由於p所指對象為A類型,故p的操作也只能訪問到從p開始的大小為sizeof(M)的區間),要訪問資料成員時,根據相對於記憶體位址p的位移量來尋找資料成員。如有虛函數(VF()),則在p記憶體處的虛表指標__vfptr所指向的虛表中尋找VF()並調用即可。
如想快速看結果,可不必用this。如果是普通成員函數,則看函數在哪個類裡,訪問的則是哪個類的資料成員;如果是虛函數,則訪問實際指向記憶體對象的虛函數。
――――――――――――――――――――――――
附:
對代碼:
class M
{
public:
M(): m_c( 300), m_d( 400), m_ch(
'a'), m_ch2( 'b'){}
void Fun()
{
int i= m_d;
char ch= m_ch2;
}
int m_c;
int m_d;
char m_ch;
char m_ch2;
};
Fun()函數的反組譯碼如下:
void Fun()
{
01271580 push ebp
01271581 mov ebp,esp
01271583 sub esp,0E4h
01271589 push ebx
0127158A push esi
0127158B push edi
0127158C push ecx
0127158D lea edi,[ebp-0E4h]
01271593 mov ecx,39h
01271598 mov eax,0CCCCCCCCh
0127159D rep stos dword ptr es:[edi]
0127159F pop ecx
012715A0 mov dword ptr [ebp-8],ecx
int i= m_d;
012715A3 mov eax,dword ptr [this]
012715A6 mov ecx,dword ptr [eax+4]
012715A9 mov dword ptr [i],ecx
char ch= m_ch2;
012715AC mov eax,dword ptr [this]
012715AF mov cl,byte ptr [eax+9]
012715B2 mov byte ptr [ch],cl
}
012715B5 pop edi
012715B6 pop esi
012715B7 pop ebx
012715B8 mov esp,ebp
012715BA pop ebp
012715BB ret
類中資料成員的對齊如下:
m_c m_c m_c m_c
m_d m_d m_d m_d
m_ch m_ch2 _ _
類訪問資料成員,是在當前類對象的起始處(即this)處,加上資料成員的位移量。例如,設this指標地址為XXX,則m_c地址為(XXX+0),m_d地址為(XXX+4),m_ch地址為(XXX+8),m_ch2地址為(XXX+9)。
(1)對2個類的對象指標進行類型轉換時,如果這2個類有繼承關係,則轉換後有一定位移。如class A{ int i;}; class B{ int k}; class C: public A, public B{};
則對C* pc= new C;//設pc=XXX
B* pb= pc;//B部分相對C起始的位移為4,pb=XXX+4。
如所示:
(2)而如果2個類沒有繼承關係,則轉換後沒有位移:
class A
{
public:
A(): m_a( 100), m_b( 200){}
void Print()
{
cout<< "A::Print()"<< endl;
}
int m_a;
int m_b;
};
class C
{
public:
C(): m_c( 300), m_d( 400), m_e( 500){}
void Print()
{
cout<< m_d<< endl;
}
int m_c;
int m_d;
int m_e;
};
void main()
{
A a;
A* pa= &a;//設pa=XXX
C *pc= ( C*)( &a);//pc=XXX,沒有位移。
pc->Print();
/*輸出200。訪問C中的m_d,因為m_d在C類中相對C類起始地址的位移量為4,所以相當於訪問從XXX開始的第4個位元組開始的int值,即訪問(XXX+4)的int值,而由於(XXX+4)處的值是被A::m_b賦值為200,所以m_d=200,而不是400。*/
system("pause");
}