在C++中,虚函数通过virtual关键字实现运行时多态,使基类指针能根据对象实际类型调用对应函数。1. 使用virtual声明基类函数以开启动态绑定;2. 派生类重写该函数时可使用override关键字确保正确覆盖;3. 虚函数机制依赖vtable(虚函数表)和vptr(虚函数表指针),每个含虚函数的类有vtable存储函数地址,每个对象包含指向vtable的vptr;4. 通过基类指针调用虚函数时,程序通过vptr找到实际类型的vtable并调用相应函数,实现运行时多态;5. 基类析构函数必须为虚函数,否则delete基类指针时派生类析构函数不会被调用,导致资源泄漏;6. 纯虚函数通过=0声明,使类成为抽象类,强制派生类实现该函数,用于定义接口规范。该机制虽带来轻微性能开销,但极大提升了系统灵活性与可扩展性。

在C++中实现虚函数,核心就是通过
virtual
要实现一个虚函数,你只需要在基类的成员函数声明前加上
virtual
virtual
下面是一个基本的例子:
#include <iostream>
#include <string>
// 基类
class Animal {
public:
// 声明一个虚函数
virtual void speak() const {
std::cout << "Animal makes a sound." << std::endl;
}
// 虚析构函数,非常重要!
virtual ~Animal() {
std::cout << "Animal destructor called." << std::endl;
}
};
// 派生类 Dog
class Dog : public Animal {
public:
// 重写基类的虚函数
void speak() const override { // 使用 override 关键字是个好习惯,编译器会检查是否真的重写了虚函数
std::cout << "Dog barks: Woof! Woof!" << std::endl;
}
~Dog() override {
std::cout << "Dog destructor called." << std::endl;
}
};
// 派生类 Cat
class Cat : public Animal {
public:
void speak() const override {
std::cout << "Cat meows: Meow!" << std::endl;
}
~Cat() override {
std::cout << "Cat destructor called." << std::endl;
}
};
int main() {
Animal* myAnimal = new Animal();
Animal* myDog = new Dog(); // 基类指针指向派生类对象
Animal* myCat = new Cat(); // 基类指针指向派生类对象
myAnimal->speak(); // 输出: Animal makes a sound.
myDog->speak(); // 输出: Dog barks: Woof! Woof! (动态绑定生效)
myCat->speak(); // 输出: Cat meows: Meow! (动态绑定生效)
std::cout << "\n--- Deleting objects ---\n";
delete myAnimal;
delete myDog; // 如果Animal的析构函数不是虚函数,这里可能只会调用Animal的析构函数,导致Dog的析构函数未被调用,造成资源泄露。
delete myCat;
return 0;
}在这个例子中,
speak()
Animal*
speak()
Dog
Cat
speak()
virtual
myDog->speak()
myCat->speak()
Animal
speak()
立即学习“C++免费学习笔记(深入)”;
要理解虚函数如何实现动态绑定,就不得不提C++编译器在幕后为我们做的一些“手脚”——虚函数表(vtable)和虚函数表指针(vptr)。我个人觉得,这是C++多态机制最巧妙,也最容易让人感到困惑的地方之一。
当一个类中声明了虚函数,或者继承了带有虚函数的基类时,编译器会为这个类生成一个虚函数表(vtable)。这个vtable本质上是一个函数指针数组,里面存储着该类所有虚函数的地址。每个对象(如果它的类有虚函数)在创建时,都会在它的内存布局中包含一个指向这个vtable的指针,我们称之为虚函数表指针(vptr)。这个vptr通常是对象内存布局中的第一个成员。
所以,当我们通过一个基类指针(比如
Animal* myDog
myDog->speak()
myDog
这个过程发生在运行时,因为vptr指向的vtable是根据对象的实际类型来确定的,所以即使指针类型是基类,也能正确地调用派生类的实现。这也就是“动态绑定”的由来。这个机制虽然带来了一点点内存和性能上的开销(每个对象多了一个vptr,每次虚函数调用多了一次间接寻址),但它换来了巨大的设计灵活性,我觉得这绝对是值得的。
这是一个C++初学者经常踩的坑,也是面试中常被问到的点。简单来说,如果基类的析构函数不是虚函数,而你通过基类指针删除一个派生类对象,那么可能只会调用基类的析构函数,而派生类的析构函数则不会被调用。这听起来可能没啥大不了,但想想看,如果派生类在析构函数中释放了它自己独有的资源(比如动态分配的内存、文件句柄、网络连接等),那么这些资源就永远不会被释放,造成内存泄漏或资源泄漏。
我们来模拟一下这种情况:
#include <iostream>
#include <string>
class Base {
public:
Base() { std::cout << "Base constructor called.\n"; }
// 如果这里没有 virtual 关键字
// ~Base() { std::cout << "Base destructor called.\n"; }
virtual ~Base() { std::cout << "Base destructor called.\n"; } // 正确的做法
};
class Derived : public Base {
private:
int* data;
public:
Derived() : data(new int[10]) {
std::cout << "Derived constructor called. Allocating data.\n";
}
~Derived() override {
delete[] data; // 释放派生类独有的资源
std::cout << "Derived destructor called. Deallocating data.\n";
}
};
int main() {
Base* obj = new Derived(); // 基类指针指向派生类对象
// ... 使用 obj ...
delete obj; // 问题就出在这里!
return 0;
}如果
Base
virtual
delete obj;
Base::~Base()
Derived
~Derived()
delete[] data;
data
虚函数提供了一种“可选”的重写机制,而纯虚函数则是一种“强制”的机制。当你希望基类定义一个接口,但又不提供这个接口的默认实现,并且强制所有派生类都必须提供自己的实现时,纯虚函数就派上用场了。
纯虚函数的声明方式是在虚函数声明的末尾加上
= 0
#include <iostream>
// 抽象基类
class Shape {
public:
// 纯虚函数:声明一个接口,但没有实现
virtual double area() const = 0;
virtual void draw() const = 0;
// 抽象类可以有非纯虚函数和成员变量
void printInfo() const {
std::cout << "This is a shape." << std::cout;
}
virtual ~Shape() { // 抽象类也应该有虚析构函数
std::cout << "Shape destructor called.\n";
}
};
// 派生类 Circle
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
// 必须实现所有纯虚函数
double area() const override {
return 3.14159 * radius * radius;
}
void draw() const override {
std::cout << "Drawing a circle with radius " << radius << std::endl;
}
~Circle() override {
std::cout << "Circle destructor called.\n";
}
};
// 派生类 Rectangle
class Rectangle : public Shape {
private:
double width;
double height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
double area() const override {
return width * height;
}
void draw() const override {
std::cout << "Drawing a rectangle with width " << width << " and height " << height << std::endl;
}
~Rectangle() override {
std::cout << "Rectangle destructor called.\n";
}
};
int main() {
// Shape s; // 错误:不能实例化抽象类!
Shape* s1 = new Circle(5.0);
Shape* s2 = new Rectangle(4.0, 6.0);
s1->draw();
std::cout << "Circle area: " << s1->area() << std::endl;
s2->draw();
std::cout << "Rectangle area: " << s2->area() << std::endl;
delete s1;
delete s2;
return 0;
}任何包含至少一个纯虚函数的类都被称为抽象类。抽象类不能被直接实例化(你不能创建
Shape
这种机制在设计模式中非常常见,比如策略模式、模板方法模式等。它能帮助我们构建一个清晰的类层次结构,强制未来的开发者遵循特定的设计约定,这对于大型项目的代码维护性和可读性来说,无疑是极大的提升。我个人在设计一些库的时候,就非常喜欢用抽象类来定义核心功能接口,让使用者去实现具体的细节,这真的能让代码结构清晰很多。
以上就是如何在C++中实现一个虚函数_C++虚函数与动态绑定的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号