C++ 析构函数怎么用?

文章导读
Previous Quiz Next 析构函数 是 的一个特殊成员函数,当该类的对象超出作用域或对该类对象的指针应用 delete 表达式时,会自动执行。
📋 目录
  1. 为什么我们需要自定义析构函数?
  2. C++ 中析构函数的特性
  3. 数组的析构函数
  4. 使用析构函数时的常见错误
  5. 结论
A A

C++ 中的析构函数



Previous
Quiz
Next

析构函数 的一个特殊成员函数,当该类的对象超出作用域或对该类对象的指针应用 delete 表达式时,会自动执行

注意: 在 C++ 中,static 关键字有不同的含义。在本章中,我们使用 static 进行基于栈的/非动态内存分配。

手动定义 析构函数语法如下所示 −

~class_name() {
   // 函数体 
}

以下是在 外部定义 析构函数 的语法,但首先需要在类中声明析构函数 −

class_name {
   public:
      // 析构函数声明
     ~class_name(); 
}

// 定义析构函数
class_name :: ~class_name() {
   // 函数体
}

在类内部定义析构函数

在这个示例中,我们使用了上述语法在类内部手动定义析构函数。这里并不需要析构函数,我们只是为了说明如何在类内部定义析构函数 −

#include <iostream>
using namespace std;

class Example{
   public:
      // 构造函数被调用
      Example(void)
      {
         cout << "Constructor is called" << endl;
      }

      // 在类内部定义析构函数
      ~Example(void)
      {
         cout << "Destructor is called" << endl;
      }
};

int main(){
   Example ex;
   return 0;
}

上述代码的输出如下所示 −

Constructor is called
Destructor is called

在类外部定义析构函数

下面的示例演示了如何使用作用域解析运算符在类外部定义析构函数(Example),但首先需要在类内部声明

#include <iostream>
using namespace std;

class Example {
   public:
      // 构造函数被调用
      Example(void)
      {
         cout << "Constructor is called" << endl;
      }
      // 在类内部声明析构函数
      ~Example();
};

// 使用作用域解析运算符在类外部定义析构函数
Example :: ~Example(void)
{
   cout << "Destructor is called" << endl;
}

int main(){
   Example ex;
   return 0;
}

上述代码的输出如下所示 −

Constructor is called
Destructor is called

为什么我们需要自定义析构函数?

C++ 为每个类在创建时提供了一个 默认析构函数。默认析构函数会清理静态分配的内存。但是,如果我们使用了 new 进行动态内存分配的指针,那么我们需要定义一个自定义析构函数,使用 delete 操作符 来清理内存。

在这个 示例 中,析构函数 Example1 仅用于显示消息,并不释放内存,因为类 Example1 的内存会自动释放。但是析构函数 Example2 用于使用 delete 操作符释放内存。

#include <iostream>
using namespace std;

class Example1 {
   public:
      // 构造函数被调用
      Example1()
      {
         cout << "Constructor of Example1 is called" << endl;
      }

      // 析构函数被调用
      ~Example1()
      {
         cout << "Destructor of Example1 is called" << endl;
      }
};

class Example2 {
   private:
      int *ptr;

   public:
      Example2()
      {
         ptr = new int; // 动态分配的内存
         *ptr = 42;
         cout << "Constructor of Example2 is called with ptr Value = " << *ptr << endl;
      }

      // 析构函数被调用以释放动态分配的内存
      ~Example2()
      {
         delete ptr; // 动态分配的内存被释放
         cout << "Destructor of Example2 is called" << endl;
      }
};

int main(){
   Example1 ex1; // 静态内存分配的对象
   Example2 ex2; // 动态内存分配的对象

   return 0;
}

上述代码的 输出 如下所示 −

Constructor of Example1 is called
Constructor of Example2 is called with ptr Value = 42
Destructor of Example2 is called
Destructor of Example1 is called

C++ 中析构函数的特性

C++ 中的析构函数具有以下特性 −

  • 对象超出作用域时,析构函数会被自动调用
  • 析构函数的名称与类相同,使用波浪号(~)符号表示。
  • 当创建 class 时,它会被自动创建
  • 析构函数没有任何返回类型,也不接受任何参数/实参。
  • 不能定义多个析构函数。
  • 析构函数不能被继承。
  • 析构函数不能被重载。

静态分配对象的自动析构函数调用

对于使用静态内存分配创建的对象,析构函数会被自动调用。下面是一个示例 −

#include <iostream>
using namespace std;

class MyClass {
   public:
      MyClass() {
         cout << "Constructor called" << endl;
      }

      ~MyClass() {
         cout << "Destructor called automatically" << endl;
      }
};

int main() {
   cout << "Creating an object using static memory allocation" << endl;
   {
      MyClass obj;
      cout << "Object created" << endl;
   }  // 对象在这里超出作用域,
   //析构函数被自动调用
   cout << "Object destroyed" << endl;
   return 0;
}

上述代码的输出如下 −

Creating an object using static memory allocation
Constructor called
Object created
Destructor called automatically
Object destroyed

动态对象的 C++ 析构函数

对于使用动态内存分配创建的对象,需要使用 delete 手动调用析构函数。在下面的示例中,我们使用delete操作符手动删除了对象:

#include <iostream>
using namespace std;

class MyClass
{
   public:
      MyClass()
      {
          cout << "Constructor called" << endl;
      }

      ~MyClass()
      {
         cout << "Destructor called" << endl;
      }
};

int main() {
   cout << "Creating an object using dynamic memory allocation:" << endl;

   // 动态创建对象
   MyClass *obj = new MyClass();

   cout << "Object created" << endl;
   // 手动调用析构函数
   delete obj;
   cout << "Object destroyed manually using delete" << endl;
   return 0;
}

上述代码的输出如下 −

Creating an object using dynamic memory allocation:
Constructor called
Object created
Destructor called
Object destroyed manually using delete

多个对象的析构函数调用顺序

对于多个对象,析构函数按照构造的逆序被调用。在下面的示例中,我们可以看到析构函数按照逆序被调用 −

#include <iostream>
using namespace std;

class MyClass
{
   private:
      int id;

   public:
      MyClass(int num) : id(num)
      {
         cout << "Constructor called for object " << id << endl;
      }

     ~MyClass()
      {
          cout << "Destructor called for object " << id << endl;
      }
};

int main() {
	cout << "Creating 3 objects:" << endl;

	{
		MyClass obj1(1); // 第一个对象
		MyClass obj2(2); // 第二个对象
		MyClass obj3(3); // 第三个对象
		cout << "All objects created" << endl;

	} // 所有对象在这里超出作用域

	cout << "All objects destroyed" << endl;
	return 0;
}

上述代码的输出如下 −

Creating 3 objects:
Constructor called for object 1
Constructor called for object 2
Constructor called for object 3
All objects created
Destructor called for object 3
Destructor called for object 2
Destructor called for object 1
All objects destroyed

数组的析构函数

为了在数组被删除或超出作用域后清理内存,我们需要为数组的每个元素调用析构函数,以避免任何内存泄漏。在使用数组的析构函数时,可能有三种不同的场景。

  • 与静态数组一起使用析构函数
  • 与动态数组一起使用析构函数
  • 与对象指针数组一起使用析构函数

与静态数组一起使用析构函数

在这个示例中,我们有一个对象的静态数组,内存由编译器自动释放。这里,析构函数仅用于显示消息。

#include <iostream>
using namespace std;

class Example
{
   private:
      int id;

   public:
      Example(int i) : id(i)
      {
         cout << "Constructor called for object " << id << endl;
      }

     ~Example()
      {
         cout << "Destructor called for object " << id << endl;
      }
};

int main(){
   cout << "Creating array of 3 objects:" << endl;
   Example arr[3] = {Example(1), Example(2), Example(3)};

   cout << "Array created" << endl;

   return 0;
   // 析构函数自动调用
}

上述代码的输出如下所示 −

Creating array of 3 objects:
Constructor called for object 1
Constructor called for object 2
Constructor called for object 3
Array created
Destructor called for object 3
Destructor called for object 2
Destructor called for object 1

与动态数组一起使用析构函数

在这个示例中,使用new[] 操作符创建了一个对象的动态数组。这里,使用delete[] 操作符与析构函数一起清理内存。

#include <iostream>
using namespace std;

class Student
{
   private:
      int rollNo;

   public:
      Student(int r = 0) // 带默认参数的构造函数
      {
         rollNo = r;
         cout << "Constructor called for roll no: " << rollNo << endl;
      }

      ~Student()
      {
         cout << "Destructor called for roll no: " << rollNo << endl;
      }
};

int main() {
   cout << "Creating dynamic array of 3 students:" << endl;

   Student *students = new Student[3] {101, 102, 103}; // 使用学号初始化

   cout << "\nDeleting array:" << endl;
   delete[] students; // 所有 3 个对象的析构函数

   return 0;
}

上述代码的输出如下所示 −

Creating dynamic array of 3 students:
Constructor called for roll no: 101
Constructor called for roll no: 102
Constructor called for roll no: 103

Deleting array:
Destructor called for roll no: 103
Destructor called for roll no: 102
Destructor called for roll no: 101

与对象指针数组一起使用析构函数

在这个示例中,创建了一个指向对象的指针数组。这里,使用 delete 操作符与析构函数一起释放每个对象的内存,因为每个指针都指向一个唯一分配的对象。

#include <iostream>
using namespace std;

class Book {
   private:
      string title;

   public:
	
      Book(string t) : title(t)
      {
         cout << "Book created: " << title << endl;
      }

     ~Book()
     {
        cout << "Book destroyed: " << title << endl;
     }
};

int main(){
   cout << "Creating array of 3 books:" << endl;
    Book *library[3];

   // 创建单个图书对象
   library[0] = new Book("C++ Basics");
   library[1] = new Book("CSS Basics");
   library[2] = new Book("Javascript Basics");

   cout << "\nDeleting books:" << endl;

   // 逐个删除每个对象
   for (int i = 0; i < 3; i++){
      delete library[i];
   }

   return 0;
}

上述代码的输出如下所示 −

Creating array of 3 books:
Book created: C++ Basics
Book created: CSS Basics
Book created: Javascript Basics

Deleting books:
Book destroyed: C++ Basics
Book destroyed: CSS Basics
Book destroyed: Javascript Basics

使用析构函数时的常见错误

使用析构函数时的一些常见初学者错误如下所示 −

  • 对静态分配的内存使用destructor。它会自动被删除,因此无需使用析构函数。
  • 对于动态分配的内存,忘记使用delete
  • inheritance中,不在基类中将析构函数声明为virtual
  • 对于数组,使用delete[]而不是delete
  • 试图删除已经删除的内存两次,也称为double deletion。删除后将指针设置为nullptr,并在删除前检查指针是否为nullptr

结论

析构函数是特殊的成员函数,用于在对象超出作用域或程序执行完毕时清理内存。在 C++ 中,静态分配的内存有默认析构函数,而自定义析构函数用于使用 new 操作符清除动态分配的内存。