C++虚函数多态

class C{
public:void x1(){};void x2(){};};C c;
cout << sizeof(c) <<"\n";1字节
class D{
public:void x1(){};void x2(){};virtual void x3(){};//void *vptr看不见的虚函数表指针
};
D d;
cout << sizeof(d) <<"\n";8字节

类A中,定义了一个虚函数,编译器就会生成一个看不见的成员变量(虚函数表指针)占用一定字节。类中至少存在一个虚函数时,编译时,就会为这个类生成一个虚函数表vtbl,经过编译、链接,类和虚函数表都会保存到可执行文件中,执行时会被装载到内存中。

在编译期间,带有虚函数的类会在编译期间安插一个赋值语句(看不到)

vptr=&A::vftable;

这样就使vptr指向vtbl。

class A{
public://void *vptr看不见的虚函数表指针void func1(){};void func2(){};virtual void vfunc1(){};virtual void vfunc2(){};virtual ~A(){};
private://vptr=&A::vftable;int m_a;int m_b;
};A a;
cout << sizeof(a) <<"\n";16字节    //虚函数一共8字节,m_a和m_b一共8字节

image-20250620213428990

多态必须存在虚函数,没有虚函数绝不可能存在多态

判断有没有多态:从代码实现上来看,查看调用调用路线是不是利用从vptr找到vtbl,然后通过查询vtbl来找到虚函数表的入口地址并去执行虚函数,如果是这个流程,则就是多态,否则就不是多态。

多态发生的核心条件(必须同时满足):

  1. 通过基类指针或基类引用调用函数
  2. 调用的是虚函数
  • 调用形式:指针->虚函数引用.虚函数
class Animal {
public:virtual void speak() { cout << "Animal sound" << endl; }
};class Cat : public Animal {
public:void speak() override { cout << "Meow" << endl; }
};// 场景1:通过基类指针调用虚函数
Animal* animal = new Cat();
animal->speak();  // ✔️ 多态:输出"Meow"// 场景2:通过基类引用调用虚函数
Cat kitty;
Animal& ref = kitty;
ref.speak();      // ✔️ 多态:输出"Meow"
案例:
class Base
{
public:virtual void myvirfunc() {}
};
Base* pa = new Base();
pa->myvirfunc();//多态Base base;
base.myvirfunc();//不是多态Base* ybase = &base;
ybase->myvirfunc();//多态

1.程序中存在继承关系,且父类至少有一个虚函数,但子类不强制重写虚函数(除非是纯虚函数 = 0),要触发运行时多态(动态多态),派生类必须重写基类的虚函数
2.必须通过父类指针或父类引用指向子类对象,才能触发多态。
3.当通过父类指针/引用调用被重写的虚函数时,才会表现出多态行为(动态绑定)。

image-20250623091821551

// 基类(父类)必须包含虚函数
class Base {
public:virtual void myvirfunc() {} // 虚函数声明(virtual 关键字)
};
// 派生类非必须重写普通虚函数(基类已有默认实现)。仅当基类声明为纯虚函数(virtual void func() = 0;)时,派生类必须重写
class Derive : public Base {
public:virtual void myvirfunc() {} // 重写虚函数(virtual 可省略,但建议保留)//C++11 后可用 override 关键字显式标记重写(如 void myvirfunc() override {})
};
//父类指针指向子类对象
Derive derive;
Base* pbase = &derive;
pbase->myvirfunc(); //Derive::myvirfunc()
//或者
Base* pbase2 = new Derive(); //释放内存请自行释放,在这里没演示
pbase2->myvirfunc(); //Derive::myvirfunc()
//父类引用绑定(指向)子类对象
Derive derive2;
Base& yinbase = derive2;
yinbase.myvirfunc(); //Derive::myvirfunc()
虚析构函数
class Base {
public:virtual ~Base() {} // 必须声明为虚析构函数!
};class Derive : public Base {
public://~Derived() {}       // 自动成为虚函数,即使不写virtual~Derive() override {} // 但建议显式使用 override(C++11)
};Base* pb = new Derive();
delete pb; // 正确调用 Derive::~Derive() → Base::~Base()
  • 若不声明虚析构函数delete pb 仅调用 Base::~Base(),导致派生类资源泄漏!
  1. 虚函数表的共享机制

    • 同一类的所有对象共享同一个 vtbl(节省内存)
    • vptr 在对象构造时被初始化指向该类的 vtbl
  2. 多继承下的 vptr

    class Derived : public Base1, public Base2 {virtual void new_func() {} 
    };
    
    • 派生类会包含多个 vptr(每个基类一个)
    • vtbl 可能包含多个子表(Thunk技术处理指针偏移)
    class Base1 {
    public:virtual void func1() { cout << "Base1::func1\n"; }virtual ~Base1() {}
    };class Base2 {
    public:virtual void func2() { cout << "Base2::func2\n"; }virtual ~Base2() {}
    };class Derived : public Base1, public Base2 {
    public:virtual void new_func() { cout << "Derived::new_func\n"; }virtual void func1() override { cout << "Derived::func1\n"; }virtual ~Derived() {}
    };
    
    1. Derived类的Base1虚函数表
    索引 | 函数类型           | 实际函数地址
    -----|--------------------|-----------------
    0    | 析构函数           | Derived::~Derived
    1    | func1()            | Derived::func1
    2    | new_func()         | Derived::new_func
    
    2. Derived类的Base2虚函数表
    索引 | 函数类型           | 实际函数地址
    -----|--------------------|-----------------
    0    | 析构函数           | Thunk to Derived::~Derived
    1    | func2()            | Base2::func2
    
    1. 每个有虚函数的基类都会有自己的vptr
    2. Derived类包含两个vptr:一个来自Base1,一个来自Base2
    3. 派生类新增的虚函数会添加到第一个基类的虚函数表中

总结:多态性的核心逻辑

步骤关键操作作用
必要条件基类声明虚函数,派生类重写建立动态绑定基础
桥梁搭建基类指针/引用指向派生类对象提供统一接口
多态触发通过基类接口调用虚函数运行时动态解析实际函数地址
底层支持虚函数表(vtable)+ 虚表指针(vptr)C++ 实现动态绑定的核心机制

📌 核心结论:多态性 = 虚函数重写 + 基类访问派生类对象 + 通过基类接口调用函数

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/86267.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/86267.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

新编辑器编写指南--给自己的备忘

欢迎使用Markdown编辑器 你好&#xff01; 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章&#xff0c;了解一下Markdown的基本语法知识。 新的改变 我们对Markdown编辑器进行了一些功能拓展与语法支持&#x…

目标检测neck算法之MPCA和FSA的源码实现

目标检测neck算法之MPCA和FSA的源码实现 使用BIBM2024 Spatial-Frequency Dual Domain Attention Network For Medical Image Segmentation的Frequency-Spatial Attention和Multi-scale Progressive Channel Attention改进neck. 接下来&#xff0c;我将讲解它的源码操作的实现…

MyBatis-Plus的3.5.7和PageHelper的那个版本对应

MyBatis-Plus的3.5.7和PageHelper的那个版本对应 根据你的知识库中提到的信息&#xff1a; MyBatis-Plus 3.5.7 使用的是 JSqlParser 4.6 版本。PageHelper 若使用了不同版本的 JSqlParser&#xff08;如 4.7&#xff09;&#xff0c;会导致冲突。 ✅ 推荐对应关系 为了保证…

Apifox 6 月产品更新|支持 AI 能力、交互优化、在线文档新增 SEO 设置、gRPC 项目支持前/后置操作

在 2025 年的 API 开发领域&#xff0c;Apifox 作为一款集 API 设计、调试、Mock 和测试于一体的协作平台&#xff0c;已成为开发者的“得力助手”。然而&#xff0c;随着业务需求的不断增长&#xff0c;开发者对工具的效率和功能提出了更高的要求。6 月份&#xff0c;Apifox 推…

Acrobat JavaScript 从浏览器到 PDF 环境的转换

目录 什么是 JavaScript?JavaScript 核心语言与 Acrobat 特定 API学习 JavaScript 核心语言的挑战浏览器与 Acrobat JavaScript 的关键差异在 Acrobat 中运行 JavaScript 代码替代浏览器特定函数的方法后续学习建议什么是 JavaScript? JavaScript 最初于 1995 年作为 Netsca…

OpenCV CUDA模块设备层-----指数运算函数exp()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 OpenCV 的 CUDA 设备端数学函数 中的一个内联函数&#xff0c;用于在 GPU 上对 uchar1 类型&#xff08;单通道图像像素&#xff09;执行指数运算…

C++面向对象5——C++关键字、构造函数与拷贝构造函数

this关键字 C关键字this的深度解析 1. this指针的本质 在C中&#xff0c;this是一个特殊的隐式指针&#xff0c;它存在于每个非静态成员函数内部&#xff0c;指向调用该函数的当前对象。其类型为&#xff1a; 对于非const成员函数&#xff1a;ClassName* const&#xff08;…

人工智能专业:探索未来的智慧前沿

亲戚家的小孩刚高考完&#xff0c;问我人工智能专业是学什么、做什么的。趁机就写一篇吧&#xff01; 人工智能专业&#xff1a;探索未来的智慧前沿 人工智能&#xff08;Artificial Intelligence&#xff0c;简称AI&#xff09;无疑是当今最热门、最具颠覆性的技术之一。它正…

618风控战升级,瑞数信息“动态安全+AI”利剑出鞘

每年的618电商促销季&#xff0c;都是各大电商平台和商家的兵家必争之地。数以亿计的消费者涌入线上平台&#xff0c;期待已久的优惠券、秒杀商品如潮水般涌现&#xff0c;海量交易在瞬间达成&#xff0c;无疑是一场商业狂欢。 然而&#xff0c;在这场狂欢背后&#xff0c;自动…

神经网络的架构

神经网络中的基本术语 以上图为例&#xff0c;相关的术语描述如下&#xff1a; 最左边的称为输⼊层&#xff0c;其中的神经元称为输⼊神经元&#xff1b;最右边的&#xff0c;即输出层包含有输出神经元&#xff1b;本例中的输出神经元只有一个&#xff1b;中间层&#xff0c;既…

安全生产监测预警系统:构筑智能化的安全防线

安全生产监测预警系统是工业安全管理的核心工具&#xff0c;它利用物联网、大数据、人工智能等技术&#xff0c;实现对生产环境、设备运行和人员行为的全方位监测&#xff0c;确保风险早发现、早预警、早处置。其核心功能涵盖实时监测、智能预警、应急处置、数据分析与优化等多…

Java练习题精选6-10

Java练习题精选6-10 一、第六题二、第七题第八题第九题第十题 一、第六题 如何将两个变量的值进行交换&#xff1f;假设变量a1&#xff0c;b2。 public class Main {public static void main(String[] args) {int a 1;int b 2;int tmp;System.out.println("交换前a&qu…

【GESP】C++四级考试大纲知识点梳理, (2) 结构体和二维数组

GESP C四级官方考试大纲中&#xff0c;共有11条考点&#xff0c;本文针对第2条考点进行分析介绍。 &#xff08;2&#xff09;掌握 C结构体、二维及多维数组的基本概念及使用 四级其他考点回顾&#xff1a; 【GESP】C四级考试大纲知识点梳理, (1) 指针 全文详见&#xff1a;【G…

自动化测试--App自动化之项目实战脚本编写及封装流程

1.App测试范围 app自动化测试主要核心测试手机程序 测试方面&#xff1a; 功能测试 安装卸载测试 升级测试 兼容性测试 网络切换&#xff0c;中断测试 横竖屏切换 健壮性 2.测试环境的搭建 需要配置的环境 java jdk Java的环境 Android sdk 安卓环境 python环境…

【Unity】什么是前向渲染、延迟渲染、单通道渲染、多通道渲染?

好的&#xff0c;我们来深入剖析这些核心渲染概念&#xff0c;理解它们的原理、优缺点以及在Unity&#xff08;特别是URP&#xff09;中的应用。 核心概念&#xff1a;渲染路径 (Rendering Path) 渲染路径决定了光照和着色在场景中是如何计算和应用的。它定义了物体被绘制到屏…

OpenCV CUDA模块设备层-----GPU上执行线程安全的 “原子取最大值” 操作函数

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 这是一个 OpenCV 的 CUDA 模块&#xff08;cv::cudev&#xff09; 中封装的原子操作函数&#xff0c;用于在 GPU 上执行线程安全的 “原子取最大…

【nRF52832】【环境搭建 1】【ubuntu下搭建nRF52832开发环境】

本文讲述如何在 ubuntu 22.04 下开发 nRF52832. host 环境说明: $ uname -a Linux leo 6.8.0-60-generic #63~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Tue Apr 22 19:00:15 UTC 2 x86_64 x86_64 x86_64 GNU/Linux1. 安装软件 sudo apt install gcc-arm-none-eabisudo apt-get i…

【Nginx】403 Forbidden错误

当 Nginx 代理配置出现 403 Forbidden 错误时&#xff0c;通常是由于权限或配置问题导致。以下是常见原因和解决方案&#xff1a; 常见原因及解决方法 1. 后端服务器拒绝访问 原因&#xff1a;后端 HTTPS 服务配置了 IP 白名单或访问控制解决&#xff1a; 检查后端服务器&…

详解 `pip install -e .` 命令【PythonLinux】

详解 pip install -e . 命令 pip install -e . 是 Python 开发中一个非常有用的命令&#xff0c;用于以"可编辑"或"开发"模式安装当前目录中的 Python 包。 命令分解 pip: Python 的包管理工具install: pip 的子命令&#xff0c;用于安装包-e: 是 --edi…

将VSCode的配置迁移到Cursor

目录 方式一&#xff1a;一键导入配置 1.适用场景 2.导入前的配置 3.导入步骤 4.查看导入效果 5.原理 6.注意 方式二&#xff1a;手动迁移配置文件 1.使用场景 2.导入步骤 3.注意 方式一&#xff1a;一键导入配置 1.适用场景 VSCode和Cursor这两个编辑器&#xff0…