虚函数、普通成员函数访问类的数据成员:
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");
}