1.多态的基本概念
多态是C++面向对象三大特性之一
多态分为两类
1.静态多态:函数重载,运算符重载属于静态多态,复用函数名
2.动态多态:派生类和虚函数实现运行时多态
静态多态和动态多态区别
1.静态多态的函数地址早绑定------编译阶段确定函数地址
2.动态多态的函数地址晚绑定-------运行阶段确定函数地址
动态多态满足条件
1.有继承关系
2.子类重写父类的虚函数
动态多态使用
父类的指针或引用,执行子类对象
重写:函数返回值类型,函数名,参数列表,完全一致成为重写
动态多态和静态多态的代码演示
//动物类classAnimal{public://虚函数virtualvoidspeak(){cout"动物在说话"endl;}};//猫类classCat:publicAnimal{public:voidspeak(){cout"小猫在说话"endl;}};//狗类classDog:publicAnimal{public:virtualvoidspeak(){cout"小狗在说话"endl;}};//执行说话函数voiddoSpeak(Animalanimal){//Animalanimal=cat引用animal.speak();}voidtest01(){Catcat;doSpeak(cat);//输出为动物在说话,因为地址早绑定,在编译阶段确定函数地址//如果想执行让猫说话,那么这个函数地址就不能提前绑定,需要在运行阶段进行绑定。Dogdog;doSpeak(dog);//在加上virtual后,输出为小狗在说话}2.多态案例01----计算机类类
案例描述:
分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算机器类
多态的优点
1.代码组织结构清晰
2.可读性强
.利用前期和后期的扩展以及维护
普通的实现
//普通写法classCalculator{public:intgetResult(stringoper){if(oper=="+"){returnm_Num1+m_Num2;}elseif(oper=="-"){returnm_Num1-m_Num2;}elseif(oper=="*"){returnm_Num1*m_Num2;}//如果想扩展新的功能,需要修改源码//在真的开发中提倡开闭原则//开闭原则:对扩展进行开发,对修改进行关闭}intm_Num1;//操作数1intm_Num2;//操作数2};voidtest01(){//创建计算器对象Calculatorc;c.m_Num1=10;c.m_Num2=10;coutc.m_Num1"+"c.m_Num2"="c.getResult("+")endl;coutc.m_Num1"-"c.m_Num2"="c.getResult("-")endl;coutc.m_Num1"*"c.m_Num2"="c.getResult("*")endl;}intmain(){test01();}
多态版本
classAbstractCalcuator{public:virtualintgetResult(){return0;}intm_Num1;intm_Num2;};classAddCalculator:publicAbstractCalcuator{public:intgetResult(){returnm_Num1+m_Num2;}};classSubCalculator:publicAbstractCalcuator{public:intgetResult(){returnm_Num1-m_Num2;}};classMulCalculator:publicAbstractCalcuator{public:intgetResult(){returnm_Num1*m_Num2;}};voidtest02(){//多态使用条件//父类指针或者引用指向子类对象AbstractCalcuator*abc=newAddCalculator;abc-m_Num1=10;abc-m_Num2=10;coutabc-m_Num1"+"abc-m_Num2"="abc-getResult()endl;//用完记的销毁deleteabc;}intmain(){test02();}.纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容
因此可以将虚函数改为纯虚函数
纯虚函数语法:virtual返回值类型函数名(参数列表)=0;
当类中有了纯虚函数,这个类也称为抽象类
抽象类特点:
1.无法实例化对象
2.子类必须重写抽象类中的纯虚函数,否则也属于抽象类
//虚函数和抽象类classBase{public://纯虚函数//只要有一个纯虚函数,这个类称为抽象类/**抽象类特点1.无法实例化对象2.子类必须重写抽象类中的纯虚函数,否则也属于抽象类*/virtualvoidfunc()=0;};classSon:publicBase{virtualvoidfunc(){cout"func函数调用"endl;}};voidtest0(){//Baseb;抽象类是无法实例化对象//newBase;抽象类是无法实例化对象//Sons;子类必须重写抽象类中的纯虚函数,否则也属于抽象类抽象类无法实例化对象Base*base=newSon;base-func();}intmain(){test0();system("pause");return0;}4.多态案例02----制作饮品
案例描述:
制作饮品的大致流程为:煮水—冲泡—倒入杯中—加入辅料
利用多态技术实现案例,提供抽象制作饮品的基类,提供子类制作咖啡和茶叶
//多态的案例二制作饮品classAbstractDrinking{public://煮水virtualvoidBoli()=0;//冲泡virtualvoidBrew()=0;//倒入杯子virtualvoidPourInCup()=0;//加入辅料virtualvoidPutSomething()=0;//制作饮品voidmakeDrink(){Boli();Brew();PourInCup();PutSomething();}};//制作咖啡classCoffee:publicAbstractDrinking{//煮水virtualvoidBoli(){cout"煮农夫山泉"endl;}//冲泡virtualvoidBrew(){cout"冲泡咖啡"endl;}//倒入杯子virtualvoidPourInCup(){cout"倒入杯中"endl;}//加入辅料virtualvoidPutSomething(){cout"加入糖和牛奶"endl;}};classTea:publicAbstractDrinking{//煮水virtualvoidBoli(){cout"煮山泉水"endl;}//冲泡virtualvoidBrew(){cout"冲泡茶叶"endl;}//倒入杯子virtualvoidPourInCup(){cout"倒入杯中"endl;}//加入辅料virtualvoidPutSomething(){cout"什么都不加"endl;}};//制作饮品函数voiddoWork(AbstractDrinking*abc){abc-makeDrink();deleteabc;}voidtest01(){//制作咖啡//第一种创建和调用AbstractDrinking*c=newCoffee;//(new是堆区数据需要释放)doWork(c);//第二种创建和调用//doWork(newCoffee);//制作茶doWork(newTea);}intmain(){test01();system("pause");return0;}5.虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码。
解决方式:将父类中的析构函数改为虚析构或者纯虚析构。
虚析构和纯虚构共性:
1.可以解决父类指针释放子类对象
2.都需要有具体的函数实现
虚析构和纯虚析构区别:
1.如果是纯虚析构,该类属于抽象类,无法实例化对象。
虚析构语法:
virtual~类名(){}
纯虚析构语法:
virtual~类名(){}=0;
classAnimal{public://纯虚函数virtualvoidspeak()=0;Animal(){cout"Animal的构造函数调用"endl;}//第一种解决方法/*利用虚析构可以解决父类指针释放子类对象时不干净问题virtual~Animal(){cout"Animal的析构函数调用"endl;}*///第二种解决方法//纯虚析构,纯虚析构函数需要重写,才可以编译通过。virtual~Animal()=0;};//纯虚析构函数的实现Animal::~Animal(){cout"Animal的纯虚析构函数调用"endl;}classCat:publicAnimal{public:string*m_Name;Cat(stringname){cout"cat的构造函数调用"endl;//目前堆区创建属性了m_Name=newstring(name);}~Cat(){if(m_Name!=NULL){cout"cat的析构函数调用"endl;deletem_Name;m_Name=NULL;}}virtualvoidspeak(){cout*m_Name"小猫会说话"endl;}};voidtest01(){Animal*animal=newCat("汤姆");animal-speak();deleteanimal;}intmain(){test01();/*运行结果Animal的构造函数调用cat的构造函数调用汤姆小猫会说话Animal的析构函数调用没有走cat的析构函数,m_Name指针指向堆区的数据没有释放,造成数据泄露出现这种情况的原因:父类指针在析构时候,不会调用子类中的析构函数,导致子类如果有堆区属性,出现内存泄露解决方法:把父类的析构函数加virtual变成虚析构此时运行结果Animal的构造函数调用cat的构造函数调用汤姆小猫会说话cat的析构函数调用Animal的析构函数调用*/system("pause");return0;}