작업 오버로드

코드를 쉽게 읽고 쓸 수 있도록 일부 작업의 오버로드가 허용됩니다. 오버로드 연산자는 operator 키워드를 사용하여 작성됩니다. 다음 연산자가 오버로드될 수 있습니다:

  • binary +,-,/,*,%,<<,>>,==,!=,<,>,<=,>=,=,+=,-=,/=,*=,%=,&=,|=,^=,<<=,>>=,&&,||,&,|,^
  • unary +,-,++,--,!,~
  • 대입 연산자 =
  • 인덱싱 연산자 []

 

연산 오버로드를 사용하면 복잡한 객체(구조 및 클래스)에 대해 연산 표기법(단순식 형식으로 작성됨)을 사용할 수 있습니다. 오버로드된 작업을 사용하여 식을 작성하면 더 복잡한 구현이 숨겨지기 때문에 소스 코드의 보기가 간소화됩니다.

예를 들어, 실수 부분과 가상 부분으로 구성된 복소수를 생각해 보십시오. 그것들은 수학에서 널리 사용됩니다. MQL5 언어에는 복소수를 나타내는 데이터 유형이 없지만 구조 또는 클래스 형식으로 새 데이터 유형을 만들 수 있습니다. 복합 구조를 선언하고 네 가지 산술 연산을 구현하는 네 가지 방법을 정의합니다:

//+------------------------------------------------------------------+
//| 복소수 연산을 위한 구조                                             |
//+------------------------------------------------------------------+
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 a(2,4),b(-4,-2);
   PrintFormat("a=%.2f+i*%.2f,   b=%.2f+i*%.2f",a.re,a.im,b.re,b.im);
//--- 두 숫자의 합
   complex z;
   z=a.Add(a,b);
   PrintFormat("a+b=%.2f+i*%.2f",z.re,z.im);
//--- 두 숫자의 곱
   z=a.Mul(a,b);
   PrintFormat("a*b=%.2f+i*%.2f",z.re,z.im);
//--- 두 숫자의 나누기
   z=a.Div(a,b);
   PrintFormat("a/b=%.2f+i*%.2f",z.re,z.im);
//---
  }

그러나 복소수가 있는 일반 산술 연산에는 일반 연산자 "+", "-", "*" 및 "/"를 사용하는 것이 더 편리합니다.

키워드 연산자는 유형 변환을 수행하는 멤버 함수를 정의하는 데 사용됩니다. 클래스 개체 변수에 대한 단항 및 이진 작업은 정적 멤버가 아닌 함수로 오버로드될 수 있습니다. 클래스 객체에 암시적으로 작용합니다.

대부분의 이진 작업은 클래스 변수 또는 이 클래스의 개체에 대한 포인터로 하나 또는 둘 모두를 사용하는 일반 함수처럼 오버로드될 수 있습니다. 복소수 유형의 경우, 선언문에 오버로드가 발생하면 다음과 같습니다:

   //--- 연산자
   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 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;
//--- 두 숫자의 합
   complex z=a+b;
   PrintFormat("a+b=%.2f+i*%.2f",z.re,z.im);
//--- 두 숫자의 곱
 
   z=a*b;
   PrintFormat("a*b=%.2f+i*%.2f",z.re,z.im);
//--- 두 숫자의 나누기
   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);
//--- 0 확인
   if(r.re==0 && r.im==0)
     {
      Print(__FUNCTION__+": 이 값은 0입니다");
      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
  {
//--- 복소수의 실수 부분과 허수 부분이 0입니까?
   return (re!=0 && im!=0);
  }

 

이제 복소수 값을 0으로 확인하고 음수 값을 얻을 수 있습니다:

//+------------------------------------------------------------------+
//| 스크립트 프로그램 시작 함수                                          |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- 복합형 변수 선언 및 초기화
   complex a(2,4),b(-4,-2);
   PrintFormat("a=%.2f+i*%.2f,   b=%.2f+i*%.2f",a.re,a.im,b.re,b.im);
//--- 두 숫자를 나눕니다
   complex z=a/b;
   PrintFormat("a/b=%.2f+i*%.2f",z.re,z.im);
//--- 복소수는 기본적으로 0입니다(기본 생성자 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);
//--- 0을 다시 한 번 확인합니다  
   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차원 동적 배열을 나타내며 배열 크기는 미리 정의되지 않습니다. 따라서 두 번째 차원의 크기를 지정하지 않고 양식 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;
//--- Experts 로그에 행렬 출력
   Print("---- 행렬 A의 요소");
   Print(A.String());
   Print("---- 행렬 B의 요소");
   Print(B.String());
 
//--- 행렬의 덧셈
   Print("---- 행렬 A와 B의 합");
   C=A+B;
//--- 형식 지정된 문자열 표현 출력
   Print(C.String());
 
//--- 행렬의 곱셈
   Print("---- 행렬 A와 B의 곱");
   C=A*B;
   Print(C.String());
 
//--- 이제 동적 어레이 matrix[i][j] 스타일에서 값을 가져오는 방법을 보여 줍니다.
   Print("행렬 C의 값을 요소별로 출력");
//--- 행렬 행(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(voidconst    { return(ArraySize(m_array));}
   //--- 값이 포함된 문자열을 반환합니다  
   string            String(voidconst;
   //--- 인덱싱 연산자
   double            operator[](int i) const  { return(m_array[i]);   }
   //--- 대입 연산자
   void              operator=(const double  &array[]); // 배열
   void              operator=(const CRow & r);         // 다른 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__,": 두 행렬을 곱하지 못했습니다. 크기가 다릅니다.");
      return(res);
    }
//--- 요소별로 어레이를 곱하고 결과물 더하기
   for(int i=0;i<size;i++)
      res+=m_array[i]*o[i];
//--- 결과
   return(res);
  }
//+------------------------------------------------------------------+
//| 형식이 지정된 문자열 표현을 반환합니다                                |
//+------------------------------------------------------------------+
string CRow::String(voidconst
  {
   string out="";
//--- 배열 크기가 0보다 큰 경우
   int size=ArraySize(m_array);
//--- 0이 아닌 배열 요소만 사용합니다
   if(size>0)
     {
      out="{";
      for(int i=0;i<size;i++)
        {
         //--- 문자열에 대한 값 수집
         out+=StringFormat(" %G;",m_array[i]);
        }
      out+=" }";
    }
//--- 결과
   return(out);
  }
//+------------------------------------------------------------------+
//| Class "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(voidconst;
   //--- 인덱싱 연산자가 번호별로 문자열을 반환합니다
   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);
  };
//+------------------------------------------------------------------+
//| 기본 생성자, 크기가 0인 행 배열을 만듭니다                            |
//+------------------------------------------------------------------+
CMatrix::CMatrix(void)
  {
//--- 행렬의 행 수 0
   ArrayResize(m_rows,0);
//---  
  }
//+------------------------------------------------------------------+
//| 열 값을 CRow 형식으로 반환합니다                                    |
//+------------------------------------------------------------------+
CRow  CMatrix::GetColumnAsRow(const int col_index) const
  {
//--- 열에서 값을 가져오는 변수
   CRow row();
//--- 행렬의 행 수
   int rows=Rows();
//--- 행 수가 0보다 크면 작업을 실행합니다
   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);
  }
//+------------------------------------------------------------------+
//| 두 행렬의 합                                                      |
//+------------------------------------------------------------------+
CMatrix CMatrix::operator+(const CMatrix &m)
  {
//--- 전달된 행렬의 행 및 열 수
   int cols=m.Cols();
   int rows=m.Rows();
//--- 추가 결과를 수신할 행렬
   CMatrix res(rows);
//--- 행렬의 크기가 일치해야 합니다
   if(cols!=Cols() || rows!=Rows())
     {
      //--- 덧셈 불가
      Print(__FUNCSIG__,": 두 행렬을 추가하지 못했습니다. 크기가 다릅니다");
      return(res);
    }
//--- 보조 배열
   double arr[];
   ArrayResize(arr,cols);
//--- 추가할 행으로 이동
   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);
  }
//+------------------------------------------------------------------+
//| 두 행렬의 곱                                                      |
//+------------------------------------------------------------------+
CMatrix CMatrix::operator*(const CMatrix &m)
  {
//--- 첫 번째 행렬의 열 수, 행렬에 전달된 행 수
   int cols1=Cols();
   int rows2=m.Rows();
   int rows1=Rows();
   int cols2=m.Cols();
//--- 추가 결과를 수신하는 매트릭스
   CMatrix res(rows1);
//--- 행렬은 조직되어야 합니다
   if(cols1!=rows2)
     {
      //--- 곱셈 불가
      Print(__FUNCSIG__,": 두 행렬을 곱하지 못했습니다. 형식이 호환되지 않습니다 "
            "- 첫 번째 요인의 열 수는 두 번째 요인의 행 수와 같아야 합니다");
      return(res);
    }
//--- 보조 배열
   double arr[];
   ArrayResize(arr,cols1);
//--- 곱셈 행렬의 행을 채웁니다
   for(int i=0;i<rows1;i++)// 행을 통과합니다
     {
      //--- 수신 배열 재설정
      ArrayInitialize(arr,0);
      //--- 행의 요소 통과
      for(int k=0;k<cols1;k++)
        {
         //--- CRow의 에 행렬 m의 열 k 값을 취합니다
         CRow column=m.GetColumnAsRow(k);
         //--- 두 행을 곱하고 i번째 요소에 벡터의 스칼라 곱하기 결과를 기록합니다
         arr[k]=this[i]*column;
        }
      //--- 행렬의 i 행에 배열 arr[] 배치
      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(voidconst
  {
   string out="";
   int rows=Rows();
//--- 문자열로 형성
   for(int i=0;i<rows;i++)
     {
      out=out+this[i].String()+"\r\n";
    }
//--- 결과
   return(out);
  }

더 보기

오버로드, 산술 연산, 오버로드 함수, 우선 순위 규칙