虚函数(日志20)
一、虚函数(Virtual Function)
1.1 定义和作用
虚函数是在基类中使用关键字 virtual 声明的成员函数,它允许派生类对其进行重写(Override),实现运行时多态。当通过基类指针或引用调用虚函数时,实际调用的是对象类型对应的派生类中的函数,这个过程称为动态绑定(Dynamic Binding)或晚绑定(Late Binding)。
1.2 实现原理
虚函数的实现原理基于虚函数表(Virtual Table,简称VTable)。每个使用虚函数的类都有一个虚函数表,该表是一个函数指针数组,存储了指向类的虚函数的指针。类的每个实例都包含一个指向其虚函数表的指针(vptr),通过这个指针可以找到并调用正确的虚函数实现。
当派生类覆盖(重写)基类的虚函数时,派生类的虚函数表中相应位置的函数指针会被更新为指向派生类中的函数。如果派生类没有重写虚函数,则派生类的虚函数表中会保留指向基类虚函数的指针。
1.3示例
#include <iostream> using namespace std; class Base { public: virtual void show() { cout << "Base class show" << endl; } }; class Derived : public Base { public: void show() override { cout << "Derived class show" << endl; } }; int main() { Base* b = new Derived(); b->show(); // 输出:Derived class show delete b; return 0; }
1.4 虚函数的重写
虚函数的重写(Override)是面向对象编程中实现多态性的一种方式。虚函数允许派生类根据需要改变或扩展基类中的行为。这里,我们将详细探讨虚函数的重写,包括它的定义、规则以及一些注意事项。
定义
虚函数重写指的是派生类中提供一个函数版本,该版本与基类中具有相同名称、相同返回类型和相同参数列表的虚函数相匹配。通过这种方式,派生类可以提供自己特定的实现,替换或扩展基类的行为。
规则
函数签名必须匹配:要重写基类中的虚函数,派生类中的函数必须具有相同的名称、返回类型和参数列表。
基类函数必须是虚函数:只有虚函数可以被重写。如果基类中的函数不是虚函数,派生类中相同签名的函数会隐藏(而非重写)基类中的函数。
访问权限可以不同:虚函数在派生类中的访问级别(public、protected、private)可以与基类中的不同,但这会影响到函数的访问性。
使用 override 关键字(C++11及以上):虽然不是强制的,但建议在派生类中重写虚函数时使用 override 关键字,这有助于编译器检查函数签名是否正确匹配,避免潜在的错误。
注意事项
析构函数应该是虚的:如果一个类有可能被继承,并且通过基类指针来删除派生类对象,那么基类的析构函数应该是虚的。这确保了通过基类指针删除派生类对象时,能够正确地调用派生类的析构函数。
构造函数不能是虚函数:在C++中,构造函数不能被声明为虚函数。因为构造函数是用来创建对象的,而虚函数的调用需要通过对象的虚函数表,这在对象构造阶段还未完全建立。
使用 final 关键字防止进一步重写:在某些情况下,你可能希望禁止进一步重写某个虚函数。C++11引入了final关键字,可以用来阻止派生类重写特定的虚函数。
#include <iostream> class Base { public: virtual void print() const { std::cout << "Base class print function" << std::endl; } virtual ~Base() {} // 虚析构函数 }; class Derived : public Base { public: void print() const override { // 使用override确保正确重写 std::cout << "Derived class print function" << std::endl; } }; int main() { Base* b = new Derived(); b->print(); // 输出:Derived class print function delete b; // 正确调用派生类析构函数 return 0; }
在上述示例中,Derived 类重写了 Base 类中的 print 函数,并且基类的析构函数被声明为虚函数,确保了通过基类指针删除派生类对象时能够正确调用派生类的析构函数。
通过理解和正确应用虚函数的重写,可以充分利用C++的多态性,设计出灵活且易于维护的面向对象程序。
1.5 基类和派生类的虚函数表
当涉及到继承时,虚函数表(vtable)的处理方式会稍微复杂一些,但关键点在于每个类都有自己的虚函数表,而不是只有一个。这意味着,如果有派生类继承自基类,并且这些类中包含虚函数,那么每个类将拥有各自独立的虚函数表。下面我们来详细解释这个过程。
基类:在基类中,编译器会为其创建一个虚函数表,这个表包含了基类中所有虚函数的地址。如果派生类没有覆盖(重写)这些虚函数,派生类对象的虚函数表会复制基类虚函数表中相应的条目。
派生类:当派生类覆盖(重写)基类中的虚函数时,派生类的虚函数表中对应位置的函数指针会被更新为指向派生类中的函数实现。如果派生类引入了新的虚函数,这些新的虚函数也会被加入到派生类的虚函数表中。
多重继承:在多重继承的情况下,每个基类都会有自己的虚函数表。派生类对象会包含多个虚函数表指针,每个指针指向对应基类的虚函数表。如果派生类覆盖了某个基类的虚函数,那么相关基类虚函数表中的条目会被更新为指向派生类中的实现。