Компоновка и наследование структур

Структуры могут иметь в качестве своих полей другие структуры. Например, определим структуру Inclosure и используем этот тип для поля data в структуре Main (StructsComposition.mq5):

struct Inclosure
{
   double XY;
};
 
struct Main
{
   Inclosure data;
   int code;
};
 
void OnStart()
{
   Main m = {{0.10.2}, -1}; // агрегатная инициализация
   m.data.X = 1.0;            // поэлементное присвоение
   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;

Все три рассмотренные структуры Main, Main2, Main3 имеют одинаковое представление в памяти и размер 20 байтов. Но это разные типы.

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

Как мы говорили ранее (см. Копирование структур), оператор присваивания '=' можно применять для копирования родственных типов структур, а точнее для тех, которые связаны цепочкой наследования. Иными словами, структуру родительского типа можно записать в структуру дочернего типа (при этом добавленные в производной структуре поля останутся нетронутыми), или наоборот, структуру дочернего типа — записать в структуру родительского типа (при этом "лишние" поля отсекаются).

Например:

   Inclosure in = {10100};
   m3 = in;

Здесь переменная m3 имеет тип Main3, унаследованный от Inclosure. В результате операции присваивания m3 = in, поля X и Y (общая часть для обоих типов) скопируются из переменной in базового типа в поля X и Y в переменной m3 производного типа. Поле code переменной m3 останется без изменений.

Неважно является ли структура-наследник прямым потомком прародителя или отдаленным, т.е. цепочка наследований может быть длинной. Подобное копирование общих полей работает между "детьми", "внуками" и прочими вариантами сочетания типов из разных ответвлений "родословной".

Если родительская структура имеет только конструкторы с параметрами, при наследовании необходимо вызвать его из списка инициализации при конструкторе производной структуры. Например,

struct Base
{
   const int mode;
   string s;
   Base(const int m) : mode(m) { }
};
 
struct Derived : Base
{
   double data[10];
   // если удалить конструктор, получим ошибку:
   Derived() : Base(1) { } // 'Base' - wrong parameters count
};

В конструкторе Base мы заполняем поле mode. Поскольку оно имеет модификатор const, конструктор — единственный способ установить ему значение, причем делать это нужно в виде специального синтаксиса инициализации после двоеточия (присвоить константу в теле конструктора уже нельзя). Наличие явного конструктора приводит к тому, что компилятор не генерирует неявный конструктор (без параметров). Однако у нас в структуре Base нет явного конструктора без параметров, а в его отсутствии любой производный класс не обладает сведениями, как правильно вызвать конструктор Base с параметром. Поэтому в структуре Derived требуется явным образом производить инициализацию базового конструктора: это так же делается с помощью синтаксиса инициализации в заголовке конструктора, после ':' — в данном случае мы вызываем Base(1).

Если убрать конструктор Derived, получим ошибку "неверное количество параметров" в базовом конструкторе, так как компилятор пытается вызвать для Base конструктор по умолчанию (а у него должно быть 0 параметров).

Более подробно мы рассмотрим синтаксис и механизм наследования в Главе про классы.