面向初學者的 MQL4 語言系列之2——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) // condition
  {
    // what to do, if the condition is not fulfilled
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:                        // case - key word; 
   // trading strategy         // if marketState is equal to 1, then 
   // for an uptrend           // perform this code
   break;                      // key word that indicates  
                               // the end of actions in this case
 
case 2:                        // if marketState is equal to 2, then
    // startegy for            // perform this
    // a downtrend          
    break;                     // end
 
case 3:                        // identical
    // strategy for flat
    break;
 
default:                       // otherwise, perform this
    // 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 one
 
someFunction(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! default
                                           // values 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

多維數組

編程期間常常會用到數組,大部分情況下,一維數組就夠用了。但在某些情況下,您需要二維數組、三維數組。現在我們來學習如何使用它們。

我們先來看看一維數組、修改聲明、初始化、指數和值的相關圖片:

任何一維數組都可顯示爲一個類型的一行值。以下是一維數組的不同參考的處理方式:

二維數組就像一般的表格,如下:

從圖片上來看,二維數組已有兩個指數用於值的參考:第一個指數確定一行,第二個指數確定一列。像在一維數組中那樣,使用值列表進行初始化。以下是參考表格單元的值的方式:

一切都很清楚。我們來看看如何遍歷一個二維數組的所有值。應使用 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 |
//|                                               [email protected] |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2007, Antonio Banderass. All rights reserved"
#property link      "[email protected]"
 
#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)的基礎。所以,您要確保完全掌握本文的內容,因爲它非常重要,在將來會經常用到。


原文鏈接:https://www.mql5.com/zh/articles/1483

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章