1、首先类大小计算遵循结构体的对齐原则
2、类的大小与普通数据成员有关,与静态成员变量和成员函数无关,包括(静态成员变量,静态成员变量函数,普通成员函数,静态常量等与类的大小均无关)
3、虚函数对类的大小有影响,这是因为虚函数表指针带来的影响
4、虚继承对类的大小有影响,这是由于虚基表指针的影响
4、空类的大小为1
对其原则:
按照结构体中长度最长的变量进行对齐,例如:
class A
{
public:
int a;
char b;
};
A a1;
sizeof(a1);
该案例输出a1的大小为8,char类型要与int 类型对齐,对齐都各占4个字节
空类的大小为1;
这里的空类指的是该类不含任何非静态变量,没有虚函数,有没有虚基类,例如;如下代码的输出为1
class A { };
int main()
{
A a1;
cout<<sizeof(a1);
}
但有两种情况例外,一种是涉及到空类的继承,另一种是一个类中包含空类对象成员变量
当是空类的继承时,如下面的代码,此时输出a1的大小为4,空类继承时空基类的一个字节不会加到派生类中
class A { };
class B :public A {
public:
int a;
};
int main()
{
B a1;
cout<<sizeof(a1);
}
当类中包含空类对象成员变量时,参考如下的代码,此时输出的a1的大小为8,此时把空类的一个字节计算在内,结果之所以为8,是因为对齐原则
class A { };
class B {
public:
int a;
A b;
};
int main()
{
B a1;
cout<<sizeof(a1);
}
类中含有虚函数成员:
虚函数的工作原理;
虚函数是通过虚函数表(Virtual Table)来实现的,编译器必须保证虚函数表指针存在于对象数据成员的最前面,为了正确取得虚函数的偏移量。每当创建一个包含有虚函数的类或从包含有虚函数的类派生一个类时,编译器就会为这个类创建一个虚函数表(VTABLE)保存该类所有虚函数的地址,其实这个VTABLE的作用就是保存自己类中所有虚函数的地址,可以把VTABLE形象地看成一个函数指针数组,这个数组的每个元素存放的就是虚函数的地址。在每个带有虚函数的类中,编译器秘密地置入一指针,称为vpoint(缩写为Vptr),指向这个对象的VTABLE。 当构造该派生类对象时,其成员Vptr被初始化指向该派生类的VTABLE。所以可以认为VTABLE是该类的所有对象共有的,在定义该类时被初始化;而Vptr则是每个类对象都有独立一份的,且在该类对象被构造时被初始化
如下代码输出结果为8
class B {
public:
virtual void f() = 0;
int a;
};
int main()
{
cout<<sizeof(B);
}
基类含有虚函数的继承:
(1)在派生类中不对基类的虚函数进行覆盖,同时派生类中还拥有自己的虚函数,如下图:
此时成员的存放如下:
(2)在派生类中对基类的虚函数进行覆盖,如下图:
此时派生对象中存储情况如下,被覆盖掉的虚函数放在最前面,其他依次序排列
(3)多继承:无虚函数覆盖,假设基类和派生类之间有如下关系:
对于派生类实例中的虚函数表,是下面这个样子:
(4)多重继承,含虚函数覆盖,假设,基类和派生类又如下关系:派生类中覆盖了基类的虚函数f
下面是对于派生类实例中的虚函数表的图:
注意,如下的代码:该段代码的输出结果为20,E继承了B和C,B和C中都包含了虚函数,因此E的大小应该为B,C,E中非静态变量+两个虚表指针,需要注意的是C类,C类中有两个char变量,此处采取的对齐原则是存储两个char型变量,需要两个字节,不够四个字节,故将其补齐为4个字节,最后的结果为4+4+4+2*4。(此处指针占4个字节)
class B
{
public:
int b1;
virtual void func0() { }
};
class C
{
public:
char c1;
char c2;
virtual void func1() { }
virtual void func2() { }
};
class E : public B, public C
{
public:
int e;
virtual void func0() { }
virtual void func1() { }
};
int main()
{
cout<<sizeof(E);
}