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キーワードでは宣言できません。
すでに存在するものから派生クラスを生成するためのシンタックスは次のようになります。
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
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キーワードでは宣言できません。