演算子の多重定義(オーバーロード)

コードの読み込みと書き込みを容易にするためには、いくつかの演算のオーバーロードが可能です。演算子のオーバーロードは、キーワード operator を使用して書かれます。可能な演算子のオーバーロードは次の通りです。

  • 二項 +、-、/、*、%、<<、>>、==、!=、<、>、<=、>=、=、+=、-=、/=、*=、%=、&=、|=、^=、<<=、>>=、&&、||、&、|、^
  • 単項 +、-、++、--、!、~
  • 代入演算子 =
  • インデックス作成演算子 []

 

操作のオーバーロードは構造体とクラスのように複雑なオブジェクトの(単純な式の形で書かれた)演算の表記を可能にします。より複雑な実装が隠されているので、オーバーロードを使用して演算の式を書くことでソースコードの表示が簡素化します。

例えば、実部と虚部で構成された複素数を見てみましょう。これらは数学で幅広く使用されています。MQL5 言語には複素数を表現するデータ型がありませんが構造体やクラスを使用して新しいデータ型を作成することが可能です。複雑な構造体を宣言し、四則演算を実装する 4 つのメソッドを定義してみます。

//+------------------------------------------------------------------+
//| 複素数演算のための構造体                                              |
//+------------------------------------------------------------------+
struct complex
 {
  double            re; // 実部
  double            im; // 虚部
  //--- コンストラクタ 
                    complex():re(0.0),im(0.0) {  }
                    complex(const double r):re(r),im(0.0) {  }
                    complex(const double r,const double i):re(r),im(i) {  }
                    complex(const complex &o):re(o.re),im(o.im) { }
  //--- 算術演算
  complex           Add(const complex &l,const complex &r) const; // 加算
  complex           Sub(const complex &l,const complex &r) const; // 減算
  complex           Mul(const complex &l,const complex &r) const; // 乗算
  complex           Div(const complex &l,const complex &r) const; // 除算
 };

さて、コードで複素数を表す変数を宣言して使用することが出来ます。

例えば、

void OnStart()
 {
//--- complex 型の変数を宣言して初期化する
  complex a(2,4),b(-4,-2);
  PrintFormat("a=%.2f+i*%.2f,   b=%.2f+i*%.2f",a.re,a.im,b.re,b.im);
//--- 2 つの数を足す
  complex z;
  z=a.Add(a,b);
  PrintFormat("a+b=%.2f+i*%.2f",z.re,z.im);
//--- 2 つの数を掛ける
  z=a.Mul(a,b);
  PrintFormat("a*b=%.2f+i*%.2f",z.re,z.im);
//--- 2 つの数を割る
  z=a.Div(a,b);
  PrintFormat("a/b=%.2f+i*%.2f",z.re,z.im);
//---
 }

しかし複素数の算術演算に「/」、「+」、「-」、「*」及び「/」の 通常の演算子を使用するのが便利です。

Keyword 演算子は型変換を実行するメンバ関数の定義に使用されます。クラスオブジェクトの変数の単項及び二項操作は非静的メンバ関数としてオーバーロードすることが出来ます。これらは暗黙的にクラスオブジェクトに使用されます。

ほとんどの二項演算は、クラス変数、またはこのクラスオブジェクトへのポインタなどの 1 つまたは両方の引数を受ける通常の関数のようにオーバーロードすることが出来ます。例の複素数型は、宣言でオーバーロードすると、次のようになります。

  //--- 演算子
  complex operator+(const complex &r) const { return(Add(this,r)); }
  complex operator-(const complex &r) const { return(Sub(this,r)); }
  complex operator*(const complex &r) const { return(Mul(this,r)); }
  complex operator/(const complex &r) const { return(Div(this,r)); }

スクリプトの完全な例は下記です。

//+------------------------------------------------------------------+
//| スクリプトプログラムを開始する関数                                          |
//+------------------------------------------------------------------+
void OnStart()
 {
//--- complex 型の変数を宣言して初期化する
  complex a(2,4),b(-4,-2);
  PrintFormat("a=%.2f+i*%.2f,   b=%.2f+i*%.2f",a.re,a.im,b.re,b.im);
  //a.re=5;
  //a.im=1;
  //b.re=-1;
  //b.im=-5;
//--- 2 つの数を足す
  complex z=a+b;
  PrintFormat("a+b=%.2f+i*%.2f",z.re,z.im);
//--- 2 つの数を乗算する
 
  z=a*b;
  PrintFormat("a*b=%.2f+i*%.2f",z.re,z.im);
//--- 2 つの数を割る
  z=a/b;
  PrintFormat("a/b=%.2f+i*%.2f",z.re,z.im);
//---
 }
//+------------------------------------------------------------------+
//| 複素数演算のための構造体                                              |
//+------------------------------------------------------------------+
struct complex
 {
  double            re; // 実部
  double            im; // 虚部
  //--- コンストラクタ 
                    complex():re(0.0),im(0.0) {  }
                    complex(const double r):re(r),im(0.0) {  }
                    complex(const double r,const double i):re(r),im(i) {  }
                    complex(const complex &o):re(o.re),im(o.im) { }
  //--- 算術演算
  complex           Add(const complex &l,const complex &r) const; // 加算
  complex           Sub(const complex &l,const complex &r) const; // 減算
  complex           Mul(const complex &l,const complex &r) const; // 乗算
  complex           Div(const complex &l,const complex &r) const; // 除算
  //--- 二項演算子
  complex operator+(const complex &r) const { return(Add(this,r)); }
  complex operator-(const complex &r) const { return(Sub(this,r)); }
  complex operator*(const complex &r) const { return(Mul(this,r)); }
  complex operator/(const complex &r) const { return(Div(this,r)); }
 };
//+------------------------------------------------------------------+
//| 加算                                                              |
//+------------------------------------------------------------------+
complex complex::Add(const complex &l,const complex &r) const
 {
  complex res;
//---
  res.re=l.re+r.re;
  res.im=l.im+r.im;
//--- 結果
  return res;
 }
//+------------------------------------------------------------------+
//| 減算                                                              |
//+------------------------------------------------------------------+
complex complex::Sub(const complex &l,const complex &r) const
 {
  complex res;
//---
  res.re=l.re-r.re;
  res.im=l.im-r.im;
//--- 結果
  return res;
 }
//+------------------------------------------------------------------+
//| 乗算                                                              |
//+------------------------------------------------------------------+
complex complex::Mul(const complex &l,const complex &r) const
 {
  complex res;
//---
  res.re=l.re*r.re-l.im*r.im;
  res.im=l.re*r.im+l.im*r.re;
//--- 結果
  return res;
 }
//+------------------------------------------------------------------+
//| 除算                                                              |
//+------------------------------------------------------------------+
complex complex::Div(const complex &l,const complex &r) const
 {
//--- 空の複素数
  complex res(EMPTY_VALUE,EMPTY_VALUE);
//--- 値がゼロかチェックする
  if(r.re==0 && r.im==0)
    {
    Print(__FUNCTION__+": number is zero");
    return(res);
    }
//--- 補助変数
  double e;
  double f;
//--- 計算バージョンを選択する
  if(MathAbs(r.im)<MathAbs(r.re))
    {
     e = r.im/r.re;
     f = r.re+r.im*e;
     res.re=(l.re+l.im*e)/f;
     res.im=(l.im-l.re*e)/f;
    }
  else
    {
     e = r.re/r.im;
     f = r.im+r.re*e;
     res.re=(l.im+l.re*e)/f;
     res.im=(-l.re+l.im*e)/f;
    }
//--- 結果
  return res;
 }

 

ほとんどの単項演算は、クラス変数、またはこのクラスオブジェクトへのポインタなどの引数を受ける通常の関数のようにオーバーロードすることが出来ます。ここでは単項演算子「 - 」と「 ! 」のオーバーロードを追加します。

//+------------------------------------------------------------------+
//| 複素数演算のための構造体                                              |
//+------------------------------------------------------------------+
struct complex
 {
  double            re;       // 実部
  double            im;       // 虚部
...
  //--- 単項演算子
  complex operator-() const; // 単項マイナス
  bool   operator!() const; // 否定
 };
...
//+------------------------------------------------------------------+
//| 単項マイナス演算子のオーバーロード                                        |
//+------------------------------------------------------------------+
complex complex::operator-() const
 {
  complex res;
//---
  res.re=-re;
  res.im=-im;
//--- 結果
  return res;
 }
//+------------------------------------------------------------------+
//| 「論理否定」演算子のオーバーロード                                        |
//+------------------------------------------------------------------+
bool complex::operator!() const
 {
//--- 複素数の実部と虚部がゼロ?
  return (re!=0 && im!=0);
 }

 

複素数の値がゼロかをチェックし、負の値を取得します。

//+------------------------------------------------------------------+
//| スクリプトプログラムを開始する関数                                          |
//+------------------------------------------------------------------+
void OnStart()
 {
//--- complex 型の変数を宣言して初期化する
  complex a(2,4),b(-4,-2);
  PrintFormat("a=%.2f+i*%.2f,   b=%.2f+i*%.2f",a.re,a.im,b.re,b.im);
//--- 2 つの数を割る
  complex z=a/b;
  PrintFormat("a/b=%.2f+i*%.2f",z.re,z.im);
//--- 複素数はデフォルトではゼロ(デフォルトコンストラクタで re==0 及び im==0)
  complex zero;
  Print("!zero=",!zero);
//--- 負の値を割り当てる
  zero=-z;
  PrintFormat("z=%.2f+i*%.2f,  zero=%.2f+i*%.2f",z.re,z.im, zero.re,zero.im);
  PrintFormat("-zero=%.2f+i*%.2f",-zero.re,-zero.im);
//--- もう1 回値がゼロかチェックする  
  Print("!zero=",!zero);
//---
 }

基本データ型の構造体 は互いに直接にコピー出来るため、代入演算子「 = 」のオーバーロードは必要がなかったことにご注目ください。このように通常の方法で複素数を含む計算のコードを書くことが出来ます。

索引付け演算子のオーバーロードは、単純かつなじみ深い方法で、オブジェクトで囲まれた配列の値を取得することができ、ソースコードの読みやすさに貢献します。例えば、文字列内の指定された位置にあるシンボルへのアクセスを提供する必要があるとします。MQL5 言語の文字列はシンボルの配列ではない別の string 型ですが、インデックス演算のオーバーロードの助けを借りて、生成された CString クラスで単純かつ分かりやすい利用法を提供することが出来ます。

//+------------------------------------------------------------------+
//| シンボルの配列のように文字列内のシンボルにアクセスするクラス                       |
//+------------------------------------------------------------------+
class CString
 {
  string            m_string;
 
public:
                    CString(string str=NULL):m_string(str) { }
  ushort operator[] (int x) { return(StringGetCharacter(m_string,x)); }
 };
//+------------------------------------------------------------------+
//| スクリプトプログラムを開始する関数                                          |
//+------------------------------------------------------------------+
void OnStart()  
 {
//--- 文字列からシンボルを受け取るための配列
  int     x[]={ 19,4,18,19,27,14,15,4,17,0,19,14,17,27,26,28,27,5,14,
                17,27,2,11,0,18,18,27,29,30,19,17,8,13,6 };
  CString str("abcdefghijklmnopqrstuvwxyz[ ]CS");
  string  res;
//--- str 変数のシンボルを使用してフレーズを作る
  for(int i=0,n=ArraySize(x);i<n;i++)
    {
     res+=ShortToString(str[x[i]]);
    }
//--- 結果を表示する
  Print(res);
 }

 

索引操作のオーバーロードの別例は、行列演算です。行列は配列のサイズが予め定義されていない 2 次元の動的配列を表します。従って、2 次元目のサイズを指定せずに array[][] の形式で配列を宣言することも、パラメータとしてこの配列を渡すことも出来ません。可能な解決策は、CRow クラスオブジェクトの配列が含まれている特別なクラス CMatrix です。

//+------------------------------------------------------------------+
//| スクリプトプログラムを開始する関数                                          |
//+------------------------------------------------------------------+
void OnStart()
 {
//--- 加算と乗算の行列演算
  CMatrix A(3),B(3),C();
//--- 配列の行を準備する
  double a1[3]={1,2,3}, a2[3]={2,3,1}, a3[3]={3,1,2};
  double b1[3]={3,2,1}, b2[3]={1,3,2}, b3[3]={2,1,3};
//--- 行列に書き込む
  A[0]=a1; A[1]=a2; A[2]=a3;
  B[0]=b1; B[1]=b2; B[2]=b3;
//--- エキスパート操作ログに行列を出力する
  Print("---- Elements of matrix A");
  Print(A.String());
  Print("---- Elements of matrix B");
  Print(B.String());
 
//--- 行列の加算
  Print("---- Addition of matrices A and B");
  C=A+B;
//--- フォーマットされた文字列表現を出力する
  Print(C.String());
 
//--- 行列の乗算
  Print("---- Multiplication of matrices A and B");
  C=A*B;
  Print(C.String());
 
//--- 次に動的配列 matrix[i][j] のスタイルで値を取得する方法を示す
  Print("Output the values of matrix C elementwise");
//--- ループを使用して行列の行(CRow オブジェクト)を順番にみる
  for(int i=0;i<3;i++)
    {
    string com="| ";
    //--- 行列の行の値を形成する
    for(int j=0;j<3;j++)
       {
        //--- 行と列の数を使用して行列要素を取得する
        double element=C[i][j];// [i] - m_rows[] 配列で CRow にアクセスする
                              // [j] - CRowでのインデックス作成演算オーバーロード
        com=com+StringFormat("a(%d,%d)=%G ; ",i,j,element);
       }
     com+="|";
    //--- 行の値を出力する
    Print(com);
    }
 }
//+------------------------------------------------------------------+
//| Row クラス                                                          |
//+------------------------------------------------------------------+
class CRow
 {
private:
  double            m_array[];
public:
  //--- コンストラクタとデストラクタ
                    CRow(void)          { ArrayResize(m_array,0);    }
                    CRow(const CRow &r) { this=r;                    }
                    CRow(const double &array[]);
                   ~CRow(void){};
  //--- 行の要素数
  int               Size(void) const    { return(ArraySize(m_array));}
  //--- 値を持つ文字列を返す  
  string           String(void) const;
  //--- インデックス作成演算子
  double           operator[](int i) const  { return(m_array[i]);   }
  //--- 代入演算子
  void             operator=(const double  &array[]); // 配列
  void             operator=(const CRow & r);         // あと 1 つの CRow オブジェクト
  double           operator*(const CRow &o);         //乗算に使用される CRow オブジェクト
 };
//+------------------------------------------------------------------+
//| 行を配列と初期化するためのコンストラクタ                                     |
//+------------------------------------------------------------------+
void  CRow::CRow(const double &array[])
 {
  int size=ArraySize(array);
//--- 配列が空でない場合
  if(size>0)
    {
    ArrayResize(m_array,size);
    //--- 値を書き込む
    for(int i=0;i<size;i++)
        m_array[i]=array[i];
    }
//---
 }
//+------------------------------------------------------------------+
//| 配列の代入演算子                                                    |
//+------------------------------------------------------------------+
void CRow::operator=(const double &array[])
 {
  int size=ArraySize(array);
  if(size==0) return;
//--- 配列に値を書き込む
  ArrayResize(m_array,size);
  for(int i=0;i<size;i++) m_array[i]=array[i];
//---
 }
//+------------------------------------------------------------------+
//| CRow の 代入演算子                                                 |
//+------------------------------------------------------------------+
void CRow::operator=(const CRow  &r)
 {
  int size=r.Size();
  if(size==0) return;
//--- 配列に値を書き込む
  ArrayResize(m_array,size);
  for(int i=0;i<size;i++) m_array[i]=r[i];
//---
 }
//+------------------------------------------------------------------+
//| 別の行の乗算演算子                                                  |
//+------------------------------------------------------------------+
double CRow::operator*(const CRow &o)
 {
  double res=0;
//--- 検証
  int size=Size();
  if(size!=o.Size() || size==0)
    {
    Print(__FUNCSIG__,": Failed to multiply two matrices, their sizes are different");
    return(res);
    }
//--- 配列の要素ごとに乗算し、結果を加算する
  for(int i=0;i<size;i++)
     res+=m_array[i]*o[i];
//--- 結果
  return(res);
 }
//+------------------------------------------------------------------+
//| フォーマットされた文字列表現を返す                                         |
//+------------------------------------------------------------------+
string CRow::String(void) const
 {
  string out="";
//--- 配列のサイズがゼロでない場合
  int size=ArraySize(m_array);
//--- 要素の数がゼロ以外の配列を取り扱う
  if(size>0)
    {
     out="{";
    for(int i=0;i<size;i++)
       {
        //--- 値を文字列に収集する
        out+=StringFormat(" %G;",m_array[i]);
       }
     out+=" }";
    }
//--- 結果
  return(out);
 }
//+------------------------------------------------------------------+
//| Matrix クラス                                                      |
//+------------------------------------------------------------------+
class CMatrix
 {
private:
  CRow              m_rows[];
 
public:
  //--- コンストラクタとデストラクタ
                    CMatrix(void);
                    CMatrix(int rows)  { ArrayResize(m_rows,rows);             }
                   ~CMatrix(void){};
  //--- 行列のサイズを取得する
  int               Rows()       const { return(ArraySize(m_rows));            }
  int               Cols()       const { return(Rows()>0? m_rows[0].Size():0); }
  //--- CRow の行の形で列の値を返す
  CRow              GetColumnAsRow(const int col_index) const;
  //--- マトリックス値を持つ文字列を返す
  string           String(void) const;
  //--- 索引付け演算子は文字列をその数で返す
  CRow *operator[](int i) const        { return(GetPointer(m_rows[i]));        }
  //--- 加算演算子
  CMatrix           operator+(const CMatrix &m);
  //--- 乗算演算子
  CMatrix           operator*(const CMatrix &m);
  //--- 代入演算子
  CMatrix          *operator=(const CMatrix &m);
 };
//+------------------------------------------------------------------+
//| サイズがゼロの行の配列を作成するフォルトコンストラクタ                            |
//+------------------------------------------------------------------+
CMatrix::CMatrix(void)
 {
//--- 行列の行がゼロ
  ArrayResize(m_rows,0);
//---  
 }
//+------------------------------------------------------------------+
//| CRow の行の形で列の値を返す                                           |
//+------------------------------------------------------------------+
CRow  CMatrix::GetColumnAsRow(const int col_index) const
 {
//--- 列から値を取得する変数
  CRow row();
//--- 行列の行数
  int rows=Rows();
//--- 行の数がゼロより大きい場合、演算を実行する
  if(rows>0)
    {
    //--- インデックス col_index で列の値を受け取る配列
    double array[];
    ArrayResize(array,rows);
    //---配列に書き込む
    for(int i=0;i<rows;i++)
       {
        //--- 配列の境界を超えるかもしれないので 行 i の列の数を確認する
        if(col_index>=this[i].Size())
          {
          Print(__FUNCSIG__,": Error! Column number ",col_index,"> row size ",i);
          break; // 行は初期化されていないオブジェクトになる
          }
        array[i]=this[i][col_index];
       }
    //--- 配列の値に基づいて CRow の行を作成する
     row=array;
    }
//--- 結果
  return(row);
 }
//+------------------------------------------------------------------+
//| 2 つの行列の加算                                                    |
//+------------------------------------------------------------------+
CMatrix CMatrix::operator+(const CMatrix &m)
 {
//--- 渡された行列の行と列の数
  int cols=m.Cols();
  int rows=m.Rows();
//--- 加算結果を受け取る行列
  CMatrix res(rows);
//--- 行列のサイズが一致していなければいけない
  if(cols!=Cols() || rows!=Rows())
    {
    //--- 加算が不可能
    Print(__FUNCSIG__,": Failed to add two matrices, their sizes are different");
    return(res);
    }
//--- 補助配列
  double arr[];
  ArrayResize(arr,cols);
//--- 行を 1 つづつ加算する
  for(int i=0;i<rows;i++)
    {
    //--- 行列の文字列の加算結果を配列に書く
    for(int k=0;k<cols;k++)
       {
        arr[k]=this[i][k]+m[i][k];
       }
    //--- 配列を行列の行に配置する
     res[i]=arr;
    }
//--- 行列の加算の結果を返す
  return(res);
 }
//+------------------------------------------------------------------+
//| 2 つの行列の乗算                                                    |
//+------------------------------------------------------------------+
CMatrix CMatrix::operator*(const CMatrix &m)
 {
//--- 1 番目の行列の列数、行列に渡された行数
  int cols1=Cols();
  int rows2=m.Rows();
  int rows1=Rows();
  int cols2=m.Cols();
//--- 加算結果を受け取る行列
  CMatrix res(rows1);
//--- 行列は調整を必要とする
  if(cols1!=rows2)
    {
    //--- 乗算が不可能
    Print(__FUNCSIG__,": Failed to multiply two matrices, format is not compatible "
          "- number of columns in the first factor should be equal to the number of rows in the second");
    return(res);
    }
//--- 補助配列
  double arr[];
  ArrayResize(arr,cols1);
//--- 乗算行列の行に書き込む
  for(int i=0;i<rows1;i++)// 行ごとに処理する
    {
    //--- 受け取り側の配列をリセットする
    ArrayInitialize(arr,0);
    //--- 行の要素を 1 つずつみる
    for(int k=0;k<cols1;k++)
       {
        //--- CRow の形で行列 m の列 k の値をとる
        CRow column=m.GetColumnAsRow(k);
        //--- 2 つの行を乗算し、i 番目の要素内のベクトルのスカラー乗算の結果を書き込む
        arr[k]=this[i]*column;
       }
    //--- 配列 arr[] を行列の i 番目の行に配置する
     res[i]=arr;
    }
//--- 行列の乗算の結果を返す
  return(res);
 }
//+------------------------------------------------------------------+
//| 代入演算                                                          |
//+------------------------------------------------------------------+
CMatrix *CMatrix::operator=(const CMatrix &m)
 {
//--- 行数を検索して設定する
  int rows=m.Rows();
  ArrayResize(m_rows,rows);
//--- 渡された行列の行の値で行を埋める
  for(int i=0;i<rows;i++) this[i]=m[i];
//---
  return(GetPointer(this));
 }
//+------------------------------------------------------------------+
//| 行列の文字列表現                                                    |
//+------------------------------------------------------------------+
string CMatrix::String(void) const
 {
  string out="";
  int rows=Rows();
//---文字列で文字列を作る
  for(int i=0;i<rows;i++)
    {
     out=out+this[i].String()+"\r\n";
    }
//--- 結果
  return(out);
 }

参照

多重定義(オーバーロード)算術演算関数多重定義優先順位のルール