继承内容:
1.继承的概念与实现
2.继承权限&访问限定符
3.赋值兼容规则
4.继承中的作用域(同名隐藏)
5.派生类默认对象
6.继承与友元
7.继承与static静态成员
8.继承体系下的派生类的对象模型
1.继承的概念与实现
-
继承:继承是代码实现复用的重要手段,在保持原有类(基类,父类)的基础上进行扩展,增加功能,这样产生的类是派生类。
-
继承的格式:class 派生类 :继承方式 基类
比如:class people : public student -
如何检测继承:
用类的大小判断 类的大小是类中成员变量的大小,参考结构体对齐
创建对象,观察对象的大小
2.继承权限&访问限定符
-
继承列表中的继承权限:
public;
protected;
private; -
类的访问权限:
public;
protected;
private; -
不同继承方式的下的结果:
1.前提,在 公有 继承方式条件下:公有的继承方式 public
如果是公有的继承方式,那么基类中的成员(成员变量和成员函数)会全部被继承到派生类中,基类中不同访问权限的成员在派生类中的访问权限没有发生改变,但是基类中私有的成员变量在派生类中不可访问---->不可见
比如: 基类中公有成员变量在 派生类中是公有的, 基类中保护的成员变量 在 派生类中是保护的(可以访问), 基类中私有的成员变量在派生类中是私有的,派生类中存在私有的成员变量,但是不能访问这个变量
2.前提,在 保护 继承方式: 保护的继承方式 protected
基类中成员全部被继承到派生类
基类中公有的成员变量在子类中不能直接访问,基类中公有的成员变量在子类中的访问权限变成了protected
基类中protected的成员变量在派生类中的访问权限仍然是protected
基类中private的成员变量在子类中存在但是却不可见,即不能使用
注意:私有成员变量在派生类中不可见
3.前提, 在私有 继承方式:private
基类中公有和保护的成员变量,在派生类中可以被直接使用(直接使用的意思是可以用父类中出现但是子类中表面没出现的成员变量进行 赋值等语句操作),但是基类中私有的成员变量不能在派生类中使用。 私有的继承方式下,所有的成员在类外都不能进行访问。
如果父类中的成员变量是私有的(private),那么在派生类中不能访问这个私有变量。
如果父类中的成员变量是受保护的(protected),那么在派生类中可以访问这个受保护变量。
注意:关键字class 的 默认权限是private
关键字struct 的 默认权限是public
- 不同继承方式下在类外的使用情况:
子类中会把父类的所有成员变量继承了,但是可不可以被访问,取决于成员量在基类中的访问权限,以及继承方式。
1.公有的继承方式,基类中公有的成员在派生类中可以直接使用,也可以在类外直接使用。
2.保护的继承方式,基类中保护的成员在子类中可以直接使用,但是不能在类外使用。
3.私有的继承方式,基类中私有的成员在派生类中不能直接使用,也不能在类外直接使用。
由此在继承方面 体现了私有和公有权限的区别。
3.赋值兼容规则
一般情况下都是同类型给同类型的对象进行赋值。在继承体系中,由于派生类有了自己
的不同于基类的成员函数,所以严格的来说,派生类和其基类不是同一类型。但是派生
类又和基类存在很密切的联系,所以可以将派生类的对象赋值给基类对象,实质上是用
到切片原理,就是将派生类对象中的基类部分再赋值给基类。
在此之前要先了解一下 什么是对象模型。
对象模型简单来说就是对象中各个成员变量在内存中的分布状况。
比如:
赋值规则:
1.可以将派生类对象赋值给基类对象,但是反过来不行—可以用对象模型分析
2.可以用基类的指针来指向派生类对象,但是不能用派生类的指针来指向基类的对象。 主要是指针访问范围有没有越界,可以使用强转来使得派生类的指针来指向基类的对象。
3.可以用基类的引用去引用派生类的对象,因为引用就是指针,所以参照上面一点。
4.继承中的作用域(同名隐藏)
继承中的作用域:
如果子类和父类中有相同名称的成员存在,子类成员将屏蔽父类对同名成员的直接访问,这叫做重定义。同名隐藏!
同名隐藏:意思就是说派生类与基类中如果具有相同名称的成员: 只看名称,不管类型,参数是否相同
相同名称的成员变量 —与变量的类型是否相同无关
相同名称的成员变量 —与成员函数的类型是否相同无关
通过派生类对象调用相同名称的成员时,优先调用派生类自己的,出现了紊乱,加入在类外想赋整型值,(但是子类中成员变量是char型,而基类中是int型),就会把整型值赋给子类中的char类型同名变量。
比如:
如何解决同名隐藏问题?
可以使用 基类::基类成员 显示进行访问
5.派生类默认对象
- 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。 就是由于继承,派生类由两部分组成,所以 无论是 构造还是 析构,都要考虑到这两个部分。
- 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
- 派生类的operator=必须要调用基类的operator=完成基类的赋值。
- 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。
- 派生类对象初始化 会先调用派生类构造函数 在 派生类的构造函数中又要 完成派生类中的基类部分 的构造。 所以,就是先调用派生类的构造,在调用基类的构造函数。
创建的是哪一个类的对象,就会调用哪一个类的构造函数
析构的是哪一个类的对象,就会调用哪一个类的析构函数- 派生类对象析构清理 会先调用派生类析构函数,在清理完之后,在派生类析构函数最后一条语句后增加一条调用基类的析构函数的语句。
6.继承与友元
友元关系不能被继承,也就是说基类中的友元不能被子类继承,也就是说基类友元函数不能访问子类私有和保护成员。
7.继承与static静态成员
静态的成员变量 必须在类外进行初始化。因为静态的成员变量保存在内存的静态区,并且所有由该类实例化出来的对象共享这个成员变量,如果在内里面进行初始化,那么每次实例化 就会改变这个成员变量的值,影响了这个静态变量在其他对象中的值。
基类中的静态成员 可以被子类继承,但是在整个继承体系中只有一份,也就是说就算是继承 大家也是共用同一份静态成员。
8.继承体系下的派生类的对象模型
- 对象模型:单继承,多继承
单继承是一个类只有一个基类
多继承是一个类有多个基类
菱形继承在内存中的存储方式:
菱形继承的缺点:
1.存在二义性,派生类调用的基类中的成员变量时不明确。
2.成员存了两份,浪费内存空间,存在数据冗余性问题。
所以,为了解决菱形继承的两个缺点,引入了菱形虚拟继承
什么是虚拟继承?在继承列表前 加上 virtual
比如: class D: virtual public B
虚基表:
含义:在虚拟继承中,存放基类偏移地址的表,是在解决菱形继承中二义性问题出现的表。
为什么会出现虚基表呢? 可以减小数据冗余度,具体是一种间接性的寻址的方法,通过虚基表中保存的地址偏移量,定位到对应的基类位置。