3.虚函数

所有代码运行在64位系统环境

1.虚函数表指针位置分析

#include <iostream>
using namespace std;

class A
{
public:
	int i; //4字节
	virtual void testfunc() {}  //虚函数,vptr8字节。
};

int main()
{
	//虚函数表指针位置分析
	//类:有虚函数,这个类会产生一个虚函数表。
	//类对象,有一个指针,指针(vptr)会指向这个虚函数表的开始地址。
	A aobj;
	int ilen = sizeof(aobj);
	cout << ilen << endl;  //16字节

	char *p1 = reinterpret_cast<char *>(&aobj); //类型转换,硬转 &aobj这是对象aobj的首地址。
	char *p2 = reinterpret_cast<char *>(&(aobj.i));
	if (p1 == p2) //说明aobj.i和aobj的位置相同,说明i在对象aobj内存布局的上边。虚函数表指针vptr在下边
	{
		cout << "虚函数表指针位于对象内存的末尾" << endl;
	}
	else
	{
		cout << "虚函数表指针位于对象内存的开头" << endl;//√
	}

	return 1;
}

2. 继承关系作用下虚函数的手工调用

#include <iostream>
using namespace std;
typedef void(*Func)(void); //定义一个函数指针类型
//父类
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 g() { cout << "Derive::g()" << endl; }
};

int main()
{
	//继承关系作用下虚函数的手工调用			
	cout << sizeof(Base) << endl;	
	cout << sizeof(Derive) << endl;

	Derive *d = new Derive(); //派生类指针。
	long *pvptr = (long *)d;  //指向对象的指针d转成了long *类型。
	long *vptr = (long *)(*pvptr); //(*pvptr) 表示pvptr指向的对象,也就是Derive本身。Derive对象是8字节的,代表的是虚函数表指针地址。

	for (int i = 0; i <= 2; i++) //循环3次;
	{
		printf("vptr[%d] = 0x:%p\n", i, vptr[i]);
	}

	
	Func f = (Func)vptr[0]; //f就是函数指针变量。 vptr[0]是指向第一个虚函数的。
	Func g = (Func)vptr[1];
	Func h = (Func)vptr[2];
	/*Func i = (Func)vptr[3];
	Func j = (Func)vptr[4];*/

	f();
	g();
	h();
	//i();

	Base *dpar = new Base();
	long *pvptrpar = (long *)dpar;
	long *vptrpar = (long *)(*pvptrpar);

	for (int i = 0; i <= 2; i++) //循环3次;
	{
		printf("vptr Base[%d] = 0x:%p\n", i, vptrpar[i]);
	}

	Func fpar = (Func)vptrpar[0]; 
	Func gpar = (Func)vptrpar[1];
	Func hpar = (Func)vptrpar[2];

	cout << "--------------------" << endl;
	fpar(); 
	gpar();
	hpar();

	return 1; 
}
/*
ubuntu下输出:
	8
	8
	vptr[0] = 0x:0x564295e94e24
	vptr[1] = 0x:0x564295e94ecc
	vptr[2] = 0x:0x564295e94e94
	Base::f()
	Derive::g()
	Base::h()
	vptr Base[0] = 0x:0x564295e94e24
	vptr Base[1] = 0x:0x564295e94e5c
	vptr Base[2] = 0x:0x564295e94e94
	--------------------
	Base::f()
	Base::g()
	Base::h()

*/

下面代码的pvptr实际上是指向对象d的指针
pvptr_b实际上是指向对象b的指针

在这里插入图片描述
输出:
在这里插入图片描述

3.虚函数表分析

(1)一个类只有包含虚函数才会存在虚函数表,同属于一个类的对象共享虚函数表,但是有各自的vptr(虚函数表指针),当然所指向的地址(虚函数表首地址)相同。
(2)父类中有虚函数就等于子类中有虚函数。话句话来说,父类中有虚函数表,则子类中肯定有虚函数表。因为你是继承父类的。也有人认为,如果子类中把父类的虚函数的virtual去掉,
是不是这些函数就不再是虚函数了?只要在父类中是虚函数,那么子类中即便不写virtual,也依旧是虚函数。但不管是父类还是子类,都只会有一个虚函数表,不能认为子类中有一个虚函数表+父类中有一个虚函数表,
而得到一个结论:子类中有两个虚函数表。子类中是否可能会有多个虚函数表呢?后续我们讲解这个事;
(3)如果子类中完全没有新的虚函数,则我们可以认为子类的虚函数表和父类的虚函数表内容相同。 但,仅仅是内容相同,这两个虚函数表在内存中处于不同位置,换句话来说,这是内容相同的两张表。
虚函数表中每一项,保存着一个虚函数的首地址,但如果子类的虚函数表某项和父类的虚函数表某项代表同一个函数(这表示子类没有覆盖父类的虚函数),则该表项所执行的该函数的地址应该相同。
(4)超出虚函数表部分内容不可知;

c++通过类的指针和引用来支持多态

静态联编和动态联编

#include <iostream>
using namespace std;

typedef void(*Func)(void); //定义一个函数指针类型

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 g()  { cout << "Derive::g()" << endl; }
};

int main()
{
	Derive derive;
	//不支持多态
	Base base = derive; //直接用子类对象给父类对象值,子类中的属于父类那部分内容会被编译器自动区分(切割)出来并拷贝给了父类对象。
						//所以Base base = derive;实际干了两个事情:
						//第一个事情:生成一个base对象
						//第二个事情:用derive来初始化base对象的值。
						//这里编译器给咱们做了一个选择,显然derive初始化base对象的时候,
						//derive的虚函数表指针值并没有覆盖base对象的虚函数表指针值,编译器帮我们做到了这点;
	long *pvptrbase = (long *)(&base);
	long *vptrbase = (long *)(*pvptrbase); 
	Func fb1 = (Func)vptrbase[0];   
	Func fb2 = (Func)vptrbase[1];   
	Func fb3 = (Func)vptrbase[2];   
	

	fb1();
	fb2();
	fb3();

	cout << "----------------------------" << endl;
	//多态,动态联编
	Base * base2 = new Derive();
	long * pvptr = (long*)base2;
	long * vptr = (long*)*pvptr;
	Func f1 = (Func)vptr[0];
	Func f2 = (Func)vptr[1];
	Func f3 = (Func)vptr[2];
	f1();
	f2();
	f3();
	cout << "----------------------------" << endl;
	//多态
	Base &base3 = derive;
	long *pvptr3 = (long *)(&base3);
	long *vptr3 = (long *)(*pvptr3);
	Func f4 = (Func)vptr3[0];
	Func f5 = (Func)vptr3[1];
	Func f6 = (Func)vptr3[2];
	f4();
	f5();
	f6(); 
	/*
		输出:
		Base::f()
		Base::g()
		Base::h()
		----------------------------
		Base::f()
		Derive::g()
		Base::h()
		----------------------------
		Base::f()
		Derive::g()
		Base::h()

	*/
	//OO(面向对象)  和OB(基于对象)概念:
	//c++通过类的指针和引用来支持多态,这是一种程序设计风格,这就是我们常说的面向对象。object-oriented model;
	//OB(object-based),也叫ADT抽象数据模型【abstract datatype model】,不支持多态,执行速度更快
    //因为 函数调用的解析不需要运行时决定(没有多态),而是在编译期间就解析完成,内存空间紧凑程度上更紧凑,因为没有虚函数指针和虚函数表这些概念了;
	//Base *pbase = new Derive();
	//Base &base2 = derive2;
	//但显然,OB的设计灵活性就差;
	//c++既支持面向对象程序设计(继承,多态)(OO),也支持基于对象(OB)程序设计。


	return 1;
}

4.多继承虚函数表分析

#include <iostream>
using namespace std;

typedef void(*Func)(void);
//基类1
class Base1
{
public:
	virtual void f()
	{
		cout << "base1::f()" << endl;
	}
	virtual void g()
	{
		cout << "base1::g()" << endl;
	}
};

//基类2
class Base2
{
public:
	virtual void h()
	{
		cout << "base2::h()" << endl;
	}
	virtual void i()
	{
		cout << "base2::i()" << endl;
	}
};

//子类
class Derived :public Base1, public Base2
{
public:
	virtual void f() //覆盖父类1的虚函数
	{
		cout << "derived::f()" << endl;
	}
	virtual void i() //覆盖父类2的虚函数
	{
		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;
	cout << sizeof(Base2) << endl;
	cout << sizeof(Derived) << endl;


	Derived ins;
	Base1 &b1 = ins; //多态
	Base2 &b2 = ins;//多态
	Derived &d = ins;

	

	long *pderived1 = (long *)(&ins);//基类对象地址
	long *vptr1 = (long *)(pderived1[0]); //取第一个虚函数表指针。
	long *vptr2 = (long *)(pderived1[1]); //取第二个虚函数表指针。

	cout << "基类第一个虚函数表地址:" << vptr1 << endl;//相等于下下行
	cout << "基类第二个虚函数表地址:" << vptr2 << endl;//想等于下下行
	cout << "b1的虚函数表地址" << (long*)*(long*)&b1 << endl;//
	cout << "b2的虚函数表地址" << (long*)*(long*)&b2 << endl;
	

	Func f1 = (Func)vptr1[0]; 
	Func f2 = (Func)vptr1[1]; 
	Func f3 = (Func)vptr1[2]; 
	Func f4 = (Func)vptr1[3]; 
	Func f5 = (Func)vptr1[4]; 
	Func f6 = (Func)vptr1[5]; //非正常


	Func f11 = (Func)vptr2[0]; 
	Func f22 = (Func)vptr2[1];
	Func f33 = (Func)vptr2[2]; //非正常


	b1.f();
	b2.i();
	d.f();
	d.i();
	d.mh();
	d.g();

	//----------------
	cout << "----------------------" << endl;
	f1();
	f2();
	f3();
	f4();
	f5();

	cout << "----------------------" << endl;
	f11();
	f22();

	/*
	输出:
		8
		8
		16
		基类第一个虚函数表地址:0x55729eec9cc8
		基类第二个虚函数表地址:0x55729eec9d08
		b1的虚函数表地址0x55729eec9cc8
		b2的虚函数表地址0x55729eec9d08
		derived::f()
		derived::i()
		derived::f()
		derived::i()
		derived::mh()
		base1::g()
		----------------------
		derived::f()
		base1::g()
		derived::i()
		derived::mh()
		derived::mi()
		----------------------
		base2::h()
		derived::i()

	
	*/

	//说明
	//一个对象,如果它的类有多个基类则有多个虚函数表指针;

	return 1;
}

在这里插入图片描述

5.vptr、vtbl创建时机

1.vptr(虚函数表指针)什么时候创建出来的?
vptr跟着对象走,所以对象什么时候创建出来,vptr就什么时候创建出来。运行的时候;
实际上,对于这种有虚函数的类,在编译的时候,编译器会往相关的构造函数中增加 为vptr赋值的代码,这是在编译期间编译器为构造函数增加的。
这属于编译器默默为我们做的事,我们并不清楚。
当程序运行的时候,遇到创建对象的代码,执行对象的构造函数,那么这个构造函数里有 给对象的vptr(成员变量)赋值的语句,自然这个对象的vptr就被赋值了;

2.虚函数表是什么时候创建的?
实际上,虚函数表是编译器在编译期间(不是运行期间)就为每个类确定好了对应的虚函数表vtbl的内容。
然后也是在编译器期间在相应的类构造函数中添加给vptr赋值的代码,这样程序运行的时候,当运行到成成类对象的代码时,会调用类的构造函数,执行到类的构造
函数中的 给vptr赋值的代码,这样这个类对象的vptr(虚函数表指针)就有值了;

6. 单纯的类不纯时引发的虚函数调用问题

在这里插入图片描述

在这里插入图片描述

多继承子类对象空间布局

#include <iostream>
using namespace std;

typedef void(*Func)(void);
//基类1
class Base1
{
	
public:
	int m1;
	virtual void f()
	{
		cout << "base1::f()" << endl;
	}
	virtual void g()
	{
		cout << "base1::g()" << endl;
	}
};

//基类2
class Base2
{
	
public:
	int m2;
	virtual void h()
	{
		cout << "base2::h()" << endl;
	}
	virtual void i()
	{
		cout << "base2::i()" << endl;
	}
};

//子类
class Derived :public Base1, public Base2
{
	
public:
	int m3;
	virtual void f() //覆盖父类1的虚函数
	{
		cout << "derived::f()" << endl;
	}
	virtual void i() //覆盖父类2的虚函数
	{
		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;//16
	cout << sizeof(Base2) << endl;//16
	cout << sizeof(Derived) << endl;//40


	Derived ins;
	ins.m1 = 1;
	ins.m2 = 2;
	ins.m3 = 3;
	

	return 1;
}

蓝色的是两根vptr
3个红色圈分别是父类1,父类2,和自己的成员变量,剩余是补齐
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值