精华内容
下载资源
问答
  • C++中虚函数继承类的内存占用大小计算
    千次阅读
    2019-08-15 17:10:23

    前半部分转自https://www.cnblogs.com/SeekHit/p/7570247.html

    其中为32位字节对齐,后半部分给出自己的理解。

    字节对齐的原理见链接https://www.cnblogs.com/0201zcr/p/4789332.html(注意64位系统虚函数指针占8字节,32位占4字节)

    计算一个类对象的大小时的规律:

    • 1、空类、单一继承的空类、多重继承的空类所占空间大小为:1(字节,下同);
    • 2、一个类中,虚函数本身、成员函数(包括静态与非静态)和静态数据成员都是不占用类对象的存储空间的;
    • 3、因此一个对象的大小≥所有非静态成员大小的总和;
    • 4、当类中声明了虚函数(不管是1个还是多个),那么在实例化对象时,编译器会自动在对象里安插一个指针vPtr指向虚函数表VTable;
    • 5、虚承继的情况:由于涉及到虚函数表和虚基表,会同时增加一个(多重虚继承下对应多个)vfPtr指针指向虚函数表vfTable和一个vbPtr指针指向虚基表vbTable,这两者总共所占的空间大小为:8(或8乘以多继承时父类的个数);理解为虚函数表和虚基表分别占4字节。即基类的虚函数表总共占4个字节,子类的虚基表每个基类地址占4个字节
    • 6、在考虑以上内容所占空间的大小时,还要注意编译器下的“补齐”padding的影响,即编译器会插入多余的字节补齐;
    • 7、类对象的大小=各非静态数据成员(包括父类的非静态数据成员但都不包括所有的成员函数)的总和+ vfptr指针(多继承下可能不止一个)+vbptr指针(多继承下可能不止一个)+编译器额外增加的字节。
    • 例子一

      class A     
      {     
      };    
        
      class B     
      {  
          char ch;     
          virtual void func0()  {  }   
      };   
        
      class C    
      {  
          char ch1;  
          char ch2;  
          virtual void func()  {  }    
          virtual void func1()  {  }   
      };  
        
      class D: public A, public C  
      {     
          int d;     
          virtual void func()  {  }   
          virtual void func1()  {  }  
      };     
        
      class E: public B, public C  
      {     
          int e;     
          virtual void func0()  {  }   
          virtual void func1()  {  }  
      };  
        
      int main(void)  
      {  
          cout<<"A="<<sizeof(A)<<endl;    //result=1 //64位为1
          cout<<"B="<<sizeof(B)<<endl;    //result=8 //64位为16 
          cout<<"C="<<sizeof(C)<<endl;    //result=8 //64位为16
          cout<<"D="<<sizeof(D)<<endl;    //result=12//64位为24
          cout<<"E="<<sizeof(E)<<endl;    //result=20//64位为40
          return 0;  
      }  

    原帖中写到“求sizeof(D)的时候,需要明白,首先VPTR指向的虚函数表中保存的是类D中的两个虚函数的地址(只有一个虚基类地址指针),然后存放基类C中的两个数据成员ch1、ch2,注意内存对齐,然后存放数据成员d,这样4+4+4=12。”32位系统虚函数表指针内存是4字节,而64位系统虚函数表指针内存是8字节。所以在64位系统中结果不同于32位系统,过程为8+2+4对齐为8+8+4(8+8是满足父类的对齐要求)继续对齐为8+8+8满足(对齐为最大类型内存整数倍的对齐要求),故结果为24.
    32位系统中求sizeof(E)的时候,首先是类B的虚函数地址,然后类B中的数据成员,再然后是类C的虚函数地址,然后类C中的数据成员,最后是类E中的数据成员e,同样注意内存对齐,这样4+4+4+4+4=20。

    64位系统中求sizeof(E)的时候,首先是类B的虚函数地址(8字节),然后类B中的数据成员(并对齐为8+8=16字节),再然后是类C的虚函数地址(8字节),然后类C中的数据成员(8+1+1对齐为8+8=16字节),最后是类E中的数据成员e,同样注意内存对齐(8字节),这样16+16+8=40。

    更多相关内容
  • #include using ... C类,虚函数继承,一个整理4B,自己的虚指针4B(指向的虚函数表仅cc一个函数),然后把B类的12B全继承一份,共20B 注意:上面两个继承与虚继承是不一样的,虚继承就记住是菱形继承时用到即可

    #include<bits/stdc++.h>
    using namespace std;
    class A{public:int k;virtual void aa(){};};
    class B:public A{public:int j;virtual void bb(){};};
    class C:public virtual B{public:int i;virtual void cc(){};};
    int main(){cout<<sizeof(A)<<sizeof(B)<<sizeof(C)<< endl;return 0;}
    //8 12 20

     

    A类,一个整型4B,一个虚函数故有一个虚函数指针4B,共8B【A类的虚函数表只有一个虚函数地址】

    B类,普通继承,一个整型4B,继承下来的一个整型4B,虚指针4B,共12B【B类的虚函数表有aa与bb两个虚函数地址】

    C类,虚函数继承,一个整理4B,自己的虚指针4B(指向的虚函数表仅cc一个函数),然后把B类的12B全继承一份,共20B

    注意:上面两个继承与虚继承是不一样的,虚继承就记住是菱形继承时用到即可

    展开全文
  • C++虚函数继承与虚继承

    万次阅读 2018-02-22 15:56:40
    虚函数继承和虚继承是完全不同的两个概念。 虚函数继承是解决多态性的,当用基类指针指向派生类对象的时候,基类指针调用虚函数的时候会自动调用派生类的虚函数,这就是多态性,也叫动态编联。 虚继承就是为了节约...

    虚函数继承和虚继承是完全不同的两个概念。

    虚函数继承是解决多态性的,当用基类指针指向派生类对象的时候,基类指针调用虚函数的时候会自动调用派生类的虚函数,这就是多态性,也叫动态编联。

    虚继承就是为了节约内存,他是多重继承中的特有的概念。适用于菱形继承形式。

    比如B继承于A、C继承于A、D继承于B和C,显然D会继承两次A(图1)。因此,为了节省空间,可以将B、C对A的继承定义为虚拟继承,而A就成了虚拟基类(图2)。代码如下:

     A    A           A
      \    /           / \
      B   C           B  C
       \  /            \  /
       D             D
      (图1)          (图2)

    class A; 
    class B:vitual public A; 
    class C:vitual public A; 
    class D:public B,public C; 

    虚继承的时候子类会有一个指向自己虚函数表的指针,同时也会加入一个指向父类的虚类指针,然后还要包含父类的所有内容。

    虚继承时如果子类父类都有虚函数,那么它会重新建立一张虚表,不包含父类虚表的内容;而在普通的继承中却是在父类虚表的基础上建立一张虚表。这就意味着如果虚继承中子类父类都有各自的虚函数,在子类里面就会有两个虚函数表指针,一个指向父类的虚表,一个指向子类的虚表,而普通的继承只有一个指向子类虚表的指针。代码说明:

    class A
    {
        int k;
    public:
        virtual void aa(){};
    };
    
    class B:public virtual A
    {
        int j;
    public:
        virtual void bb(){};
    };
    
    class C:public virtual B
    {
        int i;
    public:
        virtual void cc(){};
    };
    
    int main()
    {
        cout << sizeof(A) << endl;
        cout << sizeof(B) << endl;
        cout << sizeof(C) << endl;
        system("pause");
        return 0;
    }

    输出结果为:8、20、32。

    怎么来的呢?类A中包含一个整型变量k(4字节),一个虚表指针(4字节),所以一共8字节。类B中,一个整型变量j(4字节),一个虚表指针(4字节),因为B虚继承于A,所有会有一个指向类A的虚类指针(4字节),同时还要包含类A中的整型变量k(4字节)以及类A的虚表指针(4字节),所以一共20字节。类C同理。

    如果将上述代码改为普通继承,那么输出结果为:8、12、16。没有虚类指针,也不会有多个虚表指针。

    展开全文
  • 本文分两部分即继承虚函数与多态性,本文第一部分详细讲解了继承时的构造函数和析构函数的问题,父类与子类的 同名变量和函数问题,最后介绍了多重继承与虚基类。本文第二部分重点介绍了虚函数与多态性的问题,...
  •  GCC中, 无论是虚函数还是虚继承, 都需要将指针存储在虚函数表(virtual function table), 占用4个字节.  继承继承基类的数据, 和虚函数表, 即继承基类的空间.  代码: /* * test.cpp * * Created ...
  • C++、类继承虚函数

    千次阅读 2022-04-11 10:07:50
    继承虚函数的一些基本概念。

    1 继承

    1.1 继承方式

    1. public,基类成员在派生类中的访问权限不变。
    2. protected,基类中的 public 和 protected 成员在派生类中都变成 protected 成员。
    3. private,基类中的 public 和 protected 成员在派生类中都变成 private 成员。

    无论是那种继承,基类中的 public 和 protected 成员在派生类的定义体中都是可访问的。

    1.2 派生类到基类的转换与赋值

    派生类对象的内存头部包含了完整的基类内存结构,之后才是派生类新定义成员的内存。因此,派生类对象在一定程度上能够转换为基类对象的指针或引用,或者对基类对象进行初始化和赋值。

    在同一个程序中,派生类类型的对象既可能可以,也可能不可以转换或赋值到基类。基类对象只能访问基类的 public 成员,而对于不同的继承方式,派生类对象对这些成员的访问权限会发生改变。只有当派生类对象对于继承自基类的 public 成员的可访问性与基类对象一致时,进行转换或者赋值才是安全的。

    1. 无论是哪种继承方式,派生类定义体中基类的 public 成员都是可访问的,因此在派生类的成员函数内部进行转换或者赋值都是允许的;

    2. 对于 protected 和 private 继承,派生类对象并不能访问基类的 public 成员,此时在派生类定义体外部进行转换或赋值是不允许的。

    class A {};
    class B : private A {
       void tmp(B b) 
       {
          A x = b;    // ok,使用派生类对象进行基类对象的初始化
          x = b;      // ok,使用派生类对象对基类对象赋值
          A &y = b;   // ok,派生类通过引用转换为基类
          A *z = &b;  // ok,派生类通过指针转换为基类
       }
    };
    class C : protected B {
       void tmp(C c)
       {
          A x1 = c;   // error,C类不能访问A类的public成员,除非B类至少是以protected继承A类
          A &y1 = c;  // error,同上
          B x2 = c;   // ok,这时B类是C类的直接基类,C类可以访问B类的public成员
          B &y2 = c;  // ok,同上
       }
    };
    int main() {
       A a1 = B();    // error,这时B类不能访问A类的public成员
       A a2 = C();    // error,这时C类不能访问A类的public成员
       B b1 = C();    // error,这时C类不能访问B类的public成员
    }
    

    这里所说的转换与初始化或赋值的意义不一样(个人理解):

    1. 转换相对于指针或引用而言,即内存中并不存在基类对象的实例,但基类指针或引用可以按照基类的访问权限,访问派生类的对象实例中继承自基类的数据成员或函数成员。注意,如果派生类对基类的数据成员或者函数成员进行了重定义以及函数重写或者重载,那么进行转换后,基类指针或者引用也只能访问带有基类域限定符的成员,而不是重定义后的成员,因为重定义成员的内存不在基类的可访问范围内。

      不过对于函数成员有一点特殊,如果基类中某函数被声明为虚函数,那么基类指针或引用调用的是重写后的函数。这是继承实现运行时多态的关键,此时内存中会维持一个虚函数表,基类的指针或引用包含一个指向虚函数表的指针,当基类指针或引用调用虚函数时,实际调用的是派生类对象所对应的虚函数,从而实现运行时多态。具体查看《虚函数》部分内容。

    2. 初始化或赋值即按照基类的访问方式,将派生类对象实例的数据成员复制到正在构造或已存在的基类对象实例对应的成员中。这个一般是通过调用基类的拷贝构造函数以及赋值函数实现的。从这以后,该基类对象与输入的派生类对象再无联系,从而也不能实现运行时多态。

    注意,回忆指针与引用的区别,使用指针和使用引用进行派生类转换也是有区别的:

    1. 通过引用 A &a=b; a=c;

      由于引用只能初始化,一经绑定后再也不能更改,当执行 A &a=b; 后,a 的运行时类型就固定为 B 的类型,a 的虚表指针更新为 b 的虚表指针。当执行 a=c; 时,a 的运行时类型并不会更改为 C 的类型,而是通过调用基类的赋值函数,将 c 的基类成员复制到 b 的基类成员。a 的虚表指针不会改变。

    2. 通过指针 A *a=&b; a=&c;

      由于指针是可赋值的,当执行 A *a=&b; 后,a 的运行时类型就变为 B 的类型,a 的虚表指针也会更新为 b 的虚表指针。但执行 a=&c; 后,a 的运行时类型就变为 C 的类型,同时其虚表指针也会更新为 c 的虚表指针。在这个过程中,只有指针变量的赋值,而没有赋值函数的调用。

    2 虚函数

    因为派生类对象的内存结构包含了基类对象的完整内存结构,所以派生类对象可直接赋值给基类对象,或者将派生类的指针赋值给基类指针,或者通过基类类型进行派生类对象的引用。

    class A {};
    class B : public A {};
    B b;
    A a = b;
    A *a = &b;
    A &a = b;
    

    变量 a 的可访问成员范围由基类 A 定义。如果派生类中对基类的函数进行了重定义,基类变量并不能因此调用派生类的实现方法。为了实现变量a的成员函数的动态绑定,C++提供了虚函数的方法。

    只有类中的成员函数才有资格成为虚函数,因为虚函数仅适用于有继承关系的类对象。虚函数在基类内的声明前面加上 virtual 关键字,类外定义不能加 virtual。当存在虚函数时,基类对象实例化或者作为指针或引用时,在其内存空间头部维持一个虚表(virtual table)指针。

    一个程序的虚表通常是一大块的连续内存,编译器会将程序中存在虚函数的类的虚函数的地址按顺序填入虚表中。例如,假设 B 继承自 A,且 A 中定义了 f1() 和 f2() 两个虚函数;而 C 继承自 B,B 中又定义了一个虚函数 f3(),那么虚表可能为:

    |vptrA=A::f1|A::f2| xxx |vptrB=B::f1|B::f2|B::f3| xxx |vptrC=C::f1|C::f2|C::f3|

    当定义了一个类的对象实例或者指针或引用时,虚表指针会自动初始化为该类在虚表中存储的第一个函数指针的位置。 例如 A a;a.vptr=A::f1; B b;b.vptr=B::f1; C c;c.vptr=C::f1; 当然 vptr 并不是编程可见的。不同的编译器可能有不同的实现方法。

    当派生类对象通过指针或引用转换为基类类型时,基类指针或引用的 vptr 会更新为派生类的 vptr。由于虚函数是按顺序摆放的,当基类调用虚函数比如 f2() 时,其实际调用的函数是 vptr[1],而此时 vptr 已经更新为派生类的 vptr,所以就能调用派生类的版本。例如,A &a = b; 此时 a.vptr=vptrB=B::f1a.f2() 调用的是 a.vptr[1]=vptrB[1]=B::f2。同理,B *b = &c; 此时 b->vptr=vptrC=C::f1b->f3() 调用的是 b->vptr[2]=vptrC[2]=C::f3

    注意不同编译器对于虚函数的实现方法不一定是一样的,但总体上是相似的。在 vs 中,右键点击 xxx.cpp 文件的 属性 -> c/c++ -> 命令行,添加 /d1 reportAllClassLayout,编译的时候就会输出所有类的详细内存结构,或者 /d1 reportSingleClassLayoutX 输出特定类 X 的内存结构。在 g++ 中,可通过 g++ -fdump-class-hierarchy xxx.cpp 命令获得类的详细内存结构。 以下是 g++ 输出的内存结构示例:

       Vtable for A
       A::_ZTV1A: 4u entries
       0     (int (*)(...))0
       8     (int (*)(...))(& _ZTI1A)
       16    (int (*)(...))A::f1
       24    (int (*)(...))A::f2
    
       Class A
          size=16 align=8
          base size=12 base align=8
       A (0x0x7f9333cd3840) 0
          vptr=((& A::_ZTV1A) + 16u)
    
       Vtable for B
       B::_ZTV1B: 5u entries
       0     (int (*)(...))0
       8     (int (*)(...))(& _ZTI1B)
       16    (int (*)(...))B::f1
       24    (int (*)(...))B::f2
       32    (int (*)(...))B::f3
    
       Class B
          size=16 align=8
          base size=16 base align=8
       B (0x0x7f9333c979c0) 0
          vptr=((& B::_ZTV1B) + 16u)
       A (0x0x7f9333cd38a0) 0
             primary-for B (0x0x7f9333c979c0)
    

    派生类重定义虚函数时,可以不加 virtual 关键字,其默认为虚函数,参数列表与返回类型都必须与基类一致。但有一种例外,当基类虚函数返回的是基类指针或引用时,派生类可返回派生类的指针或引用。

    基类虚函数在派生类中的可见性和普通函数一致,即如果派生类中没有重写基类虚函数,但包含了其他同名函数的重载,则派生类对象无法直接访问虚函数,但可以通过指针或引用转换为基类类型时调用基类的版本。但是该派生类被其他类继承时,派生类的虚表中依然包含了基类的虚函数。因此基类变量同样可以接受派生类的派生类的对象通过指针或引用的转换以及虚函数的调用。

    构造函数不能为虚函数。对于定义一个对象实例,在调用构造函数时,对象还不存在,虚表指针也没有被初始化,这时候讨论虚函数是没有意义的。对于指针或者引用,因为构造函数并不是一般意义上的成员函数,你无法通过指针和引用来调用构造函数,它们必须绑定一个已经构造好的对象才有意义,而这时候构造函数是否为虚函数同样是没有意义的。

    静态函数也不能为虚函数。因为静态函数是独立于对象实例而存在的,无法进行动态绑定。

    析构函数可以为虚函数。因为析构函数是在对象实例化后才调用的,而且可以像普通成员函数那样显式调用。实际上,析构函数最好应该为虚函数。特别是当派生类中存在着内存动态分配时,基类析构函数必须定义为虚函数。因为如果析构函数不是虚函数,那么delete基类指针时,其无法访问到派生类的析构函数,这就造成了内存泄漏问题。但因为虚函数需要维持虚表,会造成内存增加和效率降低的问题,所以编译器默认析构函数为非虚函数。

    展开全文
  • 4.虚函数与多态 5.继承体系同名函数的关系   1.单继承与多继承  1.1单继承:  一个子类只有一个直接父类。   class Person class Student : public Person class Monitor : public Student  1.2多继.....
  • 文章目录1 公有继承时三种类型的函数行为1.1 纯虚函数 (pure virtual)1.2 普通虚函数**1.2.1 方法一****1.2.2 方法二**1.3 非虚函数2 重写 (override)小结 1 公有继承时三种类型的函数行为 公有继承包含两部分:一是...
  • 在父类中添加虚函数,父类中调用这个虚函数,子类继承父类后,子类实现的虚函数就会
  • 继承中的虚函数与非虚函数

    千次阅读 2018-01-09 18:06:20
    子类继承父类, 包括继承虚函数和非虚函数 子类调用父类中的非虚函数 Base::PrintWord(), 在这个非虚函数里它又调用了虚函数doPrintWord()。 实验表明调用的虚函数执行的是重写的子类虚函数。 大致的代码可以...
  • 虚函数 虚函数是应在派生类中重新定义的成员函数。 当使用指针或对基类的引用来引用派生的类对象时,可以为该对象调用虚函数并执行该函数的派生类版本。 虚函数确保为该对象调用正确的函数,这与用于进行函数调用的...
  • C++继承&虚函数

    2016-05-26 21:39:25
    C++继承的应用和虚函数的概念
  • 虚函数;子类继承父类的接口与缺省实现,即子类可以自己定义实现,也可以直接用父类的缺省实现。...尽量不要在子类中重新实现非虚函数,因为既然继承过来,证明这些是必要的,如果在子类中重新实现,那继承就多此一举。
  • C++继承和多态(虚函数、纯虚函数、虚继承

    千次阅读 多人点赞 2019-03-05 11:32:40
    C++继承和多态(虚函数、纯虚函数、虚继承) 一:继承 继承的概念:为了代码的复用,保留基类的原始结构,并添加派生类的新成员。 继承的本质:代码复用 我们用下图解释下: 那么我们这里就可以提出几...
  • 结论:当父类定义了虚函数时,在子类进行继承的时候会将父类的虚函数表也给继承下来所以那一些虚函数在子类中也是virtual类型的,如果要对父类中的虚函数进行重写时或添加虚函数,顺序是:(图表说明为图一) ...
  • 虚函数 虚函数:类的成员函数前加virtual关键字,则称这个成员函数为虚函数虚函数重写 虚函数重写:当在子类中定义了一个与父类完全相同的虚函数时,则称子类的虚函数重写(覆盖)了父类的虚函数。 多态 多态:...
  • C++虚函数在多层继承中调用 & 在多重继承中调用1 多层继承1.1 介绍1.2 示例2 多重继承 1 多层继承 1.1 介绍 在一个类中的虚函数说明,只对派生类中重定义的函数有影响,对它的基类中的函数不起作用。 1.2 示例...
  • 那么,什么是虚函数呢,我们先来看看微软的解释: 虚函数是指一个类中你希望重载的成员函数,当你用一个基类指针或引用指向一个继承类对象的时候,你调用一个虚函数,实际调用的是继承类的版本。
  • 多态的运行期行为体现在虚函数上,虚函数通过继承方式来体现出多态作用,顶层 函数不属于成员函数,是不能被继承的 2.为什么C++不支持构造函数为虚函数? 这个原因很简单,主要是从语义上考虑,所以不支持。因为...
  • 继承作为面向对象编程的一种基本特征,其使用频率...  假设derived 虚继承自base类,那么derivd与base是一种“has a”的关系,即derived类有一个指向base类的vptr。(貌似有些牵强!某些编译器确实如此)  因此
  • 简单例子展示虚函数展现的多态特性,更改一处注释就能对比基类是否是虚函数带来的变化
  • //父类的Print是虚函数继承的时候被子类的Print函数覆盖 cin >> type >> color >> daytime; Bird bird(type, color, daytime); bird.Print(); animal = &bird; animal->Print(); return 0; }
  •  面向对象程序设计的核心思想是数据抽象、继承和动态绑定(也称之为动态联编)。通过数据抽象将类的接口与实现分离;使用继承可以定义相似的类型并对相似的关系建模;使用动态绑定可以在一定程度上忽视类型的区别,...
  • c++类继承中的虚函数

    千次阅读 2019-03-24 16:08:25
    方法在基类中被声明为...继承体系中,析构函数应该被声明为虚函数。如果不声明为虚函数,一个基类指针指向派生类对象时,delete这个指针时,将只会调用这个指针类型的(基类的)析构函数。如果声明为虚函数后,使...
  • C++学习:虚函数,虚继承,纯虚函数(virtual)虚析构函数 虚函数 纯虚函数 虚析构函数 虚继承 简介 在java这种高级语言中,有abstract和interface这两个关键字.代表的是抽象类和接口,但是在C++这门语言中,并没有专属的...
  • C++中虚函数和非虚函数重载在继承... 这里唯一想说明的一点就是,使用虚函数继承时,当继承类被强转成基类后调用虚函数,调用的还是继承类的虚函数。而重载方式的继承类被强转成基类再调用重载函数,则调用的是基类的
  • 高质量的C++多态讲解,详细讲解虚函数,虚函数表,虚函数继承虚函数继承下的内存分配等
  • 在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其容真实反应实际的函数。这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子类...
  • 子类继承父类虚函数

    千次阅读 2018-08-22 00:32:38
    //c++规定,当一个成员函数被声明为虚函数后,其派生类中的同名函数都自动成为虚函数。因此,在子类重新声明该虚函数时,可以加,也可以不加,但习惯上每一层声明函数时都加virtual,使程序更加清晰。   成员函数...
  • 所以虚函数只能用于类的继承层次结构中。  一个成员函数被声明为虚函数后,在同一类族中的类不能再定义一个非virtual的但与该虚函数具有相同的参数(包括个数和类型)和函数返回值类型的同名函数。  根据什么...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 130,054
精华内容 52,021
关键字:

虚函数继承

友情链接: temexd_mod.rar