csdn博客

来源:互联网 发布:c语言程序代码生日快乐 编辑:程序博客网 时间:2024/06/05 14:22

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

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

  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,将会先调用子类函数的析构函数,然后子类析构函数自动调用父类的析构函数,真正实现了资源释放,防止了内存泄露构造派生类的时候,会先构造基类部分,然后构造子类部分;撤销派生类对象的时候,会先撤销派生类部分,然后撤销基类部分

1 0
原创粉丝点击