English Русский 中文 Español Português
preview
初級から中級まで:構造体(II)

初級から中級まで:構造体(II)

MetaTrader 5 |
11 0
CODE X
CODE X

前回の「初級から中級まで:構造体(I)」では、私の考えでは非常に重要なテーマについて議論を始めました。それは、構造体を理解することで、さまざまなことをより簡単におこなえるようになるからです。しかし、構造体を理解する真の重要性は、構造体がオブジェクト指向プログラミングと従来型プログラミングの中間に位置している点にあります。

このテーマはまだ始まったばかりで、真に「構造体を使いこなせる」と言えるようになるためには、まだ多くのことを学ぶ必要があります。


関数や手続きでの構造体の使用

構造体を扱うときに、多くの初心者が戸惑うのは、値を構造体で渡すべきかどうかという点です。実際、これは非常に興味深いテーマであり、ときには他のどの問題よりも混乱を招くことがあります。多くの人が誤解している理由は、構造体に限らず、変数を参照渡しで関数やプロシージャに渡すことができるためです。そして、その際には構造体を扱うときに注意が必要です。

私自身は古い世代のプログラマで、かつてC言語では構造体を使ってデータを直接渡すことができなかった時代を経験しています。現在ではこれは可能ですが、当時は別の手段を使わなければなりませんでした。その場合、構造体に変数が増えるほどエラーが発生する可能性も高くなりました。しかし、それは過去の話です。現在では、同じタイプのデータ転送をより安全におこなえる仕組みがあります。それでも、処理速度を重視し、セキュリティを考慮しない古い実装方法を使うことを妨げるものはありません。

とはいえ、こうしたことが可能だからといって、私はその実装方法をここで示すつもりはありません。理由は、難易度が高く、エラーのリスクが非常に大きいためです。したがって、まずは正しい方法でおこなうことを学びましょう。重要なのは、構造体は必ずしも異なるルーチン間で渡す必要はない、ということです。言い換えれば、関数や手続きが構造体を扱っているか、個別の値を扱っているかを必ず知る必要はありません。

他の記事で既に説明したメカニズムを活用すれば、コードははるかに柔軟で使いやすくなります。というのも、一度構造体を使い始めると、あるルーチンの動作が制限される場合があるからです。

このことを理解するために、以下の簡単なコード例を見てみましょう。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     MqlDateTime dt;
07. 
08.     TimeToStruct(TimeLocal(), dt);
09. 
10.     Print(DateToString(dt.day, dt.mon, dt.year));
11.     Print(DateToString(3, 15, 2024));
12. }
13. //+------------------------------------------------------------------+
14. string DateToString(int day, int month, int year)
15. {
16.     if ((day < 1) || (day > 31) || (month < 1) || (month > 12))
17.         return "ERROR...";
18. 
19.     return StringFormat("%02d/%02d/%d", day, month, year);
20. }
21. //+------------------------------------------------------------------+

コード01

このコード01を実行すると、以下に示す結果が生成されます。

図01

コード01では、複雑なことや理解が難しいことはほとんど使っていないことに注目してください。しかし、このコードを実行したときに得られる結果は、図01に示されているものとは確実に異なります。その理由は単純で、関数TimeLocalの使用にあります。この関数は標準のMQL5ライブラリに含まれており、現在の時計値を取得してdatetime型で返します。この型は8バイトのデータで、MetaTrader 5が動作している場所の現在の日付と時刻の情報を持っています。もちろん、この時間は秒単位でカウントされていますが、私たちの目的には関係ありません。しかし、TimeLocalで返されたこの値は、関数TimeToStructによってMqlDateTime構造体を埋めるために使用されます。ここが本記事で注目したいポイントです。MqlDateTime構造体は以下のように宣言されています:

struct MqlDateTime 
  { 
   int year;
   int mon;
   int day;
   int hour;
   int min;
   int sec;
   int day_of_week;
   int day_of_year;
  };

MqlDateTime宣言

ここからが重要な説明です。これは、コード01で私たちが何をしているのかを理解するために重要だからです。TimeToStruct関数は、datetime型として知られている64ビットの値を分解し、先ほど示した構造体宣言にある各変数へ、それぞれ適切な値を割り当てます。これをおこなう方法は他にもありますが、ここでのポイントはそこではありません。重要なのは、一度これらの値が割り当てられれば、コード01の10行目に示されているように、それらを使用できるようになるという点です。ただし、これは14行目の関数において、データを受け取るためにプリミティブ型の引数を使用しているからこそ可能になっています。この方法により、不要な値を切り捨て、実際に必要なものだけを利用することができます。

しかし、この同じアプローチは、11行目に示されているような使い方も可能にします。ところが、16行目でおこなわれているチェックのために、14行目のルーチンは最終的にエラーを返すことになります。なぜなら、なぜなら、15か月からなる年は存在しないからです。

これこそが、私が強調したかった種類の状況です。コード01で示したこのアプローチは、下に示すコード02のように、さまざまな異なる方法で実装することができます。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     MqlDateTime dt;
07. 
08.     TimeToStruct(TimeLocal(), dt);
09. 
10.     Print(DateToString(dt));
11.     dt.day  = 3;
12.     dt.mon  = 15;
13.     dt.year = 2024;
14.     Print(DateToString(dt));
15. }
16. //+------------------------------------------------------------------+
17. string DateToString(MqlDateTime &arg)
18. {
19.     if ((arg.day < 1) || (arg.day > 31) || (arg.mon < 1) || (arg.mon > 12))
20.         return "ERROR...";
21. 
22.     return StringFormat("%02d/%02d/%d", arg.day, arg.mon, arg.year);
23. }
24. //+------------------------------------------------------------------+

コード02

この場合、図01に示されているのと同じ結果が得られます。しかし、コード02の12行目を見るだけで、どこに問題があるのかが明確に分かるため、エラーの理由を理解するのははるかに容易です。ただし、17行目では構造体を参照渡しする必要がありました。これは問題になる可能性もありますが、受け取った引数を定数として扱うことを保証するだけで、簡単に回避することができます。この方法については、すでに他の記事で説明しています。それでもなお、事実として残るのは、この関数が以前よりもはるかに多くの情報にアクセスできるようになったという点です。つまり、この関数は、その処理に必要以上のデータを見ることができてしまいます。この理由から、関数や手続きで構造体をどのように使うべきかについて、普遍的に正しい答えは存在しません。場合によっては、必要以上に多くの情報を渡してしまっている可能性があるからです。

関数や手続きが他のデータについて持つべき「最小限の知識」という考え方は、クラスやオブジェクトを議論する際により頻繁に登場します。これは、オブジェクト指向プログラミングにおいて、この原則が想像以上に強く意識されているためです。ただし、これは将来扱う話題です。現時点で理解しておくべきことは、ルーチン(関数であれプロシージャであれ)が知る必要のある情報は、少なければ少ないほど良いという点です。これにより、宣言し、受け渡しする必要のある情報量が減り、実装が速くなり、将来的なデバッグや改良も容易になります。

さて、ここまで見てきた2つのコードは、特定の処理に関係したものでした。しかし、MQL5にはすでに12個の事前定義された構造体が用意されているにもかかわらず、独自の構造体を作成する必要が生じることは珍しくありません。将来的には、これらの構造体それぞれについて詳しく取り上げる予定です。すでにそのうちの1つであるMqlDateTimeについては簡単に触れましたが、それはあくまで、後でおこなうことになる内容への短い導入に過ぎません。

ここで読者の皆さんは、自分で作成した構造体を使った処理をどのように実装すればよいのかを疑問に思うかもしれません。この問いに正しく答えるために、私は新しいトピックを用意することにします。現在の話題と混ぜたくないからです。答えはある程度このテーマに関連していますが、話を明確に保つことを優先したいのです。


カスタム構造体の操作

前のトピックの最後で提示した質問に対する答えは密接に関連していますが、できるだけ教材として分かりやすくするために、ここでは話題を分けることにします。というのも、MQL5に用意されている12個の事前定義された構造体のいずれかを使う場合、ある決まったやり方で実装することができます。しかし、自分自身で作成した構造体を使って何かを実装する場合、多くのケースで異なる実装方法を取る必要が出てくるからです。これは前の記事で説明した理由によるものですし、さらにもう1つ、これから説明する理由もあります。

その前に、まず次の点を理解しておきましょう。 

構造体とは、あらゆる情報を含むことができる特別なデータ型です。

この文は、正しく理解することが非常に重要です。構造体が実際にはどのようなものであるかを本当に理解できれば、それをコードの中でどのように実装し、どのように使うべきかを理解するのはずっと簡単になります。ですから読者の皆さんには、構造体はucharやdouble、あるいは他のどんな型とも本質的に変わらないということを理解してほしいのです。これらの型が内部に値を保持し、それを使って処理をおこなえるのと同じように、構造体でも同じことができます。この点は、以前に見た共用体と少し似ています。共用体は厳密には特別なデータ型というよりも、同じメモリ領域を共有するデータの集合でした。

構造体が確かに特別なデータ型であるという考えを強めるために、登録レコードを例に考えてみましょう。登録レコードには、名前、姓、住所、職業、連絡先など、さまざまな情報が含まれることがあります。これらすべてのデータは、同じ基本フォーマットを共有するように整理することができます。そうすることで、レコードの操作を非常にシンプルかつ実用的におこなえるようになります。

これを実現する方法は、基本的に2つあります。1つ目は、レコードに保存したい情報を格納するために、複数の変数を個別に作成する方法です。そして、それらの変数はコード全体に散在することになります。以下に示すような形です。もちろん、これはあくまで仮の例です。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     string      Name,
07.                 Surname,
08.                 Address,
09.                 Position;
10.     double      Remuneration;
11.     datetime    Birth,
12.                 Hiring;
13. 
                   .
                   .
                   .
            Registration routines
                   .
                   .
                   .

コード03

アプリケーションの目的が「単一のレコードを扱うこと」でない限り、実際のコードがコード03に示されているような形になる可能性は低いでしょう。ただし、レコードがディスク上に保存されている場合には、コード03に似たコードが存在することはあり得ます。しかし、その場合にはいくつかの問題が発生します。たとえば、変数をどの順序で保存する必要があるかといった問題です。とはいえ、現時点で私たちが本当に関心を持っているのはその問題ではありません。本当の問題は、異なるレコード間に存在する相互参照やデータ同士の関係をどう扱うかという点にあります。

では、これをどのように処理すればよいのでしょうか。最も一般的な方法は、配列を作成することです。すでに配列については説明しているので、以下のようなものを作ることをすぐに思いつくかもしれません。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06. #define def_Number_of_Records  10
07. 
08.     string      Name[def_Number_of_Records],
09.                 Surname[def_Number_of_Records],
10.                 Address[def_Number_of_Records],
11.                 Position[def_Number_of_Records];
12.     double      Remuneration[def_Number_of_Records];
13.     datetime    Birth[def_Number_of_Records],
14.                 Hiring[def_Number_of_Records];
15. 
                   .
                   .
                   .
            Registration routines
                   .
                   .
                   .

コード04

ここで注目してほしいのは、これで複数のレコードを同時に扱えるようになったという点です。しかし、この手法は機能はするものの、かなり不格好です。なぜなら、フィールドを追加したり削除したりする必要が生じた場合、単純な変更であるにもかかわらず、多大な作業が必要になるからです。しかも、ここで言っているのは使用方法の話ではなく、実装そのものの問題です。ここで、コード04を見て、これまで構造体について議論してきた内容を踏まえると、コード04にあるすべてのものを、構造体の中にまとめることはできないのかという疑問が浮かぶかもしれません。そして、まさにここから話が腑に落ちてくるのです。なぜなら、これまでのコードで見られたすべての複雑さが、次に示すような形へと集約されるからです。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06. #define def_Number_of_Records  10
07. 
08.     struct st_Register
09.     {
10.         string      Name,
11.                     Surname,
12.                     Address,
13.                     Position;
14.         double      Remuneration;
15.         datetime    Birth,
16.                     Hiring;
17.     };
18.     
19.     st_Register Record[def_Number_of_Records];
20. 
                   .
                   .
                   .
            Registration routines
                   .
                   .
                   .

コード05

この考え方が、いかに自然に出てくるかに注目してください。この概念が形を成し、意味を持つために、特別なアプリケーションを作る必要はありませんでした。しかし、ここからが本当に面白くなります。コード05の8行目で宣言されている構造体は、手続きOnStartの内部、もしくは宣言された場所でのみアクセス可能です。この場合、st_Register構造体は、コード02のような方法では関数や手続きにデータを渡すことができません。コード05の8行目で宣言された構造体に含まれる情報を利用するには、コード01に非常によく似たアプローチを採用する必要があります。

場合によっては、これは問題になります。また別の場合には、これがまさに必要とされる振る舞いであることもあります。しかし一般的には、構造体(共用体とは異なり)はグローバルスコープで宣言されることが多いです。そうすることで、より快適かつ実用的な方法でデータを受け渡すことができます。

PythonやJavaScriptのような言語を少しでも学んだことがある人なら、次のような記述が非常に一般的であることを知っているでしょう。

cmd_entry.pack(fill='x', side='left' , expand=1)

この1行のコードはPythonとJavaScriptの両方で有効ですが、MQL5、C、C++を使っている人にとっては意味不明に見えます。というのも、これらの言語では、値を変数に代入するということは、コード中に存在する実体のある変数(グローバルかローカル)に値を代入することを意味するからです。そして、その変数を探せば、MQL5、C、あるいはC++の用語で言うところの、いずれかのスコープの中に必ず見つかります。

しかし、同じコード片をPythonやJavaScriptで見た場合、実際には変数に値を代入しているわけではありません。これは完全に直感に反するかもしれませんが、実際には、構造体に似た何かに値を代入しているのです。その結果、どんな要素にも、どんな順序でも値を代入できるようになります。これは、たとえばMQL5では不可能なことです。

それでも、このコードをMQL5における構造体に類似したものとして捉えると、すべてが理解できるようになります。特に、多数の引数を関数や手続き間で受け渡す必要がある場合、これは非常にシンプルで快適な方法になります。

ここは特に注意して読んでください。なぜなら、この考え方は、今後の問題への取り組み方を変える可能性があるからです。以前の記事で、関数や手続きをオーバーロードできることについて説明しました。これにより、異なる型や異なる数の引数を扱うことができます。しかし、場合によっては、これらの値を構造体を使って渡すことで、本来必要となる多くのオーバーロードが不要になります。これは、任意の数の引数を、単にそれらを構造体の中に入れるだけで渡すことができるからです。

なかなか興味深いと思いませんか。実際のところ、完璧で絶対的なやり方というものは存在しません。ただし、ケースによっては、より適している、あるいはより魅力的なアプローチは確かに存在します。

では、次のような状況を想定してみましょう。コード05が、引数の数が多かったり少なかったりする複数の関数や手続きを使用しているとします。コード01のような方法を使って各引数を個別に制御することもできますが、その代償として、複数箇所で関数のオーバーロードが必要になるかもしれません。あるいはコード02の方法を採用することもできますが、その場合は、気づかないうちに値が変更されてしまうリスクを負うことになります。しかし、多くの場合に採用されている解決策があります。それは、値を変更したい場合には関数を使い、変更せず参照だけしたい場合には手続きを使うという方法です。

いずれにしても、コード05の8行目で宣言された構造体は、ローカルスコープから外に出し、グローバルスコープに配置する必要があります。それが、次に示す仮想的なコードです。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct st_Register
05. {
06.     string      Name,
07.                 Surname,
08.                 Address,
09.                 Position;
10.     double      Remuneration;
11.     datetime    Birth,
12.                 Hiring;
13. };
14. //+------------------------------------------------------------------+
15. void OnStart(void)
16. {   
17.     st_Register Record[10];
18.     uchar       Counter = 0,
19.                 Index;
20. 
21.     Index = Counter;
22.     Counter += (NewRecord(Record[Counter], "Daniel", "Jose", "Brasil", "1971/03/30") ? 1 : 0);
23.     Record[Index] = UpdatePosition(Record[Index], "Chief Programmer");
24.     Index = Counter;
25.     Counter += (NewRecord(Record[Counter], "Edimarcos", "Alcantra", "Brasil", "1974/12/07") ? 1 : 0);
26.     Record[Index] = UpdatePosition(Record[Index], "Programmer");
27.     Index = Counter;
28.     Counter += (NewRecord(Record[Counter], "Carlos", "Almeida", "Brasil", "1985/11/15") ? 1 : 0);
29.     Record[Index] = UpdatePosition(Record[Index], "Junior Programmer");
30.     Index = Counter;
31.     Counter += (NewRecord(Record[Counter], "Yara", "Alves", "Brasil", "1978/07/25") ? 1 : 0);
32.     Record[Index] = UpdatePosition(Record[Index], "Accounting");
33. 
34.     Print("Number of records: ", Counter);
35.     ViewRecord(Record[3]);
36. }
37. //+------------------------------------------------------------------+
38. bool NewRecord(st_Register &arg, const string Name, const string Surname, const string Address, const string Birth)
39. {
40.     arg.Name = Name;
41.     arg.Surname = Surname;
42.     arg.Address = Address;
43.     arg.Birth = StringToTime(Birth);
44.     arg.Hiring = TimeLocal();
45. 
46.     return true;
47. }
48. //+------------------------------------------------------------------+
49. st_Register UpdatePosition(const st_Register &arg, const string NewPosition)
50. {
51.     st_Register info = arg;
52. 
53.     info.Position = NewPosition;
54. 
55.     return info;
56. }
57. //+------------------------------------------------------------------+
58. void ViewRecord(const st_Register &arg)
59. {
60.     PrintFormat("Collaborator: %s, %s\nPosition: %s\nBirth in: %s",
61.                     arg.Surname,
62.                     arg.Name,
63.                     arg.Position,
64.                     TimeToString(arg.Birth, TIME_DATE));
65. }
66. //+------------------------------------------------------------------+

コード06

コード06はやや主観的な例であり、多くの人にとって「実際には作られそうにない」と感じられるものであることは承知しています。しかし、たとえ仮想的な例にすぎないとしても、このコードには実際の現場コードに現れる多くの要素や配慮点が含まれています。これを実行すると、次のような結果が得られます。

図03

ここでは、いくつものことが同時に起こっています。そして、このコードは一見すると複雑で理解しづらく見えるかもしれませんが、これまでの記事で示してきた内容を学び、実際に手を動かしてきた人にとっては、実は非常にシンプルで、明確かつ率直なコードです。ただし、生年月日など、やや違和感のある要素も含まれているため、ここで簡単に何がおこなわれているのかを説明しておきます。

4行目では、データ構造を宣言しています。注目すべき点は、これがグローバルスコープで宣言されているということです。そのため、この構造体はメインのコードブロックの外、つまりこの場合は手続きOnStartの外からも使用できます。メインブロックの内部では、17行目で宣言がおこなわれており、これは情報の一覧を作成することを目的としたものです。言い換えると、この構造体は実際に特別な型の変数として扱われることになります。その後、21行目から32行目の間で、複数のレコードをこの一覧に追加しています。ここで覚えておいてください。配列内の各要素は、それぞれが完全に独立した要素です。しかし、すべてを1つのブロック(4行目で宣言された構造体)の中にまとめているため、あたかも各要素が物理的につながっており、1枚のレコードシートを形成しているかのように感じられます。

このように、21行目から32行目の各呼び出しでは、まず38行目の関数を通じて、新しいレコードを配列に追加しようとします。ここで注目してほしいのは、38行目の関数には、必要最小限の権限しか与えていないという点です。いくつかの情報だけを引数として渡し、他の値については、ルーチン自身が直接割り当てるようにしています。たとえば44行目では、現在の日付を取得し、それを雇用日として設定しています。実際の運用では、このような実装は非常に一般的です。なぜなら、従業員レコードが作成される瞬間こそが、まさに雇用された瞬間だからです。このため、38行目の関数が、引数として渡されていない値を、返される構造体に直接設定することを許可しているのです。

もちろん、実際のコードでは、登録段階でのエラーを防ぐために、多くのチェックがおこなわれるでしょう。しかし、このコードは純粋に教育目的のものであるため、そのようなチェックは省略しています。

各レコードには、38行目のNewRecord関数に直接渡されない追加のデータが必要となるため、49行目を使って、すでに作成されたレコードに対して特定の値を更新または代入しています。ここでも、実際のコードであれば多数のチェックが入ることになります。

しかし、49行目の関数の目的は、値を返す方法を示すことにあります。この場合、返されるのは、関数内部で作成されたその構造体そのものです。このやり方により、構造体内の値をいつどこで変更するかについて、より細かな制御が可能になります。

38行目とは異なり、49行目では、入力として受け取った構造体を直接恒久的に変更するのではなく、構造体を変更したうえで、それを呼び出し元に返しています。返された値をどこでどのように使うかは、呼び出し側に委ねられています。そして読者の皆さん、はい、この返り値は、後ほど示すように、さまざまな方法で利用することができます。

最後に、レコードを表示するための小さな手続きがあります。これにより、図03に示す結果が得られます。

では、同じコード06を、異なる形で処理がおこなわれるように作ることはできるでしょうか。答えは「はい」です。そして、それこそが、次に示すコードで見ていく内容なのです。

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. struct st_Register
005. {
006.     uchar       ID;
007.     string      Name,
008.                 Surname,
009.                 Address,
010.                 Position;
011.     double      Remuneration;
012.     datetime    Birth,
013.                 Hiring;
014. }gl_Record[10];
015. //+------------------------------------------------------------------+
016. void OnStart(void)
017. {
018.     uchar Index;
019. 
020.     ZeroMemory(gl_Record);
021. 
022.     NewCollaborator("Daniel", "Jose", "Brasil", "1971/03/30", "Chief Programmer");
023.     NewCollaborator("Edimarcos", "Alcantra", "Brasil", "1974/12/07", "Programmer");
024.     NewCollaborator("Carlos", "Almeida", "Brasil", "1985/11/15", "Junior Programmer");
025. 
026.     Index = GetNumberRecord();
027. 
028.     if (NewRecord(gl_Record[Index], "Yara", "Alves", "Brasil", "1978/07/25"))
029.     {
030.         gl_Record[Index].ID = Index + 1;
031.         gl_Record[Index] = UpdatePosition(gl_Record[Index], "Accounting");
032.     }
033. 
034.     Print("Number of records: ", GetNumberRecord());
035.     Print("--------------------");
036. 
037.     for(uchar c = 1; ViewRecord(c); c++)
038.         Print("********************");
039. }
040. //+------------------------------------------------------------------+
041. bool NewCollaborator(const string Name, const string Surname, const string Address, const string Birth, const string Position)
042. {
043.     st_Register info;
044.     uchar       Index = 0;
045. 
046.     if (!NewRecord(info, Name, Surname, Address, Birth))
047.         return false;
048. 
049.     info = UpdatePosition(info, Position);
050. 
051.     while (gl_Record[Index].ID)
052.     {
053.         if (Index >= gl_Record.Size()) return false;
054.         Index++;
055.     }
056. 
057.     info.ID = Index + 1;
058.     gl_Record[Index] = info;
059. 
060.     return true;
061. }
062. //+------------------------------------------------------------------+
063. bool NewRecord(st_Register &arg, const string Name, const string Surname, const string Address, const string Birth)
064. {
065.     arg.Name = Name;
066.     arg.Surname = Surname;
067.     arg.Address = Address;
068.     arg.Birth = StringToTime(Birth);
069.     arg.Hiring = TimeLocal();
070. 
071.     return true;
072. }
073. //+------------------------------------------------------------------+
074. st_Register UpdatePosition(const st_Register &arg, const string NewPosition)
075. {
076.     st_Register info = arg;
077. 
078.     info.Position = NewPosition;
079. 
080.     return info;
081. }
082. //+------------------------------------------------------------------+
083. uchar GetNumberRecord(void)
084. {
085.     uchar counter = 0;
086. 
087.     for (uchar c = 0; c < gl_Record.Size(); counter += (gl_Record[c].ID ? 1 : 0), c++);
088. 
089.     return counter;
090. }
091. //+------------------------------------------------------------------+
092. bool ViewRecord(const uchar ID)
093. {
094.     st_Register info;
095. 
096.     ZeroMemory(info);
097. 
098.     for (uchar c = 0; (c < gl_Record.Size()) && (!info.ID); c++)
099.         info = (gl_Record[c].ID == ID ? gl_Record[c] : info);
100. 
101.     if (info.ID)
102.         PrintFormat("Collaborator: %s, %s\nPosition: %s\nBirth in: %s",
103.                         info.Surname,
104.                         info.Name,
105.                         info.Position,
106.                         TimeToString(info.Birth, TIME_DATE));
107.     else
108.         Print("Record ID [", ID ,"] not found.");
109. 
110.     return (bool) info.ID;
111. }
112. //+------------------------------------------------------------------+

コード07

さて、ここで私たちは、本当にかなり複雑なものを扱うことになります。というのも、コード07は間違いなくより複雑であり、正直に言って、ここで何がおこなわれているのかはほとんど理解できません(苦笑)。とはいえ、本音を言えば、コード07は実際には非常にシンプルです。率直に言うと、私の見解では、コード07はコード06よりもシンプルです。両者は本質的に同じことをしているにもかかわらずです。ただし、コード07のほうがより複雑に見えると感じる読者がいるであろうことも理解しています。

まず、14行目でグローバル変数の宣言がおこなわれているという事実があります。これだけで、必要以上に物事が複雑に見えてしまいます。しかし、コード07が実際に何をしているのかを詳しく議論する前に、まずは実行結果を見てみましょう。それが次の画像に示されています。

図04

ここで注目してほしいのは、メインブロック内で作成したすべてのレコードが、この図に表示されているという点です。これは理想的な結果であり、コード07がきちんと役割を果たしていることを示しています。では、どのようにしてこれが実現されたのでしょうか。特に、ここには新しい要素がいくつも登場しています。前の記事で述べたように、構造体は、従来型プログラミングとオブジェクト指向プログラミングの中間的な存在です。しかし、構造体で本当に何ができるのかを探るには、私たちはまだごく初期段階にいます。

次に、22行目から32行目が、新しいレコードをシステムに挿入するために使われている点に注目してください。ここが重要なポイントかもしれません。22行目から24行目でおこなわれているレコード作成は、一見して比較的理解しやすいのに対し、26行目から32行目でおこなわれている処理はそうではありません。後者では、41行目の関数がおこなっているのと同じ作業を、完全に手作業でおこなっています。このようなやり方をしているという事実そのものが、コードをより危険なものにしています。ほんの小さなミスであっても、何時間ものトラブルシューティングを強いられる可能性があります。

ぜひコード07を注意深く読み込み、これを実際の動作として理解してみてください。そして、コード06で結果を得る難しさと、コード07を使って同じ結果を得る難しさを比較してみてください。これを理解することで、次の記事で議論される内容が、より追いやすくなるはずです。

とはいえ、コード07の中でおそらく最も複雑なのは、92行目の関数でしょう。では、この関数がどのように動作しているのかを見ていきましょう。まず、94行目で一時的な構造体を宣言します。次に、96行目でその構造体が配置されるメモリ領域をクリアします。これにより、98行目のループ内にあるテストを使って、要求されたIDが見つかったかどうかを判定できるようになります。

その後ループが終了し、101行目でMetaTrader 5のターミナルに出力する内容があるかどうかを確認します。IDが見つかっていれば、レコード構造体の内容を出力します。見つからなかった場合は、108行目に示されているメッセージを出力します。

この関数の戻り値の型はブール型であり、IDは数値であるため、その点をコンパイラに明示します。そこで110行目では、要求されたIDの検索が成功したかどうかを呼び出し元に返すため、明示的な型変換をおこないます。この戻り値は、37行目のループがいつ停止するかを判断するために重要です。


最終的な考察

本記事では、比較的シンプルなコードの中で構造体をどのように使用するかを見てきました。ここで扱った内容は主に教育目的のものですが、結果としてデータベースに非常に近いものを構築することができました。確かに、実際のデータベースであれば、エラーを防ぐために数多くのテストが必要になります。しかし、構造体という概念を示すという主な目的は達成できたと考えています。

言い換えれば、構造体はまさに特別な種類の変数です。ですから、添付されているコードをじっくりと学び、実際に手を動かして練習してみてください。すべてのコードについて一行ずつ詳細な説明はしていませんが、これまでの記事の内容を学習し、実践してきた人であれば、ここで扱っている内容は十分に理解できるはずです。

次回の記事では、構造体をさらに実用的で興味深い方法で使用する例を取り上げ、コーディングのプロセスを大幅に簡単にする方法を紹介します。

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

添付されたファイル |
Anexo.zip (2.8 KB)
市場シミュレーション(第8回):ソケット(II) 市場シミュレーション(第8回):ソケット(II)
ソケットを使って何か実用的なものを作ってみましょう。今回の記事では、ミニチャットの作成を始めます。一緒にどのようにおこなうかを見ていきましょう。とても面白い内容になるでしょう。ここで提供するコードは教育目的のみの使用を想定しています。商用目的や既製のアプリケーションでの使用には適していません。ソケット上で送信されるデータは安全に保護されず、内容が第三者からアクセス可能になる可能性があるためです。
取引におけるニューラルネットワーク:ResNeXtモデルに基づくマルチタスク学習(最終回) 取引におけるニューラルネットワーク:ResNeXtモデルに基づくマルチタスク学習(最終回)
ResNeXtに基づくマルチタスク学習フレームワークの探求を続けます。このフレームワークは、モジュール性が高く、計算効率に優れ、データ中の安定したパターンを特定できることが特徴です。単一のエンコーダーと専門化された「ヘッド」を使用することで、モデルの過学習のリスクを減らし、予測の精度を向上させます。
外国為替におけるフィボナッチ(第1回):価格と時間の関係を調べる 外国為替におけるフィボナッチ(第1回):価格と時間の関係を調べる
市場はフィボナッチに基づく関係性をどのように観測しているのでしょうか。各項が直前の2つの項の和になっているこの数列(1, 1, 2, 3, 5, 8, 13, 21...)は、ウサギの個体数の増加を説明するだけのものではありません。私たちは、「世界のあらゆるものは数の一定の関係に従う」というピタゴラス派の仮説を考察します。
取引におけるニューラルネットワーク:ResNeXtモデルに基づくマルチタスク学習 取引におけるニューラルネットワーク:ResNeXtモデルに基づくマルチタスク学習
ResNeXtに基づくマルチタスク学習フレームワークは、金融データの高次元性、非線形性、時間依存性を考慮しながら分析を最適化します。グループ畳み込みと専用ヘッドの使用により、モデルは入力データから重要な特徴を効果的に抽出することができます。