继承的概念
继承的本质是代码重用,不再重复设计已有的东西。比如猴子有眼耳鼻舌等器官,有摸爬滚打等行为,而人是一种猴子(科学术语强迫症患者,以及生物科学工作者请绕行,这里只是借喻),因此上帝在造人的时候,就大可不必重新为人类设计上述猴子本来就具有的特性,相反,上帝会让人类继承猴子的基因,充其量再在此基础上加以润色(直立行走、抽象思维等)使之成为人。因此人是一种继承自猴子的派生物种。从这个角度看,上帝应该是一名面向对象程序员,只不过他用的不是C++/Java,而是基因编码。
三种继承模式
C++的繁复庞杂,一直以来使其饱受诟病。其中一例便是继承。C++的类成员有public、protected和private三种封装模式,继承又有同样的public、protected和private这三种模式,根据初中数学排列组合得知,类成员继承模式居然有多达9种情况之多!这不坑我么?
1、public(公有)继承
假设有一个猴子类(基类)如下:
class monkey
{
...
};
还有一个人类(派生类)如下:
class human : public monkey
{
...
};
上述human类的定义中,使用了public方式继承了monkey类,这意味着:任何一个人类对象,都必然是一个猴子对象。换句话说:一切适用于猴子的行为,都必将适用于人类,反之不成立。
上述表达中的关键是,public继承会确保:猴子类对象的一切(注意,是一切哦)行为,在人类对象上都适用。通常,我们将这样的两个类的关系称为:is-a关系。即human is a monkey。
如果这样的is-a逻辑符合你当前的软件设计需求,那么请使用public继承来实现他们吧!
上述陈述讨论的重点是软件设计层面,而不是语法。语法细节比较枯燥和无聊,罗列如下:
被public继承的基类,①其私有成员在派生类成员方法和派生类对象中均无法访问,②其保护成员可由派生类成员方法访问,但派生类对象无法访问,③其公有成员在派生类成员方法及其对象中均可访问。
2、private(私有)继承
私有继承表达了一种非常隐晦的对象间包含的关系。我们来看一个例子,比如某个类widget内部需要实现一个计数器,而这里刚好有一个timer类:
class timer // 每隔一段时间就会嘀嗒一次
{
public:
virtual void onTick(); // 定时器每嘀嗒一次
// 该函数就调用一次
};
这种情况下,我们可以让someclass私有继承timer,像这样:
class widget : private timer
{
public:
virtual void on Tick();
};
此时,我们将这两个类的关系描述为:widget籍由timer实现而得。但你非常清楚,widget并非一种(is-a)timer,前者只是使用了后者所提供的某些功能。
再举一个例子,比如我们要使用链表(list)的方式实现栈(stack)的逻辑,那么可以让stack类private继承stack类,是前者拥有后者的属性和方法。
这种“籍由某物实现而得”的关系,有时被称为use-a关系(如widget use a timer或stack use a list)。
记住:private继承虽然也有继承两个字,但是跟猴子变人的基因继承的概念一点关系都没有,private继承意味着“用某物实现”的逻辑,比如用timer实现widget,或者用list(链表)实现stack(栈)。
这样的逻辑不仅让我们猛然想起另一种更加自然的表达方式:组合(即使用类对象成员)。我们大可在widget类中内嵌一个timer对象,也可以在stack类中内嵌一个list对象。这种所谓的组合,也是实现use-a关系的经典方式,而且比private继承更加容易理解,更加直观。
因此,除非涉及派生类需要继承基类的vitural或protected成员方法,否则尽量使用组合的方式会让你的代码更讨人喜欢。
3、protected(保护)继承
有时我会说,编程语言提供给我们的语法,不一定必须有一个对照的物理现实意义,它或者是一种极其罕见的不普遍的物件关系。
protected继承,从其语法意义上而言,是为了让其派生类拥有跟基类一样的“可以让派生类继承protected成员”的能力。