????????欢迎大家订阅我的《计算机底层原理》、《深度解析C++》系列专栏、我会持续为大家输出优质文章,能够帮助到大家就是对我最大的鼓励!😊
目录
前言
? ? ? ? 这篇文章主要为大家介绍C++当中的析构函数的用法,我们这篇文章主要需要掌握,析构函数的功能是什么?默认析构函数和析构函数的区别是什么,什么情况下必须显式定义析构函数而必须使用默认析构函数。
1.析构函数的概念
? ? ? ? 析构函数是一种在对象生命周期结束的时候自动调用的特殊成员函数,在面向对象编程当中,对象的生命周期是指对象从创建到销毁的整个过程,析构函数通常用于执行一些清理工作,例如释放动态分配的内存、关闭文件或者释放一些其它资源等等。防止资源泄漏等问题。
2.析构函数的功能
1.释放动态分配的内存
? ? ? ? 当一个对象在其生命周期内分配了内存的时候,我们要确保在对象被销毁的时候释放这些内存是只管重要的,以防止内存泄漏,在C++当中通常使用new关键字进行内存分配,而使用delete关键字进行释放,这涉及到了在对象的析构函数当中执行适当的清理工作。
#include"Yangon.h" class MyClass { private: char* data; public: MyClass(const char* str) { data = new char[strlen(str) + 1]; strcpy(data, str); } ~MyClass() { delete[] data; } void PrintData() { cout << "Data:" << data << endl; } }; int main() { MyClass* obj = new MyClass("Hello World!"); obj->PrintData(); delete obj; return 0; }
? ? ? ? 在上面我写的这段代码当中这个类的构造函数使用了new关键字进行了动态内存的分配来定义data数组,我们要知道对象在创建的时候就会立刻调用构造函数进行初始化工作,构造函数当中使用了动态分配就意味着我们的每一个对象在堆当中分配好了内存之后就会有一个data数组,因为每一个对象的初始化都会调用构造函数,但是这些动态分配好的内存必须我们手动释放,对象销毁的时候如果我们不进行手动释放,就会造成内存泄漏,所以我们必须在构造函数当中显式释放这些内存,而对象在销毁的时候会自动调用自己的析构函数,所以我们就必须在析构函数当中显式对应,这样就和构造函数对照起来了。
? ? ? ? 这里我多提一嘴,因为有些情况是不需要显式定义析构函数的,析构函数和构造函数一样我们如果不写编译器会自动生成,哪些情况需要我们显式定义析构函数并且在析构函数内部显式地释放资源呢?这具体就要看构造函数当中具体做了什么了,构造函数当中没有明确的定义一些动态内存的话或者没有明确地使用了哪些资源的话,析构函数一般情况下我们是可以不写的。
2.关闭文件或释放资源
? ? ? ? 当一个对象在其生命周期内打开了文件,建立了网络连接或者分配其它类似的资源的时候,确保在对象销毁的时候释放这些资源是十分重要的,以免泄漏资源,在C++当中你可以使用析构函数来执行这些清理操作。下面是一个简单的例子。
#include"Yangon.h" #include<fstream> class FileHandler { public: FileHandler(const string& filename) { file.open(filename); if (!file.is_open()) { cerr << "Error: Unable to open file." << endl; } } ~FileHandler() { if (file.is_open()) { file.close(); } } void WriteData(const string& data) { if (file.is_open()) { file << data << endl; } } private: ofstream file; };
? ? ? ? 这里我们的构造函数当中打开了一个文件,那么这种情况我们就必须在析构函数当中显式释放这些资源。我们要确保在对象被销毁的时候这些资源被正确地释放。
? ? ? ? 最后我再在这里提一嘴,需不需要显式地定义析构函数主要看我们的构造函数里面有没有定义一些我们必须进行释放的内容。注意我上面提到的这些情况是必须显式定义析构函数的,不能依靠默认析构函数。
? ? ? ? 默认析构函数就是我们自己不写然后编译器为我们自动生成的析构函数,这种情况下这样的析构函数其实内部是空的,也就是说默认析构函数内部实现是空的,因为对象内部实在是没有什么需要我们释放的资源。
1.普通内置类型
? ? ? ? 对于内置类型,编译器生成的默认析构函数并不执行任何的清理工作,因为内置类型本身并没有什么需要手动释放的资源(内置类型就是我们平常所使用的int、double、char等这些类型)。
2.成员对象类型
? ? ? ?针对成员对象类型我们这里分成显式定义的析构函数和默认析构函数两个部分来看,如果是默认析构函数的话,析构函数会自动调用成员对象的析构函数,如果是显式析构函数的话需要我们在析构函数内部显式调用成员对象的析构函数代码如下:
#include <iostream> //默认析构函数 class MemberObject { public: MemberObject() { std::cout << "MemberObject constructed." << std::endl; } ~MemberObject() { std::cout << "MemberObject destructed." << std::endl; } }; class Container { private: MemberObject member; // 成员对象 public: // 默认构造函数 Container() { std::cout << "Container constructed." << std::endl; } // 默认析构函数 ~Container() { std::cout << "Container destructed." << std::endl; } }; int main() { Container container; // 创建一个Container对象 // 对象超出作用域,编译器自动调用默认析构函数 return 0; } //显式析构函数 #include <iostream> class MemberObject { public: MemberObject() { std::cout << "MemberObject constructed." << std::endl; } ~MemberObject() { std::cout << "MemberObject destructed." << std::endl; } }; class Container { private: MemberObject member; // 成员对象 public: // 默认构造函数 Container() { std::cout << "Container constructed." << std::endl; } // 自定义析构函数 ~Container() { std::cout << "Container destructed." << std::endl; // 显式调用成员对象的析构函数 member.~MemberObject(); } }; int main() { Container container; // 创建一个Container对象 // 对象超出作用域,编译器自动调用自定义析构函数 return 0; }
? ? ? ? 我要在这里特别强调一下,默认析构函数是我们不写编译器自动为我们生成的,上面的案例我是为了方便给大家演示我才写出来的,按理说只要我们自己写出来析构函数,编译器就不会为我们生成默认析构函数了,所以这里大家注意一下能理解就好哈!
1.不需要显式调用析构函数
? ? ? ? 通常情况下我们不需要显式调用析构函数,因为对象生命周期结束的时候会自动调用成员的析构函数,而不是我们程序员手动调用的,(注意我这里说的可是显式调用不是显式定义千万别误会了)。
2.注意资源管理
? ? ? ? 如果在析构函数当中进行资源管理确保在对象的整个生命周期当中都能正确地处理异常和错误情况,一面造成资源泄漏。
3.避免在析构函数当中执行耗时操作
? ? ? ? 析构函数应该足够的高效,否则在销毁大量的对象的时候可能会导致性能下降。
析构函数还有很多的注意事项,等到后面讲到多态、异常的时候再为大家详细讲解。
? ? ? ? 析构函数在继承、多态、异常等部分的内容方面还有很多的注意细节,这篇文章就不为大家详细展开了,本文只介绍了析构函数最基础的内容,希望能够帮助到大家。
?