C++对象模型学习笔记十四: 单纯的类不纯时引发的虚函数调用问题

  单纯的类:比较简单的类,尤其不包含 虚函数和虚基类。
  虚函数,多态,这种概念专门给指针或者引用用的;

测试代码如下:

//  该实例演示在不不纯类(包含虚函数和虚基类)的构造函数中使用memset或者拷贝构造函数中使用memcpy方法,会出现程序崩溃的情形。
//  在栈声明的对象 调用虚函数和在堆上声明的对象 调用虚函数是不同的,一个是静态联编,一个是动态联编通过虚函数表获取虚函数指针后调用。
//  总结: 虚函数,多态,这种概念专门给指针或者引用用的;

#include <iostream>
#include <time.h >
using namespace std;

class X
{
public:
	int x;
	int y;
	int z;
	//X() :x(0), y(0), z(0)
	X()
	{
		//编译器角度 伪码;
		//vptr = vtbl; //下边的memset会把vptr(虚函数表指针)清0

		memset(this, 0, sizeof(X));
		cout << "构造函数被执行" << endl;
	}
	//X(const X &tm) :x(tm.x), y(tm.y), z(tm.z)
	X(const X &tm)
	{
		memcpy(this, &tm, sizeof(X));
		cout << "拷贝构造函数被执行" << endl;
	}
	virtual ~X()
	{
		cout << "析构函数被执行" << endl;
	}
	virtual void virfunc()
	{
		cout << "虚函数virfunc()被执行" << endl;
	}
	void ptfunc()
	{
		cout << "普通函数ptfunc()被执行" << endl;
	}
};
int main()
{
	// 单纯的类:比较简单的类,尤其不包含 虚函数和虚基类。
	//X x0;  //调用构造函数
	///*x0.x = 100;
	//x0.y = 200;
	//x0.z = 300;*/
	//x0.virfunc(); //虚函数表指针为null居然可以成功调用虚函数;

	//X x1(x0); //调用拷贝构造函数
	//cout << "x1.x=" << x1.x << " x1.y=" << x1.y << " x1.z=" << x1.z << endl;

	//如果类并不单纯,那么在构造函数中使用如上所示的memset或者拷贝构造函数中使用如上所示的memcpy方法,那么就会出现程序崩溃的情形;
	//那就是某些情况下,编译器会往类内部增加一些我们看不见 但真实存在的成员变量(隐藏成员变量),有了这种变量的类,就不单纯了;
	//同时,这种隐藏的成员变量的 增加(使用) 或者赋值的时机,往往都是在 执行构造函数或者拷贝构造函数的函数体之前进行。
	//那么你如果使用memset,memcpy,很可能把编译器给隐藏变量的值你就给清空了,要么覆盖了;

	//比如你类中增加了虚函数,系统默认往类对象中增加 虚函数表指针,这个虚函数表指针就是隐藏的成员变量。

	//X *px0 = new X();
	//px0->ptfunc(); //正常调用
	//px0->virfunc(); //无法正常调用
	//delete px0; //无法正常调用
	//new出来的对象,虚函数变得无法正常执行了;

	//针对上面 X x0; 和X *px0 = new X(); 前面一个调用虚函数正常,后一个调用虚函数不正常 的原因,解释如下: X x0;x0.virfunc(); 这里调用虚函数是静态联编,在编译期就确定了地址
	//所以memset()把虚函数指针置空情况下,对虚函数调用没有什么影响,X *pX0 = new X();pX0->virfunc(); 这里调用虚函数是动态联编,需要通过虚函数表指针找到函数执行,但这里虚函数表指针已经被memset置空,所以报错。
	//下面代码验证

	//对多态,虚函数,父类子类。虚函数,主要解决的问题父类指针指向子类对象这种情况。
	//只有虚函数,没有继承,那么虚函数和普通函数有啥区别呢? 可以认为此时就没啥时机区别。

	int i = 9;
	printf("i的地址 = %p\n", &i);
	X x0;
	printf("ptfunc()的地址=%p\n", &X::ptfunc); //打印正常的成员函数地址。
											//long *pvptrpar = (long *)(&x0);
											//long *vptrpar = (long *)(*pvptrpar);
											//printf("virfunc的地址 = %p\n", vptrpar[1]);//虚函数virfunc地址
	x0.ptfunc();
	x0.virfunc(); //不叫多态,属于静态联编
				  //我们推断:这个函数ptfunc()和virfunc()函数,是在编译的就确定好的;
				  //静态联编 和动态联编。
				  //静态联编 :我们编译的时候就能确定调用哪个函数。把调用语句和倍调用函数绑定到一起;
				  //动态联编:是在程序运行时,根据时机情况,动态的把调用语句和被调用函数绑定到一起,动态联编一般旨有在多态和虚函数情况下才存在。

	X *pX0 = new X();
	pX0->ptfunc();
	pX0->virfunc(); //通过虚函数表指针,找虚函数表,然后从虚函数表中找到virfunc虚函数的地址并调用。

					//更明白:虚函数,多态,这种概念专门给指针或者引用用的;
	X &xy = *pX0;
	xy.virfunc();
	X &xy2 = x0;
	xy2.virfunc();


	return 1;
}


运行结果:

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值