目录
1.继承的概念及定义
- 1.1继承的概念
- 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。
- 1.2 继承定义
- 1.2.1定义格式
- 1. 先要定义一个基类,基类中:放置的是将子类公共部分抽取出来的内容
- 2. 定义派生类,让其继承自基类
- class 类型 : 继承权限 基类名称
- 1.2.2继承关系和访问限定符
- 基类中任何访问权限的成员都被子类继承
- public继承方式下:
- 基类中public和protected修饰的成员在子类中的权限没有发生变化
- 基类中private修饰的成员在子类中不能直接被访问--->不可见
- 类设计时候,访问权限该如何选择?
- 1. 成员如果想要在类外被访问,将其设置为public
- 2.成员不想在类外直接被访问,但是需要在子类中被访问,将其设置为protected
- 3 .成员不想在类外直接放访问,也不想在子类总被访问,将其设置为private
- protected继承方式下:
- 基类中public修饰的成员在子类中的访问权限变为protected
- 基类中protected修饰的成员在子类中的访问权限仍旧是protected
- 基类中private修饰的成员在子类中不能直接被访问--->不可见
- private继承方式下:
- 基类中public修饰的成员在子类中的访问权限变为private
- 基类中protected修饰的成员在子类中的访问权限变为private
- 基类中private修饰的成员在子类中不能直接被访问--->不可见
- 图解:
- 总结:
- 1. 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。
- 2. 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
- 3. 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected > private。
- 4. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。
- 默认的继承权限:继承列表中没有显式指明访问权限
- class所定义的类默认的继承权限是private
- struct所定义的类默认的继承权限是public
- 5. 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用
- 基类中任何访问权限的成员都被子类继承
- 1.2.1定义格式
2.基类和派生类对象赋值转换(赋值兼容规则)
- —定是在public的继承方式下才满足
- 1.可以直接用子类对象给基类对象赋值,但是反过来不可以(不能使用基类对象给子类对象赋值)
- public继承方式,子类和基类是is-a的关系,可以将子类对象看成是一个基类对象
- 例如:狗一定是动物,但是动物不一定是狗
- 对象模型上来谈:对象中成员变量在内存中的布局形式称为对象模型
- 2.可以使用基类的指针指向子类的对象,但是反过来不可以(不能直接使用子类的指针指向基类对象)
- 不能直接使用子类的指针指向基类对象,如果一定要指向必须进行强转(虽然强转之后能通过编译,但是仍然存在安全隐患)
- 3.可以使用基类的引用去引用子类对象,但是反过来不行(不能使用子类的引用去引用基类对象)
3.继承中的作用域
- 1. 在继承体系中基类和派生类都有独立的作用域
- 2. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)
- 同名隐藏:基类和子类中可能会存在同名的成员
- 如果成员变量同名,类型是否相同无关,子类对象直接访问同名成员变量时,只能访问到自己的,基类同名成员变量无法通过子类对象直接访问
- 如果成员函数同名,和函数原型是否相同无关,子类对象直接访问同名成员函数,优先访问自己的,基类同名成员函数无法通过子类对象直接访问
- 3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
- 4. 注意在实际中在继承体系里面最好不要定义同名的成员。
4.派生类的默认成员函数
- “默认”的意思就是指我们不写,编译器会变我们自动生成一个,那么在派生类中
- 1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
- 1.如果基类没有显示定义任何构造函数,则子类可以定义也可以不用定义
- 2.如果基类显示定义了构造函数,但是基类的构造函数是无参或者全缺省的构造函数,此时子类的构造函数可以定义也可以不用定义
- 3.如果基类显示定义了构造函数,但是基类的构造函数不是无参或者全缺省的构造函数,此时子类必须定义自己的构造函数,并且需要在其子类构造函数初始化列表位置显示调用基类的构造函数,目的:完成从基类中继承下来的成员的初始化工作
- 2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化
- 1.如果基类没有显示定义拷贝构造函数,则子类可以定义也可以不用定义
- 2.如果基类定义拷贝构造函数,一般情况下子类也需要定义拷贝构造函数
- 3. 派生类的operator=必须要调用基类的operator=完成基类的赋值
- 4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序
- 编译器将子类的析构函数编译完成之后,会在子类析构函数最后一条语句之后插入一条调用基类析构函数的汇编语句
- 编译器将子类的析构函数编译完成之后,会在子类析构函数最后一条语句之后插入一条调用基类析构函数的汇编语句
- 5.创建那个类的对象,编译器就会调用该类的构造函数,析构那个类的对象,编译器就会调用该类的析构函数
5. 继承与静态成员
- 基类那些成员被子类继承了?
- 1.成员变量
- 普通成员变量:基类所有的成员普通成员变量都被子类继承了
- 静态成员变量:也被继承了,多个子类中拥有的是同一份静态成员变量,在整个继承体系中,静态成员只有一份
- 2.成员函数
- 普通成员函数:基类所有的成员函数都被子类继承了
- 静态成员函数:也被继承了
- 1.成员变量
6.继承与友元
- 友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员
7.复杂的菱形继承及菱形虚拟继承
- 单继承:一个子类只有一个直接父类时称这个继承关系为单继承
- 对象模型:对象中各个成员变量在内存中的布局方式
- 多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承
- 从对象模型上分析:基类部分在上,子类部分在下,多个基类部分在子类对象中的次序和继承列表中基类出现的先后次序一致
- 注意:继承列表中每个基类前必须要加继承权限,否则就是默认的继承权限
- 从对象模型上分析:基类部分在上,子类部分在下,多个基类部分在子类对象中的次序和继承列表中基类出现的先后次序一致
- 菱形继承
- 单继承+多继承
- 对象模型:
- 代码模拟:
- 当创建出C类的对象,并对其成员_a访问时,编译出错
- 原因分析:
- 最顶层基类A中成员在最下面子类C中存在两份:一份是从B1中继承下来的,一份是从B2中继承下来的,如果直接通过C的对象去访问最顶层基类A中成员时,会出现访问不明确,即:二义性问题。
- 解决上述二义性问题的方法:
- 方式一:访问明确化
- 方式一并没有从根本上解决问题,只是让编译器知道访问哪个基类中的成员,最顶层基类A中的成员在C中还是存在2份,比较浪费空间,二义性问题本质还存在
- 方式二:菱形虚拟继承(让最顶层基类中成员在C中只存在一份)
- 方式一:访问明确化
- 单继承+多继承
虚拟继承简单介绍:
- 在继承权限前加上virtual关键字
- 虚拟继承和普通的单继承区别:
- 1.对象模型是倒立的
- 2.对象多了4个字节
- 3.如果子类D没有显式定义构造函数,则编译器一定会生成,如果D定义了构造函数,则编译器一定会对构造函数进行修改,编译器生成或修改目的:往对象的前4个字节中填充数据
- 4.访问基类成员_b的方式不同:
- 通过汇编语言,观察d对象中_b的赋值过程
- 虚拟继承真正的作用是解决菱形继承中存在的二义性问题
- 菱形虚拟继承解决菱形继承中二义性问题:让最顶层A类中的成员在C中只存储一份
- 代码模拟:
- 菱形虚拟继承解决菱形继承中二义性问题:让最顶层A类中的成员在C中只存储一份