1多态的基本概念
多态分为两类:
- 静态多态:函数重载 和 运算符重载, 复用函数名
- 动态多态:派生类和虚函数实现运行时的多态
静态多态和动态多态的区别:
- 静态多态的函数地址早绑定- 编译阶段确定函数地址
- 动态多态的函数地址晚绑定-运行阶段确定函数地址
动态多态满足条件:
- 得有继承关系
- 子类重写父类的虚函数(重写:函数返回值类型 函数名称 参数列表 完全相同)
动态多态的使用:
父类的指针或者引用 指向子类对象
#include <iostream>
using namespace std;
class Animal
{
public:
virtual void speak() //虚函数,地址不绑定为Animal
{
cout<<"动物在说话"<<endl;
}
};
class Cat:public Animal
{
public:
void speak()
{
cout<<"小猫在说话"<<endl;
}
};
//地址早绑定,所以是动物在说话
void speak(Animal &animal)//引用
{
animal.speak();
}
void test1()
{
Cat cat;
speak(cat);
}
int main() {
test1();
return 0;
}
2多态的原理剖析
#include <iostream>
using namespace std;
class Animal
{
public:
virtual void speak() //虚函数,地址不绑定为Animal
{
cout<<"动物在说话"<<endl;
}
};
class Cat:public Animal
{
public:
void speak()
{
cout<<"小猫在说话"<<endl;
}
};
//地址早绑定,所以是动物在说话
void speak(Animal &animal)
{
animal.speak();
}
void test1()
{
Cat cat;
speak(cat);
}
void test2()
{
cout<<"sizeof Animal="<<sizeof(Animal)<<endl;//去掉virtual空类大小为1
//加上virtual为8 多了一个指针
//vfptr :virtual function pointer 虚函数(表)指针
//指向虚函数表 vftable virtual function table
//表的内部记录虚函数的地址, &Animal::speak
//子类继承后,会同时继承虚函数指针和表,
//当子类重写父类的虚函数,子类的虚函数表内会替换成子类的虚函数地址 &Cat::speak
//当父类的指针或者引用指向子类对象时候,发生多态
//Animal &animal =cat;
//animal.speak;
}
int main() {
//test1();
test2();
return 0;
}
3多态-案例1-计算器类
案例描述:
分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算器类
多态的优点:
- 代码组织结构清晰
- 可读性强
- 对于前期和后期的拓展和维护性高
#include <iostream>
#include <string>
using namespace std;
//普通写法
class Calculator
{
public:
int getResult(string oper)
{
if(oper=="+")
{
return m_Num1+m_Num2;
}
else if(oper=="-")
{
return m_Num1-m_Num2;
}
else if(oper=="*")
{
return m_Num1*m_Num2;
}
//如果想拓展新功能,需要修改源码。
//在真实的开发中 提倡 对拓展进行开放,对修改进行关闭。
}
int m_Num1;
int m_Num2;
};
void test01()
{
Calculator c;
c.m_Num1=10;
c.m_Num2=10;
cout<<c.m_Num1<<"+"<<c.m_Num2<<"="<<c.getResult("+")<<endl;
cout<<c.m_Num1<<"-"<<c.m_Num2<<"="<<c.getResult("-")<<endl;
cout<<c.m_Num1<<"*"<<c.m_Num2<<"="<<c.getResult("*")<<endl;
}
//--------------------------------------------------------
//利用多态
//实现计算器的抽象类
class AbstractCaculator
{
public:
virtual int getResult()
{
return 0;
}
int m_Num1;
int m_Num2;
};
class AddCaculator:public AbstractCaculator
{
public:
int getResult()
{
return m_Num2+m_Num1;
}
};
class SubCaculator:public AbstractCaculator//subtraction减法
{
public:
int getResult()
{
return m_Num2-m_Num1;
}
};
class MulCaculator:public AbstractCaculator
{
public:
int getResult()
{
return m_Num2*m_Num1;
}
};
void test02()
{
//多态使用条件
//父类指针或者引用指向子类对象
AbstractCaculator *abc =new AddCaculator;//堆区
abc->m_Num1=10;
abc->m_Num2=10;
cout<<abc->m_Num1<<"+"<<abc->m_Num2<<"="<<abc->getResult()<<endl;
delete abc;//数据释放,指针类型没变
abc=new SubCaculator;
abc->m_Num1=10;
abc->m_Num2=10;
cout<<abc->m_Num1<<"-"<<abc->m_Num2<<"="<<abc->getResult()<<endl;
delete abc;
}
int main()
{
//test01();
test02();
}
4纯虚函数和抽象类
在多态中,父类中虚函数的实现是毫无意义的,通常会调用子类重写的内容。
因此可以将虚函数改为纯虚函数
纯虚函数语法:virtual 返回值类型 函数名 (参数列表)=0;
当类中有了纯虚函数,这个类也称为抽象类。
抽象类特点:
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则子类也属于抽象类。
#include <iostream>
#include <string>
using namespace std;
//抽象类
class Base
{
public:
//纯虚函数
virtual void func()=0;
};
class Son:public Base
{
public:
void func()
{
cout<<"func()"<<endl;
}
};
void test01()
{
Base *base = new Son;
base->func();
}
int main ()
{
test01();
}
5多态-案例2-制作饮品。
案例描述:
制作饮品大体流程:煮水-》冲泡-〉倒入杯中-》加入辅料
6虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用子类的析构代码。
解决方式:将父类的析构函数改为虚析构或者纯虚析构。
虚析构和纯虚析构共性:
- 都可以解决父类指针释放子类对象。
- 都需要具体的函数实现。
区别:
- 如果是纯虚虚构,该类属于抽象类,无法实例化对象。
虚析构语法:
virtual ~类名()
纯虚析构语法:
virtual ~类名()=0;
类名::~类名(){}
7多态案例3-电脑组装
案例描述:
电脑的主要组成部件为CPU(计算),显卡(显示),内存条(存储)。
将每个零件封装出抽象基类,并且提供不同的厂商生产不同的零件,例如Intel厂商和Lenovo厂商。
创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口。
测试时组装三台不同电脑工作。