C++:类的六个默认函数之一 —— 析构函数


概念

前面通过构造函数的学习,我们知道一个对象时怎么来的,那一个对象又是怎么没呢的?

与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。

特性

:
析构函数是特殊的成员函数。

其特征如下:

  1. 析构函数名是在类名前加上字符 ~。
  2. 无参数无返回值。(析构函数不能重载)
  3. 一个类有且只有一个析构函数。(若未显式定义,系统会自动生成默认的析构函数)
  4. 对象生命周期结束时,C++编译系统系统(编译器)自动调用析构函数,完成对象中资源的清理。
typedef int DataType;

class SeqList
{
public :
    SeqList (int capacity = 10)
    {
        _pData = (DataType*)malloc(capacity * sizeof(DataType));
        assert(_pData);
        _size = 0;
        _capacity = capacity;
    }
    ~SeqList() {
    if (_pData) {
		free(_pData );    // 释放堆上的空间
        _pData = NULL;    // 将指针置为空
        _capacity = 0;
        _size = 0;
    } 
}
private :
    int* _pData ;
    size_t _size;
    size_t _capacity;
};
  1. 通过下面的程序我们会看到,编译器生成的默认析构函数,对会自定类型成员调用它的析构函数。
class String
{
public:
    String(const char* str = "jack")
    {
        _str = (char*)malloc(strlen(str) + 1);
        strcpy(_str, str);
    }
    ~String() 
    {
        cout << "~String()" << endl;
        free(_str);
    }
private:
    char* _str;
};

class Person
{
private:
    String _name;
    int _age; 
};

int main() 
{
    Person p;
    return 0; 
}

注意:

类中如果没有涉及到资源管理时,析构函数是否给出无所谓;但是如果涉及到资源管理,用户必须要显式给出析构函数,在析构函数中清理对象的资源。

知识点习题

  1. 有如下程序,执行后输出的结果是( )
#include <iostream.h>
class cla{
    static int n;
    public:
     cla(){n++;}
    ~cla(){n--;}
    static int get_n(){return n;}
};
int cla::n= 0;
int main()
{
   cla *p =new cla;
   delete p;
   cout<<"n="<<cla::get_n()<<endl;
   return 0;
}

A. n=3
B. n=4
C. n=1
D. n=0

正确答案:

D

答案解析:

类的实例化:cla *p = new cla,p分配在栈上,p指向的对象分配在堆上。
n为静态成员变量,没有this指针,属于类域,所有对象共享。
实例化——调用构造函数,所以n++;
delete——调用析构函数,所以n–。
最后输仍旧为0。

  1. 如果有一个类是 myClass , 关于下面代码正确描述的是:
myClass::~myClass(){ 
	delete this;
	this = NULL;
}

A. 正确,我们避免了内存泄漏
B. 它会导致栈溢出
C. 无法编译通过
D. 这是不正确的,它没有释放任何成员变量。

正确答案

C

答案解析

1.在类A的析构函数中,delete一个非A类对象通常是没有问题的;
在类A的析构函数中,delete一个类A的对象,就会造成死循环,堆栈溢出;
在析构函数外使用 delete后,应该立即给指针赋值 NULL防止野指针。

2.因为this是Myclass * const this指针,也就是说this指针指向的对象(不是指向的对象的值)不可以改变,所以给this赋值在编译期间就不会通过,如果没有this = NULL这语句的话是栈溢出,因为会不停的调用析构函数。

3.this被const修饰不能修改。 删掉this=null后,在类的析构函数中调用delete this,delete this会去调用本对象的析构函数,而析构函数中又调用delete this,形成无限递归,造成堆栈溢出,系统崩溃。

  1. 分析一下这段程序的输出
#include<iostream>
 using namespace std;
 class B
 {
 public:
     B()
     {
         cout << "default constructor" << " ";
     }
     ~B()
     {
         cout << "destructed" << " ";
     }
     B(int i): data(i)
     {
         cout << "constructed by parameter" << data << " ";
     } 
     private: int data;
 }; 
 B Play( B b)
 {
     return b;
 } 
 int main(int argc, char *argv[])
 {
     B temp = Play(5);
     return 0;
 }

A. constructed by parameter5 destructed destructed
B. constructed by parameter5 destructed
C. default constructor" constructed by parameter5 destructed
D. default constructor" constructed by parameter5 destructed destructed

正确答案

A

答案解析

从赋值右边开始执行

调用Play函数需要将5隐式类型转换为Play函数中的形参b,会调用B的B(int i): data(i),打印“constructed by parameter5”。
Play函数返回时需要调用B的拷贝构造函数给对象temp初始化。
Play函数返回后需要调用b的析构函数,将Play函数的形参释放,打印“destructed”。
main函数返回后需要释放temp,打印“destructed”。

  1. 关于以下代码,哪个说法是正确的?
myClass::foo(){
    delete this;
}
..
void func(){
    myClass *a = new myClass();
    a->foo();
}

A. 它会引起栈溢出
B. 都不正确
C. 它不能编译
D. 它会引起段错误

正确答案:

B

答案解析:

delete this(对象请求自杀)是允许的,但是必须保证:

  • 1: 该this对象是100%new出来的,并且是最普通的new出来的,不能是new[],定位new等等
  • 2: 该成员函数是this对象最后调用的成员函数(因为成员函数第一个参数是隐藏的this指针)
  • 3: delete this之后必须保证不能访问到成员变量和虚函数(调用完delete this 之后,对象的内存空间被释放了,导致不能再访问数据成员和虚函数)
  • 4: delete this不能放在析构函数中,否则递归 (导致堆栈溢出)

题目中this对象是通过最普通的new产生的,并且之调用了foo成员函数,即保证了foo是最后一个this调用的成员函数,且之后没有访问成员函数和虚函数的操作,因此没有问题。

  1. 设已经有A,B,C,D4个类的定义,程序中A,B,C,D析构函数调用顺序为?
C c;
void main()
{
    A*pa=new A();
    B b;
    static D d;
    delete pa;
}

A. A B C D
B. A B D C
C. A C D B
D. A C B D

正确答案: B

答案解析

这道题主要考察的知识点是 :全局变量,静态局部变量,局部变量空间的堆分配和栈分配

其中全局变量和静态局部变量时从 静态存储区中划分的空间,
二者的区别在于作用域的不同,全局变量作用域大于静态局部变量(只用于声明它的函数中),
而之所以是先释放 D 在释放 C的原因是, 程序中首先调用的是 C的构造函数,然后调用的是 D 的构造函数,析构函数的调用与构造函数的调用顺序刚好相反。

局部变量A 是通过 new 从系统的堆空间中分配的,程序运行结束之后,系统是不会自动回收分配给它的空间的,需要程序员手动调用 delete 来释放。

局部变量 B 对象的空间来自于系统的栈空间,在该方法执行结束就会由系统自动通过调用析构方法将其空间释放。

之所以是 先 A 后 B 是因为,B 是在函数执行到 结尾 “}” 的时候才调用析构函数, 而语句 delete a ; 位于函数结尾 “}” 之前。


如果有不同见解,欢迎留言讨论

评论 1 您还未登录,请先 登录 后发表或查看评论
相关推荐

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:数字20 设计师:CSDN官方博客 返回首页

打赏作者

AngelDg

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值