初級から中級まで:定義(I)
はじめに
なお、本記事の内容は教育目的に限定されており、完成されたアプリケーションとして捉えるべきではありません。ここでの目的は、提示された概念そのものを応用することではありません。
前回の記事「初級から中級まで:再帰」では、再帰とは何か、そしてそれがさまざまな場面で非常に有用なプログラミング手法としてどのように役立つのかを解説しました。再帰を使えば、仕組みや実装をシンプルかつ容易に作成することができます。ただし、場合によってはコードの実行が遅くなることもあるため、そのような場合には忍耐が必要です。
それでもなお、再帰は私たちを助け、作業をはるかに楽にしてくれます。したがって、専門家として経験を積みたい人や、少なくともプログラミングに関してより深く理解したい人にとって、学んでおくべき重要なテーマといえるでしょう。
本記事では、以前触れたテーマをさらに掘り下げます。それは、定義の活用についてです。定義はマクロを構築するためだけでなく、私たち自身や他のプログラマーが書いたコードの特定の部分を、より細かく制御することを可能にします。これにより、興味を持ったコードを目的に合わせてコントロールしながら修正することができるようになるのです。
定義とは何か
定義は、同じコード内でもさまざまな形をとることがあり、言語やプログラマーが達成したい目的によって、概念や使い方が多少変わることがあります。一般的に、定義を使うことで、マクロの作成、コンパイル指示子(ディレクティブ)の利用、あるいは特殊な定数の宣言を通して、コードを簡単かつ迅速、そして安全に制御し、変更することが可能になります。定義はとても面白いものです。どのように動作するのか理解しながら使うと、その便利さがさらに実感できるでしょう。特に、コードに小さな変更を加えてその結果を試したい人にとって、定義は非常に役立ちます。なぜなら、定義を使えばそれをとても簡単かつ実用的におこなうことができるからです。
さらに今回は、コンパイル指示子の利用に焦点を当てた定義について見ていきます。もちろん、外部プログラミングをおこなう際や、他の環境で作成されたコードをMQL5と連携して使用する場合にも定義は作られます。しかし、その場合は組み込みプログラミングの経験が求められることがあります。たとえMQL5だけを使う場合でも、基礎的な知識があると理解しやすくなります。
たとえば、MQL5では他のプログラマーが作成したコードや機能(主にCやC++で書かれたもの)をインポートすることが可能です。これにより、MetaTrader 5に自分の興味に応じた機能や要素を追加できるようになります。たとえば、ビデオプレイヤーを組み込んだり、高度な科学チャートを作成するユーティリティを開発したりできます。LaTeXのような数式を美しく整形できる言語を使うことも可能です。非常に面白い分野です。
今回の記事では、定義の中でもコンパイル指示子を使ったものに焦点を当てるため、話はよりシンプルでわかりやすくなります。しかも、前回の記事で既に似た内容に触れているので、理解は早いでしょう。では、まず#define指示子について説明していきます。
このコンパイル指示子は非常に特殊です。これは、これを使用すると、定数またはマクロを作成できるようになるためです。#defineを使うと、定数またはマクロを作成できます。基本的には、この指示子でできることはそれだけです。以前の「初級から中級へ:Includeディレクティブ」で扱った#include指示子と同様に、#defineも他のコンパイル指示子と組み合わせることで、小さく簡単に作成・変更できる設定を多数生成することができます。たとえば、前回の記事では次のようなコードがありました。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. const uchar who = 6; 07. 08. Print("Result: ", Fibonacci_Recursive(who)); 09. Print("Result: ", Fibonacci_Interactive(who)); 10. } 11. //+------------------------------------------------------------------+ 12. uint Fibonacci_Recursive(uint arg) 13. { 14. if (arg <= 1) 15. return arg; 16. 17. return Fibonacci_Recursive(arg - 1) + Fibonacci_Recursive(arg - 2); 18. } 19. //+------------------------------------------------------------------+ 20. uint Fibonacci_Interactive(uint arg) 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
さて、6行目ではuchar型の定数を定義しています。しかし注意すべきは、値を受け取る関数はuint型の値を期待しているということです(12行目や20行目で確認できます)。とはいえ、毎回型を合わせるために定数を調整するのは面倒です。そこで、コンパイル指示子を使うと、こうした処理をよりスマートにおこなうことができます。また、場合によってはコードの実行速度をわずかに向上させることも可能です。以下では、定数の代わりにコンパイル指示子を使うことでコードが少し速くなる理由や、その他のパフォーマンス向上につながるポイントを説明します。
ここでコンパイル指示子を使用することを前提とすると、コード01は次のように書き直すことができます。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_Fibonacci_Element 6 05. //+------------------------------------------------------------------+ 06. void OnStart(void) 07. { 08. Print("Result: ", Fibonacci_Recursive(def_Fibonacci_Element)); 09. Print("Result: ", Fibonacci_Interactive(def_Fibonacci_Element)); 10. } 11. //+------------------------------------------------------------------+ . . .
コード02
もちろん、コード全体を繰り返す必要はありません。しかし、どのように実装したかを見てみましょう。一見すると違いはないように思えますが、実際には違いがあります。コードを読んでいる人には同じに見えるかもしれませんが、コンパイラにとっては、コード01とコード02は異なります。その理由は、コード02で#define指示子を使っているからです。
つまり、コンパイラが実際に目にするのは、以下のようなコードになります。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. Print("Result: ", Fibonacci_Recursive(6)); 07. Print("Result: ", Fibonacci_Interactive(6)); 08. } 09. //+------------------------------------------------------------------+ . . .
コード03
でも、ちょっと待ってください。コード02はコード01と同等です。つまり、この指示子を使うこと自体に意味はないように思えるかもしれません。実際、すべては非常に論理的です。ここで重要なのは、あくまで教育目的での学習という点を忘れないことです。コードには、特定の値を使わなければならない箇所が多数存在することがあります。定数を使った場合、たとえそれがグローバル定数であっても、その定数が存在することによって、将来的に問題が生じる可能性があります。
しかし、コード02のように定義を使えば、コードをより自由にコントロールすることができます。さらに、定数はグローバルでない限り、宣言された手続き内でしか利用できません。一方、指示子は、宣言されればコード内のどこでも問題なく利用できます。これは、グローバル定数とは異なり、指示子はいつでも削除でき、その値を変更できるほか、同じ名前の別の指示子とは全く異なる機能を持たせることも可能だからです。
ここで挙げた各ポイントはどれも重要で、いずれ必要になる場面があるかもしれません。まずは可視性の問題から見ていきましょう。これは非常にシンプルなケースで、以下のコードを見れば一目で理解できます。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_Fibonacci_Element_Default 6 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. 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. //+------------------------------------------------------------------+
コード04
次の点に注目してください。4行目では、定数型のコンパイル指示子を宣言しています。この指示子はコード全体で利用可能で、関数や手続きの引数にデフォルト値を定義した場合と同じ型と期待される結果を持ちます。しかし、同じコンパイル指示子をグローバル定数で置き換えることも可能です。ただし、ここで面白い点があります。次のコードが示すようなことは、定数では実現できないのです。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_Fibonacci_Element_Default 6 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. #undef def_Fibonacci_Element_Default 21. //+------------------------------------------------------------------+ 22. uint Fibonacci_Interactive(uint arg = def_Fibonacci_Element_Default) 23. { 24. uint v, i, c; 25. 26. for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2) 27. v += i; 28. 29. return (c == arg ? i : v); 30. } 31. //+------------------------------------------------------------------+
コード05
このコードの20行目に注目してください。ここでは別のコンパイル指示子#undefを使用しています。この指示子を使うことで、#undefに指定された名前の指示子を削除または無効化することができます。このような機能は非常に便利です。しかし、より重要な機能について話す前に、コード05をコンパイルしようとしたときに何が起こるかを見てみましょう。コンパイルを試みると、コンパイラは次のようなエラーを返します。

図01
このエラーは22行目で発生したことを示しています。しかし、def_Fibonacci_Element_Default定数は20行目で削除されています。そのため、コンパイラがコードをコンパイルする際に、この名前付き定数をデータベース内で探しても見つからず、図01のようなエラーが発生するのです。このため、多くのプログラマーは、エラーの種類を判別しやすくするために名前の接頭辞を付ける習慣があります。これは必須のルールではありませんが、望ましいプログラミング習慣です。たとえば私は、定義された定数には必ず「def_」を付けて、通常の定数とコンパイル指示子を区別できるようにしています。
「なるほど、では削除した直後に別の値をこの指示子に宣言したい場合はできますか。」もちろん可能です、読者の皆さん。ただし、注意が必要です。後ほど、この操作で不要なトラブルを避ける方法を紹介します。100%の安全策ではありませんが、少なくとも役に立ちます。では、提案どおりに指示子を変更した場合、何が起こるのか見てみましょう。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_Fibonacci_Element_Default 6 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. #undef def_Fibonacci_Element_Default 21. //+----------------+ 22. #define def_Fibonacci_Element_Default 7 23. //+------------------------------------------------------------------+ 24. uint Fibonacci_Interactive(uint arg = def_Fibonacci_Element_Default) 25. { 26. uint v, i, c; 27. 28. for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2) 29. v += i; 30. 31. return (c == arg ? i : v); 32. } 33. //+------------------------------------------------------------------+
コード06
それでは、コード06で提案がどのように実装されたかを見てみましょう。22行目で指示子に新しい値を定義していることに注目してください。このコードを実行すると、結果は以下のようになります。

図02
ご覧のとおり、結果は変わっています。これは、値を変更したことによる当然の結果です。もし4行目で定義した指示子がグローバル定数であった場合、22行目で示したような変更はできませんし、コードの他の部分からグローバル定数を削除することもできません。この点を理解することが重要です。単に暗記するのではなく、理解し、実際に活用できるようにすることが求められます。
さて、ここまでで#defineと#undefのコンパイル指示子を組み合わせて簡単に使えることがわかりました。先ほども触れたように、これら二つの指示子をより高度に活用する方法も存在します。ただし、その場合は作業を簡単にし、制御をしやすくするための他の指示子も併用する必要があります。
コードバージョンの管理
#defineと#undef指示子の最も興味深い使い方のひとつは、同じコードのバージョンを管理することです。このフィボナッチ数列の要素を計算するコードは理解が容易なため、バージョン管理の説明に使います。
たとえば、あるフィボナッチ数列の要素を反復的に計算するバージョンを作る方法がわからない場合や、これまでのコードとは異なる計算方法を作りたい場合を考えます。これは簡単にできると思うかもしれませんが、遅かれ早かれ間違いを犯すことになります。しかし、コンパイル指示子を使うことで、間違いのリスクは大幅に減らすことができます。たとえば、計算方法を反復にするか再帰にするかを選択できるように計算部分を分離することが可能です。そのためには、以下のような形で指示子を使います。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define DEF_INTERACTIVE 05. //+----------------+ 06. #define def_Fibonacci_Element_Default 6 07. //+------------------------------------------------------------------+ 08. void OnStart(void) 09. { 10. Print("Result: ", Fibonacci()); 11. } 12. //+------------------------------------------------------------------+ 13. #ifndef DEF_INTERACTIVE 14. //+----------------+ 15. uint Fibonacci(uint arg = def_Fibonacci_Element_Default) 16. { 17. if (arg <= 1) 18. return arg; 19. 20. return Fibonacci(arg - 1) + Fibonacci(arg - 2); 21. } 22. //+----------------+ 23. #endif 24. //+------------------------------------------------------------------+ 25. #ifdef DEF_INTERACTIVE 26. //+----------------+ 27. uint Fibonacci(uint arg = def_Fibonacci_Element_Default) 28. { 29. uint v, i, c; 30. 31. for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2) 32. v += i; 33. 34. return (c == arg ? i : v); 35. } 36. //+----------------+ 37. #endif 38. //+------------------------------------------------------------------+
コード07
ここでは、最初の代替案を示し、何ができるのか見てみましょう。コード07の4行目では、定義を作成しています。この定義には必ずしも値を含める必要はなく、存在すること、つまりコンパイラが認識できる状態であれば十分です。次に10行目を見ると、フィボナッチ関数は1つだけです。しかしここで質問です。15行目と27行目のどちらの関数が使われるのでしょうか。最初は意味がわからないかもしれません。同じ名前の関数が2つ存在できるのか、と。はい、できますが、その話はまた別の機会にしましょう。ここでは、コード07の内容に注目します。
このような構造を見たことがないと、最初は理解するのが難しいかもしれません。ぱっと見では意味がないように思えます。しかし、コードをよく見てください。13行目では別のコンパイル指示子を使用しています。この指示子は、宣言された定義が存在するかどうかをチェックします。そして、#ifndefと#endifの間のコードは、その指示子が存在しない場合のみコンパイルされます。存在する場合はコンパイルされません。同様の仕組みが25行目でも見られます。ここでは指示子が存在する場合のみコードがコンパイルされます。つまり、4行目で定義した指示子に基づき、25~37行目のコードだけがコンパイルされるということです。一方で、13~23行目のコードは無視されます。
これで理解できましたか。実際に試してみましょう。よりわかりやすくするために、コード07に小さな一行を追加して、次のようにします。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define DEF_INTERACTIVE 05. //+----------------+ 06. #define def_Fibonacci_Element_Default 6 07. //+------------------------------------------------------------------+ 08. void OnStart(void) 09. { 10. Print("Result: ", Fibonacci()); 11. } 12. //+------------------------------------------------------------------+ 13. #ifndef DEF_INTERACTIVE 14. //+----------------+ 15. uint Fibonacci(uint arg = def_Fibonacci_Element_Default) 16. { 17. Print("Testing ", __LINE__); 18. if (arg <= 1) 19. return arg; 20. 21. return Fibonacci(arg - 1) + Fibonacci(arg - 2); 22. } 23. //+----------------+ 24. #endif 25. //+------------------------------------------------------------------+ 26. #ifdef DEF_INTERACTIVE 27. //+----------------+ 28. uint Fibonacci(uint arg = def_Fibonacci_Element_Default) 29. { 30. uint v, i, c; 31. 32. Print("Testing ", __LINE__); 33. for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2) 34. v += i; 35. 36. return (c == arg ? i : v); 37. } 38. //+----------------+ 39. #endif 40. //+------------------------------------------------------------------+
コード08
コード08を実行すると、次のようになります。

図03
ここで、4行目を以下のように変更します。
// #define DEF_INTERACTIVE 次に、コード08を再度コンパイルすると、結果は次のようになります。

図04
これで証明されました。正しく動作することが確認できます。一方のバージョンでは再帰計算を使い、もう一方のバージョンでは反復計算を使います。そして、どちらを使うかを選ぶには、コードの1行を書き換えるだけで十分です。このように指示子をコードに取り入れて使う実装は、とても面白いと思いませんか。しかし、話はそれだけではありません。さらに興味深いこともできます。実は、ここで示したコードは、もっとシンプルな形に整理し直すことができるのです。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define DEF_INTERACTIVE "Interactive" 05. //+----------------+ 06. #define def_Fibonacci_Element_Default 6 07. //+------------------------------------------------------------------+ 08. void OnStart(void) 09. { 10. Print("Result to ", 11. #ifdef DEF_INTERACTIVE 12. DEF_INTERACTIVE, " : " 13. #else 14. "Recursive : " 15. #endif 16. , Fibonacci()); 17. } 18. //+------------------------------------------------------------------+ 19. uint Fibonacci(uint arg = def_Fibonacci_Element_Default) 20. { 21. #ifdef DEF_INTERACTIVE 22. 23. uint v, i, c; 24. 25. for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2) 26. v += i; 27. 28. return (c == arg ? i : v); 29. 30. #else 31. 32. if (arg <= 1) 33. return arg; 34. 35. return Fibonacci(arg - 1) + Fibonacci(arg - 2); 36. 37. #endif 38. } 39. //+------------------------------------------------------------------+
コード09
コード09では、指示子をより整理された形で使っています。コード08と同様に、たった1行を書き換えるだけで複数の挙動を制御できるのです。では、以下の手順で進めます。まずは、上で示した通り、現状の4行目のままコードを実行します。実行結果は下記に示されています。

図05
コード08と同じように4行目を配置すると、コード09の実行結果は次のようになります。

図06
「すごいですね。コード09で何が起こったのか理解できなかったので、どうしてこんな結果になったのか教えてください。」もちろんです、読者の皆さん。まさにこれがこの記事が書かれた理由です。
コード09でおこなったことは、少ない手間でより多くのことができるということを示すための、ちょっとした遊びのようなものです。プログラマーがどのように考えるか理解するのは難しいと思う人も多いですが、実際にはそれほど難しくありません。優れたプログラマーは常に、作業を効率化し、パフォーマンスを向上させる方法を探しています。コード09でもまさにそれをおこなっていますが、少しクリエイティブな方法を使っています。
おそらく#ifdef指示子の基本的な考え方は理解できたと思いますが、さらに面白く使う方法を見てみましょう。以前の記事で扱ったif文は、#ifdefや#ifndef指示子でも同じように動作します。つまり、ブロック内のコードは実行されるかされないかのどちらかです。ただし、if文ではブロックは波括弧{}で囲まれますが、#ifdefや#ifndef指示子では、ブロックの終了を#endifで示します。必ずです。しかし、テストの過程で指示子を繰り返したくない場合があります。その場合は、#ifdefまたは#ifndefのブロック内で#else指示子を使うことができます。
if文で条件式が真か偽かによって実行されるブロックが切り替わるのと同様に、#ifdefや#ifndef指示子でも、定義の有無に応じて実行されるコードを切り替えることができます。つまり、if文の仕組みを理解していれば、同じような指示子を使ってコードの特定部分を選択的に実行させる方法も理解しやすくなります。
ここでは実際にはおこないませんが、これは可能であることを理解しておくとよいでしょう。では、コード自体に戻ります。4行目で何かを定義していることに注目してください。ここでは#ifdefや#ifndef指示子を使って自分のコードを柔軟に制御することができます。しかし、読者の中には、4行目でおこなっていることは6行目の操作と同じで、それが#ifdefや#ifndef指示子に干渉するのではと思う人もいるでしょう。答えは「いいえ」です。#ifdefや#ifndefは、定義が存在するかどうかを確認するだけだからです。実際には#if指示子も存在しますが、これはMQL5では使えず、CやC++で使用されます。#ifでは指示子に割り当てた値をチェックすることができます。しかし、言語仕様を簡単にするため、MQL5では#ifは含まれず、#ifdefと#ifndefのみが採用されています。
したがって、指示子に値を割り当てて、12行目のように名前付き定数として使うことも可能です。これにより、これ以降は指示子を通常の定数のように使用でき、コード09の仕組みを理解している読者にとっては非常に分かりやすくなります。さらに面白いこともできます。それは、コンパイル指示子を使ってデータを操作する方法を学ぶことで、日常的なプログラミングでも役立つ知識です。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define DEF_INTERACTIVE 05. //+----------------+ 06. #ifdef DEF_INTERACTIVE 07. #define def_OPERATION "Interactive" 08. #else 09. #define def_OPERATION "Recursive" 10. #endif 11. //+----------------+ 12. #define def_Fibonacci_Element_Default 11 13. //+----------------+ 14. #define def_MSG_TERMINAL "Result of " + def_OPERATION + " operation of element" 15. //+------------------------------------------------------------------+ 16. void OnStart(void) 17. { 18. Print(def_MSG_TERMINAL, " ", def_Fibonacci_Element_Default, " : ", Fibonacci()); 19. } 20. //+------------------------------------------------------------------+ 21. uint Fibonacci(uint arg = def_Fibonacci_Element_Default) 22. { 23. #ifdef DEF_INTERACTIVE 24. 25. uint v, i, c; 26. 27. for (c = 0, i = 0, v = 1; c < arg; i += v, c += 2) 28. v += i; 29. 30. return (c == arg ? i : v); 31. 32. #else 33. 34. if (arg <= 1) 35. return arg; 36. 37. return Fibonacci(arg - 1) + Fibonacci(arg - 2); 38. 39. #endif 40. } 41. //+------------------------------------------------------------------+
コード10
コード10は、一見すると非常に複雑で、何が起こっているのか理解せずに見ると難解に思えるかもしれません。しかし、コード10でおこなわれていることに新しい内容は何もありません。この記事で既に説明した内容ばかりです。ただし、この記事で示した内容を実際に手を動かして試していないと、少し混乱するかもしれません。多くの場合、これらのコードはアプリケーション内で利用でき、その目的は「何を変更すべきか」を示すことにあります。ですから、実際に使いながらここで示された細部を学ぶことができます。
では、コード10を実行すると何が起こるか見てみましょう。まずは、4行目で指定した指示子に実際に働いてもらいます。そのためには、上記の通りコードをコンパイルします。結果として次のようになります。

図07
次に、コード変更前に何が起こったのかを、以下のスニペットで理解してみましょう。これを理解しておくと、今後の作業がずっと簡単になります。画像07の出力がどのように生成されたかを理解したら、以下のスニペットの通りにコードを修正してください。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. // #define DEF_INTERACTIVE 05. //+----------------+ 06. #ifdef DEF_INTERACTIVE 07. #define def_OPERATION "Interactive" 08. #else 09. #define def_OPERATION "Recursive" 10. #endif 11. //+----------------+ 12. #define def_Fibonacci_Element_Default 11 13. //+----------------+ 14. #define def_MSG_TERMINAL "Result of " + def_OPERATION + " operation of element" 15. //+------------------------------------------------------------------+ . . .
コードスニペット10
注意してください。以下に示すスニペットは、コード10とまったく同じように見えてはいけません。一見ほとんど変化がないように見えますが、実際には変更が加えられています。この変更は4行目にあり、定義をコメントに変えたことです。コメントになったことで、コンパイラはこの行を無視します。そのため、定義は実行されていないように見えます。
一見するとコードにまったく影響がないように思えますが、この単純な事実が、前回のコードとは異なるコードをコンパイラに生成させます。新しいコードを実行すると、端末には以下のような結果が表示されます。

図08
これで、定義や#ifdef、#ifndef、#else、#endif指示子の使い方は理解できたと思います。しかし、#define指示子の使い方には、もう1つ残された用途があります。記事の冒頭で、この指示子には2つの目的があると述べたことを覚えていますか。1つ目は、この記事で説明した用途です。アプリケーション内のコードを使って実際に練習できます。
これにより、不要なグローバル変数を作成せずに済むほか、同じコードの異なるバージョンを簡単・迅速・効率的に分析し、実装できるようになります。これは初心者にとって非常に価値があることです。経験豊富なプログラマーにとっては少し当たり前のことですが、こうした使い方を自然に行うことで作業が非常に楽になります。CやC++の#if指示子があれば便利ですが、心配はいりません。現状でも十分に活用可能です。
2つ目の用途は、マクロを作成することです。ただし、マクロは解析に時間がかかるため、今回は含めません。今扱うと複雑になりすぎるからです。多くの人が想像するような単純なコードスニペットとは異なり、マクロは正しく使えば非常に便利ですが、誤解や誤用をするとコードが複雑で混乱しやすくなります。
この記事を締めくくる前に、最後に1つだけおまけを紹介します。これは最後まで読んでくれた皆さんへのおまけです。皆さんはすでに#define指示子を使った実験を始めたくなっているはずです。
これは、MQL5内で複数の簡単なコマンドを、コードを変更せずに作成する方法です。これにより、少しだけ意味のある操作が可能になります。マクロの話が終わった後に詳しく解説しますが、ここでは次のトピックのプレビューとして紹介しておきます。
気づいていないかもしれませんが、#define指示子を使うと、コンパイラに「あるテキストを別のテキストに置き換える」と伝えることになります。この仕組みを利用すれば、コードの書き方を変えずに代替構文を作ることができます。
これを説明するために、以下のコードを考えてみましょう。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define BEGIN_PROC { 05. #define END_PROC } 06. #define RETURN return 07. #define ENTER_POINT void OnStart (void) 08. #define Z_SET_NUMBERS long 09. #define EQUAL == 10. #define IS = 11. #define MINOR < 12. #define OR || 13. #define MORE += 14. //+------------------------------------------------------------------+ 15. #define DEF_INTERACTIVE 16. //+----------------+ 17. #ifdef DEF_INTERACTIVE 18. #define def_OPERATION "Interactive" 19. #else 20. #define def_OPERATION "Recursive" 21. #endif 22. //+----------------+ 23. #define def_Fibonacci_Element_Default 11 24. //+----------------+ 25. #define def_MSG_TERMINAL "Result of " + def_OPERATION + " operation of element" 26. //+------------------------------------------------------------------+ 27. ENTER_POINT BEGIN_PROC 28. Print(def_MSG_TERMINAL, " ", def_Fibonacci_Element_Default, " : ", Fibonacci()); 29. END_PROC 30. //+------------------------------------------------------------------+ 31. Z_SET_NUMBERS Fibonacci(Z_SET_NUMBERS arg IS def_Fibonacci_Element_Default) 32. BEGIN_PROC 33. #ifdef DEF_INTERACTIVE 34. 35. Z_SET_NUMBERS v, i, c; 36. 37. for (c IS 0, i IS 0, v IS 1; c MINOR arg; i MORE v, c MORE 2) 38. v MORE i; 39. 40. RETURN (c EQUAL arg ? i : v); 41. 42. #else 43. 44. if ((arg EQUAL 1) OR (arg MINOR 1)) 45. RETURN arg; 46. 47. RETURN Fibonacci(arg - 1) + Fibonacci(arg - 2); 48. 49. #endif 50. END_PROC 51. //+------------------------------------------------------------------+
コード11
このコードはコード10と同じように動作します。しかしここでは、多くの人が通常使う標準的な書き方とはまったく異なるものを作成しています。それでも、コンパイラはコード11を問題なく理解し、コード10と同じ結果を出力します。
見た目で、これはMQL5ではないと思われるかもしれません。しかし、この記事で学んだ知識を踏まえてよく見てみると、確かに異なる書き方ではありますが、コード11は純粋でシンプルなMQL5です。単に書き方が違うだけで、数学的な表現よりも自然言語に近い形で書かれているのです。
4~13行目で定義を追加した後、残りのコード、特に27行目以降は見た目が大きく変わっています。これによって実行可能ファイルが作成されないだろうと考える方も多いかもしれません。しかし、驚くことに、このコードはちゃんと動作します。
最終的な考察
この記事では、多くの人が奇妙でまったく脈絡がないと感じるであろうことを扱いました。しかし、それらを正しく活用すれば、学習はより楽しく、さらに興味深いものになるでしょうここで示す内容を基に、かなり面白いものを構築することも可能です。MQL5言語の構文もより深く理解できるようになるでしょう。ここで紹介した内容は、十分な学習と実践を必要とするため、この記事はこのあたりで締めくくります。次回の記事では、#define指示子のもう1つの使い方について解説します。ですので、今日の記事の内容をしっかり練習・復習しておき、次の記事で混乱しないようにしてください。
アプリケーション内には、この記事で紹介した5つのコードがすべて含まれています。ぜひ実際に触れて、楽しんで学んでください。
MetaQuotes Ltdによりポルトガル語から翻訳されました。
元の記事: https://www.mql5.com/pt/articles/15573
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
時間、価格、ボリュームに基づいた3Dバーの作成
リプレイシステムの開発(第76回):新しいChart Trade(III)
マーケットプロファイルインジケーター(第2回):キャンバス上の最適化と描画
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索