12. 虚函数

1.虚函数简介

2.虚函数的核心作用:运行时动态绑定

3.虚函数的底层原理

4.虚函数的关键规则


1.虚函数简介

csharp 复制代码
C++的虚函数(Virtual Function)是实现面向对象"多态"的核心机制, 能让程序在运行时根据对象的实际类型, 调用对应的方

法, 而非编译时的类型

为什么需要虚函数?

如果父类指针指向子类对象, 调用的方法会是父类的版本, 而非子类重写的版本
csharp 复制代码
#include <iostream>
using namespace std;

// 父类:游戏物体
class GameObject {
public:
    // 普通方法(非虚函数)
    void Move() {
        cout << "游戏物体通用移动" << endl;
    }
};

// 子类:玩家(重写Move方法)
class Player : public GameObject {
public:
    void Move() {
        cout << "玩家快速移动" << endl;
    }
};

int main() {
    // 父类指针指向子类对象
    GameObject* obj_ptr = new Player();
    obj_ptr->Move(); // 期望输出"玩家快速移动",实际输出"游戏物体通用移动"
    delete obj_ptr;
    return 0;
}
csharp 复制代码
问题根源: "编译时, 编译器根据obj_ptr的声明类型绑定方法(静态绑定), 而非对象的实际类型"

2.虚函数的核心作用:运行时动态绑定

csharp 复制代码
给父类的Move()方法加virtual关键字, 就能让方法绑定从"编译时"变成"运行时", 程序会根据对象的实际类型, 调用对应的

方法

1).虚函数的基本用法

class 父类名 {
public:
    // 声明虚函数
    virtual 返回值类型 方法名(参数) {
        // 父类实现
    }
};

class 子类名 : public 父类名 {
public:
    // 子类重写(override)虚函数(可加override关键字显式声明)
    返回值类型 方法名(参数) override {
        // 子类实现
    }
};
csharp 复制代码
2).示例

#include <iostream>
using namespace std;

class GameObject {
public:
    // 声明为虚函数
    virtual void Move() {
        cout << "游戏物体通用移动" << endl;
    }

    // 析构函数建议也声明为虚函数(下文解释)
    virtual ~GameObject() {}
};

class Player : public GameObject {
public:
    // 重写虚函数(加override更规范,编译器会检查是否真的重写)
    void Move() override {
        cout << "玩家快速移动" << endl;
    }
};

class Enemy : public GameObject {
public:
    void Move() override {
        cout << "敌人缓慢移动" << endl;
    }
};

int main() {
    // 父类指针指向不同子类对象,调用对应子类的Move方法
    GameObject* ptr1 = new Player();
    GameObject* ptr2 = new Enemy();
    
    ptr1->Move(); // 输出:玩家快速移动
    ptr2->Move(); // 输出:敌人缓慢移动

    delete ptr1;
    delete ptr2;
    return 0;
}

3.虚函数的底层原理

csharp 复制代码
虚函数的实现依赖虚函数表(vtable)和虚表指针(vptr)

a.每个包含虚函数的类, 编译器会生成一个"虚函数表"(vtable) ------ 存储该类所有虚函数的地址

b.每个对象会包含一个隐藏的"虚表指针(vptr)", 指向所属类的虚函数表

c.运行时, 程序通过vptr找到对应类的vtable, 再调用表中的方法地址 ------ 这就是动态绑定

csharp 复制代码
结合之前的GameObject/Player示例, 一步步拆解内存中的实际过程

a.编译器的预处理(编译阶段)

当你写了含虚函数的类, 编译器会做两件事
csharp 复制代码
// 父类:含虚函数
class GameObject {
public:
    virtual void Move() { cout << "通用移动" << endl; }
    virtual ~GameObject() {}
};

// 子类:重写虚函数
class Player : public GameObject {
public:
    void Move() override { cout << "玩家移动" << endl; }
};
csharp 复制代码
关键: 子类的虚表会"继承 + 覆盖"父类虚表 ------ 重写的方法替换地址, 未重写的沿用父类地址

b.对象的内存布局(创建对象时)

当你创建Player对象时, 内存中是这样的
csharp 复制代码
每个含虚函数的对象, 第一个字节一定是vptr(隐藏的)

vptr的值是"所属类的vtable的内存地址", 比如Player对象的vptr指向Player vtable

c.运行时调用方法(动态绑定核心)

当你执行GameObject* ptr = new Player(); ptr->Move(); CPU 执行的步骤是:

- 无虚函数时, 编译器直接把ptr->Move()翻译成"调用 GameObject::Move () 的地址"(编译时就定死)

- 有虚函数时, 编译器只翻译"先取vptr -> 找vtable -> 取方法地址 -> 调用"(运行时才确定具体地址)

4.虚函数的关键规则

csharp 复制代码
1).析构函数必须声明为虚函数

如果父类析构函数不是虚函数, 用父类指针删除子类对象时, 只会调用父类的析构函数, 导致子类的资源(比如堆内存、文件

句柄)无法释放, 造成内存泄漏
csharp 复制代码
class GameObject {
public:
    ~GameObject() { // 非虚析构
        cout << "GameObject析构" << endl;
    }
};

class Player : public GameObject {
public:
    ~Player() {
        cout << "Player析构" << endl; // 不会被调用!
    }
};

int main() {
    GameObject* ptr = new Player();
    delete ptr; // 只输出"GameObject析构",Player析构未执行
    return 0;
}

正确做法: 父类析构函数加virtual, 子类析构函数会自动成为虚函数("无需显式加")

csharp 复制代码
2).override关键字的作用

子类重写虚函数时加override, 编译器会检查:

a.父类是否有该虚函数

b.方法签名(返回值、参数、const 等)是否完全一致

c.如果不一致, 编译器会报错, 避免手误

csharp 复制代码
3).纯虚函数(抽象类)

如果父类只想定义接口, 不想提供实现, 可以声明纯虚函数, 这样的类称为"抽象类" ------ 不能实例化, 只能作为父类被继承

语法: "virtual 返回值类型 方法名(参数) = 0;"
csharp 复制代码
// 抽象类:所有可交互物体必须实现Interact方法
class Interactable {
public:
    virtual void Interact() = 0; // 纯虚函数
    virtual ~Interactable() {}
};

// 子类必须实现Interact,否则无法实例化
class Chest : public Interactable {
public:
    void Interact() override {
        cout << "打开宝箱,获得道具" << endl;
    }
};
相关推荐
千里马-horse2 小时前
Ray Tracing -- Ray query shadows
c++·rendering·vulkan
小y要自律2 小时前
10 string容器 - 字符串插入和删除
开发语言·c++·stl
刃神太酷啦2 小时前
Linux 基础 IO 收官:库的构建与使用、进程地址空间及核心知识点全解----《Hello Linux!》(11)
java·linux·c语言·数据库·c++·算法·php
Fcy6482 小时前
C++ 11 新增特性(下)
开发语言·c++·c++11·lambda·包装器
闻缺陷则喜何志丹2 小时前
【数论】P12191 [蓝桥杯 2025 省研究生组] 01 串|普及+
c++·数学·蓝桥杯·数论·洛谷
m0_635647482 小时前
Qt中使用opencv库imread函数读出的图片是空
开发语言·c++·qt·opencv·计算机视觉
燃于AC之乐2 小时前
【C++手撕STL】Vector模拟实现:从零到一的容器设计艺术
开发语言·c++·容器·stl·vector·底层·模板编程
御承扬3 小时前
鸿蒙原生系列之懒加载瀑布流组件
c++·harmonyos·懒加载·鸿蒙ndk ui·瀑布流布局
C++ 老炮儿的技术栈3 小时前
CMFCEditBrowseCtrl用法一例
c语言·开发语言·c++·windows·qt·visual studio code