> 文章列表 > c++ 类的特殊成员函数:析构函数(三)

c++ 类的特殊成员函数:析构函数(三)

c++ 类的特殊成员函数:析构函数(三)

1. 正文

在C++中,析构函数是一种特殊的成员函数,有与类名相同的名称,但在名字前面加上一个波浪号(~),它不会返回任何值,也不能带任何参数.析构函数用于销毁对象并释放对象使用的全部资源,包括堆上分配的内存和打开的文件等。析构函数在对象的生命周期结束时自动调用。

2. 析构函数的基本语法

class ClassName {
public:ClassName(); // 构造函数~ClassName(); // 析构函数
};

Destructor_function.cpp

#include <iostream>class Engineer {
public:std::string ID;std::string post;Engineer(){std::cout << "无参构造..." << std::endl;};~Engineer(){std::cout << "析构函数..." << std::endl;};};void GenerateStackObject(){Engineer engineer0;
}int main(){GenerateStackObject();std::cout << "--------------------" << std::endl;Engineer engineer;return 0;
}

运行结果:

无参构造...
析构函数...
--------------------
无参构造...
析构函数...

生成栈内存对象时,随着函数结束而调用析构函数.

...
Engineer * engineer1 = new Engineer();
...

运行结果:

无参构造...

生成堆内存对象时,并不执行析构函数,得加 delete

...
Engineer * engineer1 = new Engineer();
delete engineer1;
...

运行结果:

无参构造...
析构函数...

如果混在一起执行:

...GenerateStackObject();std::cout << "--------------------1" << std::endl;Engineer engineer;std::cout << "--------------------2" << std::endl;Engineer * engineer1 = new Engineer();delete engineer1;std::cout << "--------------------3" << std::endl;
...    

运行结果:

无参构造...
析构函数...
--------------------1
无参构造...
--------------------2
无参构造...
析构函数...
--------------------3
析构函数...

第一个 无参构造… 是执行 GenerateStackObject() 生成栈内存对象engineer0 初始化的结果,随着 GenerateStackObject 函数的结束,执行析构函数销毁 engineer0 对象(也就是第一个 析构函数…)
第二个 无参构造… 是在 main 函数里面生成 engineer 栈内存对象,随着 main 函数的结束,在最后执行析构函数,也就是(第三个析构函数…).
第三个 无参构造… 是在 main 函数里面生成 engineer1 堆内存对象,随着 delete 销毁 engineer1,从而执行析构函数,也就是(第二个析构函数…).

3.使用场景

析构函数的主要作用是释放对象使用的资源。下面是析构函数的常用使用场景:

3.1 类中有动态分配的内存

在类中如果进行了动态分配内存,就需要在析构函数中释放这些内存,以防止内存泄漏。

class Engineer {
public:std::string ID;std::string post;int * data;Engineer(){std::cout << "无参构造..." << std::endl;};Engineer(int num):data(new int[num]){std::cout << "有参构造..." << std::endl;};~Engineer(){std::cout << "析构函数..." << std::endl;delete[] data;};};

3.2 类中有打开的文件

在C++中,当一个文件被打开时,会将其与一个文件流对象关联起来。如果对象生命周期结束时,文件流对象没有被关闭,则会发生内存泄漏。因此,在析构函数中,必须关闭文件流对象。

class File {
public:File(string filename) {file = fopen(filename.c_str(), "r");if (file == nullptr) {throw runtime_error("Cannot open file");}}~File() {if (file.is_open()) {fclose(file);}}
private:FILE* file;
};

3.3 网络连接

当连接到服务器时,应该在对象生命周期结束时关闭该连接。

class Connection {
public:Connection(string address, int port) {sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0) {throw runtime_error("Cannot create socket");}server = gethostbyname(address.c_str());bzero((char*) &serv_addr, sizeof(serv_addr));serv_addr.sin_family = AF_INET;bcopy((char*)server->h_addr, (char*)&serv_addr.sin_addr.s_addr, server->h_length);serv_addr.sin_port = htons(port);if (connect(sockfd,(struct sockaddr *) &serv_addr,sizeof(serv_addr)) < 0) {close(sockfd);throw runtime_error("Cannot connect to server");}}~Connection() {close(sockfd);}
private:int sockfd;struct sockaddr_in serv_addr;struct hostent *server;
};

3.4 类中有锁对象

在多线程编程中,为了保证线程安全,有时需要在类中使用锁对象。在析构函数中,需要释放锁对象以避免死锁

class Demo {
public:Demo() {pthread_mutex_init(&mutex, NULL);}~Demo() {pthread_mutex_destroy(&mutex);}void lock() {pthread_mutex_lock(&mutex);}void unlock() {pthread_mutex_unlock(&mutex);}
private:pthread_mutex_t mutex;
};

3.5 对象间存在父子关系

当一个对象是另一个对象的子对象时,在子对象的析构函数中需要先调用父对象的析构函数,再释放其它资源。

class Parent {
public:Parent() {cout << "Parent constructed" << endl;}~Parent() {cout << "Parent destroyed" << endl;}
};class Child : public Parent {
public:Child() {cout << "Child constructed" << endl;}~Child() {cout << "Child destroyed" << endl;}
};int main() {Child child;return 0;
}

运行结果:

Parent constructed
Child constructed
Child destroyed
Parent destroyed