C++多继承的定义和使用
例如水鸟,既具有鸟的特性,能在天空飞翔,又具有鱼的特性,能在水里游泳。本文将针对多继承进行详细讲解。
多继承方式
多继承是单继承的扩展,在多继承中,派生类的定义与单继承类似,其语法格式如下所示:
class 派生类名:继承方式 基类1名称,继承方式 基类2名称,…,继承方式 基类n名称
{
新增成员;
};
在定义派生类对象时,派生类对象中成员变量的排列规则是:按照基类的继承顺序,将基类成员依次排列,然后再存放派生类中的新增成员。
多继承的示例代码如下所示:
class Base1 //基类Base1
{
protected:
int base1; //成员变量base1
};
class Base2 //基类Base2
{
protected:
int base2; //成员变量base2
};
class Derive:public Base1,public Base2 //Derive类公有继承Base1类和Base2类
{
private:
int derive; //派生类新增成员变量
};

图1 Derive类对象中成员变量的排列方式
多继承派生类的构造函数与析构函数
与单继承中派生类构造函数类似,多继承中派生类的构造函数除了要初始化派生类中新增的成员变量,还要初始化基类的成员变量。在多继承中,由于派生类继承了多个基类,因此派生类构造函数要负责调用多个基类的构造函数。
在多继承中,派生类构造函数的定义格式如下所示:
派生类构造函数名(参数列表):基类1构造函数名(参数列表), 基类2构造函数名(参数列表), …
{
派生类新增成员的初始化语句
}
定义派生类对象时,构造函数的调用顺序是:首先按照基类继承顺序,依次调用基类构造函数,然后调用派生类构造函数。
如果派生类中有成员对象,构造函数的调用顺序是:首先按照继承顺序依次调用基类构造函数,然后调用成员对象的构造函数,最后调用派生类构造函数。
除了构造函数,在派生类中还需要定义析构函数以完成派生类中新增成员的资源释放。析构函数的调用顺序与构造函数的调用顺序相反。如果派生类中没有定义析构函数,编译器会提供一个默认的析构函数。
【示例1】下面通过案例演示多继承派生类构造函数与析构函数的定义与调用,C++ 代码如下:
#include<iostream> using namespace std; class Wood //木材类Wood { public: Wood(){cout<<"木材构造函数"<<endl; } ~Wood(){cout<<"木材析构函数"<<endl; } }; class Sofa //沙发类Sofa { public: Sofa(){cout<<"沙发构造函数"<<endl; } ~Sofa(){cout<<"沙发析构函数"<<endl; } void sit(){cout<<"Sofa用来坐..."<<endl; } }; class Bed //床类Bed { public: Bed(){cout<<"床的构造函数"<<endl; } ~Bed(){cout<<"床的析构函数"<<endl; } void sleep(){cout<<"Bed用来睡觉..."<<endl; } }; class Sofabed:public Sofa,public Bed //Sofabed类,公有继承Sofa类和Bed类 { public: Sofabed(){cout<<"沙发床构造函数"<<endl; } ~Sofabed(){cout<<"沙发床析构函数"<<endl; } Wood pearwood; //Wood对象pearwood }; int main() { Sofabed sbed; //创建沙发床对象sbed sbed.sit(); //通过sbed调用基类Sofa的sit()函数 sbed.sleep(); //通过sbed调用基类Bed的sleep()函数 return 0; }运行结果:
沙发构造函数
床的构造函数
木材构造函数
沙发床构造函数
Sofa用来坐...
Bed用来睡觉...
沙发床析构函数
木材析构函数
床的析构函数
沙发析构函数
- 第 3~8 行代码定义了木材类 Wood,该类定义了构造函数与析构函数;
- 第 9~15 行代码定义了沙发类 Sofa,该类定义了构造函数、析构函数和普通成员函数sit();
- 第 16~22 行代码定义了床类 Bed,该类定义了构造函数、析构函数和普通成员函数sleep();
- 第 23~29 行代码定义了沙发床类 Sofabed,该类公有继承 Sofa 类和 Bed 类。Sofabed 类中包含 Wood 类对象 pearwood;此外,Sofabed 类还定义了构造函数与析构函数;
- 第 32 行代码,在 main() 函数中创建了 Sofabed 类对象 sbed;
- 第 33 行代码通过对象 sbed 调用基类 Sofa 的 sit() 函数;
- 第 34 行代码通过对象 sbed 调用基类 Bed 的 sleep() 函数。
在对象 sbed 创建和析构的过程中,构造函数的调用顺序如下:按照基类的继承顺序,先调用 Sofa 类构造函数,再调用 Bed 类构造函数。
调用完基类构造函数之后,调用派生类 Sofabed 中的成员对象(Wood类)的构造函数,最后调用派生类 Sofabed 的构造函数。在析构时,析构函数的调用顺序与构造函数相反。
多继承二义性问题
相比单继承,多继承能够有效地处理一些比较复杂的问题,更好地实现代码复用,提高编程效率,但是多继承增加了程序的复杂度,使程序的编写容易出错,维护变得困难。最常见的就是继承过程中,由于多个基类成员同名而产生的二义性问题。多继承的二义性问题包括两种情况,下面分别进行介绍。
1) 不同基类有同名成员函数
在多继承中,如果多个基类中出现同名成员函数,通过派生类对象访问基类中的同名成员函数时就会出现二义性,导致程序运行错误。【示例2】下面通过案例演示派生类对象访问基类同名成员函数时产生的二义性问题,C++ 代码如下:
#include<iostream> using namespace std; class Sofa //沙发类Sofa { public: void rest(){cout<<"沙发可以坐着休息"<<endl; } }; class Bed //床类Bed { public: void rest(){cout<<"床可以躺着休息"<<endl; } }; class Sofabed:public Sofa,public Bed //Sofabed类,公有继承Sofa类和Bed类 { public: void function(){cout<<"沙发床综合了沙发和床的功能"<<endl; } }; int main() { Sofabed sbed; //创建沙发床对象sbed sbed.rest(); //通过sbed调用rest()函数 return 0; }运行时编译器报错,如下所示:
“Sofabed::rest”不明确
示例分析:- 第 3~7 行代码定义了沙发类 Sofa,该类定义了公有成员函数 rest();
- 第 8~12 行代码定义了床类 Bed,该类也定义了公有成员函数 rest();
- 第 13~17 行代码定义了沙发床类 Sofabed,该类公有继承 Sofa 类和 Bed 类;
- 第 20 行代码,在 main() 函数中创建 Sofabed 类对象 sbed;
- 第 21 行代码通过对象 sbed 调用基类的 rest() 函数,由于基类 Sofa 和基类 Bed 中都定义了 rest() 函数,因此对象 sbed 调用 rest() 函数时会产生二义性。
本例 Sofabed 类与 Sofa 类、Bed 类的继承关系如图 2 所示。

图2 Sofabed类与Sofa类、Bed类的继承关系
由图 2 可知,在派生类 Sofabed 中有两个 rest() 函数,因此在调用时产生了歧义。
多继承的这种二义性可以通过作用域限定符
::
指定调用的是哪个基类的函数,可以将【示例2】中第 21 行代码替换为如下两行代码:
sbed.Sofa::rest(); //调用基类Sofa的rest()函数
sbed.Bed::rest(); //调用基类Bed的rest()函数
2) 间接基类成员变量在派生类中有多份拷贝
在多继承中,派生类有多个基类,这些基类可能由同一个基类派生。例如,派生类 Derive 继承自 Base1 类和 Base2 类,而 Base1 类和 Base2 类又继承自 Base 类。在这种继承方式中,间接基类的成员变量在底层的派生类中会存在多份拷贝,通过底层派生类对象访问间接基类的成员变量时,会出现访问二义性。
【示例3】下面通过案例演示多重继承中成员变量产生的访问二义性问题,C++ 代码如下:
#include<iostream> using namespace std; class Furniture //家具类Furniture { public: Furniture(string wood); //Furniture类构造函数 protected: string _wood; //成员变量_wood,表示材质 }; Furniture::Furniture(string wood) //类外实现构造函数 { _wood=wood; } class Sofa:public Furniture //沙发类Sofa,公有继承Furniture类 { public: Sofa(float length,string wood); //Sofa类构造函数 protected: float _length; //成员变量_length,表示沙发长度 }; //类外实现Sofa类构造函数 Sofa::Sofa(float length,string wood):Furniture(wood) { _length=length; }; class Bed:public Furniture //床类Bed,公有继承Furniture类 { public: Bed(float width, string wood); //Bed类构造函数 protected: float _width; //成员变量_width,表示床的宽度 }; //类外实现Bed类构造函数 Bed::Bed(float width, string wood):Furniture(wood) { _width=width; } class Sofabed:public Sofa,public Bed //Sofabed类,公有继承Sofa类和Bed类 { public: //构造函数 Sofabed(float length,string wood1, float width,string wood2); void getSize(); //成员函数getSize(),获取沙发床大小 }; //类外实现Sofabed类构造函数 Sofabed::Sofabed(float length, string wood1, float width, string wood2): Sofa(length,wood1),Bed(width,wood2) { } void Sofabed::getSize() //类外实现getSize()函数 { cout<<"沙发床长"<<_length<<"米"<<endl; cout<<"沙发床宽"<<_width<<"米"<<endl; cout<<"沙发床材质为"<< _wood<<endl; } int main() { Sofabed sbed(1.8,"梨木",1.5,"檀木"); //创建Sofabed类对象sbed sbed.getSize(); //调用getSize()函数获取沙发床信息 return 0; }运行时编译器报错,如下所示:
"参数" :从“double”到“float”截断
对“_wood”的访问不明确
"Sodabed::_wood"不明确
- 第 3~9 行代码定义了家具类 Furniture,该类定义了保护成员变量 _wood,表示家具材质,还定义了构造函数;
- 第 10~13 行代码在 Furniture 类外实现构造函数;
- 第 14~20 行代码定义了沙发类 Sofa 公有继承 Furniture 类,Sofa 类定义了保护成员变量 _length,表示沙发长度。此外,Sofa 类还定义了构造函数;
- 第 22~25 行代码在 Sofa 类外实现构造函数;
- 第 26~32 行代码定义了床类 Bed 公有继承 Furniture 类,Bed 类定义了保护成员变量 _width,表示床的宽度;此外,Bed 还定义了构造函数;
- 第 34~37 行代码在 Bed 类外实现构造函数;
- 第 38~44 行代码定义了沙发床类 Sofabed,该类公有继承 Sofa 类和 Bed 类。
Sofabed 类、Sofa 类、Bed 类和 Furniture 类之间的继承关系如图 3 所示。

图3 Sofabed类、Sofa类、Bed类和Furniture类之间的继承关系
由图 3 可知,基类 Furniture 的成员变量 _wood 在 Sofabed 类中有两份拷贝,分别通过继承 Sofa 类和 Bed 类获得。创建 Sofabed 类对象时,两份拷贝都获得数据。
本例第 58~59 行代码,创建 Sofabed 类对象 sbed,并通过对象 sbed 调用 getSize() 函数获取沙发床信息。
在 getSize() 函数中,第 54 行代码通过 cout 输出 _wood 成员值,由于 sbed 对象中有两个 _wood 成员值,在访问时出现了二义性,因此编译器报错。
为了避免访问 _wood 成员产生的二义性,必须通过作用域限定符
::
指定访问的是哪个基类的 _wood 成员。可以将【示例3】中的第 54 行代码替换为如下两行代码:
cout<<"沙发床材质为"<<Sofa::_wood<<endl;
cout<<"沙发床材质为"<<Bed::_wood<<endl;
声明:《C++系列教程》为本站“54笨鸟”官方原创,由国家机构和地方版权局所签发的权威证书所保护。