类与对象
多态
多态是c++面向对象的三大特性之一
多态分为2类:
静态多态:
函数重载和运算符重载属于静态多态,复用函数名
动态多态:
派生类和虚函数实现运行时多态
二者之间的区别:
- 静态多态的地址早绑定-编译阶段确定函数地址
- 动态多态的地址晚绑定-运行阶段确定函数地址
动态多态的满足条件:
- 要有继承关系
- 子类要重写父类的虚函数
动态多态的使用:
父类的引用指向子类对象
多态的优点:
- 代码组织结构清晰
- 可读性强
- 利于前期和后期的扩展与维护
动态多态的简单实现和使用案列:
#include <iostream>
using namespace std;
class Animal {
public:
//加上virtual 变成虚函数
virtual void speak() {
cout << "动物在说话" << endl;
}
};
class Cat : public Animal{
public:
void speak() {
cout << "喵喵喵.." << endl;
}
};
class Dog : public Animal {
public:
void speak() {
cout << "汪汪汪.." << endl;
}
};
//动态多态的使用: 父类的引用指向子类对象
void doSpeak(Animal& a) {
a.speak();
}
void test01() {
Cat cat;
doSpeak(cat);
Dog dog;
doSpeak(dog);
}
int main() {
test01();
system("pause");
return 0;
}
动态多态的原理刨析
vfptr 虚函数指针
v -virtual
f -function
ptr - pointer
vftable 虚函数表
表内记录虚函数地址
当子类重写父类的虚函数,子类的虚函数表内部会替换成子类的虚函数地址
当父类的指针或引用指向子类的时候,就发生了多态
可以看到Cat的vftable中的方法是Cat的speak()地址,如果注释掉Cat类对父类Aniaml的虚函数speak()的重写,则Cat的vftable存放的是Animal的speak()地址,图示如下
多态案例1计算机类
#include<iostream>
using namespace std;
class AbstractCalculator {
public:
int m_Num1;
int m_Num2;
virtual int getResult() {
return 0;
}
};
//加
class AddCalculator:public AbstractCalculator {
int getResult() {
return m_Num1 + m_Num2;
}
};
//减
class ReduceCalculator :public AbstractCalculator {
int getResult() {
return m_Num1 - m_Num2;
}
};
//乘
class RideCalculator :public AbstractCalculator {
int getResult() {
return m_Num1 * m_Num2;
}
};
//除
class DivideCalculator :public AbstractCalculator {
int getResult() {
return m_Num1 / m_Num2;
}
};
void test01() {
AbstractCalculator* abc = new AddCalculator;
abc->m_Num1 = 15;
abc->m_Num2 = 15;
cout << abc->m_Num1 << "+" << abc->m_Num2 << "=" << abc->getResult() << endl;
delete abc;
//new出来的数据存放在堆区,delete后删除的是堆区的数据,而指针在栈区,未被消除,可以直接使用
abc = new DivideCalculator;
abc->m_Num1 = 15;
abc->m_Num2 = 3;
cout << abc->m_Num1 << "/" << abc->m_Num2 << "=" << abc->getResult() << endl;
//堆区的数据要在不使用的时候手动释放
delete abc;
}
int main() {
test01();
system("pause");
return 0;
}
纯虚函数和抽象类
在多态中父类的虚函数基本都是没有意义的,主要调用子类重写的内容
因此可以将虚函数改成纯虚函数
语法
virtual 返回值类型 函数名(参数列表)= 0;
当类中有了纯虚函数,这个类被称为抽象类
抽象类特点
- 无法示例化对象
- 子类必须重写抽象类的纯虚函数,否则子类也算抽象类
多态案例2制作饮品
#include <iostream>
using namespace std;
class AbstractDrinks {
public:
//煮水
virtual void Boil() = 0;
//冲泡
virtual void Brew() = 0;
//加佐料
virtual void Seasoning() = 0;
//倒入杯中
virtual void PourIntoCup() = 0;
void makeDrinks() {
Boil();
Seasoning();
Brew();
PourIntoCup();
}
};
class MakeCoffe:public AbstractDrinks {
public:
void Boil() {
cout << "煮水至70度" << endl;
}
void Seasoning() {
cout << "加入碾磨好的咖啡豆和糖块" << endl;
}
void Brew() {
cout << "冲泡3分钟" << endl;
}
void PourIntoCup() {
cout << "倒入准备好的杯子中" << endl;
}
};
class MakeTea :public AbstractDrinks {
public:
void Boil() {
cout << "煮水至100度" << endl;
}
void Seasoning() {
cout << "加入大红袍" << endl;
}
void Brew() {
cout << "冲洗一次,泡1-2分钟" << endl;
}
void PourIntoCup() {
cout << "倒入准备好的杯子中" << endl;
}
};
void doDrink(AbstractDrinks *abs) {
abs->makeDrinks();
delete abs;
}
void test01() {
cout << "制作咖啡" << endl;
doDrink(new MakeCoffe);
cout << "泡茶" << endl;
doDrink(new MakeTea);
}
int main() {
test01();
system("pause");
return 0;
}
虚析构和纯虚析构
多态使用时,如果子类有属性开辟到堆区,那么父类指针在释放时无法调用子类的析构函数
解决方式:
将父类的析构函数改为虚析构或纯虚析构函数
共性
- 可以解决父类指针释放子类对象
- 都需要有具体的函数实现
区别
如果是纯虚析构,该类属于抽象类,无法实现实例化
语法
虚析构语法
virtual ~类名(){}
纯虚析构语法
virtual ~类名() = 0;
注意事项:
纯虚析构也需要有代码的实现
在类外实现,通过作用域区分
类名::~类名(){
}
多态案例3组装计算机
#include<iostream>
using namespace std;
class CPU {
public:
virtual void calculator() = 0;
};
class VideoCard {
public:
virtual void display() = 0;
};
class Memory {
public:
virtual void storage() = 0;
};
//Intel厂商
class IntelCPU :public CPU {
public:
void calculator() {
cout << "Intel的CPU开始工作了" << endl;
}
};
class IntelVideoCard :public VideoCard {
public:
void display() {
cout << "Intel的VideoCard开始工作了" << endl;
}
};
class IntelMemory :public Memory {
public:
void storage() {
cout << "Intel的Memory开始工作了" << endl;
}
};
//Lenove
class LenoveCPU :public CPU {
public:
void calculator() {
cout << "Lenove的CPU开始工作了" << endl;
}
};
class LenoveVideoCard :public VideoCard {
public:
void display() {
cout << "Lenove的VideoCard开始工作了" << endl;
}
};
class LenoveMemory :public Memory {
public:
void storage() {
cout << "Lenove的Memory开始工作了" << endl;
}
};
class Computer {
public:
Computer(CPU* cpu, VideoCard* vc, Memory* mem) {
m_Cpu = cpu;
m_Vc = vc;
m_Mem = mem;
}
//提供工作的函数
void doWork() {
m_Cpu->calculator();
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;
};
void test01() {
//组装第一台电脑
cout << "组装第一台电脑" << endl;
Computer* c1 = new Computer(new IntelCPU, new IntelVideoCard, new LenoveMemory);
c1->doWork();
delete c1;
cout << "___________________________" << endl;
cout << "组装第二台电脑" << endl;
Computer* c2 = new Computer(new IntelCPU, new IntelVideoCard, new IntelMemory);
c2->doWork();
delete c2;
cout << "___________________________" << endl;
cout << "组装第三台电脑" << endl;
Computer* c3 = new Computer(new LenoveCPU, new LenoveVideoCard, new LenoveMemory);
c3->doWork();
delete c3;
}
int main() {
test01();
system("pause");
return 0;
}