继承体系下派生类的对象模型

1.单继承
在定义继承时,如果一个派生类只有一个基础类,则称为单继承。

class A      //这就是一个简单的单继承
{
public:
    int _a;
};

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

2.多继承
多继承即一个子类可以有多个父类,它继承了多个父类的特性。

多继承可以看作是单继承的扩展。所谓多继承是指派生类具有多个 基类,派生类与每个基类之间的关系仍可看作是一个单继承。

class A             //一个简单的多继承
{
public:
    int _a;
};

class B 
{
public:
    int _b;
};

class C :public A,public B
{
public:
    int _c;
};

int main(void)
{
    C c;
    c._a = 0;
    c._b = 1;
    c._c = 2;
    cout << sizeof(C) << endl;
}

来看一下输出的结果:这里写图片描述
C类中的成员在内存中的分布:
这里写图片描述
可以看出先继承的父类的成员在前面,后继承的父类的成员在下面,子类成员在最下面。
3.菱形继承
两个子类继承同一个父类,而又有子类同时继承这两个子类。

class A 
{
public:
    int _a;
};

class B1:public A
{
public:
    int _b1;
};

class B2 :public A
{
public:
    int _b2;
};

class C :public B1, public B2
{
public:
    int _c;
};

int main()
{
    C c;
    c._a = 0;
    c._b1 = 1;
    c._b2 = 2;
    c._c = 3;
    cout << sizeof(C) << endl;  
}

这时出现了编译错误:
这里写图片描述
这时因为菱形继承下,B1和B2都继承了A的成员,C又多继承了B1和B2,用c对象访问_a时编译器不知道要访问的是B1中的_a还是B2中的_a,这就是菱形继承导致的二义性,要解决这个问题有两种方法:
① 指定访问B1或B2中成员
c.B1::_a = 0;
c.B2::_a = 5;
② 用虚拟继承解决菱形继承的二义性
4.虚拟继承
C++使用虚拟继承(Virtual Inheritance),使得派生类如果继承 基类多次,但只有一份基类的拷贝在派生类对象中。

虚拟继承的语法:

class 派生类: virtual 基类1,virtual 基类2,…,virtual 基类n{

…//派生类成员声明

};
多重继承构造执行顺序:

首先执行 虚基类的 构造函数,多个虚基类的构造函数按照被继承的顺序构造;

执行基类的 构造函数,多个基类的构造函数按照被继承的顺序构造;

执行成员对象的 构造函数,多个成员对象的构造函数按照申明的顺序构造;

执行 派生类自己的构造函数;

析构以与构造相反的顺序执行;

虚拟继承与普通继承的区别:
① 普通继承时编译器不会自动合成构造函数,虚拟继承会合成构造函数,通过一个实例来验证:

class A
{
public:
    int _a;
};

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

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

int main()
{
    C c;
    B b;
    cout << sizeof(B) << endl;
}

分别在创建C的对象和创建B的对象的代码前打断点,如果运行到断点处停下,说明调用构造函数创建了对象。
先在此处打上断点
这里写图片描述
运行查看结果
这里写图片描述
我们发现程序直接跳过这一行,说明普通继承时编译器没有自动合成构造函数。
再来看创建b对象
这里写图片描述
这里写图片描述
可以得出创建b对象时编译器调用了构造函数。
② 虚拟继承与普通多继承的对象成员在内存中的顺序不一样
普通多继承:

class A
{
public:
    int _a;
};

class B 
{
public:
    int _b;
};

class C :public A,public B
{
public:
    int _c;
};

int main()
{
    C c;
    c._a = 0;
    c._b = 1;
    c._c = 2;   
}

查看对象c的内存
这里写图片描述
可以发现顺序为,基类的成员->派生类的成员。
虚拟多继承

class A
{
public:
    int _a;
};

class B 
{
public:
    int _b;
};

class C :virtual public A,virtual public B
{
public:
    int _c;
};

int main()
{
    C c;
    c._a = 0;
    c._b = 1;
    c._c = 2;   
}

这里写图片描述
又结果得出虚拟继承下,派生类对象的内存顺序为:派生类->基类。
那么第一行的 内容是什么呢?
先猜测一下,是不是一个指针?
在地址栏中输入0x00ee6b30,结果如下
这里写图片描述
可以看出前两行的第一行存放的是基类对象基于基类的偏移量,为0。而第二行存放的是该派生类对象d基于基类的偏移量8。
其实这就是虚拟继承与普通继承的第三点区别:偏移量
5.菱形虚拟继承
虚拟继承解决了菱形继承的二义性,加入了虚拟继承的菱形继承称为菱形虚拟继承,也称钻石继承
这里写图片描述
B1和B2必须虚拟继承自A。

class A 
{
public:
    int _a;
};

class B1:virtual public A
{
public:
    int _b1;
};

class B2 :virtual public A
{
public:
    int _b2;
};

class C :public B1, public B2
{
public:
    int _c;
};

int main()
{
    C c;
    c._a  = 0;
    c._b1 = 1;
    c._b2 = 2;
    c._c  = 3;
    cout << sizeof(C) << endl;  
}

猜测一下输出结果应该是多少?
是不是16呢,C中有_a,_b1,_b2,_c四个成员,因为B1,B2是虚拟继承,所以结果是4*4是16。
经过测试发现结果是24,看一下对象c的内存
这里写图片描述
结合虚拟继承再推测,第一行与第三行分别是B1与B2的指针,他们的指向保存了各自的偏移量。
下面来验证一下推测,首先输入第一行的地址,再输入第二号的地址
这里写图片描述
这里写图片描述
而虚拟继承部分讲过第二行存放的是该派生类对象基于基类的偏移量。通过编译器的内存顺序就可以轻松的理解偏移量。
这里写图片描述
由图,以B1类为例,每一行是4个字节,基类到B1的偏移量是4*5=20,B1中的指针指向的内容第二行就是20。
通过菱形虚拟继承,派生类C中就只有一个A类的_a成员,解决了菱形继承的二义性。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值