English Русский 中文 Español Deutsch Português
preview
初級から中級まで:定義(II)

初級から中級まで:定義(II)

MetaTrader 5 |
63 0
CODE X
CODE X

はじめに

なお、本記事の内容は教育目的に限定されており、完成されたアプリケーションとして捉えるべきではありません。ここでの目的は、提示された概念そのものを応用することではありません。

前回の「初級から中級まで:定義(I)」では、#defineコンパイルディレクティブについて解説しました。このディレクティブを用いることで、コードを簡潔にし、開発効率を高め、実装を容易にする方法を確認しました。また、学習段階においてコードの理解を助ける創造的な使い方についても触れました。このディレクティブを正しく使いこなすためには、何をしているのかを明確に理解している必要があります。一般的に言えば、#defineを活用することで、MQL5コードにより独創的な表現を与えることができます。

このため、#defineコンパイルディレクティブにはもう一つの使用方法があります。前回の記事の内容を複雑にしないようにするため、今回はそれを別の記事として取り上げることにしました。このように分けることで、より落ち着いてテーマを扱うことができ、学習と実践をより楽しく進められるはずです。その結果、概念の理解と習得がより容易になり、今後の記事で扱う内容、特にプログラミングレベルでの作業(私がすでに中級レベルと見なしている段階)に進むうえで、非常に重要な知識となるでしょう。


マクロとは何か

簡単に言えば、マクロとはコード内で繰り返し利用できる小さな手続きのようなものです。これはかなり単純化した説明ですが、実際のところ(そしてもう少し複雑な状況にも当てはまりますが)、コードの中でほとんど常に繰り返し現れる部分があるとき、私たちはマクロを作成します。つまり、同じコードを何度も書く代わりに、その部分をひとまとめにして「マクロ」と呼ばれる手続きとして定義するのです。

しかし、このような方法でマクロを定義するのは、あまり適切とは言えません。その理由は、そうした定義をより難しくしてしまういくつかの要因があるからです。

問題は、マクロが多くの場合(ほとんど常にと言ってもよいでしょう)「インライン」でコードを展開するように定義されるという点にあります。つまり、手続きの中にコードを置くのではなく、メモリ上で呼び出しやスタック/アンスタックをおこなうことなく、その場に直接コードが挿入されるのです。私の意見では、これがマクロの最良の定義です。ただし、「マクロを使うとコードをインライン化できる」と言っても、だからといってそれが特別な何かというわけではありません。理論的には、どんな手続きや関数でもインライン化することは可能だからです。ここで「理論的に」と言うのは、私自身、関数や手続きをinline宣言した場合とそうでない場合とで、実行速度に大きな違いを感じたことがないからです。

おそらく今の説明だけでは、まだピンとこないかもしれませんね。もう少し具体的に説明しましょう。CやC++と同じように、MQL5にもinlineという予約語があります。ただ、少なくとも私の観察する限りでは、これを使っているプログラマはほとんど見かけません。では、このinlineという単語は実際にはどういう意味を持つのでしょうか。通常、プログラマがコードを書くとき、言語が許す限り、手続き呼び出しや関数呼び出しをインラインコードに変換することができます。 言い換えると、手続きを呼び出す代わりに、そのコード自体を直接展開して実行するようにするのです。その結果、実行速度は速くなりますが、コード量が指数的に増え、より多くのメモリを消費することになります。

これは愚か、あるいは狂気の沙汰と思われるかもしれませんが、正しく使えば、メモリを多く使う代わりに実行速度を高めるという手法は有効な解決策になり得ます。ただし、この方法を使うときは注意が必要です。コードが指数的に膨れ上がると、いずれ必ず限界に達します。つまり、必要なメモリがどんどん増え、処理速度の向上がそのコストに見合わなくなってしまうのです。

例として、実際にどのようにこれを行うかを見てみましょう。そのために、以下のコードを見てください。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Fibonacci_Element_Default   11
05. //+------------------------------------------------------------------+
06. void OnStart(void)
07. {
08.     Print("Result: ", Fibonacci_Recursive());
09.     Print("Result: ", Fibonacci_Interactive());
10. }
11. //+------------------------------------------------------------------+
12. uint Fibonacci_Recursive(uint arg = def_Fibonacci_Element_Default)
13. {
14.     if (arg <= 1)
15.         return arg;
16. 
17.     return Fibonacci_Recursive(arg - 1) +  Fibonacci_Recursive(arg - 2);
18. }
19. //+------------------------------------------------------------------+
20. inline uint Fibonacci_Interactive(uint arg = def_Fibonacci_Element_Default)
21. {
22.     uint v, i, c;
23. 
24.     for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2)
25.         v += i;
26. 
27.     return (c == arg ? i : v);
28. }
29. //+------------------------------------------------------------------+

コード01

#defineコンパイルディレクティブを用いてルーチンや手続きを定義する際に、マクロとは何かを正しく理解するために、説明に注意を払ってください。非常に重要です。

コード01は他の記事で見てきたものと非常によく似ています。ただし、20行目に違いがあります。その違いこそ予約語「inline」です。今、コード01の動作を理解した上で、20行目に追加されたあのたった一語の違いが、どのようにしてコードを違うものにしているのかという疑問が生まれます。

その答えは、コードが見た目ではなく、コンパイラがどのように処理するかによって違いが出るということです。このコードをCやC++に翻訳した場合、少し異なるコードが生成されるはずです。問題は「見えるもの」ではなく「コンパイラがこの書き方をどう扱うか」にあります。

繰り返しになりますが、私の仮定では、MQL5コンパイラはこのinline指定の有無によって、処理速度に明確な差が出るようには動いていないように感じます。実際、手続きや関数宣言にinlineを付けた場合と付けなかった場合で、実行速度に気づくほどの違いを確認したことはありません。

とはいえ、20行目を見た際に、コンパイラは、このFibonacci_Interactive関数(手続き)がコード内に現れるたびに、20〜28行目までの全コードをその呼び出し箇所に置き換えなければならないことを認識します。ただし、このときは置き換え先のコード位置で衝突しないように、手続き内で使用されているローカル変数のデータベース(名前空間)を生成しておく必要があります。

分かりやすく言えば、同じコード01はコンパイル時にコンパイラによって次のような形で展開されるということです。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Fibonacci_Element_Default   11
05. //+------------------------------------------------------------------+
06. void OnStart(void)
07. {
08.     Print("Result: ", Fibonacci_Recursive());
09.     
10.     {
11.         uint v, i, c, arg = def_Fibonacci_Element_Default;
12. 
13.         for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2)
14.             v += i;
15. 
16.         Print("Result: ",  (c == arg ? i : v));
17. 
18.     }
19. }
20. //+------------------------------------------------------------------+
21. uint Fibonacci_Recursive(uint arg = def_Fibonacci_Element_Default)
22. {
23.     if (arg <= 1)
24.         return arg;
25. 
26.     return Fibonacci_Recursive(arg - 1) +  Fibonacci_Recursive(arg - 2);
27. }
28. //+------------------------------------------------------------------+

コード02

しかし、実際に生成されて実行されるのはコード02のほうです。ここで注意していただきたいのは、コードに加えられた変更が非常に特定のやり方でおこなわれなければならないという点です。そして、その処理をおこなうのはコンパイラです。私たちプログラマがおこなうのは、コンパイラに対して、関数や手続きをinline化すべきか否かを指示することだけです。しかし、実際の微調整、つまりどのようにコードを展開するかはコンパイラの責任です。

さて、ここまで読んで、特に気にする必要はなさそうだし、同じようなものに見えると感じる人もいるかもしれません。確かにそうです。シンプルな例があります。ですが、コード01の20行目にある関数が、私たちのプログラムの中で千回使われると想像してみてください。その場合、コンパイラはコード02で示された処理を千回繰り返すことになります。その結果、生成される実行ファイルはどんどん大きくなり、必要なディスク容量も増え、読み込み時間もどんどん長くなっていきます。たとえ実行速度が若干速くなったとしても、それだけではメモリや読み込み時間のコストを補うことはできません。

さて、ここまで理解できたなら、これから説明する「マクロとは何か」を理解するのはとても簡単です。実際、私たちはこのコードを使って最初のマクロを定義することができます。見てのとおり、コード01の10〜18行目には完全なコード、つまり他の部分から独立したひとつのブロックが存在しています。このブロックを最初のマクロに変えるには、単にそれを次のような形に書き換えるだけでよいのです。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Fibonacci_Element_Default   11
05. //+------------------------------------------------------------------+
06. void OnStart(void)
07. {
08.     Print("Result: ", Fibonacci_Recursive());
09. 
10. #define macro_Fibonacci_Interactive     {                           \
11.         uint v, i, c, arg = def_Fibonacci_Element_Default;          \
12.         for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2) v += i;  \
13.         Print("Result: ",  (c == arg ? i : v));                     \
14.                                         }
15. }
16. //+------------------------------------------------------------------+
17. uint Fibonacci_Recursive(uint arg = def_Fibonacci_Element_Default)
18. {
19.     if (arg <= 1)
20.         return arg;
21. 
22.     return Fibonacci_Recursive(arg - 1) +  Fibonacci_Recursive(arg - 2);
23. }
24. //+------------------------------------------------------------------+

コード03

さて、ここからが最も興味深い部分です。正直に言えば、コード03はコード02とほとんど同じです。しかし、実行時の結果はコード02とは異なります。この違いがお分かりでしょうか。おそらく、最初にマクロに触れた段階では理解しにくいと思います。それもそのはずで、これは一見して分かるような違いではないからです。

さて、次の点に注目してください。コード02とコード03の間で異なる点はたった1つ、10行目に#defineコンパイルディレクティブを追加したことです。このディレクティブの後には定数が続きます。そうです、マクロとは定数なのです。このことを決して忘れないでください。

マクロを定義する際は、1行内にすべてを収める必要があります。

複数行にわたるマクロを作成することはできません。これはプログラミング言語によって定められたルールです。したがって、複数行にわたるマクロを作成したい場合は、各行の末尾に特別な文字を追加しなければなりません。この文字は10行目以降の各行の末尾に付いているのが確認できます。ただし注意してください。最後の行にこの文字を付けてはいけません。そうしないと、マクロが意図しない位置にまで拡張され、コンパイル時にエラーが発生する可能性があります。

すべてが非常に単純に思えるかもしれません。実際、注意深く作業すればマクロの作成は難しくありません。しかし、これでコード03のすべてを理解したと思ったなら、それは間違いです。このマクロはコード02を基にしたものです。実行してみると分かりますが、コード03の結果はコード02とは異なります。なぜでしょうか。その理由は、マクロが実際には使用されていないからです。単に定義を宣言しただけなのです。そのため、コード02を実行すると、下図のように2つのメッセージが出力されます。

図01

しかし、コード03を実行すると、出力は次のようになります。

図02

では、この問題をどのように解決すればよいのでしょうか。そのためには、コンパイラに対してマクロを使用するよう明示的に指示する必要があります。以下のとおり、その方法は非常に簡単です。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Fibonacci_Element_Default   11
05. //+------------------------------------------------------------------+
06. void OnStart(void)
07. {
08.     Print("Result: ", Fibonacci_Recursive());
09. 
10. #define macro_Fibonacci_Interactive     {                           \
11.         uint v, i, c, arg = def_Fibonacci_Element_Default;          \
12.         for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2) v += i;  \
13.         Print("Result: ",  (c == arg ? i : v));                     \
14.                                         }
15. 
16.     macro_Fibonacci_Interactive;
17. }
18. //+------------------------------------------------------------------+
19. uint Fibonacci_Recursive(uint arg = def_Fibonacci_Element_Default)
20. {
21.     if (arg <= 1)
22.         return arg;
23. 
24.     return Fibonacci_Recursive(arg - 1) +  Fibonacci_Recursive(arg - 2);
25. }
26. //+------------------------------------------------------------------+

コード04

さて、これで正しく動作しました。コード04に16行目を追加すると、画像01と同じ結果が得られ、コードが期待どおりに実行されていることが確認できます。しかし、ここまでは最も簡単な部分にすぎません。ここからが本当に興味深い部分です。この次の内容を理解するために、ここでトピックを分けて説明することにします。というのも、これまでに示してきた内容をしっかりと理解しておかなければ、これからおこなうことを正しく理解するのは難しいからです。


マクロに引数を渡す

「何てことだ。もう終わったと思っていたのに、マクロに引数を渡せるなんて言い出すとは。」さて、ここでマクロの概念を正しく理解できているか確認してみましょう。もし間違っていたら訂正してください。

マクロとは、あるルーチン内に存在するコードで、それをインライン化して自分のコードの中に直接埋め込みたい場合に使うもの、ですよね。その通りです。正解です。では、もしマクロ内に関数や手続き全体を実装できる方法を作ることができれば、わざわざ同じ関数や手続きを作る必要はありません。なぜなら、コンパイラがコードをコンパイルする際、それはもはや関数呼び出しではなく、単なるインライン展開として扱われるからです。そうですよね?そのとおりです。ただし、少しだけ注意点があります。手続きの方が関数より実装が容易です。なぜなら、関数の場合、多くのケースで追加の変数が必要になるからです。いずれにせよ、あなたの理解は正しいです。

「なるほど、ではマクロに引数を渡したい場合は、関数や手続きを宣言するときと同じ方法で引数を宣言すればいいんですね。簡単だし、当然のことだと思います。」

残念ながらそれはほぼ正解ですが完全ではありません。なぜなら、マクロに引数を渡す場合、関数や手続きのように同じ形式で宣言することはできないからです。この点がややこしいところです。関数や手続きの場合、私たちは引数を細かく制御できます。たとえば「この引数は制御できない」「この引数は参照渡し可能」 「この引数の型は○○型」などと指定できます。しかしマクロの場合、そのような制御は一切できません。つまり、コンパイラはマクロの引数に関して何の補助もしてくれないのです。

ここが大きな違いであり、そのため多くのプログラマがマクロの使用を避けるのです。なぜなら、少しの不注意が深刻なトラブルにつながるからです。マクロ内で発生したエラーは非常に見つけにくく、修正も難しい。ある部分のコードでは正しい値を返すのに、 別の部分では間違った結果を返すといったことが容易に起こりえます。そしてこの種の不具合を検出し、修正するのは、私の経験上、最も困難で骨の折れる作業のひとつです。

ですから、マクロを使う際は細心の注意を払ってください。マクロは非常に強力で有用なリソースですが、使い方を誤ると、本来なら数分で直せる問題に何時間も費やす羽目になります。

では、次に進みましょう。コード04は、基本的にコード01を修正したものです。マクロを使えるようになった今、コード04を変更してマクロ内に値を渡す方法を理解していきましょう。

ここで注意していただきたいのは次の点です。コード01の8行目と9行目では、2種類の関数を使い分けることができました。1つは再帰的な処理を行うもの、もう1つは対話的な処理をおこなうものです。しかしコード04では、両方の処理を持っているにもかかわらず、定義の4行目で指定された値を変更しない限り、対話的な計算部分に値を渡すことができません。もちろん、それでは不十分です。なぜなら、私たちはコード01のように任意の値を渡せるようにしたいからです。

上記の内容をより理解しやすくするために、以下のコードを見てみましょう。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Fibonacci_Element_Default   11
05. //+------------------------------------------------------------------+
06. #define macro_Fibonacci_Interactive     {                           \
07.         uint v, i, c, arg = def_Fibonacci_Element_Default;          \
08.         for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2) v += i;  \
09.         Print("Result: ",  (c == arg ? i : v));                     \
10.                                         }
11. //+----------------+
12. void OnStart(void)
13. {
14.     Print("Result: ", Fibonacci_Recursive(15));
15.     macro_Fibonacci_Interactive;
16. }
17. //+------------------------------------------------------------------+
18. uint Fibonacci_Recursive(uint arg = def_Fibonacci_Element_Default)
19. {
20.     if (arg <= 1)
21.         return arg;
22. 
23.     return Fibonacci_Recursive(arg - 1) +  Fibonacci_Recursive(arg - 2);
24. }
25. //+------------------------------------------------------------------+

コード05

コード05を実行すると、下の画像に示すような結果が表示されます。

図03

結果は明らかに異なります。この違いが生じるのは、コード05の14行目で、18行目の関数パラメータとして値を渡しているためです。一方で、マクロの場合はそうではありません。マクロでは、4行目の定義内で値が固定されているため、異なる結果になります。このことからもわかるように、マクロを扱う際には注意と慎重さが必要です。マクロは便利な一方で、わずかな記述の違いでも思わぬ挙動を引き起こすことがあります。この問題を解決するには、マクロに引数を渡す必要があります。そのために、マクロのコードを少し変更します。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Fibonacci_Element_Default   11
05. //+------------------------------------------------------------------+
06. #define macro_Fibonacci_Interactive(arg) {                          \
07.         uint v, i, c;                                               \
08.         for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2) v += i;  \
09.         Print("Result: ",  (c == arg ? i : v));                     \
10.                                         }
11. //+----------------+
12. void OnStart(void)
13. {
14.     Print("Result: ", Fibonacci_Recursive(15));
15.     macro_Fibonacci_Interactive(14);
16. }
17. //+------------------------------------------------------------------+
18. uint Fibonacci_Recursive(uint arg = def_Fibonacci_Element_Default)
19. {
20.     if (arg <= 1)
21.         return arg;
22. 
23.     return Fibonacci_Recursive(arg - 1) +  Fibonacci_Recursive(arg - 2);
24. }
25. //+------------------------------------------------------------------+

コード06

コード06を実行すると、結果は次のようになります。

図04

明らかに値は異なりますが、これは意図的に行ったものです。目的は、独立した値を渡せること、そして互いに関連しないことを示すためです。これにより、マクロは他の関数や手続きとは関連がないことが明確になります。

興味深い内容ではありますが、まだ十分に「面白い」わけではありません。なぜなら、マクロはコード01のような関数として動作するわけでも、別の手続き型として動作するわけでもないからです。実際には、単に独立したコードの塊に過ぎません。それでもコード06は、コード02のように実行されます。しかし、ほとんどの場合これはまったく役に立たない操作です。なぜなら、コード06の15行目を別の場所に置くだけで、それ以外の目的はないからです。今回これをおこなっているのは、あくまでマクロの使い方を学ぶための教育的目的です。

では、どうすればマクロを関数のように動作させることができるかを考えましょう。実際のところ、コード06のようなマクロをそのまま関数として使うことはできません。関数は特殊な変数のようなもので、常に渡された引数に基づいて値を返すものです。 ここで強調します。「マクロを関数として使えない」と言っているわけではありません。私が言いたいのは、この特定のマクロを関数のように使うことはできないということです。混同しないように注意してください。

しかし、この初期的な制約にもかかわらず、このマクロを参照渡しを使う手続きのように動作させることは可能です。その方法は簡単です。以下のようにコードを変更します。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Fibonacci_Element_Default   11
05. //+------------------------------------------------------------------+
06. #define macro_Fibonacci_Interactive(arg) {                          \
07.         uint v, i, c;                                               \
08.         for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2) v += i;  \
09.         arg = (c == arg ? i : v);                                   \
10.                                         }
11. //+----------------+
12. void OnStart(void)
13. {
14.     ulong value;
15. 
16.     Print("Result: ", Fibonacci_Recursive((uint)(value = 15)));
17.     macro_Fibonacci_Interactive(value);
18.     Print("Checking: ", value);
19. }
20. //+------------------------------------------------------------------+
21. uint Fibonacci_Recursive(uint arg = def_Fibonacci_Element_Default)
22. {
23.     if (arg <= 1)
24.         return arg;
25. 
26.     return Fibonacci_Recursive(arg - 1) +  Fibonacci_Recursive(arg - 2);
27. }
28. //+------------------------------------------------------------------+

コード07

さて、ここからが面白くなってきます。ポイントは、マクロをより実践的な目的で使用し始めたことです。注目すべきは次の点です。14行目で変数を宣言していますが、初期化はおこなっていません。16行目で初期化しています。16行目で一体何が起きているのかと思うかもしれませんが、心配はいりません。決しておかしなことではありません。

さらに、14行目の変数はulong型であり、21行目の関数が期待する型はuint型です。同じ値を21行目の関数と、後で使用するマクロの両方で使いたいため、コンパイラの警告を防ぐために明示的な型変換を行う必要があります。そのため、16行目の宣言はこのようになっています。

次に17行目です。ここからが本番です。これは変数を参照渡しでマクロに渡すためです。その結果、マクロ内部で変数の値が変更されると、メインコード内にも反映されます。9行目で値が変化するため、注意が必要です。さもないと「時限爆弾」のような問題が発生します。この仕組みが動作することを示すために、18行目で変数の値をターミナルに出力しています。コード07を実行すると、以下のような結果が得られます。

図05

ここから分かる通り、マクロに変数を渡す際は参照として渡しているため、マクロが値を不適切に変更しないよう注意が必要です。さて、コード07で使用しているマクロは、手続き型として動作するマクロです。では、マクロを関数のように使うことは可能でしょうか。つまり、1つの値を渡して別の値を返す使い方です。多くのプログラマは、マクロを学び始めた最初の段階でこの疑問を持ちます。

基本的にマクロは手続き志向ですが、内部のコード構造によっては関数のように動作させることも可能です。本記事で扱ったコードを使えば、その仕組みの例を作ることができます。完全に洗練されたものではなく、面白みに欠けるかもしれませんが、理解のために試してみる価値はあります。この例は以下のコードで確認できます。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_Fibonacci_Element_Default   11
05. //+------------------------------------------------------------------+
06. #define macro_Ternário(A, B, C, D)  (A == B ? C : D)
07. //+----------------+
08. #define macro_Fibonacci_Interactive(arg) {                          \
09.         uint v, i, c;                                               \
10.         for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2) v += i;  \
11.         arg = macro_Ternário(c, arg, i, v);                         \
12.                                         }
13. //+----------------+
14. void OnStart(void)
15. {
16.     ulong value;
17. 
18.     Print("Result: ", Fibonacci_Recursive((uint)(value = 15)));
19.     macro_Fibonacci_Interactive(value);
20.     Print("Checking: ", value);
21. }
22. //+------------------------------------------------------------------+
23. uint Fibonacci_Recursive(uint arg = def_Fibonacci_Element_Default)
24. {
25.     if (arg <= 1)
26.         return arg;
27. 
28.     return Fibonacci_Recursive(arg - 1) +  Fibonacci_Recursive(arg - 2);
29. }
30. //+------------------------------------------------------------------+

コード08

ここで、コード08では、関数のように動作するマクロの小さなデモを見ることができます。このマクロは6行目で定義されており、マクロを関数として動かす簡単な例になっています。11行目で実際に使用していることに注目してください。この手法の本質は、コード内の複雑さを隠すことができる点にあります。マクロに分かりやすい名前を付けることで、コードの動作を理解しやすくなるのです。

ここでの目的は、コードの効率化ではなく可読性の向上です。たとえば、日付や時間を操作するためのマクロを作り、datetime型を扱いやすくする、といった使い方が典型例です。これが、関数のような機能を持つマクロの具体例になります。より視覚的に理解できるように、次にいくつかのマクロを作成して説明してみましょう。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. enum eConvert {
05.     FORMAT_DECIMAL,
06.     FORMAT_OCTAL,
07.     FORMAT_HEX,
08.     FORMAT_BINARY
09. };
10. //+------------------------------------------------------------------+
11. #define macro_GetDate(A)            (A - (A % 86400))
12. #define macro_GetTime(A)            (A % 86400)
13. //+----------------+
14. #define macro_GetSec(A)             (A - (A - (A % 60)))
15. #define macro_GetMin(A)             (int)((A - (A - ((A % 3600) - (A % 60)))) / 60)
16. #define macro_GetHour(A)            (int)((A - (A - ((A % 86400) - (A % 3600)))) / 3600)
17. //+----------------+
18. #define PrintX(X)                   Print(#X, " => ", X);
19. //+------------------------------------------------------------------+
                   .
                   .
                   .

スニペット01

スニペット01では、私たちのインクルードファイルとなる部分があります。ここではマクロを含めています。これは、経験を積み、特定のライブラリを構築するにつれて、プログラミング能力を大幅に拡張できることを示すためです。

11行目から18行目までのマクロが実際に何をするかを確認するために、テスト用コードを用意しました。以下をご覧ください。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "Tutorial\File 01.mqh"
05. //+------------------------------------------------------------------+
06. void OnStart(void)
07. {
08.     datetime dt = D'2024.08.15 12:30:27';
09.     ulong v;
10. 
11.     Print("Date And Time: ", dt);
12.     Print("0x", ValueToString(dt, FORMAT_HEX));
13.     PrintX((datetime)(v = macro_GetDate(dt)));
14.     Print("0x", ValueToString(v, FORMAT_HEX), " :: ", ValueToString(v, FORMAT_DECIMAL));
15.     PrintX((datetime)(v = macro_GetTime(dt)));
16.     Print("0x", ValueToString(v, FORMAT_HEX), " :: ", ValueToString(v, FORMAT_DECIMAL));
17.     PrintX((datetime)(v = macro_GetSec(dt)));
18.     Print("0x", ValueToString(v, FORMAT_HEX), " :: ", ValueToString(v, FORMAT_DECIMAL));
19.     PrintX((datetime)(v = macro_GetMin(dt)));
20.     Print("0x", ValueToString(v, FORMAT_HEX), " :: ", ValueToString(v, FORMAT_DECIMAL));
21.     PrintX((datetime)(v = macro_GetHour(dt)));
22.     Print("0x", ValueToString(v, FORMAT_HEX), " :: ", ValueToString(v, FORMAT_DECIMAL));
23. }
24. //+------------------------------------------------------------------+

コード09

これは非常に興味深く、ワクワクする内容です。なぜなら、あまり手間をかけずに、これまで説明していない他のリソースを使わなければならなかった処理を分析できるからです。コード09を実行すると、以下のように表示されます。

図06

ここで、なぜコード09が非常に興味深いと言ったのかが理解できるでしょう。コード09の13、15、17、19、21行目ではマクロが呼び出されています。このマクロはスニペット01の18行目で定義されています。マクロの目的は、変数名とその値を表示することです。今回の場合、変数名は関数であり、それぞれの行で異なるマクロが使用されています。これは少し珍しく、興味深い組み合わせです。というのも、各マクロから返される値はまずローカル変数に格納され、その直後にその変数の16進数表示と10進数表示が出力されるからです。

これにより、datetime形式で正しい値を確実に取得していることが確認できます。多くの方はこんなこと意味あるのかと思うかもしれませんが、このマクロは特にコードを高速かつ安全に実行したい場合に非常に有用です。また、コードを書く作業自体をより簡単に、楽しく、かつ面白くしてくれます。


最終的な考察

この記事では、マクロとは何か、またコード作成においてマクロをどのように扱うべきかを考察しました。マクロは、しばしば誤用されたり、まったく無視されたりすることが多いリソースです。その理由は、マクロの使用方法に関する知識や実践が不足しているからです。その結果、多くのプログラマは、自分には不可能だと思えたことが、別の(しばしば未知の)プログラマによって実現できる理由を理解できないままになってしまいます。

私自身は(そしてそう信じたいのですが)、次の考え方に賛同しています。

悪いプログラマはいない。ただ、自分の道を見つけていない人がいるだけである。確かに、訓練されていないプロもいれば、自分は準備ができていると考えているプロもいます。しかし実際には、まだプログラミングに不慣れな段階にある場合も多いのです。

この記事で伝えたい概念は、まさにプログラミングを学び始めたばかりの人に向けたものです。正しい道を歩み、概念や特定ツールの存在理由を正しく理解して学習すれば、自分のアイデアを現実化しやすくなります。読者の皆さんにとっては、ここで扱った内容が一見無意味で、特定の目的や課題がないように思えるかもしれません。しかし、実際に学んだことを実践で応用できれば、これまで難しいと感じていた多くのことが、容易に達成可能であることに気付くでしょう。もちろん、まだ明確にするべき点はいくつか残っていますが、私たちはすでに正しい方向へ大きく前進しています。

MetaQuotes Ltdによりポルトガル語から翻訳されました。
元の記事: https://www.mql5.com/pt/articles/15588

添付されたファイル |
Anexo.zip (4.58 KB)
リプレイシステムの開発(第77回):新しいChart Trade (IV) リプレイシステムの開発(第77回):新しいChart Trade (IV)
この記事では、通信プロトコルを作成する際に考慮すべきいくつかの対策や注意点について説明します。内容は比較的シンプルでわかりやすいものなので、詳細には触れません。しかし、この記事の内容を理解することで、今後の展開が把握しやすくなります。
取引におけるニューラルネットワーク:マルチエージェント自己適応モデル(MASA) 取引におけるニューラルネットワーク:マルチエージェント自己適応モデル(MASA)
マルチエージェント自己適応(MASA: Multi-Agent Self-Adaptive)フレームワークについて紹介します。本フレームワークは、強化学習と適応戦略を組み合わせ、変動の激しい市場環境においても収益性とリスク管理のバランスを実現します。
取引におけるニューラルネットワーク:マルチエージェント自己適応モデル(最終回) 取引におけるニューラルネットワーク:マルチエージェント自己適応モデル(最終回)
前回の記事では、強化学習アプローチと自己適応戦略を組み合わせ、市場の変動下でも、収益性とリスクの両立を図ることができるマルチエージェント自己適応(MASA: Multi Agent Self Adaptive)フレームワークを紹介しました。MASAフレームワークにおける各エージェントの機能も構築済みです。本記事では、前回の内容をさらに発展させ、その論理的な結論へと到達します。
初心者からエキスパートへ:MQL5を使用したアニメーションニュースヘッドライン(VI) - ニュース取引のための指値注文戦略 初心者からエキスパートへ:MQL5を使用したアニメーションニュースヘッドライン(VI) - ニュース取引のための指値注文戦略
本記事では、ニュースを表示するだけでなく実際に取引を実行できるよう、EA(エキスパートアドバイザー)の機能拡張に焦点を当てます。MQL5上で自動売買の実装方法を解説し、「News Headline EA」を完全に反応的な取引システムへと発展させていきます。EAは、その豊富な機能により、アルゴリズム開発者にとって非常に強力なツールです。これまでの記事では、ニュースおよび経済指標カレンダーイベントの可視化ツールを中心に開発し、AIインサイトレーンやテクニカル指標分析を統合してきました。