Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >深入探索虚函数表(详细)

深入探索虚函数表(详细)

原创
作者头像
用户7118204
修改于 2020-04-07 03:53:03
修改于 2020-04-07 03:53:03
55800
代码可运行
举报
运行总次数:0
代码可运行

这篇博客可能有一点点长,代码也有一点点多,但是仔细阅读分析完,会对虚函数表有一个深刻的认识。

什么是虚函数表?

       对于一个类来说,如果类中存在虚函数,那么该类的大小就会多4个字节,然而这4个字节就是一个指针的大小,这个指针指向虚函数表。所以,如果对象存在虚函数,那么编译器就会生成一个指向虚函数表的指针,所有的虚函数都存在于这个表中,虚函数表就可以理解为一个数组,每个单元用来存放虚函数的地址。


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

 虚函数表存在的位置

       由于虚函数表是由编译器给我们生成的,那么编译器会把虚函数表安插在哪个位置呢?下面可以简单的写一个示例来证明一下虚函数表的存在,以及观察它所存在的位置,先来看一下代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <iostream>
#include <stdio.h>
using namespace std;
 
class A{
public:
	int x;
	virtual void b() {}
};
 
int main()
{
	A* p = new A;
	cout << p << endl;
	cout << &p->x << endl;
	return 0;
}

       定义了一个类A,含有一个x和一个虚函数,实例化一个对象,然后输出对象的地址和对象成员x的地址,我们想一下,如果对象的地址和x的地址相同,那么就意味着编译器把虚函数表放在了末尾,如果两个地址不同,那么就意味着虚函数表是放在最前面的。运行结果为16进制,然后我们把它转为10进制观察一下:

       可以观察到结果是不同的,而且正好相差了4个字节,由此可见,编译器把生成的虚函数表放在了最前面。

获取虚函数表

       既然虚函数表是真实存在的,那么我们能不能想办法获取到虚函数表呢?其实我们可以通过指针的形式去获得,因为前面也提到了,我们可以把虚函数表看作是一个数组,每一个单元用来存放虚函数的地址,那么当调用的时候可以直接通过指针去调用所需要的函数就行了。我们就类比这个思路,去获取一下虚函数表。首先先定义两个类,一个是基类一个是派生类,代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class Base {
public:
	virtual void a() { cout << "Base a()" << endl; }
	virtual void b() { cout << "Base b()" << endl; }
	virtual void c() { cout << "Base c()" << endl; }
};
 
class Derive : public Base {
public:
	virtual void b() { cout << "Derive b()" << endl; }
};

       现在我们设想一下Derive类中的虚函数表是什么样的,它应该是含有三个指针,分别指向基类的虚函数a和基类的虚函数c和自己的虚函数b(因为基类和派生类中含有同名函数,被覆盖),那么我们就用下面的方式来验证一下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    Derive* p = new Derive;
	long* tmp = (long*)p;             // 先将p强制转换为long类型指针tmp
 
	// 由于tmp是虚函数表指针,那么*tmp就是虚函数表
	long* vptr = (long*)(*tmp);
	for (int i = 0; i < 3; i++) {
		printf("vptr[%d] : %p\n", i, vptr[i]);
	}

       同理,我们把基类的虚函数表的内容也用这种方法获取出来,然后二者进行比较一下,看看是否是符合我们上面所说的那个情况。先看一下完整的代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <iostream>
#include <stdio.h>
using namespace std;
 
class Base {
public:
	virtual void a() { cout << "Base a()" << endl; }
	virtual void b() { cout << "Base b()" << endl; }
	virtual void c() { cout << "Base c()" << endl; }
};
 
class Derive : public Base {
public:
	virtual void b() { cout << "Derive b()" << endl; }
};
 
int main()
{
	cout << "-----------Base------------" << endl;
	Base* q = new Base;
	long* tmp1 = (long*)q;
	long* vptr1 = (long*)(*tmp1);
	for (int i = 0; i < 3; i++) {
		printf("vptr[%d] : %p\n", i, vptr1[i]);
	}
 
	Derive* p = new Derive;
	long* tmp = (long*)p;
	long* vptr = (long*)(*tmp);
	cout << "---------Derive------------" << endl;
	for (int i = 0; i < 3; i++) {
		printf("vptr[%d] : %p\n", i, vptr[i]);
	}
	return 0;
}

       运行结果如下图所示:

       可见基类中的三个指针分别指向a,b,c虚函数,而派生类中的三个指针中第一个和第三个和基类中的相同,那么这就印证了上述我们所假设的情况,那么这也就是虚函数表。但是仅仅只是观察指向的地址,还不是让我们观察的特别清楚,那么我们就通过定义函数指针,来调用一下这几个地址,看看结果是什么样的,下面直接上代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <iostream>
#include <stdio.h>
using namespace std;
 
class Base {
public:
	virtual void a() { cout << "Base a()" << endl; }
	virtual void b() { cout << "Base b()" << endl; }
	virtual void c() { cout << "Base c()" << endl; }
};
 
class Derive : public Base {
public:
	virtual void b() { cout << "Derive b()" << endl; }
};
 
int main()
{
	typedef void (*Func)();
	cout << "-----------Base------------" << endl;
	Base* q = new Base;
	long* tmp1 = (long*)q;
	long* vptr1 = (long*)(*tmp1);
	for (int i = 0; i < 3; i++) {
		printf("vptr[%d] : %p\n", i, vptr1[i]);
	}
	Func a = (Func)vptr1[0];
	Func b = (Func)vptr1[1];
	Func c = (Func)vptr1[2];
	a();
	b();
	c();
 
	Derive* p = new Derive;
	long* tmp = (long*)p;
	long* vptr = (long*)(*tmp);
	cout << "---------Derive------------" << endl;
	for (int i = 0; i < 3; i++) {
		printf("vptr[%d] : %p\n", i, vptr[i]);
	}
	Func d = (Func)vptr[0];
	Func e = (Func)vptr[1];
	Func f = (Func)vptr[2];
	d();
	e();
	f();
 
	
	return 0;
}

       运行结果如下:

       这样就清晰的印证了上述所说的假设,那么虚函数表就获取出来了。

多重继承的虚函数表:

       虚函数的引入其实就是为了实现多态,现在来研究一下多重继承的虚函数表是什么样的,首先我们先来看一下简单的一般继承的代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class Base1 {
public:
	virtual void A() { cout << "Base1 A()" << endl; }
	virtual void B() { cout << "Base1 B()" << endl; }
	virtual void C() { cout << "Base1 C()" << endl; }
};
 
class Derive : public Base1{
public:
	virtual void MyA() { cout << "Derive MyA()" << endl; }
};

       这是一个类继承一个类,这段代码如果我们通过派生类去调用基类的函数,应该结果可想而知,这里就不再演示和赘述了。我们来分析这两个类的虚函数表,对于基类的虚函数表其实和上面所说的虚函数表是一样的,有自己的虚函数指针,并指向自己的虚函数表,重点是在于派生类的虚函数表是什么样子的,它的样子如下图所示:

       那么Derive的虚函数表就是继承了Base1的虚函数表,然后自己的虚函数放在后面,因此这个虚函数表的顺序就是基类的虚函数表中的虚函数的顺序+自己的虚函数的顺序。那么我们现在在Derive中再添加一个虚函数,让它覆盖基类中的虚函数,代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class Base1 {
public:
	virtual void A() { cout << "Base1 A()" << endl; }
	virtual void B() { cout << "Base1 B()" << endl; }
	virtual void C() { cout << "Base1 C()" << endl; }
};
 
class Derive : public Base1{
public:
	virtual void MyA() { cout << "Derive MyA()" << endl; }
	virtual void B() { cout << "Derive B()" << endl; }
};

       那么对于这种情况的虚函数表如下图所示:

     这个是单继承的情况,然后我们看看多重继承,也就是Derive类继承两个基类,先看一下代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class Base1 {
public:
	virtual void A() { cout << "Base1 A()" << endl; }
	virtual void B() { cout << "Base1 B()" << endl; }
	virtual void C() { cout << "Base1 C()" << endl; }
};
 
class Base2 {
public:
	virtual void D() { cout << "Base2 D()" << endl; }
	virtual void E() { cout << "Base2 E()" << endl; }
};
 
class Derive : public Base1, public Base2{
public:
	virtual void A() { cout << "Derive A()" << endl; }           // 覆盖Base1::A()
	virtual void D() { cout << "Derive D()" << endl; }           // 覆盖Base2::D()
	virtual void MyA() { cout << "Derive MyA()" << endl; }
};

       首先我们明确一个概念,对于多重继承的派生类来说,它含有多个虚函数指针,对于上述代码而言,Derive含有两个虚函数指针,所以它不是只有一个虚函数表,然后把所有的虚函数都塞到这一个表中,为了印证这一点,我们下面会印证这一点,首先我们先来看看这个多重继承的图示:

       由图可以看出,在第一个虚函数表中首先继承了Base1的虚函数表,然后将自己的虚函数放在后面,对于第二个虚函数表中,继承了Base2的虚函数表,由于在Derive类中有一个虚函数D覆盖了Base2的虚函数,所以第一个表中就没有Derive::D的函数地址。那么我们就用代码来实际的验证一下是否会存在两个虚函数指针,以及如果存在两个虚函数表,那么虚函数表是不是这个样子的。来看下面的代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <iostream>
#include <stdio.h>
using namespace std;
 
class Base1 {
public:
	virtual void A() { cout << "Base1 A()" << endl; }
	virtual void B() { cout << "Base1 B()" << endl; }
	virtual void C() { cout << "Base1 C()" << endl; }
};
 
class Base2 {
public:
	virtual void D() { cout << "Base2 D()" << endl; }
	virtual void E() { cout << "Base2 E()" << endl; }
};
 
class Derive : public Base1, public Base2{
public:
	virtual void A() { cout << "Derive A()" << endl; }           // 覆盖Base1::A()
	virtual void D() { cout << "Derive D()" << endl; }           // 覆盖Base2::D()
	virtual void MyA() { cout << "Derive MyA()" << endl; }
};
 
int main()
{
	typedef void (*Func)();
	Derive d;
	Base1 &b1 = d;
	Base2 &b2 = d;
	cout << "Derive对象所占的内存大小为:" << sizeof(d) << endl;
	
	cout << "\n---------第一个虚函数表-------------" << endl;
	long* tmp1 = (long *)&d;              // 获取第一个虚函数表的指针
	long* vptr1 = (long*)(*tmp1);         // 获取虚函数表
 
	Func x1 = (Func)vptr1[0];
	Func x2 = (Func)vptr1[1];
	Func x3 = (Func)vptr1[2];
	Func x4 = (Func)vptr1[3];
	x1();x2();x3();x4();
 
	cout << "\n---------第二个虚函数表-------------" << endl;
	long* tmp2 = tmp1 + 1;               // 获取第二个虚函数表指针 相当于跳过4个字节
	long* vptr2 = (long*)(*tmp2);
 
	Func y1 = (Func)vptr2[0];
	Func y2 = (Func)vptr2[1];
	y1(); y2();
 
	return 0;
}

       先看看运行结果,然后再去分析证明:

       因为在包含一个虚函数表的时候,含有一个虚函数表指针,所占用的大小为4个字节,那么这里输出了8个字节,就说明Derive对象含有两个虚函数表指针。然后我们通过获取到了这两个虚函数表,并调用其对应的虚函数,可以发现输出的结果和上面的示例图是相同的,因此就证明了上述所说的结论是正确的。

简单的总结一下:

1. 每一个基类都会有自己的虚函数表,派生类的虚函数表的数量根据继承的基类的数量来定。 2. 派生类的虚函数表的顺序,和继承时的顺序相同。 3. 派生类自己的虚函数放在第一个虚函数表的后面,顺序也是和定义时顺序相同。 4. 对于派生类如果要覆盖父类中的虚函数,那么会在虚函数表中代替其位置。

虚函数指针和虚函数表的创建时机:

       对于虚函数表来说,在编译的过程中编译器就为含有虚函数的类创建了虚函数表,并且编译器会在构造函数中插入一段代码,这段代码用来给虚函数指针赋值。因此虚函数表是在编译的过程中创建。

       对于虚函数指针来说,由于虚函数指针是基于对象的,所以对象在实例化的时候,虚函数指针就会创建,所以是在运行时创建。由于在实例化对象的时候会调用到构造函数,所以就会执行虚函数指针的赋值代码,从而将虚函数表的地址赋值给虚函数指针。

虚函数表的深入探索:

       经过上面的学习说明,我们知道了虚函数表的作用,是用来存放虚函数的地址的,那么我们先来看一下这个代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <iostream>
using namespace std;
 
class A{
public:
	int x;
	A(){
		memset(this, 0, sizeof(x));       // 将this对象中的成员初始化为0
		cout << "构造函数" << endl;
	}
	A(const A& a) {
		memcpy(this, &a, sizeof(A));      // 直接拷贝内存中的内容
		cout << "拷贝构造函数" << endl;
	}
	virtual void virfunc() {
		cout << "虚函数func" << endl;
	}
	void func() {
		cout << "func函数" << endl;
	}
	virtual ~A() {
		cout << "析构函数" << endl;
	}
};
 
int main()
{
	A a;
	a.virfunc();
	return 0;
}

       在构造函数中用的是memset()函数进行初始化操作,在拷贝构造函数中使用memcpy的方式来拷贝,可能这样的方法效率会更高,其运行结果如下图所示:

       可以运行,但是我们要对代码进行分析,前面我们提到了虚函数表是在编译的时候就已经生成好了,那么对于上面的代码中的virfunc来说,它的地址就已经存在于虚函数表中了,又根据前面我们提到的,在实例化对象的时候,编译器会为构造函数中插入一些代码,这些代码用来给虚函数指针进行赋值,那么这些操作都是在我们执行memset之前进行的,因此在执行了这些操作后,调用了memset函数,使得所有内容都清空了,那么虚函数指针就指向了0,那为什么我们还可以调用virfunc函数和析构函数呢?

       这里就涉及到了静态联编和动态联编的问题,我们先来明确一下静态联编和动态联编的定义:

静态联编:在编译时所进行的这种联编又称静态束定,在编译时就解决了程序中的操作调用与执行该操作代码间的关系。 动态联编:编译程序在编译阶段并不能确切知道将要调用的函数,只有在程序运行时才能确定将要调用的函数,为此要确                      切知道该调用的函数,要求联编工作要在程序运行时进行,这种在程序运行时进行联编工作被称为动态联编。

 由于我们把虚函数表指针设为了0,所以我们就无法通过前面的方法来获取它,这里我们可以通过反汇编来查看virfunc的地址:

          我们发现virfunc函数的地址并不是我们设置的0,那么它就变的和普通的函数没有什么区别了(普通函数采用静态联编,在编译时就绑定了函数的地址),这显然不是我们想要的虚函数,那么肯定就无法实现多态,对于类的多态性,一定是基于虚函数表的,那么虚函数表的实现一定是动态联编的,因此也不可缺少虚函数指针寻址的过程,那么我们要实现动态联编,就需要用到指针或者引用的形式。如果按上面代码的方式去执行,由于是非指针非引用的形式,所以编译器采用了静态绑定,提前绑定了函数的地址,那么就不存在指针寻址的过程了;如果使用指针或引用的形式,那么由于对象的所属类不能确定,那么编译器就无法采用静态编联,所以就只有通过动态编联的方式,在运行时去绑定调用指针和被调用地址。那么我们把代码改成下面的样子:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
A *a = new A;
a.virfunc();

       这个时候我们再运行程序,由于我们将虚函数指针置为0,从而找不到了虚函数的位置,那么程序就会崩溃,然后我们再通过反汇编查看一下,如图所示:

       比之前的多了一些汇编指令,其中mov就是寻址的过程,这样就实现了动态绑定,也是根据这一点来实现多态性,这里我只用了一个类来进行展示说明,其实用两个类更好。总之,如果要实现虚函数和多态,就需要通过指针或者引用的方式。

       关于虚函数表的东西就是这么多,如果有错误或者遗漏或者有疑问的地方可以在评论区中指出,这篇博客主要是自己学习后的一个总结,对于讲解部分,感觉图片太少了,单纯用文字描述又过于抽象,以后应该要加以改正。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
C++虚函数表原理浅析
C++中的虚函数实现了多态的机制,也就是用父类型指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数,这种技术可以让父类的指针有“多种形态”,这也是一种泛型技术,也就是使用不变的代码来实现可变的算法
ACM算法日常
2021/04/22
1.7K0
C++虚函数表原理浅析
C++进阶:详解多态(多态、虚函数、抽象类以及虚函数原理详解)
注意:在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写(因为继承后基类的虚函数被继承后在派生类依旧保持虚函数属性)但是该种写法不规范,大家还是少用为好。
是Nero哦
2024/03/17
6170
C++进阶:详解多态(多态、虚函数、抽象类以及虚函数原理详解)
虚函数
  虚函数必须是类的非静态成员函数(且非构造函数),其访问权限是public(可以定义为private or proteceted, 但是对于多态来说,没有意义。),在基类的类定义中定义虚函数的一般形式:
全栈程序员站长
2022/09/06
9130
虚函数
C++虚函数表详解
C++的虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。在这个表中,主要是一个类的虚函数的地址表,这张表解决了继承、覆盖(override)的问题,保证其能真实的反应实际的函数。这样,在有虚函数的类的实例中这张表被分配在了这个实例的内存中,所以当我们用父类的指针操作一个子类的时候,这张虚函数表就显得尤为重要了,他就像一个地图一样,指明了实际所应该调用的函数。
用户7886150
2021/02/03
6880
【c++】全面理解C++多态:虚函数表深度剖析与实践应用
多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。比如Student继承了Person。Person对象买票全价,Student对象买票半价
用户11029103
2024/05/24
3600
C++ 虚函数表解析
C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法。比如:模板技术,RTTI技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议。
bear_fish
2018/09/20
1.1K0
C++ 虚函数表解析
C++多态
在 C++ 程序设计中,多态性是指具有不同功能的函数可以用同一个函数名,这样就可以用一个函数名调用不同内容的函数。在面向对象方法中,一般是这样表述多态性的:向不同的对象发送同一个消息,不同的对象在接收时会产生不同的行为(即方法);也就是说,每个对象可以用自己的方式去响应共同的消息所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。换言之,可以用同样的接口访问功能不同的函数,从而实现“一个接口,多种方法”。在C++中主要分为静态多态和动态多态两种,在程序运行前就完成联编的称为静态多态,主要通过函数重载和模板实现,动态多态在程序运行时才完成联编,主要通过虚函数实现。
范中豪
2021/05/19
1.9K0
C++多态
动态联编实现原理分析
所谓动态联编,是指被调函数入口地址是在运行时、而不是在编译时决定的。C++语言利用动态联编来完成虚函数调用。C++标准并没有规定如何实现动态联编,但大多数的C++编译器都是通过虚指针(vptr)和虚函数表(vtable)来实现动态联编。 基本的思路是: (1)为每一个包含虚函数的类建立一个虚函数表,虚函数表的每一个表项存放的是个虚函数在内存中的入口地址;
恋喵大鲤鱼
2018/08/03
4520
动态联编实现原理分析
【C++】虚函数指针和虚函数列表
本篇文章主要来讲述,C++多态的实现原理,也就是虚函数和虚函数列表是怎么回事?它们是如何实现多态的?
灰子学技术
2020/04/02
1.5K0
虚函数详解[通俗易懂]
面向对象的语言有三大特性:继承、封装、多态。虚函数作为多态的实现方式,重要性毋庸置疑。
全栈程序员站长
2022/09/17
1.8K0
虚函数详解[通俗易懂]
多态与虚(函数)表
续接上回(继承),我们了解了继承是如何通过虚基表,来解决派生类和父类有相同的成员变量的情况,但是类和对象中可不只有成员变量,如果成员函数也有同名,更或者如果我们想在访问不同情况(类)但是相同函数名时,根据不同类满足不同需求。
比特大冒险
2023/04/16
5880
多态与虚(函数)表
【C++深度探索】全面解析多态性机制(二)
虚函数表(Virtual Function Table,VTable)是C++中实现动态多态性的一种机制。每个包含虚函数的类都有一个对应的虚函数表,用于存储该类的虚函数地址。
大耳朵土土垚
2024/07/16
1170
【C++深度探索】全面解析多态性机制(二)
【C++高阶】掌握C++多态:探索代码的动态之美
前言: 在编程的广阔领域中,多态(Polymorphism) 无疑是一个令人着迷且至关重要的概念。它不仅是面向对象编程(OOP)的三大特性之一(与封装和继承并列),也是实现代码复用、提高软件灵活性和可扩展性的关键所在。当我们谈论C++这门强大的编程语言时,多态更是一个不可或缺的话题
Eternity._
2024/06/18
3450
【C++高阶】掌握C++多态:探索代码的动态之美
【C++修炼之路】16.C++多态
多态的概念: 通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。
每天都要进步呀
2023/03/28
5250
【C++修炼之路】16.C++多态
【C++】详解多态的底层原理
上一篇文章我们学习了多态的语法,想必大家都会有很多疑问,这篇文章,我们就来带大家看看多态是如何实现的,它底层的原理是怎样的…
YIN_尹
2024/01/23
7330
【C++】详解多态的底层原理
C++多态
多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。
南桥
2024/08/07
1090
C++多态
【C++】多态——实现、重写、抽象类、多态原理
多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。
平凡的人1
2023/10/15
5890
【C++】多态——实现、重写、抽象类、多态原理
【C++】多态
多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。
zxctscl
2024/04/25
1030
【C++】多态
【ES三周年】C++多态
凡是面向对象的语言,都有三大特性,继承,封装和多态,但并不是只有这三个特性,是因为者三个特性是最重要的特性,那今天我们一起来看多态!
The sky
2023/04/11
4120
【ES三周年】C++多态
C++从入门到精通(第九篇) :多态
持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第22天,点击查看活动详情
雪芙花
2022/10/31
4830
C++从入门到精通(第九篇) :多态
相关推荐
C++虚函数表原理浅析
更多 >
领券
💥开发者 MCP广场重磅上线!
精选全网热门MCP server,让你的AI更好用 🚀
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验