目录
继承的相关概念:
复用:重复性的代码肯定是越少越好,随着组织越来越复杂,单纯在main()中写代码会变的越来越难以维护。而函数则更像是一个小的程序。
可以从主函数中脱离出来,使我们可以将任务划分的跟更小,大大降低了我们的程序整体复杂性,函数让我们实现了一定程度的代码复用。
继承:继承的机制是面向对象程序设计使代码可以复用的一种手段,它允许程序员在保持原有类特性的基础上继承扩展,增加功能。这样产生的类
成为派生类!
继承的格式:
继承权限&访问限定符
三种类成员访问限定符:
- public公有
- protected保护
- private私有
三种继承关系
- public公有继承
- protected保护继承
- private私有继承
继承方式及继承权限对应表:
总结:
- 基类private成员在派生类中不可访问(如果基类成员不想在类外被直接访问,但需要在派生类中访问就定义为protected,保护成员限定符因继承才出现)
- public继承是一个接口继承,保持is-a的原则,每个父类可用成员对子类也可用,因为每个子类对象也都是一个父类对象
- protected/private是一个实现继承,基类的部分成员并非完全成为子类接口的一部分,是has-a原则
- 不管是那种继承方式,派生类内部都可以访问基类的公有和保护成员,私有成员则不可见(编译器语法检测无法通过,所以不能访问)
- class默认private,struct默认public
- 一般使用public,极少场景才会使用其他
赋值兼容规则(public继承)
- 子类对象可以赋值给父类对象(切割或切片:仅把父类含有的对象进行赋值)
- 父类对象不能赋值给子类对象
- 父类的指针或引用可以指向子类对象
- 子类的指针或引用不能指向父类对象(可以通过强制类型转换完成,但是不能调用成员函数,会崩溃)
继承中的作用域:
- 在继承体系中基类和派生类都有独立的作用域
- 子类和父类中有同名成员,子类将屏蔽对同名成员的直接访问(在子类成员中:可以使用基类::基类成员 访问)--(隐藏或重定义)
- 在实际体系中最好不要定义同名成员
派生类的默认成员函数:
派生类如果没有显示定义这六个默认成员函数,编译器则会合成
合成:必须依赖与基类,根据基类的相应成员的行为来合成派生类的默认成员函数
生成:不依赖于任何东西,只是编译器根据类的定义简单生成基于基础类型的成员函数
- 基类没有缺省构造函数,派生类必须要在初始化类表中显式给出基类名和参数列表
- 基类没有定义构造函数,则派生类也可以不用定义,全部使用缺省构造函数
- 基类定义了带有形参表构造函数,派生类就一定定义构造函数
【面试题】
系统自动调用析构原因:保证先析构子类在析构父类(子类隐藏了父类的析构函数)栈结构规则
同理:子类初始化时调用父类的默认成员函数初始化父类,调用自己的默认成员函数初始化自己
栈结构:先初始化父类在子类,先析构子类在析构父类
实现一个不能被继承的类:将父类的构造函数定义成私有!
子类合成时先调用父类构造函数,因为私有所有失败!
继承与友元:
友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员(父亲的朋友,不一定是孩子的朋友)
继承与static静态成员:
基类定义了static静态成员,则整个继承体系中只有一个这样的成员,无论派生出多少子类都只有static
继承体系下派生类的对象模型:
对象模型为对象中非静态成员变量在内存中的布局形式,与成员函数无关
- 单继承:一个子类只有一个直接父类
- 多继承:一个子类有两个或以上直接父类
- 菱形继承 我们发现Assistant类中存在两份Person对象,因此在访问继承于基类的成
员变量时,会存在数据冗余及二义性的问题 二义性:两边都有(选择困难) 解决方式:1》指定访问那个父类2》虚继承 - 虚拟继承—— 解决菱形继承的二义性和数据冗余的问题
虚拟继承在进程权限前面加上virtual关键字即可构成虚拟继承
特点:在任何派生类中的virtual基类总共用同一个(共享)对象表示
【注意】:尽量不要设计菱形继承的类
虚继承和直接继承有什么区别
1.时间:在通过继承类对象访问虚基类对象中的成员(包括数据成员和函数成员)时,都必须通过某种间接引用来
完成,这样会增加引用寻址时间(就和虚函数一样),其实就是调整this指针以指向虚基类对象,只不过这个调整
是运行时间接完成的。
2.空间:由于共享所以不必要在对象内存中保存多份虚基类子对象的拷贝,这样较之 多继承节省空间。虚拟继承与
普通继承不同的是,虚拟继承可以防止出现菱形继承时,一个派生类中同时出现了两个基类的子对象。也就是说,
为了保证 这一点,在虚拟继承情况下,基类子对象的布局是不同于普通继承的。因此,它需要多出一个指向基类子
对象的指针