C++析构函数

一、认识析构函数

百度百科:
析构函数(destructor) 与构造函数相反,当对象结束其生命周期,如对象所在的函数已调用完毕时,系统自动执行析构函数。析构函数往往用来做“清理善后” 的工作(例如在建立对象时用new开辟了一片内存空间,delete会自动调用析构函数后释放内存)。(咱也看不懂,直接上代码)。

这里我们来简单说说类的析构函数,它是类的一个成员函数,名字由波浪号加类名构成。看它的名字我们大概就能联想到他功能,是执行与构造函数相反的操作:释放对象使用的资源,并销毁非static成员。

同样的,我们来看看析构函数的几个特点:

  1. 函数名是在类名前加上~,无参数且无返回值。

  2. 一个类只能有且有一个析构函数,如果没有显式的定义,系统会生成一个缺省的析构函数(合成析构函数)。

  3. 析构函数不能重载。每有一次构造函数的调用就会有一次析构函数的调用。

拿程序说话:

class Date
{
public:
    Date(int year=1990,int month=1,int day=1)
        : _month(year), _year(month), _day(day)
    { }
    ~Date()
    {
        cout << "~Date()" << this << endl;
    }
private:
    int _year=1990;   
    int _month;
    int _day;
};
void test()
{
    Date d1;
}
int main()
{
    test();
    return 0;
}

在test()函数中构造了对象d1,那么在出test()作用域d1应该被销毁,此时将调用析构函数,下面是程序的输出。当然在构建对象时是先调用构造函数的,在这里就不加以说明了。
在这里插入图片描述

我们知道,在构造函数中,成员的在初始化是在函数体执行前完成的,并按照成员在类中出现的顺序进行初始化,而在析构函数中,首先执行函数体,然后再销毁成员,并且成员按照初始化的逆序进行销毁。

二、销毁,清理?

我们一直在说析构函数的作用是在你的类对象离开作用域后释放对象使用的资源,并销毁成员。那么到底这里所说的销毁到底是什么?那么继续往下看:

  void test ()

   {
       int a=10;
       int b=20;

   }

回想我们在一个函数体内定义一个变量的情况,在test函数中定义了a和b两个变量,那么在出这个函数之后,a和b就会被销毁(栈上的操作)。那么如果是是一个指向动态开辟的一块空间的指针,我们都知道需要自己进行free,否则会造成内存泄漏。
说到这里,其实在类里面的情况和这是一样的,这就是合成析构函数体为空的原因,函数并不需要做什么,当类对象出作用域时系统会释放你的内置类型的那些成员。但是像上面说的一样,如果,我的成员里有一个指针变量并且指向了一块你动态开辟的内存,那么像以前那样也需要自己来释放,此时就需要在析构函数内部写你的释放代码,这样在调用析构函数的时候就可以把你所有的资源进行释放。(其实这才是析构函数有用的地方,对吗)

那么还有一点,当类类型对象的成员还有一个类类型对象,那么在析构函数里也会调用这个对象的析构函数。

三、析构函数来阻止该类型对象被销毁?

我们如果不想要析构函数来对对象进行释放该怎么做呢,不显式的定义显然是不行的,因为编译器会生成默认的合成析构函数。之前我们知道了如果想让系统默认生成自己的构造函数可以利用default,那么其实还有一个东西叫做delete。

class Date
{
public:
    Date(int year=1990,int month=1,int day=1)
        : _year(year),_month(month),  _day(day)
    { }
    ~Date() = delete;
     
private:
    int _year=1990;   
    int _month;
    int _day;
};

如果我这么写了,又在底下创建Date类型的对象,那么这个对象将是无法被销毁的,其实编译器并不允许这么做,直接会给我们报错。

但其实是允许我们动态创建这个类类型对象的,像这样:Date* p = new Date;虽然这样是可行的,但当你delete p的时候依然会出错,原因就不用说了吧。

所以既然这样做的话既不能定义一个对象也不能释放动态分配的对象,所以还是不要这么用为好喽。

四、注意喽

一般在你显式的定义了析构函数的情况下,应该也把拷贝构造函数和赋值操作显式的定义。为什么呢??
看下面的改动:

class Date
{
public:
    Date(int year=1990,int month=1,int day=1)
        : _year(year),_month(month),  _day(day)
    { 
        p = new int;
    }
    ~Date()
    {
        delete p;
    }
     
private:
    int _year=1990;   
    int _month;
    int _day;
    int *p;
};

成员中有动态开辟的指针成员,在析构函数中对它进行了delete,如果不显式的定义拷贝构造函数,当你这样:Date d2(d1)来创建d2,我们都知道默认的拷贝构造函数是浅拷贝,那么这么做的结果就会是d2的成员p和d1的p是指向同一块空间的,呢么调用析构函数的时候回导致用一块空间被释放两次,程序会崩溃的哦!

最后,
我们知道析构函数是可以调用的,那么构造函数可不可以呢?是怎样的使用环境

更多C++相关知识体系,请移步C++知识目录

### C++ 析构函数的使用方法 析构函数是在对象生命周期结束时自动调用的一个特殊成员函数,用于执行清理操作。它的功能主要包括释放动态分配的内存、关闭打开的文件以及释放其他资源等[^3]。 #### 定义方式 析构函数的名字由波浪号 `~` 加上类名组成,不接受任何参数也不返回任何值。例如: ```cpp class MyClass { public: ~MyClass(); // 析构函数声明 }; // 析构函数定义 MyClass::~MyClass() { // 清理逻辑 } ``` 如果未显式定义析构函数,则编译器会自动生成一个默认析构函数[^1]。默认析构函数通常适用于简单的数据结构,但对于复杂的数据结构(如包含动态分配的内存),则需要手动定义以确保资源被正确释放。 --- ### 默认析构函数与用户定义析构函数的区别 - **默认析构函数**: 编译器生成的默认析构函数仅负责销毁类中的非静态成员变量,并不会处理额外的清理任务。它适合于那些不需要特别清理工作的简单类[^4]。 - **用户定义析构函数**: 当类中存在动态分配的资源(如堆内存、文件句柄或其他外部资源)时,必须显式定义析构函数来完成这些资源的释放。例如: ```cpp class ResourceHandler { private: int* data; public: ResourceHandler(int size) : data(new int[size]) {} // 动态分配内存 ~ResourceHandler() { delete[] data; } // 显式释放内存 }; ``` 在此例子中,如果没有显式的析构函数,`data` 所指向的堆内存将无法得到释放,从而引发内存泄漏问题。 --- ### 需要显式定义析构函数的情况 以下是几种常见的场景,其中需要显式定义析构函数而不是依赖默认版本: 1. 类中有动态分配的内存; 2. 类管理了某些外部资源(如文件流、数据库连接等); 3. 存在继承关系且基类拥有虚函数的情况下,为了支持多态行为,应将基类的析构函数声明为虚拟函数。 示例代码如下所示: ```cpp class Base { public: virtual ~Base() {} // 虚拟析构函数 }; class Derived : public Base { public: ~Derived() override {} // 派生类析构函数 }; ``` --- ### 私有析构函数的应用 有时出于设计需求,可能希望限制某个类的对象只能通过特定的方式创建或销毁。此时可以通过将析构函数设为私有并提供友元函数或者嵌套类来进行间接访问[^2]。然而需要注意的是,在这种模式下容易遇到一些潜在风险,比如内存泄漏和继承相关的问题。 --- ### 注意事项 编写析构函数时需注意以下几点以保障程序的安全性和效率: 1. **避免重复删除同一块内存**:多次调用 `delete` 或者错误地释放已经释放过的指针可能导致未定义行为。 2. **防止异常抛出**:由于析构函数本身不应该失败,因此在其内部尽量减少可能会触发异常的操作。 3. **考虑线程安全因素**:对于共享资源而言,析构过程中应当同步多个线程之间的交互以防竞争条件发生。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值