无继承
空类
class A
{
};
void test1()
{
A a;
std::cout << "class A size:" << sizeof(a) << "n";
A array_a[10];
std::cout << "class array a size:" << sizeof(array_a) << "n";
}
class A
{
};
void test1()
{
A a;
std::cout << "class A size:" << sizeof(a) << "n";
A array_a[10];
std::cout << "class array a size:" << sizeof(array_a) << "n";
}
从上图的输出结果可以看出,一个空类的大小并非想象中的0,而是1。
而且从上图中也可以看出,在数组情况下,其值为未初始化的默认栈值。
仅存在数据元素的类
class B
{
uint8_t var1;
};
class C
{
uint16_t var1;
};
class D
{
uint8_t var1;
uint32_t var2;
};
void test2()
{
B b;
std::cout << "class B size:" << sizeof(b) << "n";
C c;
std::cout << "class C size:" << sizeof(c) << "n";
D d;
std::cout << "class D size:" << sizeof(d) << "n";
}
从上图中可知,
当仅定义一个 uint8_t 类型时,所占用的空间和空类是相同的。
当定义的数据类型为 uint16_t 类型时,占用的空间与数据元素保持相同。
当同时定义多个数据类型时,则由于数据对齐的原因,导致数据大小大于了实际的数据类型的相加的总大小。
仅有普通方法的类
class E
{
public:
void fun1()
{
}
int fun2()
{
return 0;
}
};
void test3()
{
E e;
std::cout << "class E size:" << sizeof(e) << "n";
printf("e position:%08Xn", &e);
printf("E::fun1 position:%08Xn", &E::fun1);
printf("E::fun2 position:%08Xn", &E::fun2);
}
从上述截图中可知,仅有普通方法的类,其大小并没有变化,与空类相同。
有虚函数的类
class F
{
public:
void fun1()
{
}
virtual int fun2()
{
return 0;
}
};
void test4()
{
F f;
std::cout << "class F size:" << sizeof(f) << "n";
printf("f position:%08Xn", &f);
printf("F::fun1 position:%08Xn", &F::fun1);
printf("F::fun2 position:%08Xn", &F::fun2);
std::cout << "Hello World!n";
}
从上图中输出的信息中可以看出,当函数具备虚函数时,则所占空间大小变为了4。同时从后面图中,可以得知,所占用的4个字节所存储的内容为vfptr,即虚函数表指针。通过比较前后函数的地址,可知虚函数表中的内容即为虚函数的地址。
之后我们继续添加虚函数的个数。
class F
{
public:
void fun1()
{
}
virtual int fun2()
{
return 0;
}
virtual int fun3()
{
return 0;
}
};
void test4()
{
F f;
std::cout << "class F size:" << sizeof(f) << "n";
printf("f position:%08Xn", &f);
printf("F::fun1 position:%08Xn", &F::fun1);
printf("F::fun2 position:%08Xn", &F::fun2);
std::cout << "Hello World!n";
}
通过继续添加虚函数的个数,可知,多个虚函数并不会影响类对象所占空间的大小。但却会影响虚函数表的内容。如第二个图中所示,虚函数表中的内容变为了两个函数指针。
多情况混合
class G
{
public:
uint32_t var1;
void fun1()
{
}
virtual int fun2()
{
return 0;
}
virtual int fun3()
{
return 0;
}
uint32_t var2;
};
void test5()
{
G g;
std::cout << "class G size:" << sizeof(g) << "n";
printf("g position:%08Xn", &g);
printf("G::fun1 position:%08Xn", &G::fun1);
printf("G::fun2 position:%08Xn", &G::fun2);
std::cout << "Hello World!n";
}
由上图可知,在几种元素都存在的情况下,数据元素会按照实际情况占用空间大小。虚函数存在时则会有一个固定4字节的空间用来存放虚函数表指针。
单继承
父类与子类仅有数据元素
class H
{
public:
uint32_t var1;
uint32_t var2;
};
class I : public H
{
public :
uint32_t var3;
uint8_t var4;
};
void test6()
{
I i;
std::cout << "class I size:" << sizeof(i) << "n";
std::cout << "Hello World!n";
}
class H
{
public:
uint32_t var1;
uint32_t var2;
};
class I : public H
{
public :
uint32_t var3;
uint8_t var4;
};
void test6()
{
I i;
std::cout << "class I size:" << sizeof(i) << "n";
std::cout << "Hello World!n";
}
如上图所示可知,在这种情况下,父类数据元素会与子类数据元素进行顺序叠加,其中父类数据元素在子类数据元素之前。
父类与子类仅有普通方法
class J
{
public:
void fun1() {
}
int fun2() {
return 0;
}
};
class K : public J
{
public:
void fun3() {
}
int fun4() {
return 0;
}
};
void test7()
{
K k;
std::cout << "class K size:" << sizeof(k) << "n";
printf("k position:%pn", &k);
printf("J::fun1 position:%pn", &J::fun1);
printf("J::fun2 position:%pn", &J::fun2);
printf("K::fun3 position:%pn", &K::fun3);
printf("K::fun4 position:%pn", &K::fun4);
std::cout << "Hello World!n";
}
可知,单纯的普通方法并不会影响到类对象的大小。无论是否继承。
父类无虚函数,子类有虚函数
与有虚函数的类相同结论。
父类有虚函数,子类无虚函数
class L
{
public:
void fun1() {
}
virtual int fun2() {
return 0;
}
};
class M : public L
{
public:
void fun3() {
}
int fun4() {
return 0;
}
};
void test8()
{
M m;
std::cout << "class M size:" << sizeof(m) << "n";
printf("m position:%pn", &m);
printf("L::fun1 position:%pn", &L::fun1);
printf("L::fun2 position:%pn", &L::fun2);
printf("M::fun3 position:%pn", &M::fun3);
printf("M::fun4 position:%pn", &M::fun4);
std::cout << "Hello World!n";
}
如上图所示,在继承了虚函数后,则也会占据4个字节,用来存放虚函数表指针。其中虚函数表中存放了父类的虚函数表。
父类与子类均有虚函数
class N
{
public:
void fun1() {
}
virtual int fun2() {
return 0;
}
};
class O : public N
{
public:
void fun3() {
}
virtual int fun4() {
return 0;
}
};
void test9()
{
O o;
std::cout << "class O size:" << sizeof(o) << "n";
printf("o position:%pn", &o);
printf("N::fun1 position:%pn", &N::fun1);
printf("N::fun2 position:%pn", &N::fun2);
printf("O::fun3 position:%pn", &O::fun3);
printf("O::fun4 position:%pn", &O::fun4);
std::cout << "Hello World!n";
}
从上面可知,父子均有虚函数时,父子类的虚函数表中会顺序存放两者的虚函数,父类的虚函数在前,子类的虚函数在后,同时会用0x00000000来结束虚函数表。
多种情况混合
class P
{
public:
uint32_t var1;
void fun1() {
}
virtual int fun2() {
return 0;
}
};
class Q : public P
{
public:
uint32_t var2;
void fun3() {
}
virtual int fun4() {
return 0;
}
};
void test10()
{
Q q;
std::cout << "class q size:" << sizeof(q) << "n";
printf("q position:%pn", &q);
printf("P::fun1 position:%pn", &P::fun1);
printf("P::fun2 position:%pn", &P::fun2);
printf("Q::fun3 position:%pn", &Q::fun3);
printf("Q::fun4 position:%pn", &Q::fun4);
std::cout << "Hello World!n";
}
没有什么奇特的,同样都是最开始为虚函数表指针,之后为父类的数据元素,最后为子类的数据元素。虚函数表中,父类的虚函数在前,子类的虚函数在后。最后以0x00000000结束。
多继承
多种情况混合
class R
{
public:
uint32_t var1;
void fun1() {
}
virtual int fun2() {
printf("class R fun2n");
return 0;
}
virtual int fun3() {
return 0;
}
};
class S
{
public:
uint32_t var1;
void fun1() {
}
virtual int fun2() {
printf("class S fun2n");
return 0;
}
};
class T : public R, public S
{
public:
uint32_t var2;
int fun3() {
return 2;
}
virtual int fun4() {
return 0;
}
};
void test11()
{
T t;
std::cout << "class t size:" << sizeof(t) << "n";
void* tmpt;
R* rt;
S* st;
rt = &t;
st = &t;
tmpt = &t;
// t.fun2();
rt->fun2();
st->fun2();
printf("R::fun1 position:%pn", &R::fun1);
printf("R::fun2 position:%pn", &R::fun2);
printf("R::fun3 position:%pn", &R::fun3);
printf("S::fun1 position:%pn", &S::fun1);
printf("S::fun2 position:%pn", &S::fun2);
printf("T::fun3 position:%pn", &T::fun3);
printf("T::fun4 position:%pn", &T::fun4);
std::cout << "Hello World!n";
}
class R
{
public:
uint32_t var1;
void fun1() {
}
virtual int fun2() {
printf("class R fun2n");
return 0;
}
virtual int fun3() {
return 0;
}
};
class S
{
public:
uint32_t var1;
void fun1() {
}
virtual int fun2() {
printf("class S fun2n");
return 0;
}
};
class T : public R, public S
{
public:
uint32_t var2;
int fun3() {
return 2;
}
virtual int fun4() {
return 0;
}
};
void test11()
{
T t;
std::cout << "class t size:" << sizeof(t) << "n";
void* tmpt;
R* rt;
S* st;
rt = &t;
st = &t;
tmpt = &t;
// t.fun2();
rt->fun2();
st->fun2();
printf("R::fun1 position:%pn", &R::fun1);
printf("R::fun2 position:%pn", &R::fun2);
printf("R::fun3 position:%pn", &R::fun3);
printf("S::fun1 position:%pn", &S::fun1);
printf("S::fun2 position:%pn", &S::fun2);
printf("T::fun3 position:%pn", &T::fun3);
printf("T::fun4 position:%pn", &T::fun4);
std::cout << "Hello World!n";
}
如图中可知,当在多继承中,如果父类中丢存在虚函数,则在最终的子类中,会按照顺序依次排布父类相关内容。
首先排布第一个父类的虚函数表指针,之后是第一个父类的数据元素。
然后排布的是第二个父类的虚函数表指针,之后是第二个父类的数据元素。
最后才是子类所定义的数据元素。
而子类中的虚函数则会存放在第一个父类的虚函数表中。结束时依旧以 0x00000000 结尾。
同时上面比较神奇的是,
rt = &t;
st = &t;
但是最终 rt 和 st的值并不相同。此处估计有个类型转换。



