目录
前言
多态的概念
多态的定义
虚函数的介绍
虚函数的重写/覆盖
析构函数的重写
override和final关键字
纯虚函数和抽象类
重写/重载/隐藏总结
多态的原理
小结
前言
C++一共有三个特性,封装、继承和多态,在前面的文章中,我们分别介绍了封装和继承的概念,现在本篇文章将完成C++特性的最后一块拼图——多态。在实际生活中我们会看到这种情况,一个人有不同的身份,比如在学校的时候是老师,在家是孩子,工作了是老师;我们在买票时,成人全票,儿童半价;动物的叫声,狗是“汪汪”,猫是“喵喵”等这些都是多态的实例,多态通俗将就是一个事物的多种状态,那么有了这个认识,我们将开启多态的世界。
多态的概念
多态通俗讲就是多种状态,分为两种:编译时多态和运行时多态。我们在前面介绍的函数重载、类模板就是编译时多态,我们在这里不做过多介绍,我们今天主要介绍运行时的多态。
那么多态有什么作用呢?我们在编写代码时可能会出现下面的情况:基类当中有一个函数实现了一种功能,派生类在继承了基类后针对这个函数的功能要做出相应的调整,但函数结构大体不变,还是实现了这个功能,只不过实现方式不同,那么我们此时就可以使用多态定义这个新的函数,这样就可以保证基类代码不变的情况下,通过在派生类中的改变达到某种需求。这就是多态的核心作用
多态的定义
多态的实现需要两个条件:
1.必须是基类的指针或引用调用虚函数
2.被调用的必须是虚函数,并且完成了重写/覆盖
接下来我们将介绍虚函数和重写的概念。
虚函数的介绍
类成员函数名称前加上virtual就构成了虚函数,但是非成员函数不能用virtual修饰。
虚函数的重写/覆盖
既然要保证基类代码不变的情况下,通过在派生类中添加相关功能达到某种需求,那么C++为我们提供的解决方案就是虚函数的重写/覆盖。
概念:派生类中有一个与基类完全相同的虚函数,称派生类重写了基类的虚函数
值得注意的是:派生类的虚函数如果不在前面加virtual也构成重写(但是在实际操作过程中建议在派生类中加上virtual)。重写是一种特殊的隐藏,即:不构成重写就是隐藏。
那么有了虚函数和重写后,我们可以参考下面代码,更加深入的理解多态的概念:
class Animal
{
public:
virtual void sound()
{
cout << "啊啊" << endl;
}
protected:
string _name;
int _weight;
string _type;
};
class Dog :public Animal
{
public:
virtual void sound()
{
cout << "汪汪" << endl;
}
};
void func(Animal&a)
{
a.sound();
}
上述代码运行结果如下所示:
我们可以看到,通过传不同的类就调到了不同的函数,这就是多态要完成的任务。
那么虚函数的重写可以实现不同函数的调用,这是为什么呢?实际上重写就是组合,编译器将派生类重写的函数内容与基类虚函数的声明相互组合,从而实现函数的拼接调用。
析构函数的重写
C++从入门到入土(六)——继承的介绍
我们在前面继承章节中介绍默认成员函数时,对析构函数是这么说的:派生类的析构函数会在对象生命周期结束后自动调用基类的析构函数清理基类成员,因为这样才能保证先清理基类再清理派生类的顺序。(析构先子后父)这是因为析构函数在编译时统一将名称改为destructor(),与多态有关这是为什么呢?
实际上基类的析构函数是虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类析构函数构成重写,虽然它们的名称看起来不符合重写规则,但实际上编译器做了特殊处理,将他们的名称统一处理成destructor(),这样就支持了函数重写,所以析构函数在定义时建议加上virtual。
override和final关键字
C++标准对于重写的定义是比较严格的,有时候我们会因为疏忽大意导致函数无法完成重写,因此C++为我们提供了override关键字,用来检测函数是否发生重写,其定义方式是:在派生类重写的虚函数后面加上override。
如果我们不想让这个函数发生重写,那么C++标准为我们提供了final关键字,其定义方式是:在基类不想发生重写的函数后面加上final。
override和final使用如下所示:
纯虚函数和抽象类
纯虚函数:在虚函数后面加上=0,则这个函数就是纯虚函数,纯虚函数不需要定义实现,因为不需要重写,只要声明即可。
抽象类:包含纯虚函数的类叫做抽象类,如果派生类继承之后不重写纯虚函数,那么派生类也是抽象类,纯虚函数在某种程度上强制派生类重写虚函数,因为不重写实例化不出对象。
重写/重载/隐藏总结
重载:两个函数在同一作用域,函数名相同,参数不同,参数的类型或个数不同,返回值可以相同,可以不同
重写:两个函数分别在基类和派生类不同的两个作用域中,函数名,参数,返回值必须相同(协变除外),两个函数必须是虚函数
隐藏:两个函数或变量分别在基类和派生类两个不同的作用域,函数名相同,两个函数只要不是重写就是隐藏
多态的原理
在讲解多态之前,我们先来看一个问题:
class A
{
public:
virtual void func()
{
cout << "func()" << endl;
}
private:
int _a;
char _b;
};
int main()
{
A a;
int size = sizeof(a);
cout << size << endl;
return 0;
}
你们觉得上面代码的运行结果是什么(32位)?如果根据结构体对齐的概念,我们可以知道,这个结果应该是8,但事实真是如此吗?我们运行代码:
结果与我们推测的有所差异,这是因为什么呢?我们进行调试:
我们可以看到a当中保存了三个变量,那么应该是这个_vfptr导致大小的改变,根据后面的ptr我们可以推测这个应该是指针,那么在32位机器下,指针大小应该是四字节所以其大小变为了12字节,那么这个_vfptr是什么东西呢?实际上这个_vfptr是一个虚函数指针数组,又叫虚函数表,所有的虚函数都是存储在这个虚表当中,我们在运行过程中就是通过虚表找到每个虚函数的地址,然后实现对应的函数调用。
小结
本篇文章我们完成了C++特性的最后一块拼图——多态,通过多态我们可以更加灵活的实现代码的编辑,为我们程序的书写提供了便利,受限于博主的知识水平,可能文章当中有所纰漏,欢迎大家指正,接下来我们继续更新C++从入门到入土系列,如果觉得我的文章对您有所帮助的话,希望您能够点赞、关注加收藏,您的支持是我创作的最大动力