目录
多态
多态的优点:
- 代码组织结构清晰
- 可读性强
- 利于前期和后期的扩展以及维护
1、多态分两类:
- 静态多态:函数重载和运算符重载属于静态多态,复用函数名
- 动态多态:派生类和虚函数实现运行时多态
静态多态和动态多态区别:
- 静态多态的函数地址早绑定,编译阶段确定函数地址
- 动态多态的函数地址晚绑定,运行阶段确定函数地址
动态多态的满足条件:
- 有继承关系;
- 子类重写父类的虚函数
多态使用条件:
父类指针或引用指向子类对象
2、多态的原理
示例:
#include<iostream>
using namespace std;
class Animal{
public:
void speak() {
cout << "动物在说话。"<<endl;
}
};
class Cat:public Animal{
public:
void speak() {
cout << "小猫在说话。"<<endl;
}
};
void WhoSpeak(Animal &animal) {
animal.speak();
}
void test() {
Cat cat;
WhoSpeak(cat);
}
int main() {
test();
system("pause");
return 0;
}
输出结果为:动物在说话。虽然创建了一个cat对象,但Cat类继承了Animal类,所以输出内容为Animal::speak()里的;
同时输出Animal类的大小:类内只有一个非静态的成员函数,类似于一个空类,所以大小为1。
cout << "sizeof(animal) = " << sizeof(Animal) << endl;
当Animal类的speak函数声明为虚函数时(virtual),输出结果为:小猫在说话。此时Cat类下面重写了speak()函数;
virtual void speak() {
cout << "动物在说话。"<<endl;
}
此时输出Animal类的大小为4,即类内部结构发生了改变
如图:
找到vs开发人员工具,输入如下命令,Animal类大小为1;
加上virtual后,Animal类大小为4,此时类内部结构发生了变化:
vfptr:虚函数(表)指针
vftable:虚函数表
vfptr指针指向的是vftable,存放了Animal:speak的地址
Cat类大小,一开始继承Animal类,大小为4;
重写Animal类的speak函数后,Cat::speak的地址覆盖了Animal:speak的。
案例1:计算器类
普通写法:
//普通写法
class calculation {
public:
int num1;
int num2;
//四则运算
int getResult(string oper) {
if (oper == "+") {
return num1 + num2;
}
if (oper == "-") {
return num1 - num2;
}
if (oper == "*") {
return num1 * num2;
}
if (oper == "/") {
return num1 / num2;
}
}
};
void test() {
calculation a;
a.num1 = 2;
a.num2 = 10;
cout << a.num1 << " + " << a.num2 << " = " << a.getResult("+") << endl;
cout << a.num1 << " - " << a.num2 << " = " << a.getResult("-") << endl;
cout << a.num1 << " * " << a.num2 << " = " << a.getResult("*") << endl;
cout << a.num1 << " / " << a.num2 << " = " << a.getResult("/") << endl;
}
多态技术:开闭原则
//多态实现:开闭原则
//实现计算器抽象类
class AbstractCalculator {
public:
//虚函数
virtual int getResult() {
return 0;
}
int num1;
int num2;
};
//加法类,继承AbstractCalculator抽象类
class add :public AbstractCalculator {
int getResult() {
return num1 + num2;
}
};
//减法类
class sub :public AbstractCalculator {
int getResult() {
return num1 - num2;
}
};
//乘法类
class mul :public AbstractCalculator {
int getResult() {
return num1 * num2;
}
};
//除法类
class chu :public AbstractCalculator {
int getResult() {
return num1 / num2;
}
};
void test2() {
//父类指针或者引用指向子类对象
//new一个add类对象赋给父类指针,
//加法运算
AbstractCalculator *abc = new add;
abc->num1 = 2;
abc->num2 = 10;
cout << abc->num1 << " + " << abc->num2 << " = " << abc->getResult() << endl;
//堆区数据,用完后记得销毁
delete abc;
abc = new sub;
abc->num1 = 2;
abc->num2 = 10;
cout << abc->num1 << " - " << abc->num2 << " = " << abc->getResult() << endl;
delete abc;
}
3、纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此可以将虚函数改为纯虚函数
纯虚函数语法:virtual 返回值类型 函数名 (参数列表) =0;
当类中有了纯虚函数,这个类也称为抽象类
抽象类特点:
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
案例2:制作饮品
#include<iostream>
using namespace std;
class Drinking {
public:
//煮水
void Boil() {
cout << "煮水" << endl;
}
//冲泡
virtual void Brew() = 0;
//倒入杯中
void PourInCup() {
cout << "倒入杯中" << endl;
}
//加入佐料
virtual void PutSomething() = 0;
//制作饮品
void makeDrink() {
Boil();
Brew();
PourInCup();
PutSomething();
}
};
class tea :public Drinking {
//冲泡
void Brew() {
cout << "冲泡茶" << endl;
}
//加入佐料
void PutSomething() {
cout << "加柠檬" << endl;
}
};
class coffee :public Drinking {
//冲泡
void Brew() {
cout << "冲泡咖啡" << endl;
}
//加入佐料
void PutSomething() {
cout << "加糖" << endl;
}
};
//制作函数
void doWork(Drinking * make) {
make->makeDrink();
delete make;//销毁堆区数据,防止溢出
}
void test() {
cout << "--------制作茶--------" << endl;
doWork(new tea);//new一个tea对象
cout << "--------制作咖啡--------" << endl;
doWork(new coffee);
}
int main() {
test();
system("pause");
return 0;
}
4、虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决方式:将父类中的析构函数改为虚析构或者纯虚析构,不会有溢出问题
虚析构和纯虚析构共性:
- 可以解决通过父类指针释放子类对象
- 都需要有具体的函数实现
虚析构和纯虚析构区别:
如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构语法:
virtual ~类名(){}
纯虚析构语法:
virtual ~类名() = 0;//
类名::~类名(){}
#include<iostream>
#include<string>
using namespace std;
class Animal {
public:
//先调用父类构造函数
Animal() {
cout << "Animal构造函数调用" << endl;
}
virtual void speak() = 0;
//虚析构使得子类对象可释放
/*virtual ~Animal() {
cout << "Animal虚析构函数调用" << endl;
}*/
//纯虚析构,和虚析构只能二者选一个
virtual ~Animal() = 0;
};
Animal::~Animal() {
cout << "Animal纯虚析构函数调用" << endl;
}
class Cat :public Animal {
public:
Cat(string n) {
cout << "Cat构造函数调用" << endl;
name = new string(n);//new string(n)为创建堆区属性,在Cat析构函数中释放
}
virtual void speak() {
cout <<*name << "猫在说话。" << endl;
}
~Cat() {
if (name != NULL) {
cout << "Cat析构函数调用" << endl;
delete name;
name = NULL;
}
}
//为小猫创建一个名字指针,即在堆区的数据
string *name;
};
void test() {
Animal *anim = new Cat("Tom");
anim->speak();
delete anim;
}
int main() {
test();
system("pause");
return 0;
}
纯虚析构需要有声明,也需要有具体实现;
有了纯虚析构之后,这个类也属于抽象类,无法实例化对象