C++学习(14)—— 多态
1.多态的基本概念
多态是C++面向对象三大特性之一
多态分为两类:
- 静态多态:函数重载 和 运算符重载属于静态多态,复用函数名
- 动态多态:派生类和虚函数实现运行时多态
静态多态和动态多态区别:
- 静态多态的函数地址早绑定——编译阶段确定函数地址
- 动态多态的函数地址晚绑定——运行阶段确定函数地址
下面通过案例进行讲解多态
#include<iostream>
using namespace std;
//动物类
class Animal
public:
virtual void speak(){
cout << "动物在说话" << endl;
}
};
class Cat : public Animal{
//重写:函数返回值类型 函数名 参数列表 完全相同
void speak(){
cout << "小猫在说话" << endl;
}
};
class Dog : public Animal{
void speak(){
cout << "小狗在说话" << endl;
}
};
//执行说话的函数
//地址早绑定 在编译阶段确定函数地址
//如果想执行让猫说话,那么这个函数地址就不能提前绑定,需要在运行阶段进行绑定,地址晚绑定
void doSpeak(Animal &animal){ //Animal &animal = cat;
animal.speak();
}
void test01(){
Cat cat;
Dog dog;
doSpeak(cat);
doSpeak(dog);
}
int main(){
test01();
return 0;
}
总结:
多态满足条件
- 有继承关系
- 子类重写父类中的虚函数
多态使用条件
- 父类指针或引用指向子类对象
重写:函数返回值类型、函数名、参数列表完全一致,称为重写
多态的原理剖析:
- 未添加virtual关键字之前的Animal类(父类)
- 未重写之前的Cat类(子类)
- 重写之后的Cat类(子类)
2.多态案例——计算器类
案例描述:
分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算器类
多态的优点:
- 代码组织结构清晰
- 可读性强
- 利于前期和后期的扩展以及维护
#include<iostream>
using namespace std;
class AbstractCalculator {
public:
virtual int getResult() {
return 0;
}
int num_1;
int num_2;
};
class Add : public AbstractCalculator {
public:
int getResult() {
return num_1 + num_2;
}
};
class Sub : public AbstractCalculator {
public:
int getResult() {
return num_1 - num_2;
}
};
class Mul : public AbstractCalculator {
public:
int getResult() {
return num_1 * num_2;
}
};
void test01() {
AbstractCalculator *abc = new Add();
abc->num_1 = 10;
abc->num_2 = 10;
cout << abc->num_1 << "+" << abc->num_2 << "=" << abc->getResult() << endl;
delete abc;
abc = new Sub();
abc->num_1 = 10;
abc->num_2 = 10;
cout << abc->num_1 << "-" << abc->num_2 << "=" << abc->getResult() << endl;
delete abc;
}
int main() {
test01();
system("pause");
return 0;
}
总结:C++开发提倡利用多态设计程序结构,因为多态优点很多
3.纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容
因此可以将虚函数改为纯虚函数
纯虚函数语法:virtual 返回值类型 函数名 (参数列表) = 0;
当类中有了纯虚函数,这个类也成为抽象类
抽象类特点:
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
#include<iostream>
using namespace std;
class Base {
public:
//纯虚函数
//只要有一个纯虚函数,这个类称为抽象类
virtual void func() = 0;
};
class Son : public Base {
public:
void func() {
cout << "func函数调用" << endl;
}
};
void test01() {
//Son s; //子类必须重写父类中的纯虚函数,否则无法实例化对象
Base *s = new Son;
s->func();
}
int main() {
test01();
system("pause");
return 0;
}
4.多态案例——制作饮品
案例描述:
制作饮品的大致流程为:煮水、冲泡、倒入杯中、加入辅料
利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶
#include<iostream>
using namespace std;
class AbstractDrinking {
public:
//煮水
virtual void Boil() = 0;
//冲泡
virtual void Brew() = 0;
//倒入杯中
virtual void PourInCup() = 0;
//加入辅料
virtual void PutSmething() = 0;
//制作饮品
void makeDrink() {
Boil();
Brew();
PourInCup();
PutSmething();
}
};
class Coffee : public AbstractDrinking {
//煮水
virtual void Boil() {
cout << "煮纯净水" << endl;
}
//冲泡
virtual void Brew() {
cout << "冲泡咖啡" << endl;
}
//倒入杯中
virtual void PourInCup() {
cout << "倒入杯中" << endl;
}
//加入辅料
virtual void PutSmething() {
cout << "加入牛奶" << endl;
}
};
class Tea : public AbstractDrinking {
//煮水
virtual void Boil() {
cout << "煮矿泉水" << endl;
}
//冲泡
virtual void Brew() {
cout << "冲泡茶叶" << endl;
}
//倒入杯中
virtual void PourInCup() {
cout << "倒入杯中" << endl;
}
//加入辅料
virtual void PutSmething() {
cout << "加入枸杞" << endl;
}
};
void doWork(AbstractDrinking *abs) {
abs->makeDrink();
delete abs;
}
void test01() {
doWork(new Coffee);
cout << "--------------------" << endl;
doWork(new Tea);
}
int main() {
test01();
system("pause");
return 0;
}
5.虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决方式:将父类中的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构共性:
- 可以解决父类指针释放子类对象
- 都需要有具体的函数实现
虚析构和纯虚析构区别:
- 如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构语法:
virtual ~类名(){}
纯虚析构语法:
virtual ~类名(){}
类名::~类名(){}
#include<iostream>
#include<string>
using namespace std;
class Animal {
public:
Animal() {
cout << "Animal构造函数调用" << endl;
}
//利用虚析构可以解决父类指针释放子类对象时不干净的问题
//virtual ~Animal() {
// cout << "Animal析构函数调用" << endl;
//}
//纯虚析构
//有了纯虚析构之后,这个类也属于抽象类,无法实例化对象
virtual ~Animal() = 0;
virtual void speak() = 0;
};
//纯虚析构需要声明,也需要实现
Animal:: ~Animal() {
cout << "Animal纯虚析构函数调用" << endl;
}
class Cat :public Animal{
public:
Cat(string name) {
cout << "Cat构造函数调用" << endl;
m_Name = new string(name);
}
virtual void speak() {
cout << *m_Name << " Cat is speaking" << endl;
}
~Cat() {
if (m_Name != NULL) {
cout << "Cat析构函数调用" << endl;
delete m_Name;
m_Name = NULL;
}
}
string *m_Name;
};
void test01() {
//父类指针指向子类对象
Animal *animal = new Cat("Tom");
animal->speak();
//父类指针在析构时不会调用子类中的析构函数,导致子类如果有堆区属性会导致内存泄漏
delete animal;
}
int main() {
test01();
system("pause");
return 0;
}
总结:
- 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
- 如果子类中没有堆区数据,可以不写虚析构或纯虚析构
- 拥有纯虚析构函数的类也属于抽象类
6.多态案例——电脑组装
案例描述:
电脑主要组成部件为CPU(用于计算),显卡(用于显示),内存条(用于存储)
将每个零件封装出抽象基类,并且提供不同的厂商生产不同的零件,例如intel厂商和lenovo厂商
创建电脑类提供让电脑工作的函数,并且调用每个零件工作的结构
测试时组装三台不同的电脑进行工作
#include<iostream>
#include<string>
using namespace std;
class CPU {
public:
virtual void calculate() = 0;
};
class VideoCard {
public:
virtual void display() = 0;
};
class Memory {
public:
virtual void storage() = 0;
};
class Computer {
public:
Computer(CPU *cpu, VideoCard *vc, Memory *mem) {
m_cpu = cpu;
m_vc = vc;
m_mem = mem;
}
void work() {
m_cpu->calculate();
m_vc->display();
m_mem->storage();
}
~Computer() {
if (m_cpu != NULL) {
delete m_cpu;
m_cpu = NULL;
}
if (m_vc != NULL) {
delete m_vc;
m_vc = NULL;
}
if (m_mem != NULL) {
delete m_mem;
m_mem = NULL;
}
}
private:
CPU *m_cpu;
VideoCard *m_vc;
Memory *m_mem;
};
class intelCPU : public CPU {
virtual void calculate() {
cout << "intel的CPU开始计算了!" << endl;
}
};
class intelVideoCard : public VideoCard {
virtual void display() {
cout << "intel的显卡开始显示了!" << endl;
}
};
class intelMemory : public Memory {
virtual void storage() {
cout << "intel的内存条开始存储了!" << endl;
}
};
class lenovoCPU : public CPU {
virtual void calculate() {
cout << "lenovo的CPU开始计算了!" << endl;
}
};
class lenovoVideoCard : public VideoCard {
virtual void display() {
cout << "lenovo的显卡开始显示了!" << endl;
}
};
class lenovoMemory : public Memory {
virtual void storage() {
cout << "lenovo的内存条开始存储了!" << endl;
}
};
void test01() {
cout << "第一台电脑开始工作" << endl;
CPU *intelCpu = new intelCPU;
VideoCard *intelCard = new intelVideoCard;
Memory *intelMem = new intelMemory;
Computer *computer1 = new Computer(intelCpu, intelCard, intelMem);
computer1->work();
delete computer1;
cout << "--------------------" << endl;
cout << "第二台电脑开始工作" << endl;
Computer *computer2 = new Computer(new lenovoCPU, new lenovoVideoCard, new lenovoMemory);
computer2->work();
delete computer2;
}
int main() {
test01();
system("pause");
return 0;
}
Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.