C++ 多态:虚函数与纯虚函数的实现
阶段一:实现基础多态(虚函数)
多态的核心逻辑是“统一指令,差异化执行”。通过虚函数,基类指针或引用在调用函数时,会跳过基类默认代码,直接执行指针实际指向的派生类代码。
- 声明 虚函数原型。在基类头文件(如
Shape.h)的public:区域,编写普通成员函数。在返回值类型与函数名之间插入virtual关键字。该关键字告知编译器,此函数允许后续版本覆盖。 - 提供 基类默认实现。在对应的源文件(如
Shape.cpp)中,使用作用域运算符::编写 函数体。确保 函数具备完整逻辑,否则无法通过编译。基类实现作为“兜底方案”,在派生类未提供专属逻辑时生效。 - 派生 新类并重写函数。创建新头文件(如
Circle.h),使用class Circle : public Shape建立 继承链。在派生类的public:中重新声明 同名函数。删除 函数体末尾的virtual(非必须),并在括号后追加override。override为强校验标记,用于拦截 拼写错误或参数不一致的误写。 - 定义 派生类专属逻辑。在
Circle.cpp中填充 具体代码。核对 函数签名(名称、参数类型与顺序、const修饰符)是否与基类逐字匹配。签名不一致将导致 编译器将其识别为全新函数,多态机制立即失效。 - 执行 动态绑定调用。在
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 {
// 圆形特有绘制逻辑
}
};
阶段二:定义接口规范(纯虚函数)
当基类仅负责规定“必须包含哪些能力”,而自身无法给出通用做法时,启用 纯虚函数机制。包含纯虚函数的类自动转为“抽象类”,彻底屏蔽直接实例化的可能。
- 抹除 函数体并附加 零值标记。在基类声明中,将虚函数的花括号及内部代码全部清空。在右括号
)后紧接= 0;并添加 分号。符号= 0是 C++ 语法保留字,专用于声明“此接口当前无实现”。 - 强制 子类完成接口契约。编写继承自该抽象基类的派生类时,逐一补全 所有纯虚函数的具体实现。检查 遗漏情况:若派生类存在任何一个纯虚函数未重写,编译器会拒绝 生成该类实例,并报错 提示抽象成员缺失。
- 实例化 具体子类并向上转型。抽象类禁止 执行
new AbstractClass()。必须new DerivedClass()创建实体对象。使用 基类指针AbstractClass* ptr = new DerivedClass();接收 地址。此时ptr具备多态调度能力,且类型安全受编译器保护。 - 阻断 抽象类实例化尝试。在
main函数中尝试编写AbstractClass obj;。编译 代码。编译器将输出 类似 “invalid abstract type for variable” 的错误信息。该错误属于设计期保护,移除 该行即可恢复 正常编译流程。
| 特性维度 | 普通虚函数 | 纯虚函数 |
|---|---|---|
| 语法标记 | virtual void func() |
virtual void func() = 0; |
| 实现要求 | 基类必须提供 函数体 | 基类禁止编写 函数体 |
| 实例化权限 | 允许 直接创建对象 | 绝对禁止 直接实例化 |
| 派生类约束 | 可选择性重写 | 强制重写,未重写则派生类继承抽象属性 |
阶段三:底层调度与关键约束
掌握内存布局与生命周期规则,能根除 资源泄漏与运行时崩溃。
- 构建 虚函数表(vtable)。编译器扫描到
virtual标记后,为每个包含虚函数的类生成 独立的数据结构 vtable。该表本质是函数指针数组,按声明顺序存储该类所有虚函数的真实入口地址。此过程全自动完成,无需 手动干预。 - 注入 虚表指针(vptr)。对象分配 内存时,编译器在其起始位置插入 隐藏指针
vptr。构造函数执行 瞬间,vptr被初始化为指向所属类 vtable 的首地址。调用虚函数时,CPU 读取vptr,索引 对应偏移量,定位 目标指令。路径表述为:对象内存首址 -> 读取vptr -> 获取vtable -> 按偏移量跳转。 - 声明 虚析构函数。在基类析构函数
~BaseClass()前添加virtual。当执行delete base_ptr;且base_ptr实际指向派生类对象时,C++ 默认仅调用基类析构。添加virtual后,析构流程自动展开:先执行派生类清理逻辑,再逐级回溯至基类。忽略 此步骤将导致 派生类特有成员未释放,引发内存泄漏。 - 剥离 构造函数虚化属性。检查 所有构造函数声明,删除 误加的
virtual关键字。对象构造初期vptr尚未绑定完成,动态分发机制未就绪。强制虚化将触发 编译器语法级拦截,直接中断构建。 - 利用 协变返回类型。若需调整重写函数的返回值,限定 返回值必须为指针或引用。派生类重写后的返回值类型,必须能无损转换为基类对应类型。例如基类声明
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 查找机制
}
暂无评论,快来抢沙发吧!