C++ 虚函数、多态与绑定机制

C++ 虚函数、多态与绑定机制
在这里插入图片描述
在这里插入图片描述
在 C++ 中,对象本身只保存数据和一个隐藏的虚表指针(vptr),而所有成员函数的代码都只存在于程序的代码段中
如果类有虚函数,编译器会为每个类生成一张虚表(vtable),虚表里存放对应虚函数的函数地址。
当创建 Derived 对象时,对象内的 vptr 会指向 Derived::vtable;如果用 Base* 指向这个对象并调用虚函数,程序会在运行时通过 vptr → vtable → 函数地址,最终调用到 Derived::f,这就是动态绑定
如果是普通对象或按值传递(发生对象切片),就不会经过虚表,而是在编译期直接决定调用 Base::f,这是静态绑定
构造和析构过程中,vptr 会随着当前构造层级切换,确保虚函数只调用“当前已构造完成”的那一层实现。

引言:为什么需要多态?

设想一个图形绘制系统:

classShape{ public:voiddraw(){ /* 绘制基础形状 */}};classCircle:publicShape{ public:voiddraw(){ /* 绘制圆形 */}};classRectangle:publicShape{ public:voiddraw(){ /* 绘制矩形 */}};// 问题来了:如何统一处理?voiddrawAll(Shape* shapes[],int count){ for(int i =0; i < count; i++){  shapes[i]->draw();// 期望:调用实际类型的 draw}}

核心问题:如何让基类指针调用到正确的派生类函数?

这就引出了多态(Polymorphism)的需求,而实现多态的关键是绑定机制


一、什么是“绑定(Binding)”

绑定指的是:

在某个时间点,确定“一次函数调用”究竟对应哪一个具体函数实现。

在 C++ 中,绑定主要分为两类:

  • 静态绑定(Static Binding / Early Binding)
  • 动态绑定(Dynamic Binding / Late Binding)

二、静态绑定(Static Binding)

1️⃣ 定义

在编译期就能确定调用目标的绑定方式。

2️⃣ 典型特征

  • 不依赖对象的运行时类型
  • 调用目标在编译期已确定
  • 通常可被内联,性能最好

3️⃣ 发生静态绑定的情况

(1)非 virtual 函数
structBase{ voidf(){  std::cout <<"Base::f\n";}};structDerived:Base{ voidf(){  std::cout <<"Derived::f\n";}}; Base* p =new Derived; p->f();// 静态绑定,调用 Base::f,输出 "Base::f"

解析

  • 编译器看到 p 类型是 Base*
  • f() 不是虚函数,编译期直接确定调用 Base::f
  • 即使 p 实际指向 Derived 对象,也不会调用 Derived::f
(2)按值调用(对象切片)
voidprocess(Base b){ // 按值传递 b.f();// 始终调用 Base::f} Derived d;process(d);// 静态绑定,Base::f

切片过程

  • 传参时构造一个新的 Base 对象
  • 只拷贝 Derived 中的 Base 部分
  • Derived 的额外数据丢失
  • vptr 被重置为 Base::vtable
(3)构造 / 析构期间的虚函数调用
structBase{ Base(){ f();}// 在构造函数中调用virtualvoidf(){  std::cout <<"Base::f\n";}};structDerived:Base{ voidf()override{  std::cout <<"Derived::f\n";}}; Derived d;// 输出 "Base::f",不是 "Derived::f"
  • 即使 f 是 virtual
  • 构造期间仍 静态绑定到当前构造层级

4️⃣ 静态绑定的性能优势

// 编译器可能的优化inlinevoidBase::f(){ /* ... */} p->f();// 静态绑定 → 可内联展开 → 无函数调用开销
  • 零运行时开销
  • 可被内联优化
  • 无需查表操作

三、动态绑定(Dynamic Binding)

1️⃣ 定义

在运行时,根据对象的动态类型决定调用哪个函数实现。

2️⃣ 动态绑定的三个必要条件

  1. 函数被声明为 virtual
  2. 通过 基类指针或引用 调用
  3. 指针/引用指向 完整的派生类对象
structBase{ virtualvoidf(){  std::cout <<"Base::f\n";}};structDerived:Base{ voidf()override{  std::cout <<"Derived::f\n";}}; Base* p =new Derived; p->f();// 动态绑定,输出 "Derived::f" Base& r =*new Derived; r.f();// 动态绑定,输出 "Derived::f"

3️⃣ 完整示例:对比静态与动态绑定

#include<iostream>structBase{ voidnormalFunc(){  std::cout <<"Base::normalFunc\n";}virtualvoidvirtualFunc(){  std::cout <<"Base::virtualFunc\n";}};structDerived:Base{ voidnormalFunc(){  std::cout <<"Derived::normalFunc\n";}voidvirtualFunc()override{  std::cout <<"Derived::virtualFunc\n";}};intmain(){  Derived d

Read more

游戏开发者必看:Visual C++运行库部署实战

快速体验 1. 打开 InsCode(快马)平台 https://www.inscode.net 2. 输入框内输入如下内容: 创建一个游戏安装包集成工具,功能:1.分析游戏exe依赖的VC++运行库版本 2.自动下载对应版本的合并模块(merge module) 3.生成包含运行库的安装程序 4.提供静默安装选项 5.支持从2005到2022所有VC++版本。使用Inno Setup脚本实现,集成VC_redist.exe自动检测逻辑。 1. 点击'项目生成'按钮,等待项目生成完整后预览效果 游戏开发中经常遇到玩家反馈"缺少dll"的问题,十有八九都是Visual C++运行库没装对。今天就分享一个实战经验:如何把VC+

By Ne0inhk
Re:从零开始的 C++ 入門篇(九)类和对象·最终篇上:缓冲区同步与流绑定、取地址运算符重载、const成员函数、初始化列表

Re:从零开始的 C++ 入門篇(九)类和对象·最终篇上:缓冲区同步与流绑定、取地址运算符重载、const成员函数、初始化列表

◆ 博主名称: 晓此方-ZEEKLOG博客 大家好,欢迎来到晓此方的博客。 ⭐️C++系列个人专栏: Re:从零开始的C++_晓此方的博客-ZEEKLOG博客  ⭐️踏破千山志未空,拨开云雾见晴虹。 人生何必叹萧瑟,心在凌霄第一峰 目录 0.1概要&序論 一,缓冲区同步与流绑定 1.1缓冲区与缓冲区刷新 1.1.1缓冲区的意义 1.1.2缓冲区的定义 1.1.3刷新缓冲区的定义 1.1.4刷新缓冲区的时机 1.2C/C++缓冲区同步刷新 1.2.1同步刷新的缺陷 1.2.2解决同步缺陷的办法 1.3输入输出流绑定 1.3.1C-library官方文档摘要 1.

By Ne0inhk
【Linux/C++多进程篇(二) 】万字解析从“传纸条”到“建仓库”:一文读懂linux系统编程之进程间通信 (IPC)

【Linux/C++多进程篇(二) 】万字解析从“传纸条”到“建仓库”:一文读懂linux系统编程之进程间通信 (IPC)

⭐️在这个怀疑的年代,我们依然需要信仰。 个人主页:YYYing. ⭐️Linux/C++进阶系列专栏:【从零开始的linux/c++进阶编程】 系列上期内容:【Linux/C++多进程篇(一) 】C/C++ 程序中神奇的“分身术” 系列下期内容:【Linux/C++多线程篇(一) 】多线程编程入门 目录 前言: 进程间通信(IPC) 一、进程间通信的基础概念 二、内核提供的通信方式 2.1、无名管道  📖 无名管道的API  📖 代码案例 2.2、有名管道  📖 有名管道的API  📖 代码案例 2.3、管道特点 2.4、信号  📖 信号相关概念

By Ne0inhk

运行代码报错subprocess.CalledProcessError:Command ‘[‘which‘,‘c++‘]‘ returned non-zero exit status 1.

我现在是要在x86_64麒麟系统电脑上运行我的代码,结果出现下面的报错信息: subprocess.CalledProcessError: Command '['which', 'c++']' returned non-zero exit status 1 出现这个问题的原因是Python 或系统脚本调用 which c++ 时,系统没有找到 c++ 编译器。其根本原因可能是: * 系统中没有安装 g++ 或 build-essential; * 或者虽然安装了,但没有加入 PATH; * 或者安装到了非默认路径(比如 /home/xxx/mygcc)。  然后我就开始尝试安装这个g++,我的系统是 银河麒麟 V10(x86_64),电脑不能联网,所以就分为在线安装和离线安装两种形式。 1、在线安装

By Ne0inhk