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

#include<iostream>

using namespace std;

class X
{
public:
        int m_i;
        int m_j;
        int m_k;
public:
    X()
    {
        memset(this,0,sizeof(X));//用于初始化,3个成员变量初值为0
        cout<<"构造函数被调用"<<endl;
    }
    X(const X& tm)
    {
        memcpy(this,&tm,sizeof(X));
        cout<<"拷贝构造函数被调用"<<endl;
    }
    virtual void fun()
    {
        cout<<"虚函数被调用"<<endl;
    }
    virtual ~X()
    {
        cout<<"虚析构函数被调用"<<endl;
    }
};

int main(void)
{
    //单纯的类不纯时引发的虚函数调用问题
    //单纯的类:比较简单的类,尤其不包含虚函数和虚基类

    X x0;//调用构造函数
    x0.m_i=10;
    x0.m_j=20;
    x0.m_k=30;
    x0.fun();

    //执行以上代码惊奇的发现依然可以调用虚函数。

    X x1(x0);//调用拷贝构造函数

    X *p = new X();
    delete p;//程序执行到这一行时发生异常,由于无法调用虚析构函数引发的异常。
    /*以上程序,用到了memset(this,0,sizeof(X)); memcpy(this,&tm,sizeof(X));
        用的时机分别是在定义X x0和X x1(x0)时,这时如果类中没有虚函数程序就不会出现异常。
    */
 
   /*因为现在类里面有虚函数,来看看会出现什么问题吧*/

    /*
    因为现在类里面有虚函数,一般来说,就会有虚函数表指针,编译器在给虚函数表指针值的时候
    是在构造函数体执行之前就已经赋值了的(这是编译器的行为),所以可以想象一下,我们在构造函数体里才
    用了memset(this,0,sizeof(X))这个函数,,所以这个时候,虚函数表指针的值就会变成0,相当于我们
    覆盖了编译器给虚函数表指针的值。由于这个现象的存在会不会导致虚函数无法调用了呢?

    答案是不会,这个可以自己手工调用一下,看是否会引发异常。为什么不会呢?
    如果我们在定义类对象时是new出来的对象,这个时候,虚函数调用就会出现异常了,而像 X x0这样定义对象
    就不会,,为什么呢?

    答:由于类中的函数是在编译时就已经确定了地址了(静态联编),所以即使用了memset函数覆盖了虚函数表指针
    的值后,编译器还是知道该如何去调用本类的对象,而如果是new出来的对象,它的函数地址是在运行时才分配的
    ,所以在用了这个函数memset以后,覆盖了虚函数表指针的值以后,编译器就不知道去哪调用虚函数了。
   */
}

完整源代码学习笔记:

// project100.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include "pch.h"
#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出来的对象,虚函数变得无法正常执行了;

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


	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;
}
上一篇:用牛顿迭代法求根。方程为$ax^3+bx^2 +cx+d=0$,系数a,b,c,d的值依次为1,2,3,4,由主函数输人。求x在1附近的一个实根。求出根后由主函数输出


下一篇:套路总结