(根据《C++程序设计》(谭浩强)整理,整理者:华科小涛,@http://www.cnblogs.com/hust-ghtao转载请注明)

多重继承是指一个派生类有两个或多个基类。例如,有些学校的领导干部同时也是教师,他们既有干部的属性,又有教师的属性。C++为了适应这种情况,允许一个派生类同时继承多个基类,这就是多重继承。

1  多重继承的基础

本节包含两部分内容,即如何声明多重继承和多重继承派生类的构造函数。

1.1  声明多重继承的方法

如果已经声明了类A、类B、类C,可以声明多重继承的派生类D:

   1: class D : public A , private B , protected C

   2: {  类D新增加的成员 } ;

D是多重继承的派生类,它以公用继承方式继承类A,以私有继承方式继承类B,以保护继承方式继承类C。D按不同的继承方式的规则继承A,B,C的属性,确定各基类的的成员在派生类中的访问权限。

1.2  多重继承派生类的构造函数

多重继承派生类的构造函数的基本形式:

            派生类构造函数名(总参数表):基类1构造函数(参数表),基类2构造函数(参数表),基类3构造函数(参数表)

                   {派生类中新增数据成员初始化语句}

各个基类的排列顺序是任意的。派生类构造函数的执行顺序先调用基类的构造函数,再执行派生类构造函数的函数体。调用基类构造函数的顺序是按照声明派生类时基类出现的顺序。

   例1  声明一个Teacher类和一个Student类,用多用继承的方式声明一个Graduate派生类。Teacher类中包括数据成员nameagetitle。学生类中包括数据成员name1sexscore。在定义派生类的对象时给出初始化的数据,然后输出这些数据。

   1: class Teacher                                  //声明Teacher类

   2: {

   3: public:

   4:     Teacher( string nam , int a , string t )   //基类Teacher的构造函数

   5:     {

   6:         name = nam ;

   7:         age = a ;

   8:         title = t ;

   9:     }

  10:  

  11:     void display()                             //输出Teacher类的有关数据

  12:     {

  13:         cout << "name:" << name << endl ;

  14:         cout << "age:" << age << endl ;

  15:         cout << "title:" << title << endl ;

  16:     }

  17: protected:

  18:     string name ;

  19:     int age ;

  20:     string title ;

  21: };

  22:  

  23: class Student                                   //声明Student类

  24: {

  25: public:

  26:     Student(string nam , char s , float sco )   //基类Student的构造函数

  27:     {

  28:         name1 = nam ;

  29:         sex = s ;

  30:         score = sco ;

  31:     }

  32:  

  33:     void display1()                             //输出Student类有关数据

  34:     {

  35:         cout << "name:" << name1 << endl ;

  36:         cout << "sex:" << sex << endl ;

  37:         cout << "score:" << score << endl ;

  38:     }

  39: protected:

  40:     string name1 ;

  41:     char sex ;

  42:     float score ;

  43: };

  44:  

  45: class Graduate : public Teacher , public Student  //声明多重继承的派生类Graduate

  46: {

  47: public:  

  48:                //多重继承派生类Graduate的构造函数                              

  49:     Graduate( string nam , int a , char s , string t , float sco , float w ) : 

  50:         Teacher( nam , a , t ) , Student( nam , s , sco ) , wage( w ) {}

  51:     void show ()                                //输出Graduate的有关数据

  52:     {

  53:         cout << "name:" << name << endl ;

  54:         cout << "age:" << age << endl ;

  55:         cout << "sex:" << sex << endl ;

  56:         cout << "score:" << score << endl ;

  57:         cout << "title:" << title << endl ;

  58:         cout << "wages:" << wage << endl ;

  59:     }

  60: private:

  61:     float wage ; 

  62: };

  63:  

若在主函数中有如下语句:

   1: Graduate grad1 ( "hust_xiaotao" , 24 , 'm' , "assistant" , 90.0 , 1000 );

   2: grad1.show();

我们可以注意到,在两个基类中分别用name和name1来代表姓名,其实这是同一个人的名字,从Graduate类的构造函数中可以看到总参数表中的参数nam分别传送给两个基类的构造函数,作为基类构造函数的实参。在两个基类中是不能用同一个name来代表的,因为在同一个派生类中若存在着两个同名的数据成员,在用成员函数进行引用的时候,编译系统是无法判定应该选择哪一个类中的name。

这个问题就是多重继承时遇到的二义性问题,有以下几种解决方案:

(1)基类中不出现同名的成员。例如,在本程序中分别用name和name1来代表两个基类中的姓名,这样程序虽然能够正常运行,但这是为了通过编译而采取的不高明的方法。因为绝大多数的基类都是已经编写好的,用户只可以利用它而无法修改它。

(2)在不同的基类中可以使用同一个数据成员名,而在引用时指明其作用域。如本例,在两个基类中可以使用相同的成员名name,而在show函数中引用数据成员时指明其作用域:

   1: cout << "name:" << Teacher::name << endl ; 

这样就不致引起二义性,能正常编译运行。

我们可以看到,在多重继承时,从不同的基类中会继承重复的数据,这是很常见的,因为一般情况下使用的是现成的基类。如果有多个基类问题会更加突出。这就要求我们在设计派生类时要细致考虑其数据成员,尽量减少数据冗余。

2  多重继承二义性与虚基类

2.1 多重继承的二义性问题

由继承的成员同名而产生的二义性问题。可以分为以下三种情况:

(1)两个基类有同名成员。

   1: class A

   2: {

   3: public:

   4:     int a ;

   5:     void display() ;

   6: };

   7: class B

   8: {

   9:     int a ;

  10:     void display() ; 

  11: };

  12: class C : public A , public B

  13: {

  14: public:

  15:     int b ;

  16:     void show();

  17: };

如果在main函数中定义C类对象,在调用数据成员a和成员函数display,为了避免二义性,则要用基类名来限定:

   1: c1.A::a = 3 ;      //引用c1对象中的基类A的数据成员a

   2: c1.A::display() ;  //调用c1对象中基类A的成员函数display

(2)两个基类和派生类三者都有同名成员。将上面的C类声明为:

   1: class C : public A , public B

   2: {

   3: public:

   4:     int a ;

   5:     void display();

   6: };

若在main函数中定义C类对象,在调用数据成员a和成员函数display:

   1: c1.a = 3 ;

   2: c1.display() ;

此时程序可以通过编译,也可以正常运行。那么执行访问的是哪一个类中的成员?答案是:访问的是派生类C的成员。规则是:基类的同名成员在派生类中被屏蔽,或者说,派生类新增加的同名成员覆盖了基类中的同名成员。因此,如果通过派生类的对象名来访问同名的成员,则访问的是派生类的成员。注意:不同的成员函数,只有在函数名和参数个数相同、类型匹配的情况下才发生同名覆盖,如果只有函数名相同而参数不同,不会发生同名覆盖,儿属于函数重载。同样的,如果要在派生类外访问基类中同名成员,要指明作用域。

(3)如果类A和类B是从一个基类N派生而来。

   1: class N

   2: {

   3: public:

   4:     int a ;

   5:     void display()

   6:     {

   7:         cout << "A::a=" << a << endl ;

   8:     }

   9: };

  10: class A : public N

  11: {

  12: public:

  13:     int a1 ;

  14: };

  15: class B : public N

  16: {

  17: public:

  18:     int a2 ;

  19: };

  20: class : public A , public B

  21: {

  22: public:

  23:     int a3 ;

  24:     void show()

  25:     {

  26:         cout << "a3=" << a3 << endl ;

  27:     }

  28: };

  29: int main()

  30: {

  31: C c1 ;

  32: }

在类A和类B中虽然没有定义数据成员a和成员函数display,但是它们分别从类N继承了数据成员a和display,这样类A和类B中同时存在着两个同名的数据成员a和display。它们是类N数据成员的拷贝。如果要访问类A中从基类N中继承下来的成员,应该用:

   1: c1.A::a=3;

   2: c1.A::display();      //要访问的是类N的派生类A中的基类成员

2.2   虚基类

由上可知,如果一个派生类有多个直接基类,而这些直接基类又有一个共同的基类,则在最终的派生类中会保留改间接共同基类数据成员的多分拷贝。在引用这些同名的成员时,必须在派生类对象名后面增加直接基类名,以避免产生二义性。

在一个类中保留间接共同基类的多分数据成员一般是不必要的,因为保存多份数据成员的拷贝,不仅占用较多的存储空间,还增加了访问这些成员的困难,易错。

C++提供了虚基类( virtual base class ) 的方法,使得在继承间接共同基类时只保留一份成员。现在将A类声明为虚基类:

   1: class A                        //声明基类A

   2: {...};

   3: class B : virtual public A     //声明类B是类A的公用派生类,A是B的虚基类

   4: {...};

   5: class C : virtual public A     //声明类C是类A的公用派生类,A是C的虚基类

   6: {...};

注意:虚基类并不是在声明基类时声明的,而是在声明派生类时,指定继承方式时声明的。将关键字virtual加到相应的继承方式面前。经过这样的声明后,当基类通过多条派生路径被一个派生类继承时,该派生类只继承该基类一次,也就是说,基类成员只保留一次。 为了保证虚基类在派生类中只继承一次,应当在该基类的所有直接派生类中声明为虚基类,否则,仍然会出现对基类的多次继承。

下面介绍如何对虚基类进行初始化:如果在虚基类中定义了带参数的构造函数,而且没有定义默认构造函数,则在其所有派生类(包括直接派生或间接派生的派生类)中,通过构造函数的初始化表对虚基类进行初始化。例如:

   1: class A                          //定义基类A

   2: {

   3: public:

   4:     A(int i){}                   //基类构造函数,有一个参数

   5:     ...

   6: };

   7: class B : virtual public A       //A作为B的虚基类,

   8: {

   9: public:

  10:     B(int n):A(n){}              //B类的构造函数,在初始化表中对基类初始化

  11:     ...

  12: };

  13: class C : virtual public A       //A作为C的虚基类

  14: {

  15: public:

  16:     C(int n):A(n){}              //C类构造函数,在初始化表中对虚基类初始化

  17:     ...

  18: };

  19: class D : public B , public C

  20: {

  21: public:

  22:     D(int n):A(n),B(n),C(n){}   //D类的构造函数,在初始化表中对所有基类初始化

  23:     ...

  24: };

可以看到,在定义D类的构造函数时,与以往所使用的方法不同。以前,在派生类的构造函数中只负责对直接基类初始化,再由直接基类对间接基类初始化。现在,由于虚基类在派生类中只有一份数据成员,所以这份数据成员必须由派生类直接给出。即规定,在最后的派生类中不仅要负责对其直接基类进行初始化,还要负责对虚基类初始化。

大家可能会有疑问:类D的构造函数通过初始化表调用了虚基类的构造函数A,而类B和类C的构造函数也通过初始化表调用了A的构造函数,这样虚基类的构造函数是否被调用了3次?大家不用担心,C++编译系统只执行最后的派生类对虚基类的构造函数的调用,而忽略其他派生类的调用,这就保证了虚基类的数据成员不会被多次初始化。

C++学习之路—继承与派生(三):多重继承与虚基类的更多相关文章

  1. C++继承和派生练习(一)--关于vehicle基类

    Target:定义一个车(vehicle)基类 具有MaxSpeed.Weight等成员变量,Run.Stop等成员函数,由此派生出自行车(bicycle)类.汽车(motorcar)类. 自行车(b ...

  2. C++学习之路—继承与派生(四)拓展与总结

    (根据<C++程序设计>(谭浩强)整理,整理者:华科小涛,@http://www.cnblogs.com/hust-ghtao转载请注明) 1    拓展部分 本节主要由两部分内容组成,分 ...

  3. C++学习之路—继承与派生(一):基本概念与基类成员的访问属性

    (本文根据<c++程序设计>(谭浩强)总结而成,整理者:华科小涛@http://www.cnblogs.com/hust-ghtao,转载请注明) 1   基本思想与概念 在传统的程序设计 ...

  4. C++学习之路—继承与派生(二):派生类的构造函数与析构函数

    (根据<C++程序设计>(谭浩强)整理,整理者:华科小涛,@http://www.cnblogs.com/hust-ghtao转载请注明) 由于基类的构造函数和析构函数是不能被继承的,所以 ...

  5. C++中虚基类在派生类中的内存布局

    今天重温C++的知识,当看到虚基类这点的时候,那时候也没有太过追究,就是知道虚基类是消除了类继承之间的二义性问题而已,可是很是好奇,它是怎么消除的,内存布局是怎么分配的呢?于是就深入研究了一下,具体的 ...

  6. C++ 虚继承实现原理(虚基类表指针与虚基类表)

    虚继承和虚函数是完全无相关的两个概念. 虚继承是解决C++多重继承问题的一种手段,从不同途径继承来的同一基类,会在子类中存在多份拷贝.这将存在两个问题:其一,浪费存储空间:第二,存在二义性问题,通常可 ...

  7. Qt 学习之路 2(73):Qt 线程相关类

    Home / Qt 学习之路 2 / Qt 学习之路 2(73):Qt 线程相关类 Qt 学习之路 2(73):Qt 线程相关类  豆子  2013年11月26日  Qt 学习之路 2  7条评论 希 ...

  8. C++ 虚基类 派生与继承

    在学习设计模式时我就有一个疑问,关联和继承除了用法上的区别,好像在内存上并没有什么区别,继承也是父类作为了子类的元素(内存上),关联也是这样.而且关联好像更占内存一些.这就是设计模式里问题了“依赖倒转 ...

  9. C++学习20 虚基类详解

    多继承时很容易产生命名冲突,即使我们很小心地将所有类中的成员变量和成员函数都命名为不同的名字,命名冲突依然有可能发生,比如非常经典的菱形继承层次.如下图所示: 类A派生出类B和类C,类D继承自类B和类 ...

随机推荐

  1. WebView之2

    首先需要添加权限: <uses-permission android:name="android.permission.INTERNET"/> MainActivity ...

  2. 执行Git命令时出现 SSL certificate problem 的解决办法

    比如我在windows下用git clone gitURL 就提示  SSL certificate problem: self signed certificate 这种问题,在windows下出现 ...

  3. [置顶] 深入ResourceBundle

    ResourceBundle是java开发中非常实用的一个类,主要用来处理应用程序多语言这样的国际化问题. 如果你的应用程序如果有国际化的需求,可以考虑使用ResourceBundle, 你要做的就是 ...

  4. C Tips:显示点阵汉字的小样例

    非常简陋的一段小程序,演示怎样显示点阵字库.有时间的时候再详解. #include <stdio.h> #include <stdlib.h> struct HzkInfoSt ...

  5. Linux定义系统提示符的变量:PS1

  6. Linux高性能server编程——Linux网络基础API及应用

     Linux网络编程基础API 具体介绍了socket地址意义极其API,在介绍数据读写API部分引入一个有关带外数据发送和接收的程序,最后还介绍了其它一些辅助API. socket地址API 主 ...

  7. MSSQL奇技淫巧

    MSSQL:获得库每个表的记录数和容量 sp_msforeachtable是MS未公开的存储过程: exec sp_msforeachtable @command1="print '?'&q ...

  8. ACM比赛(第三次D)

    Time Limit:1000MS     Memory Limit:131072KB     64bit IO Format:%lld & %llu Description 有三户人家共拥有 ...

  9. 第四届蓝桥杯 c/c++真题

    第四届蓝桥杯 c/c++真题 <1>高斯日记 问题 大数学家高斯有个好习惯:无论如何都要记日记. 他的日记有个与众不同的地方,他从不注明年月日,而是用一个整数代替,比如:4210 后来人们 ...

  10. 安装MyEclipse Color Themes

    下载地址:http://eclipsecolorthemes.org/?list=toppicks&lang=html 安装步骤如下: 1.Import---Preferences 2.选择下 ...