English Русский Español Deutsch Português
preview
初級から中級へ:値渡しまたは参照渡し

初級から中級へ:値渡しまたは参照渡し

MetaTrader 5 | 26 3月 2025, 12:19
52 0
CODE X
CODE X

はじめに

ここで提示されるコンテンツは、教育目的のみを目的としています。いかなる状況においても、提示された概念を学習し習得する以外の目的でアプリケーションを閲覧することは避けてください。

前回の記事「初級から中級まで:演算子」で、算術演算と論理演算について学びました。説明はやや表面的なものでしたが、他の話題に移るには十分でした。時間が経ち、より多くの記事が公開されるにつれて、最初に紹介したテーマを徐々に深く掘り下げていきます。

したがって、忍耐強く時間をかけて教材を勉強してください。結果は一夜にして現れるものではなく、献身と継続によってもたらされます。今から勉強を始めれば、時間の経過とともに複雑さが増していくことにほとんど気づかないでしょう。前回の記事で述べたように、制御演算子について説明を始めようとしていました。ただし、そのトピックに入る前に、もう1つの重要な概念について説明する必要があります。これにより、制御演算子を使用して何が達成できるかをよりよく理解できるため、制御演算子についての学習がより魅力的で楽しいものになります。

この記事で説明および実証される内容を完全に正しく理解するには、前提条件として、変数と定数の違いを理解する必要があります。この違いがよく分からない場合は、「初級から中級まで:変数(I)」を参照してください。

初心者が作成したプログラムで最も一般的な混乱や間違いの原因の1つは、関数やプロシージャで値渡しをいつ使用し、参照渡しをいつ使用するかを知ることです。状況によっては、参照渡しの方が実用的である場合がありますが、しばしば値渡しの方が安全です。しかし、どちらを選択するべきなのでしょうか。読者の皆様、それは状況によります。この慣行には絶対的または決定的なルールはありません。場合によっては、参照渡しが最適な選択となることもありますが、値渡しが適切なアプローチとなる場合もあります。

通常、コンパイラは、可能な限り最も効率的な実行可能コードを生成する選択を試みます。それでも、安全かつ効率的なコードを記述できるように、各シナリオで何が要求されるかを理解することが重要です。


値渡し

この概念を実際に理解し始めるには、実装が簡単なコードを使用して作業するのが最も適切なアプローチです。したがって、まずは最初の使用例を調べてみましょう。これは以下のコードでは値渡しになります。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     int info = 10;
07. 
08.     Print(__FUNCTION__, " : #1 => ", info);
09.     Procedure(info);
10.     Print(__FUNCTION__, " : #2 => ", info);
11. }
12. //+------------------------------------------------------------------+
13. void Procedure(int arg1)
14. {
15.     Print(__FUNCTION__, " : #1 => ", arg1);
16.     arg1 = arg1 + 3;
17.     Print(__FUNCTION__, " : #2 => ", arg1);
18. }
19. //+------------------------------------------------------------------+

コード01

コード01が実行されると、MetaTrader 5ターミナルに、下の図01に示すようなものが表示されます。


図01

多くの人にとって、図01に示されているものは非常に複雑に見えるかもしれません。しかし、MQL5で適切にプログラミングする方法を学習することに全力を尽くしているので、この画像が何を伝えているのかを理解するために時間をかけましょう。これをおこなうには、コード01と図01に表示されている内容の両方に厳密に従う必要があります。

前回の記事で説明した内容に基づいて、6行目で特定の値を持つ変数を定義していることはすでにご存知のはずです。8行目では、同じ変数を出力しています。しかし、画像を見ると、他の情報も印刷されていることに気が付くでしょう。この場合、8行目が配置されているプロシージャまたは関数の名前です。

さて、読者の皆様、よく注意してください。コード01のコンパイルフェーズで、コンパイラは定義済みマクロ__FUNCTION__に遭遇するとすぐに、現在のルーチンの名前を検索します。この場合、その名前は4行目のOnStartで定義されます。この名前が見つかるとすぐに、コンパイラは__FUNCTION__をOnStartに置き換え、ターミナルに出力される新しい文字列を作成します。これは、図01に示すように、Print関数がその出力を標準出力(MetaTrader5ではターミナル)に送信するためです。その結果、ルーチンの名前と6行目に宣言された変数の値の両方が出力されます。他にも定義済みのマクロがあり、それぞれ特定の状況で非常に役立ちます。これらはコードの動作を追跡するのに非常に役立つので、必ず学習してください。OnStartルーチン内の__FUNCTION__がその名前に置き換えられるのと同様に、Procedureルーチン内で使用される場合もそこで置き換えられます。表示される数値の前に表示される情報を説明します。

さて、値渡しの理解に戻りましょう。値渡しシステムを使用しているため、9行目の関数呼び出しが実行されると、6行目に定義された変数に保持されている値が、13行目に宣言されたプロシージャに渡されます。ここに重要な観察点があります。ここではinfo変数の値をarg1に「渡す」、またはむしろ「コピーする」と言っていますが、これは必ずしも文字通りおこなわれるわけではありません。多くの場合、コンパイラは非常に効率的な方法で、arg1がinfoを指すようにします。しかし(ここが本当に興味深いところです)、arg1はinfoを参照していますが、同じメモリ空間を共有していません。何が起こるかというと、コンパイラは、ガラスの窓を通して見るのと同じように、値を変更せずに「確認」および「使用」できるように、arg1にinfoを参照させます。向こう側に何があるのかは見えますが、触ることはできません。同様に、arg1はinfoを定数として扱います。

このため、15行目にarg1の値を印刷すると、図01に示すように、2行目にarg1とinfoの両方に同じ値が表示されることがわかります。ただし、16行目のarg1の値を変更すると、重要なことが起こります。コンパイル中に、この操作が発生することを認識して、コンパイラはinfoと同じバイト数を格納するための新しい領域を割り当てます。それでも、arg1は最初はinfoを「監視」します。しかし、16行目が実行されると、arg1はinfoの現在の値を不変であるかのように取得し、自分自身のローカルコピーを作成します。その瞬間から、arg1はinfoから完全に独立します。したがって、17行目が実行されると、図01の3行目が出力され、arg1の値が実際に変更されたことが示されます。

ただし、プロシージャが戻って10行目が実行されると、イメージ01の4行目が出力され、info値がそのまま残っていることが示されます。

読者の皆様、これが本質的に値渡しの仕組みです。もちろん、正確な実装はコンパイラの構築方法によって異なりますが、これが一般的な考え方です。

簡単な部分はこれまでです。この値渡しの仕組みも、コード内では少し異なる方法で使用することがあります。今はそれについて詳しく説明するつもりはありません。値渡しを使用する他の方法について説明する前に、演算子とデータ型についてさらに詳しく説明する必要があるためです。ここでそのことについて議論すると、物事は明確になるどころか、より複雑になるでしょう。それでは、ステップごとに進めていきましょう。

それでも、先ほど検討したこの仕組みも適応可能です。これにより、上記の例で発生した問題を回避できます。これを適切に検討するために、小さな仮説的な状況を考えてみましょう。何らかの理由で、arg1の値を変更したくないとします。infoのみを参照し、arg1が使用されるたびにinfoの現在の値を反映するようにします。

では、どうすればこれを達成できるのでしょうか。やり方はたくさんあります。1つは、プロシージャブロック内でarg1を変更しないように細心の注意を払うことです。ただし、これは単純に思えるかもしれませんが、実際には単純ではありません。多くの場合、気づかないうちに変数の値を変更してしまい、プログラム実行中に異常な動作が現れたときに初めて問題に気付きます。こうした状況を解決するには、多大な時間と労力がかかります。しかし、もっと良い解決策があります。それは、すべきでないことを試みた場合に警告を発することで、コンパイラに作業を任せることです。

これを明確にするために、次のコードを見てみましょう。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     int info = 10;
07. 
08.     Print(__FUNCTION__, " : #1 => ", info);
09.     Procedure(info);
10.     Print(__FUNCTION__, " : #2 => ", info);
11. }
12. //+------------------------------------------------------------------+
13. void Procedure(const int arg1)
14. {
15.     Print(__FUNCTION__, " : #1 => ", arg1);
16.     arg1 = arg1 + 3;
17.     Print(__FUNCTION__, " : #2 => ", arg1);
18. }
19. //+------------------------------------------------------------------+

コード02

Code02をコンパイルしようとすると、次のエラーメッセージが表示されます。


図02

明らかに、コンパイラはエラーが16行目にあることを示していることがわかります。これは、変数arg1の値を変更しようとしているために発生します。しかし、ちょっと待ってください。arg1はもはや通常の変数ではありません。定数として宣言されています。したがって、Procedure関数全体を通じて、arg1の値を変更できなくなります。これにより、コンパイラ自体がarg1を変更できないようにするため、以前に特定したリスクが効果的に排除されます。言い換えれば、自分が何をしているのかを理解し、これらの概念を適切に理解することは非常に役立ち、プログラミングプロセスがはるかに効率的になります。

これは、コード02の17行目に印刷される値を変更できないことを意味するのでしょうか。はい、読者の皆様、この新しい変数が前のコードで明示的に宣言されていなくても、別の変数に割り当てる限り、印刷される値を変更することができます。したがって、コード02に似たアプローチを使用しながら、図01と同じ出力を実現するには、以下に示すものと非常に近いコードを記述します。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     int info = 10;
07. 
08.     Print(__FUNCTION__, " : #1 => ", info);
09.     Procedure(info);
10.     Print(__FUNCTION__, " : #2 => ", info);
11. }
12. //+------------------------------------------------------------------+
13. void Procedure(const int arg1)
14. {
15.     Print(__FUNCTION__, " : #1 => ", arg1);
16.     Print(__FUNCTION__, " : #2 => ", arg1 + 3);
17. }
18. //+------------------------------------------------------------------+

コード03

コード03を作成するために必要な調整をおこなうことで、arg1を定数として扱い続けます。ただし、コード03の16行目では、2つの定数(この場合はarg1と値3)の合計を別の変数に代入しています。この新しい変数は、Printが結果を正しく表示できるようにコンパイラによって作成されます。もちろん、この目的のためだけに専用の変数を作成することもできます。しかし、少なくともここで紹介されているようなコードでは、そうする必要はないと思います。


参照渡し

ルーチン間で値を渡すもう1つの方法は参照渡しです。この場合、追加の予防措置を講じる必要があります。しかし、先に進む前に、ここで少し立ち止まって、この時点で非常に重要なことを説明したいと思います。

読者の皆様は、本当に絶対に必要な場合を除いて、参照渡しをできるだけ使用しないでください。解決が難しいエラーの最も一般的な原因の1つは、参照渡しの不適切または不注意な使用です。実際、一部のプログラマーにとって、それはほとんど習慣となり、既存のコードの修正と改善が悪夢になる可能性があります。したがって、厳密に必要な場合を除いて、特に単一の値を変更することが唯一の目的である場合は、参照渡しを使用しないでください。この例をすぐにお見せします。しかしその前に、まず参照渡しが使用されるケースを調べ、これがアプリケーションにどのような影響を与えるかを理解しましょう。これを説明するために、以下のコードを分析してみましょう。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     int info = 10;
07. 
08.     Print(__FUNCTION__, " : #1 => ", info);
09.     Procedure(info);
10.     Print(__FUNCTION__, " : #2 => ", info);
11. }
12. //+------------------------------------------------------------------+
13. void Procedure(int & arg1)
14. {
15.     Print(__FUNCTION__, " : #1 => ", arg1);
16.     arg1 = arg1 + 3;
17.     Print(__FUNCTION__, " : #2 => ", arg1);
18. }
19. //+------------------------------------------------------------------+

コード04

さて、これから私が説明しようとしていることに細心の注意を払ってください。特に非常に複雑なコードを扱う場合には、ここが非常に厄介になる可能性があるからです。このコードを実行すると、MetaTrader 5ターミナルに次の出力が表示されます。


図03

この種の問題が意図せず発生すると、コードが期待どおりに動作しない理由を突き止めるのに何時間、何日、あるいは何ヶ月も費やすことになる可能性があります。特に図03の4行目に関しては、結果が図01に示されたものとはまったく異なることに注意してください。しかし、なぜこのようなことが起こったのでしょうか。結局のところ、コード04はコード01とまったく同じに見えます。図03の4行目が図01の4行目と異なるのは意味がありません。

さて、読者の皆様、見た目に反して、コード01とコード04には小さいながらも決定的な違いがあります。この違いは13行目にあります。何が起こっているのか理解してもらえるように、強調して説明しました。コード04には表示されるが、コード01には存在しない、一見無害な記号をよくご覧ください。その記号は&であり、アンパサンドとも呼ばれます。そして、それがまさに図03と図01の違いの原因です。通常、MQL5を操作する場合、この記号はビット単位の論理演算、具体的にはAND演算で使用されます。ただし、CおよびC++の場合、AND演算に適用するだけでなく、メモリ内の変数を参照するためにも使用されます。

そして今、事態は本当に深刻になってきています。なぜなら、1つの単純な記号の意味を理解することさえ難しいのに、同じコード内で同じ記号が2つのまったく異なる意味を持つとしたらどうなるか想像してみてください。それは文字通り狂気です。これが、CやC++でのプログラミングが非常に複雑で、習得に時間がかかる理由の1つです。しかし、幸いなことに、MQL5では、状況は少し単純になります。少なくとも、CとC++の最も紛らわしいメカニズムの1つであるポインタを直接扱うことはありません。

読者の皆様が、コード04の13行目にこの記号が存在することが最終結果に影響を及ぼす理由を正しく理解できるように、この記号が実際に何を意味するのかを明確にする必要があります。そこで、C/C++からのポインタの概念について説明しますが、複雑な部分については詳しく説明しません。

ポインタは変数に似ていますが、単なる変数ではありません。名前が示す通り、何かを指します。具体的には、ポインタは変数が格納されているメモリアドレスを指します。変数が別の変数を指しているというのは、混乱を招くかもしれないことは承知しています。しかし、この概念は、非常に効率的で多様なコードを書くためにCおよびC++で広く使用されています。これは、CとC++が実行速度の点でアセンブリに匹敵する、最も高速な言語の1つである理由の1つです。しかし、混乱を避けるためにポインタについてはあまり深く掘り下げないようにしましょう。ここで理解する必要があるのは、コード04に示すようにarg1が宣言されている場合、info変数を作成する従来の方法を使用していないということです。代わりに、arg1はMQL5コンパイラによってinfoへのポインタとして扱われます。

このため、16行目で加算を実行するときに、argは変更されません。arg1自体に追加しているわけではありません。arg1とinfoは同じメモリ空間を共有しているため、実際にはinfoを変更しています。プロシージャルーチン内では、arg1はinfoであり、infoはarg1です。

混乱しているなら、それは理解できます。これは実際にポインタの使用における「シンプル」かつ「簡単」な部分です。幸いなことに、MQL5ではC/C++のようにポインタを直接使用することは許可されていない(今後も許可されない)ため、arg1がinfoを変更する方法についての説明はここで終了できます。arg1とinfoは別の場所で宣言されており、無関係のように見えますが、同じエンティティとして考える必要があります。C/C++では、これははるかに複雑になります。読者の皆様が圧倒されることを避けるために、この話題についてはここでやめておきます。

さて、疑問があります。このような変更をブロックする方法はあるのでしょうか。つまり、16行目でarg1が変更されたときにinfoも変更されないようにする方法はあるのでしょうか。あります。1つの方法は、前のトピックで説明したように、値渡しを使用することです。もう1つの方法は、コード04をコード05のように変更することです。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     int info = 10;
07. 
08.     Print(__FUNCTION__, " : #1 => ", info);
09.     Procedure(info);
10.     Print(__FUNCTION__, " : #2 => ", info);
11. }
12. //+------------------------------------------------------------------+
13. void Procedure(const int & arg1)
14. {
15.     Print(__FUNCTION__, " : #1 => ", arg1);
16.     arg1 = arg1 + 3;
17.     Print(__FUNCTION__, " : #2 => ", arg1);
18. }
19. //+------------------------------------------------------------------+

コード05

ただし、コード05を記述すると、コード02と同じシナリオに陥ります。つまり、arg1は定数として扱われます。コード05をコンパイルしようとすると、図02に示すのと同じエラーが発生します。arg1は変数infoを指していますが、これはコンパイラエラーではありません。実際、コード05のようなことを実行せざるを得ない場合もあります。しかし、16行目ではarg1を変更しようとしているため、コンパイルされません。この問題の最終的な解決策は、コード03に似たコードを採用することです。その結果、次のコードが生まれます。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     int info = 10;
07. 
08.     Print(__FUNCTION__, " : #1 => ", info);
09.     Procedure(info);
10.     Print(__FUNCTION__, " : #2 => ", info);
11. }
12. //+------------------------------------------------------------------+
13. void Procedure(const int & arg1)
14. {
15.     Print(__FUNCTION__, " : #1 => ", arg1);
16.     Print(__FUNCTION__, " : #2 => ", arg1 + 3);
17. }
18. //+------------------------------------------------------------------+

コード06

これで、コード03に似た機能コードができました。ただし、値渡しではなく、参照渡しを使用します。コード06を実行した結果は、図01と同じになります。

これにより、このトピックの冒頭で取り上げたポイント、つまり単一の変数のみを変更するために参照渡しを避けるという点に戻ります。

何らかの理由で、変数の値、特に基本型を変更するルーチンが必要な場合は、プロシージャではなく関数を使用することをお勧めします。プログラミング言語に関数が存在するのはそのためです。関数はコードを美しくするためだけに作成されたのではなく、コード内の問題を回避するために存在します。

さて、本当にinfoを変更したいが、宣言されている場所ではなくルーチンから変更したい場合、正しい最も適切な方法は以下に示す方法と似ています。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     int info = 10;
07. 
08.     Print(__FUNCTION__, " : #1 => ", info);
09.     info = Function(info);
10.     Print(__FUNCTION__, " : #2 => ", info);
11. }
12. //+------------------------------------------------------------------+
13. int Function(const int & arg1)
14. {
15.     Print(__FUNCTION__, " : #1 => ", arg1);
16. 
17.     return arg1 + 3;
18. }
19. //+------------------------------------------------------------------+

コード07

コード07では依然として参照渡しが使用されていることに注意してください。しかし、それは完全に制御された方法で使用されます。09行目でわかるように、ここではinfoが明示的に変更されています。関数を使用してこのように記述すると、問題が発生する可能性は非常に低くなります。コード07を実行した結果を以下に示します。


図04

おわかりでしょうか。何が起こっているのか疑問に思うことはありません。コードを簡単に見れば、OnStartブロック内で情報が変更される理由がわかります。気をつけて、そんなことはしないでください。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     int info = 10;
07. 
08.     Print(__FUNCTION__, " : #1 => ", info);
09.     info = Function(info);
10.     Print(__FUNCTION__, " : #2 => ", info);
11. }
12. //+------------------------------------------------------------------+
13. int Function(int & arg1)
14. {
15.     Print(__FUNCTION__, " : #1 => ", arg1);
16. 
17.     return (arg1 = arg1 + 3) + 3;
18. }
19. //+------------------------------------------------------------------+

コード08

プログラマーの中には、目標を念頭に置いてこのようなことを試みるものの、結局は大混乱を招くという人が非常に多いです。例としてはコード08があります。ただし、このような間違いは多くの変数やステップを伴うより複雑な操作で頻繁に発生するため、これは完璧な例ではありません。いずれにせよ、それは問題を説明するのに役立ちます。

そこで、読者の皆様に質問です。コード08の10行目に印刷されるinfoの値は何でしょうか。お気きでないかもしれませんが、arg1は定数ではなくなりました。では、10行目にはどのような情報値を出力すればよいでしょうか。17行目では、返された値とinfoに割り当てられた値の両方が同時に変更されるため、混乱する可能性があります。これがそれほど混乱しない理由を説明する前に、以下に示す出力を確認してください。


図05

値が13ではなく16であることに注意してください。なぜでしょうか。arg1はinfoへのポインタであり、17行目で両方に13が割り当てられていますが、関数の戻り値によってそれが上書きされるためです。これは、17行目に割り当てられた値にさらに3が追加されたために発生します。したがって、関数の戻り値は変数infoに割り当てられるため、実際に印刷される出力は16になります。

ただし、9行目のinfoに戻り値を割り当てる代わりに、関数によって返された値を単に無視すると、必然的に前の例で観察されたのと同じ状況になってしまいます。つまり、infoの値は変更されますが、コードを理解してデバッグしようとすると、問題がどこにあるかを特定するだけでかなりの時間がかかる可能性があります。この種のエラーを含むコードは、多くの場合、それぞれ数百行のコードを含む複数のファイルに分散していることに注意してください。したがって、このような問題の根本原因を見つけるのには非常に時間がかかる可能性があります。

戻り値に関しては、完全に無視されることは珍しくありません。結局のところ、コード内で戻り値を割り当てたり使用したりする必要はありません。したがって、プログラムでこの種の構造を使用する場合は注意してください。参照渡しは柔軟性に優れていますが、特にプログラミングの経験を積んでいる場合、このアプローチから生じる問題に最終的に直面することは難しくもなければ、あり得ないことでもありません。

さて、ここで最後に触れておきたいのは、参照渡しを使用するときによく発生する別の問題です。これは通常、引数を処理するルーチンに引数を渡す際の間違いから発生します。値渡しを使用する場合、ある引数を別の引数と誤って交換しても、コード全体に与える影響は通常最小限です。多くの場合、コンパイルプロセス中に、特に予期されるデータ型が異なる場合、コンパイラは警告を発します。ただし、参照渡しを使用する場合、データ型に互換性があり、引数の順序が正しくないことを見落としていると、コンパイラが不一致について警告することがあります。それにもかかわらず、後で特定するのがはるかに難しいミスを犯す可能性があります。たとえば、日付と時刻を時間単位で渡す関数を作成したいとします。この関数は、指定された時間数を日付に追加し、同じ関数内でそれらの時間を秒数に変換して、対応する変数を更新します。これは、次のコードで実装できる、簡単で実用的なタスクのようです。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     uint        info = 10;
07.     datetime    dt = D'17.07.2024';
08. 
09.     Print(__FUNCTION__, " : #1 => ", info, " :: ", dt);
10.     dt = Function(dt, info);
11.     Print(__FUNCTION__, " : #2 => ", info, " :: ", dt);
12. }
13. //+------------------------------------------------------------------+
14. datetime Function(ulong &arg1, datetime arg2)
15. {
16.     Print(__FUNCTION__, " : #1 => ", arg1, " :: ", arg2);
17. 
18.     return arg2 + (arg1 = arg1 * 3600);
19. }
20. //+------------------------------------------------------------------+

コード09

ただし、コンパイラに実行可能ファイルのビルドを要求すると、次に示すように警告が発行されます。この種の警告はコードのコンパイルを妨げるものではありません。


図06

しかし、あなたはコンパイラが生成するすべてのメッセージに常に注意を払うプログラマーなので、警告で参照されている行をすぐに確認し、必要な修正をおこないます。この場合は、値をulongからdatetimeに明示的にキャストするだけです。重要な詳細:両方のデータ型は同じビット幅を持ちます。このため、多くの人がこのような警告を無視する傾向があります。その結果、コード09の18行目は以下のように調整されます。

return arg2 + (datetime)(arg1 = arg1 * 3600);

コンパイラの警告がなくなったので、プログラムの実行に進みます。しかし、驚いたことに、出力は次のようになります。


図07

この時点で、コードは正しいように見えても動作しないため、完全に困惑しているかもしれません。ここで重要な点が、コードが完全に間違っているわけではないということです。そこには微妙な誤りが含まれているだけです。このような誤りは、識別するのが最も難しい場合がよくあります。ここでは、コードがシンプルで短く、基本的な概念を示しているだけなので、問題を見つけるのが簡単です。実際のシナリオでは、小さな独立したコードスニペットを記述してテストすることはほとんどありません。通常、相互作用するプログラムのさまざまな部分を開発します。そして、すべてが連携して動作するかどうかを確認するためのテストを後から開始します。問題に気づくこともありますが、気づかないこともあります。複雑なコードでバグを見つけることが非常に難しいのは、同じルーチンが、あるコンテキストではエラーを引き起こす可能性があるが、他のコンテキストでは完璧に機能する可能性があるためです。これによりデバッグが非常に困難になります。

最後に、ここでどこにエラーがあるのかを突き止めましょう。欠陥はまさに10行目にあります。さて、あなたはこう考えているかもしれません:「どうして10行目に間違いがあるのですか。エラーは間違いなく14行目から始まるルーチン内、おそらく18行目にあります。10行目にあるはずがありません。」

しかし、読者の皆様、それは間違っています。注意深く分析してみましょう。infoには秒単位の最終値が保持されるものと予想されます。dt変数は7行目で宣言された日付から開始し、infoに基づいて関数によって調整されるようにします。この調整はサブルーチンの14行目、つまり18行目でおこなう必要があります。ここまでは順調ですね。しかし、コードには小さいながらも重大なエラーがあります。datetime(8バイト)とulong(8バイト)は同じサイズを共有するため、関数はデータ型の違いについてエラーを報告しません。しかし、infoはuintとして宣言されています。問題はそこにあると思うかもしれないが、そうではありません。24時間は86,400秒に相当し、4バイトを占めるuintはこの値を快適に保存できるからです。より小さいデータ型を使用することもできますが、オーバーフローが発生するリスクがあります。

ここで、arg1はポインタ(つまり参照渡し)であり、arg2は値渡しされるため、エラーは実際には10行目にあります。ここで、最初の引数は変数info(変更されるため)である必要があります。2番目はdtで、関数の戻り値に基づいて調整されます。これを念頭に置いて、10行目を次の修正バージョンに置き換える必要があります。

    dt = Function(info, dt);

この修正されたコードをコンパイルしようとすると、コンパイラは次のようなエラーを発生させます。


図08

さて、イライラしながら必死になってコードを動作させようとし、実際には8バイトの値は必要ないことに気付き(4バイトで十分であるため)、次のように14行目を変更することにしました。

datetime Function(uint &arg1, datetime arg2)

最後に、コードがコンパイルされます。再度実行すると、次に示すように、期待どおりの正しい出力が表示されます。


図09


最終的な考察

この記事では、現実世界のプログラミングにおける微妙ながら重要なニュアンスを探りました。もちろん、ここで挙げる例はすべて単純なものであり、教育目的で設計されています。それでも、実際に内部でどのように機能するかを説明することは、楽しく、価値のあることです。多くの人が、特に長い間プログラミングをしてきた人たちにとっては、この種の資料は「基本的すぎる」、あるいは特に面白くないと感じるかもしれないことはわかっています。しかし、質問させてください。ここでこのようにわかりやすく説明されている概念を学ぶのに、どれだけの時間を無駄にしたでしょうか。個人的に、私は自分のC/C++コードがなぜ奇妙で不可解な動作をするのかを解明するのに長い時間を費やし、最終的に根本的な詳細をすべて理解しました。

これらの基本を理解すれば、他のすべてがはるかにシンプルで直感的になります。現在、私はさまざまな言語でプログラミングを楽しんでおり、それぞれの言語が投げかける課題を楽しんでいます。それは、C/C++から強固な基盤を構築したからです。MQL5は、C/C++に固有の複雑さに比べると、はるかに快適で、シンプルで、説明しやすい言語です。しかし、読者の皆様がこの記事の説明とデモンストレーションを本当に理解すれば、優れたMQL5アプリケーションを作成する方法をはるかに早く習得できるようになります。次回の記事では、いよいよ、より楽しく、より実践的な内容に取り組み始めます。それでは、またすぐお会いしましょう。


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

添付されたファイル |
Anexo.zip (3.12 KB)
取引におけるニューラルネットワーク:複雑な軌道予測法(Traj-LLM) 取引におけるニューラルネットワーク:複雑な軌道予測法(Traj-LLM)
この記事では、自動運転車の動作の分野における問題を解決するために開発された興味深い軌道予測方法を紹介します。この手法の著者は、さまざまな建築ソリューションの最良の要素を組み合わせました。
リプレイシステムの開発(第60回):サービスの再生(I) リプレイシステムの開発(第60回):サービスの再生(I)
これまで長い間インジケーターだけに取り組んできましたが、今度はサービスを再び稼働させて、提供されたデータに基づいてチャートがどのように構築されるかを確認するときが来ました。しかし、すべてがそれほど単純ではないので、先に何が待ち受けているのかを理解するために注意深くならなければなりません。
ウィリアム・ギャンの手法(第3回):占星術は効果があるのか ウィリアム・ギャンの手法(第3回):占星術は効果があるのか
惑星や星の位置は金融市場に影響を与えるのでしょうか。統計とビッグデータを武器に、星と株価チャートが交差する世界への刺激的な旅に出ましょう。
取引におけるニューラルネットワーク:状態空間モデル 取引におけるニューラルネットワーク:状態空間モデル
これまでにレビューしたモデルの多くは、Transformerアーキテクチャに基づいています。ただし、長いシーケンスを処理する場合には非効率的になる可能性があります。この記事では、状態空間モデルに基づく時系列予測の別の方向性について説明します。