5.3 多重继承的设计
下面主要讨论多重继承的两种方法,即直接继承多基类和多层次继承的问题。
5.3.1 多层继承方法
多层次继承关系指任何一层派生类都可成为基下一层继承的基类。此时,原始基类可以称为第二个派生类的间接基类。其概念图是一个树型结构,如图5.4所示。
多层继承的方法是所有派生类都只需对其上一层基类负责。用户只要知道哪些是可继承的内容即可。
这种继承性有两方面的用途:一是抽象机制,把类组织成层次说明的工具;二是软件重用机制,把类继承作为软件代码重用和功能扩展的工具。
[例5.8] Multiple Inheritance using a class hierarchy(EX5_8.CPP)。
5.3.2 直接继承多个基类的方法
所谓多基类继承是指一个派生类直接继承多个基类。此时两个或多个基类联合产生一个派生类,这被称为多继承。其概念图是一个类格结构,如图5.5所示。
定义格式如下:
class derived_name:access base_class1,access base_class2,…
{
……//成员列表
};
其中access是访问限定词,各个基类可以不同;bass_class1到n都是基类名。构造函数的执行顺序按从左到右的顺序,析构函数则相反。
在多重继承中,公有派生和私有派生对于基类成员在派生类中的可访问性与单继承的规则相同。
[例5.8a] 多重继承的说明EX5_8a.CPP。
5.3.3 多继承的构造函数与析构函数
多继承的构造函数的定义与单继承类似,只是几个构造函数之间用“,”分隔。 在参数个数的设计上必须考虑完成多个基类初始化所需的参数数目。
[例5.9] 建立一个带有滚动条的窗口类。
class window
{
……
public:
window(int top,int left,int bottom,int right);
~window();
};
class scrollbar
{
……
public:
scrollbar(int top,int left,int bottom,int right);
~scrollbar();
};
class scrollbarwind:window,scrollbar
{
……
public:
scrollbarwind(int top,int left,int bottom,int right);
~scrollbarwind();
};
scrollbarwind::scrollbarwind(int top,int left,int bottom,int right):window(top,left,bottom,
right),scrollbar(top,right-20,bottom,right)
{
……
}
[例5.9a] 多重继承的构造函数与析构函数的说明(包括对象成员)。
5.3.4 继承成员二义性与虚基类方法
当一个基类被一个派生类间接继承多次时,或者说在多条继承链路有公共的基类,那么该基类就会存在多个备份,系统无法分辩对基类成员的引用是通过哪个派生类继承来的。于是编译器对于这种不确定性问题发出错误信息。
[例5.10] 二义性问题。
Class base
{
int a;
public:
void f();
};
class derived1:public base
{
};
class derived2:public base
{
};
class derived12:derived1,derived2
{
public:
void g()
{
f(); //错误,是从derived1还是从derived2继承来的?
}
};
注意:二义性(ambiguity)的检查是在访问控制或类型检查之前进行的。因此当不同基类成员中具有相同名字时就会出现二义性,即使只有一个名字可以被派生类访问或只有一个名字的类型与要求相符。
[例5.11] 二义性的进一步分析。
class base1
{
public:
int a;
int b();
int f();
int f(int);
int g();
};
class bsae2
{
int a;
int b();
public:
char f();
int g();
};
class derived:base1,base2{};
void h(derived &d)
{
d.a=1;//error
d.b();//error
d.f();//error
}
如何避免这个问题,可采用的方法如下:
(1)利用范围运算符指明所要调用的成员的类属范围。
(2)在派生类中重新定义一个与基类中同名的成员,使该成员隐蔽基类的同名成员。
(3)将公共基类说明为虚基类,避免在派生类中保留多个基类的备份,而只保存一个实例。
下面着重介绍虚基类的概念和方法。
虚基类的定义:在定义派生类时,要在基类描述前加关键字virtual。 这称为虚基类机制。引入虚基类的原因有两点:一是为了防止二义性,二是为了派生类中只有公共基类的一个数据副本。
[例5.12] 多重继承的问题EX5_12.CPP。
当一个基类被声明为虚基类后,即使它们成为了多继承链路上的公共基类,最后的派生类中也只有它的一个备份。如:
class L{};
class a:virtual public L{};
class b:virtual public L{};
class c:public a,b{};
则在类C的对象中,仅有类L的一个对象数据。
对于虚基类,有两点补充说明:
(1)虚基类构造函数的参数必须由最新派生出来的类负责初始化(即使不是直接继承)
(2)虚基类的构造函数先于非虚基类的构造函数执行。
[例5.13] 虚基类的作用。
从上例可以看出,在类derived12的构造函数初始化表中,调用了间接基类base的构造函数,这对于非虚基类是非法的,但对于虚基类则是合法且必要的。另外,由于虚基类的构造函数先于非虚基类的构造函数被执行,所以在初始化时,将基类的成员private1初始化为pb。
下面主要讨论多重继承的两种方法,即直接继承多基类和多层次继承的问题。
5.3.1 多层继承方法
多层次继承关系指任何一层派生类都可成为基下一层继承的基类。此时,原始基类可以称为第二个派生类的间接基类。其概念图是一个树型结构,如图5.4所示。
多层继承的方法是所有派生类都只需对其上一层基类负责。用户只要知道哪些是可继承的内容即可。
这种继承性有两方面的用途:一是抽象机制,把类组织成层次说明的工具;二是软件重用机制,把类继承作为软件代码重用和功能扩展的工具。
[例5.8] Multiple Inheritance using a class hierarchy(EX5_8.CPP)。
5.3.2 直接继承多个基类的方法
所谓多基类继承是指一个派生类直接继承多个基类。此时两个或多个基类联合产生一个派生类,这被称为多继承。其概念图是一个类格结构,如图5.5所示。
定义格式如下:
class derived_name:access base_class1,access base_class2,…
{
……//成员列表
};
其中access是访问限定词,各个基类可以不同;bass_class1到n都是基类名。构造函数的执行顺序按从左到右的顺序,析构函数则相反。
在多重继承中,公有派生和私有派生对于基类成员在派生类中的可访问性与单继承的规则相同。
[例5.8a] 多重继承的说明EX5_8a.CPP。
5.3.3 多继承的构造函数与析构函数
多继承的构造函数的定义与单继承类似,只是几个构造函数之间用“,”分隔。 在参数个数的设计上必须考虑完成多个基类初始化所需的参数数目。
[例5.9] 建立一个带有滚动条的窗口类。
class window
{
……
public:
window(int top,int left,int bottom,int right);
~window();
};
class scrollbar
{
……
public:
scrollbar(int top,int left,int bottom,int right);
~scrollbar();
};
class scrollbarwind:window,scrollbar
{
……
public:
scrollbarwind(int top,int left,int bottom,int right);
~scrollbarwind();
};
scrollbarwind::scrollbarwind(int top,int left,int bottom,int right):window(top,left,bottom,
right),scrollbar(top,right-20,bottom,right)
{
……
}
[例5.9a] 多重继承的构造函数与析构函数的说明(包括对象成员)。
5.3.4 继承成员二义性与虚基类方法
当一个基类被一个派生类间接继承多次时,或者说在多条继承链路有公共的基类,那么该基类就会存在多个备份,系统无法分辩对基类成员的引用是通过哪个派生类继承来的。于是编译器对于这种不确定性问题发出错误信息。
[例5.10] 二义性问题。
Class base
{
int a;
public:
void f();
};
class derived1:public base
{
};
class derived2:public base
{
};
class derived12:derived1,derived2
{
public:
void g()
{
f(); //错误,是从derived1还是从derived2继承来的?
}
};
注意:二义性(ambiguity)的检查是在访问控制或类型检查之前进行的。因此当不同基类成员中具有相同名字时就会出现二义性,即使只有一个名字可以被派生类访问或只有一个名字的类型与要求相符。
[例5.11] 二义性的进一步分析。
class base1
{
public:
int a;
int b();
int f();
int f(int);
int g();
};
class bsae2
{
int a;
int b();
public:
char f();
int g();
};
class derived:base1,base2{};
void h(derived &d)
{
d.a=1;//error
d.b();//error
d.f();//error
}
如何避免这个问题,可采用的方法如下:
(1)利用范围运算符指明所要调用的成员的类属范围。
(2)在派生类中重新定义一个与基类中同名的成员,使该成员隐蔽基类的同名成员。
(3)将公共基类说明为虚基类,避免在派生类中保留多个基类的备份,而只保存一个实例。
下面着重介绍虚基类的概念和方法。
虚基类的定义:在定义派生类时,要在基类描述前加关键字virtual。 这称为虚基类机制。引入虚基类的原因有两点:一是为了防止二义性,二是为了派生类中只有公共基类的一个数据副本。
[例5.12] 多重继承的问题EX5_12.CPP。
当一个基类被声明为虚基类后,即使它们成为了多继承链路上的公共基类,最后的派生类中也只有它的一个备份。如:
class L{};
class a:virtual public L{};
class b:virtual public L{};
class c:public a,b{};
则在类C的对象中,仅有类L的一个对象数据。
对于虚基类,有两点补充说明:
(1)虚基类构造函数的参数必须由最新派生出来的类负责初始化(即使不是直接继承)
(2)虚基类的构造函数先于非虚基类的构造函数执行。
[例5.13] 虚基类的作用。
从上例可以看出,在类derived12的构造函数初始化表中,调用了间接基类base的构造函数,这对于非虚基类是非法的,但对于虚基类则是合法且必要的。另外,由于虚基类的构造函数先于非虚基类的构造函数被执行,所以在初始化时,将基类的成员private1初始化为pb。