结构体布局和继承

结构体可以将其他结构体作为其字段。例如,我们定义 Inclosure 结构体,并对 Main 结构体中的 data 字段使用这一类型 (StructsComposition.mq5):

struct Inclosure
{
   double XY;
};
 
struct Main
{
   Inclosure data;
   int code;
};
 
void OnStart()
{
   Main m = {{0.10.2}, -1}; // aggregate initialization
   m.data.X = 1.0;            // assignment element by element
   m.data.Y = -1.0;
}

在初始化列表中,data 字段使用额外一层花括号表示,花括号内的字段值为​Inclosure。要访问这种结构体的字段,需要进行两次取消引用操作。

如果嵌套结构体未在其他地方使用,可以直接在外部结构体内声明它。

struct Main2
{
   struct Inclosure2
   {
     double XY;
   }
   data;
   int code;
};

另一种结构体布局方式是继承。这种机制通常用于构建类层次结构(将在相应 章节中详细讨论),但也可用于结构体。

在定义新的结构体类型时,程序员可以在结构体头部的冒号后注明父结构体的类型(必须在源代码的前面定义)。这样,父结构体的所有字段都将添加到子结构体中(在子结构体的开头),新结构体的字段将位于父结构体字段后面的内存中。

struct Main3 : Inclosure
{
   int code;
};

这里的父结构体不是嵌套结构,而是子结构体不可分割的一部分。因此,在初始化时填写字段不需要额外的花括号,也不需要一连串的多个取消引用操作符。

   Main3 m3 = {0.10.2, -1};
   m3.X = 1.0;
   m3.Y = -1.0;

所有三个结构体 MainMain2Main3 都具有相同的内存表示形式,大小为 20 字节。但它们的类型不同。

   Print(sizeof(Main));   // 20
   Print(sizeof(Main2));  // 20
   Print(sizeof(Main3));  // 20

如前所述(参见 拷贝结构体),赋值运算符 '=' 可用于复制相关类型的结构体,更确切地说,是拷贝由继承链链接的结构体。换言之,父类型的结构体可以写入子类型的结构体(在这种情况下,派生结构中添加的字段将保持不变),反之亦然,子类型的结构体可以写入父类型的结构体(在这种情况下,“多余”的字段将被删除)。

例如:

   Inclosure in = {10100};
   m3 = in;

这里,m3 变量的类型为 Main3,继承自 Inclosure。执行赋值 m3 = in 操作后,字段 XY(两种类型共有的成员)将从基类型变量 in 拷贝到派生类型变量 m3XY 字段中。m3 变量的 code 字段将保持不变。

子结构体是祖代结构体的直接后代还是间接后代并不重要,继承链的深度不影响此行为。这种公共字段的拷贝机制适用于“子结构体”、“孙结构体”以及“结构体继承树”不同分支的各种类型组合。

如果父结构体只有带参数的构造函数,那么在继承派生结构体构造函数时,必须从初始化列表中调用父结构体。例如,

struct Base
{
   const int mode;
   string s;
   Base(const int m) : mode(m) { }
};
 
struct Derived : Base
{
   double data[10];
   // if we remove the constructor, we get an error:
   Derived() : Base(1) { } // 'Base' - wrong parameters count
};

我们在 Base 构造函数中填充了 mode 字段。由于此字段包含 const 修饰符,因此只能在构造函数中为它设置值,而且必须在冒号后使用特殊的初始化语法才能完成(不能再在构造函数的正文中赋值常量)。有了显式构造函数,编译器就不会生成隐式(无参数)构造函数。但我们在 Base 结构体中没有显式无参数构造函数,如果没有它,任何派生类都不知道如何正确调用带参数的 Base 构造函数。因此,在 Derived 结构体中,必须显式地初始化基本构造函数:还需要在构造函数头文件中完成此操作,即在冒号 ':' 之后使用相应的初始化语法(在本例中,我们调用 Base(1))。

如果我们移除构造函数 Derived,基本构造函数中会出现“参数数量无效”错误,因为编译器会默认尝试调用 Base 的构造函数(该构造函数应该只有 0 个参数)。

我们将在 “类”一章中详细介绍语法和继承机制。