C++中的虚函数(一)

2023-11-11

虽然很难找到一本不讨论多态性的C++书籍或杂志,但是,大多数这类讨论使多态性和C++虚函数的使用看起来很难。我打算在这篇文章中通过从几个方面和结合一些例子使读者理解在C++中的虚函数实现技术。说明一点,写这篇文章只是想和大家交流学习经验因为本人学识浅薄,难免有一些错误和不足,希望大家批评和指正,在此深表感谢!

一、 基本概念

首先,C++通过虚函数实现多态."无论发送消息的对象属于什么类,它们均发送具有同一形式的消息,对消息的处理方式可能随接手消息的对象而变"的处理方式被称为多态性。"在某个基类上建立起来的类的层次构造中,可以对任何一个派生类的对象中的同名过程进行调用,而被调用的过程提供的处理可以随其所属的类而变。"虚函数首先是一种成员函数,它可以在该类的派生类中被重新定义并被赋予另外一种处理功能。

二、 虚函数的定义与派生类中的重定义

01. class 类名{
02. public:
03. virtual 成员函数说明;
04. }
05.  
06. class 类名:基类名{
07. public:
08. virtual 成员函数说明;
09. }

三、 虚函数在内存中的结构

1.我们先看一个例子:

01. #include "iostream.h"
02. #include "string.h"
03.  
04. class A {
05. public:
06. virtual void fun0() { cout << "A::fun0" << endl; }
07. };
08.  
09.  
10. int main(int argc, char* argv[])
11. {
12. A  a;
13. cout << "Size of A = " << sizeof(a) << endl;
14. return 0;
15. }

结果如下:Size of A = 4

2.如果再添加一个虚函数:virtual void fun1() { cout << "A::fun" << endl;}

得到相同的结果。如果去掉函数前面的virtual修饰符

01. class A {
02. public:
03. void fun0() { cout << "A::fun0" << endl; }
04. };
05.  
06.  
07. int main(int argc, char* argv[])
08. {
09. A  a;
10. cout << "Size of A = " << sizeof(a) << endl;
11. return 0;
12. }

结果如下:Size of A = 1

3.在看下面的结果:

01. class A {
02. public:
03. virtual void fun0() { cout << "A::fun0" << endl; }
04. int a;
05. int b;
06. };
07. int main(int argc, char* argv[])
08. {
09. A  a;
10. cout << "Size of A = " << sizeof(a) << endl;
11. return 0;
12. }

结果如下:Size of A = 12

其实虚函数在内存中结构是这样的:

图一

在window2000下指针在内存中占4个字节,虚函数在一个虚函数表(VTABLE)中保存函数地址。在看下面例子。

01. class A {
02. public:
03. virtual void fun0() { cout << "A::fun0" << endl; }
04. virtual void fun1() { cout << "A::fun1" << endl; }
05. int a;
06. int b;
07. };
08. int main(int argc, char* argv[])
09. {
10. A  a;
11. cout << "Size of A = " << sizeof(a) << endl;
12. return 0;
13. }

结果如下:结果如下:

Size of A = 4

虚函数的内存结构如下,你也可以通过函数指针,先找到虚函数表(VTABLE),然后访问每个函数地址来验证这种结构,在国外网站作者是:Zeeshan Amjad写的"ATL on the Hood中有详细介绍"

图二

4.我们再来看看继承中虚函数的内存结构,先看下面的例子

01. class A {
02. public:
03. virtual void f() { }
04. };
05. class B {
06. public:
07. virtual void f() { }
08. };
09. class C {
10. public:
11. virtual void f() { }
12. };
13. class Drive : public A, public B, public C {
14. };
15. int main() {
16. Drive d;
17. cout << "Size is = " << sizeof(d) << endl;
18. return 0;
19. }

结果如下:Size is = 12 ,相信大家一看下面的结构图就会很清楚,

图三

5.我们再来看看用虚函数实现多态性,先看个例子:

01. class A {
02. public:
03. virtual void f() { cout << "A::f" << endl; }
04. };
05. class B :public A{
06. public:
07. virtual void f() { cout << "B::f" << endl;}
08. };
09. class C :public A {
10. public:
11. virtual void f() { cout << "C::f" << endl;}
12. };
13. class Drive : public C {
14. public:
15. virtual void f() { cout << "D::f" << endl;}
16. };
17.  
18. int main(int argc, char* argv[])
19. {
20. A a;
21. B b;
22. C c;
23. Drive d;
24. a.f();
25. b.f();
26. c.f();
27. d.f();
28. return 0;
29. }
30. 结果:A::f
31. B::f
32. C::f
33. D::f

不用解释,相信大家一看就明白什么道理!注意:多态不是函数重载

6.用虚函数实现动态连接在编译期间,C++编译器根据程序传递给函数的参数或者函数返回类型来决定程序使用那个函数,然后编译器用正确的的函数替换每次启动。这种基于编译器的替换被称为静态连接,他们在程序运行之前执行。另一方面,当程序执行多态性时,替换是在程序执行期进行的,这种运行期间替换被称为动态连接。如下例子:

01. class A{
02. public:
03. virtual void f(){cout < <  "A::f" < <  endl;};
04. };
05.  
06. class B:public A{
07. public:
08. virtual void f(){cout < <  "B::f" < <  endl;};
09. };
10. class C:public A{
11. public:
12. virtual void f(){cout < <  "C::f" < <  endl;};
13. };
14. void test(A *a){
15. a->f();
16. };
17. int main(int argc, char* argv[])
18. {    
19. B *b=new B;
20. C *c=new C;
21. char choice;
22. do{
23. cout< < "type  B for class B,C for class C:"< < endl;
24. cin>>choice;
25. if(choice==''b'')
26. test(b);
27. else if(choice==''c'')
28. test(c);
29. }while(1);
30. cout< < endl< < endl;
31. return 0;
32. }

在上面的例子中,如果把类A,B,C中的virtual修饰符去掉,看看打印的结果,然后再看下面一个例子想想两者的联系。如果把B和C中的virtual修饰符去掉,又会怎样,结果和没有去掉一样。 

7.在基类中调用继承类的函数(如果此函数是虚函数才能如此)

还是先看例子:

01. class A {
02. public:
03. virtual void fun() {
04. cout << "A::fun" << endl;
05. }
06. void show() {
07. fun();
08. }
09. };
10.  
11. class B : public A {
12. public:
13. virtual void fun() {
14. cout << "B::fun" << endl;
15. }
16. };
17.  
18. int main() {
19. A a;
20. a.show();
21.  
22. return 0;
23. }

打印结果:A::fun 

在6中的例子中,test(A *a)其实有一个继承类指针向基类指针隐式转化的过程。可以看出利用虚函数我们可以在基类调用继承类函数。但如果不是虚函数,继承类指针转化为基类指针后只可以调用基类函数。反之,如果基类指针向继承类指针转化的情况怎样,这只能进行显示转化,转化后的继承类指针可以调用基类和继承类指针。如下例子:

01. class A {
02. public:
03. void fun() {
04. cout << "A::fun" << endl;
05. }
06.  
07. };
08. class B : public A {
09. public:
10. void fun() {
11. cout << "B::fun" << endl;
12. }
13. void fun0() {
14. cout << "B::fun0" << endl;
15. }
16. };
17. int main() {
18. A *a=new A;
19. B *b=new B;
20. A *pa;
21. B *pb;
22. pb=static_cast(a); //基类指针向继承类指针进行显示转化
23. pb->fun0();
24. pb->fun();
25. return 0;
26. }

 参考资料:

1.科学出版社 《C++程序设计》

2.Zeeshan Amjad 《ATL on the Hood》



FROM: http://www.vckbase.com/index.php/wv/703

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++中的虚函数(一) 的相关文章

  • ChatGPT生成内容很难脱离标准化,不建议用来写留学文书

    ChatGPT无疑是23年留学届的热门话题 也成为了不少留学生再也离不开的万能工具 从总结文献 润色论文 给教授写email似乎无所不能 各大高校对于学生使用ChatGPT的态度也有所不同 例如 哈佛大学教育代理院长 Anne Harrin
  • Unity游戏编程-——迷宫巡逻兵

    文章目录 游戏设计要求 程序设计要求 基本思路分析 模式基础 架构设计 关键模块 遇到的问题 资源地址 游戏设计要求 创建一个地图和若干巡逻兵 使用动画 每个巡逻兵走一个3 5个边的凸多边型 位置数据是相对地址 即每次确定下一个目标位置 用
  • 字节跳动(飞书)产品测试实习生一面

    下面面试问题的顺序记不清了 所以没按面试官问的顺序写 1 性能测试 2 黑盒和白盒 3 用过飞书吗 知道飞书的产品流程吗 4 谈谈你简历上写的项目 提到购物车功能 仔细讲讲 5 学过软件工程管理 说说整个软件的项目管理流程 6 看有服役的经
  • Linux系统调用指南

    Linux系统调用指南 文章是转载 但是我在后面的案例加了不少注解并debug了 如有疑问 留言交流 其实我也不懂 原文链接 blog packagecloud io https zcfy cc article the definitive
  • QTcpSocket发送数据和自定义数据

    在网络应用中 有时候我们会遇到这样的问题 用TCP不断的接收和发送不同类型的数据 数据大小 格式都不相同 起初看了qt的例子 按照例子写的程序效果相当的不好 尤其是在连续发送大数据的时候 接收端根本无法判断数据是否完整了 也不知道什么时候取
  • 基于VMD-LSTM-IOWA-RBF的碳排放混合预测研究(Matlab代码实现)

    欢迎来到本博客 博主优势 博客内容尽量做到思维缜密 逻辑清晰 为了方便读者 座右铭 行百里者 半于九十 本文目录如下 目录 1 概述 2 运行结果 3 参考文献 4 Matlab代码实现 1 概述 二氧化碳排放力争于2030年前达到峰值 努
  • uni-app checkbox全选的实现

    界面是这样的 需求 点击全选按钮上述全部选中 再次点击全部取消 解决方案 在js的data里面定义一个allCheck data return allCheck false 上面商品的的checkbox都一样的配置
  • window 无法访问docker_无法在windows中访问docker端口

    我在dockerfile中写了脚本来运行我的角度项目 It将创建集装箱 什么时候我正在运行我的容器我无法在浏览器中访问主机和端口 它说连接被拒绝了 我用windows机器 用工具箱运行docker 我的容器 gt 81471a6fbd35
  • php爬虫简单入门

    前些日子有点空闲就做了一个简单的爬虫 爬取了知乎50W条数据 因为知乎有测试流量过大 导致经常有验证码 本人图片验证码没有研究所以每次都是手动输入 有兴趣的小伙伴可以做个自动识别验证码就可以无限采取了 爬虫使用了curl public fu
  • spring嵌套事务,try-catch事务处理

    spring 事务总结 前置条件 表Teacher 别名 A 表Student 别名 B 分别插入 A中启动事务 B中不启动事务 testDemo01调用方法开启事务 testDemo01开启事务 A中insert中开启事务 调用执行 A中

随机推荐

  • 蓝牙协议介绍

    1 广播 ADV Advertising 1 1 BLE 报文结构 BLE引入access address 概念 用来指明接收者身份 概 其中 0x8E89BED6 这个access address 比较特殊 它表示要发给周边所有设备 即广
  • msys2 安装gcc g++ 命令

    pacman S base devel gcc vim
  • 常见设计模式的解析和实现(C++)之九—Decorator模式

    作用 动态地给一个对象添加一些额外的职责 就增加功能来说 Decorator模式相比生成子类更为灵活 UML结构图 抽象基类 1 Component 定义一个对象接口 可以为这个接口动态地添加职责 2 Decorator 维持一个指向Com
  • 优秀的Windows密码抓取工具

    优秀的工具如下 01 Mimikatz 简介 Mimikat是一个法国人写的轻量级调试器 Mimikat可以从内存中提取纯文本密码 hash PIN码和kerberos票证 mimikatz还可以执行哈希传递 票证传递或构建Golden票证
  • ESP8266 hspi的调试

    这一两个礼拜基本上都在爬这个坑 功夫不负有心人 终于搞定了 其实非常简单 以为这个东西有多么的复杂 其实不是这样的 被一些网上博主给误导了 8266端我用的是 ESP8266 NONOS SDK 3 0 examples periphera
  • 使用ANT打包Android应用

    转自 http blog csdn net liuhe688 article details 6679879 大家好 今天来分享一下如何使用ANT打包Android应用 通常我们习惯用eclipse来开发Android程序 它会自动帮我们打
  • 架构妄想:AJAX + REST

    原文地址 http www infoq com cn news 2011 10 ArchitecturalMirages William Vambenepe的最新文章 AJAX REST是最新的架构妄想 让我们回想起了一个具有15年历史的架
  • 粒子滤波器的Matlab实现

    前言 粒子滤波器相较于卡尔曼滤波器或者UKF无迹卡尔曼滤波器而言 可以表达强非线性的变换且无需假设后验分布为高斯分布 在描述多峰分布时具有非常大的优势 粒子滤波器被广泛的应用于机器人系统中 如著名的Gmapping算法便是在粒子滤波器的基础
  • 怎么往阿里云服务器传东西

    https zhidao baidu com question 2075785777388289788 html
  • Unity C# 委托——事件,Action,Func的作用和区别

    参考视频 三分钟彻底搞懂委托 事件 Action Func的作用和区别 哔哩哔哩 bilibili 委托关系图 Delegate 定义两个模板 一个可以传参一个不可以传参 模板 1 public delegate void xxxxx in
  • 复习:最短路径

    一 基本概念 最短路径 在非网图中 最短路径是指两顶点之间经历的边数最少的路径 在网图中 最短路径是指两顶点之间经历的边上权值之和最少的路径 源点 路径上的第一个顶点 终点 路径上最后一个顶点 二 Dijkstra算法 只适用于简单路径 不
  • 最小生成树,Prim算法与Kruskal算法,408方向,思路与实现分析

    最小生成树 Prim算法与Kruskal算法 408方向 思路与实现分析 最小生成树 老生常谈了 生活中也总会有各种各样的问题 在这里 我来带你一起分析一下这个算法的思路与实现的方式吧 在考研中呢 最小生成树虽然是只考我们分析 理解就行 但
  • NestedScrollView嵌套RecyclerView只显示一行的问题

    1 添加属性设置 设置布局管理器 LinearLayoutManager linearLayoutManager new LinearLayoutManager context linearLayoutManager setOrientat
  • node写可选参数接口

    个人网站 紫陌 笔记分享网 想寻找共同学习交流 共同成长的伙伴 请点击 前端学习交流群 今天写项目接口看到接口文档要求带四个参数两个参数必选两个可选 当时在想可选参数要怎么做 毕竟自己也没有写过 然后想了一天终于想出一个感觉不是最佳的方案
  • 极简教学

    目录 一 安装wget 二 安装git 三 安装pip 四 下载ChatGLM2 6B源码 五 安装Anaconda 六 安装pytorch 七 下载模型库 八 最后的准备工作 九 运行程序 一 安装wget 1 删除自带的wget yum
  • CAN总线的EMC设计方案

    一 CAN接口EMC设计概述 Controller Area Network简称为CAN 多用于汽车以及工业控制 用于数据的传输控制 在应用的过程中通讯电缆容易耦合外部的干扰对信号传输造成一定的影响 单板内部的干扰也可能通过电缆形成对外辐射
  • kafka多线程实现消费者实战

    前言 KafkaProducer是线程安全的 但是KafkaConsumer不是线程安全的 同一个KafkaConsumer用在了多个线程中 将会报Kafka Consumer is not safe for multi threaded
  • emplace_back和push_back的区别

    相同点 两者都是向容器内添加数据 不同点 当数据为类的对象时 emplace back相对push back可以避免额外的移动和复制操作 以下代码copy from点击打开链接 include
  • LeetCode-917. 仅仅反转字母

    给你一个字符串 s 根据下述规则反转字符串 所有非英文字母保留在原有位置 所有英文字母 小写或大写 位置反转 返回反转后的 s 示例 1 输入 s ab cd 输出 dc ba 来源 力扣 LeetCode 双指针 双指针是一种解决问题的技
  • C++中的虚函数(一)

    虽然很难找到一本不讨论多态性的C 书籍或杂志 但是 大多数这类讨论使多态性和C 虚函数的使用看起来很难 我打算在这篇文章中通过从几个方面和结合一些例子使读者理解在C 中的虚函数实现技术 说明一点 写这篇文章只是想和大家交流学习经验因为本人学