多态是对象的三大特性之一
多态分为两类:
静态多态:函数重载和运算符重载属于静态多态(复用函数名)
动态多态:派生类和虚函数实现运行时的多态
静态多态和动态多态的区别:
静态多态的地址是早绑定,即编译阶段即确定函数地址
动态多态的地址是晚绑定,即运行阶段才确定函数地址
eg:
用多态构建一个猫在说话的场景
#include
using namespace std;
class Animal {
public:
virtual void speak()//在父类的函数声明中加入virtual,此函数即可变成父类的虚函数
{
cout << "动物在说话" << endl;
}
};
class cat :public Animal {//继承父类
public:
void speak()
{
cout << "猫在说话" << endl;
}
};
//传入的是animal类的对象,根据静态多态的规则,地址早绑定,在编译阶段就会确定函数的入口地址,因此会执行animal类中的函数
//若想让猫说话,则这个函数地址不能提前绑定需要在运行阶段再进行绑定,即地址晚绑定
void dospeak(Animal &animal)//执行说话函数
{
animal.speak();
}
void test()
{
cat c;
dospeak(c);//想执行cat类中的说话函数
}
int main()
{
test();
system("pause");
return 0;
}
1、有继承关系 2、子类重写父类的虚函数
重写---函数返回值类型,函数名,参数列表完全相同
动态多态的使用(两种方法):
1、函数参数列表数据类型为父类类名的引用,传入参数为子类对象 (父类引用指向子类对象)
2、父类指针指向子类对象
eg:利用多态构建计算器的功能
#include
using namespace std;
//创建一个计算器的抽象类(父类)
class CalculateAbstract {
public:
virtual int get_result()//用于获取计算结果
{
return 0;
}
int num1;
int num2;
};
//创建一个加法计算器
class AddCalculate:public CalculateAbstract {
public:
int get_result()
{
return num1 + num2;
}
};
//创建一个减法计算器
class SubCalculate :public CalculateAbstract {
public:
int get_result()
{
return num1 - num2;
}
};
int get_result(CalculateAbstract &a)
{
return a.get_result();
}
int main()
{
//父类引用指向子类对象
AddCalculate add;
add.num1 = 10;
add.num2 = 20;
get_result(add);
cout << add.num1 << '+' << add.num2 << '=' << get_result(add) << endl;
//父类指针指向子类对象
CalculateAbstract* cal = new AddCalculate;
cal->num1 = 10;
cal->num2 = 20;
cout << cal->num1 << '+' << cal->num2 << '=' << cal->get_result() << endl;
delete(cal);//堆区空间用完手动释放
system("pause");
return 0;
}
1、代码组织结构清晰
2、可读性强
3、利于前期和后期的扩展(重写一个子类函数)和维护
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此可以将虚函数改成纯虚函数。
virtual 返回值类型 函数名 (参数列表)=0;
当类中有了纯虚函数,这个类也称为抽象类。
注意:抽象类中仍可以定义成员属性。
1、无法实例化对象
2、子类必须重写抽象类中的纯虚函数,否则也属于抽象类
以上述实现计算器功能的例子为例
//创建一个计算器的抽象类(父类)
class CalculateAbstract {
public:
virtual int get_result() = 0;//用于获取计算结果
int num1;
int num2;
};
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时只能调用父类自己的析构函数,无法调用到子类的析构代码。而将父类的析构函数改为虚析构或纯虚析构能够解决此问题。
1、都可以解决父类指针不能释放子类对象的问题
2、都需要有具体的函数实现
如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构:
virtual ~类名(){};
纯虚析构:
virtual ~类名()=0;
类名::类名(){}
示例:
#include
using namespace std;
#include
//创建一个动物说话的场景
class Animal
{
public:
Animal()
{
cout << "调用父类的构造函数" << endl;
}
virtual void speak() = 0;
//利用虚析构可以解决父类指针释放时步调用子类的析构函数导致子类对象释放不干净的问题
/*virtual ~Animal()
{
cout << "调用父类的析构函数" << endl;
}*/
//纯虚函数 需要声明也需要实现---因为父类也可能会在堆区创建数据,因此纯虚函数需要在类外进行具体实现
virtual ~Animal() = 0;
};
Animal::~Animal()
{
cout << "此为纯虚析构函数" << endl;
}
class cat:public Animal {
public:
cat(string name)//cat类的构造函数
{
cout << "调用cat类的构造函数" << endl;
m_name = new string(name);
}
void speak()
{
cout <<*m_name<< "小猫在说话" << endl;
}
~cat()//cat类的析构函数
{
cout << "调用cat类的析构函数" << endl;
if (m_name != NULL)
{
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;
}