讲完了数据类型,操作语句,接着我们把这些元素组合在一起使用。封装起来,成为函数。可供程序内调用,减少冗余代码,提高代码可维护性,降低程序复杂度。
function是一组代码块,用于完成特定动作,比如处理一个订单,调整止损价位等。我们的教程中,会讲到不少自建的函数,用于完成交易相关的动作。MQL5也提供了许多内置函数,从简单的获取订单信息,到复杂的数学运算,都可信手拈来,直接使用。
好比积木,函数精简为一个一个独立的积木块,然后我们使用程序,将独立的积木块搭建成复杂的结构。
抽象出来的函数,一定要精简,要获取订单信息,那好了,我根据需要的传入参数,传入该函数,它给我返回订单信息便是。程序的任意位置,均可调用。
函数一般要有返回值,当然没有返回值也可以,声明的时候冠以void关键字即可。下面举例:
double BuyStopLoss(string pSymbol, int pStopPoints, double pOpenPrice)
{
// 代码块
}
函数要求传入三个参数,返回一个double类型数据。按照规则写就OK了。注意三个参数都必须填写,是必填项,不能缺省。字符类型的 pSymbol,整型的 pStopPoints,实数 pOpenPrice。
下面我们实现一个功能,根据三个参数,给函数计算返回值。补充函数代码块部分:
double BuyStopLoss(string pSymbol, int pStopPoints, double pOpenPrice)
{
double stopLoss = pOpenPrice - (pStopPoints * _Point);
stopLoss = NormalizeDouble(stopLoss,_Digits);
return(stopLoss);
}
逐句分析。
double stopLoss = pOpenPrice - (pStopPoints * _Point);
使用开盘价格,减去 止损点与货币报价中当前交易品种的大小点的成绩,计算出来的就是止损价格。注意使用了_Point预定义常量。
stopLoss = NormalizeDouble(stopLoss,_Digits);
此处_Digits也是系统预定义常量。定义当前图表交易品种的价格精确度。使用NormalizeDouble格式化小数保留相应的精度。
return(stopLoss);
返回计算后的值stopLoss。这就是函数的返回值。函数执行到此,直接返回,如果后面还有语句,并不执行。
所以您看到了,函数体内,也有提前终端执行,跳出函数的方法,就是使用return返回。这与上一章循环中的break与异曲同工之处。
准备好这个函数,我们可以在程序中用一用,体现一下其价值。我们定义一个输入变量,用于与用户交互,让用户输入止损价,然后在onTick事件处理中调用此函数。
onTick 当NewTick事件发生时在EA中调用这个函数,来处理一个新报价。
也就是报价有更新时,调用此事件内的程序。
// 定义输入变量
input int StopLoss = 500;
// onTick事件
double orderPrice = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
double useStopLoss = BuyStopLoss(_Symbol,StopLoss,orderPrice);
注意,函数要先定义再使用。可以在头部定义,如果是公用函数,单独写出来,在顶部引入即可。
如果一个函数,接收5个参数,但是有些并不是经常变化的参数,或者在函数调用的时候,是否能够选填呢,作为可选参数?可以的。在声明函数的形参中,给其默认值,那么在调用的时候,就可以不给这个位置传参。
double BuyStopLoss(string pSymbol, int pStopPoints, double pOpenPrice = 0)
{
double stopLoss = pOpenPrice - (pStopPoints * _Point);
stopLoss = NormalizeDouble(stopLoss,_Digits);
return(stopLoss);
}
上述同样的函数定义,我们在形参中声明pOpenPrice默认=0。那么如果调用BuyStopLoss时,这个位置的参数如果不传,函数体内,pOpenPrice就用0.0这个默认值了。
BuyStopLoss(_Symbol,StopLoss);
就想上面这个一样。
那是不是我只要声明了默认值的参数位置,就可以不填了呢?不可以!可选参数列表放在形参的尾部,这样可变参数在前,不变参数在后,写的时候,你就可以像下面这样用:
double grad(int x1=1,int y1=2,int x2=3,int y2=4){
return (y2-y1)/(x2-x1);
}
上述函数计算两个点的斜率。坐标点都有默认值,调用的时候,下面的使用方法都是正确的:
grad();
grad(1);
grad(1,2);
grad(1,2,3);
grad(1,2,3,4);
我们再看必选参数在中间的。
double grad(int x1 = 1, int y1, int x2 = 3, int y2 = 4){
return (y2-y1)/(x2-x1);
}
那么调用的时候,只能有以下写法:
grad(1,2);
grad(7,3,3,5);
也就是第二个参数,是必填的。无论前面的是否可选,前面的也得填。所以,最佳实践就是把可选参数放后面去。
像上面所说的,如果提前终结函数运行,可以直接return。函数运行到return这个位置,就跳出了。
int ga(double price, bool is_half){
if(is_half== false) return(price * price);
else return(price / 2);
Print('Never exec');
return(0);
}
因为if-else把两种情况均考虑了,一定会返回一个数值。那么下面的print根本没有机会执行。
有时候我们写一个函数,仅仅为了一段功能和动作,可能不不期望有返回值。那么就可以在函数声明前冠以void。
void TradeEmail()
{
string subject = "Trade placed";
string text = "A trade was placed on " + _Symbol;
SendMail(subject,text);
}
这段代码,使用标题和内容发送一封邮件,不期望有返回值,使用void就可以了。函数内大可不必有return操作符。
一般,我们把参数传递给函数,传递的是这个参数的值。参数的值也保持不变。
那么,如果是某个变量,我们想要其在函数处理中改变其数值,怎么办呢?可以使用引用传递。MQL5程序中,数组和结构体,经常用到引用传递reference。
下面的例子,我们引用传递一个结构体给系统函数SymbolInfoTick()。
bool SymbolInfoTick(
string symbol,
MqlTick& tick // 引用结构体MqlTick
);
下面是代码中我们的用法:
MqlTick myTick;
SymbolInfoTick(_Symbol,myTick);
Print(myTick.bid);
第一行,使用系统内置结构体声明一个变量myTick。
第二行,系统函数调用后将返回值更改了变量myTick。
第三行,值更改后的myTick打印属性。
下面再举一个例子,我们接收一个空数组,将其进行填充。这个函数就需要引用传递。
void fill(int &a[]){
ArrayResize(a,3);
a[0] = 1;
a[1] = 2;
a[2] = 3;
}
这样做的好处是,不用返回值了,再赋值给原始函数,省去一大段代码。
调用的时候这样用:
int f[];
fill(f);
Print(f[0]); // = 1
有没有对引用传递有个质的理解了呢?
面向对象编程中,我们用到很多概念,如接口,抽象类,继承。那么,在继承层级比较深的类内,有些继承的方法在该类内会有特殊的用法,这时候我们需要重写该方法,也称为“重载”。MQL5函数也可使用重载。说白话就是,同一个函数名,参数不同,写两次。你用的时候,编译器根据传入的参数匹配相应的函数。
bool TrailingStop(string pSymbol, int pTrailPoints, int pMinProfit = 0, int pStep = 10);
bool TrailingStop(string pSymbol, double pTrailPrice, int pMinProfit = 0, int pStep = 10);
更改一个参数,函数名保持不变。像下面这样用
bool b;
b = TrailingStop(_Symbol, 549);
系统会判定使用第一个函数,因为第二个参数很显然,int型。像下面这样用
bool b;
b = TrailingSop(_Symbol, 125.89);
系统判定是访问第二个函数,因为第二个参数位置是double型。
这就是重载。