多态实现--虚函数与纯虚函数
- C++中实现多态是使用虚函数表的方法实现的。
- 那么具体怎么实现的呢?
举例说明
- 假设有这样一个多态场景:
- 有一个基类动物(animal类),动物里面又有两个派生类:猫(cat类)和狗(dog类)。现在要求动物类有一个共同的方法:叫声(voice成员函数),但猫和狗叫声是不同的(即:它们的叫声实现方法不同)。
- 那么代码怎么写呢?
多态的代码实现
#include <iostream>
using namespace std;
//1、 定义一个带纯虚函数的抽象类作为基类
class animal
{
public:
virtual void voice()= 0; //纯虚函数voice
};
//2、 定义猫(cat)狗(dog)类,共同继承自animal,但对voice进行了具体实现
class cat:public animal
{
public:
virtual void voice()
{
cout <<"喵喵喵"<<endl;
}
};
class dog:public animal
{
public:
virtual void voice()
{
cout <<"汪汪汪"<<endl;
}
};
//3、那么下一步就要给猫和狗做一个统一接口了,传参用基类指针。这个接口只有一个功能,就是调用动物叫声voice。
void animal_voice(animal * a)
{
a->voice();
}
//4、 多态写好了,我们来调用一下试试,写个test看看这个多态有没有问题。我们分别定义一个猫狗对象,来调用统一接口,看看它们的叫声是否相同。
void test()
{
cat c;
dog d;
animal_voice(&c);
animal_voice(&d);
}
int main()
{
test();
return 0;
}
-
验证结果:
喵喵喵 汪汪汪
说明这个多态已实现完成。
多态原理
那么多态的原理是怎么样的呢?为什么这样写代码,就能实现猫和狗叫声不同,它们明明调用的是同一个接口呀?
-
实际上这个例子的程序运行时,为cat和dog分别生成了一张虚函数表,将函数地址记录在各自的虚函数表中。如图所示。
- cat的虚函数表:
cat的虚函数表 cat的voice函数的地址 - dog的虚函数表:
dog的虚函数表 dog的voice函数的地址 这样当我们用接口调用cat和dog的voice函数时,它们会各自在自己的虚函数表里寻找,找到自己的voice函数地址,根据这个地址来进行调用。怎么样,多态实现原理也很简单吧?
虚函数与纯虚函数
- 那么animal里的纯虚函数“virtual void voice()= 0;”只能这样写吗?它可以像下面这样写成虚函数吗?
virtual void voice()
{
}
答案是可以,但它们是不同的。写成虚函数,说明是有实现的(animal也因此变得不再是抽象类),只是它的实现方法是空,那么在cat和dog中我们再次实现这个voice时,就自动将基类的该voice方法重写(override)了(也就是说,我们也可以选择省略(即:不override)这个voice)。但如果写成纯虚函数,那么我们就必须要在cat和dog中具体实现voice,才能使用voice,否则cat和dog也和animal一样是抽象类,不能用来实例化对象。
- 也就是虚函数是有这个函数的实现,而纯虚函数是这个函数根本还没实现。
再补一个小知识:如果基类的voice写成虚函数,而不是纯虚函数话,派生类即使将其override了,还是可以通过作用域的方法来访问的(实际上override时,基类的voice方法只是被默认隐藏了)。