簡介
這是“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(condition of cycle fulfillment)
{
code;
}
更簡單的示例如下:
while(I havent eaten up the apple) // condition { // what to do, if the condition is not fulfilled bite more; }
新的循環類型 - 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)的基礎。所以,您要確保完全掌握本文的內容,因爲它非常重要,在將來會經常用到。