继承,虚继承机制

继承体系中的作用域:

1.在继承体系中基类和派生类都有独立的作用域。

2.子类和父类中有同名成员、子类成员将屏蔽父类对成员的直接访问。(在子类成员函数中,可以使用基类:基类成员访问)-隐藏-重定义

3.注意在实际中在继承体系里面最好不要定义同名的成员。

注意事项:

(1)当基类构造函数不带参数时, 派生类不一定需要定义构造面数, 系统会自动的调用基类的无参构造函数; 然而当基类的构造函数那怕只带有一个参数, 它所有的派生类都必须定义构造函数,

甚至所定义的派生类构造函数的函数体可能为空, 它仅仅起参数的传递作用, "
Third(int x, int y)", 派生类 Third就不使用参数x和y, x和y只是被传递给了要调用的基类构造函数Second

(2)若基类使用默认构造函数或不带参数的构造函数, 则在派生类中定义构造函数时可略“:基类构造函数名(参数表)”, 此时若派生类也不需要构造函数, 则可不定义构造函数

(3)如果派生类的基类也是一个派生类, 每个派生类只需负责其直接基类数据成员的初始,依次上溯。

有虚函数只是增加vfptr;继承的类如果有增加虚函数,向vtable增加函数指针

        虚继承增加vbptr,注意:虚基类元素排在最后(这个是和 先基类后继承 不同之处)

https://www.cnblogs.com/Azhu/p/4443099.html

这篇博客写的较清楚继承机制:https://blog.csdn.net/dream_1996/article/details/68931347

继承内存分布

/**
    普通继承(没有使用虚基类)
*/

// 基类A
class A
{
public:
    int dataA;
};

class B : public A
{
public:
    int dataB;
};

class C : public A
{
public:
    int dataC;
};

class D : public B, public C
{
public:
    int dataD;
};

我们可以看到class D的内存布局如下:

从类D的内存布局可以看到A派生出B和C,B和C中分别包含A的成员。再由B和C派生出D,此时D包含了B和C的成员。这样D中就总共出现了2个A成员。大家注意到左边的几个数字,这几个数字表明了D中各成员在D中排列的起始地址,D中的五个成员变量(B::dataA、dataB、C::dataA、dataC、dataD)各占用4个字节,sizeof(D) = 20。

为了跟后文加以比较,我们再来看看B和C的内存布局:

虚继承内存分布

/**
    虚继承(虚基类)
*/

#include <iostream>

// 基类A
class A
{
public:
    int dataA;
};

class B : virtual public A
{
public:
    int dataB;
};

class C : virtual public A
{
public:
    int dataC;
};

class D : public B, public C
{
public:
    int dataD;
};

             

我们可以看到,菱形继承体系中的子类在内存布局上和普通多继承体系中的子类类有很大的不一样。对于类B和C,sizeof的值变成了12,除了包含类A的成员变量dataA外还多了一个指针vbptr,类D除了继承B、C各自的成员变量dataB、dataA和自己的成员变量外,还有两个分别属于B、C的指针。

那么类D对象的内存布局就变成如下的样子:

vbptr:继承自父类B中的指针

int dataB:继承自父类B的成员变量

vbptr:继承自父类C的指针

int dataC:继承自父类C的成员变量

int dataD:D自己的成员变量

int A:继承自父类A的成员变量

显然,虚继承之所以能够实现在多重派生子类中只保存一份共有基类的拷贝,关键在于vbptr指针。那vbptr到底指的是什么?又是如何实现虚继承的呢?其实上面的类D内存布局图中已经给出答案:

实际上,vbptr指的是虚基类表指针(virtual base table pointer),该指针指向了一个虚表(virtual table),虚表中记录了vbptr与本类的偏移地址;第二项是vbptr到共有基类元素之间的偏移量。在这个例子中,类B中的vbptr指向了虚表D::[email protected]@,虚表表明公共基类A的成员变量dataA距离类B开始处的位移为20,这样就找到了成员变量dataA,而虚继承也不用像普通多继承那样维持着公共基类的两份同样的拷贝,节省了存储空间。

为了进一步确定上面的想法是否正确,我们可以写一个简单的程序加以验证:

int main()
{
    D* d = new D;
    d->dataA = 10;
    d->dataB = 100;
    d->dataC = 1000;
    d->dataD = 10000;

    B* b = d; // 转化为基类B
    C* c = d; // 转化为基类C
    A* fromB = (B*) d;
    A* fromC = (C*) d;

    std::cout << "d address    : " << d << std::endl;
    std::cout << "b address    : " << b << std::endl;
    std::cout << "c address    : " << c << std::endl;
    std::cout << "fromB address: " << fromB << std::endl;
    std::cout << "fromC address: " << fromC << std::endl;
    std::cout << std::endl;

    std::cout << "vbptr address: " << (int*)d << std::endl;
    std::cout << "    [0] => " << *(int*)(*(int*)d) << std::endl;
    std::cout << "    [1] => " << *(((int*)(*(int*)d)) + 1)<< std::endl; // 偏移量20
    std::cout << "dataB value  : " << *((int*)d + 1) << std::endl;
    std::cout << "vbptr address: " << ((int*)d + 2) << std::endl;
    std::cout << "    [0] => " << *(int*)(*((int*)d + 2)) << std::endl;
    std::cout << "    [1] => " << *((int*)(*((int*)d + 2)) + 1) << std::endl; // 偏移量12
    std::cout << "dataC value  : " << *((int*)d + 3) << std::endl;
    std::cout << "dataD value  : " << *((int*)d + 4) << std::endl;
    std::cout << "dataA value  : " << *((int*)d + 5) << std::endl;
}

结果为

转自:https://blog.csdn.net/xiejingfa/article/details/48028491

原文地址:https://www.cnblogs.com/tianzeng/p/9769932.html

时间: 2024-08-28 10:20:19

继承,虚继承机制的相关文章

C++继承详解之三——菱形继承+虚继承内存对象模型详解vbptr(1)

在我个人学习继承的过程中,在网上查阅了许多资料,这些资料中有关菱形继承的知识都是加了虚函数的,也就是涉及了多态的问题,而我在那个时候并没有学习到多态这一块,所以看很多资料都是云里雾里的,那么这篇文章我想以我自己学习过程中的经验,由简到较难的先分析以下菱形继承,让初学者先对这个问题有一点概念,在后面会由浅入深的继续剖析. 本篇文章不会涉及到多态也就是虚函数的菱形继承,在后面的文章更新中,我会慢慢把这些内容都加进去. 菱形继承(也叫钻石继承)是下面的这种情况: 对应代码如下: #include <i

sizeof 和类继承 虚继承 求类大小

代码: #include <iostream> using namespace std; /* class a{ float k; // 4字节 virtual void foo(){} //有一个4字节的指针指向自己的虚函数表 }; class b : virtual public a{ virtual void f(){} }; 有这样的一个指针vptr_b_a,这个指针叫虚类指针,也是四个字节:还要包括类a的字节数,所以类b的字节数就求出来了. 运行结果: 8 16 */ /* clas

C++基础6 【继承】 类型兼容 satatic 多继承 虚继承 【多态】 案例 虚析构函数 重载重写重定义

[继承] 继承的访问控制域 图 类型兼容性原则  指针 与 引用 用子类直接初始化父类 类的继承模型示意 图 [继承结论] [非常重要的概念] 继承与组合混搭情况下,构造和析构调用原则 原则:先构造父类,再构造成员变量.最后构造自己 先析构自己,在析构成员变量.最后析构父类 继承中,同名的成员变量的处理办法 继承中,同名的成员函数的处理办法 派生类中的static关键字 如果静态成员变量,你没有使用,也没有初始化的话 编译不会报错 经典错误 : 类中函数默认是private的,无法在外部访问 具

C/C++ 多继承(虚继承和构造顺序)

C/C++:一个基类继承和多个基类继承的区别 1.对个基类继承会出现类之间嵌套时出现的同名问题,如果同名变量或者函数出现不在同一层次,则底层派生隐藏外层比如继承基类的同名变量和函数,不会出现二义性,而如果出现在同一阶层, 则会 出现二义性,解决办法:要么在同一阶层的底层(派生类)中重新定义可以解决,或者使用虚继承(减少部分二义性) 2.虚继承保证多次继承相同基类但只有一份基类数据(保证共享); 3.虚继承的构造顺序由编译器按照派生类列表从左往右寻找虚基类函数,先构造虚基类部分,然后按照正常构造从

C++之易混淆知识点四---虚函数与虚继承

C++面向对象中,虚函数与虚继承是两个完全不同的概念. 一.虚函数 C++程序中只要类中含有虚拟函数,编译程序都会为此类生成一个对应的虚拟函数跳转表(vtbl),该虚拟函数跳转表是一个又若干个虚拟函数体入口地址组成的一个线性表.派生类的虚拟函数跳转表的前半部分由父类的vtbl得出,但是里面的内容不一定相同,后半部分则对应着自己新定义的虚拟函数. class Employee { protected: char *Name; int Age; public: void changeAge(int

虚继承

------------------siwuxie095 看如下实例: 有 4 个类,其中:类 A 是父类,类 B 和 类 C 都继承 类 A, 而 类 D 继承了 类 B 和 类 C,称这种继承关系为 菱形继承 在菱形继承中,既有多继承,又有多重继承: 那么问题来了: 当实例化 D 的对象时,发现:D 是从 B 继承来的,B 是从 A 继承来的, D 也是从 C 继承来的,C 是从 A 继承来的 这样,D 中将含有两个完全一样的 A 的数据,这种情况是不能容忍的, 因为在一个对象中有两份完全相

C++ 虚函数和虚继承浅析

本文针对C++里的虚函数,虚继承表现和原理进行一些简单分析,有希望对大家学习C++有所帮助.下面都是以VC2008编译器对这两种机制内部实现为例. 虚函数 以下是百度百科对于虚函数的解释: 定义:在某基类中声明为 virtual 并在一个或多个派生类中被重新定 义的成员函数[1] 语法:virtual 函数返回类型 函数名(参数表) { 函数体 } 用途:实现多态性,通过指向派生类的基类指针,访问派生类中同名覆盖成员函数 函数声明和定义和普通的类成员函数一样,只是在返回值之前加入了关键字"vir

虚函数列表: 取出方法 // 虚函数工作原理和(虚)继承类的内存占用大小计算 32位机器上 sizeof(void *) // 4byte

#include <iostream> using namespace std; class A { public: A(){} virtual void geta(){ cout << "A:A" <<endl; } virtual void getb(){ cout << "A:B" <<endl; } }; class B :public A{ public: B(){} virtual void g

虚继承有什么作用

虚继承    在标准I/O库中的类都继承了一个共同的抽象基类ios,那个抽象基类管理流的条件状态并保存流所读写的缓冲区.istream和ostream类直接继承这个公共基类,库定义了另一个名为isotream的类,它同时继承istream和ostream,iostream类既可以对流进行读又可以对流进行写.如果I/O类型使用常规继承,则每个iostream对象可能包含两个ios子对象:一个包含在它的istream子对象中,另一个包含在它的 ostream子对象中.从设计角度讲,这个实现是错误的: