P20 C++虚函数与纯虚函数

目录

前言

01 虚函数能干什么呢?

02 没有虚函数前的例子

03 使用虚函数后的例子

虚函数virtual 概念

虚函数使用需要一定开销


前言

本期我们学习的是 C++ 中的虚函数。

过去的几期,我们一直在讨论类、面向对象编程、继承这些内容,所有的这些内容,包括本期我们将要学习的虚函数,对整个面向对象的概念都非常重要。

虚函数是指一个类中你希望重载的成员函数 ,当你用一个基类指针或引用指向一个继承类对象的时候,调用一个虚函数时, 实际调用的是继承类的版本。

01 虚函数能干什么呢?

虚函数允许我们在子类中重写方法。

假设我们有两个类 A 和 B,B 是 A 派生出来的类。如果我们在 A 中创建一个方法,标记为 virtual,我们可以选择在 B 类中重写那个方法,让它做其他的事情。

像之前一样,我们通过一个例子来解释今天的知识点。

02 没有虚函数前的例子

我创建了两个类,一个是 Anima,它唯一拥有的是一个名为 GetName 的公共方法,它会返回一个字符串,我们让它返回 "animal"。

还有另外一个类 Dog,它是 Animal类的子集,它提供一个构造函数,允许我们指定一个名字;然后给它提供了一个叫 GetName 的方法,我们让他返回Dog字符串。

我们来看看如何使用上面这些设定。

我有一个 Print 函数,参数是一个 Animal的指针。

现在我们有了一个函数,它可以接受任何 Animal类型的参数,你可以看到,我们不会得到任何的编译错误。

当我们试图将 a 传递给函数时,因为 p 是一个 Animal,Dog 是 Animal的·子类,在函数里面我们做的就是调用 GetName 方法,我们期望的是,在主函数中调用的部分,参数为 Animal类型时,GetName 用于 Anima,而参数为 DOg类型时,GetName 用于 Dog。

然而,运行代码之后你会发现它打印了两次 Animal。

//测试代码
//执行结果为打印两次Animal

#include <iostream>

class Animal
{
public:
  std::string getName(){return "Animal";};
};

class Dog: public Animal
{
public:
    std::string getName(){return "Dog";};
};

void print(Animal* obj)
{
    std::cout<<obj->getName()<<std::endl;
}

int main()
{
    Animal *animal = new Animal;
    Dog *dog = new Dog;
    print(animal);
    print(dog);
    return 0;   
}

为什么会这样呢?

03 使用虚函数后的例子

发生这种情况的原因时,在我们声明函数时,我们的方法通常在类内部起作用。然后当调用方法的时候,会调用属于该类型的方法。

我们看这个 Print 函数,它的参数是 Animal,这意味着当我们调用 GetName 函数时,如果是在 Animal里面,那么它会从 Animal类中找这个叫做 GetName 的函数。

然而,我们希望 C++ 能意识到一点:我在这里传递的 Animal实际上是 它的子类Dog,所以,请调用 Dog中的 GetName 函数。

这时候,虚函数就该出现了。

虚函数virtual 概念

虚函数引入了一种叫做 Dynameic Dispatch(动态联编)的东西,它通常通过 V 表(虚函数表)来实现编译。

V 表就是一个表,它包含基类中所有虚函数的映射,这样我们可以在它运行时,将它们映射到正确的覆写(overwrite)函数。

简单起见,现在你只需要知道,如果你想覆写一个函数,必须将基类中的基函数标记为虚函数。

我们回到代码中继续看一下。

我在基类 Animal类中 GetName 函数前面使用了 virtual 这个关键字,这可以告诉编译器,——嘿,为这个函数生成 V 表吧,这样,如果它被重写了,你可以指向正确的函数。

我们运行代码试试看。

我们得到了期望的结果。

现在,我们可以做的另一件事:使用在 C++11 引入的覆写函数标记的关键字 override。

这个不是必须的,无论有没有这个关键字,程序都会正常工作,但是我还是建议你这样做。因为首先这会让你的程序更具有可读性,阅读程序的时候我们可以知道这实际上是一个覆写的函数;它还可以帮助我们预防 Bug 的发生,比如拼写错误之类的

//用于测试的代码
#include <iostream>

class Animal
{
public:
    virtual std::string getName(){return "Animal";};
};

class Dog: public Animal
{
public:
    std::string getName(){return "Dog";};
};

void print(Animal* obj)
{
    std::cout<<obj->getName()<<std::endl;
}

int main()
{
    Animal *animal = new Animal;
    Dog *dog = new Dog;
    print(animal);
    print(dog);
    return 0;   
}

虚函数使用需要一定开销

这就是虚函数的本质,但是很遗憾的一点是,虚函数并不是没有额外的开销的,有两种与虚函数相关的运行时成本。

首先,我们需要额外的内存来存储 V 表,这样我们就可以分配到正确的函数,包括基类中要有一个成员指针指向 V 表;其次,每次我们调用虚函数时,我们需要遍历这个表来确定要映射到哪个函数,这些是额外的性能损失。

  • 24
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@ChenPi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值