Bootstrap

C++中的虚函数与多态

一、虚函数的工作机制

当你在C++中使用虚函数时,编译器会为每个类生成一个虚函数表(Vtable),并为每个对象添加一个虚指针(Vptr),该指针指向该对象所属类的Vtable。每当调用虚函数时,程序会通过这个指针来找到实际应该调用的函数。

  • Vtable的结构:每个Vtable存储了类的所有虚函数的地址。这使得可以通过基类指针或引用调用派生类的实现。
  • Vptr的作用:它在对象构造时被初始化,指向正确的Vtable。随着对象的析构,Vptr也被正确管理。

二、重载、覆盖与隐藏的区别

在C++中,重载、覆盖和隐藏是三种不同的函数处理方式。

  1. 重载(Overloading)

    • 函数名相同,但参数列表不同(参数类型或数量)。
    • 发生在同一作用域中,编译器通过参数列表确定调用哪个函数。
    class Example {
    public:
        void func(int a) { std::cout << "Integer: " << a << std::endl; }
        void func(double b) { std::cout << "Double: " << b << std::endl; }
    };
    

  2. 覆盖(Overriding)

    • 子类中重写父类的虚函数,确保调用子类的实现。
    • 要求返回类型、参数列表完全相同,并且父类函数必须是虚函数。
    class Parent {
    public:
        virtual void show() { std::cout << "Parent show" << std::endl; }
    };
    

  3. 隐藏(Hiding)

    • 子类中定义与父类同名的非虚函数,会隐藏父类中所有同名函数。
    • 无论是否使用virtual,如果参数列表不同,都会隐藏。
    class Base {
    public:
        void func(int a) { std::cout << "Base int" << std::endl; }
    };
    class Derived : public Base {
    public:
        void func(double b) { std::cout << "Derived double" << std::endl; } // 隐藏了Base的func
    };
    

三、多态的两种形式

在C++中,多态分为静态多态(编译时多态)和动态多态(运行时多态)。

  1. 静态多态:通过函数重载和模板实现。

    • 编译器在编译阶段决定使用哪个函数,适合在参数类型或数量固定的情况下使用。
  2. 动态多态:通过虚函数实现。

    • 运行时根据对象的实际类型来调用相应的虚函数,适合于需要在运行时确定行为的场景。

四、纯虚函数与抽象类的使用

  1. 纯虚函数

    • 允许你定义一个接口,强制子类实现特定的功能。
    • 例如,设计图形类时,可以定义一个draw函数为纯虚函数,所有子类(如圆形、方形)都必须实现自己的draw方法。
    class Shape {
    public:
        virtual void draw() = 0; // 纯虚函数
    };
    

  2. 抽象类

    • 不能直接实例化的类,只能作为基类。
    • 可以包含实现了的普通成员函数,但必须至少有一个纯虚函数。
    class AbstractShape {
    public:
        virtual void draw() = 0; // 纯虚函数
        void display() { std::cout << "Displaying shape." << std::endl; } // 普通成员函数
    };
    

五、虚析构函数的细节

使用虚析构函数可以确保在删除基类指针指向的派生类对象时,先调用派生类的析构函数,再调用基类的析构函数,从而释放资源,避免内存泄漏。

  • 析构顺序

    • 当通过基类指针删除对象时,首先会调用派生类的析构函数,然后是基类的析构函数,确保先释放子类的资源。
  • 不加虚析构的风险

    • 如果不将析构函数声明为虚的,删除基类指针时只会调用基类的析构函数,子类的析构函数不会被调用,可能导致资源泄漏。

六、使用场景

  1. 接口设计:使用抽象类和纯虚函数设计接口,使得不同的类可以实现相同的接口,增强代码的可扩展性。

  2. 工厂模式:结合多态,可以在工厂类中返回基类指针,用户不需要知道具体的实现类,通过多态特性使用不同的实现。

  3. 策略模式:定义一系列算法,将每一个算法封装起来,使得它们可以相互替换,支持动态变化。

七、总结

通过深入理解虚函数、覆盖、多态以及纯虚函数和抽象类,程序员可以编写出更加灵活、可维护和可扩展的代码。这些概念不仅是C++编程的基础,也是许多设计模式的核心。希望本篇博客能够为你提供更深入的C++知识,并在实践中得心应手。

;