class CBase
{
int a;
char *p;
};
那么运行cout<<"sizeof(CBase)="<<sizeof(CBase)<<endl之后输出什么? 8
第一步:空的
class CBase
{
}
运行cout<<"sizeof(CBase)="<<sizeof(CBase)<<endl;
sizeof(CBase)=1;
为什么空的什么都没有是1呢?
类的实例化,所谓类的实例化就是在内存中分配一块地址,每个实例在 内存中都有独一无二的地址。同样空类也会被实例化,所以编译器会给空类隐含的添加一个字节,这样空类实例化之后就有了 独一无二的地址了。所以空类的sizeof为1。
第二步:还是最初的那个类,运行结果:sizeof(CBase)=8
没什么说的,两个内部变量的大小。
第三步:添个虚函数
class CBase
{
public:
CBase(void);
virtual ~CBase(void);
private:
int a;
char *p;
};
再运行:sizeof(CBase)=12
C++ 类中有虚函数的时候有一个指向虚函数的指针(vptr),在32位系统分配指针大小为4字节。那么继承类呢?
第四步:
基类就是上面的
class CChild :
public CBase
{
public:
CChild(void);
~CChild(void);
private:
int b;
};
运行:cout<<"sizeof(CChild)="<<sizeof(CChild)<<endl;
输出:sizeof(CChild)=16;
微策略的一道笔试题
#include <iostream>
using namespace std;
class A
{
char i[3];
virtual void a();
};
class B:public A
{
char j[3];
virtual void b();
};
class C:public B
{
char k[3];
virtual void c();
};
int main()
{
cout<<sizeof(A)<<endl; //8
cout<<sizeof(B)<<endl; //12
cout<<sizeof(C)<<endl; //16
return 0;
}
类A:
vPtr 指针占4字节
char i[3]3字节
|-|-|-|-|
|-|-|-| 考虑内存对齐,因此占用的空间为4+4 = 8字节;
类B:
vPtr指针占4字节,char j[3]及继承来的数据成员char i[3],总的为6字节,考虑内存对齐,扩展为8字节,因此占用的总空间为4+8 = 12;
类C:
vPtr指针占4字节,char k[3]及继承来的数据成员char i[3],char j[3],总的字节为9字节,考虑内存对齐,扩展为12字节,因此总的内存空间为4+12 = 16;
注意:类只有一个虚函数表指针,指向第一个虚函数的地址,其他的虚函数的存放地址依次在虚函数表中记录可以通过 vPtr++ 来访问……
有虚函数的类都会自动的产生一个指向虚函数表的指针。(多态的实现就是利用虚函数表,对于每个类都新建一个虚函数表,该表包含自己的虚函数,还有父类的虚函数,如果子类函数覆盖父类函数的话就把函数指针指向子类里的函数。从而每个指向子类的基类指针调用函数时其实使用的是子类的虚函数表查找函数指针,自然就实现了多态了)。
虚继承是多继承时为了解决父类二意性而产生的继承方法。虚继承的子类中还包含一个指向父类的指针(即指向虚基表的指针)。
关于虚拟继承(相当于添加了一个接口):
class COneMember
{
public:
COneMember(int iValue = 0){m_iOne = iValue;};
private:
int m_iOne;
}; //4
class CTwoMember:virtual public COneMember
{
private:
int m_iTwo;
}//12
内存结构:
E8 2F 42 00 //指针,指向一个关于偏移量的数组,且称之虚基类偏移量表指针 CC CC CC CC // m_iTwo 00 00 00 00 // m_iOne(虚基类数据成员) |
关于闭合继承:
#include<iostream>
#include<vector>
using namespace std;
class ClassA
{
public:
ClassA(int iValue=1){m_iA = iValue;};
private:
int m_iA;
};
class ClassB:public ClassA
{
public:
ClassB(int iValue=2){m_iB = iValue;};
private:
int m_iB;
};
class ClassC:public ClassA
{
public:
ClassC(int iValue=3){m_iC = iValue;};
private:
int m_iC;
};
class CComplex :public ClassB, public ClassC
{
public:
CComplex(int iValue=4){m_iComplex = iValue;};
private:
int m_iComplex;
};
int main()
{
cout<<sizeof(ClassA)<<endl;//4
cout<<sizeof(ClassB)<<endl;//8
cout<<sizeof(ClassC)<<endl;//8
cout<<sizeof(CComplex)<<endl;//20
return 0;
}
闭合虚继承
#include<iostream>
#include<vector>
using namespace std;
class ClassA
{
public:
ClassA(int iValue=1){m_iA = iValue;};
private:
int m_iA;
};
class ClassB:virtual public ClassA
{
public:
ClassB(int iValue=2){m_iB = iValue;};
private:
int m_iB;
};
class ClassC:virtual public ClassA
{
public:
ClassC(int iValue=3){m_iC = iValue;};
private:
int m_iC;
};
class CComplex :public ClassB, public ClassC
{
public:
CComplex(int iValue=4){m_iComplex = iValue;};
private:
int m_iComplex;
};
int main()
{
cout<<sizeof(ClassA)<<endl;//4
cout<<sizeof(ClassB)<<endl;//12
cout<<sizeof(ClassC)<<endl;//12
cout<<sizeof(CComplex)<<endl;//24
return 0;
}
长度:24
内存结构:
14 30 42 00 //ClassB的虚基类偏移量表指针 02 00 00 00 //m_iB C4 2F 42 00 //ClassC的虚基类偏移量表指针 03 00 00 00 //m_iC 04 00 00 00 //m_iComplex 01 00 00 00 //m_iA |
评注:和预料中的一样,虚基类的成员m_iA只出现了一次,而且是在最后边。当然了,更复杂的情况要比这个难分析得多,但虚继承不是我们研究的重点,我们只需要知道:虚继承利用一个“虚基类偏移量表指针”来使得虚基类即使被重复继承也只会出现一次。
带有虚函数的类:
class CVirtualNull
{
public:
CVirtualNull(){printf("Construct\n");}
~CVirtualNull(){printf("Desctruct\n");}
virtual void Foo(){printf("Foo\n");} //或者是virtual void Foo()=0;都是占4个字节大小
};
长度:4
内存结构:
00 31 42 00 //指向虚函数表的指针(虚函数表后面简称“虚表”)
00423100:(虚表) 41 10 40 00 //指向虚函数Foo的指针
00401041: E9 78 02 00 00 E9 C3 03 … //函数Foo的内容(看不懂) |
子类有新的虚函数:
class CVirtualDerived: public CVirtualNull { public: CVirtualDerived(){m_iVD=0xFF;}; ~CVirtualDerived(){}; virtual void Foo2(){printf("Foo2\n");}; private: int m_iVD; }; |
长度:8
内存结构:
24 61 42 00 //虚表指针 FF 00 00 00 //m_iVD
00426124:(虚表) 23 10 40 00 50 10 40 00 |
评注:虚表还是只有一张,不会因为增加了新的虚函数而多出另一张来,新的虚函数的指针将添加在复制了的虚表的后面。
下面是几个规则,供参考:
1、空类所占空间大小为:1(字节,下同);
2、一个类中,虚函数本身、成员函数(包括静态与非静态)和静态数据成员都是不占用类对象的存储空间的;
3、因此一个对象的大小≥所有非静态成员大小的总和;
4、当类中声明了虚函数(不管是1个还是多个),那么在实例化对象时,编译器会自动在对象里安插一个指针vPtr指向虚函数表VTable;
5、虚承继的情况:由于涉及到虚函数表和虚基表,会同时增加一个(多重虚继承下对应多个)vfPtr指针指向虚函数表vfTable和一个vbPtr指针指向虚基表vbTable,这两者所占的空间大小为:8(或8乘以多继承时父类的个数);
6、在考虑以上内容所占空间的大小时,还要注意编译器下的“补齐”padding的影响,即编译器会插入多余的字节补齐;
7、类对象的大小=各非静态数据成员(包括父类的非静态数据成员但都不包括所有的成员函数)的总和+ vfptr指针(多继承下可能不止一个)+vbptr指针(多继承下可能不止一个)+编译器额外增加的字节