虛函數、普通成員函數訪問類的資料成員

來源:互聯網
上載者:User

虛函數、普通成員函數訪問類的資料成員:

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

}

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.