图一                                                                                       图二

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

 #include<iostream>
using namespace std; class Parent {
public:
Parent():a(),b(),c()
{
cout << "parent 构造。。。\n";
}
~Parent()
{
cout << "Parent 析构。。。\n";
}
int a;
int b;
int c;
void p_print()
{
cout << "a b c is" << a << " " << b << " " << c << endl;
} };
class Child1 :virtual public Parent
{
public:
Child1() :Parent(), a(), b(), c() { cout << "child 构造\n"; }
~Child1()
{
cout << "child 析构,,,\n";
}
void c_print()
{
cout << "a b c is" << a << " " << b << " " << c << endl;
}
int a;
int b;
int c;
};
class Child2 :virtual public Parent
{
public:
Child2() :Parent(), a(), b(), c() { cout << "child 构造\n"; }
~Child2()
{
cout << "child 析构,,,\n";
}
void c_print()
{
cout << "a b c is" << a << " " << b << " " << c << endl;
}
int a;
int b;
int c;
};
class Child3 :public Child1,public Child2
{
public:
58 Child3() :Parent(),Child1(),Child2(), a(10), b(20), c(30)
{ cout << "child 构造\n"; }//如果前面没有使用虚继承,这里初始化Parent构造函数将出错
~Child3()
{
cout << "child 析构,,,\n";
}
void c_print()
{
cout << "a b c is" << a << " " << b << " " << c << endl;
}
int a;
int b;
int c;
};
int main()
{
Child3 c3; return ;
}

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

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

 #include<iostream>
using namespace std; class Parent {
public:
Parent():a(),b(),c()
{
cout << "parent 无参构造。。。\n";
}
Parent(int test) :a(), b(), c()
{
cout << "parent 有参构造。。。\n";
}
~Parent()
{
cout << "Parent 析构。。。\n";
}
int a;
int b;
int c;
void p_print()
{
cout << "a b c is" << a << " " << b << " " << c << endl;
} };
class Child1 : public Parent
{
public:
Child1() :Parent(), b(), c() { cout << "child 构造\n"; }
~Child1()
{
cout << "child 析构,,,\n";
}
void c_print()
{
cout << "a b c is" << a << " " << b << " " << c << endl;
} //int a;
int b;
int c;
};
class Child2 : public Parent
{
public:
Child2() :Parent(), b(), c() { cout << "child 构造\n"; }
~Child2()
{
cout << "child 析构,,,\n";
}
void c_print()
{
cout << "a b c is" << a << " " << b << " " << c << endl;
}
//int a;
int b;
int c;
};
class Child3 : public Child1, public Child2
{
public:
Child3() :Child1(),Child2(), b(), c() { cout << "child 构造\n"; }
~Child3()
{
cout << "child 析构,,,\n";
}
//int a;
int b;
int c;
};
int main()
{
Child3 c3;
c3.a = ;
return ;
}

报错如下:

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

 #include<iostream>
using namespace std; class Parent {
public:
Parent():a(),b(),c()
{
cout << "parent 无参构造。。。\n";
}
Parent(int test) :a(), b(), c()
{
cout << "parent 有参构造。。。\n";
}
~Parent()
{
cout << "Parent 析构。。。\n";
}
int a;
int b;
int c;
void p_print()
{
cout << "a b c is" << a << " " << b << " " << c << endl;
} };
class Child1 : virtual public Parent
{
public:
Child1() :Parent(), b(), c() { cout << "child 构造\n"; }
~Child1()
{
cout << "child 析构,,,\n";
}
void c_print()
{
cout << "a b c is" << a << " " << b << " " << c << endl;
} //int a;
int b;
int c;
};
class Child2 : virtual public Parent
{
public:
Child2() :Parent(), b(), c() { cout << "child 构造\n"; }
~Child2()
{
cout << "child 析构,,,\n";
}
void c_print()
{
cout << "a b c is" << a << " " << b << " " << c << endl;
}
//int a;
int b;
int c;
};
class Child3 : public Child1, public Child2
{
public:
Child3() :Child1(),Child2(), b(), c() { cout << "child 构造\n"; }
~Child3()
{
cout << "child 析构,,,\n";
}
//int a;
int b;
int c;
};
int main()
{
Child3 c3;
c3.a = ;
return ;
}

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

问题抛出:

在child3中的构造函数:

Child3() :Child1(),Child2(), b(20), c(30) { cout << "child 构造\n"; }
我们把child1和child2的基类构造函数改成有参的,看看child3继承的老祖宗属性是如何的:
 #include<iostream>
using namespace std; class Parent {
public:
Parent():a(),b(),c()
{
cout << "parent 无参构造。。。\n";
}
Parent(int test) :a(), b(), c()
{
cout << "parent 有参构造。。。\n";
}
~Parent()
{
cout << "Parent 析构。。。\n";
}
int a;
int b;
int c;
void p_print()
{
cout << "a b c is" << a << " " << b << " " << c << endl;
} };
class Child1 : virtual public Parent
{
public:
Child1() :Parent(), b(), c() { cout << "child 构造\n"; }
~Child1()
{
cout << "child 析构,,,\n";
}
void c1_print()
{
cout << "a b c is" << a << " " << b << " " << c << endl;
} //int a;
int b;
int c;
};
class Child2 : virtual public Parent
{
public:
Child2() :Parent(), b(), c() { cout << "child 构造\n"; }
~Child2()
{
cout << "child 析构,,,\n";
}
void c2_print()
{
cout << "a b c is" << a << " " << b << " " << c << endl;
}
//int a;
int b;
int c;
};
class Child3 : public Child1, public Child2
{
public:
Child3() : Child1(),Child2(), b(), c() { cout << "child 构造\n"; }
~Child3()
{
cout << "child 析构,,,\n";
}
void c3_print()
{
cout << "a b c is" << a << " " << b << " " << c << endl;
}
//int a;
int b;
int c;
};
int main()
{
Child3 c3;
c3.c3_print();
c3.p_print();
return ;
}

运行结果:

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

 Child3() : Parent(),Child1(),Child2(), b(), c() { 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类中寻找:

 #include<iostream>
using namespace std; class Parent {
public:
Parent():a(),b(),c()
{
cout << "parent 无参构造。。。\n";
}
Parent(int test) :a(), b(), c()
{
cout << "parent 有参构造。。。\n";
}
~Parent()
{
cout << "Parent 析构。。。\n";
}
int a;
int b;
int c;
void p_print()
{
cout << "a b c is" << a << " " << b << " " << c << endl;
} };
class Child1 : public Parent
{
public:
Child1() :Parent(), b(), c() { cout << "child 构造\n"; }
~Child1()
{
cout << "child 析构,,,\n";
}
void c1_print()
{
cout << "a b c is" << a << " " << b << " " << c << endl;
} //int a;
int b;
int c;
};
class Child2 : public Parent
{
public:
Child2() :Parent(), b(), c() { cout << "child 构造\n"; }
~Child2()
{
cout << "child 析构,,,\n";
}
void c2_print()
{
cout << "a b c is" << a << " " << b << " " << c << endl;
}
//int a;
int b;
int c;
};
class Child3 : public Child1, public Child2
{
public:
Child3() : Child1(),Child2(), b(), c() { cout << "child 构造\n"; }
~Child3()
{
cout << "child 析构,,,\n";
}
void c3_print()
{
cout << "a b c is" << a << " " << b << " " << c << endl;
}
int a;
int b;
int c;
};
int main()
{
Child3 c3;
c3.a = ;
c3.c3_print(); return ;
}

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

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

test:

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

依旧报错:

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

对于图2:

 #include<iostream>
using namespace std; class Child1
{
public:
Child1() :a(), b(), c() { cout << "child 构造\n"; }
~Child1()
{
cout << "child 析构,,,\n";
}
void c1_print()
{
cout << "a b c is" << a << " " << b << " " << c << endl;
} int a;
int b;
int c;
};
class Child2
{
public:
Child2() :a(), b(), c() { cout << "child 构造\n"; }
~Child2()
{
cout << "child 析构,,,\n";
}
void c2_print()
{
cout << "a b c is" << a << " " << b << " " << c << endl;
}
int a;
int b;
int c;
};
class Child3 : public Child1, public Child2
{
public:
Child3() : Child1(),Child2(), b(), c() { cout << "child 构造\n"; }
~Child3()
{
cout << "child 析构,,,\n";
}
void c3_print()
{
//cout << "a b c is" << a << " " << b << " " << c << endl;
}
//int a;
int b;
int c;
};
int main()
{
Child3 c3;
c3.a = ;
//c3.Child1::a = 123;
//c3.c3_print(); return ;
}

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

 #include<iostream>
using namespace std; class Child1
{
public:
Child1() :a(), b(), c() { cout << "child 构造\n"; }
~Child1()
{
cout << "child 析构,,,\n";
}
void c1_print()
{
cout << "a b c is" << a << " " << b << " " << c << endl;
} int a;
int b;
int c;
};
class Child2
{
public:
Child2() :a(), b(), c() { cout << "child 构造\n"; }
~Child2()
{
cout << "child 析构,,,\n";
}
void c2_print()
{
cout << "a b c is" << a << " " << b << " " << c << endl;
}
int a;
int b;
int c;
};
class Child3 : public Child1, public Child2
{
public:
Child3() : Child1(),Child2(), b(), c() { cout << "child 构造\n"; }
~Child3()
{
cout << "child 析构,,,\n";
}
void c3_print()
{
//cout << "a b c is" << a << " " << b << " " << c << endl;
}
//int a;
int b;
int c;
};
int main()
{
Child3 c3;
//c3.a = 123;
c3.Child1::a = ;
c3.c1_print(); return ;
}

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

test:

 #include<iostream>
using namespace std; class Child1
{
public:
Child1() :a(), b(), c() { cout << "child 构造\n"; }
~Child1()
{
cout << "child 析构,,,\n";
}
void c1_print()
{
cout << "a b c is" << a << " " << b << " " << c << endl;
} int a;
int b;
int c;
};
class Child2
{
public:
Child2() :a(), b(), c() { cout << "child 构造\n"; }
~Child2()
{
cout << "child 析构,,,\n";
}
void c2_print()
{
cout << "a b c is" << a << " " << b << " " << c << endl;
}
int a;
int b;
int c;
};
class Child3 : public Child1, public Child2
{
public:
Child3() : Child1(),Child2(), b(), c() { cout << "child 构造\n"; }
~Child3()
{
cout << "child 析构,,,\n";
}
void c3_print()
{
//cout << "a b c is" << a << " " << b << " " << c << endl;
}
int a;
int b;
int c;
};
int main()
{
Child3 c3;
c3.a = ;
//c3.Child1::a = 123;
//c3.c1_print(); return ;
}

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

增加virtual关键字

test:

 class Child3 : virtual public Child1, virtual public Child2

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

summary:

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

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

  1. js 原型,原型链,原型链继承浅析

    对于网上的关于原型,原型链和原型链继承的晦涩语言说明就不累赘了,复制粘贴过来再解释一遍怕自己也整蒙了,本人最怕空气突然安静,四目对视,大眼对小眼,一脸懵逼. 我们先看下面

  2. js原型链+继承 浅析

    名称:    prototype--原型对象    __proto__--属性 原型链与继承网上搜索定义,看起来挺绕的 .先说继承: 所有的对象实例都可以共享原型对象包含的属性和方法  例如一个实例A ...

  3. android里的继承浅析

    先看一段代码: abstract class A{ public A(){ this.print(); } public abstract void print(); } class B extend ...

  4. Java 浅析三大特性之一继承

    上文Java 浅析三大特性之一封装我们说到Java是一个注重编写类,注重于代码和功能复用的语言.Java实现代码复用的方式有很多,这里介绍一个重要的复用方式--继承. 在介绍继承之前,我们要明确一点, ...

  5. 浅析 Java 中的继承和重写

    浅析 Java 中的继承和重写 Java 中的构造方法不能被继承. Java 中 static 修饰的方法可以被继承,但不能被子类重写. Java 中 final 修饰方法不允许被子类重写,但是可以被 ...

  6. C++浅析——继承类内存分布和虚析构函数

    继承类研究 1. Code 1.1 Cbase, CTEST为基类,CTest2为其继承类,并重新申明了基类中的同名变量 class CBase { public: int Data; CBase() ...

  7. C++浅析——继承类中构造和析构顺序

    先看测试代码,CTEST 继承自CBase,并包含一个CMember成员对象: static int nIndex = 1; class CMember { public: CMember() { p ...

  8. java对象中继承和变量初始化顺序浅析

    先上例子代码 public class F { int age = 5; public F() { print(); } public void print() { System.out.printl ...

  9. 浅析Javascript原型继承(转)

    引自: http://blog.csdn.net/kittyjie/article/details/4380918 原作者解释的浅显易懂,非常不错的JavaScript prototype总结 JS没 ...

随机推荐

  1. 感谢CSDN赠送的图书和杂志(5月份)

    互联网的精神就是开放.就是分享.在分享的同一时候.我们也会收获到意外的回报. 近期.因为我在5月份发表了14篇博文,因此CSDN赠送了一本图书<软件系统架构>(本人自己选的)和一本< ...

  2. vs2010支持html5+css3

    第一步. 先到微软官方下载一个 Microsoft Visual Studio 2010 sp1 . 给传送门:下载 下载到这个东东 ---

  3. iOS获取本地沙盒视频封面图片(含swift实现)

    最近做了个小应用,有涉及到本地视频播放及列表显示. 其中一个知识点就是获取本地存储视频,用来界面中的封面显示. 记录如下: //videoURL:本地视频路径 time:用来控制视频播放的时间点图片截 ...

  4. word中批量修改图片大小

    一,在word中按alt+f11组合键,进入VBA模式二,在左边的工程资源管理器中找到你的word文档,在其上右键/添加/模块三,把下面代码复制,粘贴进去.四,更改数值, 改一下宽度和高度数值(10) ...

  5. 【LeetCode】40. Combination Sum II (2 solutions)

    Combination Sum II Given a collection of candidate numbers (C) and a target number (T), find all uni ...

  6. RHCE7 管理II-4计划将来的Linux任务

    (1) at 一次性的计划任务 语法: # at [参数] [时间] at> 执行的指令 退出at命令 ctrl+d [root@localhost ~]# at now at> mkdi ...

  7. Scanner类nextInt方法的使用注意点

    一.先看一段正常的代码 1. 一段用Scanner捕获键盘输入的代码: Scanner sc = new Scanner(System.in); // 先读取键盘输入的字符串 System.out.p ...

  8. android动画具体解释一 概述

    动画和图形概述 Android 提供了大量的强大的API以应用于UI动画和绘制2D和3D图形.以下各节向你描写叙述了这些API的预览和系统能力以帮助你决定怎么才是达到你需求的最佳方法. 动画 Andr ...

  9. bzoj 1860: [Zjoi2006]Mahjong麻将 题解

    [原题] 1860: [Zjoi2006]Mahjong麻将 Time Limit: 1 Sec  Memory Limit: 64 MB Submit: 211  Solved: 122 [Subm ...

  10. mysql 5.7安装教程

    一.mysql下载地址  https://downloads.mysql.com/archives/installer/ 说在前面的话 我为什么已经尝试和使用过同类型产品的很多MySQL版本,还要书写 ...