MQL4の関数が return で返せる値は基本的に1つだけです。しかし実際のEA開発では、複数の計算結果をまとめて受け取りたい場面がよくあります。
そういう場合は参照渡しを使います。引数を参照で渡せば、関数の中で書き換えた値が呼び出し元にそのまま反映されるので、実質的に複数の値を「戻す」ことができます。値の数や型に応じて、配列・構造体・クラスと組み合わせるのが定番です。
値渡しと参照渡し
MQL4の関数の引数の渡し方には、値渡し(by value)と参照渡し(by reference)の2種類があります。公式リファレンスの説明はこちらです。
ただし例外として、配列・構造体・クラスのオブジェクトは常に参照渡しになります。int や double などの単純な型だけが、値渡しと参照渡しを選べます。
値渡しは、変数の値をコピーして関数に渡します。関数の中でその引数を書き換えても、呼び出し元の変数は変わりません。
参照渡しは、値ではなく変数そのもの(値が格納されているメモリアドレス)を渡します。関数の中で引数を書き換えると、その変更が呼び出し元の変数にそのまま反映されます。そのため関数が終わったあと、呼び出し元で変数を見れば処理後の値を受け取れるわけです。
参照はポインタと似ていますが、参照は必ず実在する変数を指していなければならず、ポインタのようにNULLにはできません。また、参照渡しの引数にはデフォルト値を設定できないという制約もあります。
値渡し
まずは値渡しの例です。2つの変数 param1・param2 にそれぞれ5を足す(+5シフトする)処理を、値渡しの関数で書くと次のようになります。
TestSeveralReturns.mq4
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
//●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●● // TestSeveralReturns //●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●● int param1 = 0; int param2 = 0; int count=0; int OnInit(){ return(INIT_SUCCEEDED); } void OnDeinit(const int reason){ } //●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●● // OnTick() //●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●● void OnTick(){ // only 1 task if(count==0){ param1 = 3; param2 = 5; param1 = FShift01(param1); param2 = FShift01(param2); Print("param1 =" + IntegerToString(param1)); Print("param2 =" + IntegerToString(param2)); count=1; } } int FShift01(int param){ // shift 5 param = param + 5; return param; } |
ストラテジーテスターで実行すると、param1・param2 がそれぞれ+5されているのが確認できます(3→8、5→10)。
値渡しの関数は戻り値を1つしか返せないため、変数の数だけ関数を呼び出す必要があります。2つくらいならまだしも、変数が増えたり型が混ざったりすると、とても煩雑になります。
参照渡し
同じ+5シフトを参照渡しで書いてみます。関数側の引数で、型名の後ろに「&」を付けると、その引数は参照渡しになります。呼び出す側は普通に変数を渡すだけでOKです。
|
1 2 3 4 5 6 7 |
FShift02(param1, param2); ... void FShift02(int &par1, int &par2){ // shift 5 par1 = par1 + 5; par2 = par2 + 5; } |
全体をまとめると次のようになります。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
... int param1 = 0; int param2 = 0; int count=0; ... //●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●● // OnTick() //●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●● void OnTick(){ // only 1 task if(count==0){ param1 = 3; param2 = 5; FShift02(param1, param2); Print("param1 =" + IntegerToString(param1)); Print("param2 =" + IntegerToString(param2)); count=1; } } void FShift02(int &par1, int &par2){ // shift 5 par1 = par1 + 5; par2 = par2 + 5; } |
結果は値渡しと同じく、+5シフトした値になっています。関数の呼び出しは1回だけで、戻り値を使わずに2つの値を受け取れました。
蛇足:「&」は日本語ではよく「アンパサンド」と書かれますが、綴りは Ampersand なので、英語の発音は「アンパーサンド」に近い音(/ˈæmpərsænd/)です。欧州の知人もそう発音していました。
配列の参照渡し
引数が2つならまだ何とかなりますが、6つにもなると関数の引数が長くなり冗長です。同じ型の値がたくさんある場合は、配列にまとめて参照渡しすれば簡単に書けます。
まず、6つの値を持つ配列を用意します。
|
1 |
int arrTestA[6] = {1, 2, 3, 4, 5, 6}; |
配列の全要素を+5シフトする関数です。配列を引数にするときは、型名の後ろの「&」が必須です(付け忘れるとコンパイルエラーになります)。
|
1 2 3 4 5 6 |
void FShift03(int &arrTestB[]){ for(int i = 0; i < ArrayRange(arrTestB, 0); i++){ // shift 5 arrTestB[i] = arrTestB[i] + 5; } } |
TestSeveralReturns.mq4
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
... int arrTestA[6] = {1, 2, 3, 4, 5, 6}; int count=0; ... //●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●● // OnTick() //●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●● void OnTick(){ // only 1 task if(count==0){ FShift03(arrTestA); for(int i = 0; i < ArrayRange(arrTestA, 0); i++){ Print("arrTestA["+IntegerToString(i)+"] =" + IntegerToString(arrTestA[i])); } count=1; } } void FShift03(int &arrTestB[]){ for(int i = 0; i < ArrayRange(arrTestB, 0); i++){ // shift 5 arrTestB[i] = arrTestB[i] + 5; } } |
ストラテジーテスターで実行すると、arrTestA の各要素がそれぞれ+5されているのが確認できます(1〜6 → 6〜11)。
構造体の参照渡し
渡したい値がすべて同じ型なら配列で十分ですが、int・double・string のように型が混ざる場合は構造体(struct)にまとめます。構造体も配列と同じく常に参照渡しで、引数の「&」は必須です。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
... struct paramSet01{ int paraInt; double paraDouble; string paraString; }; int count=0; ... //●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●● // OnTick() //●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●● void OnTick(){ // only 1 task if(count==0){ paramSet01 ps1; ps1.paraInt = 4; ps1.paraDouble = 2.2; ps1.paraString = "before"; FShift04(ps1); Print("ps1.paraInt =" + IntegerToString(ps1.paraInt)); Print("ps1.paraDouble =" + DoubleToString(ps1.paraDouble)); Print("ps1.paraString =" + ps1.paraString); count=1; } } void FShift04(paramSet01 ¶mS){ paramS.paraInt = paramS.paraInt + 5; paramS.paraDouble = paramS.paraDouble/2; paramS.paraString = "after"; } |
実行すると、int は 4→9、double は 2.2→1.1、string は「before」→「after」と、型の違う3つの値がそれぞれ書き換わっているのが分かります。
クラスの参照渡し
クラスを使う場合です。下の例は、クラス FSclass のメソッド FShift05() に配列を参照渡しして、各要素を+5シフトしています。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
... int arrSet[3] = {1, 2, 3}; int count=0; ... //●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●● // OnTick() //●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●● void OnTick(){ // only 1 task if(count==0){ FSclass fs; fs.FShift05(arrSet); for(int i = 0; i < ArrayRange(arrSet, 0); i++){ Print("arrSet["+IntegerToString(i)+"] =" + IntegerToString(arrSet[i])); } count=1; } } class FSclass{ private: int par1; public: void FShift05(int &as[]){ par1 = 5; as[0] = as[0] + par1; as[1] = as[1] + par1; as[2] = as[2] + par1; } }; |
やっていることは配列の参照渡しとほぼ同じですが、+5する値(par1)のような設定をクラスの中に持たせておけるのが利点です。
なお、クラスのオブジェクト自体を関数の引数にする場合も、配列・構造体と同じく値渡しでコピーされることはなく、参照渡し(「&」付き)かポインタ渡しになります。関数の中でメンバーを書き換えると呼び出し元のオブジェクトも変わるので、その点を意識して使う必要があります。
まとめ:複数の値を戻す方法の使い分け
MQL4で関数から複数の値を受け取りたいときは、return ではなく参照渡しを使います。渡したい値の数や型に応じて、次のように使い分けるとすっきり書けます。
| 方法 | 向いているケース | ポイント |
| 変数ごとの参照渡し | 値が2〜3個程度 | 型名の後ろに「&」を付ける |
| 配列の参照渡し | 同じ型の値がたくさんある | 常に参照渡し・「&」必須 |
| 構造体の参照渡し | 型の違う値をまとめて返したい | 常に参照渡し・「&」必須 |
| クラス | 値と処理・設定をまとめて扱いたい | オブジェクトは参照渡しかポインタ渡し |
最後に注意点をひとつ。参照渡しは「関数が呼び出し元の変数を書き換えられる」しくみなので、書き換えるつもりのない引数まで参照で渡すと、思わぬバグの原因になります。配列や構造体のように参照渡ししかできない引数を「読むだけ」で使う場合は、const を付けておきましょう(例:void Show(const int &arr[]))。const を付けた引数を関数の中で書き換えようとするとコンパイルエラーになるので、読み取り専用であることがはっきりします。























