(Boolan) C++ 類型大小和內(nèi)存分布(虛函數(shù)指針、虛表、內(nèi)存對齊問題)

題目要求

回答:

(以下大部分都是基于x64編譯器下的windows平臺的gcc version 5.3.0 (GCC)編譯器的測試結(jié)果,不能其他平臺也能得出完全一致的結(jié)論,如果在x32下編譯結(jié)果會指出)
由于class相較于struct,默認的成員就是private,代碼中沒有特地強調(diào)private

  • Fruit的類型大小為32,Apple的類型大小為40。

    • 完整測試用代碼:
      ***http://rextester.com/AUJV82101 ***
      • 點擊上方連接可以進入全套代碼,點擊左下角的“run”按鈕可以查看運行后的結(jié)果。
      • 說明:
        • 程序所有的對象均創(chuàng)建在棧中,由系統(tǒng)自動管理,無需手動釋放內(nèi)存
  • 圖示:

Fruit類型的大小所占的內(nèi)存(x64編譯器下的結(jié)構(gòu)) 4 * 8 = 32 Byte

注:虛函數(shù)指針因為是一個指針,其大小應(yīng)該為4個字節(jié),但在此我想說,如果使用x64編譯器生成的64位程序的指針大小為8個字節(jié)。(一個只含有虛函數(shù)的struct,x64編譯旗下,虛函數(shù)指針為8字節(jié);x86編譯器上虛函數(shù)指針和普通指針沒啥區(qū)別,都是4個字節(jié))。
在后續(xù)我有詳細的測試論證過程。

Fruit類型的大小所占的內(nèi)存(x86編譯器下的結(jié)構(gòu)) 4 * 8 = 32 Byte
Apple類型的大小所占的內(nèi)存(x64編譯器下的結(jié)構(gòu)) 5 * 8 = 40 Byte
Apple類型的大小所占的內(nèi)存(x86編譯器下的結(jié)構(gòu)) 5 * 8 = 40 Byte

關(guān)于答案以下是非常詳細的測試和推理,篇幅較長,感謝您閱讀,希望您多多指正。

答案分析:

  • 完整測試用代碼:
    ***http://rextester.com/AUJV82101 ***
    • 點擊上方連接可以進入全套代碼,點擊左下角的“run”按鈕可以查看運行后的結(jié)果。
    • 代碼運行的初級結(jié)論


      代碼初級結(jié)論(x64編譯器的結(jié)果)
- Fruit類和Apple類的相關(guān)定義

        class Fruit {
            int no;
            double weight;
            char key;
        public:
            void print() {   }
            virtual void process() {   }
        };

        class Apple : public Fruit {
            int size;
            char type;
        public:
            void save() {   }
            virtual void process() {   }
        };
  • 提出疑問
    1 對于Fruit類來說,成員由int、double和char組成,其中,不難由程序員算結(jié)果可知sizeof(int) = 4、sizeof(double) = 8、sizeof(char) = 1,那么1+4+8=13,為何sizeof(Fruit)的結(jié)果為32?
    2 對于Apple來說,成員有int、char組成,其中,不難由程序得知sizeof(int) = 4、sizeof(char) = 1,那么1 + 4 = 5,為何sizeof(Apple)的結(jié)果為40呢?
    3 這樣定義是否合理,是否存在著內(nèi)存的浪費?
    4 內(nèi)存中的額外空間用做了什么?這些空間是否有規(guī)律可循?他們是什么?都占多大的內(nèi)存空間?
    ......

  • 分析:

    為了弄清楚這些疑問,需要準備一系列的代碼來做實驗。

    • 首先我們先來驗證最基礎(chǔ)的一個特點就是內(nèi)存對齊的問題。
    • 什么是內(nèi)存對齊。內(nèi)存對齊是** 編譯器 **層面管理的問題,是編譯器管理數(shù)據(jù)位置的一種組織方式。
    • 對齊系數(shù)。其實可以把他理解為編譯器的來存放內(nèi)存時,劃分內(nèi)存空間的一把“尺子”。通過這個尺子來量出該怎么劃分內(nèi)存空間。也可以把它理解為切內(nèi)存——這塊蛋糕,所用的最小單位。如果被選中了相應(yīng)的對其系數(shù),那么,也就決定了存放數(shù)據(jù)的內(nèi)存單元的每一行有多寬,所以得出來的內(nèi)存空間大小,一定是對其系數(shù)的倍數(shù)!
      • 那么如何來得到對齊系數(shù)呢?

      方式一:
      程序員可以通過預(yù)編譯命令#pragma pack(n),n=1,2,4,8,16來改變這一系數(shù),其中的n就是你要指定的“對齊系數(shù)”。

      方式二:
      由編譯器自信決定。對于我這次測試的平臺來說,這個編譯器的規(guī)則為,采用成員中最長的變量的長度作為對其系數(shù)。
      - 既然知道了對齊系數(shù),那么是否可以幫助解釋之前提出的疑問呢?!
      答案是,可以解釋部分內(nèi)容,想要全部弄明白還得等等,我們先來看看這塊能解釋多少吧。
      如果這時候那Fruit為例來看,它其中的成員有int,double和char所組成,這三個變量中,最長的應(yīng)該是double了。所以Fruit的大小一定是sizeof(double)的倍數(shù),也就是8的倍數(shù)。目前看,F(xiàn)ruit的大小為32,是符合這個觀點的。那么這三個成員是如何排列呢?
      其實他們的安排順序還是狠簡單粗暴的,就是定義變量的順序來組織他們在內(nèi)存中的位置。
      比如,F(xiàn)ruit的成員定義順序是int,double,char,則編譯器會先將int按照,對齊系數(shù)放入內(nèi)存中,再看后面的變量,如果,兩者相加小于對齊系數(shù),則放在同一行,如果大于,就單獨再開一行。那么,F(xiàn)ruit的對齊系數(shù)為double的8,sizeof(int)+sizeof(double) > 8,那么double會單獨開一行,放進去。這時候,F(xiàn)ruit的內(nèi)存已經(jīng)為8*2=16了。接下來再看char,由于double單獨為一行,那么char會單獨開一行,所以此時的內(nèi)存為8 * 3 = 24。具體的圖形如下圖:

      只考慮成員變量的內(nèi)存圖

      關(guān)于這幅圖,int占用了4個內(nèi)存,double占用了8個內(nèi)存單元,char占用了1個內(nèi)存單元。其中紅色的部分為浪費的內(nèi)存空間。
      那么說了這么多,到底如何呢,我們接下來用代碼看看。

再看代碼之前,先簡單說明一下代碼的功能

測試定義順序?qū)?nèi)存的影響
the memory of Fruit8---------------------
Address of Fruit8: 0x 0x7ffcdb6a4110 | Size = 24
88  28  40  00  00  00  00  00  
00  00  00  00  00  00  00  00  
01  00  00  00  00  00  00  00  
-------------------------------

1 結(jié)果輸出測試類型的名稱
2 結(jié)果輸出該類型的對象的地址和該類型的大小
3 結(jié)果輸出對應(yīng)地址下的內(nèi)容(按字節(jié),以十六進制的方式輸出)

  • 類的定義

    // Fruit類和Fruit4類之間的區(qū)別主要是定義成員變量的順序
    
      //原始定義
      class Fruit {
          int no;
          double weight;
          char key;
      public:
          void print() {   }
          virtual void process() {   }
      };
    
      //定義順序調(diào)整(虛函數(shù)同名)(優(yōu)化后)
      //Fruit的定義成員函數(shù)的順序為從小到大。
      class Fruit8 {
          char key;
          int no;
          double weight;
      public:
          void print() {   }
          virtual void process() {   }
      };            
    
    //定義順序調(diào)整(虛函數(shù)同名)(優(yōu)化后)
    class Fruit4 {
      char key;
      int no;
      double weight;
    public:
      Fruit4(int n, double w, char k) :no(n), weight(w), key(k) {}
        void print() {   }
      virtual void process() {   }
    };
    
    // 定義了char、char、int、double
    class Fruit9 {
      char key;
      char x;
      int no;
      double weight;
    public:
      void print() {   }
        virtual void process() {   }
        Fruit9(char a, char b, int n, double w) :key(a), x(b), no(n), weight(w){}
    };
    
  • 測試代碼
    #include <iostream>
    #include <string>
    #include <iomanip>

      using namespace std;
              
              //為了方便閱讀,這個函數(shù)再次給出,但之后不在贅述
      string operator*(string z, int n) {
          string temp = z;
          for (int i = 0; i < n; i++) {
              z += temp;
          }
          return z;
      }
    
              //為了方便閱讀,這個函數(shù)再次給出,但之后不在贅述
      void printMemo(char* name, void* f, int size) {
          string s = "-";
          cout << "the memory of " << name << s*20 <<"\n";
          cout << "Address of "<<name << ": 0x " << hex << f <<  " | Size = " << dec <<size <<endl;
          unsigned char* x = (unsigned char*)f;
          for (int i = 0; i < size; i++) {
              cout << setfill('0') << setw(2) << hex << (unsigned int)*x << "  ";
              if (!((i + 1) % (size>8? 8: 4))) {
                  cout << "\n";
              }
              x++;
          }
          cout << s*30 <<"\n\n";
      }
      
      int main()
      {   
          cout << "測試輸出最原始結(jié)構(gòu)" << endl;
    
          Fruit f;
          Fruit* ft = &f;
          printMemo("Fruit", ft, sizeof(Fruit));
    
          Fruit8 f8;
          Fruit8* ft8 = &f8;
          printMemo("Fruit8", ft8, sizeof(Fruit8));
          
          cout << "定義順序調(diào)整(虛函數(shù)同名)(優(yōu)化后)" << endl;
          Fruit4 f4(1, 4.456, 'c');
          Fruit4* ft4 = &f4;
          printMemo("Fruit4", ft4, sizeof(Fruit4));
    
          Fruit9 f9('a', 'b', 77777777, 1.234);
          Fruit9* ft9 = &f9;
          printMemo("Fruit9", ft9, sizeof(Fruit9));
          return 0;
      }
    
  • 運行結(jié)果


    Fruit的測試結(jié)果

    Fruit8的測試結(jié)果

    Fruit4的測試結(jié)果

    Fruit9的測試結(jié)果
  • 結(jié)果分析

    • 僅調(diào)整成員定義的順序,F(xiàn)ruit8的大小為24字節(jié),而Fruit的字節(jié)為32字節(jié)。

      按照之前的分析,只考慮成員的定義順序,會得到一下的內(nèi)存
      修改后的(Fruit4 )內(nèi)存圖
  • 內(nèi)存輸出的結(jié)果


    Fruit4 內(nèi)存輸出結(jié)果
    • 以上說明了內(nèi)存分布和抽象畫成的一致,但是,觀察可以發(fā)現(xiàn),內(nèi)存空間并不連續(xù),char和int之間并不連續(xù)。因為int如果與char連續(xù)的話,int的內(nèi)存起止的位置都會為奇數(shù),則此時,編譯器會跳過一部分內(nèi)存。為了驗證內(nèi)存跳過的情況,可以比較Fruit4 和Fruit9對比可以看出其內(nèi)存圖分配,就可以看出int內(nèi)存的跳過的情況。其中77777777的十六進制數(shù)為:0x 04 A2 CB 71,a和b的ASCII碼的十六進制數(shù)分別為61和62,因此內(nèi)存情況,可以得到具體內(nèi)存圖。
Fruit9的內(nèi)存
Fruit9的內(nèi)存圖

函數(shù)問題

  • 關(guān)于成員屬性在內(nèi)存中是如何分布的基本說明白了,但是,目前還沒有討論完全,因為,F(xiàn)ruit的實際大小為32,我們通過以上理論,解釋了內(nèi)存為24的空間還有8字節(jié)的空間去了哪呢?那么會不會是由于成員函數(shù)而影響的呢?

那么我們先來驗證一下,函數(shù)到底會不會影響類型的大小呢?
二話不說,先上代碼~~~~~

  • 先來看看構(gòu)造函數(shù)

    //原始定義
    class Fruit {
        int no;
        double weight;
        char key;
    public:
        void print() {   }
        virtual void process() {   }
    };
    //添加構(gòu)造函數(shù)后的定義
    class Fruit2 {
        int no;
        double weight;
        char key;
    public:
        Fruit2(int n, double w, char k) :no(n), weight(w), key(k) {}
        void print() {   }
        virtual void process() {   }
    };
    
  • 測試代碼(不含預(yù)先定義的部分,需要請查看上方)

    Fruit f;
    Fruit* ft = &f;
    printMemo("Fruit", ft, sizeof(Fruit));
    
    cout << "添加構(gòu)造函數(shù)后的定義" << endl;
    Fruit2 f2(1, 2.345, 'c');
    Fruit2* ft2 = &f2;
    printMemo("Fruit2", ft2, sizeof(Fruit2));
    
  • 運行結(jié)果


  • 結(jié)論分析

1 首先可以看出,添加了構(gòu)造函數(shù),并沒有影響類型的內(nèi)存大小,都還是32字節(jié),說明** 構(gòu)造函數(shù),并不影響類型的大小**
2 其次,觀察內(nèi)存空間不難發(fā)現(xiàn),圖中畫雙框的部分的內(nèi)存很相似,而且大小也正是八個字節(jié)的大小,只要研究清楚這個是什么,也就明白了類型的大小到底是怎么一回事。

  • 考察虛函數(shù)
    二話不說,刷代碼
    • 代碼

       //原始定義
      class Fruit {
          int no;
          double weight;
          char key;
      public:
          void print() {   }
          virtual void process() {   }
      };
      
        //去掉虛函數(shù)后的定義
      class Fruit1 {
          int no;
          double weight;
          char key;
      public:
          void print() {   }
          //virtual void process() {   }
      };
      
    • 測試代碼(不含預(yù)先定義的部分,需要請查看上方)

      Fruit f;
      Fruit* ft = &f;
      printMemo("Fruit", ft, sizeof(Fruit));
      
      cout << "去掉虛函數(shù)后的定義" << endl;
      Fruit1 f1;
      Fruit1* ft1 = &f1;
      printMemo("Fruit1", ft1, sizeof(Fruit1));
      
    • 運行結(jié)果


      原始

      去掉虛函數(shù)
    • 結(jié)果分析

    1 總算發(fā)現(xiàn)了這八個字節(jié)的根本來源——** 虛 函 數(shù) ?。。?*

現(xiàn)在知道了一直困擾我們的八個字節(jié)是來自與虛函數(shù)的定義,那么,問題接著就有來了,虛函數(shù)的所占內(nèi)存的大小是多少? 是否遵循對其的原則呢?二話不說,趕快上代碼測試?。?/p>

虛函數(shù)的內(nèi)存問題

  • 代碼

     //原始定義
    class Fruit {
        int no;
        double weight;
        char key;
    public:
        void print() {   }
        virtual void process() {   }
    };
    //純虛函數(shù)是否影響
    class Fruit5 {
        int no;
        double weight;
        char key;
    public:
        Fruit5(int n, double w, char k) :no(n), weight(w), key(k) {}
        void print() {   }
        virtual void process() = 0;
    };
    
    class Fruit6 {
    public:
        Fruit6()  {}
        void print() {   }
        virtual void process() {};
    };
    
    //驗證虛函數(shù)和對齊
    class Fruit7 {
        char n;
    public:
        Fruit7(char a):n(a) {}
        void print() {   }
        virtual void process() {};
    };
    
      //多個虛函數(shù)
    class Fruit10 {
        int no;
        double weight;
        char key;
    public:
        void print() {   }
        Fruit10(char a, int n, double w) :key(a), no(n), weight(w) {}
        virtual void process1() {   }
        virtual void process2() {   }
        virtual void process3() {   }
    };
    
  • 測試代碼

    Fruit f;
    Fruit* ft = &f;
    printMemo("Fruit", ft, sizeof(Fruit));
    Apple a;
    Apple* at = &a;
    printMemo("Apple", at, sizeof(Apple));
    
    cout << "純虛函數(shù)是否影響" << endl;
    //抽象類不能創(chuàng)建對象
    printMemo("Fruit5", NULL, sizeof(Fruit5));
    
    cout << "測試虛函數(shù)的大小" << endl;
    Fruit6 f6;
    Fruit6* ft6 = &f6;
    printMemo("Fruit6", ft6, sizeof(Fruit6));
    
    cout << "驗證虛函數(shù)和對齊" << endl;
    Fruit7 f7('a');
    Fruit7* ft7 = &f7;
    printMemo("Fruit7", ft7, sizeof(Fruit7));
    
    cout << "多個虛函數(shù)測試,對齊情況" << endl;
    Fruit10 f10(1, 10.1056, 'c');
    Fruit10* ft10 = &f10;
    printMemo("Fruit10", ft10, sizeof(Fruit10));
    
  • 運行結(jié)果

原始
Fruit5(抽象類)
Fruit6(x64環(huán)境下結(jié)果)
Fruit6(x86環(huán)境下結(jié)果)
Fruit7(x64環(huán)境下結(jié)果)
Fruit7(x86環(huán)境下結(jié)果)
多個虛函數(shù)
  • 結(jié)論

1 由原始數(shù)據(jù)和Fruit5(純虛函數(shù)的抽象類)的輸出的結(jié)果可以看到,雖然Fruit5不能創(chuàng)建對象,但是不難看出兩者的大小是相同的,所以虛函數(shù)和純虛函數(shù)占用的空間相同
2 由Fruit6的輸出的結(jié)果可以得出,在x86和x64平臺的結(jié)果不相同,*** 在32位平臺的虛函大小為4字節(jié),在64位平臺下的虛函數(shù)的大小為8字節(jié)***
3 由Fruit7可以看出,虛函數(shù)所占內(nèi)存大小的分配規(guī)則,符合對齊的規(guī)則,x64平臺下,虛函數(shù)加char,會浪費7個字節(jié)的空間,x86平臺下會浪費3個字節(jié)。
4 由Fruit10可以看出,多個虛函數(shù)的情況,實際占用與一個虛函數(shù)的情況相同。

現(xiàn)在已經(jīng)完成了類型大小的整理,但是還差一件事,就是父類和子類的關(guān)系。

父類和子類

  • 基本版
    二話不說上代碼
    • 代碼

      //原始定義
      class Fruit {
          int no;
          double weight;
          char key;
      public:
          void print() {   }
          virtual void process() {   }
      };
      
      class Apple : public Fruit {
          int size;
          char type;
      public:
          void save() {   }
          virtual void process() {   }
      };
      
      //去掉虛函數(shù)后的定義
      class Fruit1 {
          int no;
          double weight;
          char key;
      public:
          void print() {   }
          //virtual void process() {   }
      };
      
      class Apple1 : public Fruit1 {
          int size;
          char type;
      public:
          void save() {   }
          //virtual void process() {   }
      };
      
    • 測試代碼

      cout << "測試輸出最原始結(jié)構(gòu)" << endl;
      
      Fruit f;
      Fruit* ft = &f;
      printMemo("Fruit", ft, sizeof(Fruit));
      Apple a;
      Apple* at = &a;
      printMemo("Apple", at, sizeof(Apple));
      
    • 輸出結(jié)果


    • 分析
      1 Apple為Fruit的子類,并且Fruit具有三個可能影響類型大小的成員,分別為int、char、虛函數(shù)。其中int為4字節(jié),char為1字節(jié),對齊系數(shù)為4,那么成員屬性的大小為8字節(jié)。Fruit的大小為32字節(jié),Apple的大小為40字節(jié)。
      2 對于去除了虛函數(shù)的情況,包含虛函數(shù)的父類和子類大小分別為32、40,去除掉后,大小分別為24, 32。相當(dāng)于每個類減少了8個字節(jié)(父類的對齊系數(shù))

    • 結(jié)論

  1. 子類會繼承父類的對齊系數(shù),子類的成員是依據(jù)父類的對齊系數(shù)來計算的
  2. 子類的虛函數(shù),不對大小產(chǎn)生影響
  • 父類在子類中的位置
    • 代碼
    class Fruit2 {
        int no;
        double weight;
        char key;
    public:
        Fruit2(int n, double w, char k) :no(n), weight(w), key(k) {}
        void print() {   }
        virtual void process() {   }
    };

    class Apple2 : public Fruit2 {
        int size;
        char type;
    public:
        Apple2(int s, char t, int n, double w, char k) :size(s), type(t), Fruit2(n, w, k) {}
        void save() {   }
        virtual void process() {   }
    };

    //定義順序調(diào)整(虛函數(shù)同名)(優(yōu)化后)
    class Fruit4 {
        char key;
        int no;
        double weight;
    public:
        Fruit4(int n, double w, char k) :no(n), weight(w), key(k) {}
        void print() {   }
        virtual void process() {   }
    };

    class Apple4 : public Fruit4 {
        char type;
        int size;
    public:
        Apple4(int s, char t, int n, double w, char k) :Fruit4(n, w, k),  size(s), type(t){}
        void save() {   }
        virtual void process() {   }
    };
  • 測試代碼
    cout << "測試元素分布" << endl;
    //驗證位置(未優(yōu)化1)
    Fruit2 f21(1, 2.345, 'c');
    Fruit2* ft21 = &f21;
    printMemo("Fruit2", ft21, sizeof(Fruit2));

        //驗證位置(未優(yōu)化2)
        Fruit2 f22(3, 2.345, 'b');
        Fruit2* ft22 = &f22;
        printMemo("Fruit2", ft22, sizeof(Fruit2));
        
        //驗證位置(未優(yōu)化1)
        Apple2 a21(9, 'd', 2, 6.789, 'e');
        Apple2* at21 = &a21;
        printMemo("Apple2", at21, sizeof(Apple2));
        
        //驗證位置(未優(yōu)化2)
        Apple2 a22(8, 'a', 3, 6.789, 'f');
        Apple2* at22 = &a22;
        printMemo("Apple2", at22, sizeof(Apple2));
    
        //驗證位置(優(yōu)化后1)
        Fruit4 f41(1, 41.4156, 'c');
        Fruit4* ft41 = &f41;
        printMemo("Fruit41", ft41, sizeof(Fruit4));
    
        //驗證位置(優(yōu)化后2)
        Fruit4 f42(2, 41.4156, 'b');
        Fruit4* ft42 = &f42;
        printMemo("Fruit42", ft42, sizeof(Fruit4));
    
        Apple4 a41(9, 'd', 1, 41.4156, 'c');
        Apple4* at41 = &a41;
        printMemo("Apple41", at41, sizeof(Apple4));
    
        Apple4 a42(8, 'e', 1, 41.4156, 'g');
        Apple4* at42 = &a42;
        printMemo("Apple42", at42, sizeof(Apple4));
    
  • 運行結(jié)果

Fruit2的系列
Fruit4的系列
  • 分析
Fruit2系列的分析
整理后的Fruit4的系列的分析
  • 結(jié)論

由之前的分析可以看出來,對于子類來說,虛函數(shù)指針是相同的位置,子類成員所占的內(nèi)從空間始終在父類之后,父類空間后面所剩下的位置,可以與子類共用

  • 子類與虛函數(shù)(多個,同名(override)與不同名的虛函數(shù)的關(guān)系)
    • 代碼

        //多個虛函數(shù)
      class Fruit10 {
          int no;
          double weight;
          char key;
      public:
          void print() {   }
          Fruit10(char a, int n, double w) :key(a), no(n), weight(w) {}
          virtual void process1() {   }
          virtual void process2() {   }
          virtual void process3() {   }
      };
      
      class Apple10 : public Fruit10 {
          int size;
          char type;
      public:
          Apple10(char t, int s, char a, int n, double w) :Fruit10(a, n, w), type(t), size(s) {}
          void save() {   }
          virtual void process1() {   }
          virtual void process2() {   }
          virtual void process4() {   }
      };
      
    • 測試代碼

      cout << "多個虛函數(shù)測試,對齊情況" << endl;
      Fruit10 f10(1, 10.1056, 'c');
      Fruit10* ft10 = &f10;
      printMemo("Fruit10", ft10, sizeof(Fruit10));
      
      Apple10 a10(9, 'd', 1, 10.1056, 'c');
      Apple10* at10 = &a10;
      printMemo("Apple10", at10, sizeof(Apple10));
      
    • 輸出結(jié)果


    • 結(jié)論

子類的虛函數(shù)所站類型的大小,和數(shù)量,是否同名無關(guān),始終處于最上方,且大小固定(為一個對齊系數(shù)的大?。?/strong>

虛表問題

  • 之前的部分,基本把這個問題講清楚了,但還留下了一個問題:為什么多個虛函數(shù),也只用一個指針就夠了???(由于此處重點非虛表,所以不做詳細說明。)
    關(guān)于這個問題,我在這里簡單解釋一下,虛函數(shù)在類中,只需要保存一個指針即可,那么這個指針所指向的內(nèi)容就很重要了,它會指向一個數(shù)組,在數(shù)組中保存著他所持有的虛函數(shù)即可,這樣他只需要持有一個固定大小的指針就行了,而不需要考慮實際擁有幾個虛函數(shù)的問題。但是,對于虛函數(shù)來說,還有一個更大的用途,那就是對于實現(xiàn)父類和子類中的虛函數(shù)的關(guān)系騎著非常重要的作用了。由之前的測試程序可以看出,對于各相同類型的不同對象,實際虛函數(shù)指針所指的區(qū)域是相同的。(比如Fruit21 f21和Fruit22 f22等),也就是虛表實際只和class相關(guān),具體的對象只需指向這塊內(nèi)存空間即可。而編譯器,實際在調(diào)用虛函數(shù)f21.xxVirtualFunction();時,實際編一起會將其轉(zhuǎn)化為(* (f21 -> vptr)[n])(f21); 或 (*f21->vptr[n])(f21);來進行執(zhí)行。可以看出實際是從數(shù)組中取出對應(yīng)函數(shù)的指針,并將對象傳入其中進行調(diào)用的過程。此時該數(shù)組中的元素指向,如果是存在子父類關(guān)系的同名虛函數(shù)(子類override父類虛函數(shù)的情況)的情況,虛表中的指針所指的虛函數(shù)的指針為同一個!這樣的設(shè)計好處,由于對象是由參數(shù)傳入的,所以能夠輕松實現(xiàn)多態(tài)。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容