目录
一、多态的基本概念
1、静态多态
2、动态多态
二、多态的原理剖析
三、多态案例 —— 计算器类
四、纯虚函数和抽象类
五、虚析构和纯虚析构
1、虚析构
2、纯虚析构
六、多态案例 —— 组装电脑
一、多态的基本概念
多态是C++面向对象三大特性之一
-
两类多态
- 静态多态:函数重载和运算符重载(复用函数名)
- 动态多态:派生类和虚函数实现运行时多态
-
静态和动态的多态区别:
- 静态多态的函数地址早绑定,编译阶段确定函数地址
- 动态多态的函数地址晚绑定,运行阶段确定函数地址
1、静态多态
多态是C++面向对象三大特性之一
-
两类多态
- 静态多态:函数重载和运算符重载(复用函数名)
- 动态多态:派生类和虚函数实现运行时多态
-
静态和动态的多态区别:
- 静态多态的函数地址早绑定,编译阶段确定函数地址
- 动态多态的函数地址晚绑定,运行阶段确定函数地址
测试代码:
#includeusing namespace std; class Person1 { public: void print(){ cout << "Person1" << endl; } }; class Person2 :public Person1 { public: void print(){ cout << "Person2" << endl; } }; void myPrint(Person1& p1){ p1.print(); } int main(){ Person2 p2; myPrint(p2); // 父类引用接受子类对象 system("pause"); return 0; }
运行结果:
地址早绑定,编译阶段就确定了地址,无论传入的是子类的什么引用,调用的都是父类的函数。
-
想要调用子类的函数就要地址晚绑定(动态多态)
2、动态多态
在父类的函数前加关键字 virtual 让函数成为虚函数
在父类的函数前加关键字 virtual 让函数成为虚函数
测试代码:
#includeusing namespace std; class Person1 { public: // 虚函数 virtual void print() { cout << "Person1" << endl; } }; class Person2 :public Person1 { public: void print() { cout << "Person2" << endl; } }; void myPrint(Person1& p1) { p1.print(); } int main() { Person2 p2; myPrint(p2); system("pause"); return 0; }
运行结果:
根据传入对象执行类的函数,print() 函数不能提早确定。
-
动态多态满足条件
1、有继承关系
2、子类重写父类虚函数
(重写需要函数完全一致,参数列表,返回值,函数名,子类 virtual 可以写也可以不写)
二、多态的原理剖析
计算类中含有虚函数所占的空间大小
64bit 指针大小 8 字节
vs 开发者命令提示符工具,当子类继承父类虚函数
三、多态案例 —— 计算器类
描述:
利用多态技术,设计实现两个操作数进行运算的计算器类
-
多态的优点
- 代码组织结构清晰
- 可读性强
- 利于前期和后期的拓展以及维护
-
实际开发中提倡 开闭原则
开闭原则:对拓展进行开放,对修改进行关闭
描述:
利用多态技术,设计实现两个操作数进行运算的计算器类
-
多态的优点
- 代码组织结构清晰
- 可读性强
- 利于前期和后期的拓展以及维护
-
实际开发中提倡 开闭原则
开闭原则:对拓展进行开放,对修改进行关闭
测试代码:
#includeusing namespace std; // 实现计算器抽象类 class AbstractCalculator { public: virtual int getResult(){ return 0; } int m_Num1; int m_Num2; }; // 乘法类 class MulCalculator :public AbstractCalculator{ public: virtual int getResult(){ return m_Num1 * m_Num2; } }; // 加法类 class AddCalculator :public AbstractCalculator{ public: virtual int getResult(){ return m_Num1 + m_Num2; } }; // // 可继续添加 void test(){ AbstractCalculator* abc = new AddCalculator; abc->m_Num1 = 10; abc->m_Num2 = 10; cout << abc->m_Num1 << " + " << abc->m_Num2 << " = " << abc->getResult() << endl; } int main(){ test(); system("pause"); return 0; }
运行结果:
四、纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是无意义的,主要都是调用子类重写的内容
-
因此可以将虚函数改为纯虚函数
-
纯虚函数语法:virtual 返回值类型 函数名(参数列表)= 0;
-
当类中有纯虚函数(父类),这个类称为 抽象类
-
抽象类特点:
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象函数
在多态中,通常父类中虚函数的实现是无意义的,主要都是调用子类重写的内容
-
因此可以将虚函数改为纯虚函数
-
纯虚函数语法:virtual 返回值类型 函数名(参数列表)= 0;
-
当类中有纯虚函数(父类),这个类称为 抽象类
-
抽象类特点:
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象函数
测试代码:
#includeusing namespace std; // 抽象类 class Base{ public: // 纯虚函数 virtual void func() = 0; }; class Son1 :public Base{ public: // 重写父类虚函数 virtual void func() { cout << "Son1::func 函数调用" << endl; } }; class Son2 :public Base { public: // 重写父类虚函数 virtual void func() { cout << "Son2::func 函数调用" << endl; } }; void test() { // 通过父类指针调用不同子类的函数 Base* base1 = new Son1; base1->func(); Base* base2 = new Son2; base2->func(); } int main(){ test(); system("pause"); return 0; }
运行结果:
五、虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用子类的析构代码
-
解决方法:将父类中的析构函数改为虚析构或者纯虚析构
-
虚析构和纯虚析构共性:
- 解决父类指针释放子类对象
- 都要有具体函数实现
虚析构和纯虚析构区别:
- 类中有纯虚析构,该类属于抽象类,无法实例化
-
虚析构语法:virtual ~类名() { } ;
纯虚析构语法:virtual ~类名() { } = 0;
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用子类的析构代码
-
解决方法:将父类中的析构函数改为虚析构或者纯虚析构
-
虚析构和纯虚析构共性:
- 解决父类指针释放子类对象
- 都要有具体函数实现
虚析构和纯虚析构区别:
- 类中有纯虚析构,该类属于抽象类,无法实例化
-
虚析构语法:virtual ~类名() { } ;
纯虚析构语法:virtual ~类名() { } = 0;
问题:父类指针在析构时候不会调用子类中的析构函数,导致子类如果有堆区属性,就会出现内存泄漏
1、虚析构
测试代码:
#include#include using namespace std; class Person1 { public: // 构造 Person1() { cout << "父类 构造函数调用" << endl; } // 纯虚函数 virtual void Print() = 0; // 析构 ~Person1() { cout << "父类 析构函数调用" << endl; } }; class Person2 :public Person1 { public: // 构造 Person2(string name) { cout << "子类 构造函数调用" << endl; m_Name = new string(name); } // 重写 virtual void Print() { cout << *m_Name << " 子类 中 Print" << endl; } // 析构函数释放堆区数据 ~Person2() { if (m_Name != NULL) { cout << "子类 析构函数调用" << endl; delete m_Name; m_Name = NULL; } } string* m_Name; }; void test() { // 父类指针指向子类对象,开辟堆区数据 Person1* p1 = new Person2("lihua"); p1->Print(); // 释放堆区数据 delete p1; } int main(){ test(); system("pause"); return 0; }
运行结果:
修改:将父类的析构变成虚析构
2、纯虚析构
virtual ~Person1() = 0;
如果纯虚析构只有声明会产生一个无法解析外部命令的错误(链接阶段的错误)
需要代码实现
class Person1 {
public:
// 纯虚析构
virtual ~Person1() = 0;
};
// 纯虚析构带代码实现
Person1::~Person1() {
cout << "Person1 下的纯虚析构函数" << endl;
}
类中含有纯虚函数这个类变为抽象类,无法实例化
六、多态案例 —— 组装电脑
代码:
#includeusing namespace std; // cpu 抽象类 class CPU{ public: // cpu计算纯虚函数 virtual void calculator() = 0; }; // gpu抽象类 class GPU{ public: // gpu显示纯虚函数 virtual void display() = 0; }; // 内存抽象类 class MEM{ public: // memory存储纯虚函数 virtual void sterage() = 0; }; // 继承 CPU 的子类,重写父类纯虚函数 class Intel_CPU :public CPU{ public: // 重写 virtual void calculator(){ cout << "Inter_CPU" << endl; } }; // 继承 GPU 的子类,重写父类纯虚函数 class Intel_GPU :public GPU{ public: // 重写 virtual void display(){ cout << "Inter_GPU" << endl; } }; // 继承 MEM 的子类,重写父类纯虚函数 class Intel_MEM :public MEM{ public: // 重写 virtual void sterage(){ cout << "Inter_MEM" << endl; } }; class AMD_CPU :public CPU{ public: // 重写 virtual void calculator(){ cout << "AMD_CPU" << endl; } }; class AMD_GPU :public GPU{ public: // 重写 virtual void dispaly(){ cout << "AMD_GPU" << endl; } }; class AMD_MEM :public MEM { public: // 重写 virtual void sterage(){ cout << "AMD_MEM" << endl; } }; // computer类 class Computer { public: // 构造 Computer(CPU* cpu, GPU* gpu, MEM* mem){ m_cpu = cpu; m_gpu = gpu; m_mem = mem; } // 拼装函数 void work(){ m_cpu->calculator(); m_gpu->display(); m_mem->sterage(); } // 析构 ~Computer(){ if (m_cpu != NULL) { delete m_cpu; m_cpu = NULL; } if (m_gpu != NULL) { delete m_gpu; m_gpu = NULL; } if (m_mem != NULL) { delete m_mem; m_mem = NULL; } } private: CPU* m_cpu; GPU* m_gpu; MEM* m_mem; }; void test(){ // 堆区创建配件 CPU* IntelCPU = new Intel_CPU; GPU* IntelGPU = new Intel_GPU; MEM* AMDMEM = new AMD_MEM; // 实例化 Computer类 对象,传入指针 Computer* computer = new Computer(IntelCPU, IntelGPU, AMDMEM); // 调用对象成员函数 - 拼装函数 computer->work(); // 销毁堆区数据 delete computer; } int main() { test(); system("pause"); return 0; }



