虚函数详解

article/2025/9/7 17:00:59

文章目录

  • 一、多态与重载
    • 1、多态的概念
    • 2、重载---编译期多态的体现
    • 3、虚函数---运行期多态的体现
  • 二、虚函数实例
  • 三、虚函数的实现(内存布局)
    • 1、无继承情况
    • 2、单继承情况(无虚函数覆盖)
    • 3、单继承情况(有虚函数覆盖)
    • 4、多重继承情况(无虚函数覆盖)
    • 5、多重继承情况(有虚函数覆盖)
  • 四、虚函数的相关问题
    • 1、构造函数为什么不能定义为虚函数
    • 2、析构函数为什么要定义为虚函数?
    • 3、如何去验证虚函数表的存在

一、多态与重载

1、多态的概念

  面向对象的语言有三大特性:继承、封装、多态。虚函数作为多态的实现方式,重要性毋庸置疑。

  多态意指相同的消息给予不同的对象会引发不同的动作(一个接口,多种方法)。其实更简单地来说,就是“在用父类指针调用函数时,实际调用的是指针指向的实际类型(子类)的成员函数。多态性使得程序调用的函数是在运行时动态确定的,而不是在编译时静态确定的

2、重载—编译期多态的体现

  重载,是指在一个类中的同名不同参数的函数调用,这样的方法调用是在编译期间确定的

3、虚函数—运行期多态的体现

  运行期多态发生的三个条件:继承关系、虚函数覆盖、父类指针或引用指向子类对象

二、虚函数实例

在这里插入图片描述
  在上述例子中,我们首先定义了一个基类base,基类有一个名为vir_func的虚函数,和一个名为func的普通成员函数。而类A,B都是由类base派生的子类,并且都对成员函数进行了重载。然后我们定义三个base类型的指针Base、a、b分别指向类base、A、B。可以看到,当使用这三个指针调用func函数时,调用的都是基类base的函数而使用这三个指针调用虚函数vir_func时,调用的是指针指向的实际类型的函数。最后,我们将指针b做强制类型转换,转换为A类型指针,然后分别调用func和vir_func函数,发现普通函数调用的是类A的函数,而虚函数调用的是类B的函数。

  以上,我们可以得出结论当使用类的指针调用成员函数时,普通函数由指针类型决定,而虚函数由指针指向的实际类型决定

  虚函数的实现过程:通过对象内存中的vptr找到虚函数表vtbl,接着通过vtbl找到对应虚函数的实现区域并进行调用。

三、虚函数的实现(内存布局)

  虚函数表中只存有一个虚函数的指针地址,不存放普通函数或是构造函数的指针地址。只要有虚函数,C++类都会存在这样的一张虚函数表,不管是普通虚函数亦或是纯虚函数,亦或是派生类中隐式声明的这些虚函数都会生成这张虚函数表。

  虚函数表创建的时间:在一个类构造的时候,创建这张虚函数表,而这个虚函数表是供整个类所共有的。虚函数表存储在对象最开始的位置。虚函数表其实就是函数指针的地址。函数调用的时候,通过函数指针所指向的函数来调用函数。

1、无继承情况

#include <iostream>
using namespace std;class Base
{
public:Base(){cout<<"Base construct"<<endl;}virtual void f() {cout<<"Base::f()"<<endl;}virtual void g() {cout<<"Base::g()"<<endl;}virtual void h() {cout<<"Base::h()"<<endl;}virtual ~Base(){}
};int main()
{typedef void (*Fun)();  //定义一个函数指针类型变量类型 FunBase *b = new Base();//虚函数表存储在对象最开始的位置//将对象的首地址输出cout<<"首地址:"<<*(int*)(&b)<<endl;Fun funf = (Fun)(*(int*)*(int*)b);Fun fung = (Fun)(*((int*)*(int*)b+1));//地址内的值 即为函数指针的地址,将函数指针的地址存储在了虚函数表中了Fun funh = (Fun)(*((int *)*(int *)b+2));funf();fung();funh();cout<<(Fun)(*((int*)*(int*)b+4))<<endl; //最后一个位置为0 表明虚函数表结束 +4是因为定义了一个 虚析构函数delete b;return 0;
}

在这里插入图片描述

2、单继承情况(无虚函数覆盖)

  假设有如下所示的一个继承关系:
在这里插入图片描述
  请注意,在这个继承关系中,子类没有重载任何父类的函数。那么,在派生类的实例中,其虚函数表如下所示:
在这里插入图片描述
【Note】:

  • 虚函数按照其声明顺序放于表中

  • 父类的虚函数在子类的虚函数前面

3、单继承情况(有虚函数覆盖)

  覆盖父类的虚函数是很显然的事情,不然,虚函数就变得毫无意义。下面,我们来看一下,如果子类中有虚函数重载了父类的虚函数,会是一个什么样子?假设,我们有下面这样的一个继承关系。
在这里插入图片描述
  为了让大家看到被继承过后的效果,在这个类的设计中,我只覆盖了父类的一个函数:f()。那么,对于派生类的实例,其虚函数表会是下面的一个样子:
在这里插入图片描述
【Note】:

  • 覆盖的f()函数被放到了虚表中原来父类虚函数的位置

  • 没有被覆盖的函数依旧在原来的位置

这样,我们就可以看到对于下面这样的程序,

Base *b = new Derive();
b->f();

由b所指的内存中的虚函数表的f()的位置已经被Derive::f()函数地址所取代,于是在实际调用发生时,是Derive::f()被调用了。这就实现了多态。

4、多重继承情况(无虚函数覆盖)

  下面,再让我们来看看多重继承中的情况,假设有下面这样一个类的继承关系。注意:子类并没有覆盖父类的函数。
在这里插入图片描述
对于子类实例中的虚函数表,是下面这个样子:

在这里插入图片描述
【Note】:

  • 每个父类都有自己的虚表(有几个基类就有几个虚函数表)

  • 子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)。

5、多重继承情况(有虚函数覆盖)

  下面我们再来看看,如果发生虚函数覆盖的情况。下图中,我们在子类中覆盖了父类的f()函数。
在这里插入图片描述
下面是对于子类实例中的虚函数表的图:
在这里插入图片描述
  我们可以看见,三个父类虚函数表中的f()的位置被替换成了子类的函数指针。这样,我们就可以任一静态类型的父类来指向子类,并调用子类的f()了。如:

Derive d;
Base1 *b1 = &d;
Base2 *b2 = &d;
Base3 *b3 = &d;
b1->f(); //Derive::f()
b2->f(); //Derive::f()
b3->f(); //Derive::f()
b1->g(); //Base1::g()
b2->g(); //Base2::g()
b3->g(); //Base3::g()

四、虚函数的相关问题

1、构造函数为什么不能定义为虚函数

  构造函数不能是虚函数。

  首先,我们已经知道虚函数的实现则是通过对象内存中的vptr来实现的。而构造函数是用来实例化一个对象的,通俗来讲就是为对象内存中的值做初始化操作。那么在构造函数完成之前,vptr是没有值的,也就无法通过vptr找到作为虚函数的构造函数所在的代码区。

2、析构函数为什么要定义为虚函数?

  析构函数可以是虚函数且推荐最好设置为虚函数。

class B
{
public:B() { printf("B()\n"); }virtual ~B() { printf("~B()\n"); }
private:int m_b;
};class D : public B
{
public:D() { printf("D()\n"); }~D() { printf("~D()\n"); }
private:int m_d;
};int main()
{B* pB = new D();delete pB;return 0;
}

在这里插入图片描述
  C++中有这样的约束:执行子类构造函数之前一定会执行父类的构造函数;同理,执行子类的析构函数后,一定会执行父类的析构函数,这也是为什么我们一直建议类的析构函数写成虚函数的原因。

3、如何去验证虚函数表的存在

typedef void(*Fun)(void);
// 取类的一个实例
Base b;
Fun pFun = NULL;
// 把&b转成int ,取得虚函数表的地址
cout << "虚函数表地址:" << (int*)(&b) << endl;
// 再次取址就可以得到第一个虚函数的地址了
cout << "虚函数表 — 第一个函数地址:" << (int*)*(int*)(&b) << endl;
pFun = (Fun)*((int*)*(int*)(&b));
pFun();

参考:https://mp.weixin.qq.com/s?__biz=MzIzNjk2NjUxOQ==&mid=2247483655&idx=1&sn=5b29918a121006d14a09e75d2dcb0a8b&chksm=e8ce861fdfb90f09aaa9a5f3c3bbf38b342f73fdfbe37c111c39937e7baa931fc0ac73cf8074#rd


http://chatgpt.dhexx.cn/article/vsvjIy86.shtml

相关文章

Java教程之NIO的基本用法

NIO的基本用法 NIO是New I/O的简称&#xff0c;与旧式基于流的I/O相对&#xff0c;从名字上来看&#xff0c;它表示新的一套I/O标准。它是从JDK1.4中被纳入到JDK中的。 与旧式的IO流相比&#xff0c;NIO是基于Block的&#xff0c;它以块为单位来处理数据&#xff0c;最为重要…

关于vp8,vp8与264比较总结

1 Other Codecs l MSN 使用的video codec “x-rtvc1”,09之前的版本使用的ML20.参考网址&#xff1a; http://www.amsn-project.net/forums/index.php?topic6612.0 l Yahoo messenger 使用GIPS的LSVX codec. l 这两个codecs技术保密性强&#xff0c;找不到有用的信息&#xff…

PCM(脉冲编码调制)、iLBC编解码、opus(声音编码格式)、VP8视频压缩格式、H.264数字视频压缩格式

目录 PCM&#xff08;脉冲编码调制&#xff09; 发展史 工作原理 iLBC编解码 基本介绍 技术优势 Opus&#xff08;声音编码格式&#xff09; 特性 播放 技术细节 VP8视频压缩格式 简介 突破创新 技术分析 H.264数字视频压缩格式 背景介绍 优势 特点 PCM&…

JavaCV音视频开发宝典:录制vp8和vp9编码的webm格式视频,以mp4转webm为例

《JavaCV音视频开发宝典》专栏目录导航 《JavaCV音视频开发宝典》专栏介绍和目录 ​ 前言 由于现代浏览器对webm格式的视频支持较好,如下图: 因此使用webm格式来作为主要的存储和回放视频格式。本章将使用mp4文件转webm为例,来讲一下JavaCV如何录制webm格式视频。 webm…

有关 VP8 的一些帧 Golden AltRef 的说明

---------------------------------------------------------------------------------------------------------------------- 一分钟快速搭建 rtmpd 服务器: https://blog.csdn.net/freeabc/article/details/102880984 软件下载地址: http://www.qiyicc.com/download/rtmpd…

主流编解码器(H.264 AVC, H.265 HEVC, VP8, VP9)比较

主流编解码器&#xff08;H.264 AVC, H.265 HEVC, VP8, VP9&#xff09;比较 本文转自&#xff1a;http://houh-1984.blog.163.com/blog/static/31127834201321995354105/ 概述 H.264(MPEG 4, class 10 )是目前嵌入式和移动设备中采用最多的视频编解码算法标准。目前超过50家…

即时通讯音视频开发(十七):视频编码H.264、VP8的前世今生

前言 目前从开发者的角度来说&#xff0c;音视频编码选H.264还是VP8几乎没有悬念&#xff08;个人认为这当然是H.264了&#xff09;。本文重在为读者从技术角度讲解H.264和VP8的发展渊源以及现时所面临的问题&#xff0c;相信读完此文后&#xff0c;对于即时通讯&#xff08;IM…

WebRTC 视频编解码类型的选择 VP8 H264 还是其他?(openh264编码,ffmpeg解码)

在你的WebRTC应用中,选择正确的视频编解码器很重要,但是如何选择又是一个棘手的问题。 WebRTC 视频编解码器 – 简要回顾 WebRTC 曾经很容易。你有 VP8、Opus 和 G.711。 G.711 被删除是因为我不想让你使用它。真的没有理由这样做。 后来,H.264 被添加为强制实现视频编解码器…

视音频编解码H264,265,MPEG-4,VP8,VP9知识总结

首先澄清几个基础知识&#xff1a; 一&#xff1a;封装格式&#xff1a; 我们常见的音视频文件格式例如&#xff1a;mp4 &#xff0c;flv,rmvb,avi等称为封装格式。封装格式里面封装了各种编码器编码的视频源信息的宽高比&#xff0c;视频轨&#xff0c;音频轨。例如视频源为…

MSVC2017 编译WebRTC Release VP8编码崩溃的问题

问题描述&#xff1a; 使用msvs2017编译webrtc。release版本使用VP8会出现奔溃&#xff0c;H264无问题。现象如下&#xff1a; 编译选项&#xff1a; "--argstarget_cpu\"x86\" is_debugfalse use_rttitrue is_clang false " 原因&#xff1a; MSVC编…

音视频基础1:H264、H265、MPEG-4、VP8、VP9编码基础知识

这里写自定义目录标题 个人认知&#xff0c;程序员职业发展出路编码器发展史编码原理H264H265 个人认知&#xff0c;程序员职业发展出路 随着5G时代的到来&#xff0c;音视频成功走上风口&#xff0c;程序员如何发展&#xff0c;其实不管是入门级选手还是30岁&#xff0c;35岁…

音视频基础:H264、H265、MPEG-4、VP8、VP9编码基础知识

编码器发展史 Android中创建编码器 MediaCodec.createEncoderByType("video/av"); //创建H264编码器 MediaCodec.createEncoderByType("video/hevc"); //创建H265编码器为什么会有这么多种编码器&#xff1f;看看他们的发展史的。 ITU-T这个组织是专门…

webrtc代码走读九(vp8 rtp 报文解析)

一、wireshark解析VP8报文方法 首先webrtc里面默认开启了FEC和SRTP功能&#xff0c;导致wireshark无法正常解析VP8的报文。所以若想了解VP8的RTP报文格式&#xff0c;还需要先关闭FEC、SRTP。 1、关闭FEC。 internalencoderfactory.cc文件屏蔽kRedCodecName、kUlpfecCodecNa…

vp8 的下载

1. vp8 的下载 页面地址 https://github.com/webmproject/libvpx git 下载 git clone https://github.com/webmproject/libvpx.git2. 在 android 上面搭建的 vp8 环境 页面地址 https://github.com/cmeng-git/vpx-android git 下载 git clone https://github.com/cmeng-gi…

VP8视频格式初探

作者&#xff1a; 阮一峰日期&#xff1a; 2010年5月20日 昨天&#xff0c;Google发布了一个开源项目WebM。 这个项目的目的&#xff0c;是在文件格式方面&#xff0c;为制作和发布互联网视频提供了一个开源的解决方案。 WebM采用MKV作为封装格式&#xff0c;里面的音频编码用V…

深入了解 VP8

部分翻译&#xff1a;http://x264dev.multimedia.cx/?p377 译者&#xff1a;delectate 问题一&#xff1a;vp8到底怎么样&#xff1f; 难道他真的比x264拥有更高的压缩比率&#xff0c;是个优秀的编码器吗&#xff1f;他真的比h264优秀吗&#xff1f;似乎On2自己都羞于承认……

VP8的前途与使命

文 / 金尹 VP8是视频压缩解决方案厂商On2推出的视频压缩格式。今年年初&#xff0c;Google完成了对On2的收购&#xff0c;随即开放了VP8视频编码技术源代码并免费提供给开发者使用。但业界对此一直褒贬不一&#xff0c;本文作者从多个角度进行阐述&#xff0c;对VP8的前途与发展…

如何使用Win10剪切板

几乎所有接触电脑的人都会使用CtrlC和CtrlV&#xff0c;但是每次CtrlC只能复制一次&#xff0c;并且会覆盖之前已经复制的内容。如果需要多次复制不同内容&#xff0c;那就操作多次。如果需要切换页面进行复制&#xff0c;特别不方便。其实&#xff0c;Win10有内置剪切板&#…

windows剪切板的历史记录

windows剪切板的历史记录 最近遇到一件比较坑的事情。当然可能也是我本人粗心大意了吧。但是这种事情难免要发生。比如说你要移动一个比较重要的东西&#xff0c;然后按了ctrlx&#xff0c;但是之间因为别的事情耽搁了一下&#xff0c;而自己的重要的东西还放在剪切板里面&…

win10如何查看剪切板内容?

cv大法我们经常用&#xff0c;但如果复制了新的内容但又想粘贴使用上次复制的内容怎么办&#xff1f; 其实复制新的内容并没有覆盖掉之前复制的内容&#xff0c;可以使用CtrlV快捷键打开剪切板&#xff0c;在剪切板里可以找到以前复制过的内容。 注意&#xff1a;这里的剪切板…