1. 虚函数
- 语法:
virtual 函数返回类型 函数名(参数表) {函数体}; - 例子:
virtual void func() { } //定义了一个虚函数,系统就会产生一个虚表。
- 结论:
- 当类中含有一个虚函数时,系统就会自动分配一个虚表指针(*vptr),指向系统的虚表
- 用户定义的所有虚函数都在该虚表中
- 虚表的地址,永远都在对象空间的最前面
- 重点,重点,重点!! 子类会把父类的虚表也继承下来,子类与父类公用一个虚表(实现动态多态的条件!!)
2. C++ 中的覆盖
- 回顾隐藏:
子类 与 父类拥有一个同名函数,子类就会把父类的功能接口给隐藏掉。new_base a; base &q=a; //通过对父类进行引用,再调用他的接口。 q.func(); //show_base
- 定义
当基类拥有一个虚函数,并在派生类中,对该虚函数进行了重写。 这种就是覆盖!! - 代码例子
#include <iostream> using namespace std; class base { public: int a; virtual void func() //声明了一个虚函数 { cout << "show_base" << endl; } }; class new_base :public base { public: void func() { cout << "show_new_base" << endl; } }; int main() { //cout << sizeof(new_base) << endl; //16 证明子类把父类的虚表也继承下来了 new_base a; a.show(); //show_new_base //通过基类把 派生隐藏的接口显示出来 base &q=a; q.show(); //show_base }
- 为什么可以覆盖??
- 覆盖的条件:
- 基类必须要有虚函数
- 派生类必须要重写基类的虚函数
- 通过基类 的指针 或 引用,指向派生类,并调用重写的方法。
只有满足上述三个条件才会出现这种覆盖现象。
3. 动态多态
- 实现条件:
- 基类要有虚函数
- 继承
- 派生类重写基类的虚函数
- 把派生类的对象,赋值给基类的 指针 或 引用 ,通过基类调用被重写的接口.
- 例子:
(代码执行情况如下)//定义一个人 class person { public: virtual void work() //设置为 虚拟的工作方式,不明确,所以是虚拟的 { cout << "这个人我们不知道它能干嘛" << endl; } }; //把这些人的工作内容实例化 class JC:public person { public: void work() { cout << "去抓小偷" << endl; } }; class HS:public person { public: void work() { cout << "去做核酸检测" << endl; } }; class CS:public person { public: void work() { cout << "上秋名山!!" << endl; } }; //设计一个工作的接口 void do_work(person *q) { //这个人去工作了 q->work(); } //一个 do_work 的接口 -》作用不同的事物 -> 得到的结果不一样。 int main() { //调用工作的函数 do_work(new person); do_work(new JC); do_work(new HS); do_work(new CS); }
4.虚析构
- 来源
因为通过基类指针去释放派生类的堆空间,系统是无法调用派生类的析构函数的。 所以就有可能造成派生类构造函数中初始化的一些数据成员未被释放。造成资源的浪费,严重的会造成内存的泄漏!! - 语法:
virtual ~析构函数{} - 例子:
(结果如下)#include <iostream> using namespace std; class base { public: base(){cout << "执行base的构造函数" << endl;} virtual ~base(){cout << "执行base的析构函数" << endl;} }; class new_base:public base { public: new_base() { cout << "执行new_base的构造函数" << endl; p = new char[1024]; cout << "分配一块1024的堆空间" << (void *)p << endl; } virtual ~new_base() { cout << "执行new_base的析构函数" << endl; cout << "释放分配的堆空间" << (void *)p << endl; delete p; } private: char *p; }; int main() { //通过基类指针,指向派生类的对象 base *p = new new_base; //通过基类的指针释放派生类的对象空间 delete p; }
总结: 析构函数不管是否应用到多态技术,最好都把他设置为虚析构。 万无一失!
5. 纯虚函数 与 抽象类
-
纯虚函数的定义:
virtual 函数返回类型 函数名(参数表) =0; -
抽象类:
如果一个类中的有纯虚函数那么这个类就是抽象类
特点: 抽象类不能实例化 (定义对象)
提示: 假设派生类,没有对纯虚函数进行实例化,那么这个类还是抽象类。
作用:设计代码的预留框架接口,让派生类去实现。应用案例 :
假设我们想要开一家饮品店, 饮品店制作各种各样饮品 . 但是需要制作 什么饮料还不知道,但是制作的过程大概知道。 把整个制作的过程给抽象出来。 1.加水 -> 2.加辅料 ->3.搅拌 -> 4.打包售卖 ,这些过程先抽象出来。 进行实例化。 假设制作咖啡 : 1.加入热水 -> 2.加入咖啡豆 -> 3. 咖啡机进行搅拌 -> 4.装杯卖 18块钱 。 假设制珍珠奶茶 : 1.加入牛奶 -> 2.加入珍珠 -> 3. 手摇 -> 4.装杯卖 8块钱 。