C++对象模型之虚函数

本文探讨了C++中的虚函数表,包括虚函数表指针的位置分析、继承关系下的虚函数调用以及虚函数表的详细分析。在继承关系中,父类对象调用虚函数执行父类实现,而子类则调用自己的实现。每个包含虚函数的类都有一个虚函数表,并且每个对象都有指向该表的指针。在多重继承情况下,子类可能有多个虚函数表指针,分别对应不同的基类。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

本次笔记记录如下知识点

  • 虚函数表指针位置分析
  • 继承关系下虚函数的调用
  • 虚函数表的分析

一、虚函数表指针位置分析

  • 一个类中如果有虚函数,针对这个类会产生一个虚函数表,生成这个类对象的时候,该对象就有一个指针用来指向这个虚函数表的起始位置。
  • 虚函数表指针位于对象内存的起始位置。
    如下示例:
#include <iostream>
using namespace std;

class A{

public:
   int i;
   virtual void fun(){ cout<<"虚函数执行";};
};

int main()
{
   A a;
   int len = sizeof(a);
   cout<< "虚函数指针的地址:"<< (int*)&a <<endl;;
   /*
        reinterpret_cast 用于进行各种不同类型的指针之间、不同
      	 类型的引用之间以及指针和能容纳指针的整数类型之间的转换。
   */
   char *p1  = reinterpret_cast<char*>(&a);
   char *p2  = reinterpret_cast<char*>(&a.i);
   //判断&a.i的地址是否为&a,如果不是,说明虚函数指针在内存起始开头
   if(p1 == p2)
   {
   	cout<<"虚函数表指针位于对象内存的末尾";
   }
   else
   {
   	cout<<"虚函数表指针位于对象内存的开头";
   }

   return 0;
}

结果如下:
在这里插入图片描述

二、继承关系下虚函数的调用

  • 父类对象调用的虚函数都是父类的。
  • 子类和父类如果虚函数相同,子类会调用子类的虚函数。
    如下示例:
#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{
public:
	void g() {cout<<"Derive"<<endl;;}
};

//定义函数指针
typedef void (*Func)(void);
int main()
{
	cout << sizeof(Base) <<endl;
	cout << sizeof(Derive) <<endl;
	Derive* d = new Derive();
	long* pvptr = (long*)d;
	//将对象d转换成虚函数表指针vptr
	long* vptr  = (long*)(*pvptr);
	//打印虚函数地址
	for(int i = 0;i<=4;i++)
	{
		cout<<"vptr"<<i<<";0x:"<<vptr[i]<<endl;
	}
	Func f = (Func)vptr[0];
	Func g = (Func)vptr[1];
	Func h = (Func)vptr[2];
	Func i = (Func)vptr[3];
	Func j = (Func)vptr[4];
	//打印虚函数  i() j()两个运行异常
	f();
	g();
	h();
	//创建父类对象,调用虚函数
	Base* dpar = new Base();
	long* pvptrpar = (long*)dpar;
	long* vptrpar = (long*)(*pvptrpar);
	//打印虚函数地址
	for(int i = 0;i<=4;i++)
	{
		cout<<"vptr"<<i<<";0x:"<<vptrpar[i]<<endl;
	}
	Func fpar = (Func)vptrpar[0];
	Func gpar = (Func)vptrpar[1];
	Func hpar = (Func)vptrpar[2];
	Func ipar = (Func)vptrpar[3];
	Func jpar = (Func)vptrpar[4];
	//打印虚函数  ipar() jpar()两个运行异常
	fpar();
	gpar();
	hpar();
	return 0;
}

运行结果如下:
在这里插入图片描述

三、虚函数表的分析

分析父类对象和子类对象内存布局

在这里插入图片描述
通过上图我们分析如下几个知识点

1、包含虚函数的类才会有虚函数表,同属于一个类的对象共享这个虚函数表,但是每个对象都有各自的vptr(虚函数指针),指针指向的地址是相同的。
2、父类中有虚函数等于子类中有虚函数。
3、如果子类完全没有新的虚函数,则可以认为子类的虚函数表和父类的虚函数表内容一致。
4、超出虚函数表部分的内存不可知也不可预测。

多重继承虚函数表分析

代码如下

class Base1
{
public:
	virtual void f() {cout<<"Base1::f()"<<endl;}
	virtual void g() {cout<<"Base1::g()"<<endl;}
	
};

class Base2
{
public:
	virtual void h() {cout<<"Base2::h()"<<endl;}
	virtual void i() {cout<<"Base2::i()"<<endl;}
};

class Derived:public Base1,public Base2
{
	virtual void f() {cout<<"Derived::f()"<<endl;}
	virtual void i() {cout<<"Derived::i()"<<endl;}
	virtual void mh() {cout<<"Derived::mh()"<<endl;}
	virtual void mi() {cout<<"Derived::mi()"<<endl;}
	virtual void mj() {cout<<"Derived::mj()"<<endl;}
};

int main()
{
	cout<<sizeof(Base1)<<endl; //4
	cout<<sizeof(Base2)<<endl; //4
	cout<<sizeof(Derived)<<endl;//8
	Derived ins;
	return 0;
}

代码结构如下如所示
在这里插入图片描述

通过上图我们分析如下几个知识点

1、子类对象ins有两个虚函数指针--------vptr1和vptr2
2、类Derived有两个虚函数表,因为它继承自两个基类
3、子类和第一个基类共用一个vptr
4、子类中的虚函数覆盖了父类中的同名虚函数。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值