您的位置:首页 > 编程语言 > C语言/C++

剖析C++5种继承模型

2017-07-25 09:48 239 查看


剖析C++5种继承模型


单继承

若一个子类(派生类)只有一个父类(基类)时,叫单继承。 

若一个子类(派生类)有有多个父类(派生类)时,叫多继承。 

先来看看单继承模型
1.class C
2.{
3.public:
4.    C()
5.    {
6.        cout<<"C()"<<endl;
7.    }
8.
9.    int _c1;
10.};
11.
12.class B: public C
13.{
14.public:
15.    B()
16.    {
17.        cout<<"B()"<<endl;
18.    }
19.
20.    int _b1;
21.
22.};
23.
24.void test()
25.{
26.    B b;
27.    b._b1 = 10;
28.    b._c1 = 20;
29.}


C类中有自己的构造函数和成员变量_c1,B类是公有继承C类的,B类中也有自己的构造函数和成员变量,如果给_c1 和 _b1分别赋值,在调用的时候是先调用哪一个呢?


 

打开内存窗口,对构造出来的对象取地址可以看到,先赋值的是_c1的值,后赋值_b1的 
所以,在单继承模型中,先调用的是基类的成员变量,然后是派生类的


多继承模型

一个子类的继承对象可能有很多,如果基类有两个及以上就叫多继承,一般格式为 
派生类 : 继承方式 基类1,继承方式 基类2
1.class C1
2.{
3.public:
4.    C1()
5.    {
6.        cout<<"C1()"<<endl;
7.    }
8.
9.    int _c1;
10.};
11.
12.class C2
13.{
14.public:
15.    C2()
16.    {
17.        cout<<"C2()"<<endl;
18.    }
19.
20.    int _c2;
21.};
22.
23.
24.class B: public C1, public C2
25.{
26.public:
27.    B()
28.    {
29.        cout<<"B()"<<endl;
30.    }
31.
32.    int _b1;
33.
34.};
35.
36.void test()
37.{
38.    B b;
39.    b._b1 = 10;
40.    b._c1 = 20;
41.    b._c2 = 30;
42.}
43.


对于B类,是继承C1和C2的,继承顺序为先继承C1,然后再继承C2,继承方式都为公有继承,先来看看,如果分别给三个类里的成员变量赋值,通过派生类能得到什么样的继承顺序


 

打开内存窗口,用调试的方式,往下看可以看到先在最上面调用的是顺序在前的基类变量,然后是基类2,最后才是派生类的变量,所以在多继承模型中,最上层是基类1,然后是基类2,最后才是派生类


菱形继承(钻石继承)

在多继承中,派生类的基类有两个,所以叫多继承。如果现在多继承中的基类同时继承一个相同的基类,那么这种继承就叫做菱形继承。也叫钻石继承



1.class A
2.{
3.public:
4.    A()
5.    {
6.        cout<<"A()"<<endl;
7.    }
8.
9.    int _a1;
10.};
11.
12.
13.class C1: public A
14.{
15.public:
16.    C1()
17.    {
18.        cout<<"C1()"<<endl;
19.    }
20.
21.    int _c1;
22.};
23.
24.class C2: public A
25.{
26.public:
27.    C2()
28.    {
29.        cout<<"C2()"<<endl;
30.    }
31.
32.    int _c2;
33.};
34.
35.
36.class B: public C1, public C2
37.{
38.public:
39.    B()
40.    {
41.        cout<<"B()"<<endl;
42.    }
43.
44.    int _b1;
45.
46.};


现在B世纪城C1 和 C2 的, 而C1 C2 分别继承于A,每个类中都有自己独有的成员变量,现在通过派生类B给_b1,_c1,_c2,_a1,_a2分别赋值,然后编译一下,系统会报错,“对_a1的访问不明确”,这是因为,在B继承的C1和C2中都有继承A的成员变量,所以要对_a1进行赋值就必须加上类作用域限定符(例:b.C1::_a1 = 10)


 

这就是所谓的菱形继承二义性问题,在内存中,B继承下来的变量存储的位置是


 

菱形继承实际上就是每一个基类中都有上一层基类的变量,然后派生类把两个基类按继承顺序依次的继承下来。所以菱形继承的一般模型为




虚拟继承

为了解决菱形继承带来的二义性和数据冗余问题,就采取了一种方法:虚拟继承。 

虚拟继承:在继承权限前面加上关键字virtual
1.class C1
2.{
3.public:
4.    C1()
5.    {
6.        cout<<"C1()"<<endl;
7.    }
8.
9.    int _c1;
10.};
11.
12.
13.class B: virtual public C1
14.{
15.public:
16.    B()
17.    {
18.        cout<<"B()"<<endl;
19.    }
20.
21.    int _b1;
22.
23.};
24.
25.void test()
26.{
27.    B b;
28.    b._b1 = 10;
29.    b._c1 = 20;
30.}


现在,让B虚拟继承C1,然后给C1和B里面的变量依次赋值,是不是还和单继承或多继承一样,基类的数据存放在前面。打开内存窗口,然后对b取地址,可以看到先是一串数字,然后是0a(派生类变量)再是14(基类变量),那第一行中的数字是什么意思?


 

这串数字占了4个字节,所以应该想到他是一个地址,那就好办。再打开一个内存窗口,将这个地址查看一下(注:VS里存储数据是小端存储)


 

进入这个地址中去,第一行是00 00 00 00 ,第二行是08 00 00 00 ,先来解释一下这两行的值代表什么含义。00 00 00 00 表示这块地址相对于自己的偏移量, 08 00 00 00 表示相对基类的偏移量。而这块表就叫做偏移量表格。所以对于一般的虚拟继承都有以下的模型来解释他




菱形虚拟继承

在刚刚的菱形继承模型中,最后发现会存在二义性和数据冗余的问题,而虚拟继承不用存储基类的基类的值,改为了存偏移量表格,然后将基类放在最下面,这就消除了二义性问题,那么用虚拟继承解决菱形继承的问题就叫做菱形虚拟继承
1.class A
2.{
3.public:
4.    A()
5.    {
6.        cout<<"A()"<<endl;
7.    }
8.
9.    int _a1;
10.};
11.
12.
13.class C1: virtual public A
14.{
15.public:
16.    C1()
17.    {
18.        cout<<"C1()"<<endl;
19.    }
20.
21.    int _c1;
22.};
23.
24.class C2: virtual public A
25.{
26.public:
27.    C2()
28.    {
29.        cout<<"C2()"<<endl;
30.    }
31.
32.    int _c2;
33.};
34.
35.
36.class B: public C1, public C2
37.{
38.public:
39.    B()
40.    {
41.        cout<<"B()"<<endl;
42.    }
43.
44.    int _b1;
45.
46.};
47.
48.void test()
49.{
50.    B b;
51.    b._b1 = 10;
52.    b._c1 = 20;
53.    b._c2 = 30;
54.    b.C2::_a1 = 2;
55.}
56.


在菱形继承中,关键字virtual是在基类的上面加,而不是在派生类继承时加。


 

虚拟继承时,是在派生类里加一个指针,那么现在的菱形继承中指针是在C1还是C2处加。首先B肯定是在下面,现在C1和C2都继承于A,那么A就是C1和C2的下面,而A和B的顺序是什么样的呢? 

打开内存窗口,分别给成员变量赋值之后可以看到A是在最下面的,而 a0 dc 19 01 和 ac dc 29 01 在这就应该是C1和C2的偏移量表格指针


 

再进一步查看一下指向偏移量表格的指针


 

果然,和虚拟继承一样,在偏移量表格里,都有相对自己的偏移量和相对基类的偏移量,按顺序继承下来之后,C1在最上面所以相对基类A的偏移量就是20,然后继承的是C2,相对基类A的偏移量就是12。而虚拟继承刚开始说是为了解决菱形继承带来的二义性问题,那么现在能够直接通过B给A里面的变量赋值吗?


 

编译之后没问题,这样菱形继承所带来的二义性问题也就得到解决了。再来看看菱形虚拟继承的一半模型

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息