c++多继承浅析

                     图一                                                                                       图二

先测试图一结构的多继承:

 1 #include<iostream>
 2 using namespace std;
 3
 4 class Parent {
 5 public:
 6     Parent():a(100),b(200),c(300)
 7     {
 8         cout << "parent 构造。。。\n";
 9     }
10     ~Parent()
11     {
12         cout << "Parent 析构。。。\n";
13     }
14     int a;
15     int b;
16     int c;
17     void p_print()
18     {
19         cout << "a b c is" << a << " " << b << " " << c << endl;
20     }
21
22 };
23 class Child1 :virtual public Parent
24 {
25 public:
26     Child1() :Parent(), a(0), b(0), c(0) { cout << "child 构造\n"; }
27     ~Child1()
28     {
29         cout << "child 析构,,,\n";
30     }
31     void c_print()
32     {
33         cout << "a b c is" << a << " " << b << " " << c << endl;
34     }
35     int a;
36     int b;
37     int c;
38 };
39 class Child2 :virtual public Parent
40 {
41 public:
42     Child2() :Parent(), a(1), b(2), c(3) { cout << "child 构造\n"; }
43     ~Child2()
44     {
45         cout << "child 析构,,,\n";
46     }
47     void c_print()
48     {
49         cout << "a b c is" << a << " " << b << " " << c << endl;
50     }
51     int a;
52     int b;
53     int c;
54 };
55 class Child3 :public Child1,public Child2
56 {
57 public:
58     Child3() :Parent(),Child1(),Child2(), a(10), b(20), c(30)       { cout << "child 构造\n"; }//如果前面没有使用虚继承,这里初始化Parent构造函数将出错
59     ~Child3()
60     {
61         cout << "child 析构,,,\n";
62     }
63     void c_print()
64     {
65         cout << "a b c is" << a << " " << b << " " << c << endl;
66     }
67     int a;
68     int b;
69     int c;
70 };
71 int main()
72 {
73     Child3 c3;
74
75     return 0;
76 }

虚继承的目的是令某个类做出声明,承诺愿意共享它的基类。其中,共享的基类对象称为虚基类。在这种机制下,无论虚基类在继承体系中出现多少次,在派生类中都只包含唯一一个共享的虚基类对象。

为了说明情况,我们把上述代码更改如下:

 1 #include<iostream>
 2 using namespace std;
 3
 4 class Parent {
 5 public:
 6     Parent():a(100),b(200),c(300)
 7     {
 8         cout << "parent 无参构造。。。\n";
 9     }
10     Parent(int test) :a(1000), b(2000), c(3000)
11     {
12         cout << "parent 有参构造。。。\n";
13     }
14     ~Parent()
15     {
16         cout << "Parent 析构。。。\n";
17     }
18     int a;
19     int b;
20     int c;
21     void p_print()
22     {
23         cout << "a b c is" << a << " " << b << " " << c << endl;
24     }
25
26 };
27 class Child1 :  public Parent
28 {
29 public:
30     Child1() :Parent(), b(0), c(0) { cout << "child 构造\n"; }
31     ~Child1()
32     {
33         cout << "child 析构,,,\n";
34     }
35     void c_print()
36     {
37         cout << "a b c is" << a << " " << b << " " << c << endl;
38     }
39
40     //int a;
41     int b;
42     int c;
43 };
44 class Child2 : public Parent
45 {
46 public:
47     Child2() :Parent(), b(2), c(3) { cout << "child 构造\n"; }
48     ~Child2()
49     {
50         cout << "child 析构,,,\n";
51     }
52     void c_print()
53     {
54         cout << "a b c is" << a << " " << b << " " << c << endl;
55     }
56     //int a;
57     int b;
58     int c;
59 };
60 class Child3 : public Child1,  public Child2
61 {
62 public:
63     Child3() :Child1(),Child2(), b(20), c(30) { cout << "child 构造\n"; }
64     ~Child3()
65     {
66         cout << "child 析构,,,\n";
67     }
68     //int a;
69     int b;
70     int c;
71 };
72 int main()
73 {
74     Child3 c3;
75     c3.a = 100;
76     return 0;
77 }

报错如下:

由于在parent类中a被Child1,Child2分别继承,而用Chils3类定义对象c3要去访问属性a,编译器发出抱怨也是应该的,因为它不知道这个a是Child1还是Child2还是parent中的。所以我们要去除这样的二义性。通过把parent类变成虚基类,可以做到,代码如下:

 1 #include<iostream>
 2 using namespace std;
 3
 4 class Parent {
 5 public:
 6     Parent():a(100),b(200),c(300)
 7     {
 8         cout << "parent 无参构造。。。\n";
 9     }
10     Parent(int test) :a(1000), b(2000), c(3000)
11     {
12         cout << "parent 有参构造。。。\n";
13     }
14     ~Parent()
15     {
16         cout << "Parent 析构。。。\n";
17     }
18     int a;
19     int b;
20     int c;
21     void p_print()
22     {
23         cout << "a b c is" << a << " " << b << " " << c << endl;
24     }
25
26 };
27 class Child1 : virtual public Parent
28 {
29 public:
30     Child1() :Parent(), b(0), c(0) { cout << "child 构造\n"; }
31     ~Child1()
32     {
33         cout << "child 析构,,,\n";
34     }
35     void c_print()
36     {
37         cout << "a b c is" << a << " " << b << " " << c << endl;
38     }
39
40     //int a;
41     int b;
42     int c;
43 };
44 class Child2 : virtual public Parent
45 {
46 public:
47     Child2() :Parent(), b(2), c(3) { cout << "child 构造\n"; }
48     ~Child2()
49     {
50         cout << "child 析构,,,\n";
51     }
52     void c_print()
53     {
54         cout << "a b c is" << a << " " << b << " " << c << endl;
55     }
56     //int a;
57     int b;
58     int c;
59 };
60 class Child3 : public Child1,  public Child2
61 {
62 public:
63     Child3() :Child1(),Child2(), b(20), c(30) { cout << "child 构造\n"; }
64     ~Child3()
65     {
66         cout << "child 析构,,,\n";
67     }
68     //int a;
69     int b;
70     int c;
71 };
72 int main()
73 {
74     Child3 c3;
75     c3.a = 100;
76     return 0;
77 }

其实也就是在继承时增加virtual关键字,让派生类包含唯一的共享虚基类。

问题抛出:

在child3中的构造函数:

Child3() :Child1(),Child2(), b(20), c(30) { cout << "child 构造\n"; }我们把child1和child2的基类构造函数改成有参的,看看child3继承的老祖宗属性是如何的:

 1 #include<iostream>
 2 using namespace std;
 3
 4 class Parent {
 5 public:
 6     Parent():a(100),b(200),c(300)
 7     {
 8         cout << "parent 无参构造。。。\n";
 9     }
10     Parent(int test) :a(1000), b(2000), c(3000)
11     {
12         cout << "parent 有参构造。。。\n";
13     }
14     ~Parent()
15     {
16         cout << "Parent 析构。。。\n";
17     }
18     int a;
19     int b;
20     int c;
21     void p_print()
22     {
23         cout << "a b c is" << a << " " << b << " " << c << endl;
24     }
25
26 };
27 class Child1 : virtual public Parent
28 {
29 public:
30     Child1() :Parent(1), b(0), c(0) { cout << "child 构造\n"; }
31     ~Child1()
32     {
33         cout << "child 析构,,,\n";
34     }
35     void c1_print()
36     {
37         cout << "a b c is" << a << " " << b << " " << c << endl;
38     }
39
40     //int a;
41     int b;
42     int c;
43 };
44 class Child2 : virtual public Parent
45 {
46 public:
47     Child2() :Parent(1), b(2), c(3) { cout << "child 构造\n"; }
48     ~Child2()
49     {
50         cout << "child 析构,,,\n";
51     }
52     void c2_print()
53     {
54         cout << "a b c is" << a << " " << b << " " << c << endl;
55     }
56     //int a;
57     int b;
58     int c;
59 };
60 class Child3 : public Child1,  public Child2
61 {
62 public:
63     Child3() : Child1(),Child2(), b(20), c(30) { cout << "child 构造\n"; }
64     ~Child3()
65     {
66         cout << "child 析构,,,\n";
67     }
68     void c3_print()
69     {
70         cout << "a b c is" << a << " " << b << " " << c << endl;
71     }
72     //int a;
73     int b;
74     int c;
75 };
76 int main()
77 {
78     Child3 c3;
79     c3.c3_print();
80     c3.p_print();
81     return 0;
82 }

运行结果:

可以看到,child3的属性a是100,等价于parent的无参构造函数,尽管我们的child1和child2用的有参构造函数初始化,但子类child3最终继承的虚基类还是要通过自身的构造函数初始化列表来完成,要想child3中的属性a是parent有参构造的属性a,更改child3的构造函数初始化列表为:

1 Child3() : Parent(1),Child1(),Child2(), b(20), c(30) { cout << "child 构造\n"; }

运行结果:

这样就调用了有参数的parent属性a了。结论:类似于初始化成员的过程,派生类构造函数同样是通过构造函数初始化列表来将实参传递给基类构造函数,在多继承中,哪怕直接基类(child1和child2)构造了间接基类(parent)的无参构造函数,但要传递给派生类child3的属性时,还是根据child3的构造函数初始化列表决定的。

对上面的访问属性a有歧义再探:

上面说得:由于在parent类中a被Child1,Child2分别继承,而用Chils3类定义对象c3要去访问属性a,编译器发出抱怨也是应该的,因为它不知道这个a是Child1还是Child2还是parent中的。所以我们要去除这样的二义性。通过把parent类变成虚基类,可以做到.

当然,问题的关键就在于编译器不知道属性a是哪个对象中,我们除了增加virtual关键字外,还可以:

在child3类中定义同名成员a,这样通过c3访问a时,默认从child3类中寻找:

 1 #include<iostream>
 2 using namespace std;
 3
 4 class Parent {
 5 public:
 6     Parent():a(100),b(200),c(300)
 7     {
 8         cout << "parent 无参构造。。。\n";
 9     }
10     Parent(int test) :a(1000), b(2000), c(3000)
11     {
12         cout << "parent 有参构造。。。\n";
13     }
14     ~Parent()
15     {
16         cout << "Parent 析构。。。\n";
17     }
18     int a;
19     int b;
20     int c;
21     void p_print()
22     {
23         cout << "a b c is" << a << " " << b << " " << c << endl;
24     }
25
26 };
27 class Child1 :  public Parent
28 {
29 public:
30     Child1() :Parent(1), b(0), c(0) { cout << "child 构造\n"; }
31     ~Child1()
32     {
33         cout << "child 析构,,,\n";
34     }
35     void c1_print()
36     {
37         cout << "a b c is" << a << " " << b << " " << c << endl;
38     }
39
40     //int a;
41     int b;
42     int c;
43 };
44 class Child2 :  public Parent
45 {
46 public:
47     Child2() :Parent(1), b(2), c(3) { cout << "child 构造\n"; }
48     ~Child2()
49     {
50         cout << "child 析构,,,\n";
51     }
52     void c2_print()
53     {
54         cout << "a b c is" << a << " " << b << " " << c << endl;
55     }
56     //int a;
57     int b;
58     int c;
59 };
60 class Child3 : public Child1,  public Child2
61 {
62 public:
63     Child3() : Child1(),Child2(), b(20), c(30) { cout << "child 构造\n"; }
64     ~Child3()
65     {
66         cout << "child 析构,,,\n";
67     }
68     void c3_print()
69     {
70         cout << "a b c is" << a << " " << b << " " << c << endl;
71     }
72     int a;
73     int b;
74     int c;
75 };
76 int main()
77 {
78     Child3 c3;
79     c3.a = 123;
80     c3.c3_print();
81
82     return 0;
83 }

这样可以编译通过了,但此时的a是child3中的,仅仅是逃过编译器的错误检测,并没有解决多继承的问题;

我们可能在想,能否通过域作用符来访问去除歧义呢?

test:

int main()
{
    Child3 c3;
    c3.Child1::a = 123;
    c3.c3_print();

    return 0;
}

依旧报错:

由此可见,多继承时复杂繁琐的,好在一般工程中都会尽量避免使用多继承,但是多继承也是有应用的,至少Qt中就有多继承,就像C语言中的goto语句一样,一般不建议使用,但总会有它上场的时候,goto用于跳出多重循环或者检错,多继承用在一个类想用时拥有其他类的某些功能。所以,必要的多继承语法还是得了解。

对于图2:

 1 #include<iostream>
 2 using namespace std;
 3
 4 class Child1
 5 {
 6 public:
 7     Child1() :a(0), b(0), c(0) { cout << "child 构造\n"; }
 8     ~Child1()
 9     {
10         cout << "child 析构,,,\n";
11     }
12     void c1_print()
13     {
14         cout << "a b c is" << a << " " << b << " " << c << endl;
15     }
16
17     int a;
18     int b;
19     int c;
20 };
21 class Child2
22 {
23 public:
24     Child2() :a(1), b(2), c(3) { cout << "child 构造\n"; }
25     ~Child2()
26     {
27         cout << "child 析构,,,\n";
28     }
29     void c2_print()
30     {
31         cout << "a b c is" << a << " " << b << " " << c << endl;
32     }
33     int a;
34     int b;
35     int c;
36 };
37 class Child3 : public Child1,  public Child2
38 {
39 public:
40     Child3() : Child1(),Child2(), b(20), c(30) { cout << "child 构造\n"; }
41     ~Child3()
42     {
43         cout << "child 析构,,,\n";
44     }
45     void c3_print()
46     {
47         //cout << "a b c is" << a << " " << b << " " << c << endl;
48     }
49     //int a;
50     int b;
51     int c;
52 };
53 int main()
54 {
55     Child3 c3;
56     c3.a = 123;
57     //c3.Child1::a = 123;
58     //c3.c3_print();
59
60     return 0;
61 }

可以看到还是报错,继续剖析,更改代码如下:

 1 #include<iostream>
 2 using namespace std;
 3
 4 class Child1
 5 {
 6 public:
 7     Child1() :a(0), b(0), c(0) { cout << "child 构造\n"; }
 8     ~Child1()
 9     {
10         cout << "child 析构,,,\n";
11     }
12     void c1_print()
13     {
14         cout << "a b c is" << a << " " << b << " " << c << endl;
15     }
16
17     int a;
18     int b;
19     int c;
20 };
21 class Child2
22 {
23 public:
24     Child2() :a(1), b(2), c(3) { cout << "child 构造\n"; }
25     ~Child2()
26     {
27         cout << "child 析构,,,\n";
28     }
29     void c2_print()
30     {
31         cout << "a b c is" << a << " " << b << " " << c << endl;
32     }
33     int a;
34     int b;
35     int c;
36 };
37 class Child3 : public Child1,  public Child2
38 {
39 public:
40     Child3() : Child1(),Child2(), b(20), c(30) { cout << "child 构造\n"; }
41     ~Child3()
42     {
43         cout << "child 析构,,,\n";
44     }
45     void c3_print()
46     {
47         //cout << "a b c is" << a << " " << b << " " << c << endl;
48     }
49     //int a;
50     int b;
51     int c;
52 };
53 int main()
54 {
55     Child3 c3;
56     //c3.a = 123;
57     c3.Child1::a = 123;
58     c3.c1_print();
59
60     return 0;
61 }

可以看到,通过域作用符可以消除歧义,那么问题又来了;是否可以通过virtual关键字或者自己定义一个属性a达到消除错误呢?

test:

 1 #include<iostream>
 2 using namespace std;
 3
 4 class Child1
 5 {
 6 public:
 7     Child1() :a(0), b(0), c(0) { cout << "child 构造\n"; }
 8     ~Child1()
 9     {
10         cout << "child 析构,,,\n";
11     }
12     void c1_print()
13     {
14         cout << "a b c is" << a << " " << b << " " << c << endl;
15     }
16
17     int a;
18     int b;
19     int c;
20 };
21 class Child2
22 {
23 public:
24     Child2() :a(1), b(2), c(3) { cout << "child 构造\n"; }
25     ~Child2()
26     {
27         cout << "child 析构,,,\n";
28     }
29     void c2_print()
30     {
31         cout << "a b c is" << a << " " << b << " " << c << endl;
32     }
33     int a;
34     int b;
35     int c;
36 };
37 class Child3 : public Child1,  public Child2
38 {
39 public:
40     Child3() : Child1(),Child2(), b(20), c(30) { cout << "child 构造\n"; }
41     ~Child3()
42     {
43         cout << "child 析构,,,\n";
44     }
45     void c3_print()
46     {
47         //cout << "a b c is" << a << " " << b << " " << c << endl;
48     }
49     int a;
50     int b;
51     int c;
52 };
53 int main()
54 {
55     Child3 c3;
56     c3.a = 123;
57     //c3.Child1::a = 123;
58     //c3.c1_print();
59
60     return 0;
61 }

自己定义属性a,是可以做到消除歧义的,但属性a还是属于child3的而不是访问继承而来的;

增加virtual关键字

test:

1 class Child3 : virtual public Child1, virtual public Child2

还是报错,因为virtual的作用是产生虚基类的,virtual说明符表达了一种愿望,即在后续的派生类中共享虚基类的同一份唯一实例,至于什么样的类能作为虚基类没有明确的规定,显然这里和图一不同。

summary:

结论已经显而易见,多继承比单继承复杂,一般不使用,但语法还是得掌握,因为你又可能会用到它,存在一定有它的道理。

时间: 2024-10-08 06:44:36

c++多继承浅析的相关文章

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

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

《浅析:java不支持多继承的原因》

很久以前,博主的一个好朋友给我考了我一个问题,为什么Java不支持多继承,如果多继承会有什么错误.这个问题困扰了我很久,我从单继承方面去推导过,又从多态的层面去思考过,但都无法解释为什么Java不支持多继承的原因,后来也有在学校问老师,老师也只是让我多去思考,好吧,那我就多去思考吧. 在1995年,Java语言的设计者James Gosling发表白皮书的时候谈到了一个想法:Java为什么不支持多继承. 我们从设计人员给Java这门语言的定义去说起:JAVA:一个简单的,面向对象的,分布式的,解

浅析C++继承与派生

测试环境: Target: x86_64-linux-gnu gcc version 5.3.1 20160413 (Ubuntu 5.3.1-14ubuntu2.1) 定义 要分析继承,首先当然要知道什么是继承:继承是面向对象程旭设计中使代码可以复用的最重要的手段,它允许程序员在原有类特性的基础上进行扩展,增加功能.这样产生的新类,就叫做派生类(子类).继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程. 继承的格式 class 子类名 :继承权限 基类名 比如下面分别定义了两

牛刀小试 - 浅析Java的继承与动态绑定

什么是继承? 继承也是面向对象的重要特性之一.顾名思义,继承就是指从已有的类中派生出新类的动作.新的类能吸收已有类的数据属性和行为,并能扩展新的能力. 而通俗一点的来说,就是指Java中可以通过继承的方式,从现有的类派生出新的类.该现有类被称为超类(父类),而派生出的新类就被称为子类(派生类). 首先,子类访问继承超类当中的所有非私有的方法和成员变量:其次,还可以在父类原有的成员的基础上添加一些新的方法和域,或者对父类的方法进行覆写(override). 所有通常也这样讲:父类是子类的一般化表现

浅析Hibernate映射(三)——继承映射

对象模型示例: 继承映射的实现方式有三种: (一)每棵类继承树一张表 关系模型: 映射文件: [html] view plaincopyprint? <hibernate-mapping package="com.jialin.hibernate"> <class name="Animal" table="t_animal" lazy="false"> <id name="id"

C++ 类继承与对象赋值 情况下 成员变量的覆盖 浅析

[摘要] 类的继承以及对象的赋值会带来成员变量的相互传递.这里详细讨论了,类间继承带来的成员变量的传递采用覆盖原则,采用函数级的成员变量的取值:对象赋值带来的成员变量的传递采用,实函数采用数据类型的实函数,虚函数采用赋值源的虚函数,成员变量采用赋值源的成员变量,其实也是函数级的成员变量. [正文] 在类继承中,成员变量存在覆盖的情况,成员函数则存在隐藏和覆盖以及重载的情况.在类继承中,公有继承会导致公有成员变量的覆盖,从而使得成员函数的调用出现各种结果. [代码示例 01] #include<i

浅析 Java 中的继承和重写

Java 中的构造方法不能被继承. Java 中 static 修饰的方法可以被继承,但不能被子类重写. Java 中 final 修饰方法不允许被子类重写,但是可以被子类继承,final 不能修饰构造方法. Java 中子类可以继承父类的私有成员,但是不能(直接)访问,私有方法不可以重写. 原文地址:https://www.cnblogs.com/hglibin/p/11247597.html

Volley框架源码浅析(一)

尊重原创http://blog.csdn.net/yuanzeyao/article/details/25837897 从今天开始,我打算为大家呈现关于Volley框架的源码分析的文章,Volley框架是Google在2013年发布的,主要用于实现频繁而且粒度比较细小的Http请求,在此之前Android中进行Http请求通常是使用HttpUrlConnection和HttpClient进行,但是使用起来非常麻烦,而且效率比较地下,我想谷歌正式基于此种原因发布了Volley框架,其实出了Voll

ReactiveCocoa2 源码浅析

ReactiveCocoa2 源码浅析 标签(空格分隔): ReactiveCocoa iOS Objective-C ? 开车不需要知道离合器是怎么工作的,但如果知道离合器原理,那么车子可以开得更平稳. ReactiveCocoa 是一个重型的 FRP 框架,内容十分丰富,它使用了大量内建的 block,这使得其有强大的功能的同时,内部源码也比较复杂.本文研究的版本是2.4.4,小版本间的差别不是太大,无需担心此问题. 这里只探究其核心 RACSignal 源码及其相关部分.本文不会详细解释里