Содержание
Доброго времени суток. В одном из первых уроков мы рассмотрели возможные Типы данных в языке программирования MQL. Уяснили, что переменные должны быть обязательно объявлены под соответствующем типом данных. Теперь пришло время рассмотреть, какие переменные бывают и чем отличаются друг от друга.
Локальные переменные
Это тип переменной, которая объявлена внутри конкретной функции, т.е. на локальном уровне. Такая переменная может существовать только внутри заданной функции.
Область видимости это тот участок программы, в котором разрешено ссылаться на эту переменную. Пример:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
void OnStart() { int Var1 = 3; FirstFunction(); SecondFunction(); } //+------------------------------------------------------------------+ void FirstFunction() { int Var2 = 10; } //+------------------------------------------------------------------+ void SecondFunction() { int Var3 = 5; } |
Первая переменная Var1 объявлена в функции обработки событий OnStart скрипта. Она является локальной, соответственно ее областью видимости является только эта функция, ее нельзя использовать в расчете функций FirstFunction или SecondFunction. Если вы попробуете вывести ее в принт в любой из них, то программа просто не позволит скомпилировать этот код. Если же вам нужно передать ее в функцию, тогда ее нужно перевести в формальную переменную этой функции, об этом более подробно мы говорили в прошлом уроке.
То же самое касается и переменных Var2 и Var3. Первая может существовать только в функции FirstFunction, вторая в SecondFunction. После того, как функция завершила свою работу — переменная стирается из памяти.
Пример внутри одной функции:
0 1 2 3 4 5 6 |
void OnStart() { int a = 0; if(a >= 0) { int b = 1; } } |
Переменная целого типа a была объявлена внутри самой функции OnStart, тогда как переменная b объявлена внутри условного оператора if. Соответственно после оператора if переменная a останется существовать, тогда как переменная b сотрется из памяти после закрытой фигурной скобки оператора и ее уже нельзя будет использовать.
Еще один похожий пример:
0 1 2 3 4 5 6 7 8 9 |
void OnStart() { int a1 = 2; for(int i=0;i<1;i++) { int b1 = a1; if(b1 >= 0) { int c1 = b1+a1; } } } |
Тут переменная b1 существует только в пределах оператора цикла for и может использовать в условном операторе if, от открытой до закрытой фигурной скобки. Переменная c1 существует только в условном операторе if, но может учитывать в расчете как переменную a1, так и b1.
Формальные переменные
Формальными называются переменные, которые указываются в свойствах функции и могут быть использованы только в ее области видимости.
0 1 2 3 4 5 6 7 8 9 10 |
void OnStart() { int Order_Ticket = 5511233; datetime Order_Time = D'2020.03.01'; Function(Order_Ticket,Order_Time); } //+------------------------------------------------------------------+ void Function(int ticket, datetime time) { ... } |
Передаваемые в функцию Function переменные Order_Ticket и Order_Time являются локальными, но в теле самой функции они принимают значение ticket и time, и являются формальными.
К формальным переменным можно применять модификатор const (константа), тогда ее значение будет нельзя изменить внутри самой функции. Объявим переменную a константой:
0 1 2 |
void Function2(const int a, double d) { d = 10; } |
При попытке изменения переменной целого типа а компилятор будет ругаться на ошибку. Но переменную d можно изменять внутри тела функции.
Формальная переменная может заранее быть проинициализирована константой — пользовательским значением, в этом случае, если с локального уровня данные переменной не поступили то будут использованы данные по умолчанию:
0 1 2 3 4 5 6 7 8 9 10 11 |
void OnStart() { int One = 5, Three = 4 double Two = 6.0; OtherFunction(One,Two); } //+------------------------------------------------------------------+ void OtherFunction(int A1, double A2 = 2.0, int A3 = 10) { Print("A1: ",A1); Print("A2: ",A2); Print("A3: ",A3); } |
В функцию OtherFunction были переданы только локальные переменные One и Two, соответственно код учтет и выдаст принт их значений 5 и 6. Так как значение Three не было передано, то значение формальной переменной A3 становится равной 10. Пропускать передаваемые значения можно только, если у формальной переменной есть константа. Стоит заметить, что формальная переменная A2 тоже инициализирована константой, но т.к. ей передано значение с локального уровня, то значение 2.0 тут не учитывается.
Значения в функции читаются слева направо, и если мы зададим формальной переменной A2 константу, то все переменные, следующие за ней тоже обязаны иметь какое-либо значение по умолчанию, иначе такой код будет невозможно скомпилировать.
В уроке Пользовательские функции мы затрагивали способ передачи аргумента по ссылке (использование знака &), поэтому не будем повторяться, просто нужно запомнить, что с помощью данного метода можно значение формальной переменной передать на локальную обратно.
Статические переменные
Статическая переменная отличается от локальной тем, что ее значение сохраняется в функции с момента ее объявления до выгрузки программы. Может быть проинициализирована только константой или константным выражением, соответствующем ее типу, т.е. ее значением нельзя указать функцию или любое выражение. Чтобы ее задать, нужно перед типом данных переменной поставить слово static:
0 |
static int Value = 1; |
Область видимости переменной локальна, в пределах функции, в которой она объявлена. Если значение при инициализации не указаны, то по умолчанию она равна нулю. Формальные переменные не могут быть статическими.
0 1 2 3 4 5 6 7 8 9 |
void OnStart() { for(int i=0;i<4;i++) Func();; } //+------------------------------------------------------------------+ void Func() { static int cnt; cnt++; Print("cnt=",cnt); } |
В примере выше в функции Func мы задали статическую переменную. Далее прибавили к ее нулевому значению единицу и вывели принт. В функции обработки событий OnStart через цикл for четыре раза обратились к этой функции. Соответственно запись в журнале по очереди выдаст нам результат: 1,2,3 и 4, а не четыре раза по единице, как это бы было с локальной функцией.
Применений ей можно придумать много, самый простой это сравнивать в советнике значение цены Bid на предыдущем тике с текущим значением, а после переписывать статическую переменную под новые данные:
0 1 2 3 4 5 |
void OnTick() { static double bid; if(bid > Bid) Print("Цена пошла вниз"); bid = Bid; } |
Глобальные переменные
Данный тип переменных объявляется вне функции, на том же уровне, что и свойства программы, include и импорт. Они не являются локальными и их можно использовать в любой части кода, в любой функции, т.е. их областью видимости является вся программа. Как правило, их следует объявлять перед всеми функциями, рядом с #property свойствами программы.
0 1 2 3 4 5 6 7 |
#property version "1.00" datetime Time1 = 0; void OnStart() { Print(Time1 ); } |
Если переменная не инициализирована программистом, то по умолчанию она равна нулю. Проинициализировать ее можно только соответствующей константой или константным выражением. Инициализация происходит однократно, т.е. уже присвоенное имя глобальной переменной нельзя передавать новым переменным внутри какой-либо функции. Время ее жизни определено началом ее объявления и окончанием работы программного кода.
0 1 2 3 4 5 6 7 8 9 10 |
int Global = 222; void OnStart() { Print(__FUNCTION__,": ",Global); FuncFunc(); } //+------------------------------------------------------------------+ void FuncFunc() { Print(__FUNCTION__,":",Global); } |
Как видите, через сообщение в журнал мы смогли без труда получить значение Global в каждой из функций.
Небольшой лайфхак: Если в принте вначале прописать слово __FUNCTION__, то оно выведет в журнал наименование той функции, в которой был запущен этот принт. Это очень помогает искать ошибки в разных частях кода, а также визуально отделять разные информационные сообщения друг от друга.
Стоит заметить, что многие путают глобальную переменную, которая объявляется на глобальном уровне с глобальной переменной клиентского терминала GlobalVariable. Это разные типы переменных. Если вы хотите увидеть урок о глобальных терминала, напишите нам об этом в комментарии, либо нам на почту.
Extern переменные
Тип переменных Extern отличается от глобальных тем, что они являются внешними. Это именно те переменные, которые пользователь видит в настройках программы в окне «Входные параметры«:
Они нужны для точной настройки скрипта, советника или индикатора. Внешние переменные не могут быть проинициализированы функцией или массивом, и должны соответствовать своему типу данных. Длина string переменной не может быть больше 63 символа. Объявляются они на глобальном уровне вне функции в самом верху кода. Для этого перед типом данных нужно добавить слово extern (внешний):
0 1 2 3 4 5 6 7 8 9 10 |
#property script_show_inputs extern string s1 = "Наши настройки:"; extern int ticket = 0; extern double TP = 20; extern datetime Time2 = 0; void OnStart() { ... } |
Чтобы эти переменные были видимы в скрипте, нужно прописать свойство программы #property script_show_inputs. В советниках и индикаторах этого делать не нужно, они видимы по умолчанию. Теперь эти четыре переменные будут иметь следующий вид в настройках программы:
Для отображения собственного имени переменной, которое более удобно для чтения пользователем, используется знак комментария // после нее. Т.е. программный код будет обращаться к объявленному имени этой переменной, но во входных параметрах вашего кода будет виден более читаемый текст. Чтобы достичь такого эффекта, нужно указать в свойствах строгий режим компиляции strict.
0 1 2 3 4 5 |
#property strict extern string s1 = "Наши настройки:"; //> > > > > extern int ticket = 0; //Order Ticket extern double TP = 20; //Take Profit extern datetime Time2 = 0; //Minimum Time |
Согласитесь, стало все намного четче и понятнее. Не стоит забывать, что это только визуальный вид. Когда вы будете тестировать советник, в результатах теста будут отображаться наименования самих переменных, что может вас запутать, особенно, если настроек много.
Переменную типа extern можно изменять в любом месте программного кода:
0 1 2 3 4 5 |
extern double StopLoss = 50; void OnStart() { StopLoss = 100; } |
Input переменные
Единственное и основное отличие переменной типа input от extern в том, что ее значение нельзя менять в программном коде после объявления.
В обоих внешних переменные предусмотрено использовать ENUM тип данных, который позволяет сделать выбор из списка. Это может быть таймфрейм индикатора, его метод, цена закрытия и т.п.
0 1 2 |
input ENUM_TIMEFRAMES TimeFrame = PERIOD_D1; input ENUM_MA_METHOD MA_Method = MODE_SMA; input ENUM_APPLIED_PRICE Price = PRICE_CLOSE; |
Проверьте этот код для большего понимания в окне настроек скрипта, там появится три списка с выбором. Вместо объявления определенного типа данных для переменных, были использованы уже предопределенные значения ENUM терминала. Таких значений огромное множество, в основном они используются для работы с индикаторами.
Первое значение это период графика, с которого будут браться данные. Мы не можем сами менять этот список, он предопределен по умолчанию. Второй параметр это тип скользящей средней, а третий — это ее цена для расчета.
Если же пользователь хочет создать свой собственный ENUM список, тогда его необходимо объявить перед внешними переменными. Попробуем сделать список из трех значений:
0 1 2 3 4 5 6 7 |
enum MyList { First = 1, Second = 2, Third = 3 }; input MyList list = First; //List |
Вначале пишется слово enum, далее его наименование (в нашем случае это MyList) и уже в фигурных скобках указывается столько параметров, сколько нам нужно. Мы взяли три и присвоили им значения 1, 2 и 3. Между параметрами ставится запятая, у последнего параметра не нужно ставить никакой знак. У енумов значения могут быть только целыми числами. После этого через input и extern мы вызываем получившийся енум, задаем его переменной какое-либо имя (list) и значение по умолчанию.
У переменных енум тоже можно задавать пользовательский текст через комментарий для отображения их в настройках окна:
0 1 2 3 4 5 |
enum MyList { First = 1, //1 Value Second = 2, //2 Value Third = 3 //3 Value }; |
Sinput переменные
Выполняют те же действия, что и input переменные, только приписка буквы S обозначает, что переменная является Static (статичной) и данные переменные нельзя оптимизировать в советнике. Порой переменные нужно оставлять статическими, в окне оптимизации квадрат для галочки выбора параметра для оптимизации уже нельзя использовать:
Получается, что у переменных типа sinput значения Старт, Шаг и Стоп автоматически становятся неактивными. Флажки переменных типа string по умолчанию нельзя выбрать для оптимизации, т.к. текст невозможно настроить.
Если вы хотите, чтобы пользователь вашего советника мог оптимизировать только часть настроек — sinput переменные помогут вам с этим.
На этой ноте можно завершить сегодняшний урок. Прикреплять .mq4 для данного урока не вижу смысла, весь пройденный материал для практики вы сможете легко скопировать с этого поста.