本课程将带领大家体会C++面向对象三大特性中的继承特性,讲述了基类、派生类的概念,公有继承、保护继承和私有继承、多重继承及多继承,虚析构函数及虚继承的作用,理解课程内容对于面向对象的学习将大有裨益,所有知识均通过编码实践的方式讲解到操作层面,力求即学即会。
1 课程介绍
2 为什么继承
2.1 继承
说明: 父类(基类)和子类(派生类)
- 被继承的类叫做基类(父类),从其他类继承而来的类叫做派生类(子类)。
- 子类中不仅继承了父类中的数据成员,也继承了父类的成员函数。
- c++ 中的继承关系是概念上的父子关系,不是个体的父子关系。
注意:父类和子类之间必须遵循概念上的父子关系,否则将造成定义和使用的混乱。
人
和工人
,后者是前者的子集,即工人
is a 人
。
1 |
|
2.2 构造过程和析构函数
- 构造过程:父类构造函数 -> 子类构造函数
- 析构过程:子类析构函数 -> 父类析构函数
3 继承方式
说明:成员访问修饰符
(父类的)和 继承修饰符
决定了子类对继承自父类的成员的 成员访问修饰符
。
3.1 成员访问修饰符
成员访问修饰符|可以内部访问|可以外部访问(通过实例访问)
—|—|—
public|是|是
protected|是|否
public|否|否
3.2 继承修饰符
3.3 子类访问继承的成员
4 继承中的特殊关系
4.1 隐藏
说明: 当成员函数同名时,子类会 隐藏
父类中同名的成员函数,即,除非明确指明,否则同名成员调用的是子类的。
注意:只要重名就会发生隐藏
,属性类型是否相同,函数参数是否一致不相干。
技巧: 好的命名方式,会在一定程度上避免父子类成员重名的情况 ,比如命名中带有类型信息,则在一定程度上降低了重名的概率。
1 |
|
4.2 is a
说明: 子类和父类之间的关系可以理解成 is a
的关系。比如,一个工人 is a
人。事实上,父类型的指针、引用或变量确实可以指向子类的实例。这时
- 父类型指针、引用或变量只能访问到在父类中定义过的属性或方法,不能访问子类独有的属性或方法。
- 如果有同名成员,则访问的是父类的。
技巧(防止内存泄漏): 当通过父类型指针销毁子类实例在栈中分配的内存时,需要注意如下问题
- 如果子类没有将析构函数定义为
虚析构函数
,则只会调用父类的析构函数。
- 如果子类定义了虚析构函数,则会先调用子类的虚析构函数,再调用父类的析构函数。
1 |
|
5 多继承与多重继承
5.1 多重继承
说明: 当 B 类从 A 类派生,C 类从 B 类派生,此时成为多重继承
。
1 |
|
5.2 多继承
说明: 一个子类同时继承多个父类就构成了多继承。多继承对父类的个数没有限制,继承方式可以是公共继承、保护继承和私有继承。
1 |
|
6 虚继承
6.1 多继承和多重继承带来的烦恼
说明: 当多继承和多重继承组合在一起的时候,就可能导致一些问题。
例如,农名工
的两个父类工人
和农民
派生自同一个类 人
,这种情况叫做菱形继承。当实例化农民工
的时候,工人
和农民
会分别初始化继承自人
的数据成员,即,有两份人的数据,造成数据的冗余,程序尝试访问非静态的冗余的成员将导致编译错误。
报错: 报错不是必然的,常见错误包括以下情形
重定义
:如果最顶层的基类(比如上面的人类)是通过外部头文件引入的,会导致重定义错误(可以通过在头文件中设置宏防止重复载入)。
重复成员
:如果最顶层的基类就在当前文件中定义,当尝试使用使用冗余的数据成员时,计算机会发现有多份数据成员而导致编译错误(可以通过虚继承来解决)。
6.2 虚继承
说明: 通过在继承过程中声明 virtual
,菱形继承顶端的类只会被实例化一次,而且是调用默认的构造器。
注意:其派生类中通过初始化列表调用的构造函数将被忽略。
1 |
|
output
1 | Person() # 棱形继承顶部类仅被初始化一次 |