English Русский Español Deutsch Português
preview
初級から中級まで:FileSaveとFileLoad

初級から中級まで:FileSaveとFileLoad

MetaTrader 5 |
12 0
CODE X
CODE X

はじめに

前回の「初級から中級まで:サンドボックスとMetaTrader」では、MQL5のライブラリ関数や手続きを使用してファイルを作成・処理・読み込む際に、サンドボックスという概念がどのようなものであり、MetaTrader 5のワークフローにどのような影響を与えるのかを、非常にシンプルかつ実践的な方法で説明しました。

前回の記事の目的は、サンドボックスとは何なのかを説明することだけであり、ファイルを扱うための最適な方法を紹介することではありませんでした。これは、OS(オペレーティングシステム)の関数に依存することなく、MQL5だけを使って、より効率的にファイル処理を実装する方法が存在するためです。

読者がすでにサンドボックスの概念と、それがワークフローに与える影響について理解していることを前提として、ここからはさまざまな目的を実現するためのファイル操作について見ていきます。なお、この記事では特定のアプリケーションを開発することが目的ではありません。いくつかの処理をどのように実装できるのかを説明するために、MQL5ライブラリに用意されている一部の関数や手続きだけを使用します。とはいえ、疑問を持つかもしれない細かな点を理解するには、実際に自分でコードを書いて試してみることが重要です。それでは、いつものように新しいテーマへ進み、基本的な部分から確認していきましょう。


万能な方法は存在しない

前回の記事では、比較的単純な処理をおこなうために、ファイルの読み書きの仕組みを実装しました。その時点では、コードをそのままにしておいても問題ありませんでした。しかし、実際の実装が、前回示した例のような形に必ずしも従うとは限りません。特定の問題が発生する可能性があるためです。ここで、その問題が具体的に何だったのかをもう一度確認し、背景を整理しておきましょう。そのために、前回の記事で取り上げたコード例の1つをもう一度使用します。以下にそのコードを示します。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     const string szText = "This file was created by a script written in MQL5.";
07.     uchar info[];
08. 
09.     StringToCharArray(szText, info);
10. 
11.     FileSave("Hello.txt", info);
12. }
13. //+------------------------------------------------------------------+

コード01

コード01が実行されると、次の図に示す内容のファイルが作成されます。

図01

図01には、通常では見慣れない文字が含まれています。この場合、それはヌル文字(NULL)です。この文字は、文字列の終端を示すために使用されます。このような文字列形式を使用するアプリケーションの内部では、特に問題にはなりません。しかし、プレーンテキストを保存するためのファイルでは、このような文字には意味がなく、テキストファイルの目的や用途によっては、しばしば問題になります。

このような問題を解決する方法はいくつかあります。直接的に解決する方法もあれば、追加の処理を必要とする間接的な方法もあります。さらに、コード01にはもう一つ注意すべき点があります。11行目では、ファイルを作成すると同時にデータを書き込むという、最もシンプルで直接的な方法を使用できます。しかし、この方法には別の問題があります。それが、これから詳しく説明するファイルデータへのランダムアクセスです。

この説明は少し特殊に聞こえるかもしれませんが、実際には、ディスク上に保存されたファイルは、必ずしもシーケンシャルアクセスだけで動作するわけではありません。たとえば、磁気テープに保存されたファイルの場合を考えてみましょう。テープからファイルや特定のデータを読み込む場合、目的のデータに到達するまでに、テープのかなりの部分を読み進めなければならないことがあります。そのため、読み書きの処理が非常に複雑になる場合があります。一方、ディスクに保存されたファイルでは、シーケンシャルアクセスとランダムアクセスの両方が可能です。つまり、ファイルの任意の場所にあるデータを、必要なタイミングで読み込んだり書き込んだりすることができます。

しかし、このようなランダムアクセスが可能であるにもかかわらず、FileSaveとFileLoadはランダムアクセスには適していません。その理由は、これらの関数がファイルを1つの完全なブロックとして読み書きするためです。通常、ファイル全体をディスクから読み込んだり、ファイル全体をディスクへ保存したりします。ファイルが小さい場合は、これは特に問題になりません。また、ファイルの内容を上書きするために、既存の内容を保持する必要がないのであれば、FileSaveとFileLoadを使用しても大きな問題はありません。これらの関数は、ファイルを読み書きするための非常に実用的で安全な方法だからです。

しかし、ファイルの一部を変更しながら、それ以外の内容をそのまま保持したい場合には、FileSaveとFileLoadは必ずしも最適な方法ではありません。なぜなら、この場合、ファイル全体をメモリ上に保持する必要があるからです。これは非常に非効率になる可能性があります。実際には、作業中の一部分だけをメモリに載せれば十分なことが少なくありません。残りのデータはディスク上に残しておき、必要になったときだけ読み込むことができます。

このような理由から、プログラムの処理を実装する際に、すべてのケースに適用できる万能な方法や、絶対的に正しい唯一の方法というものは存在しません。それぞれのケースには、それぞれ固有の条件があります。そのため、使用する方法も、そのケース固有の要件に応じて選択する必要があります。そうすることで、MetaTrader 5用アプリケーションを使用するユーザーにとって、便利で実用的な操作性を維持することができます。

ここまでの説明をより簡単かつ明確に理解するために、いくつかのシンプルな例を作ってみましょう。これによって、なぜそれぞれの実装を個別に設計する必要があるのかが分かりやすくなります。実際、多くの人は、ある設計上の判断がアプリケーションの開発方法にどれほど大きな影響を与えるのかを、明確に理解していない場合があります。

まずは、すでにディスクへ保存されているファイルに新しい情報を追加することから始めましょう。今回処理するファイルは、コード01によって作成されたファイルとまったく同じものです。ただし、今回のようなファイル処理を可能にするために、コード01にいくつか変更を加えます。ここで重要なのは、すでに保存されているファイルに新しい情報を追加することです。そのため、理論的には、以下のような方法を考えることができます。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     SaveInfo("This file was created by a script written in MQL5.");
07.     SaveInfo("New information is being added to the file.");
08. }
09. //+------------------------------------------------------------------+
10. void SaveInfo(const string szArg)
11. {
12.     uchar info[];
13. 
14.     StringToCharArray(szArg, info);
15.     FileSave("Hello.txt", info);
16. }
17. //+------------------------------------------------------------------+

コード02

コード02を実行すると、Hello.txtファイルには、図01にある元のデータと新しく追加した情報の両方が含まれることを期待します。ファイルに保存されるはずだったデータは、コード02の6行目と7行目に示されています。この処理は非常に分かりやすいものに見えます。しかし、このスクリプトをMetaTrader 5で実行すると、期待した結果にはなりません。以下のような結果になります。

図02

ここで、重要な疑問が生じます。なぜ図02のような結果になったのでしょうか。本来であれば、6行目と7行目の両方の内容が最終的なファイルに保存されるはずです。ところが、実際には7行目のメッセージだけが表示されています。

つまり、何らかの理由で6行目の内容が無視され、7行目の内容だけがファイルに書き込まれています。では、6行目の内容は一体どうなったのでしょうか。これは、内部で何が起きているのかを詳しく説明してくれる人がいないと、理解するのがかなり難しい問題の一つです。しかし、実際の仕組みは非常にシンプルです。6行目が実行されると、15行目の関数がファイルにデータを書き込みます。その直後に7行目が実行されると、そのファイルが上書きされ、新しい内容が書き込まれます。この仕組み自体は単純ですが、外部から説明してもらわなければ、なぜコードが一部のデータを書き込んでくれないのかを発見するのは難しい場合があります。その結果、コードそのものに重大なエラーがあるように見えてしまいます。しかし実際には、問題はコードの別の部分、つまりファイルへの書き込み方法にあります。

では、この問題をどう解決するのか。言い換えると、6行目と7行目の両方の内容を、同じファイルに保存するにはどうすればよいのでしょうか。ここで、このセクションのタイトルにもある重要なポイントを思い出してください。万能な方法は存在しない。この問題を解決する方法はいくつもあります。そして、それぞれにメリットとデメリットがあります。ここでは、説明のために1つの方法だけを例として紹介します。ただし、読者自身で別のアプローチを考え、それを実装してみることもできます。まずはFileSaveを出発点として、自分自身の解決方法を考えてみてください。実際には、十分に計画を立てれば、もっとシンプルな方法も存在します。

それでは、まず考えられる最初の解決方法を見てみましょう。以下に示します。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     SaveInfo("This file was created by a script written in MQL5.");
07.     SaveInfo("New information is being added to the file.");
08. }
09. //+------------------------------------------------------------------+
10. void SaveInfo(const string szArg)
11. {
12.     const string FileName = "Hello.txt";
13.     uchar info[], tmp[];
14. 
15.     FileLoad(FileName, info);
16.     StringToCharArray(szArg + "\n", tmp);
17.     ArrayCopy(info, tmp, info.Size(), 0, tmp.Size() - 1);
18.     FileSave(FileName, info);
19. }
20. //+------------------------------------------------------------------+

コード03

コード03を実行すると、以下のような結果が得られます。

図03

今回の結果は、期待していたものと完全に一致しています。つまり、6行目の内容と7行目の内容の両方がファイルに追加されています。しかし、それだけではありません。以前説明したNULLも、今回はファイルに追加されていないことが分かります。では、どのようにして2つの問題を同時に解決したのでしょうか。

さらに詳しく分析すると、コード03の10行目にあるSaveInfo関数は、実質的にログファイルを作成するためのコードになっていることが分かります。つまり、ほとんど手間やコストをかけることなく、プログラムが実行中に何を行っているのかをファイルに記録するシステムを作ることができます。これによって、プログラムの実行中に何が起こっているのかを、後から詳しく調査・分析できます。面白いと思いませんか。実装が非常に簡単でありながら、非常に興味深い結果と可能性を持った方法です。

では、コード02を修正し、コード03をより適切な形にして、6行目と7行目の両方の内容を保存できるようにした仕組みを詳しく見てみましょう。

まず、定数が追加されていることに注目してください。これによって、同じファイル名の参照を使用できます。15行目ではそのファイルを読み込み、18行目では同じファイルに対して書き込みをおこないます。では、なぜこのような処理をおこなうのでしょうか。コード02で説明したように、FileSaveはバッファの内容を一度にまとめてファイルへ書き込みます。つまり、今回の例でいうinfo配列が特定の情報を保持している場合、その情報によってファイル内にあった元のデータが上書きされてしまいます。一方、ファイルを読み込む場合は、ファイルの内容がバッファに読み込まれます。その直後に17行目の処理を使用して、元々ファイルに存在していた情報に新しい情報を追加します。イメージとしては、まるでメモリ上でファイルを作成してから、それをディスクに保存しているようなものです。

今回の例では、この処理は非常に高速に実行されます。しかし、ファイルのサイズが大きくなるにつれて問題が発生します。この方法では毎回、ファイルを読み込む → メモリ上でデータを追加する → ファイル全体を書き込むという処理をおこなうため、ファイルサイズが大きくなるほど、システムリソースの使用効率が低下します。最終的には、実行時間にも影響します。ファイル内のデータ量が増えるほど、ディスクからデータを読み込んだり、ディスクへデータを書き込んだりするのに、より長い時間が必要になるからです。ただし、今回のような単純なケースであれば、この方法は十分に実用的です。

では、アプリケーションをもう一度実行したらどうなるのか。一見すると、すべて問題なく動作するように思えます。しかし、その場合、6行目と7行目のデータが何度も何度もファイルに書き込まれることになります。その結果、同じ情報を含む行が次々に追加されていきます。そして、元のファイルを削除するまで、この状態が続きます。元のファイルを削除すると、すべての処理が最初からやり直されます。つまり、ここで新たな問題が発生しました。この問題をコード上で処理する方法は、主に2つあります。もちろん、ほかにも方法はありますが、ここではそのうちの2つだけを取り上げます。


最初の解決策

この問題に対する最初の解決方法は、アプリケーションの実行開始時にファイルを削除し、その後あらためて書き込みを開始することです。そのためには、コード03を以下のような方法に置き換える必要があります。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_FILENAME "Hello.txt"
05. //+------------------------------------------------------------------+
06. void OnStart(void)
07. {
08.     FileDelete(def_FILENAME);
09.     SaveInfo("This file was created by a script written in MQL5.");
10.     SaveInfo("New information is being added to the file.");
11. }
12. //+------------------------------------------------------------------+
13. void SaveInfo(const string szArg)
14. {
15.     uchar info[], tmp[];
16. 
17.     FileLoad(def_FILENAME, info);
18.     StringToCharArray(szArg + "\n", tmp);
19.     ArrayCopy(info, tmp, info.Size(), 0, tmp.Size() - 1);
20.     FileSave(def_FILENAME, info);
21. }
22. //+------------------------------------------------------------------+

コード04

素晴らしいことに、コード04はコード03で発生していた問題を部分的に解決しています。しかし、ここで重要な点を強調しておく必要があります。この方法は確かに機能しますが、ある意味で危険な方法でもあります。プログラマーが正しいサンドボックスを指定することを忘れてしまった場合、重大な問題が発生する可能性があるためです。コード04の8行目では、指定されたファイルを実際に削除します。ところが、17行目の読み込み処理と20行目の書き込み処理で使用しているサンドボックスが、8行目で使用しているサンドボックスと一致していない場合、意図したものとは異なるファイルを削除してしまう可能性があります。

コード04はここに掲載されているので、実際にコードを変更してみれば、異なるサンドボックスを使用した場合にどのような問題が発生するのかを確認できます。しかし、この問題を解決するために、実装全体で使用するサンドボックスを統一するdefineを追加することができます。これにより、処理の各部分で異なるサンドボックスを指定してしまうことを防ぎ、サンドボックスの違いによって発生する問題を防止できます。具体的には、以下のように実装できます。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_FILENAME        "Hello.txt"
05. #define def_FILE_COMMON                     //If set uses the shared folder
06. //+------------------------------------------------------------------+
07. void OnStart(void)
08. {
09. #ifdef def_FILE_COMMON
10.     FileDelete(def_FILENAME, FILE_COMMON);
11. #else
12.     FileDelete(def_FILENAME);
13. #endif
14.     SaveInfo("This file was created by a script written in MQL5.");
15.     SaveInfo("New information is being added to the file.");
16. }
17. //+------------------------------------------------------------------+
18. void SaveInfo(const string szArg)
19. {
20.     uchar info[], tmp[];
21. 
22. #ifdef def_FILE_COMMON
23.     FileLoad(def_FILENAME, info, FILE_COMMON);
24. #else
25.     FileLoad(def_FILENAME, info);
26. #endif
27.     StringToCharArray(szArg + "\n", tmp);
28.     ArrayCopy(info, tmp, info.Size(), 0, tmp.Size() - 1);
29. #ifdef def_FILE_COMMON
30.     FileSave(def_FILENAME, info, FILE_COMMON);
31. #else
32.     FileSave(def_FILENAME, info);
33. #endif
34. }
35. //+------------------------------------------------------------------+

コード05

ここで、コード05ではサンドボックスの同期問題を非常に実用的な方法で解決していることに注目してください。5行目でどのコードをコンパイルするのかを定義しているからです。これにより、ある場所ではある方法でファイルにアクセスし、別の場所では別の方法でアクセスするといった状況で発生する、さまざまな問題を防ぐことができます。

さて、ここまででコードにはかなり有用な変更を加えることができました。しかし、これらの記事の内容を実際に勉強し、自分でコードを書いて試してみたのであれば、次のような疑問を持ったかもしれません。「FileDeleteの呼び出しをSaveInfo手続きの中に入れることはできないのだろうか。」もしそれが可能なら、コード05でおこなった実装は不要になります。さらに、サンドボックスの同期もある程度維持できます。なぜなら、すべてのライブラリ関数を1か所にまとめることができるため、コードの修正もより簡単かつ迅速におこなえるからです。

はい、これは可能です。これは必ずしも従来の一般的な方法ではありませんが、実際に機能します。ここまで説明してきた内容を基にすると、static変数を使用して実装できます。より安全な方法としては、クラスを使用する方法があります。しかし、まだクラスという概念について説明していないため、ここではクラスを使用した実装方法については扱いません。その代わりに、今回はstatic変数を使用した方法を紹介します。具体的には、以下のコードのように実装できます。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     SaveInfo("This file was created by a script written in MQL5.");
07.     SaveInfo("New information is being added to the file.");
08. }
09. //+------------------------------------------------------------------+
10. void SaveInfo(const string szArg)
11. {
12. //+----------------+
13. #define def_FILENAME "Hello.txt"
14. //+----------------+
15.     static bool b_Clear = false;
16.     uchar info[], tmp[];
17. 
18.     if (!b_Clear) FileDelete(def_FILENAME);
19.     FileLoad(def_FILENAME, info);
20.     StringToCharArray(szArg + "\n", tmp);
21.     ArrayCopy(info, tmp, info.Size(), 0, tmp.Size() - 1);
22.     FileSave(def_FILENAME, info);
23.     b_Clear = true;
24. }
25. //+------------------------------------------------------------------+

コード06

ここからは、よく注意して読んでください。コード06はコード04と非常によく似ていますが、その動作は大きく異なります。その大きな違いを生み出しているのが、15行目で宣言されているstatic変数です。さらに、ファイルに対して実行する必要のあるすべての処理を、SaveInfo手続きという1か所に集約していることにも大きなメリットがあります。これによって、必要に応じてデータを見つけたり変更したりすることが非常に簡単になります。つまり、このファイルに対しておこなう処理と競合する可能性のあるコードを、プログラム全体から探し回る必要がなくなります。

さて、ここまでで、元のファイルを削除してから処理をおこなう方法については、もう十分に理解できたと思います。しかし、元のファイルを削除する方法とは少し異なるやり方でも、同じ結果を得ることができます。これを説明するために、次のセクションへ進み、2つ目の解決方法を紹介しましょう。


2つ目の解決策

先ほど説明したように、この問題を解決する方法は数多く存在します。しかし、ここでは1つの方法だけに固執しないように、2つのアプローチだけを取り上げます。問題を解決する方法を考えることは、優れたプログラマーに求められる重要な仕事です。それでは、2つ目の解決方法がどのようなものになるのか見てみましょう。きっと、そのシンプルさに驚くと思います。正直なところ、この解決方法は、コード03とコード06の中間のような方法になります。ただし、分かりやすくするために、今回はこの方法を「いくつかの簡単な変更を加えたコード03」として考えてください。その変更内容は、以下のようになります。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     SaveInfo("This file was created by a script written in MQL5.");
07.     SaveInfo("New information is being added to the file.");
08. }
09. //+------------------------------------------------------------------+
10. void SaveInfo(const string szArg)
11. {
12.     const string FileName = "Hello.txt";
13.     static bool b_isFirst = true;
14.     uchar info[], tmp[];
15. 
16.     if (b_isFirst)
17.     {
18.         ArrayFree(info);
19.         b_isFirst = false;
20.     }else
21.         FileLoad(FileName, info);
22.     StringToCharArray(szArg + "\n", tmp);
23.     ArrayCopy(info, tmp, info.Size(), 0, tmp.Size() - 1);
24.     FileSave(FileName, info);
25. }
26. //+------------------------------------------------------------------+

コード07

では、コード07を見て、正直に答えてみてください。ここで何が起こっているのか理解できますか。コード07そのものは、コード03とコード06を組み合わせたような非常に似た構造になっていることに注目してください。ただし、コード06ではFileDeleteを使用しましたが、コード07ではそうしていません。コード07では、書き込み処理そのものを利用してファイルの内容をクリアしながら、新しい情報が届くたびにその情報を受け取って保存するという方法を使用しています。では、これはどのように実現したのでしょうか。

さて、もし、なぜこのような処理が可能になったのか、まだ疑問が残っているのであれば、おそらく本連載の最初の方の記事をもう一度勉強する必要があります。そこでは、static変数の扱い方について説明しています。static変数は難しいテーマだと考える人も多いですが、これを理解することは非常に重要です。static変数を理解することで、さまざまな処理をシンプルかつスムーズに解決できるようになります。さらに、同じ目的であれば、異なるアプリケーション間でもコードを比較的容易に移植できるというメリットもあります。

static変数についての記事「初級から中級まで:変数(II)」でも説明しましたコード07は非常にシンプルであり、すでにその基本的な使用原理を理解しているのであれば、ここでその仕組みを詳しく説明する必要はないでしょう。その代わりに、非常によく似た別の解決方法を見ていきます。こちらには、説明する価値のある興味深い結果があります。

これまで扱ってきたすべてのコードを見てみると、ある共通点に気付くと思います。それは、常に1つのファイルに依存しているということです。そして、そのファイル名はコードのどこかで指定されています。では、もしコードそのものを変更することなく、異なるファイルにデータを保存できたら、どれほど便利でしょうか。

もちろん、多くの場合は、保存したいファイル名をSaveInfo手続きの引数として渡すだけでも問題を解決できます。これは1つの方法です。しかし、その方法を採用すると、今度は別の種類の問題が発生する可能性があります。その問題がどの程度難しいものになるかは、アプリケーションが想定している用途や必要とする処理内容によって異なります。しかし、この問題には比較的エレガントな解決方法があります。それによって、ほとんど手間をかけずに異なるファイルへデータを保存できるようになります。

その方法が、構造化プログラミングの利用です。構造化プログラミングについては、本連載でもすでにいくつかの記事にわたって説明しています。すでに複数の記事で扱っているテーマなので、ここではリンクを掲載しません。ただし、構造化プログラミングが実際にどのように機能するのかを正しく理解するには、このテーマを最初から順番に学ぶ必要があるでしょう。とはいえ、これまでの記事を読み、そこで紹介した内容を勉強してきた読者に対して、保存先となるファイル名を選択する問題の解決方法を紹介しないのも公平ではありません。

そこで、すでに構造化プログラミングの基本を理解している方を対象として、ここからは、これまで見てきた方法よりも優れた解決策でありながら、コード07と同様にディスクへのアクセス回数も少なくできる方法を見ていきます。その解決方法は、以下のコードのようになります。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct stFile
05. {
06.     private :
07.         string  FileName;
08.         int     common_flag;
09.         bool    b_isFirst;
10.     public  :
11. //+----------------+
12.         void SetFileName(const string szArg, const int iArg = 0)
13.         {
14.             FileName = szArg;
15.             common_flag = iArg;
16.             b_isFirst = true;
17.         }
18. //+----------------+
19.         void SaveInfo(const string szArg)
20.         {
21.             uchar info[], tmp[];
22. 
23.             if (FileName == NULL) return;
24.             if (b_isFirst)
25.             {
26.                 ArrayFree(info);
27.                 b_isFirst = false;
28.             }else
29.                 FileLoad(FileName, info, common_flag);
30.             StringToCharArray(szArg + "\n", tmp);
31.             ArrayCopy(info, tmp, info.Size(), 0, tmp.Size() - 1);
32.             FileSave(FileName, info, common_flag);
33.         }
34. //+----------------+
35. };
36. //+------------------------------------------------------------------+
37. void OnStart(void)
38. {
39.     stFile  file;
40. 
41.     file.SetFileName("Hello.txt");
42.     file.SaveInfo("This file was created by a script written in MQL5.\nStructured programming version.");
43.     file.SaveInfo("New information is being added to the file.\nFile saved in the local directory.");
44. 
45.     file.SetFileName("Hello.txt", FILE_COMMON);
46.     file.SaveInfo("This file was created by a script written in MQL5.\nStructured programming version.");
47.     file.SaveInfo("New information is being added to the file.\nFile saved in shared directory.");
48. }
49. //+------------------------------------------------------------------+

コード08

コード08は非常に興味深く、同時にかなり面白い内容になっています。複数の概念を同時に組み合わせて使用しているからです。今回は非常に具体的な処理を実現することを目的としているため、このような構成になっています。構造化プログラミングの仕組みを説明した記事を実際に勉強した人であっても、このコードの一部について「なぜこのように動くのか」と疑問に思うかもしれません。そこで、ここではいくつかの興味深いポイントだけを簡単に説明します。ただし、コードの大部分はコード07と非常によく似ています。一方で、今回はさらに複雑な処理をおこなっているため、少なくとも私の考えでは、より優れた実装になっています。

まず、4行目に注目してください。ここでは構造体を宣言しています。この構造体が、これからおこなう処理の基本となります。構造体の内部には、複数のprivateフィールドと、2つの手続きがあります。12行目の手続きは、現在使用するファイルの名前を指定するために使用します。さらに、この手続きによって、使用するサンドボックスを変更することもできます。ここから、この最初の段階における少し複雑な部分が始まります。

今回はクラスではなく、構造化されたコードを使用しています。そのため、クラスであれば可能な一部の処理を、そのまま実行することはできません。しかし、別のテクニックを使用することができます。今回の場合、コンパイラが7~9行目で宣言された変数にメモリを割り当てる際、それらの変数に割り当てられたメモリ領域はあらかじめ初期化されます。ここで重要なのは、そのメモリ領域がどのような初期値を持つのかを理解しておくことです。文字列は通常、NULLに初期化されます。この仕組みによって、23行目のチェックを実行できます。

このチェックの目的は、使用するファイルが指定されているかどうかを確認することです。つまり、ファイル名が設定されているかを確認しています。もし使用するファイルをまだ指定していなければ、このチェックによって条件が成立し、SaveInfo手続きは直ちに終了します。それ以外の部分については、この記事の別の箇所ですでに説明している内容なので、比較的シンプルです。

では、OnStart手続きに進みましょう。ここでは、コード08を正しく理解するために、少し説明が必要です。

まず、39行目で、4行目で定義した構造体を使用するための変数を作成しています。そして、41行目が実行されると、これ以降使用するファイルを指定します。ここは非常に重要なので注意してください。41行目がなければ、その後に行われる呼び出しは実質的な効果を持ちません。

そして、ここで特に興味深いのが45行目です。この時点で、それまで41行目で指定したファイルに向けられていた情報の流れを、同じファイル名を持つ別のサンドボックスへ切り替えています。しかし、それだけではありません。ここで注意してほしいのは、ファイル名自体は41行目で指定したものと同じだということです。それにもかかわらず、ここではMetaTrader 5に対して、書き込み先を別のサンドボックスに切り替えるよう指示しています。

そんなことができるのか。はい、できます。コードをどのように実装するべきか、あるいはどのように実装できるかにかかわらず、指定したサンドボックスに対する書き込み権限がある限り、利用可能な任意のファイルへデータの流れを簡単に切り替えることができます。

実際には、これはコードから受ける印象以上に興味深い仕組みです。コード08を実行すると、以下に示すように2つのファイルが作成されます。

図04

図05

ここで注目してほしいのは、2つのファイルの内容が異なっていることです。さらに、緑色で強調されているディレクトリも異なっています。


最終的な考察

この記事では、ファイルシステムを扱うための万能な方法は存在しないということを学びました。最初は、多くの人にとって難しく感じられるかもしれない内容を実際にコードとして扱い始めました。しかし、時間をかけて勉強し、繰り返し実践していけば、次第にこのような処理を楽しめるようになり、面白さを感じ、自信を持って扱えるようになります。魔法のような解決方法があるわけではありません。重要なのは、さまざまな概念を適切に組み合わせ、最終的に目的とする結果を実現することです。

ですから、読者の皆さんも、今回紹介した内容をぜひ勉強して、実際にコードを書いて試してみてください。正しい理解に加えて、継続的な練習、同じ処理を別の方法で実現できないか考え、新しい実装方法を探し続けることを積み重ねることで、必要な知識が身についていきます。添付ファイルには、今回説明した内容を勉強・練習するためのコードが含まれています。ただし、添付されたコードをそのまま使用しないでください。特にコード08を自分で変更してみることをおすすめします。そして、41行目と45行目を変更すると、プログラム全体の実行結果がどのように変化するのかを確認してみてください。将来的に、クラスについて学んだ後には、コード08にはさらに大きな改善の余地があり、適切に設計すれば、より簡単かつ使いやすい形にできることが分かるでしょう。ただし、そのためにはまず、この構造化コードがどのように動作しているのかをしっかり理解する必要があります。

MQ5ファイル 説明
コード01
 ファイルアクセスの例
コード02   ファイルアクセスの例 
コード03   ファイルアクセスの例
コード04   ファイルアクセスの例
コード05   ファイルアクセスの例
コード06   ファイルアクセスの例
コード07   ファイルアクセスの例
コード08   ファイルアクセスの例

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

添付されたファイル |
Anexo.zip (3.58 KB)
MQL5における建値機能の実装(第2回):ATRとRRRに基づく建値機構 MQL5における建値機能の実装(第2回):ATRとRRRに基づく建値機構
MQL5におけるATRおよびRRRRベースの建値機構の実装を完了し、パラメータを再入力することなく建値モードを簡単に切り替えられるクラスを新たに実装します。各建値方式の有効性を評価するため、複数のバックテストを実施し、アルゴリズム取引の観点からそれぞれのメリットとデメリットを分析します。
科学者コミュニティ最適化 (CoSO):実装編 科学者コミュニティ最適化 (CoSO):実装編
科学コミュニティを題材にした最適化手法の話題を引き続き取り上げます。CoSOは完成されたソリューションとして捉えるべきではなく、将来性のある研究プラットフォームとして考えるべきです。適切に発展させることで、CoSOは、適応性と変化への耐性が重要であり、計算時間がそれほど重要視されないタスクにおいて、その有用性を発揮できる可能性があります。
MQL5での量子ニューラルネットワーク(第3回):量子ビットに基づく仮想量子プロセッサ MQL5での量子ニューラルネットワーク(第3回):量子ビットに基づく仮想量子プロセッサ
数学的アナロジーではなく、量子原理に基づく本格的な量子シミュレータを使用した取引システムの構築に焦点を当てています。このシステムでは、3つの仮想量子ビット、量子ゲート、重ね合わせの原理を使用して市場を分析します。また、MQL5で実装されたMetaTrader 5用自動売買EAとして実装されています。最大の成果は、単なるシミュレーションから、金融情報処理における実際の量子原理の利用へ移行したことです。
MQL5での量子ニューラルネットワーク(第2回):ALGLIBのマルコフ行列を用いたバックプロパゲーション学習 MQL5での量子ニューラルネットワーク(第2回):ALGLIBのマルコフ行列を用いたバックプロパゲーション学習
量子力学の原理と最新の機械学習手法を組み合わせた、アルゴリズム取引向けの革新的な量子ニューラルネットワークアーキテクチャを紹介します。このシステムには、量子的な効果(共鳴、干渉、デコヒーレンス)、異なる時間スケールに対応した多層記憶、ALGLIBライブラリを用いたマルコフ連鎖、そして適応型パラメータ制御が組み込まれています。すべてMQL5で実装されており、組み込みの行列・ベクトル型を使用することで、MetaTrader 5でのでの実装ハードルを下げています。