文具网站建设规划书,关闭网站弹窗代码,做贷款网站,wordpress修改文章模板C 之多重继承 1. C中class与struct。 在C里面#xff0c;class与struct没有本质的区别#xff0c;只是class的默认权限是private#xff0c;而struct则是public。这个概念也揭示了一点#xff1a;class和struct在内部存储结构上是一致的。所以我们可以利用这一点来探讨clas…C 之多重继承 1. C中class与struct。 在C里面class与struct没有本质的区别只是class的默认权限是private而struct则是public。这个概念也揭示了一点class和struct在内部存储结构上是一致的。所以我们可以利用这一点来探讨class的实现原理。我们可以将class转换成对应的struct对象通过struct的简单性来展示class的内存存储结构。 2. 关于class的基本内存结构 class包括成员变量和成员函数。对于成员变量其结构和struct的结构是一致的即按照声明的顺序安排每个成员的内存位置。对于成员函数如果是非虚函数包括普通函数和静态函数他们实际上同其他函数没有区别。对于非静态非虚函数默认隐藏了this参数。当编译时编译器将函数地址直接编译进去。因此这类函数没有动态能力。对于虚函数其函数地址将存在类this指针关联的虚函数内参见上一篇文章在运行时从虚函数表内取得地址后再调用。 这样一个不带虚函数的类的内存结构等同与一个类似的struct而带虚函数的类的内存结构等同于一个带有虚函数指针的struct。我用伪代码可以清晰的表示出来 [cpp] view plaincopy class ClassA { int a; flaot b; void *c; }; //等同于 struct StructA { int a; float b; void *c; span stylefont-family:Arial, Helvetica, sans-serif;}; //带虚函数的类/spanpre namecode classplainoffset A::a0, offset B::a8, offset B::b12 b0x7fffad613130, pa0x7fffad613138 class ClassAWithVirtual { int a; float b; void* c; virtual ~ClassAWithVirtual();};//等同于struct StructAWithVirtual { VTable *vtable; //包括虚函数表的vtable int a; float b; void *c;}; 3. class的单线继承 单线继承是很简单也很容易理解的一种继承方式。如果有class B继承自class A。那么在class B的低地址部分是class A的成员空间。这样class B可以直接转换为class A。 如果class A有虚函数那么class B必须也有虚函数表。那么如果class A没有虚函数而class B却有虚函数那么这个时候的内存分布应该是什么样呢class B还能否直接转换为class A呢? [cpp] view plaincopy #include stdio.h #include stddef.h #define OFFSET(X,m) (offsetof(X, m)) class A { public: int a; }; class B : public A{ public: int b; virtual ~B() { } }; int main() { printf(offset A::a%d, offset B::a%d, offset B::b%d\n, OFFSET(A,a), OFFSET(B, a), OFFSET(B, b)); B b; A *pa b; printf(b%p, pa%p\n, b, pa); return 0; } 在G 4.6 ubuntu 10.04下输出的结果是 [cpp] view plaincopy offset A::a0, offset B::a8, offset B::b12 b0x7fffad613130, pa0x7fffad613138 很明显的可以看到当B有虚函数时B必须在内存的开始处添加一个8字节64位系统的虚函数表指针。这个时候在class B内A的成员变量不能从偏移为0的位置开始了。3. 不带虚函数的C的多重继承类的内存分布 一般情况下如果一个类继承自两个类以上那么它的内存分布会像垒砖头那样一层一层的添加上去。比如 [cpp] view plaincopy #include stdio.h #include stddef.h class A { public: int a; }; class B { public: int b; }; class C : public A, public B{ public: int c; }; int main() { printf(Offset: C::a%d, C::b%d, C::c%d\n, offsetof(C, a), offsetof(C, b), offsetof(C, c)); C c; A *pa c; B *pb c; printf(c%p, pa%p, pb%p\n, c, pa, pb); return 0; }; 输出结果是 [cpp] view plaincopy Offset: C::a0, C::b4, C::c8 c0x7fffc90bbab0, pa0x7fffc90bbab0, pb0x7fffc90bbab4 这个与预想的很相似它的等价结构是 [cpp] view plaincopy struct C { struct A a; struct B b; int c; }; 的确像砖头一样先放A在放B最后C的成员放上去。 那么再考虑一种情况如果是这样一种继承方式 A / \ B C \ / D 那么A的成员在D内部是一份还是两份 [cpp] view plaincopy #include stdio.h #include stddef.h class A { public: int a; }; class B : public A{ public: int b; }; class C : public A { public: int c; }; class D : public B, public C { public: int d; }; int main() { printf(Offset: D::a%d, D::b%d, D::c%d, D::d\n, offsetof(D, B::a), offsetof(D, b), offsetof(D, c), offsetof(D, d)); D d; B *pb d; C *pc d; A *pa_b (A*)pb; A *pa_c (A*)pc; printf(d%p, pa_b%p, pa_c%p, pb%p, pc%p, d.B::a%p, d.C::a%p\n, d, pa_b, pa_c, pb, pc, d.B::a, d.C::a); return 0; }; 先看一下输出结果 [cpp] view plaincopy Offset: D::a0, D::b4, D::c12, D::d d0x7fffad730f30, pa_b0x7fffad730f30, pa_c0x7fffad730f38, pb0x7fffad730f30, pc0x7fffad730f38, d.B::a0x7fffad730f30, d.C::a0x7fffad730f38 事实上如果我直接写b.a是错误的因为编译器不知到应该选择那个a。同样的如果写A *pa d也是错误的。 结合输出结果class D内部仍然等同于 [cpp] view plaincopy struct D { struct B b; struct C c; int d; }; 而且存在两份A这两份A分别包含在B和C内部。在使用时必须正确指出是那个。由此可见不管继承层次有多深C总是按照这种垒砖头的方式叠加。如果有祖先类内部有重复包含那么C也会重复包含相同的内容。 这也提醒我们多重继承不能太复杂否则就很难搞清楚其结构关系了。 4. 带虚函数的类的多重继承的内存分布 带虚函数的情况下情况会变得非常复杂。首先对于最简单的一种继承方式 A B \ / C 我们需要分好几种情况来考虑 1、A B虚 C非虚 2、A B 非虚C虚 3、A B 其中一个虚C虚 4、A B C 都虚 4.1 A B 虚 C非虚 如果只有A虚 按照默认的规则A的内存会被安排在偏移0处。这个时候A的虚函数表也就是C的虚函数表。 如果只有B虚因为B的内存会被安排在A之后那么B的虚函数表应该在B所在位置C没有虚函数表。 4.2 A B 不虚C虚 这种情况下虚函数表应该在偏移0处然后才是A和B的内存结构。我们来验证一下