作者 | 梁唐
大家好,我是梁唐。
这是EasyC++系列的第76来聊聊面向对象继承。
通过我们之前几篇关于继承的文章,相信大家也注意到了,派生类和基类之间存在一种特殊的关系。
这种关系非常特殊,今天这篇文章着重聊一下两者之间的关系。
在C++当中,继承的方式有3种,分别是公有继承、保护继承和私有继承。这三种继承方式类似,只是成员变量的权限不同,我们一一来说。
首先是公有继承,它的特点是基类的公有成员和保护成员被继承是都保持原有状态,基类的私有成员仍然是私有的,不能被派生类访问。
其次是保护继承,它的特点是基类的所有公有成员和保护成员都成为派生类的保护成员,并且只能被它的派生类成员函数或友元访问,基类的私有成员仍然是私有的。
最后是私有继承,私有继承的特点是基类的公有成员和保护成员都作为派生类的私有成员,并且不能被这个派生类的子类所访问。
我们可以用一张图来进行总结:
所以继承的逻辑是一样的,只不过权限划分不同。
在这3种继承当中,最常用的是公有继承,它建立的是一种is-a关系。即派生类对象也是一个基类对象,基类对象可以执行的操作也可以对派生类执行。
比如说我们有一个类是Fruit
,还有一个类是Apple
,显然苹果也是水果,所以水果有的属性苹果应该都有,苹果可能有一些特殊的属性,比如热量、品种等等,这些属性不一定适用于所有水果。所以苹果和水果之间的关系就是is-a,可以理解成is-a-kind-of,简单写成is-a。
也就是说苹果是水果,但水果不一定是苹果,这样的一种从属关系。
我们再来看一些反例,比如说午餐和水果之间的关系。午餐当中可能有水果,但午餐本质不是水果。所以我们不能从Fruit
类派生到Lunch
。正确的说法是午餐当中包含了水果,水果是午餐的一个成员。它们之间的关系是has-a的关系。
再来看另外一个错误答案,比如水面像是镜子,但水面不是镜子,镜子也不是水面。因此不能从Water
类派生出Mirror
类。这个相对比较明显,大家应该都能注意到。这两者之间的关系叫做is-like-a。
再来看一个不是那么明显的,叫做is-implemented-as-a(作为...来实现)关系。比如我们可以用数组实现栈,但数组不是栈,栈也不是数组。所以我们不能从Array
类来派生出Stack
类。
再比如uses-a的关系,计算机可以使用打印机,但Computer
类不能派生出Printer
类,这是没有意义的。可以将打印机作为计算机的一个成员变量。
虽然在C++当中我们并没有严格的限制,我们完全可以通过继承是来实现has-a, is-implemented-as-a, uses-a关系。但这样的做法通常会导致一些问题,因此我们在开发的时候需要保持谨慎,坚持使用is-a关系。