Всем привет! Сегодня мы продолжим изучать язык программирования mql5, и настало время более подробно познакомиться с функциями. В этом видеоуроке мы разберемся с тем, что это такое, для чего они нужны и как функции использовать.
Предыдущие уроки
- MQL5 Урок 1 – Редактор кода, События, Устройство mql-программ
- MQL5 Урок 2 – Типы переменных
- MQL5 Урок 3 – Операции и Выражения
- MQL5 Урок 4 – Операторы
- MQL5 Урок 5 – Математические функции и циклы
- MQL5 Урок 6 — Массивы
На самом деле в предыдущих уроках мы с вами уже использовали встроенные в mql5 функции, такие как Print, OnStart и прочие. Даже в прошлом уроке, когда мы обсуждали массивы, мы знакомились с некоторыми встроенными функциями. Тем не менее, в mql5 есть возможность самостоятельно создавать пользовательские функции, и именно об этой возможности мы будем говорить в данном уроке.
Итак, любая задача может быть разбита на подзадачи, каждую из которых можно либо непосредственно представить в виде кода, либо разбить на еще более мелкие подзадачи. Данный метод называется пошаговым уточнением.
Представим себе ситуацию – нам нужно подсчитать количество открытых позиций по определенной валютной паре. Естественно, мы в цикле пройдемся по всем открытым позициям и будем учитывать только те, которые открыты по нужной нам паре. Но позже вдруг выясняется, что нам нужно посчитать только позиции в покупку. А еще позже, что нужны и позиции в продажу. Каждый раз, когда понадобятся новые данные, нам придется исправлять перебор в цикле. В итоге мы получаем много кода, который при этом еще и одинаковый на 99%. К тому же подобные расчеты нам могут понадобиться не один раз за всю программу. Этих всех проблем нам помогают избежать функции.
Определение функции
Функции служат для записи программного кода непосредственно решаемых подзадач. Код, описывающий, что делает функция, называется определением функции:
Тип_возвращаемого_значения заголовок_функции (передаваемые_параметры)
{
инструкции;
return результат;
}
Все, что находится перед первой фигурной скобкой, составляет заголовок определения функции, а то, что находится между фигурными скобками, является телом определения функции. Заголовок функции включает в себя описание типа возвращаемого значения, имени (идентификатора) и формальных параметров. Параметры еще называют аргументами. Кстати, их может и не быть, но круглые скобки нужно ставить обязательно. Количество параметров, передаваемых в функцию, ограничено и не может превышать 64.
Функция может вызываться из других частей программы столько раз, сколько необходимо. По сути, возвращаемый тип, идентификатор функции и типы параметров составляют прототип функции.
Прототип функции – это объявление функции, но не ее определение. Благодаря явному объявлению возвращаемого типа и списка типов аргументов при обращении к функциям возможны строгая проверка типов и неявные преобразования типов. Особенно часто объявления функций используются в классах для улучшения читаемости кода.
Определение функции должно точно соответствовать ее объявлению. Каждая объявленная функция должна быть определена. Пример:
double // тип возвращаемого значения
my_func (double a, double b) // имя функции и список параметров
{
// составной оператор
return (a + b); // возвращаемое значение
}
Оператор return может возвращать значение выражения, стоящего в этом операторе. Значение выражения при необходимости преобразуется к типу результата функции. Можно возвращать простые типы, простые структуры, указатели объектов. При помощи оператора return нельзя возвращать любые массивы, объекты классов, переменные типа сложных структур.
Функция, которая не возвращает значения, должна быть описана как имеющая тип void. Пример:
void errmsg(string s)
{
Print("error: "+s);
}
Параметры, передаваемые в функцию, могут иметь значения по умолчанию, которые задаются константами соответствующего типа. Пример:
int my_func(double a, double d=0.0001, int n=5, bool b=true, string s="passed string")
{
Print("Обязательный параметр a= ",a);
Print("Переданы следующие параметры: d = ",d," n = ",n," b = ",b," s = ",s);
return(0);
}
Если какому-либо параметру было присвоено значение по умолчанию, то все последующие параметры тоже должны иметь такое значение по умолчанию. Пример неправильного объявления:
int somefunc(double a,
double d=0.0001, // объявлено значение по умолчанию 0.0001
int n, // значение по умолчанию не указано !
bool b, // значение по умолчанию не указано !
string s="passed string")
{
...
}
Вызов функции
Если некоторое имя, которое не было описано ранее, появляется в выражении и за ним следует левая круглая скобка, то оно по контексту считается именем некоторой функции.
имя_функции (x1, x2,…, xn)
Аргументы (формальные параметры) передаются по значению, т. е. каждое выражение x1, . . . , xn вычисляется и значение передается функции. Порядок вычисления выражений и порядок загрузки значений не гарантируются. Во время выполнения производится проверка числа и типа аргументов, переданных функции. Такой способ обращения к функции называется вызовом по значению.
Вызов функции – это выражение, определением которого является значение, возвращаемое функцией. Описанный тип функции должен соответствовать типу возвращаемого значения. Функция может быть объявлена или описана в любом месте программы на глобальном уровне, то есть вне других функций. Функция не может быть объявлена или описана внутри другой функции.
void OnTick()
{
double my_array[4]={0.3, 1.4, 2.5, 3.6};
double a = my_func(my_array, 10.5, 8);
//...
}
double my_func(double x[], double a, double b)
{
return (a*x[0] + b);
}
При вызове функции, имеющей параметры по умолчанию, список передаваемых параметров можно ограничить не ранее первого параметра.
void my_func(double init,
double sec=0.0001, //определены значения по умолчанию
int level=10);
//...
my_func(); // неправильный вызов. первый обязательный параметр должен быть.
my_func(3.14); // правильный вызов
my_func(3.14,0.0002); // правильный вызов
my_func(3.14,0.0002,10); // правильный вызов
При вызове функции нельзя пропускать параметры, даже имеющие значения по умолчанию:
my_func(3.14, , 10); // неправильный вызов -> второй параметр пропущен.
Передача параметров
Существует два метода, с помощью которых машинный язык может передавать аргумент подпрограмме (функции). Первый способ – передача параметра по значению. Этот метод копирует значение аргумента в формальный параметр функции. Поэтому любые изменения этого параметра внутри функции не имеют никакого влияния на соответствующий аргумент вызова.
double FirstMethod(int i,int j)
{
double res;
//---
i*=2;
j/=2;
res=i+j;
//---
return(res);
}
void OnStart()
{
//---
int a=14,b=8;
Print("a и b перед вызовом:",a," ",b);
double d=FirstMethod(a,b);
Print("a и b после вызова:",a," ",b);
}
//--- результат выполнения скрипта
// a и b перед вызовом: 14 8
// a и b после вызова: 14 8
Второй способ – передача аргумента по ссылке. В этом случае ссылка на параметр (а не его значение) передается параметру функции. Внутри функции она используется для того, чтобы обратиться к фактическому параметру, указанному в вызове. Это означает, что изменения параметра будут влиять на аргумент, использованный для вызова функции.
double SecondMethod(int &i,int &j)
{
double res;
//---
i*=2;
j/=2;
res=i+j;
//---
return(res);
}
void OnStart()
{
//---
int a=14,b=8;
Print("a и b перед вызовом:",a," ",b);
double d=SecondMethod(a,b);
Print("a и b после вызова:",a," ",b);
}
//--- результат выполнения скрипта
// a и b перед вызовом: 14 8
// a и b после вызова: 28 4
MQL5 использует оба метода за одним исключением: массивы, переменные типа структур и объекты классов всегда передаются по ссылке. Для того, чтобы исключить изменения фактических параметров (аргументов, переданных при вызове функции), необходимо использовать спецификатор доступа const. При попытке изменить содержимое переменной, объявленной со спецификатором const, компилятор выдаст ошибку.
Перегрузка функций
Обычно в названии функции стремятся отобразить ее основное назначение. Читабельные программы, как правило, содержат разнообразные и грамотно подобранные идентификаторы. Иногда различные функции используются для одних и тех же целей. Например, рассмотрим функцию, которая вычисляет среднее значение массива чисел двойной точности, и такую же функцию, но оперирующую массивом целых чисел. И ту, и другую удобно назвать AverageFromArray:
double AverageFromArray(const double & array[],int size)
{
if (size<=0) {
return 0.0;
}
double sum=0.0;
double aver;
for(int i=0;i<size;i++) {
sum+=array[i]; // сложение для double
}
aver=sum/size; // просто делим сумму на количество
Print("Вычисление среднего для массива типа double");
return aver;
}
double AverageFromArray(const int & array[],int size)
{
if(size<=0) return 0.0;
double aver=0.0;
int sum=0;
for(int i=0;i<size;i++)
sum+=array[i]; // сложение для int
aver=(double)sum/size;// приведем сумму к типу double и разделим
Print("Вычисление среднего для массива типа int");
return aver;
}
Компилятор выбирает нужную функцию в соответствии с типами аргументов и их количеством. Правило, по которому осуществляется этот выбор, называется алгоритмом соответствия сигнатуре. Под сигнатурой понимается список типов, который используется в объявлении функции.
void OnStart()
{
int a[5]={1,2,3,4,5};
double b[5]={1.1,2.2,3.3,4.4,5.5};
double int_aver=AverageFromArray(a,5);
double double_aver=AverageFromArray(b,5);
Print("int_aver = ",int_aver," double_aver = ",double_aver);
}
//--- Результат работы скрипта
// Вычисление среднего для массива типа int
// Вычисление среднего для массива типа double
// int_aver= 3.00000000 double_aver= 3.30000000
Перегрузка функций – это создание нескольких функций с одним именем, но с разными параметрами. Это означает, что в перегружаемых вариантах функции разным должно быть количество аргументов и/или их тип. Выбор конкретного варианта функции зависит от типов аргументов, полученных функцией. Конкретная функция выбирается в зависимости от соответствия списка аргументов при вызове функции списку параметров в объявлении функции.
На этом все, всем пока!
С уважением, Дмитрий аkа Silentspec
Tlap.com