嵌套类型、命名空间和上下文运算符 '::'

类、结构体和联合体不仅可以在全局上下文中描述,还可以在其他类或结构体中描述。甚至还可以在函数内部进行定义。这样,您可以在适当的上下文中描述任何类或结构体运行所需的所有实体,从而避免潜在的名称冲突。

具体来说,在绘图程序中,用于存储坐标的结构体 Pair 到目前为止是在全局定义的。随着程序规模增长,很可能需要另一个名为 Pair 的实体(尤其考虑到这个名称相当通用)。因此,最好将结构体的描述移到 Shape (Shapes6.mq5) 类内部。

class Shape
{
public:
   struct Pair
   {
      int xy;
      Pair(int aint b): x(a), y(b) { }
   };
   ...
};

嵌套说明的访问权限取决于指定的段修饰符。在本例中,我们将名称 Pair 设为公开可用。在 Shape 类内部,Pair 结构体类型的处理不会因转移而发生任何变化。但是,在外部代码中,您必须指定一个完全限定名称,其中包含外部类(上下文)的名称、上下文选择运算符 '::' 以及内部实体标识符本身。例如,要描述具有一对坐标的变量,可以这样写:

Shape::Pair coordinates(00);

描述实体时的嵌套层级不受限制,因此完全限定名称可以包含由 '::' 分隔的多层级标识符(上下文)。例如,我们可以将所有绘图类包装在外部类 Drawingpublic 部分中。

class Drawing
{
public:
   class Shape
   {
   public:
      struct Pair
      {
         ...
      };
   };
   class Rectangle : public Shape
   {
      ...
   };
   ...
};

然后,完全限定的类型名称(例如,用于 OnStart 或其他外部函数)将被加长:

Drawing::Shape::Rect coordinates(00);
Drawing::Rectangle rect(2001007050clrBlue);

虽然这很不方便,但在包含许多类的大型项目中,有时这很有必要。在我们的小型项目中,这种方法仅用于演示技术可行性。

为了将逻辑相关的类和结构体组合成命名组,MQL5 提供了一种比将它们包含在“空”包装器类中更简单的方法。

命名空间使用关键字 namespace 声明,后跟命名空间的名称以及包含所有必要定义的代码块(用花括号括起)。以下是使用 namespace 的相同绘图程序的示例:

namespace Drawing
{
   class Shape
   {
   public:
      struct Pair
      {
         ...
      };
   };
   class Rectangle : public Shape
   {
      ...
   };
   ...
}

主要区别在于:空间的内部内容始终公开可用(访问修饰符在其中不适用),并且右花括号后没有分号。

我们为 Shape 类添加 move 方法,该方法以 Pair 结构体作为参数:

class Shape
{
public:
   ...
   Shape *move(const Pair &pair)
   {
      coordinates.x += pair.x;
      coordinates.y += pair.y;
      return &this;
   }
};

然后,在 OnStart 函数中,您可以通过调用此函数来整理所有形状按给定值的偏移:

void OnStart()
{
   //draw a random set of shapes
   for(int i = 0i < 10; ++i)
   {
      Drawing::Shape *shape = addRandomShape();
      // move all shapes
      shape.move(Drawing::Shape::Pair(100100));
      shape.draw();
      delete shape;
   }
}

请注意,ShapePair 类型必须分别使用全名 Drawing::ShapeDrawing::Shape::Pair 来描述。

可能有多个块的空间名称相同:它们的所有内容都将归入一个具有指定名称的逻辑统一上下文中。

全局上下文中定义的标识符,特别是 MQL5 API 的所有内置函数,也可以通过上下文选择运算符(前面不带任何符号)访问。例如,对 Print 函数的调用可能如下所示:

::Print("Done!");

从全局上下文中定义的任何函数进行调用时,不需要这样的条目。

如果类或结构体中定义了同名元素(函数、变量或常量),则其中需要这样的条目例如,我们将 Print 方法添加到 Shape 类中:

   static void Print(string x)
   {
      // empty
      // (likely will output it to a separate log file later)
   }

由于派生类中 draw 方法的测试实现会调用 Print,因此它们现在被重定向到此 Print 方法:在多个相同标识符中,编译器会选择在更接近上下文中定义的标识符。在本例中,基类中的定义比全局上下文更接近 Shape 类。因此,Shape 类的日志输出将被抑制。

但是,从函数 OnStart 调用 Print 仍然有效(因为它位于 Shape 类的上下文之外)。

void OnStart()
{
   ...
   Print("Done!");
}

要修复类中的调试打印问题,您需要在所有 Print 调用之前添加一个全局上下文选择运算符:

class Rectangle : public Shape
{
   ...
   void draw() override
   {
      ::Print("Drawing rectangle"); // reprint via global Print(...)
   }
};