多态的概念
多态:多种状态、多种形态
水: 水在不同环境下会呈现出不同的状态
C++中: 调用同一个函数名,传入不同的参数,就会执行不同的函数体!
函数重载---其实就是一种多态
静态(绑定)多态和动态(绑定)多态!!!
什么是静态(静态库):
在编译阶段会链接到可执行文件中
什么是动态(动态库):
在运行阶段会链接到可执行文件中
总结:
静态和动态的区别: 链接的时刻不同
什么是静态多态:调用一个函数,在编译阶段就知道要执行的函数是哪一个
体现在:函数重载、运算符重载
G++去编译的时候,会根据函数的参数类型以及参数个数对函数进行重命名,调用的时候不同的函数名对应不同的函数体!
什么是动态多态:调用一个函数,在运行阶段才知道要执行的函数是哪一个
体现在:继承、虚函数
绑定:调用函数的语句与函数体进行绑定
案例: 形状类—计算面积、周长
圆形类—计算面积、周长
矩形类—计算面积、周长
如果后期又要计算三角形的周长和面积,先定义一个三角形的类,换需要在定义一个计算周长和面积的函数与已经存在的函数互为重载!
这样子会造成代码冗余
![](https://i-blog.csdnimg.cn/blog_migrate/71e68da2c4852ab8b1479581af57fd83.png)
解决方案:
既然圆形是形状、矩形也是形状、三角形也是形状,那能否用形状类类型来表示所有的形状,做函数参数呢?
子类是父类的特殊版本! 用父类来统一管理所有的子类!
![](https://i-blog.csdnimg.cn/blog_migrate/2078dab8964c1643f72b3b7cdf76377d.png)
问题:
并没有执行我们希望走的函数!
把子类对象的地址赋值给了父类类型的指针变量,通过父类类型的指针或者引用去调用成员函数,发现执行了子类从父类继承来的成员函数!并没有执行子类自己定义的成员函数!
怎么解决呢?
![](https://i-blog.csdnimg.cn/blog_migrate/91d2b84ab2fdda6ca0bd1ad632f270e0.png)
把父类的对应成员函数变成虚函数;
就是在函数的首部前面 加上一个关键字 virtual。
虚函数的格式:
Virtual 函数类型 函数名(参数列表) {}
![](https://i-blog.csdnimg.cn/blog_migrate/6c483824cdd807e9c5023555a272d5a9.png)
子类继承了父类的虚函数,子类中对应的函数也变成了虚函数!子类中这个关键字virtual可以加也可以不加。
总结一下实现动态多态的条件:
1. 一个父类多个子类
2. 继承关系中,子类要重写父类的虚函数;函数首部是一模一样
3. 用父类的指针或者引用去操作子类对象,调用虚函数才会触发动态!
如果只有虚函数,能触发多态?
![](https://i-blog.csdnimg.cn/blog_migrate/d5f891a771e1313be24c46af91eff240.png)
![](https://i-blog.csdnimg.cn/blog_migrate/d31cd98ea74946ebe77c7b349547d80c.png)
动态多态的实现原理!!!
为什么把父类的函数写成虚函数,通过父类的指针或者引用操作子类对象,调用虚函数就实现了多态呢?
![](https://i-blog.csdnimg.cn/blog_migrate/bc50a9b624a7d0240e20829dd86498cd.png)
虚函数表:这个表里面可以存很多个虚函数的地址;函数指针数组(数组里面存的是虚函数地址)。既然是数组,数组就需要占内存空间的!
函数指针数组什么时候存在的?当一个类中有了虚函数,这个类中就有一个虚函数表(函数指针数组)!。
父类中有虚函数,被子类继承后,在子类中对应的函数也是虚函数!父类有虚函数表,子类也有虚函数表!
子类没有重写虚函数:
![](https://i-blog.csdnimg.cn/blog_migrate/5e6f2c7dab2678a2d6968273ca5513c8.png)
子类重写了虚函数:
![](https://i-blog.csdnimg.cn/blog_migrate/9a22d87a979801211097976c4595e7ff.png)
父类中有虚函数,被子类继承后,在子类中对应的函数也是虚函数!虚函数表中保存虚函数的地址;
如果子类有重写父类的虚函数,那么子类虚函数表中对应函数的地址就变成了子类重写后虚函数的地址,当通过父类的指针或者是引用去调用虚函数的时候,实质是根据虚函数表中的地址去执行了对应的函数!
如果子类中没有重写父类的虚函数,子类虚函数表中存的函数地址是父类中虚函数的地址!
多态中存在的问题!!!
把子类对象的地址赋值给父类的指针变量,通过父类的指针去释放子类对象的时候,发现只走了父类的析构函数,并没有执行子类的析构函数!就会造成内存泄漏的问题!
![](https://i-blog.csdnimg.cn/blog_migrate/d7630a984227a50a54d11d14068ec2fa.png)
如何解决:
把父类的析构函数写成虚函数!
![](https://i-blog.csdnimg.cn/blog_migrate/a65daa6c1b7d12a345324d31d42e3987.png)
![](https://i-blog.csdnimg.cn/blog_migrate/7b2e8dcd2dc9af136f744694c715c297.png)
面试题目:
在设计类的时候,为什么要把父类的析构函数写成虚析构函数?
因为如果创建子类对象的时候,在构造函数里面有通过new的方式去动态分配内空间,通过父类的指针去释放子类对象,只走了父类的析构,子类的析构没有执行, new出来的空间没被释放掉,就造成了内存泄漏!
为什么不执行子类的析构函数,就会造成内存泄漏呢?
析构函数的作用是做对象释放前的资源回收以及内存清理工作,不执行析构,资源释放不完全!
重定义、重载、重写的区别?--面试题目
重定义(隐藏):在继承关系中,子类有和父类同名的成员函数,父类的函数被隐藏了!同名:函数名相同即可!
不加virtual是重定义
何时重定义: 父类的成员函数实现的效果满足不可子类需求
重写(覆盖): 在继承关系中,子类重写父类的虚函数,父类的虚函数在这个子类中被覆盖了!
函数的首部是一模一样的!
重载: 必须在同一个作用域中, 函数名相同,功能相似,参数不同,与返回值类型无关的一组函数互为重载!
抽象类
什么是抽象类呢?
当一个父类中的成员函数我们不知道该如何去实现的时候,但是每一个子类中都需要,并且实现的方法都是不一样的!这个时候父类中的成员函数就可以不用实现了,并且还要能够实现多态,所以必须存在,并且必须是虚函数!
此时就把这个函数写成纯虚函数!
![](https://i-blog.csdnimg.cn/blog_migrate/cb58f64b45622a3f949372168c236225.png)
纯虚函数的格式:
virtual函数类型 函数名(参数列表) = 0;
总结:
1. 抽象类就是一个类中如果有纯虚函数,这个类就是抽象类!
2. 抽象类不能用来创建对象的
![](https://i-blog.csdnimg.cn/blog_migrate/ebbc05c2f304401fd852fe6a4e363a00.png)
1. 抽象类的作用就是用来被继承的!
2. 如果一个类继承了抽象类,但是没有去重写并实现这个纯虚函数,这个类也是抽象类,仍不可创建对象!
接口类
接口类其实就是抽象类的一种应用!
接口类的特点:
所有的成员函数都是纯虚函数!没有数据成员!
何时会设计接口类出来: 描述一种能力或者是一些协议的时候!
飞机: 飞行、降落
鸟类: 飞机、降落
把共同具备的能力提取出来!
![](https://i-blog.csdnimg.cn/blog_migrate/d64dc4d70c8eb9fcf0bf577b100c82c6.png)