类定义

类定义语句包含许多可选组件,这些组件会影响其特性。其通用形式可以表示如下:

class class_name [: modifier_access name_parent_class ...]
{
  [ modifier_access:]
     [description_member...]
  ...
};

为了便于演示,我们将从最基本的可用语法开始,并随着学习内容的深入而不断延伸。

作为起点,我们使用一个包含条件绘图程序的任务,该程序支持多种形状。

要定义一个新类,请使用 class 关键字,后跟类标识符和用花括号括起来的代码块。与所有语句一样,这种定义必须以分号结尾。

代码块可以为空。例如,一个绘图程序的 Shape 类的可编译模板如下所示:

class Shape
{
};

从本书的前几章中,我们知道花括号表示变量的上下文或作用域。当这样的代码块出现在函数定义中时,它们定义了函数的局部上下文。除此之外,还有一个全局上下文,函数本身以及全局变量都在其中定义。

这次,类定义中的圆括号定义了一种新的上下文,即类上下文。它是类内部声明的变量和函数的容器。

用于存储类属性的变量的说明由块 (Shapes1.mq5) 内的常用语句完成。

class Shape
{
   int xy;              // center coordinates
   color backgroundColor// fill color
};

这里我们声明了理论小节讨论过的一些字段:形状中心的坐标和填充颜色。

完成这样的说明后,用户定义类型 Shape 便可以与内置类型一起用在程序中。具体来说,我们可以创建一个这种类型的变量并在其中包含指定的字段。但是,我们还不能对它们做任何操作,甚至不能确定它们是否存在。

void OnStart()
{
   Shape s;
   // errors: cannot access private member declared in class 'Shape'
   Print(s.x" "s.y);
}

默认情况下,类成员是私有的,因此不能从类外部代码的其他部分访问。这就是封装原则的作用。

如果我们尝试将形状输出到日志中,结果会让我们失望,原因有几个。

最直接的方法会导致错误“对象只能通过引用传递”(我们在结构体中也看到过这种情况):

Print(s); // 's' - objects are passed by reference only

对象可能包含许多字段,由于其体积庞大,通过值传递的效率很低。因此,编译器要求对象类型参数通过引用传递,而 Print 则获取值。

在有关函数参数的章节(参见 值参数和引用参数一节),我们知道符号 '&' 用于描述引用。因此,我们可以合理地认为,要获取变量(本例中为 Shape 类型的 s 对象)的引用,必须在其名称前加上相同的符号。

Print(&s);

这条语句的编译和运行都没有问题,但并没有达到预期的效果。

程序在执行过程中会输出某个整数,例如 1 或 2097152(很可能不同)。变量名前的和号表示获取指向该变量的指针,而不是引用(与函数参数说明相反)。

指针 将在一个单独的小节中详细讨论。但请注意,MQL5 并不提供直接访问内存的功能,而指向对象的指针是一个描述符,或者简单地说,是一个唯一的对象编号(由终端自己分配)。但是,即使指针指向内存中的地址(就像 C++ 中那样),也无法提供读取对象内容的合法途径。

要将 Shape 对象的内容输出到日志或其他内容中,需要一个类成员函数。我们把它叫做 toString:它应该返回一个包含对象描述的字符串。我们可以稍后决定在字符串中显示什么内容。我们还要保留用于绘制形状的 draw 方法。现在,它将作为未来对象编程接口的声明。

class Shape
{
   int xy;              // center coordinates
   color backgroundColor// fill color
   
   string toString()
   {
      ...
   }
   
   void draw() { /* future drawing interface stub */ }
};

方法函数的定义方法与通常的方法相同,唯一不同的是它们位于构成类的代码块中。

今后,我们将学习如何将类代码块内的函数声明与 类代码块外的定义分开。这种方法通常用于将声明放在头文件中,而将定义隐藏在 mq5 文件中。这使得代码更易于理解(因为编程接口是以紧凑的形式单独呈现的,不含具体实现方式)。如果需要,还可以将 软件库 作为 ex5 文件分发(不包含主源代码,只是提供一个足以调用外部接口方法的头文件)。

由于 toString 方法是类的一部分,因此它可以访问变量并将其转换为字符串。例如,

string toString()
{
  return (string)x + " " + (string)y;
}

但是,现在 toStringdraw 是私有的,其余字段也是如此。我们需要将它们设为可以从类外部访问。