输入关键词开始搜索

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 虚函数