
初級から中級へ:変数(II)
はじめに
ここで提示される資料は教育目的のみに使用されます。いかなる状況においても、提示された概念を学習し習得する以外の目的でアプリケーションを閲覧することは避けてください。
前回の「初級から中級へ:変数(I)」では、変数とそれに関連するいくつかの側面について説明を始めました。例えば、変数を定数に変換する方法について触れました。また、変数の有効期間や可視性についても話しました。
この記事では、読者が前回の資料をしっかり理解しているという前提で、さらにこのトピックを進めていきます。変数の有効期間と可視性について話すときに、初心者には少し理解が難しい部分があることに気づくことがあります。その理由は、多くの場合、グローバル変数による不便を避けたいと考えるからです。変数を単一のコードブロック内だけで有効にしたいと思うわけです。しかし、ここが複雑なところで、ブロックが終了した後でも変数の値が消えたり失われたりしないようにしたいという問題があります。
この状況は、プロを目指す初心者を含む多くのプログラマーにとって、非常に混乱を招くものの一つです。その理由は、多くのプログラミング言語には、変数の値をメモリに保持できる仕組みがあることに気づいていないことです。この複雑さは、Pythonなどの一般的なスクリプト言語がこのような仕組みを採用していないことに起因していると思われます。これにより、Pythonに慣れたプログラマーはこの概念を理解するのが非常に難しくなります。というのも、変数はそれが属するブロックが終了しても、必ずしもその値を失うわけではなく、むしろ値を忘れることはないからです。
一方、C/C++プログラマーは、この概念に非常に精通しており、経験の中で深く根付いているため、一般的にこの考え方を理解しています。MQL5はこれらの言語に似ており、同じ原則を採用しています。この機能は非常に有益であると私は考えています。もしこの機能がなければ、開発者はアプリケーションのスコープ内でグローバル変数に頼ることになり、しばしば大きな不便を引き起こすことになります。
さて、この仕組みがどのように機能するのかを説明するために、新しいトピックを始めましょう。
static変数
static変数は最も強力な概念の1つでありながら、同時に多くの初心者プログラマーにとって理解が難しい概念の1つでもあります。一見すると、直感に反しているように感じられることが多いですが、これらを学び、その仕組みを理解し始めると、特にどのような場面でどのように適用できるかがわかるようになり、その有用性を深く理解するようになるかもしれません。static変数は、そうでなければ解決に多くの労力を必要とする問題を解決する手助けとなります。
まずは比較的単純なケースから始めましょう。まだいくつかのプログラミング概念には触れていませんが、今は変数にだけ焦点を当てることをお勧めします。他のことはすべて忘れてください。変数に何が起こっているのかを理解することが、ここで最も重要なポイントになります。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. uchar counter = 0; 07. ulong value = 1; 08. 09. Print("Factorial of ", counter, " => ", value); 10. counter = counter + 1; 11. value = value * counter; 12. Print("Factorial of ", counter, " => ", value); 13. counter = counter + 1; 14. value = value * counter; 15. Print("Factorial of ", counter, " => ", value); 16. counter = counter + 1; 17. value = value * counter; 18. Print("Factorial of ", counter, " => ", value); 19. counter = counter + 1; 20. value = value * counter; 21. Print("Factorial of ", counter, " => ", value); 22. } 23. //+------------------------------------------------------------------+
コード01
ここでループを使用できることは理解しています。ただし、プログラミングの学習をゼロから始めることを考えてみましょう。あなたはその分野について全く知識がなく、これまで学んだことはすべて前の記事に基づいています。それを踏まえて、最初から始める必要があります。最初は少し複雑に感じるかもしれませんが、すぐに状況は良くなります。
ここで重要なのは、次の点です。MetaTrader5ターミナルでコード01を実行すると、結果は以下のようになります。
図01
ご覧のとおり、動作します。ここでおこなっているのは、数値の階乗を計算することです。ただし、単純に計算して結果を返すのではなく、分解して段階的に計算をおこなっています。このアプローチは、私が伝えたいポイントを理解するために不可欠です。
さて、読者の皆様、次のことを考えてみてください。6行目で変数を作成し、初期化しています。この変数は現在計算中の階乗を追跡します。そして、7行目で別の変数を宣言し、適切な値で初期化します。そして、0の階乗は1、1の階乗も1であることはご存知でしょう。この知識は、プログラミングにおいて数学的な問題を解決するために非常に重要です。多くの人が「プログラミングはランダムなコマンドを入力して、コンピューターにそれを実行させることだ」と誤解していますが、実際のところ、プログラミングの本質は、数式を機械が理解できるコードに変換することです。したがって、優れたプログラマーになるためには、数学をしっかり理解することが欠かせません。
話を戻すと、9行目、12行目、15行目、18行目、21行目で結果を人間が読める形で出力しています。これらの各ステップの間に、次の値を計算する方法をマシンに指示しています。ここで重要なのは、現在の数値の階乗を計算するたびに、前回の階乗の値を使って計算しているということです。つまり、変数counterとvalueは最初は定数のように見えますが、各計算ごとにその定数値が変わり、実質的に新しい定数となっていきます。この概念は、プログラマーとしてのキャリアにおいて非常に重要で、心に留めておくべき基本的なポイントです。
これで同じ理解に達したので、コードの行数を減らして物事を簡素化してみましょう。これを実現するためには、後で詳しく説明する別の概念を使う必要がありますが、この段階ではそれが必要です。その概念とは「ルーチン」です。
ルーチンとは、本質的には私たちが指示した際にマシンが実行するタスクです。これは、繰り返しおこなう面倒なタスクであり、毎回コードとして書き直す必要がないという意味で、効率的に扱うことができます。これは、コード01のいくつかの行で発生する処理に似ています。ルーチンには主に「関数」と「メソッド」の2種類がありますが、static変数について説明するためには、より理解しやすい方法として「メソッド」を使用します。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. uchar counter = 0; 05. ulong value = 1; 06. //+------------------------------------------------------------------+ 07. void OnStart(void) 08. { 09. Factorial(); 10. Factorial(); 11. Factorial(); 12. Factorial(); 13. Factorial(); 14. } 15. //+------------------------------------------------------------------+ 16. void Factorial(void) 17. { 18. Print("Factorial of ", counter, " => ", value); 19. counter = counter + 1; 20. value = value * counter; 21. } 22. //+------------------------------------------------------------------+
コード02
コード02の方がずっと理解しやすいことに注意してください。一見すると、0から4までの階乗を計算していることがわかります。これは、コードのメインブロック(OnStartメソッド)内でFactorialメソッドが4回呼び出されているためです。先ほども述べたように、この点については後で詳しく説明しますが、今は変数の理解に焦点を当てましょう。
コード01とコード02には見た目の違いがありますが、どちらも結果として図01を出力することに注意してください。ただし、コード02では、コード実行時に表示され、終了時にのみ消える2つのグローバル変数を使用しています。ある瞬間から次の瞬間まで、これらの変数は、それらにアクセスできる任意のメソッドや関数によって変更される可能性があります。たとえその変更がコードの実装中に私たちのミスや見落としによっておこなわれたとしても、複雑なコードではこれは珍しいことではありません。
これらの変数はグローバルで、特定のブロックに属していないにもかかわらず、値は初期化する必要があります。ご覧の通り、変数は宣言と同時に初期化されます。しかし、これはいつでも実行できるため、値の変化を追跡するのが難しくなります。ですが、ここでは簡潔にするため、宣言時にすぐに初期化をおこなっています。この時点で、ある正当な疑問が浮かぶかもしれません。もし複雑なものを設計していて、コーディング中にグローバル変数の値を誤って変更してしまうことが心配なら、変数counterとvalueをFactorialメソッド内で宣言した方が良いのではないか、ということです。こうすることで、これらの変数を誤って変更してしまうリスクを避けることができます。
はい、親愛なる読者の皆さん、最適なアプローチは、counterとvalueをグローバルスコープから削除し、それらをFactorialメソッド内で宣言することです。これによって、コード内で変数をより適切に制御できるようになります。それでは、この調整をおこない、何が起こるか見てみましょう。一見すると、コード01とコード02の両方が意図した通りに動作しているように見えます。そして、それがうまくいけば、私たちは正しい方向に進んでいるということになります。提案された変更を実装した後、更新されたコードは次のようになります。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. Factorial(); 07. Factorial(); 08. Factorial(); 09. Factorial(); 10. Factorial(); 11. } 12. //+------------------------------------------------------------------+ 13. void Factorial(void) 14. { 15. uchar counter = 0; 16. ulong value = 1; 17. 18. Print("Factorial of ", counter, " => ", value); 19. counter = counter + 1; 20. value = value * counter; 21. } 22. //+------------------------------------------------------------------+
コード03
素晴らしいです。これで、コード内のグローバル変数の問題は解決しました。私たちのプログラムは確かにずっと整理されています。しかし、結果はどうでしょうか。まだ0から4までの階乗を計算しているのでしょうか。見てみましょう。コードをコンパイルしてMetaTrader5ターミナルで実行すると、次の結果が表示されます。
図02
待って...うまくいきませんでした。でも、なぜでしょうか。以前はうまくいっていたのに。最初に見ると、コードを5回実行しているように見えるので、予想通りの結果が出ると思っていました。しかし、どうやら常に同じ値を指しているようです。うーん、少し考えさせてください。少々お待ちを。
ああ、分かりました。メインブロック(OnStartメソッド)内で呼び出しが1回実行されるたびに、変数counterとvalueが再宣言されており、さらにその都度、以前にコードで定義された初期値に再初期化されてしまっているのです。それがうまくいかなかった理由です。これにより、変数の有効期間の概念を理解することができました。しかし、まだ疑問が残ります。もし、15行目と16行目で値を初期化しなかったらどうなるのでしょうか。それならうまくいくかもしれません。いいえ、親愛なる読者の皆様、それを試すことはできますが、前回の記事で説明した問題に遭遇することになります。
では、代わりにメソッドに値を渡すことはできるでしょうか。それならうまくいきます。はい、この場合は問題を解決することができます。しかし、そのアプローチは時期尚早です。プロシージャや関数間で値を渡す方法についてはまだ説明していないため、その方法はまだ使用できません。コード03を動作させ、図02のような奇妙な結果ではなく、図01と同じ出力を得るために別の方法を考え出す必要があります。諦めますか。
もしそうなら、あなたのフラストレーションに終止符を打つ時が来ました。この問題を「魔法の修正」に頼らず、変数counterとvalueをFactorialプロシージャ内に保持したままで解決する方法があるのです。それは、static変数と呼ばれるものです。変数がstaticとして宣言されている場合、同じプロシージャまたは関数への個別の呼び出し間でその値が失われることはありません。それは複雑に聞こえますか。最初はそうかもしれませんが、実際には見た目よりもはるかに簡単です。自分がやっていることに注意を払っていれば、すべてうまくいくでしょう。しかし、不注意だと、事態はすぐに制御不能に陥る可能性があります。これをよりよく理解するために、コードが実際にどのようになるかを見てみましょう。更新されたバージョンは以下でご覧いただけます:同じプロシージャや関数への個別の呼び出しの間でその値が保持され、失われることはありません。
複雑に聞こえるでしょうか。最初はそうかもしれませんが、実際には見た目よりもはるかに簡単です。自分がやっていることに注意を払っていれば、うまくいくはずです。しかし、注意しないと、事態はすぐに制御不能になる可能性があります。これをよりよく理解するために、コードが実際にどのように変わるのか見てみましょう。以下でご覧ください。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. Factorial(); 07. Factorial(); 08. Factorial(); 09. Factorial(); 10. Factorial(); 11. } 12. //+------------------------------------------------------------------+ 13. void Factorial(void) 14. { 15. static uchar counter = 0; 16. static ulong value = 1; 17. 18. Print("Factorial of ", counter, " => ", value); 19. counter = counter + 1; 20. value = value * counter; 21. } 22. //+------------------------------------------------------------------+
コード04
コード03とコード04の唯一の違いは、15行目と16行目に予約語staticが追加されている点です。しかし、このたった一つの変更によって、コード04がMetaTrader 5ターミナルで実行された際、結果が図01に示されたものと完全に一致するようになります。つまり、期待通りの成功を収めたわけです。しかし、static変数はその性質上、慎重に扱う必要があります。なぜなら、static変数には特定の動作ルールがあるからです。こうしたルールに厳密に従う義務はありませんが、static変数の仕組みを正しく理解しておくことは、トラブルを回避する上で極めて重要です。
static変数は、主にブロック内に存在するように設計されています。特殊な目的でブロック外で使用されることもありますが、それは極めて稀なケースに限られます。通常は、静的変数は特定のコードブロックに紐づいたものとして考えるべきです。
2番目に、非常に特殊な使用例を除いて、static変数を宣言の外部ですることは避けるべきです。また、特定の例外を除き、静的変数を宣言の外部で初期化適切な考慮なしにこれをおこなうと、コードの制御を失い、意図しない挙動を引き起こす可能性があります。シンプルな学習用のコードであれば「完全に制御できている」と錯覚しがちですが、常にそうであると誤解しないでください。実際の開発現場では、一つの誤りや見落としが、数時間(あるいは数日)のデバッグ作業を引き起こす原因になり得ます。この点を明確にするために、コード04に小さな変更を加えて何が起こるかを見てみましょう。更新されたコードを以下に示します。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. Factorial(); 07. Factorial(); 08. Factorial(); 09. Factorial(); 10. Factorial(); 11. } 12. //+------------------------------------------------------------------+ 13. void Factorial(void) 14. { 15. static uchar counter; 16. static ulong value = 1; 17. 18. counter = 1; 19. Print("Factorial of ", counter, " => ", value); 20. counter = counter + 1; 21. value = value * counter; 22. } 23. //+------------------------------------------------------------------+
コード05
変更をより詳しく追跡できるように、完全なコードを記載します。こうすることで、アプリケーションで小さな間違いがあった場合に何が起こるかをよりよく理解できるようになります。コード04とコード05の違いに注意して比較してください。一見すると、これは些細なこと、ほとんど無害なことのように思えるかもしれません。結果に影響を与えるとは予想していないものです。ただし、実行すると、MetaTrader5ターミナルに表示されるはずの図01が、次の図に示すように、まったく異なるものに変わります。
図03
不思議です。階乗計算になるはずだったものが、2の累乗計算になってしまいました。どうしてこんなことが起きたのでしょうか。親愛なる読者の皆様、static変数を正しく操作するためにはちょっとしたコツがあります。経験豊富なプログラマーは、期待どおりに動作しないコードに遭遇すると、すぐに変数の使用に問題があると疑います。言語がstatic変数を許可していて、コードにstatic変数が含まれている場合、最初に確認すべきことは、それらの変数が正しく初期化されているかどうか、
そして、その価値観の変化が予想されるかどうかです。これらは最初に確認するポイントです。変数の値の変更は予想されることなので(そうでない場合は定数を使用します)、変数が変更され、最後に割り当てられた値が忘れられるかどうかを確認できます。これにはさまざまなデバッグ手法があります。状況によって若干異なるアプローチが必要になる可能性があるため、デバッグ方法を詳しく学習することをお勧めします。
しかし、コードが正常に見えるにもかかわらず、なぜ奇妙な動作をするのかという疑問に戻ります。最初に頭に浮かぶ考えは、すべてが正しいように見えるということです。ただし、static変数は作成時に初期化されます。この初期化がおこなわれないと、変数はローカルであるかのように動作します。つまり、変数は毎回再宣言され、staticな動作が失われることになります。当然、これは、コード05の18行目に示されている内容は、ブロック内では可能な限り避ける必要があることを意味します。これにより、static変数が壊れて誤動作が発生します。コードが異常な動作をした理由は、static変数だったものが通常の変数になってしまったということです。
これで、次の点に取り組み始めるための良い基盤ができました。
データ型
ここで、データ型について簡単に触れておきます。この概念について最初に理解しておくことが重要です。先に進むと、さらに多くのことが関係していることに気付くでしょう。
ここで話題にしているのは、データ型または変数型です。多くのプログラミング言語、特にJavaScriptやPythonなどのスクリプト言語では、保存されるデータのタイプをプログラマーが明示的に宣言する必要はありません。これらは動的型付け言語と呼ばれます。一方、C/C++やMQL5などの言語は静的型付けです。つまり、プログラマーは各変数で期待されるデータ型を指定する必要があります。これは実際には何を意味するのでしょうか。Pythonのような動的言語では、変数は任意の型の値を保持でき、言語は変数を適切な型に自動的に適応させます。ただし、MQL5では、変数に値を格納することはできず、特定の型で宣言する必要があります。これは理論上の話であり、そのような不便さを回避する方法はあります。
しかし、厳密に型付けされた言語でプログラミングする人にとっては、変数を宣言してそこに必要なものを入れるだけでは不十分なため、いくつかの作業はより複雑になります。これにはルールと制限があります。ただし、Pythonなどの言語では、変数にテキストやその他の値を設定することができ、言語によって変数に最も適切な型が自動的に設定されます。したがって、プログラミングの複雑さが大幅に軽減され、コードの作成がはるかに簡単かつ高速になり、プログラマーにそれほど多くの専門知識が要求されなくなります。これは、各型に何を含めることができるか、または含めることができないのかを知る必要がある静的型付け言語とは異なります。
この時点で、MQL5を理解するのをあきらめて、PythonやJavaScriptなどのよりシンプルなものを探そうと考えているかもしれません。しかし、静的型付け言語に慣れると、データ処理において細かな型の違いを意識する必要が減るため、むしろ動的型付け言語の柔軟さが物足りなく感じることもあります。データを適切に扱うためには、そのデータが何なのか、どのような型に分類できるのか、各型にはどのような制約があるのかを理解することが重要です。こうした知識を深めることで、暗号化やデータ圧縮といった高度な技術にも応用の幅が広がります。
しかし、それはまた別の機会にお話ししましょう。ここでは、MQL5のデータ型の基本について説明します。
MQL5では、整数、ブール値、リテラル、浮動小数点数、列挙型、文字列など、いくつかの基本的な型を使用できます。これらの型はC/C++にも見られますが、MQL5独自の型として、色データや日付・時刻データといった特殊な型も用意されています。後で説明するように、これらは他の型を基に派生したものですが、特定の状況において非常に便利に活用できます。
複雑なデータ型は、C/C++とMQL5の両方に存在します。構造体とクラスです。クラスに関しては、C++には存在しますが、MQL5のクラスはC++よりもはるかに理解しやすく、操作も簡単です。ただし、C++に慣れたプログラマーの視点からすると、MQL5にはC++の一部のクラス機能が存在しないため、不便に感じることもあります。しかし、最終的に目的を達成するためには、その環境に適応することが大切です。
今回は基本的な紹介にとどめますが、今後さらに詳しく解説していきます。この記事を締めくくる前に、MQL5の基本データ型が持つ制限について簡単に触れておきましょう。各型の制限を理解しておくことは、将来のコーディングにおいて非常に役立ちます。まずは、各データ型の制限をまとめた簡単な表をご覧ください。
型 | バイト数 | 最低値 | 最高値 |
---|---|---|---|
char | 1 | -128 | 127 |
uchar | 1 | 0 | 255 |
bool | 1 | 0 (False) | 1 (True) |
short | 2 | -32 768 | 32 767 |
ushort | 2 | 0 | 65 535 |
int | 4 | - 2 147 483 648 | 2 147 483 647 |
uint | 4 | 0 | 4 294 967 295 |
color | 4 | -1 | 16 777 215 |
float | 4 | 1.175494351e-38 | 3.402823466e+38 |
long | 8 | -9 223 372 036 854 775 808 | 9 223 372 036 854 775 807 |
ulong | 8 | 0 | 18 446 744 073 709 551 615 |
datetime | 8 | 0 (01/01/1970 00:00:00) | 32 535 244 799 (31/12/3000 23:59:59) |
double | 8 | 2.2250738585072014e-308 | 1.7976931348623158e+308 |
主な型の表
この表には単純なデータ型のみが表示されます。複雑な型については後で検討するため、ここでは分析しません。しかし、この単純な型の表を理解することで、計算に必要な最適な型を選択しやすくなります。これは、それぞれの型には表現できる範囲の最小値と最大値があるためです。
ただし、この表に含まれるデータ型のうち、doubleとfloatの2つについては、今後より詳しく説明していきます。というのも、これらの型には、それぞれ個別に説明すべき特徴があるからです。
では、簡単にこの表について説明しましょう。型の名前には共通点があり、一部の型では、たとえばcharとucharのように、先頭にuが付いていることがわかります。このuには特別な意味があります。型名の前にuが付く場合、その型は0から始まり、特定の上限までの範囲を持つことを意味します。このuはunsigned(符号なし)という単語に由来しています。では、「符号なし」とは実際にどういう意味でしょうか。符号なしの値は、常に正の値として解釈されます。符号付きの値は、正にも負にもなり得ます。これは、数学的な演算を説明するときにより明確になるでしょう。一般的に、次のように解釈できます。負の値を保存したい場合は、符号付きの型を使用します。しかし、保存する値がすべて正である場合は、符号なしの型を使用できます。しかし、ここで「可能なこと」について話すのはなぜでしょうか。「必要なこと」について話すほうが正しいのではないでしょうか。実際のところ、「必要」というわけではありません。この点については、数学的な演算を扱う際により詳しく説明します。ただし、ここで1つ基本的なルールがあります。整数型(浮動小数点を使用しない値)は厳密な値ですが、浮動小数点型の値は厳密ではありません。この表を見ていると、double型は広い範囲をカバーできるため、常にdoubleを使うのが最適だと思うかもしれません。しかし、実際にはそうとは限りません。そのため、優れたプログラマーは、適切なデータ型を選択する方法を知っている必要があります。
ところで、あなたはこの表を見てstring型がどこにあるか、stringは基本型ではないのかと疑問に思っているかもしれません。stringは基本型です。しかし、それは特別な型であり、別のカテゴリに分類するのが適切です。そのため、この表には含めていません。
最終的な考察
この記事では、主にstatic型変数について説明しました。いくつかの実験を通じて、それらをどのように使用できるかを示しました。本記事では、static型の一つの使用例に焦点を当てていますが、実際には他の目的にも利用できます。ただし、ほとんどの場合、static変数はここで紹介したような方法で使用されます。後ほど、static変数をさまざまな用途に活用する別の方法についても詳しく説明していきます。ひとまず、この記事で学んだことは、多くのプログラムを理解する手助けとなるだけでなく、グローバル変数に過度に依存しない、より効率的なソリューションを実装する際にも役立つでしょう。
また、次回の記事のテーマである「データ型」についても簡単に紹介しました。MQL5ではデータ型ごとに異なる特性や制限がありますが、それらの制限がなぜ存在するのか、またより具体的な目的のためにどのように拡張できるのかについては、今回は詳しく触れていません。
ですが、心配はいりません。すぐに理解できるようになります。これで本記事は終了です。記事の最後にコードを添付しておきますので、ぜひ実際に試してみて、ここで説明した内容について自分なりの結論を導き出してみてください。それでは、またすぐお会いしましょう。
MetaQuotes Ltdによりポルトガル語から翻訳されました。
元の記事: https://www.mql5.com/pt/articles/15302




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