C++ 怎么实现多态 C++虚函数与动态绑定机制详解【面试】
技术百科
裘德小鎮的故事
发布时间:2026-01-27
浏览: 次 基类指针调用虚函数时执行派生类版本,因编译器生成vtable并由对象vptr在运行时动态绑定;须通过指针或引用调用且函数声明为virtual,否则静态绑定。
为什么基类指针调用函数时,实际执行的是派生类的版本
因为编译器在遇到 virtual 声明的函数时,会为该类生成虚函数表(vtable),每个对象头部隐式存储一个指向该表的指针(vptr)。运行时通过 vptr 找到对应函数地址,完成动态绑定。
关键前提是:必须通过指针或引用调用,且函数声明为 virtual。直接用对象值传递会触发静态绑定(即切片 + 编译期决议)。
- 非
virtual函数:编译期根据变量静态类型决定调用哪个版本 -
virtual函数:运行期根据对象实际类型查 vtable 决定调用哪个版本 - 构造函数不能是
virtual;析构函数建议声明为virtual(尤其基类有指针成员时)
虚函数表(vtable)在内存中长什么样
vtable 是编译器生成的静态数组,每个元素是函数指针,顺序与类中 virtual 函数声明顺序一致。单继承下,派生类 vtable 会复制基类部分,并覆盖被重写的函数地址;多继承则可能有多个 vptr,布局更复杂。
注意:vtable 不是对象的一部分,而是类级别的只读数据;vptr 才是每个对象开头的指针(通常 8 字节,在 64 位系统上)。
- 可以用
sizeof验证:带虚函数的类,即使无成员变量,sizeof也至少为 8 - gdb 中可打印
*((void**)obj)查看 vptr 指向的首项(即第一个虚函数地址) - 纯虚函数在 vtable 中对应 nullptr,强制子类实现
override 和 final 关键字到底防什么
override 不是语法糖,它让编译器检查:当前函数是否真的重写了基类的 virtual 函数。拼错函数名、参数不匹配、const 修饰不一致都会报错。
final 则禁止后续派生类再重写该函数,或禁止整个类被继承。两者都用于把本应在运行时暴露的问题(如意外未重写、误覆写)提前到编译期捕获。
- 没加
override时,看似重写成功,实则定义了一个新函数,基类虚函数仍按原逻辑走 - 基类函数加了
final,子类里再写同签名函数并加override,编译直接失败 - 函数参数类型用引用/指针时,顶层 const 不影响重写判断;但底层 const(如
int* const)会影响
动态绑定失效的典型场景
最常见的是在构造函数和析构函数内部调用虚函数——此时动态绑定不生效,调用的是当前正在构造/析构的那个类的版本,而非最终派生类的版本。
原因:对象的 vptr 在构造函数执行过程中逐步被修改(先设为基类 vtable 地址,再逐层更新),析构时则逆向还原。所以中间状态无法反映完整类型。
- 构造函数中调用
virtual函数,等价于调用当前类的函数(哪怕派生类已重写) - 析构函数中同理,不会
跳转到派生类实现
- 避免在构造/析构中做依赖多态的行为;如需初始化逻辑,可提取为独立的
init()并由用户显式调用
虚函数机制本身开销很小(一次指针解引用 + 偏移寻址),但真正容易出问题的地方,往往不是“怎么写”,而是“在哪调”和“谁来管生命周期”。尤其是跨模块传递对象、用智能指针管理多态对象时,vptr 的存在和析构顺序会直接影响行为是否符合预期。
# 是在
# 的是
# 第一个
# 重写
# 绑定
# 并由
# 对象
# c++
# int
# void
# 字节
# 指针
# 子类
# 构造函数
# 为什么
# 成员变量
# 析构函数
# 继承
# 切片
# 多态
# 虚函数
# 派生类
# const
# 值传递
# 多继承
# 或引用
# 引用调用
# 纯虚函数
相关栏目:
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
AI推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
SEO优化<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
技术百科<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
谷歌推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
百度推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
网络营销<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
案例网站<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
精选文章<?muma echo $count; ?>
】
相关推荐
- 使用类变量定义字符串常量时如何实现类型安全的 Li
- Win11视频默认播放器怎么改_Win11关联第三
- MAC怎么在照片中添加水印_MAC自带编辑工具文字
- c++协程和线程的区别 c++异步编程模型对比【核
- Win11如何暂停系统更新 Win11暂停更新最长
- Win11怎么查看电脑配置_Win11硬件配置详细
- 如何使用Golang实现文件追加操作_向已有文件追
- 如何解决Windows时间不准的问题?(自动同步设
- 如何在Golang中操作嵌套切片指针_Golang
- 如何在Golang中实现WebSocket广播_使
- php订单日志怎么按金额排序_php按订单金额排序
- 如何使用Golang管理跨项目依赖_Golang多
- Windows10无法识别USB设备描述符请求失败
- 如何使用Golang实现文件加密_Golang c
- 使用类变量定义字符串常量时的类型安全最佳实践
- php转mp4怎么设置帧率_调整php生成mp4视
- Win11怎么关闭搜索历史 Win11清除搜索框最
- 短链接怎么用php还原_从基础原理到代码实现教学【
- php能跑在stm32上吗_php在stm32微控
- php转exe用什么工具打包快_高效打包软件推荐【
- Mac如何查看电池健康百分比_Mac系统信息电源检
- Win11怎么设置屏保时间_调整Win11屏幕保护
- Win11如何设置文件权限 Win11 NTFS文
- MAC如何快速搜索大文件_MAC磁盘空间分析与冗余
- Python数据挖掘核心算法实践_聚类分类与特征工
- Win11怎么设置开机自动连接宽带_Windows
- Python装饰器设计思路_功能增强机制说明【指导
- Win11怎么关闭通知消息_屏蔽Windows 1
- Laravel 查询 JSON 列:高效筛选包含数
- Python大型项目拆分策略_模块化解析【教程】
- c++输入输出流 c++ cin与cout格式化输
- Win11怎么开启自动HDR画质_Windows1
- c# 在ASP.NET Core中管理和取消后台任
- 如何在包含多值的列中精准搜索指定演员?
- Win11怎么清理C盘下载文件夹_Win11清理下
- php转mp4怎么保留字幕_php处理带字幕视频转
- Win11怎么更改输入法顺序_Win11调整语言首
- Win11此电脑不在桌面上_Windows 11桌
- Win11怎么修复系统文件_使用sfc命令修复Wi
- Win11怎么更改鼠标指针方案_Windows11
- 如何解决同一段404代码在不同主机上表现不一致的问
- PHP中require语句后直接调用返回对象方法的
- 如何在 Go 后端安全获取并验证前端存储的 JWT
- Python网络异常模拟_测试说明【指导】
- Win11怎样安装钉钉客户端_Win11安装钉钉教
- Win10系统更新错误0x80240034怎么办
- Win10如何关闭安全中心所有通知 Win10禁用
- Python 中将 ISO 8601 时间戳转换为
- 如何在Golang中使用内置函数_Golangle
- 如何在Mac上搭建Golang开发环境_使用Hom


QQ客服