zoukankan      html  css  js  c++  java
  • C++析构函数、构造函数、虚函数关系

    析构函数(destructor)也是一个特殊的成员函数,它的作用与构造函数相反,它的名字是类名的前面加一个“~”符号。

    在C++中“~”是位取反运算符,从这点也可以想到:析构函数是与构造函数作用相反的函数。当对象的生命期结束时,会自动执行析构函数。

    具体地说如果出现以下几种情况,程序就会执行析构函数:

    ①如果在一个函数中定义了一个对象(它是自动局部对象),当这个函数被调用结束时,对象应该释放,在对象释放前自动执行析构函数。

    ②static局部对象在函数调用结束时对象并不释放,因此也不调用析构函数,只在main函数结束或调用exit函数结束程序时,才调用static局部对象的析构函数。

    #include <string>
    #include <iostream>
    
    using namespace std;
    
    class Box
    {
    public:
        ~Box()
        {
            cout <<"Destructed called" <<height<<endl;
        }
        Box(int h=10,int w=12,int len=3):height(h),width(w),length(len){}
        
        int volume();
    private:
        int height;
        int width;
        int length;
    };
    
    int Box::volume()
    {
        return (height * width * length);
    }
    int main()
    {
        static Box a[2]={Box(1,2,3),Box(2,3,4)};
    
        cout <<"The volume a[0] is" <<a[0].volume()<<endl;
        cout <<"The volume a[1] is" <<a[1].volume()<<endl;
    
        //system("pause");
    
        return 0;
    }

     运行结果:

    如果执行注释的那行,结果又是如何??自己去尝试一下!!

    ③如果定义了一个全局对象,则在程序的流程离开其作用域时(如main函数结束或调用exit函数) 时,调用该全局对象的析构函数。

    一个很典型的例子:

    #include <iostream>
    #include <string>
    #include <cstdlib>
    using namespace std;
    
    class CDemo
    {
    public:
        CDemo(const char *str);
        ~CDemo();
    
    private:
        char name[20];
    };
    
    CDemo::CDemo(const char*str)
    {
        strcpy(name,str);
        cout <<"Construction Called For "<<name <<endl;
    }
    
    CDemo::~CDemo()
    {
        cout <<"Destruction Called For " <<name <<endl;
        //printf(" Destruction called for %s\n",name); 
    }
    
    void func()
    {
        static CDemo StaticObject="Staticobject";
        CDemo *pHeapObjectInFunc = new CDemo("heapobjectinfunc");
    
        CDemo LocalObjectInFunc("localobjectinfunc");
        
        cout <<"inside func"<<endl;
    }
    
    static CDemo GlobleObject("globeobject");
    
    int main()
    {
        CDemo LocalObjectInMain("localobjectinmain");
        
        CDemo *pHeapObjectInMain = new CDemo("heapobjectinmain");
        
        cout <<"In main,before calling func"<<endl;
        
        func();
        
        cout <<"In main,after calling func\n";
        exit(1);
        return 0;
    }

    先不要看运行结果,跟大家一起分析一下代码运行的过程:
    根据代码数据类型大致分为三个部分:静态全局变量(static CDemo GlobleObject),静态局部变量(static CDemo StaticObject),普通局部变量(CDemo LocalObjectInMain.....);

    1、由于静态全局对象的构造函数将在main函数之前运行,则析构在main函数之后运行

    2、函数内部的static对象的构造函数将在第一次调用该函数时调用,main函数结束之后执行。但其析构会在全局对象之前,因为所有对象的析构顺序和构造顺序相反,即全局对象在函数内部的static对象前构造,在后面析构。若包含静态局部对象的函数未被调用,则也不进行析构

    其运行结果:

    此代码有个小问题就是当运行注释的那一行时,结果又会不一样(估计是VC和标准C++之间的区别吧!!)

    ④如果用new运算符动态地建立了一个对象,当用delete运算符释放该对象时,先调用该对象的析构函数。

    #include <iostream>
    using namespace std;
    
    class A
    {
    public:
        A()
        {
            cout <<"A..."<<endl;
        }
        ~A()
        {
            cout <<"~A..."<<endl;    
        }
    };
    
    class B :public A
    {
    public :
        B()
        {
            cout <<"B..."<<endl;
        }
        ~B()
        {
            cout <<"~B..."<<endl;
            
        }    
    };
    
    int main()
    {
        A *a = new B();
        delete a;
    
        return 0 ;
    }

    代码分析:1、大家都知道构造函数里就可以调用成员变量,而继承中子类是把基类的成员变成自己的成员,那么也就是说子类在构造函数里就可以调用基类的成员了,这就说明创建子类的时候必须先调用基类的构造函数,只有这样子类才能在构造函数里使用基类的成员,所以是创建子类时先调用基类的构造函数然后再调用自己的构造函数。通俗点说,你要用某些物品,但这些物品你没办法自己生产,自然就要等别人生产出来,你才能拿来用。

        2、接着就是析构函数了,上面说到子类是将基类的成员变成自己的成员,那么基类就会只存在子类中直到子类调用析构函数后,做个假设:假如在基类的析构函数调用比子类的先,这样会发生什么事呢?类成员终止了,而类本身却还在,但是在类存在的情况下,类成员就应该还存在的,这不就产生矛盾了吗?所以子类是调用自身的析构函数再调用基类的析构函数。

    运行结果: A...
          B...
          ~A...

    修改一下以上代码:

    #include <iostream>
    using namespace std;
    
    class A
    {
    public:
        A()
        {
            cout <<"A..."<<endl;
        }
        virtual ~A()
        {
            cout <<"~A..."<<endl;    
        }
    };
    
    class B :public A
    {
    public :
        B()
        {
            cout <<"B..."<<endl;
        }
        ~B()
        {
            cout <<"~B..."<<endl;
            
        }    
    };
    
    int main()
    {
        A *a = new B();
        delete a;
    
        return 0 ;
    }

    运行结果:A...
         B...
           ~B...

            ~A


    那么这是为什么呢??也许delete并不是那么傻,她会去虚函数表里面查看当前析构函数是否被定义为虚函数,如果是虚函数,它就会想了,那对象会不会实例化的是子类的对象呢,于是再判断是否实例化为子类对象,如果是就先调用子类的析构函数

    总结:析构函数的作用并不是删除对象,而是在撤销对象占用的内存之前完成一些清理工作,使这部分内存可以被程序分配给新对象使用。程序设计者事先设计好析构函数,以完成所需的功能,只要对象的生命期结束,程序就自动执行析构函数来完成这些工作。

  • 相关阅读:
    宋宝华:slab在内核内存管理和用户态Memcached的双重存在
    能感知功耗的Linux调度器(EAS)
    内存检测王者之剑—valgrind
    随心所动,厂商的CPU核管理策略介绍
    一文读懂 进程怎么绑定 CPU
    Fastbootd实现原理分析
    cachestat、cachetop、pcstat-linux系统缓存命中率分析工具
    WIFI的WPS和pin码(测试失败)
    视频下载(钉钉、B站等) 解决方案
    DevExpress 报表设计文件(.vsrepx)不显示或显示空白
  • 原文地址:https://www.cnblogs.com/haoyuanyuan/p/2873584.html
Copyright © 2011-2022 走看看