概念
前面通过构造函数的学习,我们知道一个对象时怎么来的,那一个对象又是怎么没呢的?
与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。
特性
:
析构函数是特殊的成员函数。
其特征如下:
- 析构函数名是在类名前加上字符 ~。
- 无参数无返回值。(析构函数不能重载)
- 一个类有且只有一个析构函数。(若未显式定义,系统会自动生成默认的析构函数)
- 对象生命周期结束时,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;
};
- 通过下面的程序我们会看到,编译器生成的默认析构函数,对会自定类型成员调用它的析构函数。
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;
}
注意:
类中如果没有涉及到资源管理时,析构函数是否给出无所谓;但是如果涉及到资源管理,用户必须要显式给出析构函数,在析构函数中清理对象的资源。
知识点习题
- 有如下程序,执行后输出的结果是( )
#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。
- 如果有一个类是 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,形成无限递归,造成堆栈溢出,系统崩溃。
- 分析一下这段程序的输出
#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”。
- 关于以下代码,哪个说法是正确的?
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调用的成员函数,且之后没有访问成员函数和虚函数的操作,因此没有问题。
- 设已经有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 ; 位于函数结尾 “}” 之前。
如果有不同见解,欢迎留言讨论