构造函数和析构函数

在可以为结构体定义的方法中,有一些特殊的方法:构造函数和析构函数。

构造函数的名称与结构体名称相同,但不返回值(类型为 void)。如果定义了构造函数,将在初始化结构体的每个新实例时调用。因此,在构造函数中,可以通过特殊方式计算结构体的初始状态。

一个结构体可以有多个带有不同参数集的构造函数,编译器会在定义变量时根据自变量的数量和类型选择合适的构造函数。

例如,我们可以在 Result 结构体中描述一对构造函数:一个不带参数,另一个带一个字符串类型参数,用于设置状态。

struct Result
{
   ...
   void Result()
   {
      status = "ok";
   }
   void Result(string s)
   {
      status = s;
   }
};

顺便说一下,不带参数的构造函数称为默认构造函数。如果没有显式构造函数,编译器会为任何包含字符串和动态数组的结构体隐式创建一个默认构造函数,目的是在这些字段中填充 0。

重要的是,无论结构体是否有默认构造函数,其他类型的字段(例如所有数值字段)都不会被重置为零,因此内存分配后元素的初始值将是随机的。您应该创建构造函数,或者确保在创建对象后立即在代码中正确赋值。

如果存在显式构造函数,就无法使用聚合初始化语法。因此,calculate 方法中的 Result r = {}; 行将不会进行编译。现在,我们只有权使用我们自己提供的其中一个构造函数。例如,以下语句调用了无参数构造函数:

   Result r1;
   Result r2();

可以按如下所示创建一个已填充状态的结构体:

   Result r3("success");

创建结构体数组时,也会调用默认构造函数(显式或隐式)。例如,下面的语句为 10 个有结果的结构体分配内存,并使用默认构造函数对它们进行初始化:

   Result array[10];

析构函数是在销毁结构体对象时调用的函数。析构函数的名称与结构体名称相同,但前缀是一个波浪号 '~'。与构造函数一样,析构函数不返回值,但也不接受参数。

析构函数只能有一个。

您不能显式地调用析构函数。程序本身会在退出定义了局部结构体变量的代码块或释放结构体数组时调用析构函数。

如果结构体在构造函数中分配了动态资源,那么析构函数的作用就是释放这些资源。例如,结构体可以具有 persistence 属性,即从内存中卸载结构体时将其状态保存到文件中,并在程序再次创建结构体时恢复状态。在这种情况下,需要打开和关闭的描述符会被用于内置 文件函数中。

我们在 Result 结构体中定义一个析构函数,并逐步添加构造函数,以便所有这些方法都能跟踪对象实例的数量(随着它们的创建和销毁)。

struct Result
{
   ...
   void Result()
   {
      static int count = 0;
      Print(__FUNCSIG__" ", ++count);
      status = "ok";
   }
 
   void Result(string s)
   {
      static int count = 0;
      Print(__FUNCSIG__" ", ++count);
      status = s;
   }
 
   void ~Result()
   {
      static int count = 0;
      Print(__FUNCSIG__" ", ++count);
   }
};

三个名为 count 的静态变量彼此独立存在:每个变量都在各自函数的上下文中计数。

运行脚本后,我们将收到以下日志:

Result::Result() 1
Result::Result() 2
Result::Result() 3
Result::~Result() 1
Result::~Result() 2
0.5 1 ok
1.00000 2.00000 3.00000
Result::Result(string) 1
0.5 1 ok
1.00000 2.00000 3.00000
Result::~Result() 3
Result::~Result() 4

我们来弄清楚它的含义。

结构体的第一个实例是在函数 OnStart 中创建的,也就是调用 calculate 的那一行。在进入构造函数时,计数器值 count 被初始化为 0 一次,然后每次执行构造函数时都会递增,因此第一次输出的值是 1。

calculate 函数内部,定义了一个 Result 类型的局部变量;该变量的寄存器编号为 2。

第三个结构体实例并不明显。关键在于,为了从函数中传递结果,编译器隐式创建了一个临时变量,并在其中拷贝了本地变量的数据。将来这种行为很可能会改变,届时本地实例将“移出”函数而不会重复。

最后一次构造函数调用是在一个带有字符串参数的方法中进行的,因此调用计数为 1。

重要的是,两个构造函数的总调用次数必须与析构函数的调用次数相同,也就是4 次。

我们将在关于“类”的章节详细介绍 构造函数析构函数