MQL4言語の基礎2

7、継承 2

 すでに存在するものから派生クラスを生成するためのシンタックスは次のようになります。

class class_name :
          (public | protected | private) opt  base_class_name
  {                                  
    class members declaration
  };

派生クラスの外観は、そのメンバの継承の可視性(オープン性)です。

public、protected、privateのキーワードは、
派生クラスに可能な、ベースクラスの制限を示すために使用されます。

派生クラスのコロンの後の、publicキーワードは、
ベースクラスCShapeの、protectedと、publicメンバが、
派生クラスCCircleの、protected、publicメンバとして継承されることを示しています。
べースクラスの、privateなクラスメンバは、派生クラスに関しては利用可能ではありません。

public継承は、派生クラス(CCircle、CSquare)がCShapeであることを意味します。

すなわち、正方形(CSquare)は、形(CShape)です。
しかし形は正方形になる必要はありません。

派生クラスはベースクラスの修正です。
ベースクラスのprotected、publicメンバは継承します。

ベースクラスのコンストラクタとデストラクタは継承されません。
ベースクラスのメンバに加えて、新しいメンバが派生クラスに加えられます。

派生クラスは、ベースクラスからとは違う、
メンバ関数のインプリメンテーションを含みます。
同じ関数の名前の意味が異なるシグネイチャとなる時、共通なoverloadはありません。
(訳注:シグネイチャとは引数とそのデータ型の組のこと。)

protected継承での、
ベースクラスのpublicとprotectedメンバは派生クラスのprotectedメンバになります。
private継承での、
ベースクラスのpublic、protecteメンバは派生クラスのprivateメンバになります。
protectedとprivate継承は、
「派生クラスのオブジェクトはベースクラスのオブジェクトである」は正しくはないという関係です。

protectedとprivate継承の型は稀です。
それぞれ注意深く使用される必要があります。

継承の型(public、protected、private)は、
派生クラスの継承階層で、ベースクラスのアクセスに影響ありません

任意型の継承で、唯一public、protectedとしてアクセスを規定されて、
宣言されたベースクラスメンバが、
派生クラスの外で利用可能となります。

次のサンプルをじっくり考えて見ましょう。

#property copyright " "
#property link      " "
#property version   " "
//+------------------------------------------------------------------+
//| Example class with a few access types                            |
//+------------------------------------------------------------------+
class CBaseClass
  {
private:             //--- The private member is not available from derived classes
   int               m_member;

protected:           //--- The protected method is available from the base class and its derived classes
   int               Member(){return(m_member);}

public:              //--- Class constructor is available to all members of classes
                     CBaseClass(){m_member=5;return;};

private:             //--- A private method for assigning a value to m_member
   void              Member(int value) { m_member=value;};

  };
//+------------------------------------------------------------------+
//| Derived class with errors                                        |
//+------------------------------------------------------------------+
class CDerived: public CBaseClass // specification of public inheritance can be omitted, since it is default
  {
public:
   void Func() // In the derived class, define a function with calls to base class members
     {
      //--- An attempt to modify a private member of the base class
      m_member=0;        // Error, the private member of the base class is not available

      Member(0);         // Error, the private method of the base class is not available in derived classes

      //--- Reading the member of the base class

      Print(m_member);   // Error, the private member of the base class is not available

      Print(Member());   // No error, protected method is available from the base class and its derived classes
     }
  };



上記の例では、CBaseClassがコンストラクタのpublicメソッドをただ一つ持ちます。
コンストラクタはクラスオブジェクトが生成されるとき自動的に呼ばれます。
したがって、プライベートメンバm_memmberとプロテクトメンバMember()は外から呼ばれません。

しかし、public継承の場合は、ベースクラスのMember()メソッドは、
派生クラスからは呼ぶことは利用可能ではありません。

protected継承の場合、public、protectedのベースクラスの全メンバはプロテクトになります。
それはベースクラスのパブリックデータメンバとメソッドが、外からアクセスできるならば、
protected継承での派生クラスと、そのさらなる派生クラスからのみ可能であることを意味しています。


//+------------------------------------------------------------------+
//| Example class with a few access types                            |
//+------------------------------------------------------------------+
class CBaseMathClass
  {
private:             //--- The private member is not available from derived classes
   double            m_Pi;

public:              //--- Getting and setting a value for m_Pi
   void              SetPI(double v){m_Pi=v;return;};

   double            GetPI(){return m_Pi;};

public:              // The class constructor is available to all members
                     CBaseMathClass() {SetPI(3.14);  PrintFormat("%s",__FUNCTION__);};
  };
//+------------------------------------------------------------------+
//| Derived class, in which m_Pi cannot be modified                  |
//+------------------------------------------------------------------+
class CProtectedChildClass: protected CBaseMathClass // Protected inheritance
  {
private:
   double            m_radius;

public:              //--- Public methods in the derived class
   void              SetRadius(double r){m_radius=r; return;};
   double            GetCircleLength(){return GetPI()*m_radius;};
  };
//+------------------------------------------------------------------+
//| Script starting function                                         |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- When creating a derived class, the constructor of the base class will be called automatically
   CProtectedChildClass pt;

//--- Specify radius
   pt.SetRadius(10);
   PrintFormat("Length=%G",pt.GetCircleLength());
//--- If comment the string below, we will get an error at the stage of compilation, since SetPi() is now protected
// pt.SetPI(3);

//--- Now declare a variable of the base class and try to set the Pi constant equal to 10
   CBaseMathClass bc;
   bc.SetPI(10);

//--- Here is the result
   PrintFormat("bc.GetPI()=%G",bc.GetPI());
  }



例ではベースクラスCBaseMathClassのメソッドSetPI()GetPI()が、
プログラムのほかの場所から呼び出すことがオープンになり、利用可能となります。
しかし、同じとき、CProtectedChildClassは、それから派生しますが、
このメソッドは、CProtectedChildClassのメソッドあるいは派生クラスからのみ呼ぶことが出来ます。

private継承の場合、
ベースクラスのパブリック、プロテクトなアクセスである全メンバは、
プライベートになり、
それを呼び出すことは、さらなる継承からは不可能になります。

MQL5は多重継承は持ちません。

(C++では、派生クラスが複数ベースクラスを継承し定義されますが、
多重継承には菱形継承などのやっかいな問題あります。

class derived_name : base_1, ... ,base_n {...public:derived;...};
等)

//+------------------------------------------------------------------------+




8、ポリモーフィズム(多態性)


 同じ関数要素を呼び出すとき、
ポリモーフィズムは、継承された異なるクラスで、様々な方法により目標を達成します。

ポリモーフィズムは、継承クラスの生成を助け、
振る舞いを記述するユニバーサルなメカニズムをつくります。

べースクラスCShape、メンバ関数GetArea()の
面積計算の設計を続けます。
ベースクラスを継承した全ての継承クラスで、
色々な形の面積の関数を再定義します。

正方形(class CSquare)の面積は辺から、
円(class CCircle)の面積は半径から計算します。

CShape型のオブジェクトを配列にするため、arrayを定義し、
ベースクラスのオブジェクトと、全ての継承クラスのオブジェクトを
配列にします。これでarrayの各要素の関数を呼ぶことができます。

//--- Base class
class CShape
  {
protected:
   int            m_type;                // Shape type
   int            m_xpos;                // X - coordinate of the base point
   int            m_ypos;                // Y - coordinate of the base point
public:
   void           CShape(){m_type=0;};   // constructor, type=0
   int            GetType(){return(m_type);};// returns type of the shape
virtual
   double         GetArea(){return (0); }// returns area of the shape
  };

派生クラスのメンバ関数getArea()は、0の値を返します。
この関数の実装は変化します。

//--- The derived class Circle
class CCircle : public CShape            // After a colon we define the base class
  {                                      // from which inheritance is made
private:
   double         m_radius;              // circle radius

public:
   void           CCircle(){m_type=1;};  // constructor, type=1
   void           SetRadius(double r){m_radius=r;};
   virtual double GetArea(){return (3.14*m_radius*m_radius);}// circle area
  };

クラスSquareの宣言に関しても同様です。

//--- The derived class Square
class CSquare : public CShape            // After a colon we define the base class
  {                                      // from which inheritance is made
private:
   double          m_square_side;        // square side

public:
   void            CSquare(){m_type=2;}; // constructor, type=1
   void            SetSide(double s){m_square_side=s;};
   virtual double  GetArea(){return (m_square_side*m_square_side);}// square area
  };

正方形、円の面積に関しては、それぞれm_radiusとm_square_sideが必要です。
クラスの宣言では、関数SetRadius()とSetSide()をそれぞれ加えます。

一つのベースタイプCShapeから派生する
異なるタイプの(CCircleとCSquare)オブジェクトを仮に設定します。

ポリモーフィズムは、生成しているべースクラスCShapeのオブジェクトの配列を認めます。
この配列を宣言するときには、これらのオブジェクトはまだ知られておらず、タイプはまだ定義されていません。

配列の各要素のオブジェクトが何のタイプかは、プログラムの実施中、直接与えられません。
このため、適切なクラスのオブジェクトの動的生成を必要とし、
したがって、オブジェクトの代わりに、オブジェクトポインタを使用する必要性があります。

newオペレータは動的オブジェクトの生成の為に使用されます。
各々のオブジェクトはdeleteオペレータを使用して、個々、明示的に消去されます。

次のスクリプトの例は、
CShapeタイプのポインタのarrayを宣言し、
各要素(新しいClass_Name)の固有のオブジェクトを生成します。

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Declare an array of object pointers of the base type

   CShape *shapes[5];   // An array of pointers to CShape object

//--- Here fill in the array with derived objects
//--- Declare a pointer to the object of CCircle type

   CCircle *circle=new CCircle();

//--- Set object properties at the circle pointer

   circle.SetRadius(2.5);

//--- Place the pointer value in shapes[0]

   shapes[0]=circle;

//--- Create another CCircle object and write down its pointer in shapes[1]
   circle=new CCircle();
   shapes[1]=circle;
   circle.SetRadius(5);

//--- Here we intentionally "forget" to set a value for shapes[2]
//circle=new CCircle();
//circle.SetRadius(10);
//shapes[2]=circle;

//--- Set NULL for the element that is not used
   shapes[2]=NULL;

//--- Create a CSquare object and write down its pointer to shapes[3]
   CSquare *square=new CSquare();
   square.SetSide(5);
   shapes[3]=square;

//--- Create a CSquare object and write down its pointer to shapes[4]
   square=new CSquare();
   square.SetSide(10);
   shapes[4]=square;

//--- We have an array of pointers, get its size

   int total=ArraySize(shapes);

//--- Pass in a loop through all pointers in the array
   for(int i=0; i<5 i="" p="">     {
      //--- If the pointer at the specified index is valid
      if(CheckPointer(shapes[i])!=POINTER_INVALID)
        {
         //--- Log the type and square of the shape
         PrintFormat("The object of type %d has the square %G",
               shapes[i].GetType(),
               shapes[i].GetArea());
        }
      //--- If the pointer has type POINTER_INVALID
      else
        {
         //--- Notify of an error

         PrintFormat("Object shapes[%d] has not been initialized! Its pointer is %s",
                     i,EnumToString(CheckPointer(shapes[i])));
        }
     }

//--- We must delete all created dynamic objects

   for(int i=0;i     {
      //--- We can delete only the objects with pointers of POINTER_DYNAMIC type

      if(CheckPointer(shapes[i])==POINTER_DYNAMIC)
        {
         //--- Notify of deletion

         PrintFormat("Deleting shapes[%d]",i);

         //--- Delete an object by its pointer

         delete shapes[i];
        }
     }
  }

deleteオペレータを使用しオブジェクトを消去するとき、そのポインタのタイプをチェックします。

唯一、POINTER_DYNAMICポインタのオブジェクトはdeleteを使用し消去します。
他の型のポインタは、errorになります。

しかし継承で関数を再定義するポリモーフィズムは、
そのインプレメンテーションと、異なるパラメータセットの、同じ関数を定義します。

これは、異なるタイプ、パラメータセットの、同じ名前の関数を意味します。
この場合は、ポリモーフィズムは、関数オーバーロードを実装します。

---------------------------------------------------------------------------------



9、オーバーロード

 メソッドのオーバーロードは、
一つのクラスで、異なる数のパラメータを使用した同じ名前の
二つあるいはそれ以上のメソッドを定義します。

そのプロセスは、メソッドオーバーローデイングとして適用されます。

メソッドオーバーローデイングは、多態性実現の方法の一つです。
メソッドのオーバーローデイングは、
関数のオーバーロードと同じ規則(*参照1)に従って行われます。

メソッドオーバーローデイングで呼ばれる関数が、正確に適合しない時、
コンパイラは、3つのレベルを順次、適合する関数を検索します。

1、クラスメソッド内の検索
2、ベースクラスメソッド内で、最も早い、最も近い原型からの一貫した検索
3、他の関数の検索

全レベルで正確に一致するものがなければ、
異なるレベルで幾つかの適合する関数を検索し、
最小のレベルで見つけられた関数が使用されます。
一つのレベル内では、一つ以上適合する関数はありません。

(*参照1:MQL5 Function Overloadingより)
関数のオーバーロードのアルゴリズムとは、
1.正確な一致(もし可能なら )。
2.標準タイプの拡大を試みる。
(例、char、uchar、short、ushort、int、
  uint、long、ulong、float、double、etc)
3.標準タイプキャスティングを試みる。
(例、日本語ではキャストですが、英語ではTypecastingです。
   データタイプの変換は下記のように左のタイプを右に換えた時は、
   データは失われません。 
   uchar→ushort→uint→ulong、
   char→short→int→long、
   float→double、etc)

の3つです。

以下は関数オーバーロードの例です。
------------------------------------------
//+------------------------------------------------------------------+
//| The calculation of average for an array of double type           |
//+------------------------------------------------------------------+
double AverageFromArray(const double & array[],int size)
  {
   if(size<=0) return 0.0;
   double sum=0.0;
   double aver;
//---
   for(int i=0;i     {
      sum+=array[i];    // Summation for the double
     }
   aver=sum/size;       // Just divide the sum by the number
//---
   Print("Calculation of the average for an array of double type");
   return aver;
  }
//+------------------------------------------------------------------+
//| The calculation of average for an array of int type              |
//+------------------------------------------------------------------+
double AverageFromArray(const int & array[],int size)
  {
   if(size<=0) return 0.0;
   double aver=0.0;
   int sum=0;
//---
   for(int i=0;i     {
      sum+=array[i];     // Summation for the int
     }
   aver=(double)sum/size;// Give the amount of type double, and divide
//---
   Print("Calculation of the average for an array of int type");
   return aver;
  }

void OnStart()
  {
//---
   int    a[5]={1,2,3,4,5};
   double b[5]={1.1,2.2,3.3,4.4,5.5};
   double int_aver=AverageFromArray(a,5);
   double double_aver=AverageFromArray(b,5);
   Print("int_aver = ",int_aver,"   double_aver = ",double_aver);
  }
//--- Result of the script
// Calculate the average for an array of int type
// Calculate the average for an array of double type
// int_aver= 3.00000000    double_aver= 3.30000000

//+------------------------------------------------------------------+



10、仮想関数

  virtualキーワードは、関数指定子(function specifier)です。
ベーシッククラスと派生クラスで、
ランタイムでの特殊な関数メンバを、
動的に選択するメカニズムを提供します。

構造体は仮想関数を持つことは出来ません。
関数メンバに関してのみ、宣言できます。
仮想関数は、普通の関数のように、
実施する中味(関数定義の中括弧の中)を持つ必要があります。
その意味は他の関数のそれと同じにです。

 仮想関数は、派生クラスでオーバーライドされます。
仮想関数として呼ばれる関数は、動的(ランタイムで)に行われます。
典型的には、ベースクラスが仮想関数を持ち、
派生クラスがこの関数自身のバージョンを持ちます。

 ベースクラスへのポインタは、
ベースクラスのオブジェクトか、
派生クラスのオブジェクトのどちらかを、
指示することが出来ます。

 呼ばれたメンバ関数は、ランタイムでは、
オブジェクトの型に従いますが、ポインタの型ではありません。
派生クラスのメンバがないとき、
ベースクラスの仮想関数がデフォルトで使用されます。

virtualキーワードが宣言されているかいないに関わらず、
デストラクタは常に仮想です。


MT5_Teturis.mq5の例で、仮想関数を考えてみます。

ファイルMT5_Teturis.mq5で、ベースクラスCTetrisShapの仮想関数Draw()が定義されています。

//+------------------------------------------------------------------+
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[]);
  };

さらに各派生クラスで、この関数を実装します。
この例のCTetrisShape1には、下記のDraw()メソッドがあります。

class CTetrisShape1 : public CTetrisShape
  {
public:
   //--- shape drawing
   virtual void      Draw()
     {
      int    i;
      string name;
      //---
      if(m_turn==0 || m_turn==2)
        {
         //--- horizontal
         for(i=0; i<4 i="" p="">           {
            name=SHAPE_NAME+(string)i;
            ObjectSetInteger(0,name,OBJPROP_XDISTANCE,m_xpos+i*SHAPE_SIZE);
            ObjectSetInteger(0,name,OBJPROP_YDISTANCE,m_ypos);
           }
        }
      else
        {
         //--- vertical
         for(i=0; i<4 i="" p="">           {
            name=SHAPE_NAME+(string)i;
            ObjectSetInteger(0,name,OBJPROP_XDISTANCE,m_xpos);
            ObjectSetInteger(0,name,OBJPROP_YDISTANCE,m_ypos+i*SHAPE_SIZE);
           }
        }
     }
  }

正方形はクラスCTetrisShape6で、下記のDraw()メソッドを持ちます。

class CTetrisShape6 : public CTetrisShape
  {
public:
   //--- Shape drawing
   virtual void      Draw()
     {
      int    i;
      string name;
      //---
      for(i=0; i<2 i="" p="">        {
         name=SHAPE_NAME+(string)i;
         ObjectSetInteger(0,name,OBJPROP_XDISTANCE,m_xpos+i*SHAPE_SIZE);
         ObjectSetInteger(0,name,OBJPROP_YDISTANCE,m_ypos);
        }
      for(i=2; i<4 i="" p="">        {
         name=SHAPE_NAME+(string)i;
         ObjectSetInteger(0,name,OBJPROP_XDISTANCE,m_xpos+(i-2)*SHAPE_SIZE);
         ObjectSetInteger(0,name,OBJPROP_YDISTANCE,m_ypos+SHAPE_SIZE);
        }
     }
  };

下記の生成オブジェクトは、
この仮想関数か、その派生クラスの仮想関数を呼び出します。

void CTetrisField::NewShape()
  {
//--- creating one of the 7 possible shapes randomly
   int nshape=rand()%7;
   switch(nshape)
     {
      case 0: m_shape=new CTetrisShape1; break;
      case 1: m_shape=new CTetrisShape2; break;
      case 2: m_shape=new CTetrisShape3; break;
      case 3: m_shape=new CTetrisShape4; break;
      case 4: m_shape=new CTetrisShape5; break;
      case 5: m_shape=new CTetrisShape6; break;
      case 6: m_shape=new CTetrisShape7; break;
     }
//--- draw
   m_shape.Draw();
//---
  }




11、静的メンバ

 クラスの静的メンバでは、ストレージ(記憶)クラス修飾子、staticを宣言します。
静的データメンバは、このクラスの全インスタンスで共有され、
そして、一つの場所に置かれます。
非静的データメンバは、各クラスオブジェクトの変数として生成します。
クラスの静的メンバを宣言するのは、
プログラムのグローバルレベルでデータを宣言する必要があるときです。
データとクラス間の関係をブレイクし、
クラスでそれらをハンドリングするためにデータとメソッドを結合するという、
OOPの基本的方法論と矛盾することになります。

 静的メンバは、クラススコープで生じる、特定のインスタンスへ指定しないクラスデータとなります。
特定のインスタンスに従属しない、静的クラスメンバの参照は次のようになります。
//+-------------------------
class_name::variable
//+-------------------------
class_nameはクラスの名前、variableはクラスメンバの名前です。
クラスの静的メンバにアクセスするためには、
コンテクストソリューションオペレータ :: が使われます。
クラスメソッド内で静的メンバにアクセスするとき、コンテクストオペレータは任意です。
クラスの静的メンバは、望む値に明示的にイニシャライズされます。
このとき、宣言とグローバルスコープでイニシャライズされなければなりません。

 例えば、テキストの構文解析に使用されるクラスCParserがあります。
構文解析では、ワードとキャラクタを処理するため、全体のカウントが必要です。
クラスメンバの、ただ一つの静的な宣言が必要で、
グローバルレベルでそれらを初期化する必要があります。
クラスのすべての初期化において、ワードとキャラクタに共通なカウントが初期化されます。
//+------------------------------------------------------------------+
//| Class "Text analyzer"                                            |
//+------------------------------------------------------------------+
class CParser
  {
public:
   static int        s_words;
   static int        s_symbols;
   //--- Constructor and destructor
                     Parser(void);
                    ~Parser(void){};
  };
...
//--- Initialization of static members of the Parser class at the global level
int CParser::s_words=0;
int CParser::s_symbols=0;
//+-------------------------

 静的クラスメンバはconstキーワードを宣言できます。
そのような静的定数は、グローバルレベルで、constキーワードを付けて、初期化しなければなりません。
//+------------------------------------------------------------------+
//| Class "Stack" for storing processed data                         |
//+------------------------------------------------------------------+
class CStack
  {
public:
       CStack(void);
      ~CStack(void){};
...
private:
   static const int  s_max_length; // Maximum stack capacity
  };

//--- Initialization of the static constant of the CStack class

const int CStack::s_max_length=1000;
//+-------------------------


Pointer this(非静的メンバとして)
 キーワードthisは、それ自身への暗示的に宣言されたポインタを意味します。
それは、クラスの非静的メソッドとしてのみ使用できます。
ポインタthisは、任意のクラスの暗示的な非静的メンバです。


静的関数では、クラスの静的メンバ、静的メソッドのみアクセスできます。

Static メソッド
 MQL5では、static型のメンバ関数を使うことが出来ます。
static修飾子は、クラス内で宣言し、
関数の戻し値の型として、先行させなければなりません。
(下記の例では:static int    Capacity(); )
//+-------------------------
class CStack
  {
public:
   //--- Constructor and destructor
                     CStack(void){};
                    ~CStack(void){};
   //--- Maximum stack capacity
   static int        Capacity();
private:
   int               m_length;     // The number of elements in the stack
   static const int  s_max_length; // Maximum stack capacity
  };
//+------------------------------------------------------------------+
//| Returns the maximum number of elements to store in the stack     |
//+------------------------------------------------------------------+
int CStack::Capacity(void)
  {
   return(s_max_length);
  }

//--- Initialization of the static constant of the CStack class
const int CStack::s_max_length=1000;

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- declare CStack type variable
   CStack stack;
//--- call the object's static method

   Print("CStack.s_max_length=",stack.Capacity());

//--- it can also be called the following way,
//--- as the method is static and does not require the presence of the object

   Print("CStack.s_max_length=",CStack::Capacity());
  }
//+-------------------------

const
 const修飾子の付いたメソッドは定数で、
そのクラスのメンバを修正できません。
クラスのconst関数とconst引数の宣言は、
コンスト-コレクトネスコントロールと呼ばれます。
このコントロールで、
コンパイラはオブジェクトの値の一貫性を確実にし、
何か悪いときにはコンパイラでエラーを返します。
 const修飾子は、クラス宣言内で、独立変数のリストの後に置かれます。
クラスの外での定義は、const修飾子をもう一度含まなければなりません。
(下記の例では:double CRectangle::Square(void) const )
//+------------------------------------------------------------------+
//| Class "Rectangle"                                                |
//+------------------------------------------------------------------+
class CRectangle
  {
private:
   double            m_width;      // Width
   double            m_height;     // Height
public:
   //--- Constructors and destructor
                     CRectangle(void):m_width(0),m_height(0){};
                     CRectangle(const double w,const double h):m_width(w),m_height(h){};
                    ~CRectangle(void){};
   //--- Calculating the area
   double            Square(void) const;
   static double     Square(const double w,const double h);// { return(w*h); }
  };
//+------------------------------------------------------------------+
//| Returns the area of the "Rectangle" object                       |
//+------------------------------------------------------------------+
double CRectangle::Square(void) const
  {
   return(Square(m_width,m_height));
  }
//+------------------------------------------------------------------+
//| Returns the product of two variables                             |
//+------------------------------------------------------------------+
static double CRectangle::Square(const double w,const double h)
  {
   return(w*h);
  }
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Create a rectangle rect with the sides equal to 5 and 6
   CRectangle rect(5,6);
//--- Find the rectangle area using a constant method

   PrintFormat("rect.Square()=%.2f",rect.Square());

//--- Find the product of numbers using the static method of class CRectangle

   PrintFormat("CRectangle::Square(2.0,1.5)=%f",CRectangle::Square(2.0,1.5));
  }
//+-------------------------

コンスタンシーコントロールが選択された場合、
コンパイラは特殊な最適化をします。
例えば、コンスタントオブジェクトはリードオンリーメモリになります。

この関数を呼ぶとき、
この修飾子はインスタンスメンバのコンスタンシーを守るので、
static関数はconst修飾子を定めることは出来ません。
上記の記述では、static関数は、クラスの非静的メンバにアクセス出来ません。
//+---筆者の解説
つまり、
static double     Square(const double w,const double h);// { return(w*h); }
では、
CRectangle rect(5,6);の初期値のメンバ数値で、
 PrintFormat("rect.Square()=%.2f",rect.Square());を計算し、
CRectangle::Square(2.0,1.5));の入力値で
PrintFormat("CRectangle::Square(2.0,1.5)=%f",CRectangle::Square(2.0,1.5));の計算をします。
なので、
PrintFormat("rect.Square()=%.2f",rect.Square());
は30になり、
PrintFormat("CRectangle::Square(2.0,1.5)=%f",CRectangle::Square(2.0,1.5));
は3.000000になります。


12、関数テンプレート

argument(実引数)とparameter(仮引数)の違い。
サブルーチンや関数で呼ぶ側の引数は実引数、
呼ばれる側の引数は仮引数。

 オーバーロード関数は、色々なデータ型で同じオペレータとして、
共通に動作させるとき使用します。
ArraySize()は、そのような関数の例です。
関数は、任意の型のArray(配列)のサイズを返します。
このシステム関数は、オーバーロードされ、
完全な実装により、MQL5アプリケーション開発者から隠されています。
int  ArraySize(
   void&  array[]      // checked array
   );

MQL5言語コンパイラは、この関数の各々の呼び出しのとき、必要な実装をします。
例えば、それは、どのようにIntegerタイプのarray(配列)にするかということです。
int  ArraySize(
   int&  array[]      // array with int type elements
   );

ヒストリカルデータフォーマットの相場の動きに関するMqlRates型arrayを、
ArraySize()関数は次の方法で表現します。
int  ArraySize(
   MqlRates&  array[] // array filled with MqlRates type values
   );

このように、異なる型で動作するとき、
同じ関数(同じタイプの)を使用するには便利です。
しかしながら、必要な関数が全てのデータタイプで正しく動作するためには、
オーバーロードが行われなければなりません。

 それには便利なソリューションがあります。
もし同じ操作が各データ型で実行されるならば、関数テンプレートを使用します。
このとき、ただ一つ関数テンプレートのための記載事項が必要となります。
テンプレートを書くときは、動作しなければならないただ一つの形式的パラメータが指定されます。

関数が呼ばれるとき、使用される独立変数をベースにした各型の、
目的にふさわしいハンドリングのために、コンパイラは自動的に様々な関数を生成します。

 関数テンプレートの定義は、アングルブラケットの形式的なパラメータリストの、
temmplateキーワードで始まります。
各形式的 parameter(仮引数)は、typenameキーワードでプロテクトされます。
形式的パラメータの型は、ビルトインか、ユーザ定義の型になります。
それらは次のように使用されます。
1、関数独立変数の型の指定
2、関数戻し値の型の指定
3、関数定義内の変数の宣言
テンプレートパラメータの数は8個を超えません。
テンプレート定義の各々の形式的パラメータは、
関数パラメータのリストで、少なくとも一度表示されます。
各々の形式的なパラメータの名前はユニークにすべきです。

 以下は、任意の数(整数、実数)のarray(配列)の、
最も大きい値を検索する、関数テンプレートの定義です。

template
T ArrayMax(T &arr[])
  {
   uint size=ArraySize(arr);
   if(size==0) return(0);        
 
   T max=arr[0];
   for(uint n=1;n      if(max//---
   return(max);
  }

このテンプレートでは、
渡されたarray(配列)で最も大きい値を見つけ、
その値を返す関数を定義しています。

MQL5にビルトインされているArrayMaximum()関数の場合は、
最も大きい値を見つけるために使われ、
最も大きい値のインデックスのみ返します。
例えば、
//--- create an array

   double array[];
   int size=50;
   ArrayResize(array,size);

//---  fill with random values

   for(int i=0;i     {
      array[i]=MathRand();
     }

//--- find position of the highest value in the array

   int max_position=ArrayMaximum(array);

//--- now, get the highest value itself in the array

   double max=array[max_position];

//--- display the found value

   Print("Max value = ",max);

となります。
array(配列)で最も大きい値を得るために、
このように2つのステップで行われています。

しかし、
ArrayMax()関数テンプレートは、
関数内で目的の型のarray(配列)を渡し、
必要な型の結果を得ます。
それは、以下の最後の2つの行の代わりとなります。

//--- find position of the highest value in the array

   int max_position=ArrayMaximum(array);

//--- now, receive the highest value itself in the array

   double max=array[max_position];

ArrayMax()関数では、以下のように一つの行を使い、
返される値は関数に渡されるarray(配列)と同じ型です。

//--- find the highest value

   double max=ArrayMax(array);

この場合には、結果の型は自動的にarray(配列)の型に合わせ、ArrayMax()関数で返します。

 typenameキーワードは、
色々なデータ型で働く一般的な目的のメソッドを生成する手段として、
stringのargument(実引数)の型を得るために使用します。

 stringデータタイプ、string GetTypeName(const T &t)の関数を調べます。
(注:Trade.mqhは、MQL5にはあるけれどもMQL4にはないので、
String.mqhに変更しています。)

//#include
#include
void OnStart()
  {
//---
   //CTrade trade;
   CString str;
   double d_value=M_PI;
   int i_value=INT_MAX;
   Print("d_value: type=",GetTypeName(d_value), ",   value=", d_value);
   Print("i_value: type=",GetTypeName(i_value), ",   value=", i_value);
   //Print("trade: type=",GetTypeName(trade));  
   Print("trade: type=",GetTypeName(str));
//---
  }
//+------------------------------
//| Type is returned as a line                                    
//+------------------------------
template
string GetTypeName(const T &t)
  {
//--- return the type as a line
   return(typename(T));
//---
  }

関数テンプレートは、クラスメソッドにも使用できます。
例えば、

class CFile
  {
   ...
public:
   ...
   template
   uint WriteStruct(T &data);
  };

template

uint CFile::WriteStruct(T &data)
  {
   ...
   return(FileWriteStruct(m_handle,data));
  }
となります。

しかし、関数テンプレートはexport、virtual、#importキーワードでは宣言できません。