虚函数与多态
在 C++ 中,虚函数(virtual
function)使得通过基类指针或引用调用派生类中的函数时能够实现动态绑定(动态多态)。虚函数允许程序在运行时根据实际对象的类型(而不是指针或引用的类型)来决定调用哪个函数,这就是多态的核心。
多态有两种类型:
- 静态多态:编译时决定调用哪个函数,通常通过函数重载和模板实现。
- 动态多态:运行时根据对象的实际类型决定调用哪个函数,这通常通过虚函数来实现。
虚函数与多态的使用通常依赖于继承。当基类和派生类之间存在继承关系时,基类中的虚函数可以被派生类重写(重载),并且在使用基类指针或引用调用该函数时,程序会自动调用派生类的实现。
虚函数和多态的实现
我们来看一个详细的例子来理解虚函数与多态是如何工作的:
#include<iostream>
using namespace std;
class Base {
public:
virtual void display() { // 声明虚函数
cout << "Base display function" << endl;
}
virtual ~Base() { // 虚析构函数
cout << "Base destructor called" << endl;
}
};
class Derived : public Base {
public:
void display() override { // 重写虚函数
cout << "Derived display function" << endl;
}
~Derived() { // 派生类析构函数
cout << "Derived destructor called" << endl;
}
};
int main() {
// 使用基类指针指向派生类对象
Base* basePtr = new Derived(); // 基类指针指向派生类对象
basePtr->display(); // 调用的是Derived类的display函数,因为display是虚函数
// 删除派生类对象,调用虚析构函数
delete basePtr; // 这里会先调用Derived的析构函数,然后调用Base的析构函数
return 0;
}
输出如下
虚函数的声明: 在
Base
类中,display
函数被声明为虚函数 (virtual
),这意味着如果有派生类继承自Base
并重写这个函数,那么在通过基类指针或引用调用时,程序将根据实际的对象类型来决定调用哪个版本的display
函数。函数重写:
Derived
类继承自Base
类,并重写了display
函数。注意,在Derived
类中重写display
时,使用了override
关键字。这个关键字不是必需的,但它能帮助编译器检查我们是否正确重写了基类的虚函数。如果基类没有虚函数,或者函数签名不匹配,编译器会报错。动态绑定: 在
main
函数中,我们创建了一个基类指针basePtr
,并将其指向一个Derived
类对象。虽然basePtr
是Base
类型的指针,但由于display
是虚函数,C++ 会在运行时通过指针指向的实际对象类型来调用合适的版本。因此,即使我们通过基类指针调用了display
函数,实际调用的是Derived
类中的版本,而不是Base
类中的版本。虚析构函数: 在删除对象时,如果基类指针指向派生类对象,并且基类的析构函数是虚拟的,那么程序会先调用派生类的析构函数,再调用基类的析构函数。这样可以确保派生类对象的资源得到正确释放,避免资源泄漏。
为什么要使用虚函数和多态?
-
动态绑定: 通过虚函数,可以在运行时决定调用哪个版本的函数。这样可以让代码更加灵活。例如,如果你有多个类继承自基类,并且这些类实现了相同的接口(虚函数),那么你就可以通过基类指针或者引用来操作这些派生类对象,而不需要关心它们的具体类型。
-
代码的扩展性: 如果你的程序中有一个使用基类指针的部分,而你需要向程序中添加新的类,只要这些新类实现了基类的虚函数接口,你的程序就能够正确处理它们,而不需要修改原有代码。
-
多态的实现: 多态是面向对象编程中一个强大的特性,它可以让你用统一的接口操作不同类型的对象,这使得代码更加简洁和高效。
小小总结一下
- 虚函数:声明为
virtual
的函数,可以被派生类重写,支持运行时动态绑定。- 动态多态:基类指针或引用调用派生类的重写函数,在运行时通过对象的实际类型决定调用哪个版本的函数。
- 虚析构函数:当通过基类指针删除派生类对象时,确保调用派生类的析构函数。