MQL5 リファレンス言語基礎データ型構造体、クラス、インターフェイス 

構造体、クラス、インターフェイス

構造体

構造体は void 型を除いた任意のタイプの要素の集合です。従って、構造体は、異なるタイプの論理的に関連するデータを組み合わせます。

構造体の宣言

構造体のデータ型は、以下の記載によって決定されます。

struct 構造体名
 {
  要素の記述
 };

構造体名は識別子(変数または関数の名称)として使用することは出来ません。MQL5 では構造要素が位置合わせ(アライン)されず、直接に前後に並んでいることに留意すべきです。C++ では、このような順序は、次の命令を使用して、コンパイラに行われます。

#pragma pack(1)

構造体内で位置合わせをしたい場合は、適切なサイズに補助メンバである「フィラー」を使用します。

例:

struct trade_settings
 {
  uchar  slippage;     // 使用可能の slippage サイズが 1 バイト
  char   reserved1;   // 1 バイトを抜かす
  short  reserved2;   // 2 バイトを抜かす
  int    reserved4;   // 4 バイトを抜かし、8 バイト境界の整列を保証する。
  double take;         // 利益固定の値
  double stop;         // プロテクティブストップの値
 };

このような整合構造体の記述は、インポートされた DLL 関数に転送するためのみに必要です。

注意:この例は誤って設計されたデータを示します。初めに take 及び stopdouble型の大きいデータを宣言してから uchar 型の slippage を宣言したほうがよりよい宣言となります。この場合には、データの内部表現は#pragma pack()で指定された値に関係なく、常に同じになります。

構造体が string 型の変数及び/また動的配列オブジェクトを含む場合、コンパイラによって暗黙のコンストラクタが割り当てられます。このコンストラクタは、string 型の全ての構造体のメンバをリセットし、動的配列オブジェクトを正しく初期化します。

基本構造体

文字列、クラスオブジェクト、ポインタ、動的配列オブジェクトが含まれていない構造体は基本構造体と呼ばれます。基本構造体型の変数や基本構造体の配列型の変数は DLL からインポートされた関数にパラメータとして渡されることが出来ます。

基本構造体をコピーすることは、2つの場合にのみ許可されます。

  • オブジェクトが同じ構造体型の場合
  • オブジェクトが、一つの構造体があと一つの構造体の子孫であることを意味する系統によって関連付けられている場合

例として、内容が内蔵されたMqlTickと同一なCustomMqlTickカスタム構造体を開発してみましょう。 コンパイラは、MqlTickオブジェクト値をCustomMqlTick型オブジェクトにコピーすることを許可しません。必要な型に直接型キャストすると、コンパイルエラーも発生します。

    //--- 異なる型の基本構造体をコピーすることは禁じられている
     my_tick1=last_tick;               // コンパイラはここでエラーを返す
   
    //--- 異なる型の構造体を互いに型キャストすることも禁止されている
     my_tick1=(CustomMqlTick)last_tick;// コンパイラはここでエラーを返す

したがって、残るのは、構造体要素の値を1つずつコピーするという選択肢だけです。同じ型のCustomMqlTickの値をコピーすることはできます。

     CustomMqlTick my_tick1,my_tick2;
    //--- 同じ型のCustomMqlTickオブジェクトは次のようにコピーすることができる
     my_tick2=my_tick1;
   
    //--- CustomMqlTick基本構造体オブジェクトから配列を作成し、それに値を書き込み
     CustomMqlTick arr[2];
     arr[0]=my_tick1;
     arr[1]=my_tick2;

操作ログにarr[] 配列値を表示するのにはArrayPrint()関数が呼び出されます。

//+------------------------------------------------------------------+
//| スクリプトプログラムを開始する関数                                          |
//+------------------------------------------------------------------+
void OnStart()
 {
//--- 内蔵されたMqlTickと似ている構造体を開発する
  struct CustomMqlTick
    {
    datetime          time;         // 直近約定値更新時間
    double            bid;           // 現在の売値
    double            ask;           // 現在の買値
    double            last;         // 最終取引価格 (Last)
    ulong             volume;       // 現在の直近約定ボリューム
    long              time_msc;     // ミリ秒単位の直近約定値更新時間
    uint              flags;         // ティックフラグ    
    };
  //--- 直近のティック値を取得する
  MqlTick last_tick;
  CustomMqlTick my_tick1,my_tick2;
//--- MqlTick からCustomMqlTickへのデータのコピーを試みる
  if(SymbolInfoTick(Symbol(),last_tick))
    {
    //--- 無関係な基本構造体をコピーすることは禁止されている
    //1. my_tick1=last_tick;               // コンパイラはここでエラーを返す
   
    //--- 無関係の構造体を互いに型キャストすることも禁止されている
    //2. my_tick1=(CustomMqlTick)last_tick;// コンパイラはここでエラーを返す
   
    //--- よって、構造体のメンバを一つずつコピーする
     my_tick1.time=last_tick.time;
     my_tick1.bid=last_tick.bid;
     my_tick1.ask=last_tick.ask;
     my_tick1.volume=last_tick.volume;
     my_tick1.time_msc=last_tick.time_msc;
     my_tick1.flags=last_tick.flags;
   
    //--- 同じ型のCustomMqlTickオブジェクトは次のようにコピーすることができる
     my_tick2=my_tick1;
   
    //--- CustomMqlTick基本構造体オブジェクトから配列を作成し、それに値を書き込み
     CustomMqlTick arr[2];
     arr[0]=my_tick1;
     arr[1]=my_tick2;
    ArrayPrint(arr);
//--- CustomMqlTick型のオブジェクトを含む配列の値を表示する例
     /*
                      [time]   [bid]   [ask]   [last] [volume]    [time_msc] [flags]
     [0] 2017.05.29 15:04:37 1.11854 1.11863 +0.00000  1450000 1496070277157       2
     [1] 2017.05.29 15:04:37 1.11854 1.11863 +0.00000  1450000 1496070277157       2          
     */
    }
  else
    Print("SymbolInfoTick() failed, error = ",GetLastError());
 }

2番目の例は、基本構造体を系統にコピーする関数を示します。Animal基本構造体があって、そこからCat構造体とDog構造体が継承されているとします。AnimalオブジェクトとCatオブジェクト、またAnimalオブジェクトとDogオブジェクトをお互いにコピーすることはできますが、CatとDogは両方ともAnimal構造体の子孫ですがお互いにコピーすることはできません。

//--- 犬を記述するための構造体
struct Dog: Animal
 {
  bool              hunting;       // 狩猟犬
 };
//--- 猫を記述するための構造体
struct Cat: Animal
 {
  bool              home;         // ペット用の猫
 };
//--- 子構造体のオブジェクトを作成する
  Dog dog;
  Cat cat;
//--- 祖先から子孫にコピーすることができる (Animal ==> Dog)
  dog=some_animal;
  dog.swim=true;   // 犬は泳げる
//--- 子構造のオブジェクトをコピーすることはできない (Dog != Cat)
  cat=dog;        // コンパイラがエラーを返す

この例の完全なコード:

//--- 動物を記述するための基本構造体
struct Animal
 {
  int               head;         // 頭の数
  int               legs;         // 脚の数
  int               wings;         // 羽の数
  bool              tail;         // 尾
  bool              fly;           // 飛ぶ
  bool              swim;         // 泳ぐ
  bool              run;           // 走る
 };
//--- 犬を記述するための構造体
struct Dog: Animal
 {
  bool              hunting;       // 狩猟犬
 };
//--- 猫を記述するための構造体
struct Cat: Animal
 {
  bool              home;         // ペット用の猫
 };
//+------------------------------------------------------------------+
//| スクリプトプログラムを開始する関数                                          |
//+------------------------------------------------------------------+
void OnStart()
 {
//--- 基本的なAnimal型のオブジェクトを作成して記述する
  Animal some_animal;
  some_animal.head=1;
  some_animal.legs=4;
  some_animal.wings=0;
  some_animal.tail=true;
  some_animal.fly=false;
  some_animal.swim=false;
  some_animal.run=true;
//--- 子の型のオブジェクトを作成する
  Dog dog;
  Cat cat;
//--- 祖先から子孫にコピーすることができる (Animal ==> Dog)
  dog=some_animal;
  dog.swim=true;   // 犬は泳げる
//--- 子構造のオブジェクトをコピーすることはできない (Dog != Cat)
  //cat=dog;        // コンパイラはここでエラーを返す
//--- したがって、要素を1つずつコピーすることのみが可能
  cat.head=dog.head;
  cat.legs=dog.legs;
  cat.wings=dog.wings;
  cat.tail=dog.tail;
  cat.fly=dog.fly;
  cat.swim=false;   // 猫は泳げない
//--- 子孫から祖先に値をコピーすることは可能
  Animal elephant;
  elephant=cat;
  elephant.run=false;// ゾウは走れない
  elephant.swim=true;// ゾウは泳げる
//--- 配列を作成する
  Animal animals[4];
  animals[0]=some_animal;
  animals[1]=dog;  
  animals[2]=cat;
  animals[3]=elephant;
//--- プリントする
  ArrayPrint(animals);
//--- 実行結果
/*
      [head] [legs] [wings] [tail] [fly] [swim] [run]
  [0]      1      4       0   true false false true
  [1]      1      4       0   true false   true true
  [2]      1      4       0   true false false false
  [3]      1      4       0   true false   true false
*/  
 }

基本データ型をコピーするもう一つの方法は、共用体を使用することです。構造体のオブジェクトは、同じ共用体のメンバーでなければなりません。unionの例を参照してください。

構造体メンバへのアクセス

構造体の名称が新しいデータ型になり、この型の変数を宣言することが出来るようになります。構造体は、プロジェクト内で一度だけ宣言することが出来ます。構造体のメンバは点演算( . )を使用してアクセスされます。

例:

struct trade_settings
 {
  double take;         // 利益固定価格
  double stop;         // プロテクティブストップの値
  uchar  slippage;     // 使用可能のスリッページの値
 };
//--- trade_settings 型の変数を作成して初期化する
trade_settings my_set={0.0,0.0,5};  
if (input_TP>0) my_set.take=input_TP;

'pack' は構造体およびクラスのフィールドを位置合わせします。

特別の pack 属性によって、構造体およびクラスのフィールドを位置合わせすることができます。

pack([n])

ここで n は 1、2、4、8、16 のいずれかです。指定は任意です。

例:

  struct pack(sizeof(long)) MyStruct
    {
    // 構造体のメンバは 8 バイト境界で位置合わせされている
    };
または
  struct MyStruct pack(sizeof(long))
    {
    // 構造体のメンバは 8 バイト境界で位置合わせされている
    };

'pack(1)' はデフォルトで構造体に適用されています。これは、構造体のメンバがメモリ内で次々に配置され、構造体サイズがメンバのサイズの合計に等しいことを意味します。

例:

//+------------------------------------------------------------------+
//| スクリプトプログラムを開始する関数                                          |
//+------------------------------------------------------------------+
void OnStart()
 {
//--- 位置合わせなしの基本構造体
  struct Simple_Structure
    {
    char              c; // sizeof(char)=1
    short             s; // sizeof(short)=2
    int               i; // sizeof(int)=4
    double            d; // sizeof(double)=8
    };
  //--- 基本構造体型のインスタンスを宣言する
  Simple_Structure s;  
//--- それぞれの構造体メンバのサイズを表示する  
  Print("sizeof(s.c)=",sizeof(s.c));
  Print("sizeof(s.s)=",sizeof(s.s));
  Print("sizeof(s.i)=",sizeof(s.i));
  Print("sizeof(s.d)=",sizeof(s.d));
//--- POD構造体のサイズがそのメンバのサイズの合計と等しいことを確認する
  Print("sizeof(simple_structure)=",sizeof(simple_structure));
/*
  結果:
  sizeof(s.c)=1
  sizeof(s.s)=2
  sizeof(s.i)=4
  sizeof(s.d)=8
  sizeof(simple_structure)=15
*/    
 }

構造体フィールドの位置合わせは、そのような位置合わせが適用されているサードパーティライブラリ(* .DLL)とデータを交換するときに必要になることがあります。

位置合わせがどのように機能するかを示すためにいくつかの例を使用しましょう。位置合わせなしの 4 つのメンバからなる構造体を適用します。

//--- 位置合わせなしの基本構造体
  struct Simple_Structure pack() // サイズが指定されていない、1 バイト境界で位置合わせ
    {
    char              c; // sizeof(char)=1
    short             s; // sizeof(short)=2
    int               i; // sizeof(int)=4
    double            d; // sizeof(double)=8
    };
//--- 基本構造体型のインスタンスを宣言する  
  Simple_Structure s;

構造体フィールドは、宣言の順序と型サイズに従って、メモリー内に次々に配置されます。構造体のサイズは 15 ですが、配列内の構造体フィールドへのオフセットは未定義です。

simple_structure_alignment

4 バイトの位置合わせで同じ構造体を宣言し、コードを実行します。

//+------------------------------------------------------------------+
//| スクリプトプログラムを開始する関数                                          |
//+------------------------------------------------------------------+
void OnStart()
 {
//--- 4 バイトで位置合わせされた基本構造体
  struct Simple_Structure pack(4)
    {
    char              c; // sizeof(char)=1
    short             s; // sizeof(short)=2
    int               i; // sizeof(int)=4
    double            d; // sizeof(double)=8
    };
  //--- 基本構造体型のインスタンスを宣言する
  Simple_Structure s;  
//--- それぞれの構造体メンバのサイズを表示する  
  Print("sizeof(s.c)=",sizeof(s.c));
  Print("sizeof(s.s)=",sizeof(s.s));
  Print("sizeof(s.i)=",sizeof(s.i));
  Print("sizeof(s.d)=",sizeof(s.d));
//--- POD構造体のサイズがそのメンバのサイズの合計と等しいことを確認する
  Print("sizeof(simple_structure)=",sizeof(simple_structure));
/*
  結果:
  sizeof(s.c)=1
  sizeof(s.s)=2
  sizeof(s.i)=4
  sizeof(s.d)=8
  sizeof(simple_structure)=16 // 構造体サイズが変化した
*/    
 }

構造体のサイズが変更され、4 バイト以上のすべてのメンバの構造体の先頭からのオフセットが 4 バイトの倍数になりました。より小さいメンバは、自身のサイズ境界に合わせて配置されます(たとえば、 'short'の場合は 2)。これがその外観です(追加されたバイトは灰色で表示されています)。

simple_structure_alignment_pack

この場合、s.cメンバの後に 1 バイトが追加されるため、s.s (sizeof(short)==2) フィールドの境界が 2 バイトになります('short' 型の位置合わせ)。

配列内の構造体の先頭へのオフセットも 4 バイト境界に揃えられます。つまり、a[0]、a[1]...a[n]要素のアドレスは、Simple_Structure arr[]では 4 バイトの倍数になります。

同様の型からなって 4 バイトで位置合わせされているがメンバの順序が異なる構造体をもう 2 つ考えてみましょう。最初の構造体では、メンバは型サイズの昇順で配置されています。

//+------------------------------------------------------------------+
//| スクリプトプログラムを開始する関数                                          |
//+------------------------------------------------------------------+
void OnStart()
 {
//--- 4 バイト境界で位置合わせされた基本構造体
  struct CharShortInt pack(4)
    {
    char              c; // sizeof(char)=1
    short             s; // sizeof(short)=2
    int               i; // sizeof(double)=4
    };
//--- 基本構造体型のインスタンスを宣言する  
  CharShortInt ch_sh_in;
//--- それぞれの構造体メンバのサイズを表示する  
  Print("sizeof(ch_sh_in.c)=",sizeof(ch_sh_in.c));
  Print("sizeof(ch_sh_in.s)=",sizeof(ch_sh_in.s));
  Print("sizeof(ch_sh_in.i)=",sizeof(ch_sh_in.i));
 
//--- POD構造体のサイズがそのメンバのサイズの合計と等しいことを確認する
  Print("sizeof(CharShortInt)=",sizeof(CharShortInt));
/*
  結果:
  sizeof(ch_sh_in.c)=1
  sizeof(ch_sh_in.s)=2
  sizeof(ch_sh_in.i)=4
  sizeof(CharShortInt)=8
*/  
 }

ご覧のとおり、構造体サイズは 8 で、2 つの 4 バイトブロックで構成されています。最初のブロックには 'char' 型と 'short' 型のフィールドが含まれ、2 番目のブロックには 'int' 型のフィールドが含まれています。

charshortint

ここで1番目の構造体を2番目の構造体に変換します。これらで違うのはフィールドの順番だけです。'short' 型メンバを最後に移動します。

//+------------------------------------------------------------------+
//| スクリプトプログラムを開始する関数                                          |
//+------------------------------------------------------------------+
void OnStart()
 {
//--- 4 バイト境界で位置合わせされた基本構造体
  struct CharIntShort pack(4)
    {
    char              c; // sizeof(char)=1
    int               i; // sizeof(double)=4
    short             s; // sizeof(short)=2
    };
//--- 基本構造体型のインスタンスを宣言する  
  CharIntShort ch_in_sh;
//--- それぞれの構造体メンバのサイズを表示する  
  Print("sizeof(ch_in_sh.c)=",sizeof(ch_in_sh.c));
  Print("sizeof(ch_in_sh.i)=",sizeof(ch_in_sh.i));
  Print("sizeof(ch_in_sh.s)=",sizeof(ch_in_sh.s));
//--- POD構造体のサイズがそのメンバのサイズの合計と等しいことを確認する
  Print("sizeof(CharIntShort)=",sizeof(CharIntShort));
/*
  結果:
  sizeof(ch_in_sh.c)=1
  sizeof(ch_in_sh.i)=4
  sizeof(ch_in_sh.s)=2
  sizeof(CharIntShort)=12
*/  
 }

構造体の内容は変更されていませんが、メンバの順序を変更するとサイズが大きくなります。

charintshort

位置合わせは継承によっても考慮される必要があります。単一の 'char' 型メンバを持つ単純な Parent 構造体を使ってこれを実証しましょう。位置合わせなしの構造体のサイズは 1 です。

  struct Parent
    {
    char              c;   // sizeof(char)=1
    };

'short' (sizeof(short)=2) 型メンバを持った Children 子クラスを作成しましょう。

  struct Children pack(2) : Parent
    {
    short             s;   // sizeof(short)=2
    };

その結果、位置合わせを 2 バイトに設定した場合、そのメンバのサイズは 3 ですが、構造体のサイズは 4 になります。この例では、2 バイトが Parent クラスに割り当てられるため、子クラスの 'short' フィールドへのアクセスは 2 バイトに揃えられます。

MQL5アプリケーションがファイルまたはストリームレベルで書き込み/読み取りを行うことによってサードパーティのデータとやり取りする場合は、構造体メンバにメモリがどのように割り当てられるかについての知識が必要です。

標準ライブラリの MQL5\Include\WinAPI ディレクトリには、WinAPI 関数を使用するための関数が含まれています。これらの関数は、WinAPI での作業に必要な場合に、指定された位置合わせを構造体に適用します。 

offsetofpack 属性に直接関連した特別のコマンドで、構造体の先頭からのオフセットを取得するのに使用されます。

//--- Children 型変数を宣言する
  Children child;  
//--- 構造体の先頭からのオフセットを検知する
  Print("offsetof(child.c)=",offsetof(child.c));
  Print("offsetof(child.s)=",offsetof(child.s));  
/*
  結果:
  offsetof(child.c)=0
  offsetof(child.s)=2
*/  

final修飾子

final修飾子は、構造体の宣言時にそこからのさらなる継承を禁止します。構造体がそこへ更なる変更を加える必要がないものである、またはセキュリティ上の理由で変更が許可されていない場合は、構造体をfinal修飾子で宣言してください。また、全ての構造体のメンバーも暗黙的にfinalとみなされます。

struct settings final
{
//--- тело структуры
};
 
struct trade_settings : public settings
{
//--- 構造体
};

final修飾子を使用した構造体からの継承を試行すると、上記の例で示したように、コンパイルはエラーを出します。

cannot inherit from 'settings' as it has been declared as 'final'
see declaration of 'settings'

クラス

クラスと構造体の異なる点は次の通りです。

  • クラスの宣言には class キーワードが使用されます。
  • クラスメンバは、他の値が示されていない限り、デフォルトで private アクセス指定子を持っています。構造体のデータメンバは、他の値が示されていない限り、デフォルトで public アクセスを持っています。
  • クラスオブジェクトは、クラス内で仮想関数が宣言されない場合でも常に仮想関数の表を持っています。構造体は仮想関数を持つことが出来ません。
  • new 演算子はクラスオブジェクトに適用することが出来ますが、構造体に適用することは出来ません。
  • クラスは他のクラスからのみ継承でき、構造体は他の構造体のみから継承出来ます。

クラスと構造体は、明示的なコンストラクタとデストラクタを持つことが出来ます。コンストラクタが明示的に定義されている場合は、初期化シーケンスを使用しての構造体またはクラス変数の初期化は不可能です。

例:

struct trade_settings
 {
  double take;         // 利益固定価格
  double stop;         // プロテクティブストップの値
  uchar  slippage;     // 使用可能のスリッページの値
  //--- コンストラクタ
         trade_settings() { take=0.0; stop=0.0; slippage=5; }
  //--- デストラクタ
        ~trade_settings() { Print("This is the end"); }
 };
//--- コンパイラが初期化不可能のエラーメッセージを生成する
trade_settings my_set={0.0,0.0,5};  

コンストラクタとデストラクタ

コンストラクタは、構造体またはクラスのオブジェクトを作成する時に自動的に呼び出され、通常クラスメンバの初期化に使用されます。ここからはクラスのみに参照しますが、特に指定がない限り、同じことが構造体にも適用されます。コンストラクタ名は、クラス名と一致する必要があります。コンストラクタには戻り値の型がありません(void 型を指定出来ます)。

定義されたクラスメンバのうち文字列動的配列及び初期化を必要とするオブジェクトはコンストラクタのあるなしに関係なく初期化されます。

各クラスには、パラメータの数と初期化リストにより異なる複数のコンストラクタが存在出来ます。パラメータの指定が必要なコンストラクタは、パラメトリックコンストラクタと呼ばれています。

パラメータのないコンストラクタは、デフォルトコンストラクタと呼ばれます。クラスでコンストラクタが宣言されていない場合、コンパイラはコンパイル時にデフォルトコンストラクタを作成します。

//+------------------------------------------------------------------+
//| 日付を操作するクラス                                                   |
//+------------------------------------------------------------------+
class MyDateClass
 {
private:
  int               m_year;         // 年
  int               m_month;         // 月
  int               m_day;           // 日
  int               m_hour;         // 時間
  int               m_minute;       // 分
  int               m_second;       // 秒
public:
  //--- デフォルトコンストラクタ
                    MyDateClass(void);
  //--- パラメトリックコンストラクタ
                    MyDateClass(int h,int m,int s);
 };

 

コンストラクタは、クラス記述の中で宣言することができ、その本体も定義することが出来ます。例えば、MyDateClass の 2 つのコンストラクタは、次のように定義することが出来ます。

//+------------------------------------------------------------------+
//| デフォルトコンストラクタ                                                   |
//+------------------------------------------------------------------+
MyDateClass::MyDateClass(void)
 {
//---
  MqlDateTime mdt;
  datetime t=TimeCurrent(mdt);
  m_year=mdt.year;
  m_month=mdt.mon;
  m_day=mdt.day;
  m_hour=mdt.hour;
  m_minute=mdt.min;
  m_second=mdt.sec;
  Print(__FUNCTION__);
 }
//+------------------------------------------------------------------+
//| パラメトリックコンストラクタ                                                 |
//+------------------------------------------------------------------+
MyDateClass::MyDateClass(int h,int m,int s)
 {
  MqlDateTime mdt;
  datetime t=TimeCurrent(mdt);
  m_year=mdt.year;
  m_month=mdt.mon;
  m_day=mdt.day;
  m_hour=h;
  m_minute=m;
  m_second=s;
  Print(__FUNCTION__);
 }

デフォルトコンストラクタ内では、クラスの全メンバが TimeCurrent() 関数で書き入れられます。 パラメトリックコンストラクタ内では hour 値がのみが書き入れられます。クラスの他メンバ(m_year、m_month と m_day)は、自動的に現在の日付で初期化されます。

デフォルトコンストラクタは、クラスのオブジェクトの配列の初期化に特別な目的を持っています。全てのパラメータに初期値があるコンストラクタはデフォルトコンストラクタではありません。下記が例です。

//+------------------------------------------------------------------+
//| デフォルトコンストラクタを持つクラス                                           |
//+------------------------------------------------------------------+
class CFoo
 {
  datetime          m_call_time;     // 最後のオブジェクト呼び出し時
public:
  //--- パラメータに初期値があるコンストラクタはデフォルトコンストラクタではない
                    CFoo(const datetime t=0){m_call_time=t;};
  //--- コピーコンストラクタ
                    CFoo(const CFoo &foo){m_call_time=foo.m_call_time;};
 
  string ToString(){return(TimeToString(m_call_time,TIME_DATE|TIME_SECONDS));};
 };
//+------------------------------------------------------------------+
//| スクリプトプログラムを開始する関数                                          |
//+------------------------------------------------------------------+
void OnStart()
 {
// CFoo foo; // デフォルトコンストラクタが設定されていないので使用出来ない
//--- CFoo オブジェクト作成に可能な選択肢
  CFoo foo1(TimeCurrent());     // パラメトリックコンストラクタの明示的呼び出し
  CFoo foo2();                 // デフォルトパラメータを使用したパラメトリックコンストラクタの明示的呼び出し
  CFoo foo3=D'2009.09.09';     // パラメトリックコンストラクタの暗黙的呼び出し
  CFoo foo40(foo1);             // コピーコンストラクタの明示的呼び出し
  CFoo foo41=foo1;             // コピーコンストラクタの暗黙的呼び出し
  CFoo foo5;                   // デフォルトコンストラクタの明示的呼び出し(デフォルトコンストラクタのない場合は
                                // パラメトリックコンストラクタが初期値で呼ばれる)
//--- CFoo ポインタの受け取りに可能な選択肢
  CFoo *pfoo6=new CFoo();       // 動的オブジェクトの作成とポインタの受け取り
  CFoo *pfoo7=new CFoo(TimeCurrent());// 動的オブジェクト作成のもう 1 つの選択肢
  CFoo *pfoo8=GetPointer(foo1); // pfoo8 が foo1 オブジェクトに参照する
  CFoo *pfoo9=pfoo7;           // pfoo9 と pfoo7 が同じオブジェクトに参照
  // CFoo foo_array[3];         // デフォルトコンストラクタが指定されていないので使用出来ない
//--- m_call_time の値を表示
  Print("foo1.m_call_time=",foo1.ToString());
  Print("foo2.m_call_time=",foo2.ToString());
  Print("foo3.m_call_time=",foo3.ToString());
  Print("foo4.m_call_time=",foo4.ToString());
  Print("foo5.m_call_time=",foo5.ToString());
  Print("pfoo6.m_call_time=",pfoo6.ToString());
  Print("pfoo7.m_call_time=",pfoo7.ToString());
  Print("pfoo8.m_call_time=",pfoo8.ToString());
  Print("pfoo9.m_call_time=",pfoo9.ToString());
//--- 動的に作成した配列を削除する
  delete pfoo6;
  delete pfoo7;
  //delete pfoo8;  // pfoo8 は自動的に作成されたオブジェクトのfoo1を指すので、明示的に削除する必要がない
  //delete pfoo9;  // pfoo9 は pfoo7 と同じオブジェクトを参照するため明示的に pfoo9 を削除する必要はない
 }

下記のコメントが解除された場合

  //CFoo foo_array[3];     // デフォルトコンストラクタが設定されていないので使用出来ない

または

  //CFoo foo_dyn_array[];  // デフォルトコンストラクタが設定されていないので使用出来ない

コンパイラは「default constructor is not defined(デフォルトコンストラクタが定義されていない)」とのエラーメッセージを生成します。

クラスに、ユーザ定義のコンストラクタがある場合、コンパイラはデフォルトコンストラクタを生成しません。つまり、クラス内でパラメトリックコンストラクタが宣言されていてもデフォルトコンストラクタが宣言されていない場合は、このクラスのオブジェクトの配列を宣言することは出来ません。コンパイラは、このスクリプトに対してエラーを返します。

//+------------------------------------------------------------------+
//| デフォルトコンストラクタを持たないクラス                                        |
//+------------------------------------------------------------------+
class CFoo
 {
  string            m_name;
public:
                    CFoo(string name) { m_name=name;}
 };
//+------------------------------------------------------------------+
//| スクリプトプログラムを開始する関数                                          |
//+------------------------------------------------------------------+
void OnStart()
 {
//--- コンパイル時に「default constructor is not defined(デフォルトコンストラクタが定義されていない)」エラ—が出る
  CFoo badFoo[5];
 }

この例では、CFoo クラスにはパラメトリックコンストラクタが宣言されています。このような場合には、コンパイラは、コンパイル時に自動的にデフォルトコンストラクタを作成しません。同時に、オブジェクトの配列を宣言する時はオブジェクトは全て自動的に作成されて初期化されていることが想定されます。オブジェクトの自動初期化中にはデフォルトコンストラクタを呼び出す必要がありますが、デフォルトのコンストラクタが明示的に宣言されず自動的にコンパイラによって生成されていないため、このようなオブジェクトを作成することは不可能です。このため、コンパイル段階でコンパイラエラーが発生します。

コンストラクタを使用してオブジェクトを初期化するためには特別な構文があります。構造体やクラスのメンバのコンストラクタの初期化子(初期化のための特別な構造)は初期化リストで指定することが出来ます。

初期化リストは、コンストラクタのパラメータリストの後のコロンの後及び本体に先行するコンマで区切られた初期化子のリストです(左中括弧の前に配置されます)。いくつかの要件があります。

  • 初期化リストは、コンストラクタ内でのみ使用することが出来ます。
  • 親メンバ は初期化リストで初期化することは出来ません。
  • 初期化リストは関数の定義(実装)で続けられる必要があります。

下記はクラスのメンバを初期化するためのコンストラクタの例です。

//+------------------------------------------------------------------+
//| 文字名を格納するためのクラス                                             |
//+------------------------------------------------------------------+
class CPerson
 {
  string            m_first_name;     // 名
  string            m_second_name;   // 姓
public:
  //--- 空のデフォルトコンストラクタ
                    CPerson() {Print(__FUNCTION__);};
  //--- パラメトリックコンストラクタ
                    CPerson(string full_name);
  //--- 初期化リストを持ったコンストラクタ
                    CPerson(string surname,string name): m_second_name(surname), m_first_name(name) {};
  void PrintName(){PrintFormat("Name=%s Surname=%s",m_first_name,m_second_name);};
 };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CPerson::CPerson(string full_name)
 {
  int pos=StringFind(full_name," ");
  if(pos>=0)
    {
     m_first_name=StringSubstr(full_name,0,pos);
     m_second_name=StringSubstr(full_name,pos+1);
    }
 }
//+------------------------------------------------------------------+
//| スクリプトプログラムを開始する関数                                          |
//+------------------------------------------------------------------+
void OnStart()
 {
//--- 「default constructor is not defined(デフォルトコンストラクタが定義されていない)」エラーを取得
  CPerson people[5];
  CPerson Tom="Tom Sawyer";                       // トムソーヤ
  CPerson Huck("Huckleberry","Finn");             // ハックルベリー•フィンの冒険
  CPerson *Pooh = new CPerson("Winnie","Pooh"); // くまのプーさん
  //--- 出力値
  Tom.PrintName();
  Huck.PrintName();
  Pooh.PrintName();
 
  //--- 動的に作成されたオブジェクトを削除する
  delete Pooh;
 }

この場合、CPersonクラスには、次の 3 つのコンストラクタがあります。

  1. クラスオブジェクトの配列を作成出来る明示的なデフォルトコンストラクタ
  2. 1 つのパラメータとして姓名を取得し、見つかったスペースに応じて名に分割するコンストラクタ
  3. 初期化リストを含む 2 つのパラメータを持ったコンストラクタイニシャライザ - m_second_name(surname) と m_first_name(name)]

リストを使用した初期化が代入を置き換えていることにご注意ください。個々のメンバは次のように初期化する必要があります。

 クラスメンバ(式のリスト)

初期化リストでは、メンバは任意の順序を持てますが、その順を従って初期化されます。つまリ、3 番目のコンストラクタでは、最初に指定された m_first_name メンバが最初に初期化され、m_second_name がその後に初期化されます。クラスメンバの初期化が他のクラスメンバの値に依存する場合にはこのことが考慮されるべきです。

デフォルトコンストラクタが基本クラスで宣言されていないと同時にパラメータを持つ 1 つ以上のコンストラクタが宣言されている場合は、初期化リスト内の基本クラスのコンストラクタのいずれかが呼び出されるべきです。それは、初期化リスト内のどこにあるかに関係なく、リストの通常のメンバとしてコンマを通過し、オブジェクトの初期化時に最初に呼び出されます。

//+------------------------------------------------------------------+
//| 基本クラス                                                          |
//+------------------------------------------------------------------+
class CFoo
 {
  string            m_name;
public:
  //--- 初期化リストを持ったコンストラクタ
                    CFoo(string name) : m_name(name) { Print(m_name);}
 };
//+------------------------------------------------------------------+
//| CFoo の派生クラス                                                    |
//+------------------------------------------------------------------+
class CBar : CFoo
 {
  CFoo              m_member;     // クラスメンバが親のオブジェクト
public:
  //--- 初期化リスト内のデフォルトコンストラクタが、親のコンストラクタを呼び出す
                    CBar(): m_member(_Symbol), CFoo("CBAR") {Print(__FUNCTION__);}
 };
//+------------------------------------------------------------------+
//| スクリプトプログラムを開始する関数                                          |
//+------------------------------------------------------------------+
void OnStart()
 {
  CBar bar;
 }

この例では、bar オブジェクトを作成する時に、デフォルトコンストラクタ CBar() が呼び出されます。その中で最初に親 CFoo のコンストラクタが呼び出され、その後 m_member クラスメンバのコンストラクタが呼び出されます。

デストラクタは、クラスのオブジェクトが破壊された時に自動的に呼び出される特殊な関数です。デストラクタ名はクラス名とチルダ( ~ )として記述されています。初期化解除とする文字列、動的配列やオブジェクトは、デストラクタの有無に関係なく初期化解除されます。デストラクタがある場合、これらのアクションは、デストラクタを呼び出した後に実行されます。

デストラクタは virtual キーワードと宣言されたかどうかにかかわらず常に virtual(仮想)です。

クラスメソッドの定義

クラス関数メソッドは、クラスの内部及びクラス宣言の外側の両方で定義することが出来ます。メソッドがクラス内で定義されている場合は、その本体はメソッドの宣言の後に来ます。

例:

class CTetrisShape
 {
protected:
  int               m_type;
  int               m_xpos;
  int               m_ypos;
  int               m_xsize;
  int               m_ysize;
  int               m_prev_turn;
  int               m_turn;
  int               m_right_border;
public:
  void              CTetrisShape();
  void              SetRightBorder(int border) { m_right_border=border; }
  void              SetYPos(int ypos)          { m_ypos=ypos;           }
  void              SetXPos(int xpos)          { m_xpos=xpos;           }
  int               GetYPos()                  { return(m_ypos);        }
  int               GetXPos()                  { return(m_xpos);        }
  int               GetYSize()                 { return(m_ysize);       }
  int               GetXSize()                 { return(m_xsize);       }
  int               GetType()                  { return(m_type);        }
  void              Left()                     { m_xpos-=SHAPE_SIZE;    }
  void              Right()                    { m_xpos+=SHAPE_SIZE;    }
  void              Rotate()                   { m_prev_turn=m_turn; if(++m_turn>3) m_turn=0; }
  virtual void      Draw()                     { return;                }
  virtual bool      CheckDown(int& pad_array[]);
  virtual bool      CheckLeft(int& side_row[]);
  virtual bool      CheckRight(int& side_row[]);
 };

SetRightBorder(int border) から Draw() までの関数は CTetrisShape クラス内部で直接定義及び宣言されています。

CTetrisShape() コンストラクタと CheckDown(int& pad_array[])、CheckLeft(int& side_row[]) 及び CheckRight(int& side_row[]) メソッドはクラス内で宣言されただけでまだ定義されていません。これらの関数の定義は、コードの後に出てきます。クラス外にメソッドを定義するためにはスコープ解決演算子が使用され、クラス名がスコープとして利用されます。

例:

//+------------------------------------------------------------------+
//| 基本クラスのコンストラクタ                                                |
//+------------------------------------------------------------------+
void CTetrisShape::CTetrisShape()
 {
  m_type=0;
  m_ypos=0;
  m_xpos=0;
  m_xsize=SHAPE_SIZE;
  m_ysize=SHAPE_SIZE;
  m_prev_turn=0;
  m_turn=0;
  m_right_border=0;
 }
//+------------------------------------------------------------------+
//| (スティックとキューブの)下に移動する能力を確認する                              |
//+------------------------------------------------------------------+
bool CTetrisShape::CheckDown(int& pad_array[])
 {
  int i,xsize=m_xsize/SHAPE_SIZE;
//---
  for(i=0; i<xsize; i++)
    {
    if(m_ypos+m_ysize>=pad_array[i]) return(false);
    }
//---
  return(true);
 }

Public、Protected 及び Private アクセス修飾子

新しいクラスを開発する場合には、外部からのメンバへのアクセスを制限することをお勧めします。このために、キーワード private もしくは protected が使用されます。この場合には、隠されたデータは、同じクラスの関数メソッドからのみアクセスすることが出来ます。もし protected キーワードが使用されれば、隠されたデータは承継クラスのメソッドからもアクセス出来ます。同様の方法はクラスの関数メソッドへのアクセスを制限するために使用出来ます。

メンバ及び/またはクラスメソッドへのアクセスを完全にオープンにする必要がある場合は public キーワードが使用されるべきです。

例:

class CTetrisField
 {
private:
  int               m_score;                           // 得点
  int               m_ypos;                             // 図形の現在位置
  int               m_field[FIELD_HEIGHT][FIELD_WIDTH]; // ウェルの行列
  int               m_rows[FIELD_HEIGHT];               // ウェルの行の番号付け
  int               m_last_row;                         // 最後の空いている行
  CTetrisShape     *m_shape;                           // テトリスの図形
  bool              m_bover;                           // ゲームオーバー
public:
  void              CTetrisField() { m_shape=NULL; m_bover=false; }
  void              Init();
  void              Deinit();
  void              Down();
  void              Left();
  void              Right();
  void              Rotate();
  void              Drop();
private:
  void              NewShape();
  void              CheckAndDeleteRows();
  void              LabelOver();
 };

public: 指定子の後 (と次のアクセス指定子の前)に宣言されたクラスメンバとメソッドはプログラムによってクラスオブジェクトへの参照として利用出来ます。この例では、CTetrisField()、Init()、Deinit()、Down()、Left()、Right()、Rotate() 及び Drop() 関数がそれらのメンバです。

private: 指定子の後(と次のアクセス指定子の前)に宣言されたクラスメンバはこのクラスのメンバ関数によってのみ使用出来ます。要素へのアクセスの指定子は、必ずコロン( : )で終了し、クラス定義に何回でも使用することが出来ます。

基本クラスのメンバへのアクセスは、派生クラスの承継中に再定義することが出来ます。

final修飾子

final修飾子は、クラスの宣言時にそこからのさらなる継承を禁止します。クラスのインターフェイスがそこへ更なる変更を加える必要がないものである、またはセキュリティ上の理由で変更が許可されていない場合は、クラスをfinal修飾子で宣言してください。また、全てのクラスのメソッドも暗黙的にfinalとみなされます。

class CFoo final
{
//--- тело класса
};
 
class CBar : public CFoo
{
//--- クラス本体
};

final修飾子を使用したクラスからの継承を試行すると、上記の例で示したように、コンパイルはエラーを出します。

cannot inherit from 'CFoo' as it has been declared as 'final'
see declaration of 'CFoo'

共同体 (union)

共同体は、同じメモリ領域を共有する複数の変数からなる特別なデータ型です。したがって、共用体は、同じビットシーケンスを 2 つ(またはそれ以上)の異なる方法で解釈する機能を提供します。共同体の宣言は structure の宣言と似ていて、union キーワードから始まります。

union LongDouble
{
long   long_value;
double double_value;
};

構造体とは異なり、さまざまな共用体メンバは同じメモリ領域に属します。この例では、LongDouble の共用体は、同じメモリ領域を共有する long および double 型値で宣言されます。構造体とは異なり、この共同体で long 整数値と double 実数値を同時に設定することは不可能です。これは、long_value 変数と double_value 変数は(メモリ内で)重複しているためです。一方、MQL5プログラムは、共用体に含まれるデータをいつでも整数値(long)または実数値(double)として処理することができます。そのため、共用体を使用すれば、同じデータシーケンスを表すために2つ(またはそれ以上)のオプションを受け取ることができます。

共用体が宣言されると、コンパイラは自動的に サイズが最大の型を格納するのに十分なメモリ領域を変数共用体に割り当てます。共用体要素にアクセスするためには、構造体の場合と同じ構文が使用されます。これはドット演算子です。

union LongDouble
{
long   long_value;
double double_value;
};
//+------------------------------------------------------------------+
//| スクリプトプログラムを開始する関数                        |
//+------------------------------------------------------------------+
void OnStart()
 {
//---
  LongDouble lb;
//--- 無効な -nan(ind) 番号を取得して表示する
  lb.double_value=MathArcsin(2.0);
  printf("1.  double=%f                integer=%I64X",lb.double_value,lb.long_value);
//--- 最大の正規値 (DBL_MAX)
  lb.long_value=0x7FEFFFFFFFFFFFFF;
  printf("2.  double=%.16e  integer=%I64X",lb.double_value,lb.long_value);
//--- 最小の正の正規値 (DBL_MIN)
  lb.long_value=0x0010000000000000;    
  printf("3.  double=%.16e  integer=%.16I64X",lb.double_value,lb.long_value);
 }
/*  実行結果
   1. double=-nan(ind)                integer=FFF8000000000000
   2. double=1.7976931348623157e+308  integer=7FEFFFFFFFFFFFFF
   3. double=2.2250738585072014e-308  integer=0010000000000000
*/

共用体はプログラムが同じメモリデータを異なった方法で解釈することを可能にするので、普通でない型変換が必要なときに使われます。

その特性によって、共用体は継承に使用することも、static メンバを持つこともできません。他のすべての側面では、共用体は、すべてのメンバがゼロオフセットを持つ構造体のように動作します。次の型は、共用体のメンバになることはできません。

  • 動的配列
  • 文字列
  • オブジェクトポインタおよび関数ポインタ
  • クラスオブジェクト
  • コンストラクタまたはデストラクタを持つ構造体オブジェクト
  • ポイント1〜5のメンバを持つ構造体オブジェクト

クラス同様、共用体はコンストラクタ、デストラクタ、メンバを持つことができます。デフォルトで、共用体メンバのアクセスタイプは public です。private 要素を作成するには、private キーワードを使用します。これらの可能性はすべて、ColorToARGB() 関数のように color 型を ARGB に変換する例で示すことができます。

//+------------------------------------------------------------------+
//| color(BGR) を ARGB に変換する共用体                          |
//+------------------------------------------------------------------+
union ARGB
 {
  uchar             argb[4];
  color             clr;
  //--- コンストラクタ
                    ARGB(color col,uchar a=0){Color(col,a);};
                   ~ARGB(){};
  //--- public メソッド
public:
  uchar   Alpha(){return(argb[3]);};
  void    Alpha(const uchar alpha){argb[3]=alpha;};
  color   Color(){ return(color(clr));};
  //--- private メソッド
private:
  //+------------------------------------------------------------------+
  //| アルファチャンネルの値と色を設定する                            |
  //+------------------------------------------------------------------+
  void   Color(color col,uchar alpha)
    {
    //--- clr メンバの色を設定する
     clr=col;
    //--- アルファコンポーネント(不透明度)の値を設定する
     argb[3]=alpha;
    //--- R(赤) および B(青) コンポーネントのバイトを交換する
    uchar t=argb[0];argb[0]=argb[2];argb[2]=t;
    };
 };
//+------------------------------------------------------------------+
//| スクリプトプログラムを開始する関数                        |
//+------------------------------------------------------------------+
void OnStart()
 {
//--- 0x55 は 55/255=21.6 % を意味する(0% - 完全に透明)
  uchar alpha=0x55;
//--- カラー型は 0x00BBGGRR で示される
  color test_color=clrDarkOrange;
//--- ARGB 共用体からのバイトの値はここで受け入れられる
  uchar argb[];
  PrintFormat("0x%.8X - here is how the 'color' type look like for %s, BGR=(%s)",
              test_color,ColorToString(test_color,true),ColorToString(test_color));
//--- ARGB 型は 0x00RRGGBB で示され、RR および BB コンポーネントが交換されている
  ARGB argb_color(test_color);
//--- バイト配列を複製する
  ArrayCopy(argb,argb_color.argb);
//--- ARGB 表現では次のようになる  
  PrintFormat("0x%.8X - ARGB representation with the alpha channel=0x%.2x, ARGB=(%d,%d,%d,%d)",
              argb_color.clr,argb_color.Alpha(),argb[3],argb[2],argb[1],argb[0]);
//--- 不透明度を追加する
  argb_color.Alpha(alpha);
//--- ARGB を 'color' 型として定義してみる
  Print("ARGB как color=(",argb_color.clr,")  alpha channel=",argb_color.Alpha());
//--- バイト配列を複製する
  ArrayCopy(argb,argb_color.argb);
//--- ARGB 表現では次のようになる
  PrintFormat("0x%.8X - ARGB representation with the alpha channel=0x%.2x, ARGB=(%d,%d,%d,%d)",
              argb_color.clr,argb_color.Alpha(),argb[3],argb[2],argb[1],argb[0]);
//--- ColorToARGB() 関数の結果で確認する
  PrintFormat("0x%.8X - result of ColorToARGB(%s,0x%.2x)",ColorToARGB(test_color,alpha),
              ColorToString(test_color,true),alpha);
 }
/* 実行結果
  0x00008CFF - here is how the color type looks for clrDarkOrange, BGR=(255,140,0)
  0x00FF8C00 - ARGB representation with the alpha channel=0x00, ARGB=(0,255,140,0)
  ARGB as color=(0,140,255)  alpha channel=85
   0x55FF8C00 - ARGB representation with the alpha channel=0x55, ARGB=(85,255,140,0)
  0x55FF8C00 - result of ColorToARGB(clrDarkOrange,0x55)
*/

インターフェイス

インターフェイスは、クラスが今後実装することができる特定の機能を決定する為にあります。実際には、このクラスはメンバを含めたり、コンストラクタやデストラクタを持つことはできません。インターフェイス宣言された全てのメソッドは、明示的定義なしでも純粋仮想です。

例の通り、interfaceというキーワードを使ってインターフェイスを定義します。

//--- アニマルを記述する為の基本的なインターフェイス
interface IAnimal
 {
//--- インターフェイスメソッドはデフォルトでpublicアクセスを持っています
  void Sound(); // アニマルが生成するサウンド
 };
//+------------------------------------------------------------------+
//|  CCatクラスはIAnimalインターフェイスから継承される                             |
//+------------------------------------------------------------------+
class CCat : public IAnimal
 {
public:
                    CCat() { Print("Cat was born"); }
                   ~CCat() { Print("Cat is dead");  }
  //--- IAnimalインターフェイスのSoundメソッドを実装します
  void Sound(){ Print("meou"); }
 };
//+------------------------------------------------------------------+
//|  CDogクラスはIAnimalインターフェイスから継承されます                           |
//+------------------------------------------------------------------+
class CDog : public IAnimal
 {
public:
                    CDog() { Print("Dog was born"); }
                   ~CDog() { Print("Dog is dead");  }
  //--- IAnimalインターフェイスのSoundメソッドを実装します
  void Sound(){ Print("guaf"); }
 };
//+------------------------------------------------------------------+
//| スクリプトプログラムを開始する関数                        |
//+------------------------------------------------------------------+
void OnStart()
 {
//--- IAnimal型のオブジェクトへのポインタ配列
  IAnimal *animals[2];
//--- IAnimalの子孫を生成し、配列にそれらへのポインタを保存します    
  animals[0]=new CCat;
  animals[1]=new CDog;
//--- 各子孫にIAnimalベースインターフェイスのSound()メソッドを呼び出します  
  for(int i=0;i<ArraySize(animals);++i)
     animals[i].Sound();
//--- オブジェクトを削除します
  for(int i=0;i<ArraySize(animals);++i)
    delete animals[i];
//--- 実行結果
/*
  Cat was born
  Dog was born
  meou
  guaf
  Cat is dead
  Dog is dead
*/
 }

抽象クラスの場合のように、継承なしにインターフェイスのオブジェクトを作成することはできません。インターフェイスは他のインターフェイスからのみ継承することができ、クラスの祖先とすることもできます。また、インターフェイスは常に公共の可視性を持っています。

インターフェイスはクラス宣言または構造内で宣言することができませんが、インターフェイスへのポインタはvoid *型変数に格納することができます。一般的に、void *型変数にはあらゆるクラスのオブジェクトへのポインタを保存することができます。void *ポインタを特定のクラスのオブジェクトへのポインタに変換するには、dynamic_cast演算子を使用する必要があります。変換ができない場合には、dynamic_castの操作結果はNULLとなります。

参照

オブジェクト指向プログラミング