虚函数、普通成员函数访问类的数据成员

虚函数、普通成员函数访问类的数据成员:

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");

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值