public,protected,private三个关键字的作用,实用经验 64和实用经验 66对他们都进行了相关说明。只是前面只讨论了访问机制,未涉及继承机制,所以也相对较简单。实际上public,protected及private还可作为继承方式。如果将它们和继承多态机制结合,则会变的复杂,即它促使了C++复杂的多态,从而令很多初学者望而生畏。
关于继承
通过继承机制,可利用已有的数据类型来定义新的数据类型。所定义的新的类类型不仅拥有新定义的成员,而且还同时拥有旧的成员。我们称已存在的用来派生新类的类为基类,又称为父类。由已存在的类派生出的新类称为派生类,又称为子类。例如:在C++中,如果一个类A继承于类B,我们说B是A的父类(基类)。A是B的子类(派生类)。此时B是A的一部分,B包含于A。
在C++语言中,一个派生类可从一个基类派生,也可从多个基类派生。从一个基类派生的继承称为单继承;从多个基类派生的继承称为多继承。多继承只有C++语言支持,Java和C#就不支持这种多继承机制。这是因为多继承会带来对象切片的问题,实用经验79详细讨论多继承切片问题。
派生类将其本身与基类区别开来的方法是添加数据成员和成员函数。因此,继承的机制将使得在创建新类时,只需说明新类与已有类的区别,从而大量原有的程序代码都可以复用,所以有人称类是“可复用的软件构件”。
C++语言继承的定义格式其实很简单,你只需要指定直接上层父类就可实现,而不需要指定所有的继承层次。
单继承的定义格式:
class <派生类名>:<继承方式><基类名>
{
? ? <派生类新定义成员>
};
其中,<派生类名>是新定义类的名称,它是从<基类名>派生而来的,并且按指定<继承方式>派生。<继承方式>常使用如下三种关键字表示:
- public 表示公有基类;
- ?private 表示私有基类;
- protected 表示保护基类;
多继承的定义格式如下:
class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,…
{
? ? <派生类新定义成员>
};
多重继承和单继承的区别
- 多继承与单继承的区别从定义格式上看,主要是多继承的基类多于一个。而单继承的基类只有一个。
- 多重继承和单继承的差异不仅只是是否仅有一个基类的问题,单继承永远不会存在对象切片问题,如果使用不注意多重继承出现对象切片是常见的。
- ?多重继承相对于单继承,还会存在所有的基类继承一个公共父类的问题。这会涉及virtual继承的问题。
public,protected,private的继承机制
- public继承是最常用的继承机制,public继承时子类具有最大的权限。公有继承的特点是基类的公有成员和保护成员作为派生类的成员时,它们都保持原有的状态,而基类的私有成员仍然是私有的。
- protected是一种比较有特点的继承机制。保护继承的特点是基类的所有公有成员和保护成员都成为派生类的保护成员,并且只能被它的派生类成员函数或友元访问,基类的私有成员仍然是私有的。
- private是一种很少见的继承方式,私有继承的特点是基类的公有成员和保护成员都作为派生类的私有成员,并且不能被这个派生类的子类所访问。
可见性是我们比较关注的,事实也是如此不同的继承方式其可见性也不尽相同。
在公有继承时,派生类的对象可以访问基类中的公有成员;派生类的成员函数可以访问基类中的公有成员和保护成员。这里,一定要区分清楚派生类的对象和派生类中的成员函数对基类的访问是不同的。
public继承可见性:
- 基类成员对其对象的可见性:公有成员可见,其他不可见。这里保护成员同于私有成员。
- 基类成员对派生类的可见性:公有成员和保护成员可见,而私有成员不可见。这里保护成员同于公有成员。
- 基类成员对派生类对象的可见性:公有成员可见,其他成员不可见。
在私有继承时,基类的成员只能由直接派生类访问,而无法再往下继承。这也是private继承最大的特征。
private继承可见性:
- 基类成员对其对象的可见性:公有成员可见,其他成员不可见。
- 基类成员对派生类的可见性:公有成员和保护成员是可见的,而私有成员是不可见的。
- 基类成员对派生类对象的可见性:所有成员都是不可见的。
protected继承方式与私有继承方式的情况相同。两者的区别仅在于对派生类的成员而言,对基类成员有不同的可见性。基类中public、protected成员在子类中表现为protected成员。
protected继承可见性:
- 基类成员对其对象的可见性:公有成员可见,其他成员不可见。
- 基类成员对派生类的可见性:公有成员和保护成员是可见的,而私有成员是不可见的。
- 基类成员对派生类对象的可见性:所有成员都是不可见的。
三种不同的继承方式的基类数据在派生类中的可见性,可总结为表11-1所示。
表11-1 ?基类数据在派生类中的可见性
????????? 继承方式? ? ? ? ? | public | protected | private |
---|
public | public | protected | 不可见 | protected | protected | protected | 不可见 | private | private | private | 不可见 |
私有继承后,派生类在自己的内部依旧可以访问其基类的public和protected方法,但在外部访问该派生类时不能访问其基类的public方法了,因为已经变成private了。
其他说法
关于可访问性还有另的一种说法。这种规则中,称派生类的对象对基类访问为水平访问,称派生类的派生类对基类的访问为垂直访问。一般规则如下:
- 公有继承时,水平访问和垂直访问对基类中的公有成员不受限制;
- 私有继承时,水平访问和垂直访问对基类中的公有成员也不能访问;
- 保护继承时,对于垂直访问同于公有继承,对于水平访问同于私有继承。
- 对于基类中的私有成员,只能被基类中的成员函数和友元函数所访问,不能被其他的函数访问。
接口继承和实现继承及组合
基类和派生类描述了两者的相对关系,其实基类和派生类的关系还有接口继承和实现继承。
接口继承,派生类是基类定义的延续。这种方式先定义一个抽象基类,该基类中有些操作并未实现。然后定义非抽象的派生类,实现抽象基类中定义的操作。例如,虚函数就属此类情况。这时,派生类是抽象的基类的实现,即可看成是基类定义的延续。这也是派生类的一种常用方法。
实现继承,派生类是基类的具体化,类的层次通常反映了客观世界中某种真实的模型。在这种情况下,不难看出:基类是对若干个派生类的抽象,而派生类是基类的具体化。基类抽取了它的派生类的公共特征,而派生类通过增加行为将抽象类变为某种有用的类型。
除此之外,还有另外一种关系机制,就是派生类是基类的组合(就是聚合)。这种现象出现在多继承的情况下,此时一个派生类有多个基类,此时派生类是所有基类行为的组合。但是这种多继承方式也是C++最让人诟病的地方,多继承的确可以给我们带来基类行为的聚合,但是它也同时给我们带来了对象的MI(对象切片)陷阱问题。下面这段代码展现了这个问题:
// 沙发
class CSofa { ......};
// 床
class CBed { .... };
// 沙发床
class CSofaBed : public CSofa, public CBed { ....};
// 声明一个沙发床对象
CSofaBed MySofaBed;
// 将沙发床对象地址,赋值给沙发对象指针。
CSofa *pMySofa = &MySofaBed;
// 将沙发对象指针,强制转换为床对象指针,导致床对象指针非法。
CBed *pMyBed = (CBed *)pMySofa; ? // pMyBed 是一个非法指针,这就是MI陷阱。
最佳实践
- 对象MI最容易出现的地方是强制类型转换,所以在C++中,请不要轻易的使用强制类型转换。因为这是很危险的。关于改问题实用经验 79中会详细讲述。
- 除非必要,请不要以多继承方式实现类的继承。
请谨记
- 继承是C++中实现多态的重要方法,C++支持public,protected和private三种继承方式,请明确知道3者的区别和可见性
- 除非必要请不要实现多继承。多继承的强制类型转换会导致MI问题。
|