1 条评论

  • @ 2025-8-11 14:38:08

    C++ 虚函数(virtual)学习笔记教程

    一、虚函数的基本概念

    虚函数是C++中实现多态性的核心机制,它允许在派生类中重写基类的成员函数,并通过基类指针或引用调用派生类的实现。

    • 定义方式:在函数声明前加上virtual关键字
    • 作用:实现动态绑定(运行时多态),即根据对象的实际类型而非指针/引用的声明类型来调用相应的函数
    • 特点:仅需在基类中声明时添加virtual,派生类中重写的函数自动成为虚函数(即使不加virtual
    class Base {
    public:
        // 声明虚函数
        virtual void show() {
            cout << "Base class show()" << endl;
        }
    };
    

    二、虚函数的使用规则

    1. 重写规则(函数签名需完全一致):

      • 函数名必须相同
      • 参数列表必须相同(类型、顺序、数量)
      • 返回值类型必须相同(协变返回类型除外,即返回基类/派生类指针/引用时可不同)
    2. 访问控制:派生类中重写的虚函数可以改变访问权限(如基类中public,派生类中protected),但不影响多态调用

    3. 示例代码

    class Base {
    public:
        virtual void func() {  // 基类虚函数
            cout << "Base::func()" << endl;
        }
    };
    
    class Derived : public Base {
    public:
        void func() override {  // 派生类重写(建议加override关键字)
            cout << "Derived::func()" << endl;
        }
    };
    
    int main() {
        Base* ptr = new Derived();
        ptr->func();  // 输出"Derived::func()",实现动态绑定
        delete ptr;
        return 0;
    }
    

    三、纯虚函数与抽象类

    3.1 纯虚函数

    纯虚函数是没有具体实现的虚函数,用于定义接口规范。

    • 声明方式:在函数声明后加上= 0
    • 特点:包含纯虚函数的类无法实例化
    • 示例
    class Shape {
    public:
        virtual double area() const = 0;  // 纯虚函数
    };
    

    3.2 抽象类

    包含纯虚函数的类称为抽象类,它主要作为基类使用,为派生类提供统一接口。

    • 规则

      • 抽象类不能创建对象
      • 派生类必须实现所有纯虚函数才能成为非抽象类
      • 抽象类可以有非纯虚函数和成员变量
    • 示例

    class Shape {  // 抽象类
    public:
        virtual double area() const = 0;  // 纯虚函数
        virtual void print() const {  // 普通虚函数
            cout << "This is a shape" << endl;
        }
    };
    
    class Circle : public Shape {
    private:
        double radius;
    public:
        Circle(double r) : radius(r) {}
        double area() const override {  // 必须实现纯虚函数
            return 3.14 * radius * radius;
        }
    };
    

    四、虚析构函数

    虚析构函数用于确保当通过基类指针删除派生类对象时,能正确调用派生类的析构函数。

    • 必要性:如果析构函数不是虚函数,删除基类指针指向的派生类对象时,可能只调用基类析构函数,导致内存泄漏
    • 声明方式:在析构函数前加virtual关键字
    class Base {
    public:
        virtual ~Base() {  // 虚析构函数
            cout << "Base destructor" << endl;
        }
    };
    
    class Derived : public Base {
    private:
        int* data;
    public:
        Derived() { data = new int; }
        ~Derived() override {  // 派生类析构函数
            delete data;
            cout << "Derived destructor" << endl;
        }
    };
    
    int main() {
        Base* ptr = new Derived();
        delete ptr;  // 先调用Derived析构函数,再调用Base析构函数
        return 0;
    }
    

    五、虚函数表(vtable)原理

    C++通过虚函数表实现动态绑定,每个包含虚函数的类都有一个虚函数表(vtable),存储该类所有虚函数的地址。

    • 机制

      • 每个对象包含一个指向虚函数表的指针(vptr)
      • 派生类的虚函数表继承自基类,重写的函数会替换表中对应位置的地址
      • 调用虚函数时,通过vptr找到vtable,再调用对应函数地址
    • 特点

      • 增加了内存开销(每个对象多一个指针,每个类多一个虚函数表)
      • 虚函数调用比普通函数调用稍慢(需通过指针间接访问)

    六、常见问题与注意事项

    1. 构造函数不能是虚函数:因为对象在构造时,虚函数表尚未完全建立
    2. 静态成员函数不能是虚函数:静态函数属于类,不依赖对象实例,无法实现动态绑定
    3. 内联函数与虚函数:虚函数可以声明为inline,但多态调用时不会内联(编译期无法确定具体调用哪个函数)
    4. override关键字:建议在重写虚函数时使用,编译器会检查是否正确重写,避免因函数签名不一致导致的错误
    5. final关键字:可用于阻止函数被重写或类被继承
      class Base {
      public:
          virtual void func() final {}  // 该函数不能被重写
      };
      
      class FinalClass final {};  // 该类不能被继承
      

    七、虚函数的应用场景

    1. 多态行为:如图形系统中,不同图形(圆、矩形等)有不同的面积计算方式
    2. 接口设计:通过抽象类定义接口,派生类实现具体功能
    3. 回调机制:利用虚函数实现回调功能
    4. 框架设计:在框架中定义虚函数作为扩展点,用户通过重写虚函数扩展框架功能

    掌握虚函数是学好C++面向对象编程的关键,它为代码的灵活性和可扩展性提供了强大支持,但也需注意合理使用以避免不必要的性能开销。

    • 1