常见笔/面试题-之构造函数和析构函数

常见笔/面试题-之构造函数和析构函数

构造函数是用来初始化一个对象的,而析构函数的作用则是释放对象占用的空间。如果将虚函数、构造函数和析构函数结合起来会有怎么样的效果呢?

  1. 构造函数可以是虚函数吗?

    答:构造函数不可以是虚函数!基于以下几点原因:

    (1)构造一个对象的时候,必须知道对象的实际类型,而虚函数行为是在运行期间动态确定实际类型的。在构建一个对象时,构造函数执行期间,对象未完全构建完成,编译器无法知道对象的实际类型,如果构造函数为虚函数,虚函数的执行是基于对象类型确定的,然而构建的对象本身自己都无法确定自己的类型,虚函数更加无法正确执行!

    (2)虚函数的执行依赖于虚函数表。而虚函数表的初始化工作是在构造函数中完成的,即在构造函数中初始化vptr,让他指向正确的虚函数表。而在构造对象期间,虚函数表还未被初始化,将导致虚函数无法工作。

    (3)虚函数的意思就是开启动态绑定,程序会根据对象的动态类型来选择要调用的方法。然而在构造函数运行的时候,这个对象的动态类型还不完整,没有办法确定它到底是什么类型,故构造函数不能动态绑定。(动态绑定是根据对象的动态类型而不是函数名,在调用构造函数之前,这个对象根本就不存在,它怎么动态绑定?)

  2. 析构函数可以是纯虚函数吗?能举个例子吗?

    答:析构函数可以是纯虚函数!如果一个类要作为基类存在,最好将该类的析构函数申明虚函数(除非不使用基类指针的方式构造子类)。
    (1)拥有纯虚函数的类称为抽象类,专门提供函数接口,不能实例化。如果某个类需要作为一个抽象类,但是其中并没有其他方法,这时可以将析构函数声明为纯虚函数。(因为构造函数不能是虚函数,更不必提纯虚函数了)
    (2)例子:

    
    #include <iostream>
    
    using namespace std;
    
    class test
    {
    public:
        test(){ cout << "test constructor" << endl; }
        virtual ~test() = 0;
    };
    
    test::~test()
    {
    cout << "test destructor! it's specail!" << endl;
    }
    
    class derived:public test
    {
    public:
        derived(){ cout << "derived constructor" << endl; };
        virtual ~derived(){cout << "derived destructor" << endl;};
    };
    
    int main()
    {
        derived d;
        //system("pause");
        return 0;
    }

    运行结果:

    注意test基类中的析构函数是纯虚函数,但是该析构函数还是有实现,而且必须要有实现!!!test::~test(){}这个定义是必需的,因为虚析构函数工作的方式是:最底层的派生类的析构函数最先被调用,然后各个基类的析构函数被调用。这就是说,即使是抽象类,编译器也要产生对~test的调用,所以要保证为它提供函数体。如果不这么做,链接器就会检测出来(报错error link 1120),最后还是得回去把它添上。虽然抽象类的析构函数可以是纯虚函数,但要实例化其派生类对象,仍必须提供抽象基类中析构函数的函数体。

    **通常情况下,抽象类的纯虚函数实现必须且只能由派生类实现,但是对于基类的纯虚析构函数实现可以由自身给出,也可以由派生类给出。
    拥有纯虚析构函数的类也是抽象类!!!**

  3. 下面代码执行后会发生内存泄漏吗?如果存在该如何修改?

    
    #include <iostream> 
    
        using namespace std;
        //基类
        class Base{
        public:
            Base(){}
            ~Base(){cout << "Base destructor" << endl;}
            void dosomething(){cout << "do something in Base" << endl;}
        };
        //派生类 
        class Derived : public Base{
        public:
            Derived(){}
            ~Derived(){cout << "Derived destructor" << endl;}
            void dosomething(){cout << "do something in Derived" << endl;}
        };
    
        int main(){
            Base *base = new Derived;
            base->dosomething();
            delete base;
            system("pause");
            return 0;
        }

    答:此段代码执行后将会发生内存泄漏,由于基类指针指向的是子类对象,但是由于基类的析构函数不是虚函数,基类指针无法找到他所指向的实际类型,从而在delete的时候只能释放derived对象中从基类继承的部分,其余部分将无法得到释放导致内存泄漏!

    修改:

    
    #include <iostream> 
    
    using namespace std;
    
    //情景1:普通成员函数和析构函数,都不是虚函数 
    class Base{
    public:
        Base(){}
        //父类的析构函数,是虚函数,只做了这里的更改 
        virtual ~Base(){
            cout << "Base destructor" << endl;
        }
        //普通成员 
        void dosomething(){
            cout << "do something in Base" << endl;
        }
    };
    //派生类 
    class Derived : public Base{
    public:
        Derived(){}
        ~Derived(){
            cout << "Derived destructor" << endl;
        }
        void dosomething(){
            cout << "do something in Derived" << endl;
        }
    };
    
    int main(){
        //这个时候,虽然父类指针实际指向子类,可是没有虚函数表,
        //所以不能调用实际的类型,因此输出的只能是父类指针自身能看到的内容 
        Base *base = new Derived;
        base->dosomething();//输出的是父类的函数 
        delete base;//调用的是父类的析构函数 
        system("pause");
        return 0;
    }

    运行结果2
    由此可以看出,这个时候,就真正实现了将实际类型对象进行释放的。同时可以得到,如果父类析构是虚函数,子类调用析构函数的话,会先调用子类的析构函数,之后会调用父类的析构函数

    其实这里父类的析构函数加上了virtual,并不是说pbase释放的时候,同时调用了子类的析构函数和父类的析构函数,它实际上指向的是子类的虚函数表,那么就是说父类指针最终只调用了子类的析构函数,由C++类本身特性,当子类析构函数调用的时候,会自动调用父类的析构函数,完成了释放。

    相关小结

    对于Base *pbase = new Derived;

    如果父类函数不是析构函数,那么pbase只能“看见”父类本身的函数,这是因为没有虚函数表让它可以找到本身
    如果父类析构函数是虚函数,如果delete pbase,将会先调用子类函数的析构函数,然后子类析构函数自动调用父类的析构函数,真正实现了资源释放,防止了内存泄露构造派生类的时候,会先构造基类部分,然后构造子类部分;撤销派生类对象的时候,会先撤销派生类部分,然后撤销基类部分

猜你喜欢

转载自blog.csdn.net/owen7500/article/details/52663114