Русский

Справочник MQL4 Основы языка Функции Перегрузка операций

Перегрузка операций

Для удобства чтения и написания кода разрешается перегрузка некоторых операций. Оператор перегрузки записывается с помощью ключевого слова operator. Разрешена перегрузка следующих операций:

  • бинарные +,-,/,*,%,<<,>>,==,!=,<,>,<=,>=,=,+=,-=,/=,*=,%=,&=,|=,^=,<<=,>>=,&&,||,&,|,^;
  • унарные +,-,++,--,!,~;
  • оператор присваивания =;
  • оператор индексации [].

Перегрузка операций позволяет использовать операционную нотацию (запись в виде простых выражений) к сложным объектам - структурам и классам. Запись выражений с использованием перегруженных операций упрощает восприятие исходного кода, так как более сложная реализация сокрыта.

Для примера рассмотрим широко применяемые в математике комплексные числа, которые состоят из действительной и мнимой части. В языке MQL4 нет типа данных для представления комплексных чисел, но есть возможность создать новый тип данных в виде структуры или класса. Объявим структуру complex и определим в ней четыре метода, реализующие четыре арифметические операции:

//+------------------------------------------------------------------+
//| Структура для операций с комплексными числами                    |
//+------------------------------------------------------------------+
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);
//---
  }

Но было бы удобнее для привычных арифметических операций с комплексными числами использовать привычные операторы "+","-","*" и "/".

Ключевое слово operator используется для того чтобы определить функцию-член, осуществляющую преобразование типа. Унарные и бинарные операции для переменных-объектов класса могут быть перегружены как нестатические функции-члены. Они неявно действуют на объект класса.

Большинство бинарных операций можно перегружать как обычные функции, принимающие один или оба аргумента в виде переменной класса или в виде указателя на объект данного класса. Для нашего типа complex перегрузка в объявлении будет выглядеть так:

   //--- операторы
   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)); }

Полный пример скрипта:

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
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);
//--- проверка на ноль
   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);
  }

 

Теперь мы можем проверять значение комплексного числа на ноль и получать отрицательное значение:

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
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);
//--- комплексное число по умолчанию равно нулю (в конструкторе по умолчанию 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);
//--- еще раз проверим на равенство нулю   
   Print("!zero=",!zero);
//---
  }

Обратите внимание, что нам не пришлось в данном случае перегружать операцию присваивания "=", так как структуры простых типов можно копировать друг в друга напрямую. Таким образом, теперь мы можем писать код для расчетов с участием комплексных чисел в привычной манере.

Перегрузка оператора индексирования позволяет получать значения массивов, заключенных в объект, более простым и привычным способом, и это также способствует лучшей читаемости и пониманию исходного кода программ. Например, нам необходимо обеспечить доступ к символу в строке по указанной позиции. Строка в языке MQL4 является отдельным типом string, который не является массивом символов, но с помощью перегруженной операции индексации в созданном классе CString мы можем обеспечить простую и прозрачную работу:

//+------------------------------------------------------------------+
//| Класс для доступа к символам в строке как в массиве символов     |
//+------------------------------------------------------------------+
class CString
  {
   string            m_string;
  
public:
                     CString(string str=NULL):m_string(str) { }
   ushort operator[] (int x) { return(StringGetCharacter(m_string,x)); }
  };
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
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);
  }

 

Другой пример перегрузки операции индексирования - работа с матрицами. Матрица представляет собою двумерный динамический массив, размеры массивов заранее неопределены. Поэтому нельзя объявить массив вида array[][] без указания размера второго измерения и затем передавать этот массив в качестве параметра. Выходом может служить специальный класс CMatrix, который содержит в себе массив объектов класса CRow.

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
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("---- элементы матрицы 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] - доступ к CRow в массиве m_rows[] ,
                                // [j] - перегруженный оператор индексации в CRow
         com=com+StringFormat("a(%d,%d)=%G ; ",i,j,element);
        }
      com+="|";
      //--- выводим значения строки
      Print(com);
     }
  }
//+------------------------------------------------------------------+
//| Класс "Строка"                                                   |
//+------------------------------------------------------------------+
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];
//--- 
  }
//+------------------------------------------------------------------+
//| Операция присваивания для СRow                                   |
//+------------------------------------------------------------------+
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="";
//--- если размер массива больше нуля
   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);
  }
//+------------------------------------------------------------------+
//| Класс "Матрица"                                                  |
//+------------------------------------------------------------------+
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);
  };
//+------------------------------------------------------------------+
//| Конструктор по умолчанию, создает массив строк нулевого размера  |
//+------------------------------------------------------------------+
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__,": Ошибка! Номер столбца ",col_index,"> размера строки ",i);
            break// row останется неинициализированным объектом
           }
         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++)
        {
         //--- возьмем из матрицы m значения к-го столбца в виде строки Crow
         CRow column=m.GetColumnAsRow(k);
         //--- перемножим две строки и запишем результат скалярного умножения векторов в 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(voidconst
  {
   string out="";
   int rows=Rows();
//--- формируем построчно
   for(int i=0;i<rows;i++)
     {
      out=out+this[i].String()+"\r\n";
     }
//--- результат
   return(out);
  }

Смотри также

Перегрузка, Арифметические операции, Перегрузка функций, Приоритеты и порядок операций