多态实现--虚函数与纯虚函数

时间:2022-04-22 21:57:04

多态实现--虚函数与纯虚函数

  • 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方法只是被默认隐藏了)。