zoukankan      html  css  js  c++  java
  • 构造函数与析构函数的调用顺序

    摘自:http://blog.csdn.net/bresponse/article/details/6914155  部分修改


    构造函数
     

       先看看构造函数的调用顺序规则,只要我们在平时编程的时候遵守这种约定,任何关于构造函数的调用问题都能解决;构造函数的调用顺序总是如下:
    1.基类构造函数。如果有多个基类,则构造函数的调用顺序是某类在类派生表中出现的顺序,而不是它们在成员初始化表中的顺序。
    2.成员类对象构造函数。如果有多个成员类对象则构造函数的调用顺序是对象在类中被声明的顺序,而不是它们出现在成员初始化表中的顺序。
    3.派生类构造函数。

    析构函数
        析构函数的调用顺序与构造函数的调用顺序正好相反,将上面3个点反过来用就可以了,首先调用派生类的析构函数;其次再调用成员类对象的析构函数;最后调用基类的析构函数。
        析构函数在下边3种情况时被调用:
        1.对象生命周期结束,被销毁时(一般类成员的指针变量都不自动调用析构函数,因为默认析构函数不会释放类型为内置指针的成员变量或对象指向的空间;);
        2.delete指向对象的指针时;或delete指向对象的基类类型指针,而其基类虚构函数是虚函数时;
        3.对象i是对象o的成员,o的析构函数被调用时,对象i的析构函数也被调用。

    下面用例子来说说构造函数的的调用顺序:
    #include <iostream>
    using namespace std;
    
    class X
    {
        public:
            X()
            {
                cout << "X构造函数" << endl;
            }
            ~X()
            {
                cout << "X析构函数" << endl;
            }
    
    
    };
    
    class A
    {
        public:
            A()
            {
                cout << "A构造函数" << endl;
            }
            ~A()
            {
                cout << "A析构函数" << endl;
            }
    };
    class B:public A
    {
        public:
            B()
            {
                xptr = new X;
                cout << "B构造函数" << endl;
            }
            ~B()
            {
                //delete xptr;
                cout << "B析构函数" << endl;
            }
        private:
            X *xptr;
            X x;
    };
    
    
    
    int main()
    {
        B b;
        return 0;
    }

     结果:

    可以看到,B类中的X类的对象成员x的构造与析构函数都被调用。但B类中指向X类的指针,虽然调用了构造函数,但是并没有调用析构函数。因此应该在析构函数中添加delete销毁指针所指向的内存块。(虽然在一般程序中,即使不用delete销毁,程序结束时,操作系统也会帮你销毁)


    那么根据上面的输出结果,笔者稍微进行一下讲解,构造函数的调用顺序是:首先,如果存在基类,那么先调用基类的构造函数,如果基类的构造函数中仍然存在基类,那么程序会继续进行向上查找,直到找到它最早的基类进行初始化;如上例中类B,继承于类A;其次,如果所调用的类中定义的时候存在着对象被声明,那么在基类的构造函数调用完成以后,再调用对象的构造函数(如上面的X x和new X;);最后,将调用派生类的构造函数,如上例最后调用的是B类的构造函数。

    virtual析构函数
    下面来说一说为多态基类声明virtual析构函数:
    在C++中,构造函数不能声明为虚函数,这是因为编译器在构造对象时,必须知道确切类型,才能正确的生成对象,因此,不允许使用动态束定;其次,在构造函数执行之前,对象并不存在,无法使用指向此此对象的指针来调用构造函数,然而,析构函数是可以声明为虚函数;C++明白指出,当derived class对象经由一个base class指针被删除,而该base class带着一个non-virtual析构函数,其结果未有定义---实际执行时通常发生的是对象的derived成分没被销毁掉。

    看下面的例子:
    class Base
    {
    public:
        Base(){ std::cout<<"Base::Base()"<<std::endl; }
        ~Base(){ std::cout<<"Base::~Base()"<<std::endl; }
    };

    class Derive:public Base
    {
    public:
        Derive(){ std::cout<<"Derive::Derive()"<<std::endl; }
        ~Derive(){ std::cout<<"Derive::~Derive()"<<std::endl; }
    };

    int _tmain(int argc, _TCHAR* argv[])
    {
        Base* pBase = new Derive(); 
        //这种base classed的设计目的是为了用来"通过base class接口处理derived class对象"
        delete pBase;

        return 0;
    }

    输出的结果是:
    Base::Base()
    Derive::Derive()
    Base::~Base()
    从上面的输出结果可以看出,析构函数的调用结果是存在问题的,也就是说析构函数只作了局部销毁工作,这可能形成资源泄漏败坏数据结构等问题;那么解决此问题的方法很简单,给base class一个virtual析构函数

    class Base
    {
    public:
        Base(){ std::cout<<"Base::Base()"<<std::endl; }
        virtual ~Base(){ std::cout<<"Base::~Base()"<<std::endl; }
    };

    class Derive:public Base
    {
    public:
        Derive(){ std::cout<<"Derive::Derive()"<<std::endl; }
        ~Derive(){ std::cout<<"Derive::~Derive()"<<std::endl; }
    };

    int _tmain(int argc, _TCHAR* argv[])
    {
        Base* pBase = new Derive();
        delete pBase;

        return 0;
    }

    输出结果是:
    Base::Base()
    Derive::Derive()
    Derive::~Derive()
    Base::~Base()
    可能上面的输出结果正是我们所希望的吧,呵呵!由此还可以看出虚函数还是多态的基础,在C++中没有虚函数就无法实现多态特性;因为不声明为虚函数就不能实现“动态联编”,所以也就不能实现多态啦!
  • 相关阅读:
    重写移动端滚动条[iScroll.js核心代码]
    利用canvas将网页元素生成图片并保存在本地
    微信小程序的拖拽、缩放和旋转手势
    设计模式之访问者(visitor)模式
    设计模式之原型(prototype)模式
    设计模式之享元(flyweight)模式
    设计模式之职责链模式
    设计模式之组合(compose)模式
    leetcode16
    校招真题练习013 找零(头条)
  • 原文地址:https://www.cnblogs.com/sfqtsh/p/3604861.html
Copyright © 2011-2022 走看看