C++虚函数表详解

article/2025/10/4 4:14:14

C++的虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。在这个表中,主要是一个类的虚函数的地址表,这张表解决了继承、覆盖(override)的问题,保证其能真实的反应实际的函数。这样,在有虚函数的类的实例中这张表被分配在了这个实例的内存中,所以当我们用父类的指针操作一个子类的时候,这张虚函数表就显得尤为重要了,他就像一个地图一样,指明了实际所应该调用的函数。

虚函数表中只存有一个虚函数的指针地址,不存放普通函数或是构造函数的指针地址。只要有虚函数,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)();  //定义一个函数指针类型变量类型 Fun  Base *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;
}

注意:在下面这个图中,虚函数表中最后一个节点相当于字符串的结束符,其标志了虚函数表的结束,在Codeblocks下打印为0。 

 

 

2、继承,无虚函数覆盖的情形

#include <iostream>  
using namespace std;  class Base {  
public:  virtual void f() { cout << "Base::f()" << endl; }  virtual void g() { cout << "Base::g()" << endl; }  virtual void h() { cout << "Base::h()" << endl; }  
};  class Derive: public Base {  virtual void f1() { cout << "Derive::f1()" << endl; }  virtual void g1() { cout << "Derive::g1()" << endl; }  virtual void h1() { cout << "Derive::h1()" << endl; }  
};  int main()  
{  typedef void (*Fun)();  Base *b = new Derive;  cout << *(int*)b << endl;  Fun funf = (Fun)(*(int*)*(int*)b);  Fun fung = (Fun)(*((int*)*(int*)b + 1));  Fun funh = (Fun)(*((int*)*(int*)b + 2));  Fun funf1 = (Fun)(*((int*)*(int*)b + 3));  Fun fung1 = (Fun)(*((int*)*(int*)b + 4));  Fun funh1 = (Fun)(*((int*)*(int*)b + 5));  funf(); // Base::f()  fung(); // Base::g()  funh(); // Base::h()  funf1(); // Derive::f1()  fung1(); // Derive::g1()  funh1(); // Derive::h1()  cout << (Fun)(*((int*)*(int*)b + 6));  return 0;
}

从表上可以看出

1、虚函数按照声明的顺序放在表中。

2、父类的虚函数在子类的虚函数前面。

 

 

3. 继承,虚函数覆盖的情形

#include <iostream>  
using namespace std;  class Base {  
public:  virtual void f() { cout << "Base::f()" << endl; }  virtual void g() { cout << "Base::g()" << endl; }  virtual void h() { cout << "Base::h()" << endl; }  
};  class Derive: public Base {  virtual void f() { cout << "Derive::f()" << endl; }  virtual void g1() { cout << "Derive::g1()" << endl; }  virtual void h1() { cout << "Derive::h1()" << endl; }  
};  int main()  
{  typedef void (*Fun)();  Base *b = new Derive;  cout << *(int*)b << endl;  Fun funf = (Fun)(*(int*)*(int*)b);  Fun fung = (Fun)(*((int*)*(int*)b + 1));  Fun funh = (Fun)(*((int*)*(int*)b + 2));  Fun fung1 = (Fun)(*((int*)*(int*)b + 3));  Fun funh1 = (Fun)(*((int*)*(int*)b + 4));  funf(); // Derive::f()  fung(); // Base::g()  funh(); // Base::h()  fung1(); // Derive::g1()  funh1(); // Derive::h1()  cout << (Fun)(*((int*)*(int*)b + 5));  return 0;  
}

从表上可以看出:

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

2、没有被覆盖的函数依旧。

3、可通过获取成员函数指针来调用成员函数(即时是private类型的成员函数),这就出现一定的安全问题。

 

4、多继承情况

#include <iostream>  
using namespace std;  class Base1 {  
public:  virtual void f() { cout << "Base1::f()" << endl; }  virtual void g() { cout << "Base1::g()" << endl; }  virtual void h() { cout << "Base1::h()" << endl; }  
};  class Base2 {  
public:  virtual void f() { cout << "Base2::f()" << endl; }  virtual void g() { cout << "Base2::g()" << endl; }  virtual void h() { cout << "Base2::h()" << endl; }  
};  class Base3 {  
public:  virtual void f() { cout << "Base3::f()" << endl; }  virtual void g() { cout << "Base3::g()" << endl; }  virtual void h() { cout << "Base3::h()" << endl; }  
};  class Derive: public Base1,public Base2, public Base3 {  virtual void f() { cout << "Derive::f()" << endl; }  virtual void g1() { cout << "Derive::g1()" << endl; }  
};  int main()  
{  typedef void (*Fun)();  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()  Fun b1fun = (Fun)(*(int*)*(int*)b1);  Fun b2fun = (Fun)(*(int*)*((int*)b1+1));  Fun b3fun = (Fun)(*(int*)*((int*)b1+2));  b1fun(); // Derive::f()  b2fun(); // Derive::f()  b3fun(); // Derive::f()  return 0;  
}

从表上可以看出:

1、每个父类都有自己的虚函数表。

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

3、对于多继承无虚函数覆盖的情况,布局与上图类似(Derive的位置对应Base)

 

 

一般继承(无虚函数覆盖)

下面,再让我们来看看继承时的虚函数表是什么样的。假设有如下所示的一个继承关系:

         

请注意,在这个继承关系中,子类没有重载任何父类的函数。那么,在派生类的实例中,其虚函数表如下所示:

对于实例:Derive d; 的虚函数表如下:

我们可以看到下面几点:

1)虚函数按照其声明顺序放于表中。

2)父类的虚函数在子类的虚函数前面。

我相信聪明的你一定可以参考前面的那个程序,来编写一段程序来验证。

 

 

一般继承(有虚函数覆盖)

覆盖父类的虚函数是很显然的事情,不然,虚函数就变得毫无意义。下面,我们来看一下,如果子类中有虚函数重载了父类的虚函数,会是一个什么样子?假设,我们有下面这样的一个继承关系。

为了让大家看到被继承过后的效果,在这个类的设计中,我只覆盖了父类的一个函数:f()。那么,对于派生类的实例,其虚函数表会是下面的一个样子:

我们从表中可以看到下面几点,

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

2)没有被覆盖的函数依旧。

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

Base *b = new Derive();

b->f();

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

 

 

多重继承(无虚函数覆盖)

下面,再让我们来看看多重继承中的情况,假设有下面这样一个类的继承关系。注意:子类并没有覆盖父类的函数。

对于子类实例中的虚函数表,是下面这个样子:

我们可以看到:

1) 每个父类都有自己的虚表。

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

这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

 

 

 

 

多重继承(有虚函数覆盖)

下面我们再来看看,如果发生虚函数覆盖的情况。

下图中,我们在子类中覆盖了父类的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()

 

 

附: 通过父类型的指针访问子类自己的虚函数

我们知道,子类没有重载父类的虚函数是一件毫无意义的事情。因为多态也是要基于函数重载的。虽然在上面的图中我们可以看到Base1的虚表中有Derive的虚函数,但我们根本不可能使用下面的语句来调用子类的自有虚函数:

Base1 *b1 = new Derive();

b1->f1(); //编译出错

任何fu妄图使用父类指针想调用子类中的未覆盖父类的成员函数的行为都会被编译器视为非法,所以,这样的程序根本无法编译通过。但在运行时,我们可以通过指针的方式访问虚函数表来达到违反C++语义的行为。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


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

相关文章

【虚函数指针 虚函数表】

文章目录 虚函数指针和虚函数表1.虚函数的含义2.虚函数的作用3.虚函数的实现原理 多态的实现原理普通类当类中存在虚函数子类继承父类不重写虚函数子类继承父类重写虚函数 1.虚函数表指针2.虚函数表 虚函数指针和虚函数表 1.虚函数的含义 只有用virtual声明类的成员函数&…

虚函数表 以及 虚函数表的继承过程

目录 一、虚函数表 和 虚表继承 1、虚函数表 2、虚表继承 (1) 子类未重写父类虚函数 (2) 子类重写了父类虚函数 二、虚表的特点 1、同一个类的对象的虚表指针相同 2、多继承时子类中的两个父类虚表地址不一样&#xff08;实际调用是同一个函数&#xff09; 三、虚表指…

C++虚函数和虚函数表原理

虚函数的地址存放于虚函数表之中。运行期多态就是通过虚函数和虚函数表实现的。 类的对象内部会有指向类内部的虚表地址的指针。通过这个指针调用虚函数。 虚函数的调用会被编译器转换为对虚函数表的访问&#xff1a; ptr->f(); //ptr代表this指针&#xff0c;f是虚函数…

虚函数 虚函数表

虚函数是面向对象编程函数的一种特定形态&#xff0c;是C用于实现多态的一种有效机制。C的多态可以分为静态多态和动态多态。函数重载和运算符重载实现的多态属于静态多态&#xff0c;而通过虚函数可以实现动态多态。实现函数的动态联编其本质核心则是虚表指针与虚函数表。 虚…

虚函数表和虚表指针

1&#xff0c;虚函数的含义 用virtual声明类的成员函数称之为虚函数 2&#xff0c;作用 用于实现多态 存在继承关系&#xff0c;子类继承父类子类重写了父类的virtual function子类以父类的指针或者引用的身份出现 3&#xff0c;虚函数的实现原理 其中的关键就是两点&#xf…

C++ 虚函数和虚函数表

一、虚函数 1.虚函数的概念 1.虚函数就是在基类中被关键字 virtual 说明&#xff0c;并在派生类中重新定义的函数。 2.虚函数的作用是允许在派生类中重新定义与基类同名的函数&#xff0c;并且可以通过基类指针或引用来访问基类和派生类中的同名函数。 3.虚函数的定义是在基类…

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;自带…