当前位置: 首页 > 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 注释...

Java EE Servlet之Servlet API详解

文章目录 1. HttpServlet1.1 核心方法 2. HttpServletRequest3. HttpServletResponse 接下来我们来学习 Servlet API 里面的详细情况 1. HttpServlet 写一个 Servlet 代码&#xff0c;都是要继承这个类&#xff0c;重写里面的方法 Servlet 这里的代码&#xff0c;只需要继承…...

neo4j运维管理

管理数据库 概念 Neo4j 5(从v4.0)&#xff0c;可以同时创建和使用多个活动数据库。 DBMS Neo4j是一个数据库管理系统(DBMS)&#xff0c;能够管理多个数据库。DBMS可以管理一个独立的服务器&#xff0c;也可以管理集群中的一组服务器。 实例 Neo4j实例是运行Neo4j服务器代…...

【MYSQL】-函数

&#x1f496;作者&#xff1a;小树苗渴望变成参天大树&#x1f388; &#x1f389;作者宣言&#xff1a;认真写好每一篇博客&#x1f4a4; &#x1f38a;作者gitee:gitee✨ &#x1f49e;作者专栏&#xff1a;C语言,数据结构初阶,Linux,C 动态规划算法&#x1f384; 如 果 你 …...

传统船检已经过时?AR智慧船检来助力!!

想象一下&#xff0c;在茫茫大海中&#xff0c;一艘巨型货轮正缓缓驶过。船上的工程师戴着一副先进的AR眼镜&#xff0c;他们不再需要反复翻阅厚重的手册&#xff0c;一切所需信息都实时显示在眼前。这不是科幻电影的场景&#xff0c;而是智慧船检技术带来的现实变革。那么问题…...

JAVA进化史: JDK11特性及说明

JDK 11&#xff08;Java Development Kit 11&#xff09;是Java平台的一个版本&#xff0c;于2018年9月发布。这个版本引入了一些新特性和改进&#xff0c;以下是其中一些主要特性。 HTTP Client&#xff08;标准化&#xff09; JDK 11引入了一个新的HTTP客户端&#xff0c;用…...

模型 安索夫矩阵

本系列文章 主要是 分享模型&#xff0c;涉及各个领域&#xff0c;重在提升认知。产品市场战略。 1 安索夫矩阵的应用 1.1 江小白的多样化经营策略 使用安索夫矩阵来分析江小白市场战略。具体如下&#xff1a; 根据安索夫矩阵&#xff0c;江小白的现有产品是其白酒产品&…...

性能手机新标杆,一加 Ace 3 发布会定档 1 月 4 日

12 月 27 日&#xff0c;一加宣布将于 1 月 4 日发布新品一加 Ace 3。一加 Ace 系列秉持「产品力优先」理念&#xff0c;从一加 Ace 2、一加 Ace 2V 到一加 Ace 2 Pro&#xff0c;款款都是现象级爆品&#xff0c;得到了广大用户的认可与支持。作为一加 2024 开年之作&#xff0…...

Vue 框架前导:详解 Ajax

Ajax Ajax 是异步的 JavaScript 和 XML。简单来说就是使用 XMLHttpRequest 对象和服务器通信。可以使用 JSON、XML、HTML 和 text 文本格式来发送和接收数据。具有异步的特性&#xff0c;可在不刷新页面的情况下实现和服务器的通信&#xff0c;交换数据或者更新页面 01. 体验 A…...

3分钟快速安装 ClickHouse、配置服务、设置密码和远程登录以及修改数据目录

下面是一个完整的 ClickHouse 安装和配置流程&#xff0c;包括安装 ClickHouse、配置服务、设置密码和远程登录以及修改数据目录。 安装 ClickHouse 安装 YUM 工具包&#xff1a; sudo yum install -y yum-utils添加 ClickHouse YUM 仓库&#xff1a; sudo yum-config-manager…...

PHP8使用PDO对象增删改查MySql数据库

PDO简介 PDO&#xff08;PHP Data Objects&#xff09;是一个PHP扩展&#xff0c;它提供了一个数据库访问层&#xff0c;允许开发人员使用统一的接口访问各种数据库。PDO 提供了一种用于执行查询和获取结果的简单而一致的API。 以下是PDO的一些主要特点&#xff1a; 统一接口…...