English Русский Español Deutsch 日本語 Português
MQL4程序的常见错误以及如何避免它们

MQL4程序的常见错误以及如何避免它们

MetaTrader 4示例 | 26 三月 2014, 09:21
18 088 3
MetaQuotes
MetaQuotes

介绍

一些较旧的程序可能在新版本的MQL4编译器中返回错误。

为了避免关键的程序完成,以前版本的编译器在运行环境中处理了许多错误。例如,除数为零或数组越界都是严重错误,并通常会导致应用程序崩溃。这些错误只在一些状态下针对某些变量值而发生。阅读这篇文章了解如何处理这样的情况。

新的编译器可以检测实际或潜在的错误源并提高代码质量。

在这篇文章中,我们讨论了旧程序编译过程中检测到的可能出现的错误,以及解决这些问题的方法。

  1. 编译错误
  2. 运行时间错误
  3. 编译器警告

1编译错误

如果程序代码中包含错误,则它不能被编译。

要完全控制所有的错误,建议使用严谨的编译模式,它通过以下指令来设置:

#property strict

这种模式大大简化了故障排除。


1.1. 与关键字一致的标识

如果变量或函数的名称与其中一个关键字一致

int char[];  // incorrect
int char1[]; // correct
int char()   // incorrect
{
 return(0);
}

编译器会返回一个错误信息:

图1. 错误“unexpected token(非预期标记)”和“name expected(预期名称)”

图1. 错误“unexpected token(非预期标记)”和“name expected(预期名称)”

要解决这个错误,您需要使用变量或函数的正确名称。


1.2. 变量和函数名的特殊字符

如果变量或函数名称中包含特殊字符($,@,点):

int $var1; // incorrect
int @var2; // incorrect 
int var.3; // incorrect
void f@()  // incorrect
{
 return;
}

编译器会返回一个错误信息:

图2. 错误“unknown symbol(未知交易品种)”与“semicolon expected(预期分号)”

图2. 错误“unknown symbol(未知交易品种)”与“semicolon expected(预期分号)”

要解决这个错误,您需要使用正确的函数或变量名。


1.3. 使用switch操作符的错误

在旧版本的编译器中,您可以在switch操作符的表达式和常量中使用任何值:

void start()
  {
   double n=3.14;
   switch(n)
     {
      case 3.14: Print("Pi");break;
      case 2.7: Print("E");break;
     }
  }

在新的编译器中,switch操作符的常量和表达式必须是整数,所以当您尝试使用这样的结构时会发生错误:

图3. 错误“illegal switch expression type(非法switch表达式类型)”和“constant expression is not integral(常量表​​达式不是整数)”

图3. 错误“illegal switch expression type(非法switch表达式类型)”和“constant expression is not integral(常量表​​达式不是整数)”

在这种情况下,您可以使用明确的数值比较,例如:

void start()
  {
   double n=3.14;
   if(n==3.14) Print("Pi");
   else
      if(n==2.7) Print("E");
  }

1.4. 函数返回值

除了空值外的所有函数都应该返回声明的类型值。例如:

int function()
{
}

在严谨的编译模式下发生错误:


图4. 错误“not all control paths return a value(并非所有的控制路径返回一个值)”

图4. 错误“not all control paths return a value(并非所有的控制路径返回一个值)”

在默认的编译模式下,编译器会返回一个警告:

图5. 警告:“not all control paths return a value(并非所有的控制路径返回一个值)”

图5. 警告:“not all control paths return a value(并非所有的控制路径返回一个值)”

如果函数的返回值与声明的不匹配:

int init()                         
  {
   return;                          
  }

在严格的编译中会检测错误:

图6. 错误“function must return a value(函数必须返回一个值)”

图6. 错误“function must return a value(函数必须返回一个值)”

在默认的编译模式下,编译器会返回一个警告:

图7. 警告 'return - function must return a value(回报 - 函数必须返回一个值)”

图7. 警告 'return - function must return a value(回报 - 函数必须返回一个值)”

要解决这样的错误,添加带有相应类型返回值的return操作符到函数代码。



1.5. 函数参数数组

在函数参数,数组现在只引用传递。

double ArrayAverage(double a[])
{
 return(0);
}
在严谨的编译模式下,该代码将导致错误:

图8. 编译器错误“arrays passed by reference only(数组只引用传递)”

图8. 编译器错误“arrays passed by reference only(数组只引用传递)”

在默认的编译模式下,编译器会返回一个警告:

图9. 编译器警告“arrays passed by reference only(数组只引用传递)”

图9. 编译器警告“arrays passed by reference only(数组只引用传递)”

要修复此错误,您必须通过在数组名称之前添加前缀来指定数组是通过引用传递的:

double ArrayAverage(double &a[])
{
 return(0);
}

但应注意的是,现在常量数组 (Time[], Open[], High[], Low[], Close[], Volume[]) 不能引用传递。例如,下面的调用:

ArrayAverage(Open);

无论何种编译模式都会导致错误:

图10. 错误'Open' - constant variable cannot be passed as reference(‘打开’ - 常量变量不能引用传递)

图10. 错误'Open' - constant variable cannot be passed as reference(‘打开’ - 常量变量不能引用传递)

为了避免这些错误,从常量数组​​复制所需的数据:

   //--- an array that stores open price values
   double OpenPrices[];
   //--- copy the values of open prices to the OpenPrices[] array
   ArrayCopy(OpenPrices,Open,0,0,WHOLE_ARRAY);
   //--- function call
   ArrayAverage(OpenPrices);



2. 运行时间错误

程序代码执行过程中出现的错误称为运行时间错误。这种错误通常是依赖于程序的状态,并与变量的不正确的值相关联。

例如,如果变量用作数组元素的索引,其负值将不可避免地导致数组超出范围的错误。


2.1. 数组超出范围

访问指标缓冲区时常常在指标中发生这个错误。该IndicatorCounted()函数返回自上次指标调用的不变的柱数。先前计算的柱的指标值不需要重新计算,所以为了更快的计算,您只需要处理最后的几个柱。

大部分使用这种计算优化的方法的指标看起来如下:

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int start()
  {
   //--- some calculations require no less than N bars (e.g. 100)      
   if (Bars<100) // if less bars are available on a chart (for example on MN timeframe)    
     return(-1); // stop calculation and exit

   //--- the number of bars that have not changed since the last indicator call
   int counted_bars=IndicatorCounted();
   //--- exit if an error has occurred
   if(counted_bars<0) return(-1);
      
   //--- position of the bar from which calculation in the loop starts
   int limit=Bars-counted_bars;

   //--- if counted_bars=0, reduce the starting position in the loop by 1,   
   if(counted_bars==0) 
     {
      limit--;  // to avoid the array out of range problem when counted_bars==0
      //--- we use a shift of 10 bars back in history, so add this shift during the first calculation
      limit-=10;
     }
   else //--- the indicator has been already calculated, counted_bars>0
     {     
      //--- for repeated calls increase limit by 1 to update the indicator values for the last bar
      limit++;
     } 
   //--- the main calculation loop
   for (int i=limit; i>0; i--)
   {
     Buff1[i]=0.5*(Open[i+5]+Close[i+10]) // values of bars 5 and 10 bars deeper to history are used
   }
}

通常counted_bars==0的情况处理不当(初始限制持仓应该通过等于相对循环变量的1 +最大指数的值来降低)。

另外,请记住,在执行start()函数的时候,我们可以从0到Bars ()-1的访问指标缓冲区的数组元素。如果您需要使用无指标缓冲区的数组,那么按照指标缓冲区的当前大小使用ArrayResize()函数来增加其大小。也可以通过调用用作参数的指标缓冲区的 ArraySize()来获得元素地址的最大指数。


2.2. 除数为零

当除法运算中除数为零时则会发生零除的错误:

void OnStart()
  {
//---
   int a=0, b=0,c;
   c=a/b;
   Print("c=",c);
  }

当您运行这个脚本时,专家选项卡会出现一条错误的消息,并且程序关闭:

图11. 错误消息“zero divide(除数为零)”

图11. 错误消息“zero divide(除数为零)”

当除数的值由任何外部数据值来决定时,通常会出现此错误。例如,如果交易参数进行分析,如果没有新建订单,那么已用预付款的值等于0。另一个例子:如果要从一个文件读取分析数据,如果该文件不可用,那么我们也不能保证正确的操作。所以您应该考虑到这样的情况并正确地处理它们。

最简单的方法是除法运算前检查除数并报告不正确的参数值:

void OnStart()
  {
//---
   int a=0, b=0,c;
   if(b!=0) {c=a/b; Print(c);}
   else {Print("Error: b=0"); return; };
  }

这不会导致严重的错误,但是不正确参数值的消息一出现则程序即关闭:

图12. 不正确的除数消息

图12. 不正确的除数消息


2.3. 当前字符用0替代NULL

在旧版本的编译器中0(零)可用作满足金融工具规范的函数参数。

例如,当前交易品种的移动平均线技术指标的值可能被要求如下:

AlligatorJawsBuffer[i]=iMA(0,0,13,8,MODE_SMMA,PRICE_MEDIAN,i);    // incorrect

在新的编译器中您应该明确地指定NULL来规定当前的交易品种:

AlligatorJawsBuffer[i]=iMA(NULL,0,13,8,MODE_SMMA,PRICE_MEDIAN,i); // correct

此外,当前交易品种和图表周期可使用Symbol()Period()函数来指定。

AlligatorJawsBuffer[i]=iMA(Symbol(),Period(),13,8,MODE_SMMA,PRICE_MEDIAN,i); // correct


2.4. Unicode字符串和它们在DLL中的使用

字符串现在表示为Unicode字符序列。

记住这一点,并使用适当的Windows函数。例如,使用wininet.dll库来替代 InternetOpenA() 和InternetOpenUrlA(),您应该调用InternetOpenW() 和InternetOpenUrlW()。

字符串的内部结构在MQL4中(现在只需要12个字节)发生了变化,当传递字符串到DLL时应使用MqlString结构:

#pragma pack(push,1)
struct MqlString
  {
   int      size;       // 32 bit integer, contains the size of the buffer allocated for the string
   LPWSTR   buffer;     // 32 bit address of the buffer that contains the string
   int      reserved;   // 32 bit integer, reserved, do not use
  };
#pragma pack(pop,1)


2.5. 文件共享

在新MQL4中,FILE_SHARE_WRITE和FILE_SHARE_READ标志应明确地指定以便打开文件时共享使用。

如果标志不存在,那么该文件以单独模式打开,直到文件由打开它的用户关闭才可以被其他人打开。

例如,使用离线图表时共享标志应明确指定:

   // 1-st change - add share flags
   ExtHandle=FileOpenHistory(c_symbol+i_period+".hst",FILE_BIN|FILE_WRITE|FILE_SHARE_WRITE|FILE_SHARE_READ);

欲了解更多详情,请阅读新MQL4的离线图表


2.6. 日期时间转换

转换日期时间为一个字符串现在取决于编译模式:

  datetime date=D'2014.03.05 15:46:58';
  string str="mydate="+date;
//--- str="mydate=1394034418" - old compiler, no directive #property strict in the new compiler
//--- str="mydate=2014.03.05 15:46:58" - new compiler with the directive #property strict

例如,尝试使用文件名中包含冒号的文件会导致错误。


3. 编译器警告

编译器警告是信息性而非错误的讯息,但它们指出了可能的错误来源。

一个清晰的代码不应该包含警告。


3.1. 全局和局部变量名称一致

如果全局和局部各级变量具有相似的名称:

int i; // a global variable
void OnStart()
  {
//---
   int i=0,j=0; // local variables
   for (i=0; i<5; i++) {j+=i;}
   PrintFormat("i=%d, j=%d",i,j);
  }

编译器会显示指出全局变量的声明行号的警告:

图13. 警告“declaration of '%' hides global declaration at line %(声明'%'隐藏在行%的全局声明)”

图13. 警告“declaration of '%' hides global declaration at line %(声明'%'隐藏在行%的全局声明)”

要解决这样的警告需要修正全局变量的名称。


3.2. 类型不匹配

新的编译器有一个新的类型转换操作。

#property strict
void OnStart()
  {
   double a=7;
   float b=a;
   int c=b;
   string str=c;
   Print(c);
  }

在严谨的编译模式下如果类型不匹配则编译器会显示警告:

Figure 14. Warnings "possible loss of data due to type conversion" and "implicit conversion from 'number' to 'string'

图14. 警告“possible loss of data due to type conversion(由于类型转换可能丢失数据)”和“implicit conversion from 'number' to 'string'(从'数字'到'字符串'的隐式转换)”

在这个示例中,编译器警告关于分配的不同数据类型的可能的精确度损失和从整数到字符串的隐式转换。

要解决此警告需要使用明确的类型转换:

#property strict
void OnStart()
  {
   double a=7;
   float b=(float)a;
   int c=(int)b;
   string str=(string)c;
   Print(c);
  }

3.3. 未使用的变量

程序代码中存在不使用的变量(多余的实体)不是一个好习惯。

void OnStart()
  {
   int i,j=10,k,l,m,n2=1;
   for(i=0; i<5; i++) {j+=i;}
  }

无论何种编译模式都会显示这些变量的报告:

图15. 警告“variable '%' not used('%'变量未使用)”

图15. 警告“variable '%' not used('%'变量未使用)”

要修复它,需要从代码中移除未使用的变量。


结论

本文描述了包含错误的旧程序的编译过程中可能出现的常见问题。

在所有情况下,建议使用严谨的编译模式来调试程序。


p align="center"// stop calculation and exit br/pnbsp;span class="keyword"

本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/1391

最近评论 | 前往讨论 (3)
Little---Prince
Little---Prince | 24 11月 2015 在 04:34
  外汇黄金技术分析交流,群号是375124107,进群验证777,谢谢配合  
Bing Liu
Bing Liu | 25 12月 2015 在 02:43
不错,看了之后终于知道怎么解决这些报警了,谢谢高手指点
金字塔Ai 汇飞量化
金字塔Ai 汇飞量化 | 5 12月 2017 在 07:08
good
MQL5 代码自动生成文档 MQL5 代码自动生成文档
大多数 Java 代码编写者熟悉可通过 JavaDocs 创建的自动生成文档。其思路是以一种半结构化的方式向代码添加注释,然后可以将这些注释提取到易于导航的帮助文件。C++ 世界也有若干文档自动生成器,其中微软的 SandCastle 和 Doxygen 是两款领先产品。本文说明使用 Doxygen,从 MQL5 代码的结构化注释创建 HTML 帮助文件。试验非常成功,我认为 Doxygen 从 MQL5 代码生成的帮助文档会增加很多价值。
利用 MQL5 向导和 Hlaiman EA 生成器创建神经网络 EA 利用 MQL5 向导和 Hlaiman EA 生成器创建神经网络 EA
本文讲述的是利用 MQL5 向导和 Hlaiman EA 生成器自动创建神经网络 EA 的一种方法。向您展示如何轻松开始神经网络的使用,且无需学习整体的理论知识,也不必编写自己的代码。
用 MQL5 向导创建您自己的 EA 交易 用 MQL5 向导创建您自己的 EA 交易
编程语言知识不再是创建自动交易的一个先决条件。以前,缺乏编程技能是实现自己的交易策略的不可逾越的障碍,但是随着 MQL5 向导的出现,这种情况迅速改变了。交易新手能够不再因为缺乏编程经验而担心 - 使用让您能够生成 EA 代码的新向导,编程经验不再是必不可少的了。
另一个 MQL5 OOP 类 另一个 MQL5 OOP 类
本文会从一种理论性交易概念的构想,到编制一个在经验世界中实现这一概念的 MQL5 EA 交易,为您讲解如何从头建立一个面向对象的 EA 交易。依本人看,边做边学是取得成功的一种可靠方法。所以,我会拿出一个实用的例子,让您明白如何才能整理自己的想法,并最终完成外汇自动交易代码。和您一起遵守“面向对象”原则,也是我的目标之一。