目录
4.7 多态
4.7.1 多态的基本概念
多态是C++面向对象三大特性之一
多态分为两类
- 静志多态: 函数重载 和 运算符重载属于静态多态,复用函数名
- 动态多态: 派生类和虚函数实现运行时多态
静志多态和动态多态区别:
- 静态多态的函数地址早绑定 - 编译阶段确定函数地址
- 动态多态的函数地址晚绑定 - 运行阶段确定函数地址
下面通过案例进行讲解多态
#include <iostream>
using namespace std;
// 多态的基本概念
// 动物类
class Animal {
public:
virtual void speak() {// 虚函数,子类可以重写
cout << "动物说话" << endl;
}
};
// 猫类
class Cat : public Animal {
public:
void speak() {
cout << "猫说话" << endl;
}
};
// 狗类
class Dog : public Animal {
public:
void speak() {
cout << "狗说话" << endl;
}
};
// 动态多态满足条件
// 1. 有继承关系
// 2. 父类有一个虚函数
// 3. 子类重写了父类的虚函数
// 动态多态使用
// 父类的指针或引用指向子类的对象,调用父类的函数,实际调用的是子类的函数
//执行说话的函数
void speak(Animal& animal) {// 这里的Animal&参数表示传入的是Animal类的引用
animal.speak();// 调用Animal类的speak()函数
}
void test() {
Cat cat;// 实例化一个猫对象
Dog dog;// 实例化一个狗对象
speak(cat); // 输出 "猫说话"
speak(dog); // 输出 "狗说话"
}
int main() {
test();
return 0;
}
总结:
多态满足条件
- 有继承关系
- 子类重写父类中的虚函数
多态便用条件
- 父类指针或引用指向子类对象
重写:函数返回值类型 函数名 参数列表 完全一致称为重写
4.7.2 多态案例--计算器类
案例描述:
分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算器类
多态的优点:
- 代码组织结构清晰
- 可读性强
- 利于前期和后期的扩展以及维护
示例:
#include <iostream>
using namespace std;
// 多态案例--计算器类
// 案例描述:设计一个计算器类,分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算器类
// 普通写法
class Calculator {
public:
int getResult(string oper) {
if (oper == "+") {
return num_1 + num_2;
}
else if (oper == "-") {
return num_1 - num_2;
}
else if (oper == "*") {
return num_1 * num_2;
}
else if (oper == "/") {
return num_1 / num_2;
}
else {
cout << "Invalid operator!" << endl;
return 0;
}
}
void setNum1(int num) {
num_1 = num;
}
void setNum2(int num) {
num_2 = num;
}
int num_1;
int num_2;
};
void testCalculator() {
Calculator c;
c.setNum1(10);
c.setNum2(5);
cout << "10 + 5 = " << c.getResult("+") << endl;
cout << "10 - 5 = " << c.getResult("-") << endl;
cout << "10 * 5 = " << c.getResult("*") << endl;
cout << "10 / 5 = " << c.getResult("/") << endl;
cout << "10 % 5 = " << c.getResult("%") << endl;
cout << "------------------------------------- " << endl;
}
// 利用多态实现计算器
// 多态好处
// 1、组织代码更加清晰
// 2、可读性更好
// 3、可扩展性更强,维护性更好
// 定义计算器基类
class CalculatorBase {
public:
virtual int getResult(){ return 0; }
int num_1;
int num_2;
};
// 定义加法类
class Add : public CalculatorBase {
public:
int getResult() {return num_1 + num_2;}
};
// 定义减法类
class Subtract : public CalculatorBase {
public:
int getResult() {return num_1 - num_2;}
};
// 定义乘法类
class Multiply : public CalculatorBase {
public:
int getResult() {return num_1 * num_2;}
};
// 定义除法类
class Divide : public CalculatorBase {
public:
int getResult() {return num_1 / num_2;}
};
// 测试多态计算器
void testCalculator_2() {
CalculatorBase *c1 = new Add();
c1->num_1 = 10;
c1->num_2 = 5;
cout << "10 + 5 = " << c1->getResult() << endl;
CalculatorBase *c2 = new Subtract();
c2->num_1 = 10;
c2->num_2 = 5;
cout << "10 - 5 = " << c2->getResult() << endl;
CalculatorBase *c3 = new Multiply();
c3->num_1 = 10;
c3->num_2 = 5;
cout << "10 * 5 = " << c3->getResult() << endl;
CalculatorBase *c4 = new Divide();
c4->num_1 = 10;
c4->num_2 = 5;
cout << "10 / 5 = " << c4->getResult() << endl;
delete c1;
delete c2;
delete c3;
delete c4;
}
// 程序入口
int main() {
testCalculator();
testCalculator_2();
return 0;
}
总结:C++开发提倡利用多态设计程序架构,因为多态优点很多
4.7.3 纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容
因此可以将虚函数改为纯虚函数
纯虚函数语法: virtual 返回值类型 函数名(参数列表)=0;
当类中有了纯虚函数,这个类也称为抽象类
抽象类特点:
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
示例:
#include <iostream>
using namespace std;
// 纯虚函数和抽象类
class Base {
public:
// 只要有一个纯虚函数,该类就是抽象类
// 不能创建对象,只能作为基类被继承
// 抽象类特点
// 1. 无法实例化对象
// 2. 抽象类的子类,必须要重写父类的纯虚函数,否则子类也是抽象类
virtual void fun() = 0; // 纯虚函数
};
class Derived : public Base {
public:
virtual void fun() {
cout << "Derived::fun()" << endl;
}
};
void test() {
//Base b; // 抽象类不能创建对象
// Base b1; // 抽象类不能实例化
// Base b2 = new Base; // 抽象类不能实例化
Derived d;// 继承抽象类,必须重写父类的纯虚函数
d.fun();
Base *base = new Derived; // 指针指向抽象类的子类对象,可以调用子类的成员函数
base->fun(); // 调用子类的成员函数
}
int main() {
test();
return 0;
}
4.7.4 多态案例二 - 制作饮品
案例描述:
制作饮品的大致流程为:煮水-冲泡-倒入杯中-加入辅料
利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶
#include <iostream>
#include <string>
using namespace std;
// 制作饮品的基类
class Drink {
public:
virtual void BoilWater() = 0;// 烧水
virtual void AddCondiments() = 0;// 添加调料
virtual void PourInCup() = 0;// 把饮品倒进杯子
virtual void AddSugar() = 0;// 添加糖
virtual void AddMilk() = 0;// 添加牛奶
void makeDrink(){
BoilWater();
AddCondiments();
PourInCup();
AddSugar();
AddMilk();
}
};
// 制作咖啡的子类
class Coffee : public Drink {
public:
void BoilWater() {
cout << "烧水" << endl;
}
void AddCondiments() {
cout << "添加糖" << endl;
}
void PourInCup() {
cout << "倒进杯子" << endl;
}
void AddSugar() {
cout << "添加糖" << endl;
}
void AddMilk() {
cout << "添加牛奶" << endl;
}
};
// 制作茶的子类
class Tea : public Drink {
public:
void BoilWater() {
cout << "烧水" << endl;
}
void AddCondiments() {
cout << "添加柠檬" << endl;
}
void PourInCup() {
cout << "倒进杯子" << endl;
}
void AddSugar() {
cout << "添加糖" << endl;
}
void AddMilk() {
cout << "添加牛奶" << endl;
}
};
// 制作饮品的函数
void makeDrinkWork(Drink* drink) {
drink->makeDrink();// 调用基类的makeDrink()函数
cout << "制作--完成" << endl;
delete drink;// 释放内存
}
void test() {
// 制作咖啡
makeDrinkWork(new Coffee());// 动态创建咖啡对象并调用makeDrinkWork()函数
cout << "--------------------------------" << endl;
// 制作茶
makeDrinkWork(new Tea());
}
int main() {
test();
return 0;
}
4.7.5 虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决方式:将父类中的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构共性:
- 可以解决父类指针释放子类对象
- 都需要有具体的函数实现
虚析构和纯虚析构区别:
- 如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构语法:
virtual ~类名(){}
纯虚析构语法:
virtual~类名()=0;
类名::~类名(){}
#include <iostream>
#include <string>
using namespace std;
// 虚析构和纯虚析构
class Animal {
public:
virtual void speak() = 0; // 纯虚函数
// 利用虚析构函数释放内存,可以解决父类指针释放子类对象时不干净的问题
//virtual ~Animal() {} // 虚析构函数,释放内存
// 纯虚析构函数,需要声明也需要实现
// 有了纯虚析构函数,这个类也就成为了抽象类,不能实例化对象,只能作为基类被派生
virtual ~Animal() = 0; // 纯虚析构函数,声明,但不定义,用于派生类中
};
// 纯虚析构函数,用于派生类中,声明但不定义
Animal::~Animal(){}
class Cat : public Animal {
public:
Cat(string *name) {
m_Name = new string(*name);
}
virtual void speak() {
cout << "Cat: " << *m_Name << ";Meow!" << endl;
}
~Cat() { cout << "Cat: " << *m_Name << ";Bye!" << endl; delete m_Name; } // 虚析构函数,释放内存
string *m_Name;
};
void test_Cat() {
string name = "Kitty";// 注意:这里传递的是指针,而不是字符串
Animal* a = new Cat(&name);// 注意:这里传递的是指针,而不是对象
a->speak();// 注意:这里调用的是虚函数,而不是对象指针
delete a;// 注意:这里释放的是指针,而不是对象
}
int main() {
test_Cat();
return 0;
}
总结:
1.虚析构或纯虚析构就是用来解决通过父类指针释放子类对象2.如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
3.拥有纯虚析构函数的类也属于抽象类
4.7.6 多态案例三-电脑组装
案例描述:
电脑主要组成部件为 CPU(用于计算),显卡(用于显示),内存条(用于存储)
将每个零件封装出抽象其类,并且提供不同的厂商生产不同的零件,例如Intel厂商和Lenovo厂商创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口
测试时组装三台不同的电脑进行工作
示例:
#include <iostream>
#include <string>
using namespace std;
// CPU类
class CPU {
public:
virtual void calculate() = 0;// 虚函数,用于计算功能
};
// 主板类
class Mainboard {
public:
virtual void motherboard() = 0;// 虚函数,用于组装主板
};
// 内存条类
class Memory {
public:
virtual void RAM() = 0;// 虚函数,用于安装内存条
};
// 显卡类
class GraphicsCard {
public:
virtual void display() = 0;// 虚函数,用于安装显卡
};
// 电脑类
class Computer {
public:
Computer(CPU* cpu, Mainboard* mainboard, Memory* memory, GraphicsCard* graphicsCard)
: cpu(cpu), mainboard(mainboard), memory(memory), graphicsCard(graphicsCard) {// 构造函数,传入各个部件对象
/*cpu = cpu;
mainboard = mainboard;
memory = memory;
graphicsCard = graphicsCard;*/
}
void assemble() {// 组装电脑
cpu->calculate();
mainboard->motherboard();
memory->RAM();
graphicsCard->display();
}
private:
CPU* cpu;// CPU对象
Mainboard* mainboard;// 主板对象
Memory* memory;// 内存条对象
GraphicsCard* graphicsCard;// 显卡对象
};
// 具体实现类
class IntelCPU : public CPU {
public:
virtual void calculate() {
cout << "Intel CPU is calculating..." << endl;
}
};
class ASUSMainboard : public Mainboard {
public:
virtual void motherboard() {
cout << "ASUS Mainboard is assembling..." << endl;
}
};
class KingstonMemory : public Memory {
public:
virtual void RAM() {
cout << "Kingston Memory is installing..." << endl;
}
};
class NvidiaGraphicsCard : public GraphicsCard {
public:
virtual void display() {
cout << "Nvidia GraphicsCard is installing..." << endl;
}
};
void test() {
// 第一台电脑零件
CPU* intelCPU = new IntelCPU();
Mainboard* asusMainboard = new ASUSMainboard();
Memory* kingstonMemory = new KingstonMemory();
GraphicsCard* nvidiaGraphicsCard = new NvidiaGraphicsCard();
// 组装电脑
Computer* computer = new Computer(intelCPU, asusMainboard, kingstonMemory, nvidiaGraphicsCard);
computer->assemble();
// 释放内存
delete intelCPU;
delete asusMainboard;
delete kingstonMemory;
delete nvidiaGraphicsCard;
delete computer;
}
int main() {
test();
return 0;
}