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; ?>
】
相关推荐
- mac怎么右键_MAC鼠标右键设置与触控板手势技巧
- Python列表推导式与字典推导式教程_简化代码高
- php订单日志怎么按状态筛选_php筛选不同状态订
- Win11开始菜单打不开_修复Windows 11
- Windows7怎么找回经典开始菜单_Window
- Win11怎么设置快速访问_Windows11文件
- 如何在Golang中捕获JSON序列化错误_Gol
- Win11怎么更改输入法顺序_Win11调整语言首
- 短链接怎么自定义还原php_修改解码规则适配需求【
- 如何用正则与预处理高效拦截带干扰符的恶意域名
- php错误怎么开启_display_errors与
- c++ stringstream用法详解_c++字
- 如何在 Go 中正确初始化结构体中的 map 字段
- Win11怎么开启上帝模式_创建Windows 1
- php嵌入式日志记录怎么实现_php将硬件数据写入
- Win11怎么设置快速访问主页_Windows11
- php串口通信波特率怎么选_根据硬件手册设置正确波
- php怎么操作Redis_Redis扩展连接与基本
- php打包exe怎么传递参数_命令行参数接收方法【
- Mac的“调度中心”与“空间”怎么用_Mac多桌面
- Mac怎么开启“任何来源”_Mac安装未签名应用的
- Mac如何备份到iCloud_Mac桌面与文稿文件
- Win10系统怎么查看显卡温度_Win10任务管理
- 如何使用Golang table-driven基准
- Win11怎样安装企业微信_Win11安装企业微信
- Win11怎么设置默认邮件客户端 Win11修改M
- c++怎么使用std::unique实现去重_c+
- 如何使用Golang模拟请求超时_Golang c
- Win11任务栏天气怎么关闭 Win11隐藏天气小
- Win11怎么退出高对比度模式_Win11取消反色
- Go 中实现 Python urllib.quot
- Django密码修改后会话失效的解决方案
- Windows蓝屏BAD_POOL_HEADER故
- Win11怎么设置虚拟内存_Windows 11优
- c++怎么实现高并发下的无锁队列_c++ std:
- Win11怎么开启窗口对齐助手_Windows11
- Win10如何优化内存使用_Win10内存优化技巧
- Win11色盲模式怎么开_Win11屏幕颜色滤镜设
- Win11怎么更改系统语言_Win11中文语言包下
- Win11时间格式怎么改成12小时制 Win11时
- 一文教你快速开通网站LOGO图
- C++如何将C风格字符串(char*)转换为std
- Win11怎么打开旧版计算器_Win11恢复传统计
- Python函数参数高级用法_默认值与可变参数解析
- c++如何使用std::bind绑定函数参数_c+
- 静态属性修改会影响所有实例吗_php作用域操作符下
- 作用域操作符会影响性能吗_php静态调用性能分析【
- php485读数据时阻塞怎么办_php485非阻塞
- 如何在 ACF 中正确更新嵌套多层的 Group
- Go 中 defer 在 goroutine 内部


QQ客服