文章目录

C++ 多态:虚函数与纯虚函数的实现

发布于 2026-04-06 19:21:22 · 浏览 13 次 · 评论 0 条

C++ 多态:虚函数与纯虚函数的实现


阶段一:实现基础多态(虚函数)

多态的核心逻辑是“统一指令,差异化执行”。通过虚函数,基类指针或引用在调用函数时,会跳过基类默认代码,直接执行指针实际指向的派生类代码。

  1. 声明 虚函数原型。在基类头文件(如 Shape.h)的 public: 区域,编写普通成员函数。在返回值类型与函数名之间插入 virtual 关键字。该关键字告知编译器,此函数允许后续版本覆盖。
  2. 提供 基类默认实现。在对应的源文件(如 Shape.cpp)中,使用作用域运算符 :: 编写 函数体。确保 函数具备完整逻辑,否则无法通过编译。基类实现作为“兜底方案”,在派生类未提供专属逻辑时生效。
  3. 派生 新类并重写函数。创建新头文件(如 Circle.h),使用 class Circle : public Shape 建立 继承链。在派生类的 public:重新声明 同名函数。删除 函数体末尾的 virtual(非必须),并在括号后追加 overrideoverride 为强校验标记,用于拦截 拼写错误或参数不一致的误写。
  4. 定义 派生类专属逻辑。在 Circle.cpp填充 具体代码。核对 函数签名(名称、参数类型与顺序、const 修饰符)是否与基类逐字匹配。签名不一致将导致 编译器将其识别为全新函数,多态机制立即失效。
  5. 执行 动态绑定调用。在 main.cpp 中,使用基类指针声明 变量 Shape* ptr。使用 new Circle() 实例化 派生类对象并赋值 给指针。通过 ptr->draw() 发起 调用。程序运行时,系统自动读取对象内部的隐藏标记,跳转Circle::draw() 执行,而非 Shape::draw()
// Shape.h
class Shape {
public:
    virtual void draw() const {
        // 基础图形绘制逻辑
    }
    virtual ~Shape() = default; // 预留析构函数接口
};

// Circle.h
class Circle : public Shape {
public:
    void draw() const override {
        // 圆形特有绘制逻辑
    }
};

阶段二:定义接口规范(纯虚函数)

当基类仅负责规定“必须包含哪些能力”,而自身无法给出通用做法时,启用 纯虚函数机制。包含纯虚函数的类自动转为“抽象类”,彻底屏蔽直接实例化的可能。

  1. 抹除 函数体并附加 零值标记。在基类声明中,将虚函数的花括号及内部代码全部清空。在右括号 )紧接 = 0;添加 分号。符号 = 0 是 C++ 语法保留字,专用于声明“此接口当前无实现”。
  2. 强制 子类完成接口契约。编写继承自该抽象基类的派生类时,逐一补全 所有纯虚函数的具体实现。检查 遗漏情况:若派生类存在任何一个纯虚函数未重写,编译器会拒绝 生成该类实例,并报错 提示抽象成员缺失。
  3. 实例化 具体子类并向上转型。抽象类禁止 执行 new AbstractClass()。必须 new DerivedClass() 创建实体对象。使用 基类指针 AbstractClass* ptr = new DerivedClass(); 接收 地址。此时 ptr 具备多态调度能力,且类型安全受编译器保护。
  4. 阻断 抽象类实例化尝试。在 main 函数中尝试编写 AbstractClass obj;编译 代码。编译器将输出 类似 “invalid abstract type for variable” 的错误信息。该错误属于设计期保护,移除 该行即可恢复 正常编译流程。
特性维度 普通虚函数 纯虚函数
语法标记 virtual void func() virtual void func() = 0;
实现要求 基类必须提供 函数体 基类禁止编写 函数体
实例化权限 允许 直接创建对象 绝对禁止 直接实例化
派生类约束 可选择性重写 强制重写,未重写则派生类继承抽象属性

阶段三:底层调度与关键约束

掌握内存布局与生命周期规则,能根除 资源泄漏与运行时崩溃。

  1. 构建 虚函数表(vtable)。编译器扫描到 virtual 标记后,为每个包含虚函数的类生成 独立的数据结构 vtable。该表本质是函数指针数组,按声明顺序存储该类所有虚函数的真实入口地址。此过程全自动完成,无需 手动干预。
  2. 注入 虚表指针(vptr)。对象分配 内存时,编译器在其起始位置插入 隐藏指针 vptr。构造函数执行 瞬间,vptr 被初始化为指向所属类 vtable 的首地址。调用虚函数时,CPU 读取 vptr索引 对应偏移量,定位 目标指令。路径表述为:对象内存首址 -> 读取vptr -> 获取vtable -> 按偏移量跳转
  3. 声明 虚析构函数。在基类析构函数 ~BaseClass()添加 virtual。当执行 delete base_ptr;base_ptr 实际指向派生类对象时,C++ 默认仅调用基类析构。添加 virtual 后,析构流程自动展开:先执行派生类清理逻辑,再逐级回溯至基类。忽略 此步骤将导致 派生类特有成员未释放,引发内存泄漏。
  4. 剥离 构造函数虚化属性。检查 所有构造函数声明,删除 误加的 virtual 关键字。对象构造初期 vptr 尚未绑定完成,动态分发机制未就绪。强制虚化将触发 编译器语法级拦截,直接中断构建。
  5. 利用 协变返回类型。若需调整重写函数的返回值,限定 返回值必须为指针或引用。派生类重写后的返回值类型,必须能无损转换为基类对应类型。例如基类声明 virtual Base* create() = 0;,派生类允许 声明 Derived* create() override;。编译器在编译期自动校验 继承链,确认安全后放行
class IRenderer {
public:
    virtual ~IRenderer() = default;
    virtual void render() const = 0; // 纯虚接口
};

class GLRenderer : public IRenderer {
public:
    void render() const override {
        // OpenGL 渲染管线逻辑
    }
};

void process(IRenderer& r) {
    r.render(); // 引用传递同样触发 vptr 查找机制
}

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文