今天,我们继续踏入追寻C++的冒险历程。上一章我们简单介绍并且了解了C++中的STL。那么本章将为大家讲解C++作为面向对象编程的三大特性之一的继承。下面让我们一起来进入继承的学习。
继承(inheritance)机制是⾯向对象程序设计使代码可以复⽤的最重要的⼿段,它允许我们在保持原有类特性的基础上进⾏扩展,增加⽅法(成员函数)和属性(成员变量),这样产⽣新的类,称派⽣类,原类称为基类。继承呈现了⾯向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的函数层次的复⽤,继承是类设计层次的复⽤。
下⾯我们看到没有继承之前我们设计了两个类Student和Teacher,Student和Teacher都有姓名/地址/电话/年龄等相同的成员变量,都有identity成员函数,设计到两个类⾥⾯就是冗余的。当然他们也有⼀些不同的成员变量和函数,⽐如⽼师独有成员变量是职称,学⽣的独有成员变量是学号;学⽣的独有成员函数是学习,⽼师的独有成员函数是授课。
这种冗余该怎么解决呢?我们可以把公共的成员都放到另一个类:Person类中,并且使Student类和Teacher类都继承Person类,这样,在这两个类中就可以复⽤这些成员,就不需要重复定义了,省去了很多⿇烦。
继承的语法如下图所示,在类名后面加冒号+ 继承方式 + 继承的类名
通过上图我们可以看到Person是基类,也称作⽗类。Student是派⽣类,也称作⼦类。(因为翻译的原因,所以既叫基类/派⽣类,也叫⽗类/⼦类) 。
继承方式有三种:
可以发现,继承方式与我们的访问限定符是一样的。在前面说到访问限定符时,我们知道public修饰的成员是可以在类外直接访问的,而protected和private修饰的成员是不能够在类外直接访问的。既然protected和private在这里的功能相同,那为什么会有两个呢,它们之间有没有什么区别呢?其实在普通的类中它们两个是没有什么区别的,使用哪个都可以。但是在继承中,它们的区别就会显现出来。我们先来看一看继承方式不同对基类的影响:
上表的含义是基类的成员通过不同的继承方式继承后在其派生类中会成为哪种类型的成员。
需要注意的是:
基类private成员在派⽣类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派⽣类中能访问,就定义为protected。可以看出protected成员限定符是因继承才出现的。
实际上⾯的表格我们进⾏⼀下总结会发现,基类的私有成员在派⽣类都是不可⻅。基类的其他成员在派⽣类的访问⽅式 == Min(成员在基类的访问限定符,继承⽅式)。(public > protected > private)
使⽤关键字class时默认的继承⽅式是private,使⽤struct时默认的继承⽅式是public,不过最好显⽰的写出继承⽅式。在实际运⽤中⼀般使⽤都是public继承,⼏乎很少使⽤protetced和private继承,也不提倡使⽤protetced和private继承,因为protetced和private继承下来的成员都只能在派⽣类的类⾥⾯使⽤,实际中扩展维护性不强。
前面我们学习了stack是容器适配器,通过对一般容器的接口进行封装得到的。那么我们是不是也可以通过继承的方式去实现呢?当然是可以的,看下列代码:
在继承类模板时需要注意使用基类的成员时需要指定类域,这是因为模板是按需实例化的,如果没有用到某个成员函数那么是不会实例化出来的,这样当我们在派生类直接使用基类的成员函数时会找不到该函数,导致编译器报错。
如上图所示,我们将派生类Student的对象赋值给基类的指针,那么这个指针指向的内容只包括派生类Student中的基类部分,引用同理。至于能否将基类的指针和引用能否赋值给派生类呢?答案是可以的,我们可以将基类的指针或者引⽤通过强制类型转换赋值给派⽣类的指针或者引⽤。但是必须是基类的指针是指向派⽣类对象时才是安全的。这⾥基类如果是多态类型,可以使⽤RTTI(Run-Time Type Information)的dynamic_cast 来进⾏识别后进⾏安全转换(了解即可,后续章节会进行讲解)。
派生类对象可以赋值给基类(通过调用基类的拷贝构造函数,下面会将)。
在继承体系中基类和派⽣类都有独⽴的作⽤域。 派⽣类和基类中有同名成员,派⽣类成员将屏蔽基类对同名成员的直接访问,这种情况叫隐藏。(在派⽣类成员函数中,可以使⽤ 基类::基类成员 显⽰访问)需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏,并不会构成重载,因为它们不在同一作用域。
上述代码无法通过编译,会产生报错,因为被final修饰的类无法被继承
友元关系不能被继承,也就是说基类友元不能访问派⽣类私有和保护成员。一个很简单的例子就可以理解,父亲的朋友一定是我的朋友吗?当然不是,只有通过我们自己的努力才能使父亲的朋友也成为我的朋友。
基类定义了static静态成员,则整个继承体系⾥⾯只有⼀个这样的成员。⽆论派⽣出多少个派⽣类,都只有⼀个static成员实例。
单继承:⼀个派⽣类只有⼀个直接基类时称这个继承关系为单继承。
多继承:⼀个派⽣类有两个或以上直接基类时称这个继承关系为多继承,多继承对象在内存中的模型是,先继承的基类在前⾯,后⾯继承的基类在后⾯,派⽣类成员在放到最后⾯。
菱形继承:菱形继承是多继承的⼀种特殊情况。菱形继承的问题,从下⾯的对象成员模型构造,可以看出菱形继承有数据冗余和⼆义性的问题,在Assistant的对象中Person成员会有两份。⽀持多继承就⼀定会有菱形继承,像Java就直接不⽀持多继承,规避掉了这⾥的问题,所以实践中我们也是不建议设计出菱形继承这样的模型的。
为了解决菱形继承中数据冗余和二义性的问题,C++中提出来了另一个概念:虚继承。虚继承即是在菱形继承的中间,也就是继承同一个类的若干个派生类在继承时加上virtual关键字,如下列代码所示:
我们可以设计出多继承,但是不建议设计出菱形继承,因为菱形虚拟继承以后,⽆论是使⽤还是底层都会复杂很多。当然有多继承语法⽀持,就⼀定存在会设计出菱形继承,像Java是不⽀持多继承的,就避开了菱形继承。
其实在C++中是存在菱形继承的,如我们所熟知的iostream,这就是一个菱形继承的例子。当然,菱形继承能不用就尽量不要使用。
很多⼈说C++语法复杂,其实多继承就是⼀个体现。有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,底层实现就很复杂,性能也会有⼀些损失,所以最好不要设计出菱形继承。多继承可以认为是C++的缺陷之⼀,后来的⼀些编程语⾔都没有多继承,如Java。
继承允许你根据基类的实现来定义派⽣类的实现。这种通过⽣成派⽣类的复⽤通常被称为⽩箱复⽤(white-box reuse)。术语“⽩箱”是相对可视性⽽⾔:在继承⽅式中,基类的内部细节对派⽣类可⻅ 。继承⼀定程度破坏了基类的封装,基类的改变,对派⽣类有很⼤的影响。派⽣类和基类间的依赖关系很强,耦合度⾼。
对象组合是类继承之外的另⼀种复⽤选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接⼝。这种复⽤⻛格被称为⿊箱复⽤(black-box reuse),因为对象的内部细节是不可⻅的。对象只以“⿊箱”的形式出现。 组合类之间没有很强的依赖关系,耦合度低。优先使⽤对象组合有助于你保持每个类被封装。
优先使⽤组合,⽽不是继承。实际尽量多去⽤组合,组合的耦合度低,代码维护性好。不过也不太那么绝对,类之间的关系就适合继承(is-a)那就⽤继承,另外要实现多态,也必须要继承。类之间的关系既适合⽤继承(is-a)也适合组合(has-a),就⽤组合。
在设计程序时,我们要尽可能的做到低耦合,高聚合。
若有纰漏或不足之处欢迎大家在评论区留言或者私信,同时也欢迎各位一起探讨学习。感谢您的观看!
您需要 登录 才可以下载或查看,没有账号?立即注册
使用道具 举报
本版积分规则 发表回复 回帖后跳转到最后一页
荣誉红客
昨天 20:09
昨天 16:56
昨天 16:49
昨天 13:35
前天 08:06
中国红客联盟公众号
联系站长QQ:5520533