c++虚函数、静态绑定与动态绑定

news2024/12/31 6:14:48

首先说明,所谓绑定,就是指函数的调用

接下来,我们直接看一段代码来说明问题

class Base
{
public:
    Base(int data=10):m_a(data){}
    void show(){cout<<"Base::show()"<<endl;}
    void show(int){cout<<"Base::show(int)"<<endl;}
protected:
    int m_a;
};

class Derive:public Base
{
public:
    Derive(int data=20):Base(data),m_b(data){}
    void show(){cout<<"Derive:show()"<<endl;}
private:
    int m_b;
};

上述代码中,定义了一个Base类和一个Derive类,并且Derive类继承了Base类,其中

Base类中有一组互为重载关系的成员函数show

Derive类中有一个与Base类中同名的成员函数,因此,Derive::show()与Base::show()、Base::show(int)构成了隐藏关系。

静态绑定

接下来,我们写一段测试代码来说明问题,这段测试代码包括

  • 定义一个基类指针,并指向其子类对象
  • 并使用基类指针调用show成员函数,观察运行结果
  • 查看基类指针的类型和基类指针所指对象的类型
void test()
{
    Derive d(50);
    Base* pb=&d;
    pb->show();
    pb->show(11);

    cout<<"Base size:"<<sizeof(Base)<<endl;
    cout<<"Derive size:"<<sizeof(Derive)<<endl;

    cout<<typeid(pb).name()<<endl;
    cout<<typeid(*pb).name()<<endl;
}

 

通过实验结果可以看到,尽管基类指针(Base* pb)指向的是基类对象,但是通过pb所调用的函数仍旧是基类作用域下的成员函数。

静态绑定就是指在编译期间就确定好了函数的具体实现版本,由于pb的类型是Base*(也被称为静态类型),因此通过pb所调用的成员函数show在编译期间就被确定在Base作用域下的show成员函数

  • 静态绑定适用于非虚函数和静态函数
  • 静态绑定中,函数调用的实现版本在编译期间就已经确定,无法在运行期间改变

为加深理解,我们再写一例

class Base {
public:
    void func() {
        cout << "Base::func()" << endl;
    }
};

class Derived : public Base {
public:
    void func() {
        cout << "Derived::func()" << endl;
    }
};

int main() {
    Base b;
    Derived d;

    b.func(); // 静态绑定,调用 Base::func()
    d.func(); // 静态绑定,调用 Derived::func()

    Base* p = &d;
    p->func(); // 静态绑定,调用 Base::func(),因为 p 的静态类型是 Base*
    return 0;
}

 虚函数

接下来,我们对Base类的代码做一点小小的改动

class Base
{
public:
    Base(int data=10):m_a(data){}
    virtual void show(){cout<<"Base::show()"<<endl;}
    virtual void show(int){cout<<"Base::show(int)"<<endl;}
protected:
    int m_a;
};

在Base类的成员函数前加一个virtual关键字,此时Base的成员函数就被称之为虚函数

一个类里如果定义了虚函数,那么

  • 编译期间,编译器就会为该类产生一个唯一的vftable虚函数表
  • 该vftable虚函数表中主要存储的内容就是RTTI指针和虚函数的地址
  • 当程序运行时,每一张虚函数表都被加载到rodata区.(只读)

注:RTTI(run-time type infomation),即运行时的类型信息

以上Base类的虚函数表(vftable)为

除此以外,如果一个类里定义了虚函数,那么

  • 该类所定义的对象,其运行时,内存中的开始部分,会多存储一个vfptr虚函数指针,指向该类的虚函数表(存储该虚函数表的首地址)
  • 该类所定义的每个对象,都会有一个vfptr指针,但虚函数表只有一张

 

因此,一个类里虚函数的个数,不影响内存的大小(对象内存中只有一个虚函数指针vfptr),影响的是虚函数表的大小 

此外,如果派生类中的某个成员函数和基类中某个成员函数完全相同(包括函数名、函数类型),只有函数体的实现不同,那么该成员函数也将自动被处理为虚函数。

class Base
{
public:
    Base(int data=10):m_a(data){}
    virtual void show(){cout<<"Base::show()"<<endl;}
    virtual void show(int){cout<<"Base::show(int)"<<endl;}
protected:
    int m_a;
};

class Derive:public Base
{
public:
    Derive(int data=20):Base(data),m_b(data){}
    void show(){cout<<"Derive:show()"<<endl;}
private:
    int m_b;
};

void test()
{
    Derive d(50);
    Base* pb=&d;
    pb->show();
    pb->show(11);

    cout<<"Base size:"<<sizeof(Base)<<endl;
    cout<<"Derive size:"<<sizeof(Derive)<<endl;

    cout<<typeid(pb).name()<<endl;
    cout<<typeid(*pb).name()<<endl;
}

 再次观察上述修改后的代码,在测试函数中查看运行结果

可以看到,无论是Base还Derive,其大小都是16B,而不再是4B,其原因就在于,Base类中除了有一个int类型的成员变量外还有一个占8B的vfptr,又根据内存对齐原则,故而Base类的大小就是8(vfptr)+4(int)+4(内存对齐)=16B 

覆盖

接下来,我们将目光转向上述代码的Derive类。

上边说到,由于Derive子类中出现了与Base父类完全相同的成员函数(void show()),因此编译器自动将其声明为虚函数,因此我们知道,编译器也将在编译阶段为Derive生成一张唯一的虚函数表vftable,因此在测试代码中定义子类对象d时,d也将拥有一个虚函数指针vfptr

 动态绑定

接下来,我们再来看测试代码中这行代码的运行结果的差异

    pb->show();

可以看到,在成员函数show没有被声明为virtual之前,该行代码执行的是Base::show(),而当其被声明为虚函数后,执行结果就成为了Derive::show()

这是因为,代码在执行到pb->show()时,如果发现show不是虚函数,就进行静态绑定,如果发现show是虚函数,就进行动态绑定。

所谓动态绑定,实质上是因为其汇编过程为

mov eax dword ptr[pb]
mov ecx dword ptr[eax]
call ecx(虚函数的地址)

第一行汇编代码执行:将指针pb指向的地址(虚函数表的首地址)放到寄存器eax中

第二行汇编代码执行:将eax中的前四个字节的地址(也就是对应show()函数的地址)放到ecx寄存器中

第三行汇编代码执行:执行ecx寄存器中的代码

从上述汇编过程可以看到,由于我们执行的是ecx寄存器中的代码,但是ecx中保存的地址需要等到运行时期才能确定。

这种在程序运行时需要根据对象的实际类型来确定调用哪个方法或函数的机制就叫动态绑定

接下来,我们再来看指针pb和*pb的类型变化

    cout<<typeid(pb).name()<<endl;
    cout<<typeid(*pb).name()<<endl;

 

可以看到,

  • pb的类型:无论是否有虚函数,基类指针pb的类型永远都是Base
  • *pb的类型:
    • 如果Base有虚函数,*pb识别的就是运行时期的类型(RTTI类型)
    • 如果Base没有虚函数,*pb识别的就是编译时期的类型(Base类型)

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

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

相关文章

引流技术-通过文件中增加联系方式并传播

文章目录 前言文档增加联系方式扩散网盘扩散自建网站借力 注意 前言 很多人在找资料的时候可能都遇到过下图情况&#xff1a; 1、文档最后面留一个自己的联系方式&#xff1b; 2、找的一堆文件中都有相同的情况&#xff1b; 3、一段时间全网搜到的很多相同文件也有这个联系方式…

springboot156基于SpringBoot+Vue的常规应急物资管理系统

基于SpringBootVue的常规应急物资管理系统的设计与实现 摘 要 1 ABSTRACT 2 第一章 绪论 3 1.1研究背景 3 1.2研究意义 3 1.3国内外研究现状 4 1.3.1国外研究现状 4 1.3.2国内研究现状 4 1.4研究内容与方法 5 1.4.1研究内容 5 1.4.2研究方法 5 1.5论文的组织结构 5…

3D力导向树插件-3d-force-graph学习002

一、实现效果&#xff1a;节点文字同时展示 节点显示不同颜色节点盒label文字并存节点上添加点击事件 二、利用插件&#xff1a;CSS2DRenderer 提示&#xff1a;以下引入文件均可在安装完3d-force-graph的安装包里找到 三、关键代码 提示&#xff1a;模拟数据可按如下格式填…

年终奖,还得是腾讯。。。

腾讯年终奖 什么是真正的好公司&#xff1f; 一年到头&#xff0c;出不了几次裁员等劳务纠纷的吃瓜新闻。 只有到年底了&#xff0c;才因为年终奖远高于行业水平&#xff0c;实在没法低调了&#xff0c;"被迫"上热搜。 最近网友爆料了腾讯头牌部门的年终奖&#xff1…

GPT用来润色论文\生成完整长篇论文\进行AI绘图,真的太香了!

详情点击公众号&#xff1a;技术科研吧 链接&#xff1a;GPT用来润色论文\生成完整长篇论文\进行AI绘图&#xff0c;真的太香了&#xff01; 一&#xff1a;AI领域最新技术 1.OpenAI新模型-GPT-5 2.谷歌新模型-Gemini Ultra 3.Meta新模型-LLama3 4.科大讯飞-星火认知 5.百…

C# CAD界面-自定义工具栏(二)

运行环境 vs2022 c# cad2016 调试成功 一、引用 acdbmgd.dllacmgd.dllaccoremgd.dllAutodesk.AutoCAD.Interop.Common.dllAutodesk.AutoCAD.Interop.dll using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.T…

CICD注册和使用gitlab-runner常见问题

1、现象 fatal: unable to access https://github.com/homebrew/brew/: 2、解决 git config --global --unset http.proxy git config --global --unset https.proxy 查看gitlab-runner是否成功&#xff1a; userusers-MacBook-Pro ~ % gitlab-runner -h 查看gitlab-run…

C# .Net学习笔记—— 异步和多线程(异常处理)

一、异常处理 1、下面for循环20个线程&#xff0c;到11&#xff0c;12号的时候执行失败&#xff0c;这里我也用了try catch来捕获异常。 private void button11_Click(object sender, EventArgs e){TaskFactory taskFactory new TaskFactory();List<Task> taskList ne…

c语言:贪吃蛇的实现

目录 贪吃蛇实现的技术前提&#xff1a; Win32 API介绍 控制台程序&#xff08;console&#xff09; 控制台屏幕上的坐标 GetStdHandle GetConsoleCursorInfo CONSOLE_CURSOR_INFO SetConsoleCursorInfo SetConsoleCursorPosition GetAsyncKeyState 宽字符的打印 …

企业级大数据安全架构(九)FreeIPA管理员密码忘记后如何修改

作者&#xff1a;楼高 1重置Directory Server管理员密码 1.1停止directory server服务 [rootipa schema]# start-dirsrv HDP-HADOOP 如果你不知道你的实例名&#xff0c;可以通过如下方式获取 1.2生成一个新的HASH密码 停止服务后使用pwdhash命令生成一个新的HASH密码 [r…

计算机毕业设计 | springboot 高校新生报到系统(附源码)

1&#xff0c;绪论 1.1 开发背景 学校新生报到仅仅靠原始的手工管理&#xff0c;面对大量的新生信息&#xff0c;无法有效率地将其中的重要部分提取出来&#xff0c;并做出相应的判断和处理。学校的决策只能依据报表数据&#xff0c;在浪费大量人力、物力的同时无法做到实时监…

WorkPlus构建安全高效的内网通讯平台,助力企业内部协作

在现代企业中&#xff0c;高效的内部沟通和协作是团队成功的关键。而内网通讯软件成为了实现内部沟通和协作的首选工具。作为一款领先的内网通讯软件&#xff0c;WorkPlus以其卓越的性能和安全的特性&#xff0c;助力企业打造高效内部沟通与协作的新时代。 为何选择WorkPlus作为…

【51单片机】开发板和单片机的介绍(2)

前言 大家好吖&#xff0c;欢迎来到 YY 滴单片机系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过单片机的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; YY的《C》专栏YY的《C11》专栏YY的…

Pandoc+Typora文件格式转换

前言 上一期写了一篇名为体验PicGoGitHubjsDelivr搭建免费图床的文章&#xff0c;介绍了如何用Github作为图床&#xff0c;并使用了jsDelivr提供的免费CDN加速&#xff0c;配合markdown来写文章本地使用起来还不错&#xff0c;但是在把.md格式文章上传其他平台的时候遇到了问题…

Apache POI与easyExcel:Excel文件导入导出的技术深度分析

在处理Excel文件时&#xff0c;Java开发者经常会面临多种选择&#xff0c;其中Apache POI和easyExcel是两个非常受欢迎的选择。这两个库都提供了强大的Excel文件处理功能&#xff0c;但在性能、内存使用、API设计以及扩展性方面有所不同。本文将深入分析Apache POI和easyExcel在…

【NTN 卫星通信】基于NTN的多3GPP连接应用场景

1 概述 同时聚合两条3GPP接入链路&#xff0c;其中一条为非地面网络&#xff0c;可以提供以下5G业务使能&#xff0c;尤其适用于带宽有限或接入链路不可靠的服务不足地区:   -扩展流动宽频   -超可靠的服务通信 如技术报告38.821所述&#xff0c;若干服务场景(例如在偏远地…

Spring AOP 常见错误(下)

上一章, 我们介绍了 Spring AOP 常遇到的几个问题&#xff0c;通过具体的源码解析&#xff0c;相信你对 Spring AOP 的基本原理已经有所了解了。不过&#xff0c;AOP 毕竟是 Spring 的核心功能之一&#xff0c;不可能规避那零散的两三个问题就一劳永逸了。所以这一章&#xff0…

OJ刷题:《剑指offer》之单身狗1、2 !(巧用位操作符,超详细讲解!)

目录 1.单身狗1 1.1 题目描述 1.2排序寻找 1.3巧用位操作符 2.单身狗2 1.1 题目描述 1.2排序寻找 1.3巧用位操作符 不是每个人都能做自己想做的事&#xff0c;成为自己想成为的人。 克心守己&#xff0c;律己则安&#xff01; 创作不易&#xff0c;宝子们&#xff01;如…

「悬浮捷径SoftCircle」安卓平台的hao123,一键打开万物

罗老师的onestep一步发布之前, 终端的打开形式还拘泥于桌面和负一屏 这种方式够简洁,但缺点明显: 1.入口单一性:只能在app首页和各种扫一扫之间选择和切换 2.操作复杂:入口切换需要频繁的进入退出桌面,步骤过于繁杂 以下是悬浮捷径SoftCircle的解决方式 1.入口的丰富性: 安卓平…

代码随想录算法训练营Day24 | 回溯理论基础、77.组合

回溯理论基础 回溯和递归是相辅相成的&#xff0c;只要有递归就有回溯&#xff08;执行完一次递归就自动回溯到上一层&#xff09; 回溯的效率 回溯不是一个高效的算法&#xff0c;而是一个纯暴力的过程 有些问题没有更好的解法&#xff0c;只能使用暴力搜索&#xff0c;这时…