: | : | :期货程序化 | :期货程序化研究 | :期货量化学习 | :期货量化 |
返回列表 发帖

MQL4 语言入门 简单词组中的难题

MQL4 语言入门 简单词组中的难题

简介

这是“MQL4 语言入门”系列的第二篇文章。第一篇文章“MQL4 语言入门简介”描述了 MQL4 的功能,我们学习编写简单脚本,了解变量含义,学习使用变量,分析函数、数组、内置数组和变量、循环“for”以及简单和复杂的条件。现在我们将探讨该语言更复杂、更高级的构造,学习新内容,并了解如何将它们应用到日常实践中。您将了解新循环类型“while”,新条件类型“switch”,运算符“break”和“continue”。此外,我们将学习编写您自己的函数和使用多维数组。作为餐前甜点,我还准备了一份有关预处理器的说明。

建议

请在充分理解上一篇文章之后,再来阅读本文。否则您会犯很多错误,同时也学不到什么。本文要以上一篇文章为基础,所以别太心急!放轻松 - 学习这些新东西时碰到的难题都不过是纸老虎。总有那么一天,您无需再三考虑循环怎么编写,要放上哪些条件等等问题,所有东西都会按部就班地完成。多多使用 MQL4 语言,它就会变得越来越简单。

新的循环类型 - while

我想提一下,上一篇文章中讲到的循环“for”是一个通用循环,可以替代我们现在要学的任何其他循环类型。但它也并不是适用于所有情况的。有时用 while 会更高效。。很快您就会知道使用哪种循环类型会比较合理。我们用两种方式做一个任务:使用两种循环找出所有条柱的总成交量,看看有什么区别:

// using the cycle for double sum = 0.0; for(int a = 0; a < Bars; a++)     sum += Volume[a]; // now using while, the result is the same double sum = 0.0; int a = 0; while(a < Bars)     {       sum += Volume[a];       a++;     }看,现在计数器被声明并被单独使用。这里的“While”表示,当条件为真时,循环将继续。以下是常规格式:

while(condition of cycle fulfillment)   {    code;   }

更简单的示例如下:

while(I havent eaten up the apple)  {   bite more;   }实际上,循环“while”与循环“for”的不同之处仅在于缺少一个计数器。如果不需要计数器,用 while,尽管它并不是必要的。比如我经常使用 while 搭配一个计数器,这只关乎个人喜好。就像在使用 for 的案例中,如果循环主体只包括一个指令,那就可以省略大括号。要想自己开发,还要记住迭代一词的含义。它是由循环执行的一种多重操作(重复)。执行一次循环主体即表示执行一次迭代。

新的循环类型 - switch

在使用循环的案例中,应注意switch可替代为一组您熟悉的“if”和“else”条件。当您需要根据变量值执行一些操作时,就可以使用“switch”结构。这就像微波炉上常见的模式开关。例如,假设您在编写一个 EA,它的行为会根据市场情况进行变化。就用变量int marketState来执行这个任务。它可拥有以下含义:

  • 1 - 上升趋势
  • 2 - 下跌趋势
  • 3 - 平盘趋势

无论这个价位是怎么定义的,我们的任务是实现某种机制,以便 EA 根据市场情况执行对应的操作。您知道该怎么做。以下是最明显的变体:

if(marketState == 1)  {// trading strategy for an uptrend} else if(marketState == 2)  {// strategy for a downtrend}     else if(marketState == 3)  {// strategy for a flat}     else  {// error: this state is not supported!}

这里有一些特性:

  • 所有条件采用的是同一个变量;
  • 所有条件都在将该变量与其可接受的意义之一进行比较。

因此,所有这些还都涉及到 switch 结构。以下是使用 switch 的一个代码,结果是相同的:

switch(marketState)     {  case 1:// trading strategy for an uptrend   break;       case 2:// strategy for a downtrend   break;       case
3:// strategy for a flat   break;       default:// error: this state is not supported!   break;     }

注意,我们首先定义要比较的变量:

// switch - key word, marketState - a variable for comparison switch(marketState)

然后指示特定情况下应执行的操作:

case 1:// trading strategy for an uptrend   break;     case 2:// strategy for a downtrend   break;     case 3:// strategy for a flat   break;     default:// error: this state is not supported!   break;  

在一般视图中,switch采用以下格式:

switch(a variable for comparison)     {  case [a variable value]:// a code for this case    break;         case [another value of the variable]:// a code for this case    break;         default:// a code for all other cases    break;     }

在将switch进行比较,和将特定代码块与一个值进行比较时,使用 switch。其他情况下,使用“if”和“else”条件的常用组合。有时您需要根据某个变量的多个值执行一个代码。例如,如果 marketState == 1 或 2,则执行某个特定代码。以下就是这个任务中 switch

switch(marketState)     {       case 1:  // if marketState is equal to 1       case 2:  // or if marketState is equal to 2, then perform this  break;   default: // in any other case perform  this code   break;     }运算符:continue 和 break

我们刚才就看到了这个 break 运算符。它用于跳出 switch。除此以外,您可用它跳出一个循环。例如在某些条件下您不需要执行某个循环时就能用到。假设我们需要找出第一批条柱的金额,这批条柱需要包括 1000 个点。我们可以编写以下代码:

int a = 0; double volume = 0.0; while(volume < 1000.0)     {       volume += Volume[a]; // equivalent to volume = volume + Volume[a];       a++;     } // now variable "a" includes the amount of bars, the volume of their sums is no less than 1000 points

现在让我们用break 运算符

int a = 0; double volume = 0.0; while(a < Bars)     {   // if the volume is exceeds 1000 points, then       if(volume > 1000.0) // exit the cycle  break;                  volume += Volume[a];      a++;   }

你看,break 运算符运算符很好用,可以避免不需要的循环迭代。还有一个很有用的操作符continue,它主要用于“忽略”不需要的迭代。假设我们需要计算一个总金额,但我们必须把出现重大消息时的交易量排除在外。如你所知,重大消息的出现总是伴随着大量的点。举个简单的例子,假设条柱金额包括 50 个点,超过这 50 个点的都是针对消息的。要解决这个任务,让我们使用 continue 运算符:

int a = -1; double volume = 0.0; while(a < Bars)     {       a++;  // if the volume exceeds 50 points, then it must  be news, omit it       if(Volume[a] > 50.0)  continue;                volume += Volume[a];     }

你看,使用continue操作符相当繁琐,但某些时候是很有用的。很明显,此脚本主要用于小时间框架。

编写自己的函数

但我们为何需要它们?事实上,您经常会发现重复的代码。也就是说,您会在不同的案例中使用同一套指令。要节省时间和精力,您可将这种重复的代码编写成一个单独的函数。需要时,只要写下该函数的名称,它就会帮你搞定一切。让我们看看它们是怎么工作的。假设您需要找出某个烛台的颜色。已知一根白色烛台的收盘价高于开盘价,一根黑色烛台的收盘价低于开盘价。我们来编写一段代码,确定烛台的颜色。

bool color; // as there are only 2 variants (white or black candlestick), then suppose that the velue false corresponds to a black candlestick, and true - white if(Close[0] > Open[0])  color = true;// white candlestick      if(Open[0] > Close[0])  color = false;// black candlestick

好了,现在变量颜色包含最后一根烛台的颜色。要确定另一根烛台的颜色,例如倒数第二根,您需要将指数 0 更改为 1。但难道您每次需要查找烛台的颜色,就要编一次这些代码?如果出现大量这种情况呢?所以我们需要函数。想想看应该怎么做。此类函数应接受一个参数 - 需要确定其颜色的烛台的指数,并返回该颜色 - 一个布尔型变量。想象一下,我们编写了这个函数并激活它:

bool color; // here will be the color of a wanted candlestick color = GetColor(0);

如您预料的那样,我们的函数名为 GetColor。我们调用此函数是为了查找最后一根烛台的颜色。所以这个唯一的参数等于 0。此函数返回一根烛台的颜色,以便我们立即进行分配。这是一个非常重要的时刻!将在函数内部创建一个变量,然后该变量值将取代此函数调用。最终,上述的函数调用和函数确定代码会产生相同的结果 - 变量颜色将包含最后一根烛台的颜色,但使用函数我们会更省力。

现在我们回头说说空脚本的代码。实际上,空脚本的代码已经包含了函数 start() 的完整说明。最有趣的一点是,您其实一直是在这个函数中编写代码。当您打开脚本时,终端就会激活函数 start()。我们来看看空脚本的代码:

int start()

这一行非常关键!它包括函数名称,即一个用于激活此函数的关键字。在我们的示例中,它是“start”。它还包含返回值类型- int。它的意思是,执行此函数之后,此函数将向我们返回一些 int 类型的值。括号中包含参数列表,但在我们的实例中,此函数不接受任何参数。

然后在大括号中可以看到函数描述,即在函数调用时执行的代码。

{    // a code that will be performed at the function call.  return(0); }

很明显,我们是在 start() 函数的主体内编写代码。在函数末尾,我们看到运算符return,它返回函数值。在我们的示例中,它返回 0。

以下是编写一个函数时的常用格式:

[type of return value] [function name] ([list of arguments])     {       // function code       return([a value, which the function returns]);     }

现在返回到我们的烛台和GetColor 函数。看看此函数的代码:

bool GetColor(int index)     {       bool color;       if(Close[index] > Open[index]) color = true;// white candlestick       if(Open[index] > Close[index]) color = false;// black candlestick       return(color);     }

我们仔细想想第一行:

bool GetColor(int index)

里面有:bool - 返回值的类型;GetColor- 函数名称;int- 参数类型;index- 参数名称。注意,我们在函数主体内使用index,但在函数调用时,不会再提到此名称。例如:

bool lastColor = GetColor(0);

然后:

{    bool color;    if(Close[index]>Open[index]) color=true;// white candlestick    if(Open[index]>Close[index]) color=false;// black candlestick

此函数主体是一个通用代码,每次函数调用时都可执行此代码。之后:

return(color); }

运算符 return。返回值应与最开头确定的类型相对应。必要时,可以在一个函数中使用多个运算符“return”,例如:

bool GetColor(int index)     {      if(Close[index] > Open[index]) return(true);// white candlestick      if(Open[index] > Close[index]) return(false);// black candlestick     }

很明显,使用多个return运算符可避免使用变量color。此外,在运算符 return中,您甚至可以使用逻辑表达式:

return(Close[index] > Open[index]);

这是可行的,因为比较运算符也会像一些其他常用函数一样返回布尔型的变量(true 或 false)。看上去有点难,但用用就很快习惯了。

现在让我们返回参数列表。我们的函数中仅使用了参数 int index 。如果您需要使用多个参数,列举它们,并用逗号隔开:

bool SomeСomplicatedFunction(int fistArgument, int secondArgument, sting stringArgument)

要参考参数,像在上一个函数中那样使用其名称。调用使用多个参数的名称时,注意参数顺序序列:切勿混淆任何东西!如果函数不应返回任何值,使用关键字 void 指示这一点。注意,这种情况下不使用 return运算符

void function()   {   // code   }

还有个细节:您可设置函数参数默认值。这是什么?假设您编写了一个复杂函数,其中包括 5 个影响函数行为的参数。但最后几个参数几乎总是使用相同的值。只有在二三十个函数调用中才需要使用不同的值。为了不必每次都指示最后几个参数几乎总是相同的值,我们使用参数的默认值。在这种情况下,您只是忽略了最后几个参数,就像它们不存在一样,尽管实际上使用了这些参数,但它们的值是默认分配的。碰到特殊情况时,您还是需要指示所有函数。让我们看看如何声明使用默认值的函数:

void someFunction(int argument1, int argument2, int specialArgument = 1)  {   // code   }

你看,一些都很简单:我们向需要的参数分配一个需要的值,现在可以在调用函数时忽略这个参数:

someFunction(10,20);// we omitted the last argument, but actually it is assigned a value by default someFunction(10,20,1);// this activation is fully identical to the previous onesomeFunction(10,20,2);// here we indicate another value,it is a rare case

对于参数默认值,您想要分配多少就可以分配多少。但记住一条重要的规则:它们都要放在末尾。例如:

void someFunction(int argument1, int argument2, int specialArgument = 1)   // all right void someFunction(int argument1, int argument2 = 10, int specialArgument=1)   // all right void someFunction(int argument1, int argument2 = 10, int specialArgument)// wrong! defaultvalues must stay
at the end of the list of arguments                                                                            void someFunction(int argument1 = 0, int argument2 = 10, int specialArgument = 1) // you can assign default values to all arguments多维数组

编程期间常常会用到数组,大部分情况下,一维数组就够用了。但在某些情况下,您需要二维数组、三维数组。现在我们来学习如何使用它们。

我们先来看看一维数组、修改声明、初始化、指数和值的相关图片:

语言入门 简单词组中的难题" style="margin: 0px; padding: 0px; border: 0px; list-style: none; max-width: 100%; height: auto !important;">

任何一维数组都可显示为一个类型的一行值。以下是一维数组的不同参考的处理方式:

语言入门 简单词组中的难题" style="margin: 0px; padding: 0px; border: 0px; list-style: none; max-width: 100%; height: auto !important;">

语言入门 简单词组中的难题" style="margin: 0px; padding: 0px; border: 0px; list-style: none; max-width: 100%; height: auto !important;">

二维数组就像一般的表格,如下:

语言入门 简单词组中的难题" style="margin: 0px; padding: 0px; border: 0px; list-style: none; max-width: 100%; height: auto !important;">

从图片上来看,二维数组已有两个指数用于值的参考:第一个指数确定一行,第二个指数确定一列。像在一维数组中那样,使用值列表进行初始化。以下是参考表格单元的值的方式:

语言入门 简单词组中的难题" style="margin: 0px; padding: 0px; border: 0px; list-style: none; max-width: 100%; height: auto !important;">

语言入门 简单词组中的难题" style="margin: 0px; padding: 0px; border: 0px; list-style: none; max-width: 100%; height: auto !important;">

语言入门 简单词组中的难题" style="margin: 0px; padding: 0px; border: 0px; list-style: none; max-width: 100%; height: auto !important;">

语言入门 简单词组中的难题" style="margin: 0px; padding: 0px; border: 0px; list-style: none; max-width: 100%; height: auto !important;">

一切都很清楚。我们来看看如何遍历一个二维数组的所有值。应使用 2 个循环:

int array2D[3][3]={10,20,30, 40,50,60, 70,80,90}; for(int y=0;y<3;y++)    for(int x=0;x<3;x++)       MessageBox("array2D["+y+"]["+x+"]="+array2D[y][x]);

此例中,参考是从上到下,从左到右进行的。可以做个练习,试着更改方向,例如从下到上。

三维数组的区别仅在于多了一个指数用于参考单元值。一个三维数组可以轻松呈示为几个表格(二维数组)。我们来看看如何遍历一个三维数组的所有元素:

int array3D[3][3][3] = {11, 12, 13,   14, 15, 16,   17, 18, 19,   21, 22, 23,    24, 25, 26,  27, 28, 29,   31, 32, 33,   34, 35, 36,   37, 38, 39}; for(int z = 0; z < 3; z++)     for(int y = 0; y < 3; y++)         for(int x = 0; x < 3; x++)             MessageBox("array3D["+z+"]["+y+"]["+x+"]=" +                        array3D[z][y][x]);

彻底理解二维和三维数组 - 这一点非常重要。再次非常认真地浏览说明图片。很多不同的任务都可以用数组解决,所以请投入足够时间理解这些数组,将来它们会给你带来极大的帮助。如果您理解了数组的工作原理,那么任何多维数组都可以手到擒来。

使用数组时所用的一些函数

我们从简单的函数开始说起。

int ArraySize(object array[]);

此函数返回该数组包含的元素数量。它适用于所有类型。例如:

// create two different arrays int arrayInt[] = {1, 2, 3, 4};           double arrayDouble[] = {5.9, 2.1, 4.7};// here store the amount of elements int amount;                         amount = ArraySize(arrayInt);// note: to define a specific array, you need to indicate only its name.  Now amount is equal to 4 amount = ArraySize(arrayDouble); // amount is equal to 3

下一个函数:

int ArrayInitialize(object array[],double value);ArrayInitialize

分配一个值到所有数组元素,返回所有分配到值的元素的数量。将此函数用于 int 和 double 类型的数组。

下一对函数:

int ArrayMaximum(double array[], int count = WHOLE_ARRAY, int start = 0); int ArrayMinimum(double array[], int count = WHOLE_ARRAY, int start = 0);

这两个函数返回最大和最小单元值的指数。要使用它们,只需指示应在什么数组中查找它们:

int array[] = {10, 100, 190, 3, 1}; // will be returned 1, because array[1] - maximal value ArrayMaximum(array); // will be returned 4, because array[4] - minimal value ArrayMinimum(array);

下一对函数:

int ArrayDimension(object array[]);

使用这些函数,您可确定数组的维数,即您可确定它是一维、二维还是 n 维数组。使用这些函数,您可确定数组的维数,即您可确定它是一维、二维还是 n 维数组。例如:

int array1D[15]; int array4D[3][3][3]; ArrayDimension(array1D);// get 1 ArrayDimension(array3D);// 3

以下还有更多复杂而有用的函数:

int ArraySort(double&array[],int count=WHOLE_ARRAY,int start=0,int sort_dir=MODE_ASCEND);

此函数对元素进行排序。如果没有直接指示参数的默认值,如下例:

int array[5] = {1, 10, 5, 7, 8}; ArraySort(array);

元素将按升序排序。您可使用其他参数指定此函数行为:

  • int count - 要排序的元素数量。
  • int start - 排序起始元素的指数
  • int sort_dir - 排序方向(升序 - MODE_ASCEND 或降序 - MODE_DESCEND)

这里您可能会问:什么是 MODE_ASCEND 和 MODE_DESCEND??根据 int,它必须是一个整数!别紧张,所有问题都会在下一部分“预处理器”中得到解答。例如,如果您需要从第 2 个元素开始对 5 个元素进行升序排序,进行如下指示:

ArraySort(array, 5, 1, MODE_DESCEND);

今天的最后一个函数:

int ArrayCopy(object&dest[],object source[],int start_dest=0,int start_source=0,int count=WHOLE_ARRAY);

它用于将一个数组复制到另一个数组。我们来看必要的参数:

  • dest[] - 目的地数组
  • dest[] - 复制的数组

可选参数:

  • start_dest - 复制到的目的地数组元素的指数
  • start_source - 开始复制的数组元素的指数
  • int count - 要复制的元素数量

此函数返回复制元素的数量。非常小心地使用 ArrayCopy:确保目的地数组有足够的容量容纳复制的数组。

预处理器

这是什么?预处理器是一种特殊的机制,旨在用于处理源代码。也就是说,首先是由预处理器准备代码,然后传达此代码用于编译。今天我们再学一个有用的选项 - 常数

它主要讲什么?要了解这个选项,我们回忆一下 switch 部分中的一个示例:

switch(marketState)     {       case 1:     // trading strategy for an uptrend     break;       case 2:     // strategy for a downtrend     break;       case 3:     // strategy for a flat     break;       default:     // error: this state is not supported!     break;     }

这里我们激活了一个机制,它会根据市场状态进行不同的操作。还记得吗?那么,我们只需编写诸如 TREND_UP、TREND_DOWN、FLAT 的项目来取代 1、 2 、3,非常简单,只是更具描述性而已。

switch(marketState)     {       case TREND_UP:     // trading strategy for an uptrend     break;       case TREND_DOWN:     // strategy for a downtrend     break;       case FLAT:     // strategy for a flat     break;       default:     // error: this state is not supported!     break;     }

在这种情况下,源代码看起来更易读也更生动,不是吗?所以,在编译之前,常数允许将 TREND_UP、TREND_DOWN 和 FLAT 用对应的值 1、2 和 3 替代。您要做的只是指示预处理器应更改的内容。这个工作可通过预处理器指令来完成,此指令以特殊符号“#”开头。预处理器指令应放在源文件的开头,与其他指令放在一起。来看一个完整的常数示例:

//+------------------------------------------------------------------+//|                                                 preprocessor.mq4 | //|         Copyright © 2007, Antonio Banderass. All rights reserved |//|                                               banderassa@ukr.net | //+------------------------------------------------------------------+ #property copyright "Copyright © 2007, Antonio Banderass. All rights reserved" #property link      "banderassa@ukr.net" #define TREND_UP   1 #define TREND_DOWN 2 #define FLAT      3 //+------------------------------------------------------------------+ //| script program start function                                    | //+------------------------------------------------------------------+ int start()   {    MessageBox("TREND_UP=" + TREND_UP + " TREND_DOWN=" + TREND_DOWN +  " FLAT=" + FLAT);   return(0);   }

注意,我们将常数声明放在文件开头的其他预处理器指令之下。让我们更进一步检查该声明:

#define TREND_UP 1

首先我们编写关键字#define.它向预处理器表明,之后的内容是常数声明。然后我们编写常数名称(标识符),即一个可通过其参考常数值的词。在我们的示例中,它是“TREND_UP”。值 - 1紧跟其后。现在,当预处理器看到源代码中的 TREND_UP 时,它会将其替换为 1,并对所有其他常数执行相同的操作。以下是被预处理器处理前的源代码示例:处理

int start()     {      MessageBox("TREND_UP=" + TREND_UP + " TREND_DOWN=" +  TREND_DOWN + " FLAT=" + FLAT);      return(0);     }

处理后的源代码示例:

int start()     {      MessageBox("TREND_UP=" + 1 + " TREND_DOWN=" + 2 +   " FLAT=" + 3);      return(0);     }

现在您应该理解上一部分中 MODE_ASCEND 和 MODE_DESCEND 所代表的含义了。它们是带相应值的常数。

总结

您已在本文中学习了很多新知识:新循环类型 - while;新条件类型 - switch;运算符 break 和 continue。您学习编写自己的函数和使用多维数组,了解如何使用常数。所有这些都是您的主要工具,是您编写更高级代码(例如用户的指标和 EA)的基础。所以,您要确保完全掌握本文的内容,因为它非常重要,在将来会经常用到。


论坛官方微信、群(期货热点、量化探讨、开户与绑定实盘)
 
期货论坛 - 版权/免责声明   1.本站发布源码(包括函数、指标、策略等)均属开放源码,用意在于让使用者学习程序化语法撰写,使用者可以任意修改语法內容并调整参数。仅限用于个人学习使用,请勿转载、滥用,严禁私自连接实盘账户交易
  2.本站发布资讯(包括文章、视频、历史记录、教材、评论、资讯、交易方案等)均系转载自网络主流媒体,内容仅为作者当日个人观点,本网转载此文出于传递更多信息之目的,并不意味着赞同其观点或证实其描述。本网不对该类信息或数据做任何保证。不对您构成任何投资建议,不能依靠信息而取代自身独立判断,不对因使用本篇文章所诉信息或观点等导致的损失承担任何责任。
  3.本站发布资源(包括书籍、杂志、文档、软件等)均从互联网搜索而来,仅供个人免费交流学习,不可用作商业用途,本站不对显示的内容承担任何责任。请在下载后24小时内删除。如果喜欢,请购买正版,谢谢合作!
  4.龙听期货论坛原创文章属本网版权作品,转载须注明来源“龙听期货论坛”,违者本网将保留追究其相关法律责任的权力。本论坛除发布原创文章外,亦致力于优秀财经文章的交流分享,部分文章推送时若未能及时与原作者取得联系并涉及版权问题时,请及时联系删除。联系方式:http://www.qhlt.cn/thread-262-1-1.html
如何访问权限为100/255贴子:/thread-37840-1-1.html;注册后仍无法回复:/thread-23-1-1.html;微信/QQ群:/thread-262-1-1.html;网盘链接失效解决办法:/thread-93307-1-1.html

返回列表