多态
含义:顾名思义,多种状态;一个接口多种实现,即为多态。
分类:
静态多态:函数重载和运算符重载,复用函数名;
动态多态:派生类和虚函数实现运行时多态;
静态多态和动态多态的区别:
1.静态多态的函数地址早绑定,编译阶段确定函数地址;
2.动态多态的函数地址晚绑定,运行阶段确定函数地址;
虚函数
在实现c++多态时会用到虚函数。虚函数使用的其核心目的是通过基类访问派生类定义的函数。所谓虚函数就是在基类定义一个未实现的函数名,为了提高程序的可读性,建议后代中虚函数都加上virtual关键字。
class base //定义一个基类
{
public:
base();
virtual void test(); //定义一个虚函数
private:
char *basePStr;
};
上述代码在基类中定义了一个test的虚函数,所有可以在其子类重新定义父类的做法这种行为成为覆盖(override),或者为重写。
常见用法:声明基类指针,利用指针指向任意一个子类对象,调用相关的虚函数,动态绑定,由于编写代码时不能确定被调用的是基类函数还是那个派生类函数,所以被称为“”虚“”函数。如果没有使用虚函数的话,即没有利用C++多态性,则利用基类指针调用相应的函数的时候,将总被限制在基类函数本身,而无法调用到子类中被重写过的函数。
#include<iostream>
using namespace std;
class A
{
public:
void foo()
{
printf("1\n");
}
virtual void fun() //定义了一个虚函数fun
{
printf("2\n");
}
};
class B : public A
{
public:
void foo() //隐藏:派生类的函数屏蔽了与其同名的基类函数
{
printf("3\n");
}
void fun() //多态、覆盖,子类重写父类中的虚函数;
{
printf("4\n");
}
};
int main(void)
{
A a; //此时A是基类,B是派生类
B b;
A *p = &a; //p是指向A类对象a的指针
p->foo(); //输出1,执行了A类中的函数
p->fun(); //输出2,执行了A类中的函数
p = &b;
p->foo(); //取决于指针类型,输出1
p->fun(); //取决于对象类型,输出4,体现了多态
return 0;
}
总结:
构成多态需要满足的条件:
1.有继承关系;
2.子类重写父类中的虚函数;重写是:函数返回值类型 函数名 参数列表 完全一致。
使用多态的条件:
父类的指针或引用指向子类对象;
多态的优点:
代码组织结构清晰,可读性强,利于拓展和维护;C++提倡使用多态设计程序架构。
纯虚函数和抽象类
在多态中父类中的虚函数的实现是毫无意义的,主要都是调用子类重写的内容,所以可以将虚函数改成纯虚函数:
vitural 返回值类型 函数名 (参数列表) = 0;
当类中有了纯虚函数,这个类也称为抽象类。
抽象类不能实例化对象,子类必须重写抽象类中的虚函数,否则也属于抽象类。
虚析构和纯虚析构
问题:多态使用时如果有属性开辟到了堆区,那么父类指针在释放时,无法调用到子类的析构代码;通过父类指针来释放,会导致子类对象可能清理不干净,造成内存泄露。
解决方法:将父类中的析构函数改成虚析构或者纯析构函数;
虚析构函数和纯虚析构函数的共性:
1.可以解决父类指针释放子类对象;
2.都需要有具体的函数实现;
虚析构函数和纯虚析构函数的区别:
1.纯虚析构函数的类属于抽象类,无法实例化对象;
虚析构语法:
virtual ~类名(){}
纯虚构语法:
virtual ~类名()= 0;
类名::~类名(){}
如果子类对象中没有堆取数据,那么可以不写虚析构或纯虚析构函数。