<C++>析构函数、虚析构函数和构造函数

C++析构函数

  • 知识点:栈清空顺序
  • 知识点:派生类实例释放空间顺序
  • 知识点:派生类实例在堆空间中的处理(析构函数)
  • 知识点:构造函数的调用顺序
  • 代码展示

代码

以下展示两个类,子类父类是派生的关系。

//code block 1
class Base 
{
public:
    Base () {}
    ~Base ()
    { 
        cout << "Base Destructor" << endl; 
        //~Base();
    }
};
class Derive : public Base 
{
public:
    Derive() {}
    ~Derive()
    { 
        cout << "Derive Destructor" << endl; 
        //~Base();
    }
};

一开始想写一个不涉及到指针的代码:

//code block 2
int main()
{
    Base base;
    Derive derive;
    return 0;
}

想想输出的结果会是什么?
自己本来以为会这么输出(证明是错误的,先释放base再释放derive):

Base Destructor
Base Destructor
Derive Destructor

结果输出结果是这样的:

Derive Destructor
Base Destructor
Base Destructor

其实忽略了两个知识点:栈清空的顺序、派生类实例调用析构函数释放空间顺序。

知识点:栈清空的顺序

栈是不用coder去释放空间的。你只管用,只管吃。至于刷盘子洗碗做饭你不需要干。栈清空的顺序完全就是栈的特性——先进后出、后进先出。base先入栈、derive再入栈。释放空间的时候,栈顶元素首先弹栈、释放空间。然后再base释放空间。

derive
base

先释放derive,再释放base。我记得是父类首先调用析构函数释放空间、然后子类调用析构函数释放空间嘛,那输出可能是(证明是错误的,先调用子类析构函数):

Base Destructor
Derive Destructor
Base Destructor

知识点:调用析构函数顺序

派生类对象先调用自己的析构函数,然后再调用父类的析构函数。我觉得可以这么解释,先把属于子类的成员释放掉,再去释放父类的成员。
可以这么想(二级派生看得比较爽):

//code block 5
class GrandBase
{
public:
    GrandBase() {}
    ~GrandBase() 
    {
        cout << "GrandBase Destructor" << endl; 
        //NO father
    }
};

class Base : public GrandBase
{
public :
    Base(){}
    Base::~Base()
    {
        cout << "Base Destructor" << endl;
        //~GrandBase();
    }
};

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

编译器自己在析构函数中加了调用父类析构函数的代码。
现在让我们回到code block 2中,我们把代码改成带有指针的。

//code block 3
int main()
{
    Base *pBase = new Derive();
    delete pBase;
    pBase = nullptr;
    return 0;
}

这回我们把变量空间放在了堆中。堆有一个特点:在堆中开辟的空间,你要自己去释放。如果不去释放,就等着软件进程死掉,操作系统一块回收。还拿吃饭来说,不光要吃、还要管做饭、洗碗。我们开辟了一个Derive对象,这个对象的内存里包括Derive的空间和Base的空间。如果我们最后手动释放内存的时候,不光要释放Base部分的还要释放Derive部分的。
那么code block 3输出的结果会是什么呢?

Base Destructor

我们发现只调用了Base的析构函数,也就是说只释放了Base的部分。和我们预想的不一样啊。为什么会这样呢?我们注意到pBase是Base 类型,也就是指向Base的指针,没人告诉它要调用Derive析构函数,它为什么要调用呢?你说这不是贱吗对吧!这就引出了“虚析构函数”:在父类析构函数前面加一个virtual。系统在运行时,就会利用多态,认识到:“这个pBase虽然长得像Base 但它不止Base对象这么简单,他是我儿子的实例,它释放空间的时候按照我儿子的方法去释放。”

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

在派生类的使用上父类不写成虚析构函数可能会内存泄露。一泻千里,雅美蝶!

知识点:构造函数的调用顺序

把code block 5代码稍加修改,我们研究一下构造函数的调用顺序
先看代码:

//code block 5
class GrandBase
{
public:
    GrandBase() 
    { 
        //No Father
        cout << "GrandBase Constructor" << endl; 
    }
    ~GrandBase() 
    {
        cout << "GrandBase Destructor" << endl; 
        //NO father
    }
};

class Base : public GrandBase
{
public :
    Base()
    {
        //GrandBase();
        cout << "Base Constructor" << endl; 
    }
    Base::~Base()
    {
        cout << "Base Destructor" << endl;
        //~GrandBase();
    }
};

class Derive : public Base 
{
public:
    Derive() 
    {
        //Base ();
        cout << "Derive Constructor" << endl; 
    }
    ~Derive()
    { 
        cout << "Derive Destructor" << endl; 
        //~Base();
    }
};

int main()
{
    //我这么写
    Derive *pDerive = new Derive ();
    delete pDerive ;
    pDerive = nullptr;
}

会输出什么?

GrandBase Constructor
Base Constructor
Derive Constructor
Derive Destructor
Base Destructor
GrandBase Destructor

这回对了,哈哈。
一样可以理解为编译器加了几段代码。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值