图一                                                                                       图二

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

 #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. eclipse 如何对maven项目进行打包?

      eclipse 如何对maven项目进行打包? CreateTime--2018年4月19日22:02:50 Author:Marydon 1.方式一:使用eclipse中的maven插件(命令) ...

  2. HDU 5402 Travelling Salesman Problem(棋盘染色 构造 多校啊)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5402 Problem Description Teacher Mai is in a maze wit ...

  3. SSO之安装CAS Server

    JA-SIG CAS(Central Authentication Service)为Web应用系统提供了单点登录服务.它的特性包括:一个开放和具有很好文档支持的协议:一个Java开源服务器组件:提供 ...

  4. Oracle-client支持exp|imp|rman

    官方精简版的驱动,不支持持exp/imp/rman,故需要安装oracle_client客户端. 实验环境: Centos6.5 x64   Oracle 11.2.0.4.0 Oracle_clie ...

  5. python实现的电影票房数据可视化

    代码地址如下:http://www.demodashi.com/demo/14275.html 详细说明: Tushare是一个免费.开源的python财经数据接口包.主要实现对股票等金融数据从数据采 ...

  6. Xiuno BBS 3.0 轻论坛程序正式版发布。

    github:git clone -b v3.0 https://git.oschina.net/xiuno/xiunobbs 安装包:http://bbs.xiuno.com/down/xiuno_ ...

  7. 深入PHP内核之全局变量

    在阅读PHP源码的时候,会遇到很多诸如:CG(),EG() ,PG(),FG()这样的宏,如果不了解这些宏的意义,会给理解源码造成很大困难 EG().这个宏可以用来访问符号表,函数,资源信息和常量 C ...

  8. HDUOJ----1166敌兵布阵(线段树单点更新)

    敌兵布阵 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submis ...

  9. 为Github 托管项目的访问添加SSH keys

    为了便于访问远程仓库,各个协作者将自己的本地的项目内容推送到远程仓库中,使用 SSH keys 验证github的好处:不用每次提交代码时都输入用户名和密码. 如果SSH key没有添加到github ...

  10. javascript中function和object的区别,以及javascript如何实现面向对象的编程思想.

    <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <m ...