今天我们探讨一下C++特性之一: 继承
说到继承,我原以为这是一个高大上的东西,到我了解完之后,我发现懒人创造世界这个理论是一点都没错:这只是一种类层次的代码复用的手段。并且为了广大程序猿能够好好偷懒,在这个本就是为了方便的继承机制上添加了一些防止大家用的不愉快的优化措施。
继承的方法
继承很简单,定义一个类时在后面加上冒号和你要继承的类就好了~
继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分。
等等!!这是不是太简单了些?
简单?(naive)
继承方法/成员 | public继承 | protected继承 | private继承 |
---|---|---|---|
基类的public | 子类的public成员 | 子类的protected成员 | 子类的private成员 |
基类的protected | 子类的protected | 子类的protected成员 | 子类的private成员 |
基类的private | 不可见 | 不可见 | 不可见 |
哦 那是不是在要继承的类前面加上继承方式就可以轻松搞定呢?
答案是肯定的,但是要注意的一点就是基类的private成员无论怎么继承在子类里面都不可见(但是却消耗内存),多么难受!而实际情况就是:人们一般都是只使用public继承,极少用到protected和private继承。
继承之后…
父子转换
既然有了继承这个概念之后,那么这个偷懒的产物也肯定给了基类和子类一个关系:基类与子类的复制转换
- 派生类对象可以赋值给基类的对象/基类的指针/基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。
- 基类对象不能赋值给派生类对象,这个容易理解:只有把蛋糕切少的,还没有把蛋糕越切越多的。。。
- 基类的指针可以通过强制类型转换赋值给派生类的指针。但是必须是基类的指针是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用RTTI(Run-Time Type Information)的dynamic_cast来进行识别后进行安全转换。
- 基类的指针可以通过强制类型转换赋值给派生类的指针,但是之后你得注意不要越界了~
子类成家立业(子类独立)
虽然是继承而来,但是基类和子类终究是两个类。子类也会有他自己的想法~
- 在继承体系中基类和派生类都有独立的作用域。毕竟不在一个大括号了。
- 去找父亲办事,但是只有儿子的电话怎么办?简单!让儿子告诉你他父亲的电话。子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用基类::基类成员显示访问)
- 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。也就是说就算参数不一致也算隐藏哦。
- 注意在实际中在继承体系里面最好不要定义同名的成员。没人会想弄出来一个用起来很别扭的类吧。。。
爸爸教会的技能
既然存在父子的转换,那就说明子类不仅继承了父类的成员和函数还有一些其他的东西。。
- 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函
数,则必须在派生类构造函数的初始化列表阶段显示调用。 - 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
- 派生类的operator=必须要调用基类的operator=完成基类的复制。
- 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类
对象先清理派生类成员再清理基类成员的顺序。 - 派生类对象初始化先调用基类构造再调派生类构造。
- 派生类对象析构清理先调用派生类析构再调基类的析构。
明白了以上几点之后,我们知道了一个子类是怎么创造并且销毁的了:
基类构造->派生类构造->。。。->派生类析构->基类析构
利用这点我们可以利用构造函数的私有化使这个类无法被继承!当然了,喜欢偷懒的你一定会发现定义类时用final修饰之后这个类就无法被继承了。
爸爸的朋友不是我的朋友
友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员,这个其实很容易理解:毕竟父子是独立的嘛!
爸爸的传家宝:static成员
说到成员会被继承就不得不说这个特殊的成员:静态成员
值得注意的是:基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例。
嗯。。。这样就可以轻松算出这个家族有过多少对象了呢~
近亲婚姻的烦恼。。。
说起这个,真是让人绝望:一个可以使人偷懒的设定,却因为一些缺陷使人更加头疼:C++多继承机制。
让我们想象一下这个场景:A有两个孩子子B和C,D又是B和C的儿子…
缺陷很明显了:A里的数据D有两份。。
还好聪明的A想出了一个法子:领养的孩子总不算近亲了吧~
只要在B和C继承A时加上关键字virtual就能轻松解决。
不得不说设计者还真是煞费苦心:为了大家能够愉快的编程(偷懒),想出了这个点子。。
在内存里面是这样的:B和C中应该存A数据的部分没有存A的数据而是存了一个虚基表指针,这个表里存着A数据相对与当前位置的偏移量(鼓掌声)。。精彩绝伦!!!
很可惜。。现实中我们都不鼓励使用这个继承。。
甚至在可以使用组合的情况下我们不鼓励使用继承。。