• 【C++】动态联编、delete/free【有无析构】的使用,虚析构


    动态联编的条件:

    1.必须是公有继承
    2.类中的函数必须为虚函数
    3.必须使用指针和引用的方式进行。(->,&)
    静态联编。,就是我们使用对象名调用我们的函数。函数名和对象的关系在编译阶段就已经确认。

    联编的概念:

    联编是指计算机程序自身彼此关联的过程,是把一个标识符和存储地址连载在一起的过程,也就是把一个对象的操作相结合的过程。

    1. 动态联编:

    如果使用基类指针或引用指明派生类对象,并使用该指针调用虚函数,则程序动态地选择派生类的虚函数,称为动态联编(运行时绑定),也叫滞后联编。
    如:Obect为基类 Base为派生类

    Object *op = *base  op->fun() (动态选择虚函数)
    
    • 1

    2. 静态联编:

    如果使用对象名加.成员函数或者原则运算符“ ”引用特定的一个对象调用虚函数,则被调用的虚函数在编译时是确定的。则是静态联编。
    如:

    virtual void fun();
    Object obj; obj.fun();
    
    • 1
    • 2

    不管对象是够是虚的,均采用静态联编,因为对象和成员函数在编译器就已经绑定。

    静态联编时确认了那些属性:

    1.确认类型(class)
    2.访问限定符
    3.函数默认值(很重要)
    函数的默认值在编译结点就已经固定了。
    注意

    1. 编译的过程和执行的过程一定要分开!!!
    2. 变量的生存期是在运行阶段确定的。
    class Object
    {
    public:
        virtual void print(int x = 20)
        {
            cout << "object ::print::x:" << x << endl;
        }
    };
    class Base :public Object
    {
    
    public:
        //普通成员函数 
        virtual void print(int a)
        {
            cout << "Base ::print::a:" << a << endl;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    int main(void)
    {
    	Base base;
    	Object* op = &base;
    	  //op指向的base对象的地址。
    	op->print();
    	//所以op调用的是base中print()。
    	//但调用的值是在编译期就已经确定的。
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    请添加图片描述
    值在编译阶段,就已经确认。
    请添加图片描述
    调用的是派生类方法。
    **请添加图片描述
    问题
    请添加图片描述

    结论:

    1. 虽然派生类的虚函数会覆盖掉同名的基类虚函数。但是函数默认值在编译阶段就已经确认了。
    2. 上部分代码,实际上,是因为已经编译完成,故基类指针可以访问到。但是派生类指针是无法访问的。因为派生类的默认值属于私有。

    基类指针和派生类指针访问虚函数

    class Object
    {
    private:
        int value;
    public:
        Object(int x = 0) :value(x) {}
        virtual void add() { cout << "Object::add" << endl; }
        virtual void fun() { cout << "Object::fun" << endl; }
    };
    class Base :public Object
    {
    
    private:
        int num;
    public:
        Base(int x = 0) :num(x), Object(x + 10) {}
        virtual void add() { cout << "Base::add" << endl; }
        virtual void fun(int x) { cout << "Base::fun" << endl; }
    
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    int main()
    {
        Base base;
        Object* op = &base;
        Base* bp = &base;
        op->fun();
        op->add();
        op->fun(12); error //无法访问,通过隐藏基类指针,查找派生类虚表,访问不到派生类中有,而基类中没有的虚方法。
        op->Base::fun(12);error//访问不到,如下图:
        bp->add();
        bp->fun();   error   //通过查表的方式,直接访问不到,但是可以通过加作用域范围,进行访问。因为派生类继承它没有
        bp->Object::fun();                                                                    //而基类有的虚函数
        bp->fun(10);
       
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    请添加图片描述
    op能访问:add() fun()
    bp能访问:add() Object::fun() fun(10)
    请添加图片描述

    结论:

    1. 基类指指针只能访问派生类同名覆盖掉和继承的基类中的虚函数,基类中没有而派生类中有的虚函数访问不到。
    2. 而派生类指正均可。如果基类虚函数,派生类中没有,由于是公有继承关系,派生类也可以通过加作用域的方式进行访问。

    delete和free的使用条件:

    1. 没有析构函数时:

    class Object
    {
    private:
        int value;
    public:
        Object(int x = 0) :value(x) {cout<<"create construct" <<endl;}
        //~Object() {}
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    1. 创建一个对象
    OBject *ip = new Object(10);
    
    • 1
    1. 创建10个对象
    OBject *ip = new Object[10];
    
    • 1
    int main()
    {
        //创建了10个对象
        Object* op2 = new Object[10];
        delete[]op2;//ok
        delete op2;//ok
        free(op2);//ok
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    int main()
    {
        //创建一个对象
       Object* op1 = new Object(10);
       free(op1); //ok
        delete(op1); //ok
        delete[]op1;  //ok
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    结论:

    在没有析构函数时,不论申请多少个对象,free,delete detete [] 随便用

    2.有析构函数

    申请一个对象

    OBject *op1 = new Object(10);
    
    • 1

    在申请堆空间:有上越界和下越界标志。
    free(op1);delete(op1);不会读取计数位置。
    delete[]op1; 会把上越界当做计数位读取,从而导致错误。
    请添加图片描述

    申请10个对象

    OBject *op1 = new Object[10];
    
    • 1

    申请10个空间,还会申请计数位置和头部信息。如下图:
    如果使用:free(op1);delete(op1);会导致读取头部信息失败。(因为不会把记录的个数也认为是头部信息,最终在读的时候出问题)
    只有使用 delete[]op1; 才能够获取头部信息和计数位置。

    请添加图片描述

    结论:

    free无法调动析构函数来析构对象,delete无法将记录的对象个个数也认为是头部信息,所以只能使用delete[]来释放多个对象。

    malloc和delete可不可以混用?

    如果是内置类型,就可以。
    如果是自己设置的类型,要看有没有析构函数。
    如果有,如果申请多个对象,就需要调用10次析构函数。

    虚析构函数的使用

    class Object
    {
    private:
    	int value;
    public:
    	Object(int x = 0) : value(x) { cout << "Obejct::Create: " << this << endl; }
    	~Object() { cout << "Obejct::destroy: " << this << endl; }
    };
    
    class Base : public Object
    {
    private:
    	int num;
    public:
    	Base(int x = 10) : Object(x + 10), num(x)
    	{
    		cout << "Base::Create: " << this << endl;
    	}
    	~Base() { cout << "Base::Destroy: " << this << endl; }
    };
    
    int main(void)
    {
    	Object* op = new Base(10);
    
    	delete op;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    请添加图片描述
    当有基类指针指向派生类的对象,如果基类和派生类都有析构函数,那么析构op指向的base对象时,使用的是基类的析构函数,。如果想连级调用base中的析构函数,必须将基类中的析构函数设置为虚函数,那么派生类中的析构函数就会覆盖掉基类中的析构函数,从而调用派生类中的析构函数。
    采用虚析构

     virtual ~Object() { cout << "Obejct::destroy: " << this << endl; }
    
    virtual~Base() { cout << "Base::Destroy: " << this << endl; }
    
    • 1
    • 2
    • 3

    请添加图片描述

    重置虚表

    重置虚表的意义
    当析构完Base中的资源,如果不把虚表指针指向Object的虚表,那么析构Object的时候,会再次析构Base中的add(),从而导致重复析构。

    • 在之前的代码析构函数中加入add()
    class Object
    {
    private:
        int value;
    public:
        Object(int x = 0) : value(x) { cout << "Obejct::Create: " << this << endl; }
        virtual void add() { cout << "Object::add" << endl; }
        ~Object() 
        {
            add();
            cout << "Obejct::destroy: " << this << endl; }
    };
    
    class Base : public Object
    {
    private:
        int num;
    public:
        Base(int x = 10) : Object(x + 10), num(x)
        {
            cout << "Base::Create: " << this << endl;
        }
        virtual void add() { cout << "Object::add" << endl; }
        ~Base()
        {
            add();
            cout << "Base::Destroy: " << this << endl; }
    };
    int main()
    {
        Object* op = new Base(10);
    
        delete op;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    请添加图片描述

    构造函数:置虚表

    :调用object类中的构造函数,创建隐藏基类对象,让该对象内保存的虚表指针,指向虚表。即为置虚表
    上面的程序;是先new一个base对象,先构造隐藏基类对象,使其内的虚表指针指向Object的虚表,当在此对base实例化时,基类的虚表指针会指向Base的虚表。
    如下如:
    请添加图片描述

    析构函数重置虚表

    在析构过程中,析构函数没第一部是先将虚表指针重新指向该对象的虚表。
    因此会出现这个结果:请添加图片描述
    很明显,虚表指针指向Base的虚表时,析构base对象
    虚表指针指向Object对象时,析构隐藏基类对象。
    请添加图片描述

  • 相关阅读:
    服务器迁移踩的坑
    学习太极创客 — MQTT 第二章(二)ESP8266 QoS 应用
    Linux下automake应用
    MySQL 1055报错 -this is incompatible with sql_mode=only_full_group_by
    杜教筛练习题
    【正点原子FPGA连载】第九章 按键控制LED实验摘自【正点原子】DFZU2EG/4EV MPSoC 之FPGA开发指南V1.0
    【数据结构】树与二叉树(六):二叉树的链式存储
    Thinkphp6 分布式事务异常处理 1440 XAER_DUPID: The XID already exists
    2022年最新山西机动车签字授权人模拟考试及答案
    mac的node版本安装及升降级
  • 原文地址:https://blog.csdn.net/weixin_52958292/article/details/127587037