c++ 虚函数使用笔记

1. 虚函数基础

定义:虚函数是实现 运行时多态(动态绑定) 的核心机制,允许通过基类指针或引用调用派生类的函数。
语法:使用 virtual 关键字声明,非静态成员函数析构函数可以声明为虚函数,构造函数不能是虚函数。

class Base {
public:
    virtual void func() { 
        std::cout << "Base::func()" << std::endl; 
    }
    virtual ~Base() {} // 虚析构函数(关键!)
};

2. 动态绑定(运行时多态)

原理:通过基类指针或引用调用虚函数时,实际执行的是 对象实际类型 的虚函数。
示例

class Derived : public Base {
public:
    void func() override { 
        std::cout << "Derived::func()" << std::endl; 
    }
};

int main() {
    Base* ptr = new Derived();
    ptr->func(); // 输出 "Derived::func()"
    delete ptr;
}

关键点

  • 动态绑定仅通过指针或引用生效(直接对象调用不触发多态)。
  • 虚函数调用依赖 虚函数表(vtable) ,每个多态类有一个 vtable,存储虚函数地址。

3. 虚函数表(vtable)

结构

  • 每个多态类(含虚函数)隐式生成一个 vtable。
  • 对象内存布局中有一个指向 vtable 的指针(vptr)。
  • vtable 中按声明顺序存储虚函数地址。

内存模型示例

+---------------------+
| Derived 对象        |
| +-----------------+ |
| | vptr            | --> [Derived::func()地址, ...]
| | 其他成员变量     | |
| +-----------------+ |
+---------------------+

4. 纯虚函数与抽象基类

纯虚函数:用 = 0 标记,强制派生类实现。含纯虚函数的类为 抽象基类,不能实例化。

class Shape {
public:
    virtual void draw() = 0; // 纯虚函数
};

class Circle : public Shape {
public:
    void draw() override { 
        std::cout << "绘制圆形" << std::endl; 
    }
};

5. 虚函数重写规则
  • 函数签名一致:函数名、参数列表、const 修饰符必须相同。
  • 返回类型协变:允许返回基类虚函数返回类型的派生类型(仅限指针或引用)。
  • 使用 override 关键字(C++11+):显式标记重写,避免签名错误。
class Derived : public Base {
public:
    void func() override { 
        // 显式标记重写,若签名不匹配则编译报错
    }
};

6. final 关键字
  • 标记类:禁止继承。
  • 标记虚函数:禁止派生类重写。
class Base final {};          // 类不可继承
class Base {
public:
    virtual void func() final; // 函数不可重写
};

7. 关键注意事项
7.1 构造函数中调用虚函数
  • 问题:在基类构造函数中调用虚函数,实际调用的是基类的版本(派生类尚未构造完成)。
class Base {
public:
    Base() { func(); } // 调用 Base::func()
    virtual void func() { std::cout << "Base"; }
};

class Derived : public Base {
public:
    void func() override { std::cout << "Derived"; }
};

int main() {
    Derived d; // 输出 "Base"
}
7.2 虚析构函数
  • 必要性:若通过基类指针删除派生类对象,基类析构函数必须为虚函数,否则导致资源泄漏。
Base* ptr = new Derived();
delete ptr; // 若 ~Base() 非虚,仅调用 Base 的析构函数!
7.3 虚函数的缺省实参
  • 静态绑定:缺省实参的值由调用者的静态类型决定,而非动态类型。
class Base {
public:
    virtual void func(int x = 1) { 
        std::cout << x; 
    }
};

class Derived : public Base {
public:
    void func(int x = 2) override { 
        std::cout << x; 
    }
};

int main() {
    Base* ptr = new Derived();
    ptr->func(); // 输出 "1"(缺省实参由 Base 决定)
}
7.4 修改派生类虚函数的访问权限
  • 允许但危险:派生类可以修改虚函数的访问权限(如从 public 改为 private),但会破坏设计逻辑。
class Base {
public:
    virtual void func() {}
};

class Derived : public Base {
private:
    void func() override {} // 合法但违反直觉
};

8. dynamic_cast 与虚函数
  • 关联性dynamic_cast 依赖虚函数表(vtable)实现运行时类型检查,因此只能用于多态类型。
Base* ptr = new Derived();
Derived* d = dynamic_cast<Derived*>(ptr); // 成功
if (d) { /* 安全使用 d */ }

9. 性能与设计建议
  • 虚函数调用成本:比非虚函数多一次间接寻址(访问 vtable)。
  • 何时使用虚函数
    • 需要运行时多态时。
    • 基类定义通用接口,派生类实现具体逻辑。
  • 何时避免虚函数
    • 性能敏感代码(如高频循环)。
    • 简单对象无需多态时。

总结

虚函数是 C++ 多态的核心机制,通过动态绑定实现灵活的运行时行为。关键要点:

  1. 基类析构函数必须为虚函数
  2. 使用 overridefinal 增强代码安全性。
  3. 避免在构造函数中调用虚函数。
  4. 理解虚函数表的内存模型及其性能影响。

示例代码整合

#include <iostream>

class Animal {
public:
    virtual void speak() = 0; // 纯虚函数
    virtual ~Animal() = default;
};

class Dog : public Animal {
public:
    void speak() override { 
        std::cout << "Woof!" << std::endl; 
    }
};

class Cat : public Animal {
public:
    void speak() override { 
        std::cout << "Meow!" << std::endl; 
    }
};

int main() {
    Animal* animals[] = {new Dog(), new Cat()};
    for (auto* animal : animals) {
        animal->speak(); // 动态绑定
        delete animal;
    }
    return 0;
}

输出

Woof!
Meow!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值