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

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

MetaTrader 5 |
22 0
CODE X
CODE X

はじめに

前回の「初級から中級まで:構造体(VI)」では、シンプルなデータ構造の汎用実装をどのように構築し始めるかを確認しました。一見すると特殊に思えるかもしれませんが、このようなモデリングは多くの人が想像する以上に頻繁に使用されています。なぜなら、大規模なデータベースも同様の原理でデータを整理し、検索をおこなっているからです。

本連載で扱っている内容は、一部の方にとっては冗長に感じられるかもしれません。しかし、ここでの目的は、将来的に細かな説明を補足する必要がないレベルまで理解を積み上げることにあります。言い換えれば、最初に理解できることをできる限り丁寧に伝えることで、後の学習が大幅に楽になるということです。

単に動作するコードを見るだけでは不十分であり、「なぜそのコードが動作するのか」を理解し、必要に応じて他のプログラマーが書いた実装を自分の目的に合わせて適応できる必要があります。そのためには、こうした基礎概念の理解と、それがどのように応用されるかを把握することが不可欠です。

前回の記事と同様に、構造体を別の構造体の中でどのように活用できるかを理解するための興味深い実装を試しました。前回のコードの結果に少し意外性を感じた方もいるかもしれません。それは、本来ある目的のために作成された構造体を、別の用途にも適用できるようにしたためです。このとき、元々double型だったデータを、最終的には任意の型として扱えるようになりました。

しかし、ここで示したものは本来目指していた最終形ではありません。そこに到達するには、もう少し先へ進む必要があります。ただし、この過程での学習は必ず将来的に大きな価値を持つものになります。お約束します。それでは、前回の記事の続きから進めていきましょう。


構造体の構造体(第2部):帰還

前回の記事では、以下に示すコードで終了しました。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. template <typename T>
05. struct st_Data
06. {
07.     //+----------------+
08.     private:
09.     //+----------------+
10.         struct st_Reg
11.         {
12.             T       h_value;
13.             uint    k_value;
14.         }Values[];
15.     //+----------------+
16.         string ConvertToString(T arg)
17.         {
18.             if ((typename(T) == "double") || (typename(T) == "float")) return DoubleToString(arg, 2);
19.             if (typename(T) == "string") return arg;
20. 
21.             return IntegerToString(arg);
22.         }
23.     //+----------------+
24.     public:
25.     //+----------------+
26.         bool Set(const uint &arg1[], const T &arg2[])
27.         {
28.             if (arg1.Size() != arg2.Size())
29.                 return false;
30.             
31.             ArrayResize(Values, arg1.Size());
32.             for (uint c = 0; c < arg1.Size(); c++)
33.             {
34.                 Values[c].k_value = arg1[c];
35.                 Values[c].h_value = arg2[c];
36.             }
37. 
38.             return true;
39.         }
40.     //+----------------+
41.         string Get(const uint index)
42.         {
43.             for (uint c = 0; c < Values.Size(); c++)
44.                 if (Values[c].k_value == index)
45.                     return ConvertToString(Values[c].h_value);
46. 
47.             return "-nan";
48.         }
49.     //+----------------+
50. };
51. //+------------------------------------------------------------------+
52. #define PrintX(X) Print(#X, " => ", X)
53. //+------------------------------------------------------------------+
54. void OnStart(void)
55. {
56.     const string T = "possible loss of data due to type conversion";
57.     const uint   K[] = {2, 1, 4, 0, 7, 5, 3, 6};
58. 
59.     st_Data <string> info;
60.     string H[];
61. 
62.     StringSplit(T, ' ', H);
63.     info.Set(K, H);
64.     PrintX(info.Get(3));
65. }
66. //+------------------------------------------------------------------+

コード01

コード01は正常に動作しますが、少し厄介な点があります。コンパイル時に、以下のようなメッセージが表示されるためです。

図01

図01のような表示は、いくつかの不便を引き起こします。これは、実装の種類によってはコンパイラのメッセージが将来的なエラーにつながる可能性があるためです。コード自体が誤っているわけではありませんが、コンパイラが潜在的な重大エラーの警告を出している場合もあります。そのため、エラーメッセージを無視すると、本当に重要な警告を見落とす可能性があります。

いずれにせよ、コード01を実行すると以下の結果が得られます。

図02

興味深い結果です。しかしその前に、図01で示された問題を解決しましょう。方法はいくつかありますが、最終的にはすべて「明示的型変換」に帰着します。つまり、どのように実現するかは重要ではなく、明示的変換がおこなわれていること自体が重要です。そのため、コード01は以下のように修正できます。

                   .
                   .
                   .
15.     //+----------------+
16.         string ConvertToString(T arg)
17.         {
18.             if (typename(T) == "double") return DoubleToString((double)arg, 2);
19.             if (typename(T) == "float") return DoubleToString((float)arg, 2);
20.             if (typename(T) == "string") return (string)arg;
21.             return IntegerToString((long)arg);
22.         }
23.     //+----------------+
24.     public:
25.     //+----------------+
                   .
                   .
                   .

コード02

この図01の問題の解決策がコード02だと知ると、少し拍子抜けするかもしれません。多くの方は、もっと大規模な手順や関数を期待していたでしょう。しかし、実際にはコード02に示されているコードだけで済む場合もあります。このようなこともプロセスの一部です。どれだけ落胆するかは、事前にどれだけ期待していたかによります。冗談はさておき、コードに戻り、どのようにすれば大きな変更を加えずに異なる種類のデータを扱えるよう、より拡張性を持たせられるのかを理解していきましょう。

複雑に思えるかもしれません。しかし、それを説明する前に、配列のある要素を別の配列の要素へどのように、そしてなぜ関連付けるのかを正しく理解する必要があります。

この概念自体は非常に単純です。これは単に対応付けに過ぎません。つまり、キーとなる配列要素が分かっていれば、その結果は別の配列の要素として返されるということです。コード01ではまさにこれをおこなっています。では、なぜここで多次元配列を使わないのでしょうか。理論的には多次元配列が最も適切ですが、今回は概念説明としての実装であるため適していません。つまり、特定の言語機能の使い方を示すことが目的だからです。

さて、ここまでの説明で理解できたと思いますので、もう一度Code01を見てください。そして、値の変換だけでなく、他の類似した用途にも使えない原因は何かを考えてみてください。

結論を急がないでください。少し考えてみましょう。ここからは、コードをより柔軟にし、あらゆるものを格納できるようにする方法の一つを見ていきます。しかしコードを拡張しようとすると、問題は10行目で宣言されている構造体にあることが分かります。ただし、問題といっても一般的な意味での問題ではありません。

10行目の構造体の問題は、この構造体がメイン構造の汎用化を妨げている点にあります。これが難しく時間がかかる内容であることは理解していますが、この記事は続けます。ただし、後で混乱しないように、このシリーズと前回の記事を繰り返し読み返すことを推奨します。

構造体も他の構造と同様に、より単純または複雑なデータを表現するための手段です。問題の解決方法は、その問題の実際の複雑さに依存します。単純な問題であれば、浮動小数点や整数のような離散的な型で十分です。

しかし問題が複雑になると、単純な型では不十分になります。その場合は配列や共用体のようなより複雑なデータ型が必要になります。それでも足りない場合、最終的に構造体が必要になります。

したがって、コード01の10行目にある構造体は「複雑なデータ型」の一つに過ぎません。これを理解することが重要です。

まず、コード01を保持したまま、04行目で定義された構造の10行目からこの構造を取得します。しかしこれをおこなうと、話がさらに複雑になり、前回の記事を理解できていない場合はより分かりにくくなります。そのため段階的に進めることはできず、非常に少ないステップで進める必要があります。最初のステップは以下のとおりです。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct st_Reg
05. {
06.     string  h_value;
07.     uint    k_value;
08. };
09. //+----------------+
10. struct st_Data
11. {
12.     //+----------------+
13.     private:
14.     //+----------------+
15.         st_Reg Values[];
16.     //+----------------+
17.     public:
18.     //+----------------+
19.         bool Set(const uint &arg1[], const string &arg2[])
20.         {
21.             if (arg1.Size() != arg2.Size())
22.                 return false;
23.             
24.             ArrayResize(Values, arg1.Size());
25.             for (uint c = 0; c < arg1.Size(); c++)
26.             {
27.                 Values[c].k_value = arg1[c];
28.                 Values[c].h_value = arg2[c];
29.             }
30. 
31.             return true;
32.         }
33.     //+----------------+
34.         string Get(const uint index)
35.         {
36.             for (uint c = 0; c < Values.Size(); c++)
37.                 if (Values[c].k_value == index)
38.                     return Values[c].h_value;
39. 
40.             return NULL;
41.         }
42.     //+----------------+
43. };
44. //+------------------------------------------------------------------+
45. #define PrintX(X) Print(#X, " => ", X)
46. //+------------------------------------------------------------------+
47. void OnStart(void)
48. {
49.     const string T = "possible loss of data due to type conversion";
50.     const uint   K[] = {2, 1, 4, 0, 7, 5, 3, 6};
51. 
52.     st_Data info;
53.     string H[];
54. 
55.     StringSplit(T, ' ', H);
56.     info.Set(K, H);
57.     PrintX(info.Get(3));
58. }
59. //+------------------------------------------------------------------+

コード03

警告:コード(およびこれから行う内容)をできる限りシンプルかつ明確にするため、ここで一度立ち戻ってstring型を使用します。ここで注意してほしいのは、これまで構築してきた汎用化された構造はここでは存在しなくなっており、実行結果は依然として図02と同じであるという点です。

コード03を見て、「なるほど、これが複雑だと言っていたけれど、今のところは単純ではないか。どこが複雑なのか」と思うかもしれません。それでは続けましょう。次に変更すべき点は、SetおよびGet関数(それぞれ19行目と34行目にあるもの)がst_Dataの関数ではなく、st_Regの関数であるという点です。

では、なぜそれらがst_Dataの中に存在しているのでしょうか。その答えは、それらが構造体をより汎用的に動作させることを妨げているためです。その結果、任意の型のデータを扱うことができなくなっています。したがって、コード03におけるst_Data構造体からこれらの関数を削除する必要があります。より分かりやすくするために、修正後のコードスニペットを以下に示します。以下に示します。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct st_Reg
05. {
06.     //+----------------+
07.     private:
08.     //+----------------+
09.         string  h_value;
10.         uint    k_value;
11.     //+----------------+
12.     public:
13.     //+----------------+
14.         void Set(const uint arg1, const string arg2)
15.         {
16.             k_value = arg1;
17.             h_value = arg2;
18.         }
19.     //+----------------+
20.         uint Get_K(void)    { return k_value; }
21.     //+----------------+
22.         string Get_H(void)  { return h_value; }
23.     //+----------------+
24. };
25. //+----------------+
26. struct st_Data
27. {
28.     //+----------------+
29.     private:
30.     //+----------------+
31.         st_Reg Values[];
32.     //+----------------+
33.     public:
34.     //+----------------+
35.         bool Set(const st_Reg &arg)
36.         {
37.             if (ArrayResize(Values, Values.Size() + (Values.Size() == 0 ? 2 : 1)) == INVALID_HANDLE)
38.                 return false;
39. 
40.             Values[Values.Size() - 1] = arg;
41. 
42.             return true;
43.         }
44.     //+----------------+
45.         st_Reg Get(const uint index)
46.         {
47.             for (uint c = 0; c < Values.Size(); c++)
48.                 if (Values[c].Get_K() == index)
49.                     return Values[c];
50. 
51.             return Values[0];
52.         }
53.     //+----------------+
54. };
55. //+------------------------------------------------------------------+
                   .
                   .
                   .

コード04

「なんという狂気だ?」落ち着いてください。これはまだ始まりに過ぎません。新しいst_Data構造の中にデータをまとめるための構造は、1つではなく2つになっています。しかしメインコード(OnStartに入る部分)へ進む前に、ここで何が起きているのかを理解する必要があります。

実際、このコード片はコード03と同じように動作しますが、これまで慣れてきたものとは完全に異なる側面がいくつか存在します。それでも私の見解では、扱っている内容は非常に単純であり、これは依然として初級レベルのコードです。

まず最初に重要なのは、st_Regは構造化コードのために設計された構造体であるという点です。つまり、私たちは変数を直接操作するのではなく、それらを管理するためのコンテキストを構築しているということです。

本質的に、st_Regはネスト構造の話に入る前と同じように動作します。ここからが多くの初心者にとって難しくなる部分です。特に「変数とは何か」「データ型をどう扱うのか」という基本概念が曖昧な場合には混乱します。では、st_Dataという2つ目の構造体で何が起きているのかを理解しましょう。st_Regは複雑なデータ型であるため、31行目ではその型の変数、つまり配列を宣言することができます。つまりst_Reg型のデータを保存するための配列です。ここまでは問題は見えないはずです。

しかし35行目を見ると、急に分かりにくくなります。特に37行目に注目するとさらにそう感じるでしょう。「一体何が起きているのか」この行では単にメモリ領域を確保しているだけです。しかしMQL5には小さな制約があります。それはポインタを返すことができないという点です。そのため45行目の関数では、この制約を回避するための工夫が必要になります。それが37行目の処理です。

注意してください。st_Data構造体に格納された値を検索して見つからなかった場合、無効なデータを返す必要があります。CやC++では通常nullを返すことでこれを実現します。しかしMQL5ではそれができません。そのため少し工夫が必要です。そこで37行目では、st_Reg型データを追加する最初のブロックを確保する際に、2つのメモリ領域を確保します。1つ目は空のまま、しかし2つ目には40行目の値が格納されます。

これは最初の要素であるため2つ分の領域が確保されます。次の呼び出し以降は37行目で既に2要素が存在していると判断されるため、以降は1つずつのみ追加されます。これがst_Data構造体のSet関数が呼ばれるたびに繰り返されます。ただしこれはst_Reg構造体のSet処理とは無関係であり、その理由については後ほど説明されます。

そしてst_Data構造体のGet関数はSet処理と密接に関係しているため、重要なのはGet関数に渡される値が配列のインデックスを示すのではなく、実際に格納されている値そのものを対象としているという点です。そのため特定の値を探すためにはループ処理が必要になります。この点についても後ほど説明します。概ね、以上です。

さて、ここでメインコードを確認できます。思い出してほしいのですが、コード04のスニペットはヘッダーファイルに配置しても問題なく動作します。しかし汎用化を進める前に、残りのコードを確認する必要があります。以下に示します。

                   .
                   .
                   .
55. //+------------------------------------------------------------------+
56. #define PrintX(X) Print(#X, " => [", X, "]")
57. //+------------------------------------------------------------------+
58. void OnStart(void)
59. {
60.     const string T = "possible loss of data due to type conversion";
61.     const uint   K[] = {2, 1, 4, 0, 7, 5, 3, 6};
62. 
63.     st_Data info;
64.     string H[];
65. 
66.     StringSplit(T, ' ', H);
67.     for (uint c = 0; c < H.Size(); c++)
68.     {
69.         st_Reg  reg;
70. 
71.         reg.Set(K[c], H[c]);
72.         info.Set(reg);
73.     }
74. 
75.     PrintX(info.Get(3).Get_H());
76.     PrintX(info.Get(13).Get_H());
77. }
78. //+------------------------------------------------------------------+

コード05

そしてここからが最も楽しく、興味深い部分です。すでに前で示した通り、このコードの多くは理解できているはずなので、新しい部分へ進みましょう。ここで特に重要なのは、67行目のループ、そして75行目と76行目です。

まずループから説明します。st_Data構造体に格納される各要素はst_Reg型であるため、71行目でst_Reg構造体を生成する必要があります。その後72行目で、そのst_Reg型の要素をst_Data構造体に格納します。ここで疑問が生じます。なぜこれをst_Data構造体の中で直接おこなわないのでしょうか。その答えは、もしst_Reg型の要素の記録をst_Data構造体の内部で直接おこなってしまうと、後からこの構造体を拡張できなくなるためです。しかし心配する必要はありません。すぐに理解できるようになります。

すべての要素がst_Data構造体に書き込まれた後、この構造体を75行目と76行目でテストできます。まず75行目では、インデックスが3である文字列を検索しています。そして60行目と61行目を見れば、そのインデックスにどの値が対応しているかが分かります。次に76行目では、インデックスが13である要素を検索します。このインデックスは61行目では定義されていないため、結果としてコード04の51行目で定義された値が返されます。

この前提知識をもとにコードを実行すると、以下の結果が得られます。

図03

つまり、完璧に動作しているということです。これでコードの大部分を一般化する準備が整いましたが、そのためには新しいトピックへ進む必要があります。このようにして、これまでに行った内容を落ち着いて整理しつつ、次に何をすべきかを理解していきます。


構造体の構造体(第3部):復讐

前のトピックの内容が難しく感じられたのであれば、ここからさらに面白くなります。ここからは本当に複雑になります。次の点に注意してください。前回のコード04において、st_Data構造体はst_Reg型という1種類のデータしか扱うことができません。これはst_Dataを汎用的なものにはしていません。結果として、st_Reg構造体で定義された範囲にしか対応できず、それら同士に直接的な関係がなくても制限を受けてしまいます。

しかし、st_Data構造体のコードを少し変更することで、その制約を外し、異なるデータ型を扱えるようにすることができます。ただし重要なのは、これをテンプレート化したとしても、完全にst_Regから独立するわけではないという点です。特にGet関数は依然としてst_Reg型に依存します。それでも、この改善には十分な価値があります。

上記の通り、必要なのはst_Data構造体をテンプレート化し、それに合わせてメインコードを調整することです。これはコンパイラが適切な型を生成するために必要です。これは以下のコードで行います。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. struct st_Reg
05. {
06.     //+----------------+
07.     private:
08.     //+----------------+
09.         string  h_value;
10.         uint    k_value;
11.     //+----------------+
12.     public:
13.     //+----------------+
14.         void Set(const uint arg1, const string arg2)
15.         {
16.             k_value = arg1;
17.             h_value = arg2;
18.         }
19.     //+----------------+
20.         uint Get_K(void)    { return k_value; }
21.     //+----------------+
22.         string Get_H(void)  { return h_value; }
23.     //+----------------+
24. };
25. //+----------------+
26. template <typename T>
27. struct st_Data
28. {
29.     //+----------------+
30.     private:
31.     //+----------------+
32.         T Values[];
33.     //+----------------+
34.     public:
35.     //+----------------+
36.         bool Set(const T &arg)
37.         {
38.             if (ArrayResize(Values, Values.Size() + (Values.Size() == 0 ? 2 : 1)) == INVALID_HANDLE)
39.                 return false;
40. 
41.             Values[Values.Size() - 1] = arg;
42. 
43.             return true;
44.         }
45.     //+----------------+
46.         T Get(const uint index)
47.         {
48.             for (uint c = 0; c < Values.Size(); c++)
49.                 if (Values[c].Get_K() == index)
50.                     return Values[c];
51. 
52.             return Values[0];
53.         }
54.     //+----------------+
55. };
56. //+------------------------------------------------------------------+
57. #define PrintX(X) Print(#X, " => [", X, "]")
58. //+------------------------------------------------------------------+
59. void OnStart(void)
60. {
61.     const string T = "possible loss of data due to type conversion";
62.     const uint   K[] = {2, 1, 4, 0, 7, 5, 3, 6};
63. 
64.     st_Data <st_Reg> Info_1;
65.     string H[];
66. 
67.     StringSplit(T, ' ', H);
68.     for (uint c = 0; c < H.Size(); c++)
69.     {
70.         st_Reg  reg;
71. 
72.         reg.Set(K[c], H[c]);
73.         Info_1.Set(reg);
74.     }
75. 
76.     PrintX(Info_1.Get(3).Get_H());
77.     PrintX(Info_1.Get(13).Get_H());
78. }
79. //+------------------------------------------------------------------+

コード06

理論的には、コード06のst_Data構造体にはあらゆるデータ型や構造体を格納できます。理論的にはこれは正しいです。その理由はコード06の46行目のGet関数にあります。汎用化するためには、26行目を追加し、32行目・36行目・46行目のst_Regを型Tに置き換えるだけです。そして64行目の宣言を修正することで、コンパイラが適切な型を生成できます。

コード06の実行結果はコード05と同じで、図03と同じになります。しかし依然としてst_Reg構造体との結びつきは残っています。この依存は問題でもありますが、場合によっては利点にもなります。最終的には用途・発想力・知識レベルによって変わります。ここで重要なのは、st_Dataとst_Regの唯一の残る結びつきがGet_K関数(49行目)であるという点です。残っている依存関係はこれだけです。さらにst_Regはオーバーロード可能なので、異なる型にも対応できます。

ただし、こうした内容は最終段階では複雑になるため、ここでは扱いません。

それでも、興味深いことがまだできます。これは記事の残りで説明できます。対象はまさにコード06の49行目です。ここで注意してください。「初級から中級へ:オーバーロード」の記事で関数オーバーロードについて説明しました。本連載の他の記事では、また標準ライブラリと同じ名前を使ったオーバーロードについても説明しました。

では、コード06の49行目のGet_K関数をオーバーロードできるとしたらどうなるでしょうか。どのような可能性が広がるでしょうか。ここからは創造性と概念の応用が重要になります。なぜなら、よく見るとst_Reg構造体(コード06)は2つの変数しか持っていません。しかしこれを拡張することで、より多くの変数や異なる型を持つ構造体にすることもできます。

ただしこの方法を使っても、コード06ですでに作成されたst_Regを削除したり、st_Dataを修正する必要はありません。もし意味が分かりにくければ、具体例としてコードを見てみましょう。以下の通りです。

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. struct st_Reg
005. {
006.     //+----------------+
007.     private:
008.     //+----------------+
009.         string  h_value;
010.         uint    k_value;
011.     //+----------------+
012.     public:
013.     //+----------------+
014.         void Set(const uint arg1, const string arg2)
015.         {
016.             k_value = arg1;
017.             h_value = arg2;
018.         }
019.     //+----------------+
020.         uint Get_K(void)    { return k_value; }
021.     //+----------------+
022.         string Get_H(void)  { return h_value; }
023.     //+----------------+
024. };
025. //+------------------------------------------------------------------+
026. struct st_Bio
027. {
028.     //+----------------+
029.     private:
030.     //+----------------+
031.         string  h_value;
032.         string  b_value;
033.         uint    k_value;
034.     //+----------------+
035.     public:
036.     //+----------------+
037.         void Set(const uint arg1, const string arg2, const string arg3)
038.         {
039.             k_value = arg1;
040.             h_value = arg2;
041.             b_value = arg3;
042.         }
043.     //+----------------+
044.         uint Get_K(void)    { return k_value; }
045.     //+----------------+
046.         bool Get_Bio(string &arg1, string &arg2)
047.         {
048.             arg1 = h_value;
049.             arg2 = b_value;
050. 
051.             return true;
052.         }
053.     //+----------------+
054. };
055. //+------------------------------------------------------------------+
056. template <typename T>
057. struct st_Data
058. {
059.     //+----------------+
060.     private:
061.     //+----------------+
062.         T Values[];
063.     //+----------------+
064.     public:
065.     //+----------------+
066.         bool Set(const T &arg)
067.         {
068.             if (ArrayResize(Values, Values.Size() + (Values.Size() == 0 ? 2 : 1)) == INVALID_HANDLE)
069.                 return false;
070. 
071.             Values[Values.Size() - 1] = arg;
072. 
073.             return true;
074.         }
075.     //+----------------+
076.         T Get(const uint index)
077.         {
078.             for (uint c = 0; c < Values.Size(); c++)
079.                 if (Values[c].Get_K() == index)
080.                     return Values[c];
081. 
082.             return Values[0];
083.         }
084.     //+----------------+
085. };
086. //+------------------------------------------------------------------+
087. #define PrintX(X) Print(#X, " => [", X, "]")
088. //+------------------------------------------------------------------+
089. void CheckBio(st_Data <st_Bio> &arg)
090. {
091.     string sz[2];
092. 
093.     Print("Checking data in the structure...");
094.     for (uint i = 7; i < 11; i += 3)
095.     {
096.         Print("Index: ", i, " Result: ");
097.         if (arg.Get(i).Get_Bio(sz[0], sz[1]))
098.             ArrayPrint(sz);
099.         else
100.             Print("Failed.");
101.     }
102. }
103. //+------------------------------------------------------------------+
104. void OnStart(void)
105. {
106.     const string T = "possible loss of data due to type conversion";
107.     const string M[] = {"2", "cool", "4", "zero", "mad", "five", "what", "xoxo"};
108.     const uint   K[] = {2, 1, 4, 0, 7, 5, 3, 6};
109. 
110.     st_Data <st_Reg> Info_1;
111.     st_Data <st_Bio> Info_2;
112. 
113.     string  H[];
114. 
115.     StringSplit(T, ' ', H);
116.     for (uint c = 0; c < H.Size(); c++)
117.     {
118.         st_Reg  reg;
119.         st_Bio  bio;
120. 
121.         reg.Set(K[c], H[c]);
122.         bio.Set(K[c], M[c], H[c]);
123. 
124.         Info_1.Set(reg);
125.         Info_2.Set(bio);
126.     }
127. 
128.     PrintX(Info_1.Get(3).Get_H());
129.     PrintX(Info_1.Get(13).Get_H());
130.     CheckBio(Info_2);
131. }
132. //+------------------------------------------------------------------+

コード07

Code07を実行すると、ターミナルには次のような画像が表示されます。

図05

なんということでしょう。とても奇妙で大胆なものを作ってしまいました。ですが落ち着いてください。ここで本当に重要なのは、図05で強調表示されている情報です。これは89行目の手続きによって生成されたものであり、これから重点的に説明する部分でもあります。コード全体の大部分は比較的理解しやすいためです。次回の記事で扱う内容を理解するためには、この89行目の手続きを正しく理解することが非常に重要になります。

ただし、89行目の手続きが何をしているのかを説明する前に、いくつか追加で理解しておくべき点があります。まず26行目から見ていきましょう。ここでは新しい構造体が作成されています。この構造体はst_Reg構造体と似た基盤を持っていますが、似ているのはそこまでです。st_Bio構造体には、より多くの変数と異なる操作群が含まれているからです。ここで重要なのは44行目の関数です。この関数は20行目と同じ目的・役割を持っているためです。そして、これはこの後行うことにとって非常に重要になります。

また、コード07のst_Data構造体は、Code06にあったものとまったく同じであることにも注意してください。実際に変わったのはst_Bio構造体が追加されたことだけです。その後、コードのエントリポイントであるOnStart手続きへ進むことができます。ここではコード06には存在しなかった追加要素が加えられています。これらの例は、st_Data構造体がどれほど柔軟になり得るかを示すためのものです。特に、実装段階を慎重に設計した場合にその柔軟性が発揮されます。

本質的には、107行目と111行目が追加されました。ここで注目してください。110行目と111行目の違いは、st_Data構造体に格納されるデータ型そのものです。そのため、116行目のループ内部では、119行目で新しい変数を追加し、122行目で値を代入し、125行目で構造体へ新しいデータを追加しています。この場合、125行目で扱う型は124行目とは異なっています。ここまではコード06で予測していた通りに動作しています。

しかし、ここから複雑さが始まると同時に、最も面白い部分にも入っていきます。130行目によって89行目の手続きが実行されます。では最も興味深い部分を見ていきましょう。94行目のループ内部にいることに注意してください。そして特に重要なのが97行目です。これは128行目と比較する必要があります。この2つの違いは何でしょうか。実際には、この2つに本質的な違いはありません。76行目が62行目で宣言された変数のデータ型に対応する構造体型を返しているため、この2つの行の違いは実質的に存在しないのです。

初級から中級へ:変数(III)」では、関数は特殊な種類の変数であると説明しました。ここではその概念が非常に興味深い形で利用されています。76行目の関数から返される値によって、別のデータ型、この場合は別の構造体を参照できるからです。構造化プログラミングを使用しなければ、ここでおこなっていることを実現するのはほぼ不可能、あるいは極めて困難だったでしょう。重要なのは、97行目ではst_Bio構造体への参照を使用している一方で、128行目ではst_Reg構造体を参照しているという点です。そして両方ともst_Data構造体の内部に存在しているため、異なる種類の情報やデータセットを同じ方法で処理できるのです。

ここで実際に何が起きているのかを本当に理解するには、実際に試しながら学ぶ必要があります。プログラミング経験が少ない状態でコードを眺めるだけでは理解できません。そして忘れないでください。私の考えでは、私たちはまだ初心者レベルの内容を扱っているに過ぎないのです。


最後に

今回の記事では、問題を構造化しながら解決し、よりシンプルで魅力的な解決策を作る方法について説明しました。ここで示した内容は学習を目的としたものであり、そのため本番環境向けのコードではありません。しかし、ここで扱った概念や知識をしっかり理解することは非常に重要です。そうすることで、今後私たちが示していくコードを理解できるようになります。なぜなら、今後さらに話を進めていく中で、コードの各行を毎回説明する必要がなくなれば、より深く興味深いプログラミングの詳細を紹介できるようになるからです。

ですから、この機会にインデックス内の内容を焦らず丁寧に学んでください。そして、この知識を実際に応用してみてください。次回の記事では、今回の続きから話を進めます。構造化プログラミングについて、そしてオブジェクト指向プログラミングへ進む前にそれをどのように活用できるかについて、私たちはまだ説明を終えていません。

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

添付されたファイル |
Anexo.zip (3.41 KB)
市場シミュレーション(第19回):SQL入門(II) 市場シミュレーション(第19回):SQL入門(II)
最初のSQLに関する記事でも説明したように、SQLにすでに組み込まれていることを実現するために、わざわざ時間をかけて手続きをプログラミングする意味はありません。しかし、基礎を理解していなければ、SQLを使って何かをおこなうことも、このツールが提供する機能を十分に活用することもできません。そこで今回の記事では、データベースにおける基本的な作業をどのようにおこなうかを見ていきます。
MQL5における建値機能の実装(第1回):基底クラスと固定ポイントの建値モード MQL5における建値機能の実装(第1回):基底クラスと固定ポイントの建値モード
本記事では、MQL5言語を用いた自動売買戦略における建値(損益分岐点)機能の実装について解説します。まず、建値モードとは何か、その仕組みや実装パターンについて簡単に説明します。 その後、前回のリスク管理に関する記事で作成したOrder Blocksエキスパートアドバイザーに、この機能を統合していきます。さらに、その有効性を検証するため、同一条件下で2種類のバックテストを実施します。1つはブレイクイーブン機能を有効化した場合、もう1つは無効化した場合です。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
FX裁定取引:リスク管理を伴う公正価値への回帰を目指す行列取引システム FX裁定取引:リスク管理を伴う公正価値への回帰を目指す行列取引システム
本記事では、クロスレート計算アルゴリズムの詳細な説明、不均衡マトリクスの可視化、さらに効率的な取引のためのMinDiscrepancyおよびMaxRiskパラメータの最適な設定方法について解説します。本システムは、クロスレートを用いて各通貨ペアの「公正価値」を自動的に算出し、価格が公正価値より低い方向へ乖離した場合には買いシグナルを、高い方向へ乖離した場合には売りシグナルを生成します。