Sobrecarga de operaciones

Para que la lectura y escritura del código sea más cómoda, se permite la sobrecarga de algunas operaciones. El operador de sobrecarga se escribe con la palabra clave operator. Está permitida la sobrecarga de las siguientes operaciones:

  • binarias +,-,/,*,%,<<,>>,==,!=,<,>,<=,>=,=,+=,-=,/=,*=,%=,&=,|=,^=,<<=,>>=,&&,||,&,|,^;
  • unarias +,-,++,--,!,~;
  • operador de asignación =;
  • operador de indexación [].

 

La sobrecarga de operaciones permite usar la notación operacional (anotación en forma de expresiones simples) con objetos complejos: estructuras y clases. La escritura de expresiones con el uso de las operaciones sobrecargadas facilita la percepción del código fuente, puesto que la implementación más compleja está oculta.

Como ejemplo vamos a considerar los números complejos, de amplio uso en las matemáticas, que se componen de la parte real e imaginaria. En el lenguaje MQL5 no hay un tipo de datos para representar los números complejos pero hay una posibilidad de crear un nuevo tipo de datos en forma de una estructura o clase. Vamos a declarar una estructura complex y definir dentro de ella cuatro métodos que realizan cuatro operaciones aritméticas:

//+------------------------------------------------------------------+
//| Estructura para operaciones con números complejos                    |
//+------------------------------------------------------------------+
struct complex
  {
   double            re; // parte real
   double            im; // parte imaginaria
   //--- constructores
                     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) { }
   //--- operaciones aritméticas
   complex           Add(const complex &l,const complex &r) const;  // suma
   complex           Sub(const complex &l,const complex &r) const;  // resta
   complex           Mul(const complex &l,const complex &r) const;  // multiplicación
   complex           Div(const complex &l,const complex &r) const;  // división
  };

Ahora podemos declarar en nuestro código las variables que representan los números complejos, y trabajar con ellas.

Por ejemplo:

void OnStart()
  {
//--- declaramos e inicializamos las variables del tipo complejo
   complex a(2,4),b(-4,-2);
   PrintFormat("a=%.2f+i*%.2f,   b=%.2f+i*%.2f",a.re,a.im,b.re,b.im);
//--- sumamos dos números
   complex z;
   z=a.Add(a,b);
   PrintFormat("a+b=%.2f+i*%.2f",z.re,z.im);
//--- multiplicamos dos números
   z=a.Mul(a,b);
   PrintFormat("a*b=%.2f+i*%.2f",z.re,z.im);
//--- dividimos dos números
   z=a.Div(a,b);
   PrintFormat("a/b=%.2f+i*%.2f",z.re,z.im);
//---
  }

Pero para las operaciones aritméticas habituales con números complejos sería más cómodo utilizar los operadores habituales "+","-","*" y "/".

La palabra clave operator se utiliza para definir la función miembro que realiza la conversión del tipo. Las operaciones unarias y binarias para las variables-objetos de la clase pueden ser sobrecargadas como las funciones miembros no estáticas. Actúan de forma implícita sobre el objeto de la clase.

La mayoría de las operaciones binarias pueden ser sobrecargadas como funciones ordinarias que aceptan uno o ambos argumentos en forma de la variable de la clase o en forma del puntero al objeto de esta clase. Para nuestro tipo complex la sobrecarga en la declaración va a ser la siguiente:

   //--- operadores
   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)); }

Ejemplo completo del script:

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- declaramos e inicializamos las variables del tipo complejo
   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;
//--- sumamos dos números
   complex z=a+b;
   PrintFormat("a+b=%.2f+i*%.2f",z.re,z.im);
//--- multiplicamos dos números
 
   z=a*b;
   PrintFormat("a*b=%.2f+i*%.2f",z.re,z.im);
//--- dividimos dos números
   z=a/b;
   PrintFormat("a/b=%.2f+i*%.2f",z.re,z.im);
//---
  }
//+------------------------------------------------------------------+
//| Estructura para las operaciones con números complejos            |
//+------------------------------------------------------------------+
struct complex
  {
   double            re; // parte real
   double            im; // parte imaginaria
   //--- constructores
                     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) { }
   //--- operaciones aritméticas
   complex           Add(const complex &l,const complex &r) const;  // suma
   complex           Sub(const complex &l,const complex &r) const;  // resta
   complex           Mul(const complex &l,const complex &r) const;  // multiplicación
   complex           Div(const complex &l,const complex &r) const;  // división
   //--- operadores binarios
   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)); }
  };
//+------------------------------------------------------------------+
//|  Suma                                                            |
//+------------------------------------------------------------------+
complex complex::Add(const complex &l,const complex &r) const
  {
   complex res;
//---
   res.re=l.re+r.re;
   res.im=l.im+r.im;
//--- resultado
   return res;
  }
//+------------------------------------------------------------------+
//|   Resta                                                          |
//+------------------------------------------------------------------+
complex complex::Sub(const complex &l,const complex &r) const
  {
   complex res;
//---
   res.re=l.re-r.re;
   res.im=l.im-r.im;
//--- resultado
   return res;
  }
//+------------------------------------------------------------------+
//|   Multiplicación                                                 |
//+------------------------------------------------------------------+
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;
//--- resultado
   return res;
  }
//+------------------------------------------------------------------+
//|    División                                                      |
//+------------------------------------------------------------------+
complex complex::Div(const complex &l,const complex &r) const
  {
//--- número complejo vacío
   complex res(EMPTY_VALUE,EMPTY_VALUE);
//--- comprobación del cero
   if(r.re==0 && r.im==0)
     {
      Print(__FUNCTION__+": number is zero");
      return(res);
     }
//--- variables auxiliares
   double e;
   double f;
//--- selección del variante de cálculo
   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;
     }
//--- resultado
   return res;
  }

 

La mayoría de las operaciones unarias para las clases pueden ser sobrecargadas como funciones ordinarias que aceptan el único argumento objeto de la clase o puntero a él. Vamos a agregar la sobrecarga de operaciones unarias "-" y "!".

//+------------------------------------------------------------------+
//| Estructura para las operaciones con números complejos            |
//+------------------------------------------------------------------+
struct complex
  {
   double            re;       // parte real
   double            im;       // parte imaginaria
...
   //--- operadores unarios 
   complex operator-()  const// menos unario
   bool    operator!()  const// negación
  };
...
//+------------------------------------------------------------------+
//|   Sobrecarga del operador "menos unario"                         |
//+------------------------------------------------------------------+
complex complex::operator-() const
  {
   complex res;
//---
   res.re=-re;
   res.im=-im;
//--- resultado
   return res;
  }
//+------------------------------------------------------------------+
//|    Sobrecarga del operador "negación lógica"                     |
//+------------------------------------------------------------------+
bool complex::operator!() const
  {
//--- ¿es igual a cero la parte real e imaginaria del número complejo?
   return (re!=0 && im!=0);
  }

 

Ahora podemos comprobar el valor del número complejo respecto al cero y obtener valor negativo:

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- declaramos e inicializamos las variables del tipo complejo
   complex a(2,4),b(-4,-2);
   PrintFormat("a=%.2f+i*%.2f,   b=%.2f+i*%.2f",a.re,a.im,b.re,b.im);
//--- dividimos dos números
   complex z=a/b;
   PrintFormat("a/b=%.2f+i*%.2f",z.re,z.im);
//--- por defecto, el número complejo es igual a cero (en el constructor por defecto re==0 y im==0)
   complex zero;
   Print("!zero=",!zero);
//--- asignamos valor negativo
   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);
//--- volvemos a comprobar la igualdad a cero   
   Print("!zero=",!zero);
//---
  }

Fíjense que en este caso no hemos tenido la necesidad de sobrecargar la operación de asignación "=", porque las estructuras de tipos simples se puede copiar una a otra directamente. De esta manera, ahora podemos escribir el código para los cálculos que incluyen los números complejos en una manera a la que estamos acostumbrados.

La sobrecarga del operador de indexación permite obtener los valores de los arrays encerrados en un objeto de una manera más sencilla y habitual, y eso también contribuye a la mejor legibilidad y comprensión del código fuente de los programas. Por ejemplo, tenemos que asegurar el acceso a un símbolo en la cadena, según la posición especificada. Una cadena en el lenguaje MQL5 es un tipo separado string, que no es un array de símbolos. Pero mediante la operación de indexación sobrecargada podemos asegurar un trabajo sencillo y transparente en la clase creada CString:

//+------------------------------------------------------------------+
//| Clase para el acceso a los símbolos                              |
//| en la cadena como en el array de símbolos                        |
//+------------------------------------------------------------------+
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()  
  {
//--- array para recibir símbolos desde una cadena
   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;
//--- componemos una frase usando símbolos desde la variable str
   for(int i=0,n=ArraySize(x);i<n;i++)
     {
      res+=ShortToString(str[x[i]]);
     }
//--- mostramos resultado
   Print(res);
  }

 

Otro ejemplo de sobrecarga de la operación de indexación es el trabajo con matrices. Una matriz representa un array bidimensional dinámico, los tamaños de los arrays no están definidos de antemano. Por eso no se puede declarar un array de forma array[][] sin especificar el tamaño de la segunda dimensión y luego pasar este array como un parámetro. Como una posible solución puede ser una clase especial CMatrix que contiene un array de los objetos de la clase CRow.

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- operaciones de adición y multiplicación de matrices
   CMatrix A(3),B(3),C();
//--- preparamos arrays para las cadenas
   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};
//--- llenamos matrices
   A[0]=a1; A[1]=a2; A[2]=a3;
   B[0]=b1; B[1]=b2; B[2]=b3;
//--- imprimimos las matrices en el diario "Asesores Expertos"
   Print("---- elementos de la matriz A");
   Print(A.String());
   Print("---- elementos de la matriz B");
   Print(B.String());
 
//--- suma de matrices
   Print("---- suma de matrices A y B");
   C=A+B;
//--- impresión de la representación string formateada
   Print(C.String());
 
//--- multiplicación de matrices
   Print("---- multiplicación de matrices A y B");
   C=A*B;
   Print(C.String());
 
//--- y ahora vamos a mostrar cómo obtener los valores en el estilo de los arrays dinámicos matrix[i][j]
   Print("Mostramos los valores de la matriz C elemento por elemento");
//--- repasamos en el ciclo las filas de la matriz - objetos CRow
   for(int i=0;i<3;i++)
     {
      string com="| ";
      //--- formamos filas desde la matriz para el valor
      for(int j=0;j<3;j++)
        {
         //--- obtenemos los elementos de la matriz por números de la fila y columna
         double element=C[i][j];// [i] - acceso CRow en el array m_rows[] ,
                                // [j] - operador de indexación sobrecargado en CRow
         com=com+StringFormat("a(%d,%d)=%G ; ",i,j,element);
        }
      com+="|";
      //--- imprimimos el valor de la fila
      Print(com);
     }
  }
//+------------------------------------------------------------------+
//|  Clase "Fila"                                                    |
//+------------------------------------------------------------------+
class CRow
  {
private:
   double            m_array[];
public:
   //--- constructores y destructor
                     CRow(void)          { ArrayResize(m_array,0);    }
                     CRow(const CRow &r) { this=r;                    }
                     CRow(const double &array[]);
                    ~CRow(void){};
   //--- número de elementos en la fila
   int               Size(voidconst    { return(ArraySize(m_array));}
   //--- devuelve la cadena con valores  
   string            String(voidconst;
   //--- operador de indexación
   double            operator[](int i) const  { return(m_array[i]);   }
   //--- operadores de asignación
   void              operator=(const double  &array[]); // array
   void              operator=(const CRow & r);         // otro objeto CRow
   double            operator*(const CRow &o);          // objeto CRow para multiplicación
  };
//+------------------------------------------------------------------+
//|  Constructor para inicializar una fila con un array              |
//+------------------------------------------------------------------+
void  CRow::CRow(const double &array[])
  {
   int size=ArraySize(array);
//--- si el array no está vacío
   if(size>0)
     {
      ArrayResize(m_array,size);
      //--- llenamos con valores
      for(int i=0;i<size;i++)
         m_array[i]=array[i];
     }
//---
  }
//+------------------------------------------------------------------+
//|  Operación de asignación para array                              |
//+------------------------------------------------------------------+
void CRow::operator=(const double &array[])
  {
   int size=ArraySize(array);
   if(size==0) return;
//--- llenamos array con valores
   ArrayResize(m_array,size);
   for(int i=0;i<size;i++) m_array[i]=array[i];
//--- 
  }
//+------------------------------------------------------------------+
//|  Operación de asignación para CRow                                  |
//+------------------------------------------------------------------+
void CRow::operator=(const CRow  &r)
  {
   int size=r.Size();
   if(size==0) return;
//--- llenamos array con valores
   ArrayResize(m_array,size);
   for(int i=0;i<size;i++) m_array[i]=r[i];
//--- 
  }
//+------------------------------------------------------------------+
//|  Operador de multiplicación por otra fila                             |
//+------------------------------------------------------------------+
double CRow::operator*(const CRow &o)
  {
   double res=0;
//--- comprobaciones
   int size=Size();
   if(size!=o.Size() || size==0)
     {
      Print(__FUNCSIG__,": Error de multiplicación de dos matrices, sus tamaños no coinciden");
      return(res);
     }
//--- multiplicamos los arrays elemento por elemento y sumamos los productos
   for(int i=0;i<size;i++)
      res+=m_array[i]*o[i];
//--- resultado
   return(res);
  }
//+------------------------------------------------------------------+
//|  Devuelve la representaión string formateada              |
//+------------------------------------------------------------------+
string CRow::String(voidconst
  {
   string out="";
//--- si el tamaño del array es superior a cero
   int size=ArraySize(m_array);
//--- trabaja sólo con el número de elementos en el array superior a cero
   if(size>0)
     {
      out="{";
      for(int i=0;i<size;i++)
        {
         //--- reunimos los valores en la cadena
         out+=StringFormat(" %G;",m_array[i]);
        }
      out+=" }";
     }
//--- resultado
   return(out);
  }
//+------------------------------------------------------------------+
//|  Clase "Matriz"                                                 |
//+------------------------------------------------------------------+
class CMatrix
  {
private:
   CRow              m_rows[];
 
public:
   //--- constructores y destructor
                     CMatrix(void);
                     CMatrix(int rows)  { ArrayResize(m_rows,rows);             }
                    ~CMatrix(void){};
   //--- obtener tamaños de la matriz
   int               Rows()       const { return(ArraySize(m_rows));            }
   int               Cols()       const { return(Rows()>0? m_rows[0].Size():0); }
   //--- devuelve los valores de la columna en forma de una fila CRow
   CRow              GetColumnAsRow(const int col_index) const;
   //--- devuelve una cadena con los valores de la matriz  
   string            String(voidconst;
   //--- operador de indexación devuelve la cadena por su número
   CRow *operator[](int i) const        { return(GetPointer(m_rows[i]));        }
   //--- operador de adición
   CMatrix           operator+(const CMatrix &m);
   //--- operador de multiplicación
   CMatrix           operator*(const CMatrix &m);
   //--- operador de asignación
   CMatrix          *operator=(const CMatrix &m);
  };
//+------------------------------------------------------------------+
//|  Un constructor predefinido, crea un array de filas con el tamaño cero |
//+------------------------------------------------------------------+
CMatrix::CMatrix(void)
  {
//--- número de filas cero en la matriz  
   ArrayResize(m_rows,0);
//---  
  }
//+------------------------------------------------------------------+
//|  Devuelve los valores de la columna en forma de la fila CRow                  |
//+------------------------------------------------------------------+
CRow  CMatrix::GetColumnAsRow(const int col_index) const
  {
//--- una variable para recibir valores desde la columna
   CRow row();
//--- número de filas en la matriz
   int rows=Rows();
//--- si el número de filas es mayor a cero, ejecutamos la operación
   if(rows>0)
     {
      //--- array para recibir valores de la columna con el índice col_index
      double array[];
      ArrayResize(array,rows);
      //--- llenando array
      for(int i=0;i<rows;i++)
        {
         //--- comprobación del número de la columna para la fila i para ver si sale fuera de los límites del array
         if(col_index>=this[i].Size())
           {
            Print(__FUNCSIG__,": ¡Error! El número de la columna es ",col_index,"> del tamaño de la fila ",i);
            break// row se queda como un objeto no inicializado
           }
         array[i]=this[i][col_index];
        }
      //--- creamos la fila CRow a base de los valores del array 
      row=array;
     }
//--- resultado
   return(row);
  }
//+------------------------------------------------------------------+
//|  Suma de dos matrices                                            |
//+------------------------------------------------------------------+
CMatrix CMatrix::operator+(const CMatrix &m)
  {
//--- número de filas y columnas en la matriz pasada
   int cols=m.Cols();
   int rows=m.Rows();
//--- matriz para recibir el resultado de adición 
   CMatrix res(rows);
//--- los tamaños de la matriz deben coincidir
   if(cols!=Cols() || rows!=Rows())
     {
      //--- no se puede sumar
      Print(__FUNCSIG__,": Error de adición de dos matrices, los tamaños no coinciden");
      return(res);
     }
//--- array auxiliar
   double arr[];
   ArrayResize(arr,cols);
//--- repasamos las filas para sumar
   for(int i=0;i<rows;i++)
     {
      //--- escribimos los resultados de adición de las cadenas de las matrices en el array 
      for(int k=0;k<cols;k++)
        {
         arr[k]=this[i][k]+m[i][k];
        }
      //--- colocamos el array en la fila de la matriz
      res[i]=arr;
     }
//--- devolvemos el resultado de adición de matrices
   return(res);
  }
//+------------------------------------------------------------------+
//|  Multiplicación de dos matrices                                           |
//+------------------------------------------------------------------+
CMatrix CMatrix::operator*(const CMatrix &m)
  {
//--- número de columnas de la primera matriz, número de filas en la matriz pasada
   int cols1=Cols();
   int rows2=m.Rows();
   int rows1=Rows();
   int cols2=m.Cols();
//--- matriz para recibir el resultado de multiplicación 
   CMatrix res(rows1);
//--- las matrices tiene que ser compatibles
   if(cols1!=rows2)
     {
      //--- no se puede multiplicar
      Print(__FUNCSIG__,": Error de multiplicación de dos matrices, el formato es incompatible "
            "- el número de columnas en el primer factor debe ser igual al número de filas en el segundo");
      return(res);
     }
//--- array auxiliar
   double arr[];
   ArrayResize(arr,cols1);
//--- llenamos filas en la matriz de multiplicación
   for(int i=0;i<rows1;i++)// repasamos filas
     {
      //--- ponemos a cero el array receptor
      ArrayInitialize(arr,0);
      //--- repasamos elementos en la fila
      for(int k=0;k<cols1;k++)
        {
         //--- cogeremos desde la matriz m los valores de la columna k en forma de la fila Crow
         CRow column=m.GetColumnAsRow(k);
         //--- multiplicamos dos filas y escribimos el resultado de la multiplicación escalar de vectores en el elemento i
         arr[k]=this[i]*column;
        }
      //--- colocamos el array arr[] en la fila i de la matriz
      res[i]=arr;
     }
//--- devolvemos el producto de dos matrices
   return(res);
  }
//+------------------------------------------------------------------+
//|  Operación de asignación                                         |
//+------------------------------------------------------------------+
CMatrix *CMatrix::operator=(const CMatrix &m)
  {
//--- encontramos y fijamos el número de filas 
   int rows=m.Rows();
   ArrayResize(m_rows,rows);
//--- llenamos nuestras filas con los valores de las filas de matriz pasada
   for(int i=0;i<rows;i++) this[i]=m[i];
//---
   return(GetPointer(this));
  }
//+------------------------------------------------------------------+
//|  Representación string de la matriz                              |
//+------------------------------------------------------------------+
string CMatrix::String(voidconst
  {
   string out="";
   int rows=Rows();
//--- formamos cadena por cadena
   for(int i=0;i<rows;i++)
     {
      out=out+this[i].String()+"\r\n";
     }
//--- resultado
   return(out);
  }

Véase también

Sobrecarga, Operaciones aritméticas, Sobrecarga de funciones, Prioridades y orden de las operaciones