4月1日
一、多态
1、多态分为两类:静态多态、动态多态
静态多态:函数的重载和运算符的重载属于静态多态,复用函数名
动态多态:派生类和虚函数运行时实现多态
区别:
静态多态的函数地址早绑定,编译阶段确定函数地址
动态多态的函数地址晚绑定,运行阶段确定函数地址
2、函数重写:函数返回值类型、函数名、参数列表完全相同
3、动态多态满足条件:
1)有继承关系
2)子类重写父类的虚函数
4、动态多态的使用
父类的指针或者引用指向子类的对象
5、多态案例—计算机类
1)多态的优点
代码组织清晰
可读性强
利于后期的扩展以及维护
2)案例
传统写法:
传统写法:如果想扩展新功能,需要修改源码,在真实的开发中,提倡开闭原则:对扩展进行开发,对修改进行关闭
多态写法:
代码:
#include<iostream>
using namespace std;
//利用多态实现计算器
//实现计算器抽象类
class AbstractCalculator{
public:
int m_num1;
int m_num2;
//设为虚函数
virtual int getResult()
{
return 0;
}
};
//加法计算器类
class AddCalculator:public AbstractCalculator {
public:
int getResult()
{
return AbstractCalculator::m_num1 +AbstractCalculator::m_num2;
}
int m_num1; //若此处再声明成员变量,则会造成此类的成员将父类的覆盖掉相加,而不能实现多态,或者加作用域也可以也可以
int m_num2;
};
//减法计算器类
class SubCalculator :public AbstractCalculator {
public:
int getResult()
{
return m_num1 - m_num2;
}
};
//乘法计算器类
class MulCalculator :public AbstractCalculator {
public:
int getResult()
{
return m_num1 * m_num2;
}
};
//除法计算器类
class ChuCalculator :public AbstractCalculator {
public:
int getResult()
{
return m_num1 / m_num2;
}
};
void test02()
{
//多态使用方法:父类的指针或引用指向子类对象
AbstractCalculator *abc = new AddCalculator;
abc->m_num1 = 10;
abc->m_num2 = 5;
cout << abc->m_num1 << "+" << abc->m_num2 << "=" << abc->getResult() << endl; //注意子类中不能再重新声明m_num1,m_num2,否则会造成出错
//对象用完后,记得销毁,因为其置于堆区:需手动销毁
delete abc;
abc = new SubCalculator;
abc->m_num1 = 20;
abc->m_num2 = 15;
cout << abc->m_num1 << "-" << abc->m_num2 << "=" << abc->getResult() << endl;
delete abc;
abc = new MulCalculator;
abc->m_num1 = 10;
abc->m_num2 = 15;
cout << abc->m_num1 << "*" << abc->m_num2 << "=" << abc->getResult() << endl;
delete abc;
abc = new ChuCalculator;
abc->m_num1 = 200;
abc->m_num2 = 2;
cout << abc->m_num1 << "/" << abc->m_num2 << "=" << abc->getResult() << endl;
delete abc;
}
int main()
{
test02();
return 0;
}
6、纯虚函数和抽象类
在多态中,通常父类中的虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此可以将其改为纯虚函数。
1)语法 virtual 返回值类型 函数名(参数列表)=0;
当类中有了纯虚函数,这个类也称为抽象类
2)抽象类的特点:
无法实例化对象;子类必须重写抽象类中的纯虚函数,否则也属于抽象类;
7、虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决方法:将父类中的析构函数改为虚析构和纯虚析构
1)虚析构和纯虚析构的共性:
可以解决父类指针释放子类对象、都需要具体的函数实现
2)区别
如果是纯虚析构,该类属于抽象类,无法实例化对象
3)示例
常规不写虚析构的写法
#include<iostream>
using namespace std;
#include<string>
class Drink{
public:
int m_num1;
int m_num2;
Drink()
{
cout << "Drink的构造函数调用" << endl;
}
//纯虚函数 此时类为抽象类
virtual void zhuWater() = 0;
void makeDrink()
{
zhuWater();
}
~Drink()
{
cout << "Drink的析构函数调用" << endl;
}
};
//子类
class Tea:public Drink {
public:
Tea(string name)
{
cout << "Tea构造函数" << endl;
m_Name = new string(name);
}
virtual void zhuWater()
{
cout<<*m_Name << "煮水" << endl; //注意指针加*解引用
}
~Tea()
{
if (m_Name != NULL)
{
cout << "Tea的析构函数调用" << endl;
delete m_Name;
m_Name = NULL;
}
}
string *m_Name;
};
void test02()
{
Drink *a = new Tea("龙井");
a->makeDrink();
delete a;
}
int main()
{
test02();
return 0;
}
结果:
可以看出并没有调用子类的析构来销毁在堆区建的属性数据。堆区数据没有释放干净会导致内存泄漏。
用虚析构解决:
纯虚析构(需要声明又需要有实现):
有了纯虚析构之后这个类也属于抽象类,无法实例化;
不是每个类都需要写虚析构或者纯虚析构,它们都是为了解决多态时,在子类堆区创建了数据,父类无法调用子类析构代码来释放堆区数据造成内存泄漏的问题。若子类堆区无数据,则可以不写它们。
8、案例:电脑组装
一台电脑分为CPU、内存条、显卡 分别写为三个抽象基类 并提供其功能的纯虚函数作为后续调用接口;
然后写不同的厂商类(生产不同的CPU、显卡、内存条),并实现其纯虚函数;
然后创建一个电脑类提供让电脑工作的函数,并且调用每个零件工作的接口;
即用不同厂商的零件去组装它,并使它工作;
代码:
#include<iostream>
#include<string>
using namespace std;
//cpu类
class CPU {
//注意若不写明其访问权限 则默认为私有,类外不可访问
public:
//纯虚函数 计算
virtual void calculate() = 0;
};
//显卡类
class VideoCard {
public:
//纯虚函数 显示
virtual void display() = 0;
};
//内存条类
class StoreTiao {
public:
//纯虚函数 存储
virtual void store() = 0;
};
//电脑类 提供工作的函数
class Computer {
public:
//构造函数 传入详细零件指针
Computer(CPU *cpu, VideoCard *videoCard, StoreTiao *memory)
{
m_cpu = cpu;
m_videoCard = videoCard;
m_memory = memory;
}
//让零件工作起来 调用其接口
void doWork()
{
m_cpu->calculate();
m_videoCard->display();
m_memory->store();
}
//提供电脑析构函数 释放3个电脑零件
~Computer()
{
if (m_cpu != NULL)
{
delete m_cpu;
m_cpu = NULL;
}
if (m_videoCard != NULL)
{
delete m_videoCard;
m_videoCard = NULL;
}
if (m_memory != NULL)
{
delete m_memory;
m_memory = NULL;
}
cout << "析构函数将零件内存释放完毕" << endl;
}
private:
CPU *m_cpu;
VideoCard *m_videoCard;
StoreTiao *m_memory;
};
//具体厂商
//戴尔
class DelMemory : public StoreTiao {
public:
virtual void store()
{
cout << "戴尔内存条开始工作 存储" << endl;
}
};
class DelCPU :public CPU {
public:
virtual void calculate()
{
cout << "戴尔CPU开始工作 计算" << endl;
}
};
class DelVideoCard : public VideoCard{
public:
virtual void display()
{
cout << "戴尔显卡开始工作 显示" << endl;
}
};
//Lenovo
class LenovoMemory : public StoreTiao {
public:
virtual void store()
{
cout << "联想内存条开始工作 存储" << endl;
}
};
class LenovoCPU :public CPU {
public:
virtual void calculate()
{
cout << "联想CPU开始工作 计算" << endl;
}
};
class LenovoVideoCard : public VideoCard {
public:
virtual void display()
{
cout << "联想显卡开始工作 显示" << endl;
}
};
//Intel
class IntelMemory : public StoreTiao {
public:
virtual void store()
{
cout << "因特尔内存条开始工作 存储" << endl;
}
};
class IntelCPU :public CPU {
public:
virtual void calculate()
{
cout << "因特尔CPU开始工作 计算" << endl;
}
};
class IntelVideoCard : public VideoCard {
public:
virtual void display()
{
cout << "因特尔显卡开始工作 显示" << endl;
}
};
void test01()
{
//第一台电脑零件
//数据存放在堆区 需手动释放
CPU *intelCpu = new IntelCPU;
VideoCard *delVideoCard = new DelVideoCard;
StoreTiao *lenovoMemore = new LenovoMemory;
//创建第一台电脑
//Computer computer1(intelCpu, delVideoCard, lenovoMemore);//第一种创建 放在栈区
Computer *computer1 = new Computer(intelCpu, delVideoCard, lenovoMemore);//第二种创建 放在堆区 需手动释放
computer1->doWork();
delete computer1;
}
int main()
{
test01();
return 0;
}
注意释放内存!!