4.7.1 多态的基本概念
多态是C++面向对象三大特性之一
多态分为两类:
- 静态多态:函数重载 和 运算符重载 属于静态多态,复用函数名
- 动态多态:派生类和虚函数实现运行时多态
静态多态和动态多态区别:
- 静态多态的函数地址早绑定 - 编译阶段确定函数地址
- 动态多态的函数地址晚绑定 - 运行阶段确定函数地址
class Animal {
public:
//函数前面加virtual被称为虚函数
//虚函数编译器在编译的时候就不能确定函数调用了
void virtual speak() {
cout << "动物再叫!" << endl;
}
};
class Cat : public Animal {
public:
void speak() {
cout << "猫再叫!" << endl;
}
};
class Dog : public Animal {
public:
void speak() {
cout << "狗再叫!" << endl;
}
};
//执行说话函数
//地址如果早绑定 在编译阶段确定函数地址 执行Animal的speak方法
//如果想执行猫说话,那么地址就不能提前绑定,需要在运行阶段进行绑定,地址晚绑定
//动态多态的两个条件
//1、必须有继承关系
//2、子类重写父类的虚函数(重写:函数返回值类型、函数名称、参数列表完全相同 虚函数在父类里的函数名前加virtual)
//动态多态使用
//父类的指针或引用 执行子类对象
void dospeak(Animal& animal) {
animal.speak();
}
void test() {
Cat cat;
dospeak(cat);
Dog dog;
dospeak(dog);
}
void main() {
test();
}
总结:
多态满足条件
- 有继承关系
- 子类重写父类中的虚函数
多态使用条件
- 父类指针或引用指向子类对象
重写:函数返回值类型 函数名 参数列表 完全一致称为重写
动态多态的虚函数是把原本的函数替换成了一个虚函数指针(vfptr)指向虚函数地址,如果继承了就会继承这个虚函数地址,子类如果重写该虚函数,就会覆盖这个虚函数地址,成子类的虚函数地址。
4.7.2 多态案例一 计算机类
案例描述:
分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算机类
多态的优点:
- 代码组织结构清晰
- 可读性强
- 利于前期和后期的扩展以及维护
示例
//普通实现
/*
class Calculator {
public:
int num_1;
int num_2;
int jisuan(string fh) {
if (fh == "+") {
return num_1 + num_2;
}
else if(fh == "-") {
return num_1 - num_2;
}
else if (fh == "*") {
return num_1 * num_2;
}
else if (fh == "/" && num_2 != 0) {
return num_1 / num_2;
}
else {
cout << "不是正确的运算符!" << endl;
return 0;
}
}
//如果想扩展新的功能,需求修改代码
//在真实的开发中,提倡 开闭原则
//开闭原则: 对扩展进行开发,对修改进行关闭
};
void test() {
Calculator c1;
c1.num_1 = 8;
c1.num_2 = 2;
int num = c1.jisuan("-");
cout << "结果为:" << num << endl;
}
void main() {
test();
}
*/
//多态实现
/*
多态实现好处:
1、组织结构清晰,出错可以快速定位
2、可读性强
3、便于前后期扩展和维护
*/
class AbstractCalculator {
public:
int num_1;
int num_2;
virtual int jisuan() {
return 0;
}
};
//加法计算器类
class Add :public AbstractCalculator {
public:
virtual int jisuan() {
return num_1 + num_2;
}
};
//减法计算器类
class Sub :public AbstractCalculator {
public:
virtual int jisuan() {
return num_1 - num_2;
}
};
//乘法计算器类
class Mul :public AbstractCalculator {
public:
virtual int jisuan() {
return num_1 * num_2;
}
};
void test1() {
//多态使用条件
//父类指针或者引用指向子类对象
//加法运算
//通过父类指针指向新建的子类对象的方法进行运算
AbstractCalculator* abs;//创建一个AbstractCalculator类指针
abs = new Mul;
abs->num_1 = 20;
abs->num_2 = 10;
int res = abs->jisuan();
cout<<"加法结果为:"<< res << endl;
//用完后需要销毁
delete abs;
//通过父类的引用指向子类对象的方法进行运算
Sub sub;
AbstractCalculator& abc= sub;
abc.num_1 = 20;
abc.num_2 = 10;
int ress = abc.jisuan();
cout << "减法结果为:" << ress << endl;
}
void main() {
test1();
}
new创建类对象与不new区别
下面是自己总结的一些关于new创建对象特点:
- new创建对象需要指针接收,一处初始化,多处使用
- new创建对象使用完需delete销毁
- new创建对象直接使用堆空间,而局部不用new定义对象则使用栈空间
- new对象指针用途广泛,比如作为函数返回值、函数参数等
- 频繁调用场合并不适合new,就像new申请和释放内存一样
1、new创建对象例子:
CTest* pTest = new CTest();
delete pTest;
pTest用来接收对象指针。
2、不用new,直接使用类定义申明:
CTest mTest;
此种创建方式,使用完后不需要手动释放,该类析构函数会自动执行。而new申请的对象,则只有调用到delete时再会执行析构函数,如果程序退出而没有执行delete则会造成内存泄漏。
CTest* pTest = NULL;
但使用普通方式创建的类对象,在创建之初就已经分配了内存空间。而类指针,如果未经过对象初始化,则不需要delete释放。
4.7.3 纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是在调用子类重写的内容
因此可以将虚函数改为纯虚函数
纯虚函数语法:virtual 返回值类型 函数名 (参数列表) = 0;
当类中有纯虚函数时,这个类也成为抽象类
抽象类特点:
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
示例:
class AbstractCalculator {
public:
int num_1;
int num_2;
//纯虚函数,只要有一个纯虚函数,这个类就被称为抽象类
virtual int jisuan() = 0;
};
//抽象类子类,如果不重写父类中的纯虚函数,也会被归为抽象类
class vir : public AbstractCalculator{};
void test(){
AbstractCalculator* abs = new Vir;//报错:不允许使用抽象类类型
AbstractCalculator abs;//子类不重写父类纯虚函数,也被归为抽象类
}
void main(){
test();
}
4.7.4 多态案例二-制作饮品
案例描述:
制作饮品大致流程: 煮水 、冲泡、倒入杯中、加入辅料
利用多态技术实现本案例,提供抽象类制作饮品基类,提供子类制作咖啡和茶
冲咖啡:煮水、冲泡咖啡、倒入杯中、加糖和牛奶
泡茶:煮水、冲泡茶叶、倒入杯中、加柠檬
#include <iostream>
using namespace std;
class AbstractDreak {
public:
void water()//煮水
{
cout << "正在煮水" << endl;
}
virtual void chong() = 0;//冲泡
void take() //倒入杯中
{
cout << "倒入杯中" << endl;
}
virtual void add() = 0;//加调料
void makedreak() {
water();
chong();
take();
add();
}
};
class Coffee : public AbstractDreak {
public:
virtual void chong() {
cout << "冲咖啡" << endl;
}
virtual void add() {
cout<<"加入糖和牛奶" << endl;
}
};
class Tea : public AbstractDreak {
public:
virtual void chong() {
cout << "泡茶" << endl;
}
virtual void add() {
cout << "加入柠檬" << endl;
}
};
void dreaking(AbstractDreak& dreak) {
dreak.makedreak();
}
void test() {
cout << "请问你想和coffee或者 tea" << endl;
string dr;
cin >> dr;
if (dr == "coffee") {
cout<< "正在为您冲泡咖啡" << endl;
Coffee coffee;
dreaking(coffee);
}
else if (dr == "tea") {
cout << "正在为您冲泡茶" << endl;
Tea tea;
dreaking(tea);
}
else {
cout << "输入错误!" << endl;
}
}
void main() {
test();
}
4.7.5 虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码。
解决方式:将父类中的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构共性:
- 可以解决父类指针释放子类对象
- 都需要有具体的函数实现
虚析构和纯虚析构的区别:
- 如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构语法:
virtual ~类名(){}
纯虚析构语法:
virtual ~类名() = 0
类名::~类名(){}
示例:
#include <iostream>
using namespace std;
class Animal {
public:
Animal()
{
cout << "调用了Animal的构造函数" << endl;
}
virtual void speak() {}
//虚析构函数 可以调用子类的虚析构函数,释放子类开辟的堆空间
//virtual ~Animal()
//{
// cout << "调用了Animal的虚析构函数" << endl;
//}
//纯虚析构函数
// 有了纯虚析构函数,即使没有其他虚函数,这个类也属于抽象类,无法实例化对象
//virtual ~Animal() = 0;
};
//Animal::~Animal()
//{
// cout << "调用了Animal的纯虚析构函数" << endl;
//}
class Dog :public Animal {
public:
Dog(string name)
{
dog_name = new string(name);
cout << "调用了Dog的构造函数" << endl;
}
virtual void speak()
{
cout << *dog_name << "小狗在叫" << endl;
}
virtual ~Dog()
{
cout << "调用了Dog的析构函数" << endl;
if (dog_name != NULL)
{
delete dog_name;
dog_name = NULL;
}
}
string* dog_name;
};
void test()
{
//父类指针指向子类对象,父类指针在析构时不会调用子类的析构对象,导致如果子类有堆区属性,无法被释放,会存在内存泄露问题
Animal* animal = new Dog("大黄");
animal->speak();
delete animal;
Animal a1;
}
void main()
{
test();
system("pause");
}
总结:
- 虚析构和纯虚析构就是用来解决通过父类指针释放子类对象
- 如果子类中没有堆区数据,可以不写虚析构或纯虚析构
- 拥有纯虚析构函数的类也属于抽象类
4.7.6多态案例三-电脑组装
案例描述:
电脑主要组成部分为CPU、显卡、硬盘
将每个零件封装出抽象基类,并且提供补贴的厂商生产不同的零件
创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口
测试时组装三台不同的电脑进行工作
代码:
#include <iostream>
#include <random>
using namespace std;
class CPU {
public:
virtual void calculation() = 0;
};
class Graphics {
public:
virtual void rendering() = 0;
};
class Disk {
public:
virtual void savefile() = 0;
};
class Intel :public CPU,Graphics,Disk {
public:
void calculation() {
cout << "Intel CPU在进行运算" << endl;
}
void rendering() {
cout << "Intel 显卡在进行渲染" << endl;
}
void savefile() {
cout << "Intel 硬盘在进行存储" << endl;
}
};
class Nvidia :public Graphics {
public:
void rendering() {
cout << "Nvidia 显卡在进行渲染" << endl;
}
};
class AMD :public CPU, Graphics {
public:
void calculation() {
cout << "AMD CPU在进行运算" << endl;
}
void rendering() {
cout << "AMD 显卡在进行渲染" << endl;
}
};
class Kingston :public Disk {
public:
void savefile() {
cout << "Kingston 硬盘在进行存储" << endl;
}
};
class Computer {
public:
Computer(CPU* cpu, Graphics* grap, Disk* disk) {
cpu->calculation();
grap->rendering();
disk->savefile();
}
private:
CPU* c_cpu;
Graphics* c_grap;
Disk* c_disk;
};
void Assemble(int cpu,int grap,int disk)
{
Intel intel;
Nvidia nvidia;
AMD amd;
Kingston kingston;
//使用电脑类进行组装
//Computer computer(&amd, &nvidia, &kingston);
if (cpu == 0) {
intel.calculation();
}
else if (cpu == 1) {
amd.calculation();
}
else {
cout << "CPU品牌有误" << endl;
}
if (grap == 0) {
intel.rendering();
}
else if (grap == 1) {
amd.rendering();
}
else if (grap == 2) {
nvidia.rendering();
}
else {
cout << "显卡品牌有误" << endl;
}
if (disk == 0) {
intel.savefile();
}
else if (disk == 1) {
kingston.savefile();
}
else {
cout << "硬盘品牌有误" << endl;
}
}
void test() {
int cpu, grap, disk,i=0;
cout << "为您生成三套推荐配置:" << endl;
while (i < 3) {
cpu = rand() % 2;
grap = rand() % 3;
disk = rand() % 2;
cout << "第"<<i+1<<"套推荐配置如下:" << endl;
Assemble(cpu, grap, disk);
i++;
}
cout << "您可自定义电脑配置:" << endl;
cout << "请选择你想要的CPU品牌(Intel or AMD)输入0,1:" << endl;
cin >> cpu;
cout << "请选择你想要的显卡品牌(Intel or AMD or Nvidia)输入0,1,2:" << endl;
cin >> grap;
cout << "请选择你想要的硬盘品牌(Intel or Kingston)输入0,1:" << endl;
cin >> disk;
Assemble(cpu, grap, disk);
}
void main() {
test();
}