C++虚函数表、地址详解(x86/x64)

news2024/11/25 1:05:18

参考博文:c++虚函数表、地址详解-CSDN博客

本文在上述博文的基础上,补充了x64下的验证代码。

一.什么是虚函数表,它有什么特点?

        虚函数大家都知道是基本用于实现多态的,当父类指针指向子类对象的时候,如何确定调用的函数是父类里的还是子类里面的,这就要用到虚函数表。下面一点点表诉什么是虚函数表,和虚函数表的特点。如有说错的地方,欢迎指正:

1.编译器会为每个有虚函数的类创建一个虚函数表

        如有类中没有虚函数,那么这个虚函数表就不存在,而不是表中无数据。同时,有虚函数的类都会有自己的虚函数表,一个类不会有另外一个类的虚函数表,包括两个类属于继承关系。

2.虚函数表会被一个类的所有对象所拥有

        类的每个虚函数成员占据虚函数表的一行,所以说,如果类中有N个虚函数,那么该虚函数表将会有N*8的大小(x64)。并不是每个类对象都会有自己的表。

3.编译器会将虚函数表指针存放在类对象最前面的位置

        对于类的每个对象,编译器都会为其生成一个透明不可见的指针,这个指针就是虚函数表指针,存放在该对象内存的最前位置。例如:一个类拥有虚函数表,类对象内存布局中前8个字节就是虚函数表的地址(64位)。这个接下来我们会进行测试。

4.延伸,由第一条可知,子类继承父类,其虚函数表和表函数地址是不一样的。

5.父类指针指向子类对象时,调用时实际上是根据子类对象的虚函数表进行查找。

6.如果父类有虚函数,子类没有定义虚函数,那么子类也会有虚函数表。

二.测试

1.测试虚函数表地址和虚函数地址
#include <stdio.h>
#include <stdlib.h>
#include <iostream>

#ifdef _WIN64
#define PTR_VAL int64_t
#else
#define PTR_VAL int32_t
#endif

using namespace std;

class Father
{
	virtual void FatherFun1() { cout << "FatherFun1" << endl; }
	virtual void FatherFun2() { cout << "FatherFun2" << endl; }
	virtual void FatherFun3() { cout << "FatherFun3" << endl; }
};

typedef void (*Fun)(void);

int main()
{
	Father father;
	cout << "类对象地址:" << &father << endl;
	cout << "虚函数表地址: " << (PTR_VAL*)*(PTR_VAL*)&father << endl;
	cout << "虚函数FatherFun1地址:" << (PTR_VAL*)*(PTR_VAL*)&father << endl;
	cout << "虚函数FatherFun2地址:" << (PTR_VAL*)*(PTR_VAL*)&father + 1 << endl;
	cout << "虚函数FatherFun3地址:" << (PTR_VAL*)*(PTR_VAL*)&father + 2 << endl;

	cout << "测试地址是否正确" << endl;
	Fun fun = (Fun) * (PTR_VAL*)*(PTR_VAL*)&father;
	fun();
	fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(&father) + 1);
	fun();
	fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(&father) + 2);
	fun();

	system("pause");
}

         上面说过, x64下,类对象内存中前8个字节就是虚函数表的地址,那么我们获取类对象前8个字节,(int64_t)&father就是虚函数表地址,我们再给他转换成地址指针为(int64_t*)(int64_t)&father。然后我们在看一下输出结果:

类对象地址:000000ADA0B2F768
虚函数表地址: 00007FF7FEAABCB0
虚函数FatherFun1地址:00007FF7FEAABCB0
虚函数FatherFun2地址:00007FF7FEAABCB8
虚函数FatherFun3地址:00007FF7FEAABCC0
测试地址是否正确
FatherFun1
FatherFun2
FatherFun3
请按任意键继续. . .

        从这个输出我们可以看到,FatherFun1、FatherFun2、FatherFun3的地址只差了8个字节,且输出是正确的,那么第三点已确认

2.多个类对象的虚函数表地址

#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#ifdef _WIN64
#define PTR_VAL int64_t
#else
#define PTR_VAL int32_t
#endif
using namespace std;

class Father
{
	virtual void FatherFun1() { cout << "FatherFun1" << endl; }
	virtual void FatherFun2() { cout << "FatherFun2" << endl; }
	virtual void FatherFun3() { cout << "FatherFun3" << endl; }
};

typedef void (*Fun)(void);

int main()
{

	Father father;
	cout << "类对象地址:" << &father << endl;
	cout << "虚函数表地址: " << (PTR_VAL*)*(PTR_VAL*)&father << endl;
	cout << "虚函数FatherFun1地址:" << (PTR_VAL*)*(PTR_VAL*)&father << endl;
	cout << "虚函数FatherFun2地址:" << (PTR_VAL*)*(PTR_VAL*)&father + 1 << endl;
	cout << "虚函数FatherFun3地址:" << (PTR_VAL*)*(PTR_VAL*)&father + 2 << endl;

	cout << "第二个类对象" << endl;

	Father father1;
	cout << "类对象地址:" << &father1 << endl;
	cout << "虚函数表地址: " << (PTR_VAL*)*(PTR_VAL*)&father1 << endl;
	cout << "虚函数FatherFun1地址:" << (PTR_VAL*)*(PTR_VAL*)&father1 << endl;
	cout << "虚函数FatherFun2地址:" << (PTR_VAL*)*(PTR_VAL*)&father1 + 1 << endl;
	cout << "虚函数FatherFun3地址:" << (PTR_VAL*)*(PTR_VAL*)&father1 + 2 << endl;

	system("pause");
}

定义了两个对象,分别看两个对象的虚函数表地址是否正确,发现两个类对象虚函数表地址相同。

以下为输出:

类对象地址:00000070C3DCFC18
虚函数表地址: 00007FF775FCBCF8
虚函数FatherFun1地址:00007FF775FCBCF8
虚函数FatherFun2地址:00007FF775FCBD00
虚函数FatherFun3地址:00007FF775FCBD08
第二个类对象
类对象地址:00000070C3DCFC38
虚函数表地址: 00007FF775FCBCF8
虚函数FatherFun1地址:00007FF775FCBCF8
虚函数FatherFun2地址:00007FF775FCBD00
虚函数FatherFun3地址:00007FF775FCBD08
请按任意键继续. . .

由此可见,第二点现在已经确认

3.测试类中无虚函数,是否存在虚函数表

#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#ifdef _WIN64
#define PTR_VAL int64_t
#else
#define PTR_VAL int32_t
#endif
using namespace std;

class Test
{
	void TestFun1() { cout << "TestFun1" << endl; }
	void TestFun2() { cout << "TestFun2" << endl; }
	void TestFun3() { cout << "TestFun3" << endl; }
};

typedef void (*Fun)(void);

int main()
{
	Test test;
	cout << "类对象地址:" << &test << endl;
	cout << "虚函数表地址: " << (PTR_VAL*)*(PTR_VAL*)&test << endl;
	cout << "虚函数TestFun1地址:" << (PTR_VAL*)*(PTR_VAL*)&test << endl;
	cout << "虚函数TestFun2地址:" << (PTR_VAL*)*(PTR_VAL*)&test + 1 << endl;
	cout << "虚函数TestFun3地址:" << (PTR_VAL*)*(PTR_VAL*)&test + 2 << endl;

	system("pause");
}

以下为输出:

类对象地址:000000AEBF9DFB44
虚函数表地址: CCCCCCCCCCCCCCCC
虚函数TestFun1地址:CCCCCCCCCCCCCCCC
虚函数TestFun2地址:CCCCCCCCCCCCCCD4
虚函数TestFun3地址:CCCCCCCCCCCCCCDC
请按任意键继续. . .

由此可见虚函数表地址为错误,第一条确认

4.子类继承父类,但不继承虚函数表

#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#ifdef _WIN64
#define PTR_VAL int64_t
#else
#define PTR_VAL int32_t
#endif
using namespace std;

class Father
{
    virtual void FatherFun1() { cout << "FatherFun1" << endl; }
    virtual void FatherFun2() { cout << "FatherFun2" << endl; }
    virtual void FatherFun3() { cout << "FatherFun3" << endl; }
};

class Son : public Father
{
    virtual void SonFun1() { cout << "SonFun1" << endl; }
    virtual void SonFun2() { cout << "SonFun2" << endl; }
    virtual void SonFun3() { cout << "SonFun3" << endl; }
};

typedef void (*Fun)(void);

int main()
{
    Father father;
    cout << "类对象地址:" << &father << endl;
    cout << "虚函数表地址: " << (PTR_VAL*)*(PTR_VAL*)&father << endl;
    cout << "虚函数FatherFun1地址:" << (PTR_VAL*)*(PTR_VAL*)&father << endl;
    cout << "虚函数FatherFun2地址:" << (PTR_VAL*)*(PTR_VAL*)&father + 1 << endl;
    cout << "虚函数FatherFun3地址:" << (PTR_VAL*)*(PTR_VAL*)&father + 2 << endl;
    //cout << "虚函数sonFun1地址:" <<(PTR_VAL*)*(PTR_VAL*)&father + 3 << endl;
    //cout << "虚函数sonFun2地址:" <<(PTR_VAL*)*(PTR_VAL*)&father + 4 << endl;
    //cout << "虚函数sonFun3地址:" <<(PTR_VAL*)*(PTR_VAL*)&father + 5 << endl;
    Fun fun = (Fun) * (PTR_VAL*)*(PTR_VAL*)&father;
    fun();
    fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(&father) + 1);
    fun();
    fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(&father) + 2);
    fun();
    //fun = (Fun)*((PTR_VAL*)*(PTR_VAL*)(&father) + 3);
    //fun();
    //fun = (Fun)*((PTR_VAL*)*(PTR_VAL*)(&father) + 4);
    //fun();
    //fun = (Fun)*((PTR_VAL*)*(PTR_VAL*)(&father) + 5);
    //fun();

    cout << "----------------测试子类------------------" << endl;

    Son son;
    cout << "类对象地址:" << &son << endl;
    cout << "虚函数表地址: " << (PTR_VAL*)*(PTR_VAL*)&son << endl;
    cout << "虚函数继承FatherFun1地址:" << (PTR_VAL*)*(PTR_VAL*)&son << endl;
    cout << "虚函数继承FatherFun2地址:" << (PTR_VAL*)*(PTR_VAL*)&son + 1 << endl;
    cout << "虚函数继承FatherFun3地址:" << (PTR_VAL*)*(PTR_VAL*)&son + 2 << endl;
    cout << "虚函数sonFun1地址:" << (PTR_VAL*)*(PTR_VAL*)&son + 3 << endl;
    cout << "虚函数sonFun2地址:" << (PTR_VAL*)*(PTR_VAL*)&son + 4 << endl;
    cout << "虚函数sonFun3地址:" << (PTR_VAL*)*(PTR_VAL*)&son + 5 << endl;
    fun = (Fun) * (PTR_VAL*)*(PTR_VAL*)&son;
    fun();
    fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(&son) + 1);
    fun();
    fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(&son) + 2);
    fun();
    fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(&son) + 3);
    fun();
    fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(&son) + 4);
    fun();
    fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(&son) + 5);
    fun();

    system("pause");
}

如果将代码中注释代码给恢复,会直接发生段错误。

以下为输出结果:

类对象地址:000000A9936FF9A8
虚函数表地址: 00007FF667CABCF8
虚函数FatherFun1地址:00007FF667CABCF8
虚函数FatherFun2地址:00007FF667CABD00
虚函数FatherFun3地址:00007FF667CABD08
FatherFun1
FatherFun2
FatherFun3
----------------测试子类------------------
类对象地址:000000A9936FF9E8
虚函数表地址: 00007FF667CABD50
虚函数继承FatherFun1地址:00007FF667CABD50
虚函数继承FatherFun2地址:00007FF667CABD58
虚函数继承FatherFun3地址:00007FF667CABD60
虚函数sonFun1地址:00007FF667CABD68
虚函数sonFun2地址:00007FF667CABD70
虚函数sonFun3地址:00007FF667CABD78
FatherFun1
FatherFun2
FatherFun3
SonFun1
SonFun2
SonFun3
请按任意键继续. . .

        由输出结果我们可以看到,父类和子类中的虚函数表地址是不一样的,而且子类继承了父类的虚函数,但是其地址是和父类中不一样的!。第四点确认!
        并且我们根据输出可以发现,子类虚函数表中虚函数地址排序为先是继承的父类的函数,再是子类中的虚函数。

5.子类重写父类中虚函数,或子类重定义父类虚函数

#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#ifdef _WIN64
#define PTR_VAL int64_t
#else
#define PTR_VAL int32_t
#endif
using namespace std;

class Father
{
	virtual void FatherFun4() { cout << "FatherFun4" << endl; }
	virtual void FatherFun1() { cout << "FatherFun1" << endl; }
	virtual void FatherFun2() { cout << "FatherFun2" << endl; }
	virtual void FatherFun3() { cout << "FatherFun3" << endl; }
};

class Son : public Father
{
	virtual void SonFun1() { cout << "SonFun1" << endl; }
	virtual void SonFun2() { cout << "SonFun2" << endl; }
	virtual void SonFun3() { cout << "SonFun3" << endl; }
	virtual void FatherFun4() { cout << "SonGetFatherFun4" << endl; }
	virtual void FatherFun4(int a = 0) { cout << "SonGetFatherFun4 diffrent param" << endl; }
};

typedef void (*Fun)(void);
typedef void (*FunParam)(int);

int main()
{
#if 1
	Father father;
	cout << "类对象地址:" << &father << endl;
	cout << "虚函数表地址: " << (PTR_VAL*)*(PTR_VAL*)&father << endl;
	cout << "虚函数FatherFun4地址:" << (PTR_VAL*)*(PTR_VAL*)&father << endl;
	cout << "虚函数FatherFun1地址:" << (PTR_VAL*)*(PTR_VAL*)&father + 1 << endl;
	cout << "虚函数FatherFun2地址:" << (PTR_VAL*)*(PTR_VAL*)&father + 2 << endl;
	cout << "虚函数FatherFun3地址:" << (PTR_VAL*)*(PTR_VAL*)&father + 3 << endl;
	Fun fun = (Fun) * (PTR_VAL*)*(PTR_VAL*)&father;
	fun();
	fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(&father) + 1);
	fun();
	fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(&father) + 2);
	fun();
	fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(&father) + 3);
	fun();


	cout << "----------------测试子类------------------" << endl;

	Son son;
	cout << "类对象地址:" << &son << endl;
	cout << "虚函数表地址: " << (PTR_VAL*)*(PTR_VAL*)&son << endl;
	cout << "虚函数继承SonGetFatherFun4地址:" << (PTR_VAL*)*(PTR_VAL*)&son << endl;
	cout << "虚函数继承FatherFun1地址:" << (PTR_VAL*)*(PTR_VAL*)&son + 1 << endl;
	cout << "虚函数继承FatherFun2地址:" << (PTR_VAL*)*(PTR_VAL*)&son + 2 << endl;
	cout << "虚函数继承FatherFun3地址:" << (PTR_VAL*)*(PTR_VAL*)&son + 3 << endl;
	cout << "虚函数sonFun1地址:" << (PTR_VAL*)*(PTR_VAL*)&son + 4 << endl;
	cout << "虚函数sonFun2地址:" << (PTR_VAL*)*(PTR_VAL*)&son + 5 << endl;
	cout << "虚函数sonFun3地址:" << (PTR_VAL*)*(PTR_VAL*)&son + 6 << endl;
	cout << "虚函数SonGetFatherFun4 diffrent param地址:" << (PTR_VAL*)*(PTR_VAL*)&son + 7 << endl;
	fun = (Fun) * (PTR_VAL*)*(PTR_VAL*)&son;
	fun();
	fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(&son) + 1);
	fun();
	fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(&son) + 2);
	fun();
	fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(&son) + 3);
	fun();
	fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(&son) + 4);
	fun();
	fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(&son) + 5);
	fun();
	fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(&son) + 6);
	fun();
	FunParam funParam = (FunParam) * ((PTR_VAL*)*(PTR_VAL*)(&son) + 7);
	funParam(0);
#endif
	system("pause");
}

        我们故意将父类中FatherFun4放在最前,而子类中的重写和重定义的FatherFun4 函数放在最后,看一下虚函数表中虚函数排放顺序是否有发生改变。

以下为输出结果:

虚函数FatherFun1地址:00007FF76199BD00
虚函数FatherFun2地址:00007FF76199BD08
虚函数FatherFun3地址:00007FF76199BD10
FatherFun4
FatherFun1
FatherFun2
FatherFun3
----------------测试子类------------------
类对象地址:0000003360F5F648
虚函数表地址: 00007FF76199BD68
虚函数继承SonGetFatherFun4地址:00007FF76199BD68
虚函数继承FatherFun1地址:00007FF76199BD70
虚函数继承FatherFun2地址:00007FF76199BD78
虚函数继承FatherFun3地址:00007FF76199BD80
虚函数sonFun1地址:00007FF76199BD88
虚函数sonFun2地址:00007FF76199BD90
虚函数sonFun3地址:00007FF76199BD98
虚函数SonGetFatherFun4 diffrent param地址:00007FF76199BDA0
SonGetFatherFun4
FatherFun1
FatherFun2
FatherFun3
SonFun1
SonFun2
SonFun3
SonGetFatherFun4 diffrent param
请按任意键继续. . .

        我们可以清楚的看到,位于子类最后函数重写的FatherFun4被放在了本该继承的父类FatherFun4位置上(先输出的是SonGetFatherFun4),而函数重定义则没有发生改变。

由此可以得出结论:

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

(2)没有被覆盖的函数没有变化

6.子类指针指向父类

#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#ifdef _WIN64
#define PTR_VAL int64_t
#else
#define PTR_VAL int32_t
#endif
using namespace std;

class Father
{
	virtual void FatherFun1() { cout << "FatherFun1" << endl; }
	virtual void FatherFun2() { cout << "FatherFun2" << endl; }
	virtual void FatherFun3() { cout << "FatherFun3" << endl; }
};

class Son : public Father
{
	virtual void SonFun1() { cout << "SonFun1" << endl; }
	virtual void SonFun2() { cout << "SonFun2" << endl; }
	virtual void SonFun3() { cout << "SonFun3" << endl; }
};

#if 0
class Test
{
	void TestFun1() { cout << "TestFun1" << endl; }
	void TestFun2() { cout << "TestFun2" << endl; }
	void TestFun3() { cout << "TestFun3" << endl; }
};
#endif

typedef void (*Fun)(void);
typedef void (*FunParam)(int);

int main()
{
	cout << "----------------测试父类------------------" << endl;
	Father father;
	cout << "类对象地址:" << &father << endl;
	cout << "虚函数表地址: " << (PTR_VAL*)*(PTR_VAL*)&father << endl;
	cout << "虚函数FatherFun1地址:" << (PTR_VAL*)*(PTR_VAL*)&father << endl;
	cout << "虚函数FatherFun1地址:" << (PTR_VAL*)*(PTR_VAL*)&father + 1 << endl;
	cout << "虚函数FatherFun2地址:" << (PTR_VAL*)*(PTR_VAL*)&father + 2 << endl;

	Fun fun = (Fun) * (PTR_VAL*)*(PTR_VAL*)&father;
	fun();
	fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(&father) + 1);
	fun();
	fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(&father) + 2);
	fun();


	cout << "----------------测试子类------------------" << endl;
	Son son;
	cout << "类对象地址:" << &son << endl;
	cout << "虚函数表地址: " << (PTR_VAL*)*(PTR_VAL*)&son << endl;
	cout << "虚函数继承FatherFun1地址:" << (PTR_VAL*)*(PTR_VAL*)&son << endl;
	cout << "虚函数继承FatherFun2地址:" << (PTR_VAL*)*(PTR_VAL*)&son + 1 << endl;
	cout << "虚函数继承FatherFun3地址:" << (PTR_VAL*)*(PTR_VAL*)&son + 2 << endl;
	cout << "虚函数sonFun1地址:" << (PTR_VAL*)*(PTR_VAL*)&son + 3 << endl;
	cout << "虚函数sonFun2地址:" << (PTR_VAL*)*(PTR_VAL*)&son + 4 << endl;
	cout << "虚函数sonFun3地址:" << (PTR_VAL*)*(PTR_VAL*)&son + 5 << endl;
	fun = (Fun) * (PTR_VAL*)*(PTR_VAL*)&son;
	fun();
	fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(&son) + 1);
	fun();
	fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(&son) + 2);
	fun();
	fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(&son) + 3);
	fun();
	fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(&son) + 4);
	fun();
	fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(&son) + 5);
	fun();

	cout << "----------------测试父类指针指向子类------------------" << endl;
	Father* pointSon = new Son;
	cout << "类对象地址:" << pointSon << endl;
	cout << "虚函数表地址: " << (PTR_VAL*)*(PTR_VAL*)pointSon << endl;
	cout << "虚函数继承FatherFun1地址:" << (PTR_VAL*)*(PTR_VAL*)pointSon << endl;
	cout << "虚函数继承FatherFun2地址:" << (PTR_VAL*)*(PTR_VAL*)pointSon + 1 << endl;
	cout << "虚函数继承FatherFun3地址:" << (PTR_VAL*)*(PTR_VAL*)pointSon + 2 << endl;
	cout << "虚函数sonFun1地址:" << (PTR_VAL*)*(PTR_VAL*)pointSon + 3 << endl;
	cout << "虚函数sonFun2地址:" << (PTR_VAL*)*(PTR_VAL*)pointSon + 4 << endl;
	cout << "虚函数sonFun3地址:" << (PTR_VAL*)*(PTR_VAL*)pointSon + 5 << endl;
	fun = (Fun) * (PTR_VAL*)*(PTR_VAL*)pointSon;
	fun();
	fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(pointSon)+1);
	fun();
	fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(pointSon)+2);
	fun();
	fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(pointSon)+3);
	fun();
	fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(pointSon)+4);
	fun();
	fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(pointSon)+5);
	fun();

	system("pause");
}

实际上父类指针指向子类,调用的是子类的虚函数表。

以下为输出:

类对象地址:00000058117AF688
虚函数表地址: 00007FF6F469CD50
虚函数继承FatherFun1地址:00007FF6F469CD50
虚函数继承FatherFun2地址:00007FF6F469CD58
虚函数继承FatherFun3地址:00007FF6F469CD60
虚函数sonFun1地址:00007FF6F469CD68
虚函数sonFun2地址:00007FF6F469CD70
虚函数sonFun3地址:00007FF6F469CD78
FatherFun1
FatherFun2
FatherFun3
SonFun1
SonFun2
SonFun3
----------------测试父类指针指向子类------------------
类对象地址:0000021C918185C0
虚函数表地址: 00007FF6F469CD50
虚函数继承FatherFun1地址:00007FF6F469CD50
虚函数继承FatherFun2地址:00007FF6F469CD58
虚函数继承FatherFun3地址:00007FF6F469CD60
虚函数sonFun1地址:00007FF6F469CD68
虚函数sonFun2地址:00007FF6F469CD70
虚函数sonFun3地址:00007FF6F469CD78
FatherFun1
FatherFun2
FatherFun3
SonFun1
SonFun2
SonFun3
请按任意键继续. . .

        由虚函数表地址我们就可以看到,父类指针指向子类的时候,其虚函数表地址和子类虚函数表地址是一样的。由此,第五点确认。

7.子类重写父类虚函数,并父类指针指向子类

#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#ifdef _WIN64
#define PTR_VAL int64_t
#else
#define PTR_VAL int32_t
#endif
using namespace std;

class Father
{
    virtual void FatherFun4() { cout << "FatherFun4" << endl; }
    virtual void FatherFun1() { cout << "FatherFun1" << endl; }
    virtual void FatherFun2() { cout << "FatherFun2" << endl; }
    virtual void FatherFun3() { cout << "FatherFun3" << endl; }
};

class Son : public Father
{
    virtual void SonFun1() { cout << "SonFun1" << endl; }
    virtual void SonFun2() { cout << "SonFun2" << endl; }
    virtual void SonFun3() { cout << "SonFun3" << endl; }
    virtual void FatherFun4() { cout << "SonGetFatherFun4" << endl; }
};

#if 0
class Test
{
    void TestFun1() { cout << "TestFun1" << endl; }
    void TestFun2() { cout << "TestFun2" << endl; }
    void TestFun3() { cout << "TestFun3" << endl; }
};
#endif

typedef void (*Fun)(void);
typedef void (*FunParam)(int);

int main()
{
    cout << "----------------测试父类------------------" << endl;
    Father father;
    cout << "类对象地址:" << &father << endl;
    cout << "虚函数表地址: " << (PTR_VAL*)*(PTR_VAL*)&father << endl;
    cout << "虚函数FatherFun4地址:" << (PTR_VAL*)*(PTR_VAL*)&father << endl;
    cout << "虚函数FatherFun1地址:" << (PTR_VAL*)*(PTR_VAL*)&father + 1 << endl;
    cout << "虚函数FatherFun2地址:" << (PTR_VAL*)*(PTR_VAL*)&father + 2 << endl;
    cout << "虚函数FatherFun3地址:" << (PTR_VAL*)*(PTR_VAL*)&father + 3 << endl;

    Fun fun = (Fun) * (PTR_VAL*)*(PTR_VAL*)&father;
    fun();
    fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(&father) + 1);
    fun();
    fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(&father) + 2);
    fun();
    fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(&father) + 3);
    fun();

    cout << "----------------测试子类------------------" << endl;
    Son son;
    cout << "类对象地址:" << &son << endl;
    cout << "虚函数表地址: " << (PTR_VAL*)*(PTR_VAL*)&son << endl;
    cout << "虚函数继承FatherFun4地址:" << (PTR_VAL*)*(PTR_VAL*)&son << endl;
    cout << "虚函数继承FatherFun1地址:" << (PTR_VAL*)*(PTR_VAL*)&son + 1 << endl;
    cout << "虚函数继承FatherFun2地址:" << (PTR_VAL*)*(PTR_VAL*)&son + 2 << endl;
    cout << "虚函数继承FatherFun3地址:" << (PTR_VAL*)*(PTR_VAL*)&son + 3 << endl;
    cout << "虚函数sonFun1地址:" << (PTR_VAL*)*(PTR_VAL*)&son + 4 << endl;
    cout << "虚函数sonFun2地址:" << (PTR_VAL*)*(PTR_VAL*)&son + 5 << endl;
    cout << "虚函数sonFun3地址:" << (PTR_VAL*)*(PTR_VAL*)&son + 6 << endl;
    fun = (Fun) * (PTR_VAL*)*(PTR_VAL*)&son;
    fun();
    fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(&son) + 1);
    fun();
    fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(&son) + 2);
    fun();
    fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(&son) + 3);
    fun();
    fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(&son) + 4);
    fun();
    fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(&son) + 5);
    fun();
    fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(&son) + 6);
    fun();


    cout << "----------------测试父类指针指向子类------------------" << endl;
    Father* pointSon = new Son;
    cout << "类对象地址:" << pointSon << endl;
    cout << "虚函数表地址: " << (PTR_VAL*)*(PTR_VAL*)pointSon << endl;
    cout << "虚函数继承FatherFun1地址:" << (PTR_VAL*)*(PTR_VAL*)pointSon << endl;
    cout << "虚函数继承FatherFun2地址:" << (PTR_VAL*)*(PTR_VAL*)pointSon + 1 << endl;
    cout << "虚函数继承FatherFun3地址:" << (PTR_VAL*)*(PTR_VAL*)pointSon + 2 << endl;
    cout << "虚函数sonFun1地址:" << (PTR_VAL*)*(PTR_VAL*)pointSon + 3 << endl;
    cout << "虚函数sonFun2地址:" << (PTR_VAL*)*(PTR_VAL*)pointSon + 4 << endl;
    cout << "虚函数sonFun3地址:" << (PTR_VAL*)*(PTR_VAL*)pointSon + 5 << endl;
    fun = (Fun) * (PTR_VAL*)*(PTR_VAL*)pointSon;
    fun();
    fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(pointSon)+1);
    fun();
    fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(pointSon)+2);
    fun();
    fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(pointSon)+3);
    fun();
    fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(pointSon)+4);
    fun();
    fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(pointSon)+5);
    fun();
    fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(pointSon)+6);
    fun();

    system("pause");
}

直接看输出结果:

----------------测试父类------------------
类对象地址:000000555C70FBF8
虚函数表地址: 00007FF61736CCF8
虚函数FatherFun4地址:00007FF61736CCF8
虚函数FatherFun1地址:00007FF61736CD00
虚函数FatherFun2地址:00007FF61736CD08
虚函数FatherFun3地址:00007FF61736CD10
FatherFun4
FatherFun1
FatherFun2
FatherFun3
----------------测试子类------------------
类对象地址:000000555C70FC38
虚函数表地址: 00007FF61736CD68
虚函数继承FatherFun4地址:00007FF61736CD68
虚函数继承FatherFun1地址:00007FF61736CD70
虚函数继承FatherFun2地址:00007FF61736CD78
虚函数继承FatherFun3地址:00007FF61736CD80
虚函数sonFun1地址:00007FF61736CD88
虚函数sonFun2地址:00007FF61736CD90
虚函数sonFun3地址:00007FF61736CD98
SonGetFatherFun4
FatherFun1
FatherFun2
FatherFun3
SonFun1
SonFun2
SonFun3
----------------测试父类指针指向子类------------------
类对象地址:0000019CE3F99140
虚函数表地址: 00007FF61736CD68
虚函数继承FatherFun1地址:00007FF61736CD68
虚函数继承FatherFun2地址:00007FF61736CD70
虚函数继承FatherFun3地址:00007FF61736CD78
虚函数sonFun1地址:00007FF61736CD80
虚函数sonFun2地址:00007FF61736CD88
虚函数sonFun3地址:00007FF61736CD90
SonGetFatherFun4
FatherFun1
FatherFun2
FatherFun3
SonFun1
SonFun2
SonFun3
请按任意键继续. . .

实际上和第六点结果是一样的。

8.如果父类有虚函数,子类没有定义虚函数,子类也会有虚函数表

#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#ifdef _WIN64
#define PTR_VAL int64_t
#else
#define PTR_VAL int32_t
#endif
using namespace std;

class Father
{
    virtual void FatherFun1() { cout << "FatherFun1" << endl; }
    virtual void FatherFun2() { cout << "FatherFun2" << endl; }
    virtual void FatherFun3() { cout << "FatherFun3" << endl; }
};

class Son : public Father
{
    //virtual void SonFun1() { cout << "SonFun1" << endl; }
    //virtual void SonFun2() { cout << "SonFun2" << endl; }
    //virtual void SonFun3() { cout << "SonFun3" << endl; }
};

typedef void (*Fun)(void);

int main()
{
    Father father;
    cout << "类对象地址:" << &father << endl;
    cout << "虚函数表地址: " << (PTR_VAL*)*(PTR_VAL*)&father << endl;
    cout << "虚函数FatherFun1地址:" << (PTR_VAL*)*(PTR_VAL*)&father << endl;
    cout << "虚函数FatherFun2地址:" << (PTR_VAL*)*(PTR_VAL*)&father + 1 << endl;
    cout << "虚函数FatherFun3地址:" << (PTR_VAL*)*(PTR_VAL*)&father + 2 << endl;

    Fun fun = (Fun) * (PTR_VAL*)*(PTR_VAL*)&father;
    fun();
    fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(&father) + 1);
    fun();
    fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(&father) + 2);
    fun();

    cout << "----------------测试子类------------------" << endl;

    Son son;
    cout << "类对象地址:" << &son << endl;
    cout << "虚函数表地址: " << (PTR_VAL*)*(PTR_VAL*)&son << endl;
    cout << "虚函数继承FatherFun1地址:" << (PTR_VAL*)*(PTR_VAL*)&son << endl;
    cout << "虚函数继承FatherFun2地址:" << (PTR_VAL*)*(PTR_VAL*)&son + 1 << endl;
    cout << "虚函数继承FatherFun3地址:" << (PTR_VAL*)*(PTR_VAL*)&son + 2 << endl;
    //cout << "虚函数sonFun1地址:" << (PTR_VAL*)*(PTR_VAL*)&son + 3 << endl;
    //cout << "虚函数sonFun2地址:" << (PTR_VAL*)*(PTR_VAL*)&son + 4 << endl;
    //cout << "虚函数sonFun3地址:" << (PTR_VAL*)*(PTR_VAL*)&son + 5 << endl;
    fun = (Fun) * (PTR_VAL*)*(PTR_VAL*)&son;
    fun();
    fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(&son) + 1);
    fun();
    fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(&son) + 2);
    fun();
    //fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(&son) + 3);
    //fun();
    //fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(&son) + 4);
    //fun();
    //fun = (Fun) * ((PTR_VAL*)*(PTR_VAL*)(&son) + 5);
    //fun();

    system("pause");
}

        将子类中的虚函数注释掉,输出结果:

类对象地址:00000097B6CFF958
虚函数表地址: 00007FF6E459BCF8
虚函数FatherFun1地址:00007FF6E459BCF8
虚函数FatherFun2地址:00007FF6E459BD00
虚函数FatherFun3地址:00007FF6E459BD08
FatherFun1
FatherFun2
FatherFun3
----------------测试子类------------------
类对象地址:00000097B6CFF998
虚函数表地址: 00007FF6E459BD50
虚函数继承FatherFun1地址:00007FF6E459BD50
虚函数继承FatherFun2地址:00007FF6E459BD58
虚函数继承FatherFun3地址:00007FF6E459BD60
FatherFun1
FatherFun2
FatherFun3
请按任意键继续. . .

  第6条确认。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1992759.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

国自然即将放榜,还没消息是不是就凉了?

本周投稿推荐 SCI&EI • 医学与心理学&#xff0c;纯正刊&#xff08;基本不拒稿&#xff09; • 1区计算机水刊&#xff0c;3.5-4.0&#xff08;1个月录用&#xff09; • 2区-Top水刊&#xff0c;2.0-3.0&#xff08;沾边可录&#xff09; EI • 各领域沾边均可&am…

超分辨率重建——冠军队EDVR视频超分网络训练自己数据集与推理测试(详细图文教程)

&#x1f4aa; 专业从事且热爱图像处理&#xff0c;图像处理专栏更新如下&#x1f447;&#xff1a; &#x1f4dd;《图像去噪》 &#x1f4dd;《超分辨率重建》 &#x1f4dd;《语义分割》 &#x1f4dd;《风格迁移》 &#x1f4dd;《目标检测》 &#x1f4dd;《暗光增强》 &a…

LeetCode刷题笔记第17题:电话号码的字母组合

LeetCode刷题笔记第17题&#xff1a;电话号码的字母组合 题目&#xff1a; 想法&#xff1a; 先构建手机号码的字典&#xff0c;利用回溯的思想&#xff0c;组合数字对应的字母&#xff0c;代码如下&#xff1a; class Solution:def letterCombinations(self, digits: str) …

Animate软件基本概念:元件(影片剪辑、图形、按钮)

这一篇是说明Animate软件中常见的几种元件类型的定义。 FlashASer&#xff1a;AdobeAnimate2021软件零基础入门教程https://zhuanlan.zhihu.com/p/633230084 FlashASer&#xff1a;实用的各种Adobe Animate软件教程https://zhuanlan.zhihu.com/p/675680471 FlashASer&#x…

数据结构:栈与队列OJ题

目录 前言 一、用栈实现队列 二、用队列实现栈 三、括号匹配问题 前言 前面讲了栈和队列的基础知识&#xff0c;今天来巩固一下加深理解&#xff0c;这里说明一下&#xff0c;因为现在都是在用C语言写&#xff0c;这些OJ题里都要用到前面实现栈和队列的代码&#xff0c;每道题…

Java 自定义注解 笔记总结(油管)

Java系列文章目录 IDEA使用指南 Java泛型总结&#xff08;快速上手详解&#xff09; Java Lambda表达式总结&#xff08;快速上手详解&#xff09; Java Optional容器总结&#xff08;快速上手图解&#xff09; Java 自定义注解笔记总结&#xff08;油管&#xff09; Jav…

AI时代,我们还可以做什么?

最近看了本书&#xff0c;书名叫做《拐点&#xff1a;站在 AI 颠覆世界的前夜》&#xff0c;作者是万维钢。 本想着看完后&#xff0c;就能掌握一整套 AI 技巧&#xff0c;结果——竟然学了很多道理。 这本书讨论了以下话题&#xff1a; 我们该怎么理解这个 AI 大时代的哲学&am…

思迈特发布全新AI应用,Smartbi AIChat白泽来了

8月8日&#xff0c;Smartbi AIChat白泽新品发布会在云端与大家如期美好相约&#xff0c;共同见证思迈特软件基于AI Agent的新一代智能BI应用落地的全新里程碑时刻。 思迈特软件创始人吴华夫和产品总监杨礼显先后围绕商业智能行业发展趋势、产品demo show、技术原理及未来规划展…

Mysql,用户名重复,无法调用问题

问题描述&#xff1a; 我电脑的数据库用户名是&#xff0c;root。 因为经常需要帮别人封装程序&#xff0c;所以需要在我本机跑通别人的程序。有的程序里面也涉及到数据库&#xff0c;用户名也是&#xff0c;root&#xff0c;但是密码与我本机的不同。 之前我会修改我用户名…

【LVS】防火墙mark标记解决调度问题

实验环境是在之前部署DR模式集群的基础上做的&#xff0c;参考如下 部署DR模式集群 以http和https为例&#xff0c;当我们在webserver中同时开放80和443端口&#xff0c;那么默认控制是分开轮询的&#xff0c;就会出现了一个轮询错乱的问题&#xff1a; 当第一次访问80被轮询…

为什么要用数据库管理系统?5个你不得不知道的理由

你是否曾经想过,为什么几乎所有的企业和组织都在使用数据库管理系统(DBMS)?为什么不直接使用文件系统来存储和管理数据呢?如果你有这样的疑问,那么这篇文章正是为你而写。在接下来的内容中,我们将深入探讨使用数据库管理系统的5个关键原因,这些原因将彻底改变你对数据管理的认…

【Kubernetes】pod状态与故障排查

一、Pod启动阶段&#xff08;相位 phase&#xff09; pod创建完之后&#xff0c;一直到持久运行起来&#xff0c;中间有很多步骤&#xff0c;也就有很多出错的可能&#xff0c;因此会有很多不同的状态。 Pod的启动过程如下&#xff1a; 0&#xff09;controller-manager管理的…

qt-06QStackeddialog堆栈窗体应用

QStackeddialog堆栈窗体应用 QStackeddialog.hQStackeddialog.cppmain.cpp运行图 QStackeddialog.h #ifndef QSTACKEDDIALOG_H #define QSTACKEDDIALOG_H#include <QDialog> #include <QListWidget> #include <QStackedWidget> #include <QLabel>clas…

2024年【上海市安全员B证】模拟考试及上海市安全员B证证考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 上海市安全员B证模拟考试参考答案及上海市安全员B证考试试题解析是安全生产模拟考试一点通题库老师及上海市安全员B证操作证已考过的学员汇总&#xff0c;相对有效帮助上海市安全员B证证考试学员顺利通过考试。 1、【…

PySide6||QPushButton的QSS样式

1、狗狗拜按钮 QQ202484-03338 (online-video-cutter.com) /* QPushButton的基本样式 */ QPushButton { background-image:url(:/xxx/第1帧.png); /* 设置背景图片 */ background-repeat: no-repeat; /* 不重复背景图片 */ background-position: center; /* 将背景图片居中…

PS网页版在线制作:云端设计让效率更上一层楼!

在当今的设计行业中&#xff0c;PS曾经被认为是不可替代的工具。然而&#xff0c;对于设计师&#xff0c;尤其是UI设计师来说&#xff0c;PS有点太复杂了。PS更新频率快&#xff0c;稳定性差&#xff0c;对计算机配置要求高。对于初学者来说&#xff0c;这显然是一个“负担”。…

简单Qt贪吃蛇项目

目录 先看效果 项目介绍 界面一&#xff1a;游戏大厅界面 界面二&#xff1a;关卡选择界面​编辑 界面三&#xff1a;游戏界面 游戏大厅页面 游戏关卡选择页面 游戏房间页面 封装贪吃蛇数据结构 初始化游戏房间界面 设置窗口大小、标题、图标等 蛇的移动 初始化贪…

TR3复现Tramsformer

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 前言 Transformer模型是深度学习中的一个革命性架构&#xff0c;自从在NLP领域引入以来&#xff0c;就因其高效处理序列数据的能力而迅速成为主流。本文将通过…

谷粒商城实战笔记-nginx问题记录

记录在使用nginx中遇到的问题。 文章目录 1&#xff0c;网关路由匹配不生效2&#xff0c;网关路由配置前后顺序导致的问题(非nginx问题)3&#xff0c;nginx.conf upstream配置缺少端口4&#xff0c;配置结尾少分号5&#xff0c; proxy_pass 后跟的服务器 URL 是否以 / 结尾5.1 …

C语言学习:汉诺塔问题

汉诺塔_百度百科 (baidu.com)https://baike.baidu.com/item/%E6%B1%89%E8%AF%BA%E5%A1%94/3468295 // // Created by zzh on 2024/8/6. ////汉诺塔问题#include<stdio.h>void move(char x, char y) {printf("%c --> %c \n", x, y); }int hanoi(int count, i…