07 - 虚函数及其实现原理,override关键字
在子类中,我们需要用到虚函数和override
关键字来对父类的函数进行重写操作,顺便提到了虚函数的实现原理。
介绍
虚函数就是面向对象的第三大特点:多态。以上篇文章为例,在父类Spear
中定义一个开火的虚函数,让两个子类去重写他,最后通过以该函数指针为参数的射击函数统一调用即可。
以下代码是使用虚函数virtual
和关键字override
的一个简单示例:
class Spear
{
public:
// ...
virtual void openFire() const
{
// ...
}
private:
// ...
};
class IceSpear : public Spear
{
public:
// ...
virtual void openFire() const override
{
// ...
}
private:
// ...
}
一些注意点
override关键字
当父类指针指向子类对象,且子类重写父类某一函数时。父类指针调用该函数,就会产生以下的可能 :
- 该函数为虚函数:父类指针调用的是子类的成员函数。
- 该函数不是虚函数:父类指针调用的是父类的成员函数。
因此,最好在子类重写的函数上标识override
,virtual
可选。
析构函数
析构函数必须是虚函数:如果未声明成virtual
,很容易在销毁对象时不释放内存。如果希望它什么也不做,可以显式设置=default
。
virtual ~Test() = default;
禁用重写
在函数后标注final
关键字可以禁止子类重写这个方法。
动态绑定与静态绑定
静态绑定
静态绑定,也被称为早绑定。C++在编译类时,会创建一个包含类中所有方法的二进制对象。在非虚情况下,程序在编译时就确定了函数的地址。
动态绑定
程序在编译时确定的是其寻找函数地址的方法,只有在程序运行时才能真正确定函数的地址。
每个有虚函数的类都会有张虚表,对象其实就是指向虚函数表的指针,编译时编译器只告诉了程序会在运行时查找虚函数表的对应函数。
例如Base类和它的子类Derived:
纯虚函数与虚基类
以上边的枪类为例,基础的枪类有对应的对象吗?没有。它唯一的作用就是被子类继承。 因此,可以利用纯虚函数将此类变成虚基类。虚基类会被认为其无实际意义,无法产生对象:
class Spear
{
public:
Spear(const std::string& name_, const std::string& icon_) :name(name_), icon(icon_)
{}
virtual ~Spear() = default;
// 纯虚函数
virtual void openFire() const = 0;
protected:
std::string name;
std::string icon;
};
// 子类可以这样继承虚基类
class FireSpear() : public virtual Spear
{
// ...
};
基类的openFire()
实现过程没有意义,它就是用来被重写的。所以纯虚函数就此诞生,只要将一个虚函数写为纯虚函数,那么该类将被认为无实际意义的类,无法产生对象。纯虚函数也不用去写实际部分。写了编译器也会自动忽略。
纯虚函数的特点就是语法简单,却经常使用,必会。
参考资料
- 飘零的落花 - 现代C++详解
- C++20高级编程