当前位置: 首页 > news >正文

虚函数的讲解

文章目录

  • 虚函数的声明与定义
  • 代码演示
    • 基类Person
    • 派生类Man
    • 派生类Woman
  • 测试代码
  • 动态绑定
  • 静态绑定
  • 访问私有虚函数
  • 总结一下通过成员函数指针调用函数的方式

虚函数的声明与定义

虚函数存在于C++的类、结构体等中,不能存在于全局函数中,只能作为成员函数存在。

代码演示

基类Person

Person.h

#pragma once
//定义一个Person类
class Person
{
public:virtual void speak();//声明一个虚成员函数(简称虚函数)virtual void eat();//声明一个虚成员函数(简称虚函数)void walk();//声明一个普通成员函数
};

Person.cpp

#include "Person.h"
#include <iostream>void Person::speak()//虚函数的定义,前面不能加vitrual,否则编译不过
{std::cout << "Person::speak" << std::endl;
}void Person::eat()//虚函数的定义,前面不能加vitrual,否则编译不过
{std::cout << "Person::eat" << std::endl;
}void Person::walk()//普通函数的定义
{std::cout << "Person::walk" << std::endl;
}

派生类Man

Man .h

#pragma once
#include "Person.h"
//定义一个类Man并继承Person类
class Man :public Person
{//这个函数是重写了基类中的虚函数,此时这个函数也是虚函数,前面的virtual可以省略。void speak() override;//override 关键字只能用于虚函数,明确表明要重写基类的虚函数//virtual void speak() override;//等价于上面
};

Man.cpp

#include "Man.h"
#include <iostream>void Man::speak()
{std::cout << "Man::speak" << std::endl;
}

派生类Woman

Woman.h

#pragma once
#include "Person.h"
//定义一个类Woman并继承Person类
class Woman :public Person
{void speak() override;
};

Woman.cpp

#include "Woman.h"
#include <iostream>
void Woman::speak()
{std::cout << "Woman::speak" << std::endl;
}

在这里插入图片描述
含有虚函数的类,在编译期间会生成一张虚函数表及虚表指针。虚函数表其实是一个指针数组,里面的元素是虚函数地址。而续表指针指向虚函数表的首地址。也可以这么认为编译器给含有虚函数的类,自动增加了一个 const void* pvtr 的指针成员变量。和一个静态的 static const void* vtable[]的指针数组;也就是说同一个类的虚函数表是共享的,同一个类下不同对象的虚表指针指向的同一个虚函数表。
在这里插入图片描述

测试代码

#include "Person.h"
#include "Man.h"
#include "Woman.h"//test函数的声明
void test(Person* person);
void test(Person& person);int main(int argc, char* argv[])
{Person person;test(&person);//传地址//test(person);Man man;test(&man);//传地址//test(man);Woman woman1;test(&woman1);//传地址Woman woman2;test(&woman2);//传地址return 0;
}//test函数的实现,体现了多态
void test(Person* person)//指针传递
{person->speak();//person->eat();//person->walk();
}void test(Person& p)//引用传递
{p.speak();p.eat();p.walk();
}

打印结果:
在这里插入图片描述

动态绑定

虚函数的好处就是体现了C++中多态。即当基类类型的指针或引用 指向子类的对象时,在调用虚函数时,实际调用的虚函数是子类对象中的虚函数。
就像上面的test(Person* person)函数,编译器在编译期间不确定函数内实际调用哪个类中的speak函数,需要在实际代码执行时,才能确定,指针 person 到底指向的实际对象是哪个类的,从而实现了动态绑定,即多态性。

静态绑定

所有的类中的非虚成员函数都是静态绑定的。即在编译期间就确定了实际调用哪个函数。普通成员函数的实际调用者,是根据代码里的调用者的类型来判断的,即在编译期就能确定,无法体现多态性。

注意:即使派生类没有重写基类中的虚函数,也没有自己特有的虚函数。那么派生类就会继承父类中的虚函数,即派生类也拥有虚函数表及虚表指针,只是自己的虚函数表中的虚函数都是父类的虚函数,除非自己重写过,虚函数表中的基类虚函数地址会被替换为自己重写后的。

访问私有虚函数

我们把上方的代码修改一下:

class Person
{
private:virtual void speak(){cout << "Person::speak" << endl;}
public:virtual void eat(){cout << "Person::eat" << endl;}
}class Woman : public Person
{
public:void eat(){cout << "Woman::eat" << endl;}
};
int main(int argc, char *argv[])
{Person person;//person.speak();//编译报错,无法访问私有属性成员Woman woman;//woman.speak();//编译报错,同上//void (Person:: *fun1)(void) = &Person::speak;//编译报错,右值报错,无法通过&Person::speak获取函数地址。还是因为私有属性问题return 0;
}

我们想要在类的定义外部访问speak函数。通过常规的手段是访问不到的。

int main(int argc, char *argv[])
{typedef void (*Fun)(void);//给函数指针类型取别名Woman woman;Fun pfun1 = (Fun)*((int*)*(int*)&woman);Fun2 pfun2 = (Fun)*((int*)*(int*)&woman + 1);//此时pfun2 将不再是一个被类作用域限制的成员函数的指针了,而相当于一个全局函数的指针。pfun1();//输出结果:Person::speakpfun2();//输出结果:Woman::eatreturn 0;
}

分析一下:上面的pfun1 和 pfun2 函数指针,在执行时的输出结果。首先要明白一点,含有虚函数的类,在编译时,创建的虚表指针变量会优化为类中第一个成员属性,那么创建对象时,虚表指针所占的内存地址和对象的地址是相同的。类似于 数组首元素的地址和数组名的关系。
在这里插入图片描述
上方的图中的:有两处的地址为何可以转为(int )即int型指针,因为地址值就是整型的。虚表指针vptr的值是地址,虚函数表元素也是地址。
又因为指针的类型和指针所指向的地址存储的内容类型是关联的。另一处是转为(Fun)即函数指针。也是一个道理,函数指针指向的地址(即函数地址)存储的是真实的函数。
(int
)(int)&woman //指针指向虚函数表中第一个元素
(int*)(int)&woman + 1 //指针指向虚函数表中第二个元素
注意:这种方式能够获取私有虚函数的地址,不能获取私有普通成员函数的地址。这也是虚函数的特殊实现原理优势的。
而且这种方式最后获取的虚函数指针,不用再使用对象.* 或对象.->的动态方式来访问。直接 虚函数指针名(参数);来调用函数。

总结一下通过成员函数指针调用函数的方式

void (Woman:: *fun1)(void) = &Woman ::speak;//访问公共属性的成员普通函数或成员虚函数
Woman woman;
(woman.*fun1)();//调用speak函数
Woman & woman1 = &woman;
(woman.->fun1)();//调用speak函数typedef void (*Fun)(void);
Fun fun = (Fun)*((int*)*(int*)&woman);//访问公共成员虚函数或者私有成员函数
fun();//调用speak函数

关于函数地址的相关博客类中成员函数及普通函数地址获取方式

相关文章:

虚函数的讲解

文章目录 虚函数的声明与定义代码演示基类Person派生类Man派生类Woman 测试代码动态绑定静态绑定访问私有虚函数总结一下通过成员函数指针调用函数的方式 虚函数的声明与定义 虚函数存在于C的类、结构体等中&#xff0c;不能存在于全局函数中&#xff0c;只能作为成员函数存在…...

Java强软弱虚引用

面试&#xff1a; 1.强引用&#xff0c;软引用&#xff0c;弱引用&#xff0c;虚引用分别是什么&#xff1f; 2.软引用和弱引用适用的场景&#xff1f; 3.你知道弱引用的话&#xff0c;能谈谈WeakHashMap吗&#xff1f; 目录 一、Java引用 1、强引用&#xff08;默认支持模式…...

QCharView使用

QCharView概念:title、系列、图标Chart、视图 说明: 需要添加Qt组件charts 在使用QChart或者QChartView之前需要添加宏定义QT_CHARTS_USE_NAMESPACE &#xff08;其实是使用了命名空间&#xff09;&#xff0c;不然不能识别QChart或者QChartView 3.在添加宏定义QT_CHARTS_USE_N…...

华为hcia之ipv6实验手册

R3: dhcp enable ipv6 dhcpv6 pool test address prefix 2000:23::/64 excluded-address 2000:23::2 dns-server 2000:23::2 interface GigabitEthernet0/0/0 ipv6 enable ipv6 address 2000:12::2/64 ipv6 address auto link-local undo ipv6 nd ra halt //无状态配置 inter…...

算法设计与分析-图算法小结BFS/DFS/Topologic/Dijkstra/Floyd/最大流

图 注:CSDN貌似不支持较长公式&#xff0c;可以复制到Markdown编辑器查看 图的表示 邻接矩阵 空间复杂度 Θ ( V 2 ) Θ(V^2) Θ(V2)邻接链表 空间复杂度 Θ ( V E ) Θ(VE) Θ(VE) BFS 邻接链表 时间复杂度 Θ ( V E ) Θ(VE) Θ(VE) void BFS(Graph G, int v) {//…...

CentOS 8 安装指定版本ansible

背景&#xff1a;想要练习ansible使用&#xff0c;用于面试&#xff0c;结果使用centos 8 的yum安装失败&#xff0c;提示版本不兼容&#xff08;指的是python版本&#xff09;&#xff0c;故而使用python来安装指定版本的ansible&#xff0c;特此记录 环境&#xff1a;win11虚…...

策略模式(及案例)

策略模式 1.策略接口 定义一组算法或操作的通用接口&#xff0c;通常是一个抽象类或接口。该接口声明了策略类所必须实现的方法。 示例&#xff1a; class Strategy {doOperation() {} }2.具体策略 实现策略接口&#xff0c;提供具体的算法实现。每个具体策略类负责处理一…...

苹果CMS超级播放器专业版无授权全开源,附带安装教程

源码介绍 超级播放器专业版v1.0.8&#xff0c;内置六大主流播放器&#xff0c;支持各种格式的视频播放&#xff0c;支持主要功能在每一个播放器内核中都相同效果。 搭建教程 1.不兼容IE浏览器 2.php版本推荐7.4 支持7.1~7.4 3.框架引入不支持同时引入多个播放器 json对接教…...

项目记录:利用Redis实现缓存以提升查询效率

一、概述 当我们查询所有数据时&#xff0c;如果缓存中没有&#xff0c;则去数据库查询&#xff0c;如果有&#xff0c;直接查缓存的数据就行。注意定期更新缓存数据。 二、主体代码 private static final String ROOM_SCHEDULES_HASH "RoomSchedules";Overridepu…...

腾讯云16核32G28M轻量服务器CPU流量性能测评

腾讯云轻量16核32G28M服务器28M公网带宽下载速度峰值可达3584KB/s&#xff0c;折合3.5M/秒&#xff0c;系统盘为380GB SSD盘&#xff0c;6000GB月流量&#xff0c;折合每天200GB流量。腾讯云百科txybk.com来详细说下腾讯云轻量应用服务器16核32G28M配置性能、CPU主频型号、公网…...

【并发设计模式】聊聊等待唤醒机制的规范实现

在多线程编程中&#xff0c;其实就是分工、协作、互斥。在很多场景中&#xff0c;比如A执行的过程中需要同步等待另外一个线程处理的结果&#xff0c;这种方式下&#xff0c;就是一种等待唤醒的机制。本篇我们来讲述等待唤醒机制的三种实现&#xff0c;以及对应的应用场景。 G…...

CentOS:docker同一容器间通信

docker同一容器中不同服务以别名访问 1、创建bridge网络 docker network create testnet 2、查看Docker网络 docker network ls 3、运行容器连接到testnet网络 使用方法&#xff1a;docker run -it --name <容器名> —network --network-alias <网络别名> <…...

数据治理:释放数据价值的关键

随着数字化时代的到来&#xff0c;数据已成为组织和企业最重要的资产之一。然而&#xff0c;数据的快速增长和复杂性也给数据管理带来了巨大的挑战。为了确保数据的质量、安全性和合规性&#xff0c;数据治理已成为组织和企业必须面对的重要问题。数据治理是数据要素市场建设的…...

新手快速上手掌握基础排序<一>

听说看到日落金山的人&#xff0c;接下来的日子会顺顺利利&#xff0c;万事胜意&#xff0c;生活明朗-----------林辞忧 引言 从基础的两数交换排序&#xff0c;三四个数排序输出&#xff0c;到学习入门级的排序方法&#xff0c;如冒泡法&#xff0c;选择法&#xff0c;再学…...

2023年03月21日_chatgpt宕机事件的简单回顾

你能想象吗 ChatGPT挂了 昨天半夜呢 来自全球各地的用户纷纷发现 ChatGPT的网站弹出了报错警告的信息 然后立即就无法使用了 即使是有特权的plus账户也未能幸免 一时之间呢 chatgptdown的话题在Twitter刷屏 不少重度的用户表示很着急 有的用户说呢没了ChatGPT 这工作…...

RK3568测试tdd

RK3568测试tdd 一、门禁取包二、烧录三、跑tdd用例四、查看结果参考资料 一、门禁取包 右键复制链接&#xff0c;粘贴下载&#xff1b;解压到文件夹&#xff1b; 二、烧录 双击\windows\RKDevTool.exe打开烧写工具&#xff0c;工具界面击烧写步骤如图所示&#xff1a; 推荐…...

机器学习系列13:通过随机森林获取特征重要性

我们已经知道通过 L1 正则化和 SBS 算法可以用来做特征选择。 我们还可以通过随机森林从数据集中选择相关的特征。随机森林里面包含了多棵决策树&#xff0c;我们可以通过计算特征在每棵决策树决策过程中所产生的的信息增益平均值来衡量该特征的重要性。 你可能需要参考&…...

flink中值得监控的几个指标

背景 为了维持flink的正常运行&#xff0c;对flink的日常监控就变得很重要&#xff0c;本文我们就来看一下flink中要监控的几个重要的指标 重要的监控指标 1.算子的处理速度的指标&#xff1a;numRecordsInPerSecond/numRecordsOutPerSecond,这有助于你了解到算子的是否正在…...

最优化方法Python计算:无约束优化应用——逻辑分类模型

逻辑回归模型更多地用于如下例所示判断或分类场景。 例1 某银行的贷款用户数据如下表&#xff1a; 欠款&#xff08;元&#xff09;收入&#xff08;元&#xff09;是否逾期17000800Yes220002500No350003000Yes440004000No520003800No 显然&#xff0c;客户是否逾期&#xff…...

springboot定时执行某个任务

springboot定时执行某个任务 要定时执行的方法加上Schedule注解 括号内跟 cron表达式 “ 30 15 10 * * &#xff1f;” 代表秒 分 时 日 月 周几 启动类上加上EnableScheduling 注释...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序

一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...

MySQL账号权限管理指南:安全创建账户与精细授权技巧

在MySQL数据库管理中&#xff0c;合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号&#xff1f; 最小权限原则&#xf…...

用机器学习破解新能源领域的“弃风”难题

音乐发烧友深有体会&#xff0c;玩音乐的本质就是玩电网。火电声音偏暖&#xff0c;水电偏冷&#xff0c;风电偏空旷。至于太阳能发的电&#xff0c;则略显朦胧和单薄。 不知你是否有感觉&#xff0c;近两年家里的音响声音越来越冷&#xff0c;听起来越来越单薄&#xff1f; —…...

MySQL 8.0 事务全面讲解

以下是一个结合两次回答的 MySQL 8.0 事务全面讲解&#xff0c;涵盖了事务的核心概念、操作示例、失败回滚、隔离级别、事务性 DDL 和 XA 事务等内容&#xff0c;并修正了查看隔离级别的命令。 MySQL 8.0 事务全面讲解 一、事务的核心概念&#xff08;ACID&#xff09; 事务是…...

逻辑回归暴力训练预测金融欺诈

简述 「使用逻辑回归暴力预测金融欺诈&#xff0c;并不断增加特征维度持续测试」的做法&#xff0c;体现了一种逐步建模与迭代验证的实验思路&#xff0c;在金融欺诈检测中非常有价值&#xff0c;本文作为一篇回顾性记录了早年间公司给某行做反欺诈预测用到的技术和思路。百度…...

day36-多路IO复用

一、基本概念 &#xff08;服务器多客户端模型&#xff09; 定义&#xff1a;单线程或单进程同时监测若干个文件描述符是否可以执行IO操作的能力 作用&#xff1a;应用程序通常需要处理来自多条事件流中的事件&#xff0c;比如我现在用的电脑&#xff0c;需要同时处理键盘鼠标…...

Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement

Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement 1. LAB环境2. L2公告策略2.1 部署Death Star2.2 访问服务2.3 部署L2公告策略2.4 服务宣告 3. 可视化 ARP 流量3.1 部署新服务3.2 准备可视化3.3 再次请求 4. 自动IPAM4.1 IPAM Pool4.2 …...

[USACO23FEB] Bakery S

题目描述 Bessie 开了一家面包店! 在她的面包店里&#xff0c;Bessie 有一个烤箱&#xff0c;可以在 t C t_C tC​ 的时间内生产一块饼干或在 t M t_M tM​ 单位时间内生产一块松糕。 ( 1 ≤ t C , t M ≤ 10 9 ) (1 \le t_C,t_M \le 10^9) (1≤tC​,tM​≤109)。由于空间…...

简单介绍C++中 string与wstring

在C中&#xff0c;string和wstring是两种用于处理不同字符编码的字符串类型&#xff0c;分别基于char和wchar_t字符类型。以下是它们的详细说明和对比&#xff1a; 1. 基础定义 string 类型&#xff1a;std::string 字符类型&#xff1a;char&#xff08;通常为8位&#xff09…...

21-Oracle 23 ai-Automatic SQL Plan Management(SPM)

小伙伴们&#xff0c;有没有迁移数据库完毕后或是突然某一天在同一个实例上同样的SQL&#xff0c; 性能不一样了、业务反馈卡顿、业务超时等各种匪夷所思的现状。 于是SPM定位开始&#xff0c;OCM考试中SPM必考。 其他的AWR、ASH、SQLHC、SQLT、SQL profile等换作下一个话题…...