C++ 虚函数和虚函数表

article/2025/10/4 4:48:33

一、虚函数

1.虚函数的概念

1.虚函数就是在基类中被关键字 virtual 说明,并在派生类中重新定义的函数。


2.虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数。


3.虚函数的定义是在基类中进行的,它是在基类中在那些需要定义为虚函数的成员函数的声明中冠以关键字 virtual 。

定义虚函数的方法如下:

virtual 函数类型 函数名(形参表){函数体;
}

总而言之,虚函数是在编译时,并不能确定的类函数,而是在运行时确定的。

核心点:通过基类对象访问派生类实现的函数。

2.虚函数的例子

虚函数的例子,通常有三步。

  • 第一步,定义基类,声明基类函数为 virtual 的。

  • 第二步,定义派生类(继承基类),派生类实现了定义在基类的 virtual 函数。

  • 第三步,声明基类指针,并指向派生类,调用virtual函数,此时虽然是基类指针,但调用的是派生类实现的基类virtual 函数。

// 例子来源于: 菜鸟教程
class A
{public:virtual void foo(){cout<<"A::foo() is called"<<endl;}
};class B:public A
{public:void foo(){cout<<"B::foo() is called"<<endl;}
};int main(void)
{A *a = new B();a->foo();   // 在这里,a虽然是指向A的指针,但是被调用的函数(foo)却是B的!return 0;
}

3.纯虚函数

纯虚函数与虚函数的区别在于,纯虚函数的基类中的virtual函数,只定义了,但不实现。实现交给派生类来做。

PS:带纯虚函数的类也叫抽象类,因为这种基类不能直接生成对象。

优点:

  • 防止派生类忘记实现虚函数,纯虚函数使得派生类必须实现基类的虚函数。

  • 在某些场景下,创建基类对象是不合理的,含有纯虚拟函数的类称为抽象类,它不能生成对象。

声明方法

在基类中纯虚函数的方法的后面加 =0:

// 例子来源于: 菜鸟教程
virtual void funtion()=0

4.虚函数的使用

例1:

#include<iostream>
using namespace std;
class B0{public:virtual void print(char *p){	//定义虚函数 print cout<<p<<"print()"<<endl;}
};
class B1:public B0{public:virtual void print(char *p){	//重新定义虚函数 print cout<<p<<"print()"<<endl;}
};
class B2:public B1{public:virtual void print(char *p){	//重新定义虚函数 print cout<<p<<"print()"<<endl;}
};
int main(){B0 ob0,*op;	//定义基类对象 ob0 和对象指针 opop=&ob0; op->print("B0::");    //调用基类 B0 的 print B1 ob1;  //定义派生类 B1 的对象 op=&ob1;op->print("B1::");  //调用派生类 B1 的 print B2 ob2;op=&ob2;op->print("B2::");return 0;
}

执行结果:

 说明:

(1)若在基类中,只声明虚函数原型(需加上 virtual),而在类外定义虚函数时,则不必再加 virtual。例如:

class B0{public:virtual void print(char *p);     //声明虚函数原型,需加上 virtual
};

在类外,定义虚函数时,不要加 virtual:

void B0::print(char *p){cout<<p<<"print()"<<endl;
}

(2)在派生类中,虚函数被重新定义时,其函数的原型与基类中的函数原型(即包括函数类型、函数名、参数个数、参数类型的顺序)都必须完全相同。
(3)C++ 规定,当一个成员函数被定义为虚函数后,其派生类中符合重新定义虚函数要求的同名函数都自动称为虚函数。因此,在派生类中重新定义该虚函数时,关键字 virtual 可以不写。 但是,为了使程序更加清晰,最好在每一层派生类中定义该函数时都加上关键字 virtual。
(4)如果在派生类中没有对基类的虚函数重新定义,则公有派生类继承其直接基类的虚函数。一个虚函数无论被公有继承多少次,它仍然保持其虚函数的特性。 例如:
 

class B0{···public:virtual void show();	//在基类定义 show 为虚函数
};
class B1:public B0{···
};

若在公有派生类 B1 中没有重新定义虚函数 show ,则函数 show 在派生类中被继承,仍是虚函数。
(5)虚函数必须是其所在类的成员函数,而不能是友元函数,也不能是静态成员函数,因为虚函数调用要靠特定的对象来决定该激活哪个函数。
(6)使用对象名和点运算符的方式调用虚函数是在编译时进行的,是静态联编,没有利用虚函数的特性。只有通过基类指针访问虚函数时才能获得运行时的多态性。
例 2:使用对象名和点运算符的方式调用虚函数

#include<iostream>
using namespace std;
class B0{public:virtual void print(char *p){	//定义虚函数 print cout<<p<<"print()"<<endl;}
};
class B1:public B0{public:virtual void print(char *p){cout<<p<<"print()"<<endl;}
};
class B2:public B1{public:virtual void print(char *p){cout<<p<<"print()"<<endl;}	
};
int main(){B0 ob0;ob0.print("B0::");B1 ob1;ob1.print("B1::");B2 ob2;ob2.print("B2::");return 0;
}

5.虚析构函数

我们在动态分配堆上内存的时候,析构函数必须是虚函数

原因:动态分配堆上内存,无法自动回收。若基类指针指向派生类,然后基类指针调用delete方法,只能释放基类的内存,无法释放派生类特有的部分内存,进而导致内存泄露。

析构函数定义成虚函数,基类指针调用delete方法,会先调用派生类的析构函数,然后自动调用基类的析构函数。

所以,将可能被继承的基类的构造函数设置为虚函数,可以防止用基类指针指向子类是,释放基类指针是可以释放掉子类独有的空间,进而防止内存泄漏。

例1

#include<iostream>
using namespace std;
class B{public:~B(){cout<<"调用基类 B 的析构函数\n";}
};
class D:public B{public:~D(){cout<<"调用派生类 D 的析构函数\n";}
};
int main(){D obj;return 0;
}

这段程序会先执行派生类的析构函数,再执行基类的析构函数。但是,如果在主函数中用 new 运算符建立一个派生类的无名对象和定义了一个基类的对象指针,并将无名对象的地址赋给这个对象指针。当用 delete 运算符撤销无名对象时,系统只执行基类的析构函数,而不执行派生类的析构函数。

例2:虚析构函数的使用

#include<iostream>
using namespace std;
class B{public:virtual ~B(){cout<<"调用基类 B 的析构函数\n";}
};
class D:public B{public:virtual ~D(){cout<<"调用派生类 D 的析构函数\n";}
};
int main(){B *p;	//定义指向基类 B 的指针变量 pp=new D;	
//用运算符 new 为派生类的无名对象动态地分配了一个存储空间,并将地址赋给对象指针 pdelete p;
//用 delete 撤销无名对象,释放动态存储空间return 0; 
}

测试结果:

 由于使用了虚析构函数,程序执行了动态联编,实现了运行的动态性。虽然派生类的析构函数与基类的析构函数名字不相同,但是如果将基类的析构函数定义为虚函数,由该基类所派生的所有派生类的析构函数也都自动成为虚函数。

二、虚函数表

虚函数表是由有虚函数的类生成的,简称为 V-Table

虚函数表由编译器生成,如果一个类有虚函数,那么该类就会生成一个4个字节的虚函数表指针,指向虚函数表。指针存储在对象实例的最前面位置。

虚函数表就可以理解为一个数组,每个单元用来存放虚函数的地址(这个概念与我们前面学习的deque的中控器有些相似,可以分段管理duque中使用的分段连续线性空间)

1.单继承下的虚函数表

派生类直接继承基类虚函数

下图展示了一个派生类继承基类虚函数,且没有重写基类的虚函数,对应的虚函数指针、虚函数表、和虚函数表对应指针调用的方法。可以看出:

  • 虚函数表中的指针顺序,按照虚函数声明的顺序。

  • 基类的虚函数指针在派生类的前面。

 派生类重写基类虚函数

下图展示了一个派生类重写基类虚函数,且重写基类的部分虚函数,对应的虚函数指针、虚函数表、和虚函数表对应指针调用的方法。可以看出:

虚函数表中,派生类重写的虚函数替换了基类虚函数指针,并指向了派生类的函数实现。

 2.多继承下的虚函数表

多继承下的虚函数表,还是只有一个虚函数表。

多个基类之间的虚函数,按照继承的顺序,存放虚函数指针。

基类内部的虚函数,按照虚函数内部声明的顺序存放。

派生类直接继承基类虚函数

下图展示了多继承下的虚函数表,派生类直接继承基类虚函数,类似与单继承下的虚函数表的情况。

区别在于:

  • 多个基类之间的虚函数,按照继承的顺序,存放虚函数指针。

  • 基类内部的虚函数,按照虚函数内部声明的顺序存放。

 派生类重写基类虚函数

下图展示了多继承下的虚函数表,派生类重写部分基类虚函数,类似与单继承下的虚函数表的情况。

区别在于:

  • 多个基类之间的虚函数,按照继承的顺序,存放虚函数指针。

  • 基类内部的虚函数,按照虚函数内部声明的顺序存放。

  • 虚函数表中,派生类重写的虚函数替换了基类虚函数指针,并指向了派生类的函数实现。

这就是C++虚函数和虚函数表的部分内容了,相信通过今天的学习,我们一定能对虚函数和虚函数表有着更加深刻的理解。


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

相关文章

C++虚函数表剖析

关键词&#xff1a;虚函数&#xff0c;虚表&#xff0c;虚表指针&#xff0c;动态绑定&#xff0c;多态 一、概述 为了实现 C 的多态&#xff0c;C 使用了一种动态绑定的技术。这个技术的核心是虚函数表&#xff08;下文简称虚表&#xff09;。本文介绍虚函数表是如何实现动态…

C++ 虚函数表解析

C 虚函数表解析 陈皓 http://blog.csdn.net/haoel 前言 C中的虚函数的作用主要是实现了多态的机制。关于多态&#xff0c;简而言之就是用父类型别的指针指向其子类的实例&#xff0c;然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”&#x…

虚函数表详解

关键词&#xff1a;虚函数&#xff0c;虚表&#xff0c;虚表指针&#xff0c;动态绑定&#xff0c;多态 一、概述 为了实现C的多态&#xff0c;C使用了一种动态绑定的技术。这个技术的核心是虚函数表&#xff08;下文简称虚表&#xff09;。本文介绍虚函数表是如何实现动态绑定…

虚函数及虚函数表

虚函数及虚函数表 各个类对象共享类的虚函数表&#xff0c;每个类对象有个虚函数指针vptr&#xff0c;虚函数指针vptr指向虚函数表&#xff08;对于只有一个虚函数表的情况&#xff09;。 虚函数 简单的说&#xff0c;每一个含有虚函数&#xff08;无论是其本身的&#xff0…

简述虚函数表

前段时间我在博客中简单地说了下C的虚函数&#xff0c;所谓虚函数&#xff0c;就是C实现多态性的方法。那么编译器是如何识别虚函数的呢&#xff1f;据百度百科描述&#xff0c;C并未规定用何种方法实现虚函数&#xff0c;但是大部分编译器厂商都选择使用虚函数表这种方法&…

虚函数表的问题

虚函数表&#xff1a; 多态是由虚函数实现的&#xff0c;而虚函数主要是通过虚函数表&#xff08;V-Table&#xff09;来实现的。 如果一个类中包含虚函数&#xff08;virtual修饰的函数&#xff09;&#xff0c;那么这个类就会包含一张虚函数表&#xff0c;虚函数表存储的每…

虚函数表详解及其应用场景

目录 概述1. 虚函数表概述2. 虚函数表的实现原理2.1. 虚函数的声明和定义2.2. 虚函数表的创建和初始化2.3. 虚函数调用的过程 3. 虚函数表的应用场景3.1. 多态性3.2. 基类指针和引用的使用3.3. 动态绑定3.4. 接口定义 结论 概述 在面向对象编程中&#xff0c;虚函数表&#xf…

C++ 面试必问:深入理解虚函数表

点击蓝字 关注我们 深入理解C 虚函数表 C中的虚函数的作用主要是实现了多态的机制。关于多态&#xff0c;简而言之就是用父类型别的指针指向其子类的实例&#xff0c;然后通过父类的指针调用实际子类的成员函数。 Derive d; Base1 *b1 &d; Base2 *b2 &d; Base3 *b3 …

C++ 虚函数表

C类在内存中的存储方式 C 内存分为 5 个区域&#xff1a; 堆 heap &#xff1a;由 new 分配的内存块&#xff0c;其释放编译器不去管&#xff0c;由程序员自己控制。如果程序员没有释放掉&#xff0c;在程序结束时系统会自动回收。涉及的问题&#xff1a;“缓冲区溢出”、“内…

ThinkPHP3.2.2获取数据列getField()优化

getField()是一个常用方法&#xff0c;我习惯用来获取带key的数组&#xff0c;方便数据整合。 使用第1个参数&#xff0c;传入一个字段名&#xff0c;获取某一个数据值&#xff0c;返回满足条件的数据表中的该字段的第一行的值&#xff1a; $id M("User")->getFi…

java class getfield_java.lang.Class.getField()方法实例

全屏 java.lang.Class.getField() 返回一个Field对象&#xff0c;它反映此Class对象所表示的类或接口的指定公共成员字段。 name参数是一个字符串&#xff0c;指定所需字段的简单名称。 声明 以下是java.lang.Class.getField()方法的声明public Field getField(String name) th…

java getfield_Java 反射:通过 getField() 设置公共全局变量

Java 通过 getField() 操作公共全局变量 以前写 JavaWeb 项目启动初始化系统配置全局变量的代码&#xff0c;都是 variable Properties.getProperty(name) 这样一行一行代码的设置&#xff0c;变量少还好说&#xff0c;变量一多真的很磨叽。所以一直想通过 循环 简化代码&…

getField和getDeclaredField的区别

这两个方法都是用于获取字段 1.getField 只能获取public的&#xff0c;包括从父类继承来的字段。 2.getDeclaredField 可以获取本类所有的字段&#xff0c;包括private的&#xff0c;但是不能获取继承来的字段。 (注&#xff1a; 这里只能获取到private的字段&#xff0c;但并不…

vscode插件开发总结

一、关于vscode插件 相信大家对vscode应该都不陌生&#xff0c;VSCode是微软出的一款轻量级代码编辑器&#xff0c;免费而且功能强大&#xff0c;以功能强大、提示友好、不错的性能和颜值俘获了大量开发者的青睐&#xff0c;对JavaScript和NodeJS的支持非常好&#xff0c;自带…

2021-前端-VsCode插件

此乃吾习前端&#xff0c;VsCode之插件&#xff0c;个人所装&#xff0c;喜着自拿&#xff0c;不足之处还望海涵&#xff0c;多加批评。 1.Auto Close Tag——自动闭合尾部的标签 2.Atuo Rename Tag——修改 html 标签 自动帮你完成头部和尾部闭合标签的同步修改 3.Bracket…

关于VSCode插件的安装位置

VSCode的插件地址修改_上善若泪-CSDN博客_vscode插件位置文章目录1 data文件夹2 使用--extensions-dir命令3 使用mklink命令vscode编辑器强大的地方是可以使用各种各样的插件&#xff0c;但是插件默认的地方是在:C盘&#xff0c;让一些强迫症可能会受不了&#xff0c;非要迁移到…

vscode 插件-常用插件

VSCode常用插件(安装步骤同汉化) 1、*Auto Close Tag (自动闭合HTML/XML标签) 2、*Auto Rename Tag (自动帮你完成尾部闭合标签的同步修改&#xff0c;不过有些bug) 3、*Prettier(Prettier 是目前 Web 开发中最受欢迎的代码格式化程序) 安装Prettier -Code formatter这个插件…

Vscode 插件包下载并离线安装

打开VSCode插件官网 官网链接是https://marketplace.visualstudio.com/vscode 搜索Go 在输入框中输入go&#xff0c;搜索&#xff0c;结果如下&#xff1a; 点击Download Extension下载 注意&#xff1a;有时候找不到Download Extension&#xff0c;可能是网速加载慢&…

VsCode插件安装及推荐

1、快捷键Ctrl P&#xff0c;打开插件&#xff0c;输入 ext install &#xff08;我习惯的输入方式&#xff09;&#xff1b; 2、或者点击图片中的圈红的按钮&#xff0c;也可以进入插件安装商城&#xff1b; 3、下面开始说下我目前安装的插件&#xff08;我目前是vue开发&…

VScode安装离线插件

1. 下载及安装 首先在VScode官方插件库下载自己所需要的插件&#xff1a;https://marketplace.visualstudio.com/vscode 下载成功之后是以**.vsix**结尾的文件 然后再VScode软件中进行导入刚下载的文件 如果提示蓝色信息则为安装成功&#xff0c;红色则为失败 2. 版本不兼容报…