Определение структур

Структура состоит из переменных, которые могут иметь встроенные или другие пользовательские типы. Назначение структуры — объединить логически связанные данные в едином контейнере. Представим, что у нас есть функция, выполняющая некий расчет и принимающая набор параметров: количество баров в истории котировок для анализа, дата начала анализа, тип цены, количество выделяемых сигналов (например, гармоник).

double calculate(datetime startint barNumber,
                 ENUM_APPLIED_PRICE priceint components);

В реальности параметров может быть больше и передавать их в функцию списком будет неудобно. Кроме того, по результатам нескольких расчетов имеет смысл сохранить несколько лучших вариантов настроек в какой-то массив. Поэтому набор параметров удобно представить единым объектом.

Описание структуры с теми же переменными выглядит следующим образом:

struct Settings
{
   datetime start;
   int barNumber;
   ENUM_APPLIED_PRICE price;
   int components;
};

Описание начинается с ключевого слова struct, за которым идет выбранный нами идентификатор. Далее следует блок кода в фигурных скобках, а внутри него — описания переменных в составе структуры. Они еще называются полями или членами структуры. После фигурных скобок стоит точка с запятой, так как всё вместе это — инструкция определения нового типа, а после инструкций требуется ';'.

После определения типа мы можем его применять точно также как и встроенные типы. В частности, новый тип разрешает описать в программе переменную Settings привычным образом.

Settings s;

Важно отметить, что одиночное описание структуры позволяет создавать произвольное количество переменных-структур и даже массивов этого типа. В каждом экземпляре структуры будет выделен свой собственный набор элементов, и они будут содержать независимые значения.

Для обращения к членам структуры предусмотрен специальный оператор разыменования — символ точка '.'. Слева от него должна стоять переменная типа структуры, а справа — идентификатор одного из имеющихся в ней полей. Вот каким образом можно присвоить значение элементу структуры:

void OnStart()
{
   Settings s;
   s.start = D'2021.01.01';
   s.barNumber = 1000;
   s.price = PRICE_CLOSE;
   s.components = 8;
}

Существует более удобный способ заполнить структуру — агрегатная инициализация. В этом случае справа от переменной-структуры пишется знак '=' и далее в фигурных скобках список начальных значений всех полей через запятую.

   Settings s = {D'2021.01.01', 1000PRICE_CLOSE8};

Типы значений должны совпадать с типами соответствующих элементов. Допускается указать меньшее количество значений, чем количество полей: тогда оставшиеся поля получат нулевые значения.

Обратите внимание, что этот способ работает только при инициализации переменной, во время её определения. Присвоить таким образом содержимое уже существующей структуре нельзя, мы получим ошибку компиляции.

   Settings s;
   // error: '{' - parameter conversion not allowed
   s = {D'2021.01.01', 1000PRICE_CLOSE8};

С помощью оператора разыменования можно также и прочитать значение элемента структуры. Например, используем количество баров для расчета количества компонентов.

   s.components = (int)(MathSqrt(s.barNumber) + 1);

Здесь MathSqrt — это встроенная функция взятия квадратного корня.

Мы ввели новый тип Settings для упрощения передачи набора параметров в функцию. Теперь его можно использовать в качестве единственного параметра обновленной функции calculate:

double calculate(Settings &settings);

Обратите внимание на знак амперсанда '&' перед именем параметра, что означает передачу по ссылке. Структуры можно передавать в качестве параметров только по ссылке.

Структуры также бывают полезны, если из функции нужно вернуть не одно значение, а набор. Представим, что функция calculate должна вернуть не значение типа double, а несколько коэффициентов и некие торговые рекомендации (направление сделок и вероятность успеха). Тогда мы можем определить тип структуры Result и использовать его в прототипе функции (Structs.mq5).

struct Result
{
   double probability;
   double coef[3];
   int direction;
   string status;
};
 
Result calculate(Settings &settings)
{
   if(settings.barNumber > 1000// редактируем поля
   {
      settings.components = (int)(MathSqrt(settings.barNumber) + 1);
   }
   // ...
   // эмулируем получение результата
   Result r = {};
   r.direction = +1;
   for(int i = 0i < 3i++) r.coef[i] = i + 1;
   return r;
}

Пустые фигурные скобки в строке Result r = {} представляют собой минимальный агрегатный инициализатор: он заполняет все поля структуры нулями.

Определение и объявление типа структуры можно, при необходимости, сделать отдельно (как правило, объявление идет в заголовочном mqh-файле, а определение - в mq5-файле). Данный расширенный синтаксис будет рассмотрен в Главе про классы.