<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      虛方法的調用是怎么實現的(單繼承VS多繼承)

      我們知道通過一個指向之類的父類指針可以調用子類的虛方法,因為子類的方法會覆蓋父類同樣的方法,通過這個指針可以找到對象實例的地址,通過實例的地址可以找到指向對應方法表的指針,而通過這個方法的名字就可以確定這個方法在方法表中的位置,直接調用就行,在多繼承的時候,一個類可能有多個方法表,也就有多個指向這些方法表的指針,一個類有多個父類,怎么通過其中一個父類的指針調用之類的虛方法?

      其實前面幾句話并沒有真正說清楚,在單繼承中,父類是怎么調用子類的虛方法的,還有多繼承又是怎么實現這點的,想知道這些,請認真往下看。

      我們先看單繼承是怎么實現的。先上兩個簡單的類:

      #include <iostream> 
      using namespace std; 
      
      class A
      {
      public:
          A():a(0){}
      
          virtual ~A(){}
          
          virtual void GetA()
          {
              cout<<"A::GetA"<<endl; 
          }
      
          void SetA(int _a)
          {
              a=_a; 
          } 
          int a;
      };
      
      class B:public A
      {
      public:
          B():A(),b(0){}
      
          virtual ~B(){}
           
          virtual void GetA()
          { 
              cout<<"B::GetA"<<endl; 
          }
      
          virtual void GetB()
          { 
              cout<<"B::GetB"<<endl; 
          }
      private:
          int b;
      };
      
      typedef int (*Fun)(void);
      
      void TestA()
      {
          Fun pFun;
          A a; 
          cout<<"類A的虛方法(第0個是A的析構函數):"<<endl;
          int** pVtab0 = (int**)&a;
          for (int i=1; (Fun)pVtab0[0][i]!=NULL; i++){ 
              pFun = (Fun)pVtab0[0][i]; 
              cout << "    ["<<i<<"] "; 
              pFun(); 
          }
          cout<<endl;
          B b ;
          A* b1=&b;
      
          cout<<"類B的虛方法(第0個是B的析構函數)通過類B的實例:"<<endl;
          int** pVtab1 = (int**)&b;
          for (int i=1; (Fun)pVtab1[0][i]!=NULL; i++){ 
              pFun = (Fun)pVtab1[0][i]; 
              cout << "    ["<<i<<"] "; 
              pFun(); 
          }
          cout<<endl;
          cout<<"類B的虛方法(第0個是B的析構函數)通過類A的指針:"<<endl;
          int** pVtab2 = (int**)&*b1;
          for (int i=1; (Fun)pVtab2[0][i]!=NULL; i++){ 
              pFun = (Fun)pVtab2[0][i]; 
              cout << "    ["<<i<<"] "; 
              pFun(); 
          }
          cout<<endl;
          cout<<"     b的地址:"<<&b<<endl;
          cout<<"b1指向的地址:"<<b1<<endl<<endl;
      }

      運行結果如下:

      通過運行結果我們知道:通過父類指向子類的指針調用的是子類的虛方法。在單一繼承中,雖然父類有父類的虛方法表,子類有子類的虛方法表,但是子類并沒有指向父類虛方法的指針,在子類的實例中,子類和父類是公用一個虛方法表,當然只有一個指向方法表的指針,為什么可以公用一個虛方法表呢,虛方法表的第一個方法是析構函數,子類的方法會覆蓋父類的同樣的方法,子類新增的虛方法放在虛方法表的后面,也就是說子類的虛方法表完全覆蓋父類的虛方法表,即子類的每個虛方法與父類對應的虛方法,在各種的方法表中的索引是一樣的。

      但是在多繼承中就不是這樣了,第一個被繼承的類使用起來跟單繼承是完全一樣的,但是后面被繼承的類就不是這樣了,且仔細往下看。

      還是先上3個簡單的類

      #include <iostream> 
      using namespace std; 
      
      class A
      {
      public:
          A():a(0){}
      
          virtual ~A(){}
          
          virtual void GetA()
          {
              cout<<"A::GetA"<<endl; 
          }
           
          int a;
      };
      
      class B 
      {
      public:
          B():b(0){}
      
          virtual ~B(){}
           
          virtual void SB()
          { 
              cout<<"B::SB"<<endl; 
          } 
      
          virtual void GetB()
          {  
              cout<<"B::GetB"<<endl; 
          }
      
      private:
          int b;
      };
      
      class C:public A,public B 
      {
      public:
          C():c(0){}
      
          virtual ~C(){}
      
          virtual void GetB()//覆蓋類B的同名方法
          { 
              cout<<"C::GetB"<<endl; 
          }
      
          virtual void GetC()
          { 
              cout<<"C::GetC"<<endl; 
          }
      
          virtual void JustC()
          {
              cout<<"C::JustC"<<endl; 
          }
      private:
          int c;
      };
      
      typedef int (*Fun)(void);
      
      void testC()
      {
          C* c=new C();
          A* a=c;
          B* b=c;
          Fun pFun;
          cout<<"sizeof(C)="<<sizeof(C)<<endl<<endl;
          cout<<"c的地址:"<<c<<endl;
          cout<<"a的地址:"<<a<<endl;
          cout<<"b的地址:"<<b<<endl<<endl<<endl;
           
          cout<<"類C的虛方法(第0個是C的析構函數)(通過C類型的指針):"<<endl;
          int** pVtab1 = (int**)&*c;
          for (int i=1; (Fun)pVtab1[0][i]!=NULL; i++){ 
              pFun = (Fun)pVtab1[0][i]; 
              cout << "    ["<<i<<"] "<<&*pFun<<"    "; 
              pFun(); 
          }
          cout<<endl<<endl;
          cout<<"類C的虛方法(第0個是C的析構函數)(通過B類型的指針):"<<endl;
          pVtab1 = (int**)&*b;
          for (int i=1; (Fun)pVtab1[0][i]!=NULL; i++){ 
              pFun = (Fun)pVtab1[0][i]; 
              cout << "    ["<<i<<"] "<<&*pFun<<"    "; 
              pFun(); 
          }
      }

      運行結果如下:

      從結果說話:

      Sizeof(C)=20,我們并不意外,在單繼承的時候,父類和子類是公用一個指向虛方法表的指針,在多繼承中,同樣第一個父類和子類公用這個指針,而從第二個父類開始就有自己單獨的指針,其實就是父類的實例在子類的內存中保持完整的結構,也就是說在多重繼承中,之類的實例就是每一個父類的實例拼接而成的,當然可能因為繼承的復雜性,會加一些輔助的指針。

      指針a與指針c指向同一個地址,即c的首地址,而b所指的地址與a所指的地址相差8字節剛好就是類A實例的大小,也就是說在C的內存布局中,先存放了A的實例,在存放B的實例,sizeof(B)=8(字段int b和指向B虛方法表的指針),在家上C自己的字段int c剛好是20字節。

      讓我有點意外的是:方法B::SB,C::GetB并沒有出現在類C的方法表中,而且C::GetB是C覆寫B中的GetB方法,怎么沒有出現在C的方法表中呢?在《深入探索C++對象模型》一書中講到,這兩個方法同時應該出現在C的方法表中,同樣也會覆蓋B的虛方法表??赡苁遣煌ǖ木幾g器有不同的實現,我用的是VS2010,那本書上講的是編譯器cfront

      OK,我們不用管不同的編譯器實現上的區別,這點小區別無傷大雅,虛方法的調用機制還是一樣的。

      先來分析幾個小例子,看看虛方法的實現機制。

             C* c=new C();

             A* a=c;

             a->GetA();

             c->GetA();

             c->GetC();

      上面已經說了,a與c指向的是同一個地址,且公用同一個虛方法表,而方法GetA,GetC的地址就在這個方法表中,那么調用起來就簡單多了,大致就是下面這個樣子:

      a->GetA()   ->   (a->vptr1[1])(a);   // GetA在方法表中的索引是1

      c->GetA()  ->  (c->vptr1[1])(c);   // GetA在方法表中的索引是1

      c->GetC()   ->   (a->vptr1[2])(c);   // GetC在方法表中的索引是2

      vptr1表示指向類C第一個方法表的指針,這個指針實際的名字會復雜一些,暫且將指向類C的第一個方法表的指針命名為vptr2,下面會用到這個指針。

      再來分析幾行代碼:

           B* b=c;

             c->GetB();

             b->GetB();

      指針b和指針c指向的不是同一個地址,那么B* b=c;到底是做了啥呢?大致是會轉換成下面這個樣子:

      B* b=c+sizeof(A);

      c所指的地址加上A的大小,剛好是b所指的地址。

      c->GetB();同樣需要轉換,因為方法GetB根本不在c所指的那個方法表中,可能轉換成這個樣子(實際轉換成啥樣子我真不知道):

      this=c+sizeof(A);

      (this->vptr2[2])(c);

      如果像編譯器cfront所說的那樣,方法GetB在vptr1所指的方法表中,那么就不用產生調整this指針了,如果在vptr1所指的方法表中,就讓方法表變大了,且跟別的方法表是重復的。

      b->GetB();就不需要做過多的轉換了,因為b正好指向vptr2,可能轉換成下面這個樣子:

      b->GetB()   ->   (b->vptr2[2])(b);   // GetB在方法表中的索引是2

      總之指針所指的方法表如果沒有要調用的方法,就要做調整,虛方法需要通過方法表調用,相對于非虛方法,性能就慢那么一點點,這也是別人常說的C++性能不如C的其中一點。

      虛多繼承就更麻煩了,不熟悉可能就會被坑?!渡钊胩剿鰿++對象模型》這本書是這樣建議的:不要在一個virtual base class中聲明nonstatic data members,如果這樣做,你會距復雜的深淵越來越近,終不可拔。

      virtual base class還是當做接口來用吧。

      posted @ 2013-07-25 11:53  古文觀芷  閱讀(3892)  評論(3)    收藏  舉報
      主站蜘蛛池模板: 亚洲精品国产中文字幕| 日本夜爽爽一区二区三区| 免费又黄又爽又猛的毛片| 久久午夜无码免费| 中文字幕成熟丰满人妻| 伊人久久大香线蕉AV网禁呦| 石狮市| 国产婷婷精品av在线| 国产一精品一av一免费| 成人污视频| 国产精品无码无卡在线观看久 | 日韩精品人妻系列无码av东京| 99精品国产综合久久久久五月天| 一本无码人妻在中文字幕免费 | 人妻少妇无码精品专区| 日韩不卡在线观看视频不卡| 国产免费AV片在线看| 亚洲国产精品综合久久网络| 国产成人亚洲一区二区三区| 人人澡超碰碰97碰碰碰| 精品久久亚洲中文无码| 欧美日韩精品一区二区三区高清视频| 日韩精品 中文字幕 视频在线| 国产精品一品二区三四区| 福利一区二区在线视频| 国产精品午夜福利资源| 国产欧美另类久久久精品不卡| 欧美不卡无线在线一二三区观| 日韩中文字幕人妻一区| 91麻豆精品国产91久| 麻豆精品在线| 久久涩综合一区二区三区| 免费国产又色又爽又黄的网站| 色悠久久网国产精品99| 男女性杂交内射女bbwxz| 午夜免费无码福利视频麻豆| 日韩一本不卡一区二区三区| 一本色道国产在线观看二区| 无码国产精品一区二区av| 精品乱码一区二区三四五区 | 精品无码国产日韩制服丝袜|