图一                                                                                       图二

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

 #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. Linux安装SQLite轻量级数据库

    SQLite,是一款轻型的数据库,是遵守ACID的关系型数据库管理系统,它包含在一个相对小的C库中.它是D.RichardHipp建立的公有领域项目.它的设计目标是嵌入式的,而且目前已经在很多嵌入式产 ...

  2. 通过JS控制各种元素的点击事件的【时间间隔】,特别适合【发表评论】功能

    1.使用情景模拟:下单(防止用户无脑狂点).支付(防止用户无脑点击支付).发表评论(防止用户无脑点击),当然如果你用了一个提示框进行屏蔽,下面代码可以无视了,右上角谢谢. 2.默写情景比如:比如发表评 ...

  3. 警告: [SetContextPropertiesRule]{Context} Setting property 'source' to 'org.eclipse.jst.jee.server:

    当你用Eclipse运行web项目的时候,你就会看到控制台出现: 警告: [SetContextPropertiesRule]{Context} Setting property 'source' t ...

  4. PHP-手册阅读笔记

    1.第一次遇到$_ENV为空数组的情况, 原来是PHP.INI中variables_order为'GPCS'(表示系统在定义PHP预定义变量时的顺序是GET,POST,COOKIES,SERVER,只 ...

  5. Mustache模板引擎

    Mustache是一个Logic-Less模板引擎,即:零逻辑引擎,原因在于它只有标签,没有流程控制语句,这是它与其它模板引擎不同的地方. Mustache小巧玲珑,几乎用各种语言都实现了一遍. Mu ...

  6. YACC、LEX、JAVACC-------常用的编译工具

    CC(Compiler Compiler) CC的意思就是"编译器的编译器". 你可以定义一种上下文无关文法(CFG),然后针对这个特定的CFG你可以写出一个C程序来解释这种CFG ...

  7. Python的copy()与deepcopy()区别

    Python的copy()与deepcopy()分别对应浅拷贝和深拷贝. 它们的理论区别: deepcopy():深复制(也就是寻常意义上的复制),即将被复制对象完全再复制一遍作为独立的新个体单独存在 ...

  8. AspxPivotGrid和WebChartControl数据联动处理日志

    AspxPivotGrid具有很好的表格样式体验,WebChartControl也是个很内容丰富的做图控件,我希望实现的功能是这样的, 处理题库统计分析图表,用户点AspxPivotGrid绑定知识点 ...

  9. Python 字典 popitem() 方法

    描述 Python 字典 popitem() 方法随机返回并删除字典中的一个键/值对(一般删除末尾对). 如果字典已经为空,却调用了此方法,就报出KeyError异常. 语法 popitem() 方法 ...

  10. iOS端JSON转Model链式编程框架SuperKVC使用方法与原理

    背景 在client编程中.字典转模型是一个极为常见的问题,苹果提供了KVC来实现NSDictionary到Model的注入,可是KVC仅仅能进行单层浅注入.且无法处理类型转换.key与属性名不正确应 ...