Статические переменные

Иногда бывает необходимо описать переменную внутри функции, но обеспечить её существование на протяжении всего времени выполнения программы. Например, мы хотим подсчитывать количество раз, сколько данная функция вызывалась.

Такая переменная не может быть локальной, потому что тогда она потеряет свою "долговременную память", поскольку будет создаваться каждый раз при вызове функции и удаляться при выходе из неё. Чисто технически её можно было бы описать глобально, но если переменная используется только в этой функции, такой подход неверен с точки зрения дизайна программ.

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

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

Следовательно, счетчик выполнений функции следует описывать внутри самой функции. Здесь и приходит на помощь новый атрибут переменных — статичность.

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

Статические переменные могут быть описаны и на глобальном уровне, но ничем не отличаются от обычных глобальных (по крайней мере, на момент написания книги). Это разнится с их поведением в C++: там их видимость ограничивается тем файлом, в котором они описаны. В MQL5 программа собирается на основе одного главный mq5-файла и, возможно, нескольких подключаемых (см. директиву #include), поэтому и статические, и обычные глобальные переменные доступны из всех исходных файлов программы.

Локальная статическая переменная создается единожды — в тот момент, когда программа первый раз входит в функцию, где эта переменная описана. А удалится такая переменная только при выгрузке программы. Если какая-либо функция ни разу не вызывалась, то локальные статические переменные, описанные в ней (если такие есть), так и не будут созданы.

В качестве примера модифицируем функцию Greeting из первой части книги таким образом, чтобы она выдавала разные приветствия при каждом обращении. Назовем новый скрипт GoodTimes.mq5.

Уберем входной параметр скрипта GreetingHour и параметр самой функции Greeting. Внутри функции Greeting опишем новую статическую переменную counter целого типа, с начальным значением 0. Напомним, что это именно инициализация, и она выполнится лишь однажды, потому что переменная статическая.

string Greeting()
{
  static int counter = 0;
  static string messages[3] =
  {
    "Good morning""Good day""Good evening"
  };
  return messages[counter++ % 3];
}

Поскольку мы теперь знаем модификатор static, имеет смысл применить его и для массива messages. Дело в том, что ранее он был объявлен локальным, и при многократном вызове функции Greeting создавался бы каждый раз заново (и удалялся бы при выходе). Это не эффективно.

Напомним, что массив — это именованный набор нескольких однотипных переменных, доступных по индексу (который указывается в квадратных скобках после имени). Многое из того, что было сказано о переменных, напрямую относится и к массивам. Дополнительные нюансы работы с массивами будут освещены в разделе Массивы.

Но вернемся к нашей текущей задаче. Выбор варианта из массива на основе значения переменной counter производится в инструкции return и выглядит пока довольно кабалистически:

  return messages[counter++ % 3];

Операция деления по модулю с помощью символа '%' уже освещалась вскользь в первой части. С помощью неё мы здесь гарантируем, что индекс элемента не сможет превысить размер массива: каким бы ни был counter, деление его по модулю на 3 даст либо 0, либо 1, либо 2.

Что же касается конструкции counter++, она означает увеличение значения переменной на 1 (единичный инкремент).

Важно отметить, что в указанной нотации инкремент будет происходить уже после вычисления всего выражения, в данном случае — после деления counter % 3. Это означает, что счет пойдет, начиная с нуля (начального значения). Существует возможность делать инкремент до вычисления выражения, написав ++counter % 3. Тогда счет начинался бы с 1. Мы изучим операции такого типа в разделе Инкремент и декремент.

Вызовем функцию Greeting из OnStart 3 раза подряд.

void OnStart()
{
  Print(Greeting(), ", "Symbol());
  Print(Greeting(), ", "Symbol());
  Print(Greeting(), ", "Symbol());
  // Print(counter); // error: 'counter' - undeclared identifier
}

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

GoodTimes (EURUSD,H1)        Good morning, EURUSD
GoodTimes (EURUSD,H1)        Good day, EURUSD
GoodTimes (EURUSD,H1)        Good evening, EURUSD

Если продолжать вызовы функции, счетчик будет увеличиваться, а сообщения пойдут по кругу.

Попытка обратиться к переменной counter в конце OnStart (закомментирована) не даст коду скомпилироваться, поскольку статическая переменная хоть и продолжает существовать, доступна только внутри функции Greeting.

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