目录
一、继承的概念和定义
二、基类和派生类对象赋值转换
三、继承中的作用域
四、派生类的默认成员函数
五、继承与友元、静态成员
六、复杂的菱形继承及菱形虚拟继承
七、继承的总结与反思
八、多态的概念、定义以及实现
九、抽象类
十、多态的原理
一、继承的概念和定义
继承概念:继承是类设计层次的复用
派生类(子类):继承使代码复用,在保持原有类特性的基础上进行扩展,增加功能,这样一个新的类,就是派生类
基类(父类):被继承的类就是基类
继承定义
例如:人和学生,学生具有人的特征同时,还具备学生这个身份特有的一些特征,人可称为基类,学生则是对应的派生类
继承方式
公有继承,保护继承,以及私有继承
派生类中有基类继承的成员类型(是公有是保护还是私有)
在派生类中不可见:不是没有继承,而是无论在派生类类里面还是类外面都不能使用
基类的其他成员在子类的访问方式=Min( 成员在基类的访问限定符,继承方式)public>protected>private 默认情况(不写继承方式):关键字class时,是私有继承,关键字struct时,是公有继承,但最好还是显示的写出继承方式 注意 1,实际运用中一般都是把基类成员定义为公有成员或保护成员,继承方式采用公有继承 2、继承不会影响派生类自己的成员二、基类和派生类对象赋值转换
派生类对象可以赋值给基类的对象/基类的指针/基类的引用,也能被成为切割或切片 注意:切割或者切片是语法天然的支持行为,不存在类型转换 因为类型转换会涉及临时变量,临时变量具有常性,需要加const,而切割或切片没有加const,所以不存在类型转换基类对象不能赋值给派生类对象,强制类型转换都不行!
派生类对象的指针(引用)指向基类对象,强制类型转换后可以实现,但是会有越界的风险,不建议这样使用
三、继承中的作用域
基类和派生类都有独立的作用域
子类和父类有同名成员时,使用派生类对象访问同名成员时,会屏蔽父类成员,访问子类成员,这种情况叫隐藏或者是重定义,若要访问父类成员,则需指定类域
对于子类与父类成员函数同名(仅需同名),就会构成隐藏(重定义),而不是函数重载,因为函数重载需要在同一作用域中
建议:在继承体系中不要定义同名成员
四、派生类的默认成员函数
四个较为重要的默认成员函数:构造,析构,拷贝构造,赋值重载
我们自己不写时,从父类继承下来的成员会调用父类处理,自己的按照普通类基本规则
一般在这几种情况下,我们必须自己写
父类没有默认构造函数,需要我们自己显示写构造;
子类有资源需要清理,则需自己来写析构函数;
子类存在浅拷贝问题,就需自己来写拷贝构造和赋值运算符重载来解决浅拷贝问题
如何自己写
父类成员调用父类的对应构造、拷贝构造、operator=和析构处理
自己成员按普通类处理
普通构造函数
拷贝构造
赋值运算符重载
析构函数
析构函数名字会被统一处理成destructor(),所以子类的析构函数跟父类的析构函数就构成隐藏
子类析构函数结束时,会自动调用父类的析构函数,所以我们自己实现子类析构函数时,不需要显示调用父类析构函数,这样才能保证先析构子类成员,再析构父类成员,因为要满足先构造的后析构,后构造的先析构,父类成员比子类成员先构造,所以后析构
父类成员
子类成员
五、继承与友元、静态成员
继承与友元
友元关系不能继承,即基类友元不能访问子类私有和保护成员 继承与静态成员 基类定义了static静态成员,则整个继承体系里面只有一个这样的成员六、复杂的菱形继承及菱形虚拟继承
单继承
多继承
菱形继承
如上图,D继承了B和C,但B和C都继承了A,所以D中则有两份A,就有了两个问题
数据冗余,例如:如果A有一个成员开辟了大量空间,就会严重浪费空间
二义性,因为有2份A,所以在调用时,不知道是调用B的成员还是C的成员,模糊不清(可通过指定类域解决)
解决办法:在初始基类的两个派生类中加关键字virtual即可(虚继承)
虚继承解决数据冗余和二义性的原理把B、C类都继承了的A类的成员放在一个公共区域,然后再用虚基表去存储偏移量,然后B,C均有一个指针去指向各自的虚基表,从而可以找到A类的成员
七、继承的总结与反思
多继承其实是C++中的一个缺陷,因为出现了菱形继承问题
继承与组合
继承可以看成是一种is-a关系,比如学生是派生类,人是基类,学生在独有特征的基础上,还继承了人的特征,学生是人,就是一种是的关系
组合则可看成是一种has-a关系,即你是我的一部分,比如头是人的一部分,或者人有头
在继承和组合二者皆可使用时,优先选用组合,因为组合的耦合度相对继承较低,代码维护性好
八、多态的概念、定义以及实现
概念:即多种形态,具体的来说,去完成某个行为,不同的对象去执行会产生不同的效果
比如买票,有的人是半价,有的则是全价
多态的分类
静态多态:函数重载,看起来调用同一个函数有不同行为。静态:原理是编译时实现
动态多态:一个父类的引用或指针去调用同一个函数,传递不同的对象,会调用不同的函数。
动态:原理运行时实现
构成多态的条件
必须通过基类的指针或者引用调用虚函数
注意:
构成多态,跟p的类型没有关系,传的哪个类型的对象,调用的就是这个类型的虚函数 -- 跟对象有关
不构成多态,调用就是p类型的函数 -- 跟类型有关
被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
虚函数:即被virtual修饰的非静态类成员函数称为虚函数,其它函数不能成为虚函数
虚函数的重写(覆盖)
派生类中有一个跟基类完全相同的虚函数(函数名,参数,返回值),称子类的虚函数重写了基类的虚函数虚函数重写的两个例外
协变(基类与派生类虚函数返回值类型不同),当其派生类与基类的返回类型构成父子关系的指针或引用时即可满足协变
析构函数的重写(基类与派生类析构函数的名字不同)
析构函数也构成多态,因为析构函数名会被 统一处理为destructor
析构函数构不构成多态,对于普通对象而言,没有什么影响,但对于动态内存开辟的对象,如果给了父类指针来管理,则有巨大影响
不构成多态,子类对象资源有些未清理,造成内存泄露
构成多态
注意:基类的函数有virtual,派生类的函数没有virtual时也构成多态,派生类虽然没有virtual,但它是先继承父类的虚函数的属性,再完成重写,也算是虚函数
C++11 final和override
在C++98中,如果想让一个类不被继承,采用的方法就是将基类的构造函数定义为私有,就不能实例化对象,同时基类本身也不能直接实例化对象
C++11则是直接用关键字final来进行限制
final:修饰虚函数,表示该虚函数不能再被重写 override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错
重载、覆盖(重写)、隐藏(重定义)的对比
九、抽象类
概念:包含纯虚函数的类叫做抽象类(也叫接口类),抽象类 不能实例化出对象 纯虚函数:在虚函数的后面写上 =0 ,则这个函数为纯虚函数 注意:派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象,所以规定了派生类必须重写,同时,纯虚函数只声明,不实现,因为实现没有价值 实现继承和接口继承 普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现 虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口,如果不实现多态,不要把函数定义为虚函数 十、多态的原理虚函数表实际上就是一个用来存储虚函数指针的指针数组,一般在末尾会存放一个nullptr
普通函数和虚函数的区别
都是存储在代码段,只是虚函数要把地址存一份到虚表,方便实现多态,所以一个类实例化的两个不同对象,他们的虚表是同一份
普通函数是编译时确定地址,而虚函数则需去虚表里面找地址,所以相对而言效率低一点点



