动态创建对象:new 和 delete
到目前为止,我们只尝试创建了自动对象,也就是 OnStart 函数内部的局部变量。在全局上下文中声明的对象(在 OnStart 或其他函数外部)也会被自动创建(加载脚本时)和删除(卸载脚本时)。
除了这两种模式之外,我们还提到了描述对象类型的字段的能力(在我们的示例中,这是 Shape 对象内部用于 coordinates 字段的 Pair 结构体)。所有这些对象也都是自动的:由编译器在“宿主”对象的构造函数中创建,并在其析构函数中删除。
然而,在程序中仅依赖自动对象往往无法满足需求。在绘图程序中,我们需要根据用户的请求创建形状。此外,形状需要存储在数组中,而为了实现这一点,自动对象必须拥有默认构造函数(但在我们的例子中并非如此)。
对于这种情况,MQL5 提供了动态创建和删除对象的能力。创建通过 new 运算符实现,删除通过 delete 运算符实现。
运算符new
new 关键字后面是所需类的名称,以及用于调用所有现有构造函数的自变量列表(用圆括号括起)。执行 new 运算符会导致创建类的实例。
new 运算符会返回特殊类型的值,即指向对象的指针。要描述这种类型的变量,需要在类名后添加星号 '*'。例如:
Rectangle *pr = new Rectangle(100, 200, 50, 75, clrBlue); |
在这里,pr 变量的类型是指向 Rectangle 类对象的指针。指针将在一个单独的 一节中详细讨论。
需要注意的是,声明一个对象指针类型的变量本身并不会为对象分配内存,也不会调用其构造函数。当然,指针本身会占用一定空间(8 个字节),但实际上,它是一个无符号长整型 ulong,系统会以一种特殊的方式来解读它。
您可以像操作对象一样操作指针,也就是说,可以通过取消引用运算符来调用可用的方法,并且可以访问字段。
Print(pr.toString()); |
一个尚未被赋予动态对象描述符的指针变量(例如,new 运算符不是在初始化新变量时立即调用,而是放在了源代码后面的某些行),会包含一个特殊的空指针,用 NULL 表示(以区别于数字),但实际上其值为 0。
运算符delete
在算法结束时,应该使用 delete 运算符释放通过 new 获得的指针。例如:
delete pr; |
如果不这样做,new 运算符分配的实例将一直驻留在内存中。如果以这种方式创建越来越多的新对象,并且不需要时没有将其删除,这将导致不必要的内存消耗。程序终止时,剩余的未释放动态对象会导致打印警告信息。例如,如果您不删除指针 pr,那么在脚本卸载后,您将在日志中看到类似这样的信息:
1 undeleted object left
|
终端会报告程序员忘记释放了多少个对象以及它们的类名,以及它们占用了多少内存。
一旦对某个指针调用了 delete 运算符,该指针就会失效,因为对象已不再存在。随后尝试访问其属性会导致运行时错误:“访问了无效指针”:
Critical error while running script 'shapes (EURUSD,H1)'.
|
MQL 程序随后会被中断。
然而,这并不意味着同一个指针变量无法再使用。只需要将该指针分配给另一个新创建的对象实例即可。
MQL5 有一个内置函数 CheckPointer,可用于检查变量中指针的有效性:
ENUM_POINTER_TYPE CheckPointer(object *pointer); |
它接受一个指向类类型的指针作为参数,并返回 ENUM_POINTER_TYPE 枚举类型的值:
- POINTER_INVALID 指针不正确;
- POINTER_DYNAMIC 指向动态对象的有效指针;
- POINTER_AUTOMATIC 指向自动对象的有效指针。
只有当 CheckPointer 函数返回 POINTER_DYNAMIC 时,执行 delete 语句才有意义。对于自动对象,delete 操作不会产生任何影响(这类对象在控制流离开定义该变量的代码块时会自动删除)。
以下宏简化并确保指针的正确清理:
#define FREE(P) if(CheckPointer(P) == POINTER_DYNAMIC) delete (P) |
为了获得动态对象和指针所提供的灵活性,显式地进行“清理”是不可避免的代价。