
初級から中級へ:変数(I)
はじめに
ここで提供される資料は教育目的のみに使用されるべきです。このアプリケーションは、提示された概念を学習し習得する以外の目的で閲覧しないでください。
この記事の目的は、さまざまなアクティビティにおけるMQL5の概念とその使用法を説明し、支援することです。インジケーター、スクリプト、エキスパートアドバイザー(EA)の作成だけに焦点を当てているわけではなく、ここでの主な目的は知識を伝えることです。
皆さんの多くは、すでに他の記事を通じて私のことをご存じかもしれません。しかし、これまでの記事はある程度の経験を持つプログラマーを対象としていました。多くの方がこのテーマについてさらに詳しく知りたいと考えていることから、今回は基本的な説明を提供する別の連載を作成するのが興味深いと考えました。これは、誰もがすでに知っているトピックを繰り返し議論し、記事を退屈で無意味なものにするということではありません。それどころか、本当に興味深く、ワクワクするような内容を提供できるよう努めます。しかし、すでに上級者向けの記事が多数公開を待っているため、この新しい連載を通じて、私の知識を共有する機会を得ることができました。基礎レベルから中級レベルへと進むための疑問を解決していきます。それでも、MQL5の概念を学ぶことは、他のプログラミング言語を学習しようとしている人にとっても役立つでしょう。
この記事では、プログラミング全般の最も基本的で基礎的な部分から始めます。今日は変数についてお話します。内容を明確に区別しながら、新しいトピックとして説明を進めていきます。
変数:プログラミングの基礎
多くの人は、コンピュータプログラムは関数とメソッドに基づいて構築されていると誤解しています。しかし、この仮定は間違っています。コンピュータプログラムは主に変数に基づいて構築されます。その他の目的のためにプログラムが作成または提供されることはありません。目的は常に、変数を認識させ、使用可能にすることです。
もちろん、この考え方は読者にとって奇妙に思えるかもしれません。特に、すべてのコースや本が関数とメソッドに焦点を当て、これらがプログラミングの基本原則であると示唆している場合にはなおさらです。しかし、私は論争や意見の相違を引き起こすためにここにいるわけではありません。私は貢献するためにここにいます。関数やメソッドと呼ばれる用語は非常に重要ですが、それらは計算された変数の一種にすぎません。これは次の記事でさらに明らかになります。
この最初の記事では、変数の概念を紹介したいと思います。はい、変数間には違いがあります。それぞれの型が何であるか、そしてそれぞれを最適に使用する方法を知ることは、よく書かれたプログラムと特定のタスクを実行する単純なプログラムの違いです。
まず、値を変更できる変数と値を変更できない変数の2種類の変数があるという事実から始めましょう。2番目のケースでは、このような変数は定数と呼ばれます。ただし、ユーザーモードで実行されるコンピュータプログラムは常にRAM内に存在するため、定数という言葉はあまり適切ではありません。その理由は、値がRAM内にあるため、何らかの方法で変更される可能性があるからです。制限を回避するためにCRACKを試したことがない人はいるでしょうか。多くの場合、CRACKはRAM内またはディスク上の実行可能ファイルの一部に直接作用します。
ディスク上で介入が発生すると、CRACKは定数に対して直接作用します。これは、ディスク上に表示される値が時間の経過とともに変化しないためです。それらは変化しない傾向があります。したがって、CRACKは実際には一定であるように見える値に対して作用します。しかし、CRACKがそれを変更できるため、可変です。ただし、RAMの場合、一部の部分は定数と見なされる可能性があり、そのような値を変更しようとするとエラーと見なされます。プロセッサは、必要な対策を講じるためにオペレーティングシステムに通知する必要があります。したがって、最初に注意すべきことは、変数は定数である場合もそうでない場合もあるということです。値がRAM内にある場合、アプリケーションは何が定数で何が変数かを判断できます。その他のシナリオでは、変数を定数として扱うべきではありません。
この小さな説明をおこなった後、次のステップに進み、同じ概念を実際に実装することができます。少しずつ始めましょう。いくつかの概念は分離するのが非常に難しいため、最初はすべてが非常に複雑に見えるかもしれません。親愛なる読者の皆様、しばらくお待ちください。すぐに自信がつき、さまざまなことがやりやすくなるでしょう。以下に示す内容から始めて、一つずつ勉強してみましょう。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. int value1; 07. char value2; 08. long value3; 09. 10. value1 = 10; 11. value2 = 5; 12. Print("Result: ", value1 + value2 + value3); 13. } 14. //+------------------------------------------------------------------+
コード01
このコードは非常にシンプルで基本的なものです。ここでは、06行目、07行目、08行目に3つの変数宣言があります。まだ型について心配する必要はありません。これについては別の記事で詳しくお話しします。今は、説明されている内容に注意を払ってください。ここでは3つの変数が宣言されていますが、そのうち2つは実際には一時定数であり、もう1つは参照されていないか、正しい用語を使用すると初期化されていません。多くの場合、このコードは特定の結果を生成すると考えるかもしれません。しかし、さまざまな考え方がどのように機能するかを理解しなかったり、無視したりすると、作業全体が危険にさらされる可能性があります。
これを理解するには、コンパイラの出力を見てみましょう。MetaEditorを使用すると、結果は図01のようになります。
図01:
私の場合、コードを編集するために別のツールを使用していますが、アプリケーションの作成にはMetaTrader 5コンパイラも使用しています。出力は以下の通りです。
図02
どちらの場合もコンパイラは警告を返します。そして、警告が表示された際は注意が必要です。これは、警告がアプリケーションの予期しない動作を示唆していることが多いためです。コンパイラの出力に関連するポイントを示す必要がある場合は、図02に示すモデルに従って表示してください。ただし、その結果はMetaEditorで表示されるメッセージと非常に似たものになります。今後の記事もすべて理解できるよう、注意深くお読みください。
さて、ここでこのコンパイラの警告を無視したとしましょう。無視できる警告もあれば、無視すべきでない警告もあります。それでも、MetaTrader 5でコンパイルしたこのアプリケーションをリリースすると決めた場合、どのような結果が得られると予想されるでしょうか。10行目で変数value1が10に等しいと述べており、11行目で変数value2が5に等しいと述べられています。そのため、おそらく戻り値は15になると予想されるでしょう。したがって、12行目が実行されると、期待される結果はこれら2つの値の合計になるのでしょうか。見てみましょう。結果を以下に示します。
図03
では、ここで何が起こったのでしょうか。本当にそんなことがあり得るのでしょうか。はい、読者の皆さん、これは実際に可能です。ご自身で確かめることもできます。しかし、出力される値が15になると明らかに予想していたため、疑問を持つのはもっともなことです。では、なぜこのゼロ値が生成されるのでしょうか。その原因はvalue3という変数にあります。value3は計算に含まれていますが、適切に初期化されていないため、任意の値を持つ可能性があります。プログラミングでは、このような未初期化の値を「ジャンク」と呼びます。このジャンク値が原因で、最終的な計算結果が不正確になってしまうのです。
この問題を解決する方法は2つあります。1つ目は、value3を正しく初期化すること。2つ目は、12行目の計算からvalue3を削除することです。今回は、目標とする結果が15であるため、2つ目の方法を選択します。したがって、コード01の12行目を以下のように変更します。
Print("Result: ", value1 + value2);
この場合、コードをコンパイルしようとすると、コンパイラは次の警告を生成します。
図04
この警告は先ほど見たものと異なることに注意してください。分析し、この特定のケースでは無視できると判断します。MetaTrader 5でアプリケーションを起動すると、以下に示す結果が得られます。
図05
うまくいきました。まさに期待通りの結果が出ました。それでは、ソースコード01に戻り、基本的な概念を理解しましょう。前述のように、変数value1とvalue2は一時的な定数として考えることができます。そして、実際にそう考えるべきです。なぜでしょうか。その理由は、コード全体を通して値が変化せず、常に同じ値を持つおいうことです。しかし、ここで1つ問題があり、状況が少し面白くなります。
value1、value2、value3は変数ですが、プログラマーとしてこれを変更することができます。こうすることで、コードの記述中に間違いを犯したとしても、変数に割り当てられた値が変更されないように保証することができます。より複雑で入り組んだコードを扱う経験豊富なプログラマーの多くは、このような制御を可能にする言語を好みます。このような場合、プログラマーは次のように考えることができます。「コンパイラよ、この変数は通常の変数として扱われるべきではない。定数として扱われるべきだ。」コンパイラは、この指示に従い、該当の変数を定数として格納するようになります。MQL5でこの制御を実現するには、予約語(キーワード)を使用します。したがって、同じ結果を出力するコードを、別の方法で記述することも可能です。しかし、その前にもう少し別の点について見ていきましょう。その結果、次のコードが作成されます。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. const int value1; 07. char value2; 08. 09. value1 = 10; 10. value2 = 5; 11. Print("Result: ", value1 + value2); 12. } 13. //+------------------------------------------------------------------+
コード02
コード02をコンパイルしようとすると、以下に示すエラーが発生し、コンパイラがコンパイルしてくれしません。
図06
ここの場合、2つのエラーが発生しました。1つは6行目で、もう1つは9行目で発生しています。では、これらのエラーに対処していきましょう。最も重要なのは、エラーを修正することではなく、エラーの意味を理解することです。なぜなら、エラーとして表示されるものが、必ずしも本当のエラーとは限らないからです。多くの場合、それは「実行すべきでない操作をしようとしている」ことを警告する単なるコンパイラ警告である可能性があります。実際、9行目のエラーはまさにこのケースに該当します。
MQL5のプログラミングでは、変数を定数として定義すると、その値を変更しようとしたときに重大なエラーが発生します。ここで重要なのは、まだアプリケーションを起動していない段階で、MQL5コンパイラがすでに警告を出しているという点です。これは、実行中に重大なエラーが発生する可能性を事前に知らせてくれているのです。そのため、コード02はコンパイルできません。このエラーが発生する理由は、変数value1を変数ではなく定数として扱うようにコンパイラに指示したためです。具体的には、コード02の6行目で、変数の前にconstというキーワードを付けたことが原因です。経験の浅いプログラマーの多くは、このエラーを解消するために、6行目の宣言からconstを削除することを選びます。これはエラーではありませんが、その結果、コンパイラの支援が失われ、value1が定数として扱われなくなるという問題が発生します。その影響で、コードが期待通りに動作しなくなり、特定のタイミングで誤った値が生成されるなど、アプリケーションの動作に予期しない影響を及ぼす可能性があります。
しかし、もう1つの問題があります。これが、画像06に示されている6行目のエラーの原因です。この問題により、コード01の12行目でvalue3が使用された際に、誤った値が生成されてしまいました。
この問題の本質は、変数の初期化にあります。プログラミング言語によっては、変数を宣言するとデフォルトでゼロに初期化されるものもあります。しかし、MQL5では「定数」として扱われる変数は、明示的に値を定義しなければならない」というルールがあります。もし、コンパイラが未初期化の変数に暗黙的にゼロを割り当てた場合、最終的なアプリケーションに悪影響を及ぼす可能性があります。これはまさに、コード02に示されている詳細によるものです。つまり、コンパイラが初期化されていないすべての変数にゼロの値を自動的に割り当てた場合、11行目の演算の結果は完全に間違ったものになる可能性があります。そして、プログラマーであるあなたは、その理由を解明するために多くの時間を費やすことになるでしょう。したがって、エラーは必ずしもエラーとは限らず、同様に、コンパイラの警告も必ずしも修正しなければならないわけではありません。場合によっては、それらが示す情報の一部を無視することもあります。
さて、これで問題は解決しました。では、コード02をコンパイルできるようにするには、どのように記述すればよいでしょうか。もし、正しく動作させたいのであれば、以下のようにコードを修正する必要があります。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. const int value1 = 10; 07. char value2; 08. 09. value2 = 5; 10. Print("Result: ", value1 + value2); 11. } 12. //+------------------------------------------------------------------+
コード03
これですべて正しいです。このコード03はコンパイルされます。結果は画像05に示されています。しかし、なぜ著者が示すとおりにプログラムするべきなのかと、疑問に思うかもしれません。以下のコードのように、他のコードを使用する方が簡単ではないでしょうか。
1. #property copyright "Daniel Jose" 2. //+------------------------------------------------------------------+ 3. void OnStart(void) 4. { 5. Print("Result: ", 10 + 5); 6. } 7. //+------------------------------------------------------------------+
コード04
まあ、定数だけを扱っているので、コード04のようなものをプログラミングするのが実際には最も簡単な方法である場合もあります。ただし、このアプローチは必ずしも望ましいとは限りません。これは、数値定数が「コールド」(意味を持たない)であり、なぜその値が存在するのかという情報を持たないためです。コード04を見て、なぜ値10と5を使用しているのか説明できますか。おそらく、最初は理由が分からず、因数分解などを試して意味を探ろうとするかもしれません。このような場合、より適切な型の変数を使用するのが良いでしょう。これについては、別の記事で詳しく説明します。しかし、もし適切な変数を使わなければ、なぜそのような計算がおこなわれているのか疑問に思うことになり、最終的に自分のコードを理解するのが難しくなる可能性があります。
このため、多くのプログラマーは、使用するプログラミング言語の特定のリソースを活用します。今回のように、より効率的なプログラミング方法を説明するために話を進めている場合、この時点では変数を使用するのが最善の選択です。なぜなら、変数には適切な名前を付けることができるため、たとえその変数が定数として実装されていたとしても、時間が経った後でも、その値の目的を理解しやすくなるからです。
これで、最初のポイントは適切に説明できたと思います。これで、読者は、変数が定数であると述べる場合とそうでない場合がある理由、また、そのような宣言がお気に入りの変数にどのような影響を与えるかを理解できるようになったのではないでしょうか。このトピックについては、別の機会にさらに詳しく説明する予定です。そのため、今回は変数の基本的な概念を超えずに、次のポイントへ進むことにしましょう。
有効期間と可視性
経験豊富なプログラマーでさえ困惑することが多い質問の1つに、変数の有効期間と可視範囲があります。一見すると、これらは別々の概念のように思えますが、実際には密接に関連しているため、一緒に考えることができます。可視範囲の問題は非常に奥が深く、アプリケーションの設計方法に大きな影響を与える重要な要素です。そのため、詳細な解説は今後の記事で取り上げる予定ですが、適切なタイミングでしっかりと説明していきます。
ここでは、最も重要なこと、つまり変数がいつ「生まれる」のか、どこで「死ぬ」のか、そしてその「死」を防ぐことができるかどうかに焦点を当てます。
物事を複雑にしないために、いくつか考慮してみましょう。最初の、そして最も単純なものは次のとおりです。
変数は宣言された瞬間に誕生します。
この記述は明白に思えますが、変数の可視性の観点から見ると、状況は少しはっきりしなくなります。このトピックを詳しく調べる前に、簡単なケースを見てみましょう。このために次のコードを使用します。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. const int value1 = 10; 07. int value2; 08. 09. value2 = 5; 10. { 11. int value2; 12. 13. value2 = 8; 14. 15. Print("Result #01: ", value1 + value2); 16. } 17. Print("Result #02: ", value1 + value2); 18. } 19. //+------------------------------------------------------------------+
コード05
コード05を実行する前に、行15と17が実行されたときにターミナルにどのような値が印刷されるかを答えていただけますか。このコードはコンパイルすらされないのでしょうか。疑い始める前に、はい、このコードはコンパイルされ、実行されます。ただし、ここで理解しておかなければならない点が1つあります。これは、この旅を続けるうちに理解することになるいくつかのことのうちの最初のものです。
コード05を実行すると、下の画像のような結果が表示されます。
図07
さて、もう一度お尋ねします。これらの結果を理解できましたか。これは、私たちが実際に何を行えるかを示す一例にすぎません。多くのプログラマー、特にかなり経験豊富なプログラマーでさえ、同じコードブロック内で同じ名前の変数を使うことを避けます。ですが、ここでは1つのコードブロックではなく、2つのコードブロックを使用しています。では、なぜこれが可能なのでしょうか。コード05には本当に2つのコードブロックが存在するのでしょうか。親愛なる読者の皆様、私はまだ正気を失っていません。重要なのは、私たちがここで触れたのは、別の機会に明らかにされるトピックであるということです。しかし、MQL5では、他のCやC++などの言語と同様に、各コードブロックは開き括弧「{」で始まり、閉じ括弧「}」で終わります。
これら2つの括弧の間にあるすべてが、コードブロックとみなされます。ループなど、コードブロックを定義する他の方法もありますが、この括弧を使う方法が最も直感的で理解しやすいです。したがって、このコードでは、ブロックは5行目から始まり、18行目で終わります。別のブロックは10行目から16行目まで続いています。このように理解することで、一度に2つの問題を解決できます。
1つ目は、コード内で宣言された変数は、その変数が宣言されたブロックが終了するまで有効であり、そのブロックが終了すると有効期間が終了するということです。例外があるかもしれませんが、まだその点を気にする必要はありません。今は、このシンプルな概念をしっかり理解しましょう。2つ目のポイントは、ブロックの外部で宣言された変数は、そのブロック内から見ると「グローバル変数」として扱われるということです。
この2番目の概念は少し難解かもしれませんが、段階的に説明していきましょう。まず、コード05が画像07に示された出力を生成する理由を見てみましょう。上記のコードから、7行目と11行目で同じ名前の変数が宣言されていることがわかります。9行目と13行目では、理論的には同じ変数に異なる値を割り当てていますが、コードをよく見ると、それらが異なるコードブロック内にあることがわかります。1つは1つのブロック内にあり、もう1つは別のブロック内にあります。
簡単に言うと、7行目でvalue2を宣言するブロックはグローバルです。したがって、7行目で宣言されたvalue2はグローバル変数です。一方、11行目で宣言されたvalue2はローカル変数です。ブロックの外から見ると、11行目の変数はローカルですが、10行目から始まるブロック内では、11行目で宣言された変数はそのコンテキストにおいてグローバル変数として扱われます。
これは混乱するかもしれませんので、少し立ち止まって考えてみましょう。この概念を理解することは非常に重要です。なぜなら、常に異なる名前を使う必要がないからです。同じブロック内の変数には異なる名前を使うことがコンパイラによって要求されますが、異なるブロック内にある場合、コンパイラは同じ名前の異なる変数だと仮定します。
では、最終結果でなぜ二つの異なる値が得られるのでしょうか。理由は簡単です:ローカル変数は、グローバル変数と同じ名前を持っている場合、変数の型に関係なく優先されます。名前が同じであれば、コンパイラはローカル変数を使用することを優先します。ただし、他の言語では異なる手法が使われることもあるので注意が必要です。ここで見てきたのはMQL5に特有の動作です。
したがって、次の事実は簡単に理解できます。13行目でvalue2が8であると言った場合、それはグローバルスコープのvalue2(9行目で定義された5)の値を削除するわけではありません。このため、15行目でvalue1とvalue2を足し合わせて結果を表示した場合、最初のケースでは18が得られます。17行目で同じ計算をおこなうと、結果は15になります。
ただし、次のコードを使用して同様のことを実行しようとすると、
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. const int value1 = 10; 07. int value2; 08. 09. value2 = 5; 10. 11. int value2; 12. 13. value2 = 8; 14. 15. Print("Result #01: ", value1 + value2); 16. 17. Print("Result #02: ", value1 + value2); 18. } 19. //+------------------------------------------------------------------+
コード06
コンパイラは下の図08のようなエラーを出します。
図08
これは、コード05とは異なり、コード06は5行目から始まり18行目で終わる1つのブロックで構成されているためです。したがって、コードを書くときや、他のプログラマーが書いた内容を理解しようとするときは、コードの変更が失敗した理由に注意してください。
ただし、少し異なる状況に遭遇する可能性があります。以下のコードに例を示します。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. int value2; 05. //+------------------------------------------------------------------+ 06. void OnStart(void) 07. { 08. const int value1 = 10; 09. 10. value2 = 5; 11. 12. int value2; 13. 14. value2 = 8; 15. 16. Print("Result #01: ", value1 + value2); 17. 18. Print("Result #02: ", value1 + value2); 19. } 20. //+------------------------------------------------------------------+
コード07
この場合、コンパイラは図09に示すようなメッセージを出力します。
図09
画像09に表示されたメッセージを見て、私たちの目標が画像07に示されたような結果を得ることだとすると、コード07についてどのような対応を取るべきか考えてみましょう。親愛なる読者の皆様、ここから状況が格段に複雑になります。その理由は、4行目で行われたvalue2の宣言は、アプリケーションが終了するか、実行が停止するまで「死なない」ということです。このため、このような場合には、ローカルスコープを持つ変数と、同じ名前のグローバルスコープの変数を一緒に使用することはできません。
一般的に、グローバル変数やその値自体は避けるべき問題です。これを完全に避けることは難しいこともありますが、そのような状況が発生した場合、または他に選択肢がない場合は、変数に異なる名前を付けるべきです。私を含む多くのプログラマーは、グローバル変数を区別するために、プログラムの生涯にわたって存在するグローバル変数と、特定のブロックに属するローカル変数やグローバル変数を区別するために、接頭辞や接尾辞を追加することがよくあります。
最終的な考察
第一回の記事はここで終了します。この記事では、初心者やカジュアルなプログラマーの皆さんに、MQL5をより正確かつ生産的に扱う方法を説明しました。この記事の目的は、プロのプログラマーとしての私の経験を少し皆さんと共有し、自分自身のコードを簡単かつ効果的に、無駄な手間をかけずに作成する方法を示すことです。
プログラミングは美しく、忘れられないものです。正しいアプローチと、堅実で一貫した知識があれば、将来必ず良い結果が得られるでしょう。したがって、次回の記事では引き続き変数を扱い、ここで紹介した方法論をさらに深掘りしていきます。楽しんでいただけたなら嬉しいです。もし、より複雑な内容にも触れたい、そして同様の方法論をさらに学びたいという方は、私の別のプロファイルで公開している他の記事もぜひご覧ください。
MetaQuotes Ltdによりポルトガル語から翻訳されました。
元の記事: https://www.mql5.com/pt/articles/15301





- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索