C++ 虚函数与多态
为什么需要虚函数
class Animal {
public:
void speak() { cout << "Animal\n"; } // 普通函数
};
class Dog : public Animal {
public:
void speak() { cout << "Woof!\n"; }
};
Animal *a = new Dog();
a->speak(); // "Animal" ❌ 不是 "Woof!"
// 编译器根据指针类型(Animal)决定调用哪个函数
加上 virtual 后:
class Animal {
public:
virtual void speak() { cout << "Animal\n"; }
};
class Dog : public Animal {
public:
void speak() override { cout << "Woof!\n"; }
};
Animal *a = new Dog();
a->speak(); // "Woof!" ✅ 运行时查虚函数表找到 Dog::speak
虚函数表(vtable)
Animal 对象: Dog 对象:
┌──────────┐ ┌──────────┐
│ vptr ────┼──→ vtable │ vptr ────┼──→ vtable
│ data... │ ┌──────────┐ │ data... │ ┌──────────┐
└──────────┘ │ speak() │ └──────────┘ │ speak() │
└──────────┘ └──────────┘
→ Animal::speak → Dog::speak
每个含虚函数的类有一张 vtable,对象里藏一个 vptr 指向它。
调用 obj->speak() → 查 vptr → 查 vtable → 调对应函数。
override / final
class Base {
public:
virtual void foo() const {}
virtual void bar() {}
};
class Derived : public Base {
public:
void foo() const override {} // ✅ 明确覆盖 Base::foo
// void foo() override {} // ❌ 编译错误:少了 const
void bar() final {} // 禁止子类再覆盖
};
class More : public Derived {
public:
// void bar() override {} // ❌ 编译错误:bar 是 final
};
规则:覆盖虚函数永远加 override。
纯虚函数与抽象类
class Shape {
public:
virtual double area() const = 0; // 纯虚函数
virtual ~Shape() = default;
};
class Circle : public Shape {
double r;
public:
double area() const override { return 3.14159 * r * r; }
};
// Shape s; // ❌ 不能实例化抽象类
Shape *p = new Circle(); // ✅ 指针/引用可以
虚析构函数
class Base {
public:
~Base() { cout << "~Base\n"; }
};
class Derived : public Base {
string data;
public:
~Derived() { cout << "~Derived\n"; }
};
Base *p = new Derived();
delete p; // 只输出 "~Base" ❌ Derived 的 data 没析构 = 内存泄漏!
class Base {
public:
virtual ~Base() { cout << "~Base\n"; } // ✅ 虚析构
};
delete p; // "~Derived" → "~Base" ✅ 正确析构
规则:有虚函数的基类,析构函数必须 virtual(或 protected 非虚)。
运行期类型识别
// dynamic_cast — 安全的向下转型
Base *b = new Derived();
if (auto *d = dynamic_cast<Derived *>(b)) {
d->derivedMethod(); // 安全调用派生类方法
}
// 失败返回 nullptr(指针)或抛 bad_cast(引用)
// typeid
if (typeid(*b) == typeid(Derived)) {
cout << "是 Derived\n";
}
常见陷阱
// 陷阱 1: 构造/析构函数中调虚函数
class Base {
public:
Base() { init(); } // ❌ 构造函数中调虚函数
virtual void init() {}
};
class Derived : public Base {
void init() override { /* 期望执行这个但不会 */ }
};
// 构造时 vptr 还指向 Base → 调的是 Base::init
// 陷阱 2: 默认参数是静态绑定的
class Base {
public:
virtual void f(int n = 10) { cout << n; }
};
class Derived : public Base {
public:
void f(int n = 20) override { cout << n; }
};
Derived d;
Base *p = &d;
p->f(); // 输出 10 ← 默认参数来自 Base(静态),函数体来自 Derived(动态)
何时用虚函数
| 场景 | 用不用 |
|---|---|
| 基类指针/引用调用派生类方法 | ✅ 必须 virtual |
| 析构函数(基类有虚函数) | ✅ 必须 virtual |
| 非多态类的小对象 | ❌ 避免 vptr 开销 |
| 模板 — 编译期多态 | ❌ 用 CRTP 代替 runtime 虚函数 |