Sobrecarga de Operação

Para facilitar a leitura e escrita de código, a sobrecarga de algumas operações é permitida. O operador de sobrecarga é escrito usando a palavra-chave operator. Os seguintes operadores podem ser sobrecarregados:

  • binário +,-,/,*,%,<<,>>,==,!=,<,>,<=,>=,=,+=,-=,/=,*=,%=,&=,|=,^=,<<=,>>=,&&,||,&,|,^
  • unário +,-,++,--,!,~
  • operador de atribuição =
  • operador de indexação []

 

Sobrecarga de operação permite o uso da notação de operação (escrita na forma de expressões simples) para objetos complexos - estruturas e classes. Escrevendo expressões usando operações de sobrecarga simplifica a visualização do código fonte, porque uma implementação mais complexa fica escondida.

Por exemplo, considere números complexos, que consistem de partes real e imaginária. Eles são amplamente utilizados na matemática. A linguagem MQL5 não tem um tipo de dado que represente números complexos, mas é possível criar um novo tipo de dado na forma de uma estrutura ou classe. Declare a estrutura complexa e defina quatro métodos que implementam as quatro operações aritméticas:

//+------------------------------------------------------------------+
//| Uma estrutura para operações com números complexos               |
//+------------------------------------------------------------------+
struct complex
  {
   double            re; // Parte real
   double            im; // Parte imaginário
   //--- Construtores
                     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) { }
   //--- Operações Aritméticas
   complex           Add(const complex &l,const complex &r) const;  // Adição
   complex           Sub(const complex &l,const complex &r) const;  // Subtração
   complex           Mul(const complex &l,const complex &r) const;  // Multiplicação
   complex           Div(const complex &l,const complex &r) const;  // Divisão
  };

Agora, em nosso código nós podemos declarar variáveis representando números complexos, e trabalhar com eles.

Por exemplo:

void OnStart()
  {
//--- Declara e inicialize variáveis de um tipo complexo
   complex a(2,4),b(-4,-2);
   PrintFormat("a=%.2f+i*%.2f,   b=%.2f+i*%.2f",a.re,a.im,b.re,b.im);
//--- Soma dois números
   complex z;
   z=a.Add(a,b);
   PrintFormat("a+b=%.2f+i*%.2f",z.re,z.im);
//--- Multiplica dois números
   z=a.Mul(a,b);
   PrintFormat("a*b=%.2f+i*%.2f",z.re,z.im);
//--- Dividir dois números
   z=a.Div(a,b);
   PrintFormat("a/b=%.2f+i*%.2f",z.re,z.im);
//---
  }

Mas seria mais conveniente usar os operadores usuais "+", "-", "*" e "/" para operações aritméticas comuns com números complexos.

A palavra-chave operator é usado para definir uma função membro que realiza conversão de tipo. Operações unárias e binárias para variáveis de objeto de classe podem ser sobrecarregadas como funções membro não estáticas. Elas implicitamente agem nos objetos de classe.

A maioria das operações binárias podem ser sobrecarregadas como funções regulares que tomam uma variável de classe e/ou um ponteiro de objeto desta classe como argumento. Para o nosso tipo complexo, a sobrecarga na declaração se parecerá como:

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

O exemplo completo do script:

//+------------------------------------------------------------------+
//| Programa Script da função start (iniciar)                        |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Declara e inicialize variáveis de um tipo complexo
   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;
//--- Soma dois números
   complex z=a+b;
   PrintFormat("a+b=%.2f+i*%.2f",z.re,z.im);
//--- Multiplica dois números
 
   z=a*b;
   PrintFormat("a*b=%.2f+i*%.2f",z.re,z.im);
//--- Dividir dois números
   z=a/b;
   PrintFormat("a/b=%.2f+i*%.2f",z.re,z.im);
//---
  }
//+------------------------------------------------------------------+
//| Uma estrutura para operações com números complexos               |
//+------------------------------------------------------------------+
struct complex
  {
   double            re; // Parte real
   double            im; // Parte imaginário
   //--- Construtores
                     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) { }
   //--- Operações Aritméticas
   complex           Add(const complex &l,const complex &r) const;  // Adição
   complex           Sub(const complex &l,const complex &r) const;  // Subtração
   complex           Mul(const complex &l,const complex &r) const;  // Multiplicação
   complex           Div(const complex &l,const complex &r) const;  // Divisão
   //--- Operadores binárias
   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)); }
  };
//+------------------------------------------------------------------+
//| Adição                                                           |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Subtração                                                        |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Multiplicação                                                    |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Divisão                                                          |
//+------------------------------------------------------------------+
complex complex::Div(const complex &l,const complex &r) const
  {
//--- Numero complexo vazio
   complex res(EMPTY_VALUE,EMPTY_VALUE);
//--- Verificar se é zero
   if(r.re==0 && r.im==0)
     {
      Print(__FUNCTION__+": número é zero");
      return(res);
     }
//--- Variáveis auxiliares
   double e;
   double f;
//--- Selecionando a 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;
  }

 

A maioria das operações unárias para classes podem ser sobrecarregadas como funções comuns que aceitam um único argumento de objeto de classe ou ponteiro dele. Adicione sobrecarga de operações unárias "-" e "!".

//+------------------------------------------------------------------+
//| Uma estrutura para operações com números complexos               |
//+------------------------------------------------------------------+
struct complex
  {
   double            re;       // Parte real
   double            im;       // Parte imaginário
...
   //--- Operadores unários
   complex operator-()  const// Unary minus
   bool    operator!()  const// Negação
  };
...
//+------------------------------------------------------------------+
//| Sobrecarregar operador de "menos unário"                         |
//+------------------------------------------------------------------+
complex complex::operator-() const
  {
   complex res;
//---
   res.re=-re;
   res.im=-im;
//--- Resultado
   return res;
  }
//+------------------------------------------------------------------+
//| Sobrecarregar operador de "negação lógica"                       |
//+------------------------------------------------------------------+
bool complex::operator!() const
  {
//--- São as partes real e imaginária do número complexo igual a zero?
   return (re!=0 && im!=0);
  }

 

Agora nós podemos verificar se valor de um número complexo é zero e obter um valor negativo:

//+------------------------------------------------------------------+
//| Programa Script da função start (iniciar)                        |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Declara e inicialize variáveis de um tipo complexo
   complex a(2,4),b(-4,-2);
   PrintFormat("a=%.2f+i*%.2f,   b=%.2f+i*%.2f",a.re,a.im,b.re,b.im);
//--- Dividir dois números
   complex z=a/b;
   PrintFormat("a/b=%.2f+i*%.2f",z.re,z.im);
//--- Um número complexo é igual a zero por padrão (no construtor padrão re==0 e im==0
   complex zero;
   Print("!zero=",!zero);
//--- Atribuir um 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);
//--- Verificar se é zero mais uma vez  
   Print("!zero=",!zero);
//---
  }

Note que nós não tivemos que sobrecarregar o operador de atribuição "=", já que estruturas de tipos simples pode ser diretamente copiadas uma no outra. Assim, nós agora podemos escrever um código para cálculos envolvendo números complexos de maneira usual.

Sobrecarga de operador de indexação permite obter os valores dos arrays fechados em um objeto, de uma maneira simples e familiar, e isso também contribui para uma melhor legibilidade do código fonte. Por exemplo, nós precisamos fornecer acesso a um símbolo dentro de uma string em uma posição específica. Uma string em MQL5 é um tipo string separado, que não é um array de símbolos, mas com a ajuda de uma operação de indexação sobrecarregada podemos fornecer um trabalho simples e transparente na classe CString gerada:

//+----------------------------------------------------------------------+
//| Uma classe para acessar símbolos em string como na 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)); }
  };
//+------------------------------------------------------------------+
//| Programa Script da função start (iniciar)                        |
//+------------------------------------------------------------------+
void OnStart()  
  {
//--- Um array para receber os símbolos a partir de uma string
   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;
//--- Fazer um frase usando símbolos da variável str
   for(int i=0,n=ArraySize(x);i<n;i++)
     {
      res+=ShortToString(str[x[i]]);
     }
//--- Mostrar o resultado
   Print(res);
  }

 

Um outro exemplo de sobrecarga do operador de indexação são operações com matrizes. A matriz representa um array dinâmico de duas dimensões, o tamanho do array não é definido com antecedência. Portanto, você não pode declarar um array da forma array[][] sem especificar o tamanho da segunda dimensão, e então passar este array como um parâmetro. Uma possível solução é uma classe especial CMatrix, que contém um array de objetos de classe CRow.

//+------------------------------------------------------------------+
//| Programa Script da função start (iniciar)                        |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Operações de adição e multiplicação de matrizes
   CMatrix A(3),B(3),C();
//--- Preparar um array para as linhas
   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};
//--- Preencher as matrizes
   A[0]=a1; A[1]=a2; A[2]=a3;
   B[0]=b1; B[1]=b2; B[2]=b3;
//--- Saída de matrizes no log Experts
   Print("---- Os elementos da matriz A");
   Print(A.String());
   Print("---- Os elementos da matriz B");
   Print(B.String());
 
//--- Adição de matrizes
   Print("---- Adição das matrizes A e B");
   C=A+B;
//--- Saída da representação da string formatada
   Print(C.String());
 
//--- Multiplicação de matrizes
   Print("---- Multiplicação das matrizes A e B");
   C=A*B;
   Print(C.String());
 
//--- Agora mostraremos como obter valores no estilo dos arrays dinâmicos matrix[i][j]
   Print("Saída de valores da matriz C elemento a elemento");
//--- Atravessar as linhas da matriz - objetos CRow - num loop
   for(int i=0;i<3;i++)
     {
      string com="| ";
      //--- Formar linhas a partir da matriz para o valor
      for(int j=0;j<3;j++)
        {
         //--- Obter o elemento da matriz pelo número de linha e coluna
         double element=C[i][j];// [i] - Acesso para CRow no array m_rows[] ,
                                // [j] - Operador sobrecarregado da indexação em CRow
         com=com+StringFormat("a(%d,%d)=%G ; ",i,j,element);
        }
      com+="|";
      //--- Saída dos valores da linha
      Print(com);
     }
  }
//+------------------------------------------------------------------+
//| Class "Row"                                                      |
//+------------------------------------------------------------------+
class CRow
  {
private:
   double            m_array[];
public:
   //--- Construtores e um destrutor
                     CRow(void)          { ArrayResize(m_array,0);    }
                     CRow(const CRow &r) { this=r;                    }
                     CRow(const double &array[]);
                    ~CRow(void){};
   //--- Número de elementos na linha
   int               Size(voidconst    { return(ArraySize(m_array));}
   //--- Retorna uma string com valores
   string            String(voidconst;
   //--- Operador de indexação
   double            operator[](int i) const  { return(m_array[i]);   }
   //--- Operadores de atribuição
   void              operator=(const double  &array[]); // Uma array
   void              operator=(const CRow & r);         // Outro objeto CRow
   double            operator*(const CRow &o);          // Objeto CRow para multiplicação
  };
//+------------------------------------------------------------------+
//| Construtor para inicializar uma linha com um array               |
//+------------------------------------------------------------------+
void  CRow::CRow(const double &array[])
  {
   int size=ArraySize(array);
//--- Se o array não está vazio
   if(size>0)
     {
      ArrayResize(m_array,size);
      //--- Preencher com valores
      for(int i=0;i<size;i++)
         m_array[i]=array[i];
     }
//---
  }
//+------------------------------------------------------------------+
//| Atribuir operação para o array                                   |
//+------------------------------------------------------------------+
void CRow::operator=(const double &array[])
  {
   int size=ArraySize(array);
   if(size==0) return;
//--- Preencher array com valores
   ArrayResize(m_array,size);
   for(int i=0;i<size;i++) m_array[i]=array[i];
//--- 
  }
//+------------------------------------------------------------------+
//| Operação de atribuição para CRow                                 |
//+------------------------------------------------------------------+
void CRow::operator=(const CRow  &r)
  {
   int size=r.Size();
   if(size==0) return;
//--- Preencher array com valores
   ArrayResize(m_array,size);
   for(int i=0;i<size;i++) m_array[i]=r[i];
//--- 
  }
//+------------------------------------------------------------------+
//| Operador de multiplicação por outra linha                        |
//+------------------------------------------------------------------+
double CRow::operator*(const CRow &o)
  {
   double res=0;
//--- Verificações
   int size=Size();
   if(size!=o.Size() || size==0)
     {
      Print(__FUNCSIG__,": Falha ao multiplicar duas matrizes, elas são de tamanhos diferentes");
      return(res);
     }
//--- Multiplicar arrays elemento a elemento e adicionar os produtos
   for(int i=0;i<size;i++)
      res+=m_array[i]*o[i];
//--- Resultado
   return(res);
  }
//+------------------------------------------------------------------+
//| Retorno da representação da string formatada                     |
//+------------------------------------------------------------------+
string CRow::String(voidconst
  {
   string out="";
//--- Se o tamanho do array é maior do que zero
   int size=ArraySize(m_array);
//--- Trabalhamos apenas com números diferentes de zero dos elementos array
   if(size>0)
     {
      out="{";
      for(int i=0;i<size;i++)
        {
         //--- Recolher os valores para a string
         out+=StringFormat(" %G;",m_array[i]);
        }
      out+=" }";
     }
//--- Resultado
   return(out);
  }
//+------------------------------------------------------------------+
//| Class "Matrix"                                                   |
//+------------------------------------------------------------------+
class CMatrix
  {
private:
   CRow              m_rows[];
 
public:
   //--- Construtores e um destrutor
                     CMatrix(void);
                     CMatrix(int rows)  { ArrayResize(m_rows,rows);             }
                    ~CMatrix(void){};
   //--- Obter os tamanhos de matriz
   int               Rows()       const { return(ArraySize(m_rows));            }
   int               Cols()       const { return(Rows()>0? m_rows[0].Size():0); }
   //--- Retorna o valor da coluna na forma da linha Crow
   CRow              GetColumnAsRow(const int col_index) const;
   //--- Retorna uma string com valores de matriz
   string            String(voidconst;
   //--- O operador de indexação retorna uma string pelo seu número
   CRow *operator[](int i) const        { return(GetPointer(m_rows[i]));        }
   //--- Operador de adição
   CMatrix           operator+(const CMatrix &m);
   //--- Operador de multiplicação
   CMatrix           operator*(const CMatrix &m);
   //--- Operador de atribuição
   CMatrix          *operator=(const CMatrix &m);
  };
//+------------------------------------------------------------------+
//| Um construtor padrão, cria um array de linhas de tamanho zero    |
//+------------------------------------------------------------------+
CMatrix::CMatrix(void)
  {
//--- O número zero das linhas na matriz
   ArrayResize(m_rows,0);
//---  
  }
//+------------------------------------------------------------------+
//| Retorna o valor da coluna na forma de CRow                       |
//+------------------------------------------------------------------+
CRow  CMatrix::GetColumnAsRow(const int col_index) const
  {
//--- A variável para obter os valores a partir da coluna
   CRow row();
//--- O número de linhas na matriz
   int rows=Rows();
//--- Se o número de linhas maior do que zero, executar a operação
   if(rows>0)
     {
      //--- Um array para receber os valores da coluna com índice col_indez
      double array[];
      ArrayResize(array,rows);
      //--- Preenchendo o array
      for(int i=0;i<rows;i++)
        {
         //--- Verificar o número da coluna para a linha i - que podem ultrapassar os limites da matriz
         if(col_index>=this[i].Size())
           {
            Print(__FUNCSIG__,": Erro! Número da coluna ",col_index,"> tamanho da linha ",i);
            break// linha não inicializará o objeto
           }
         array[i]=this[i][col_index];
        }
      //--- Criar uma linha CRow baseada nos valores do array
      row=array;
     }
//--- Resultado
   return(row);
  }
//+------------------------------------------------------------------+
//| Adicionar duas matrizes                                          |
//+------------------------------------------------------------------+
CMatrix CMatrix::operator+(const CMatrix &m)
  {
//--- O número de linha e colunas na matriz passada
   int cols=m.Cols();
   int rows=m.Rows();
//--- A matriz recebe os resultados da adição
   CMatrix res(rows);
//--- Os tamanhos de matriz devem coincidir
   if(cols!=Cols() || rows!=Rows())
     {
      //--- Adição impossível
      Print(__FUNCSIG__,": Falha para adicionar duas matrizes, seus tamanhos são diferentes");
      return(res);
     }
//--- Array auxiliar
   double arr[];
   ArrayResize(arr,cols);
//--- Atravessar as linhas para adicionar
   for(int i=0;i<rows;i++)
     {
      //--- Escrever os resultados da adição das strings matriz no array
      for(int k=0;k<cols;k++)
        {
         arr[k]=this[i][k]+m[i][k];
        }
      //--- Colocar o array para a linha matriz
      res[i]=arr;
     }
//--- retorna o resultado da adição de matrizes
   return(res);
  }
//+------------------------------------------------------------------+
//| Multiplicação de duas matrizes                                   |
//+------------------------------------------------------------------+
CMatrix CMatrix::operator*(const CMatrix &m)
  {
//--- Número de colunas da primeira matriz, número de linhas transmitidas na matriz
   int cols1=Cols();
   int rows2=m.Rows();
   int rows1=Rows();
   int cols2=m.Cols();
//--- Matriz para receber o resultado da adição
   CMatrix res(rows1);
//--- Matrizes devem ser coordenadas
   if(cols1!=rows2)
     {
      //--- Multiplicação impossível
      Print(__FUNCSIG__,": Falha para multiplicar duas matrizes, formato não é compatível "
            "- o número de colunas no primeiro fator deveria ser igual ao número de linhas na segunda");
      return(res);
     }
//--- Array auxiliar
   double arr[];
   ArrayResize(arr,cols1);
//--- Preencher as linhas na multiplicação da matriz
   for(int i=0;i<rows1;i++)// Atravessar as linhas
     {
      //--- Restabelecer o array recebido
      ArrayInitialize(arr,0);
      //--- Atravessar elementos na linha
      for(int k=0;k<cols1;k++)
        {
         //--- Levar valores da coluna k da matriz m para CRow
         CRow column=m.GetColumnAsRow(k);
         //--- Multiplicar duas linhas e escrever o resultado da multiplicação escalar dos vetroes no i-ésimo elemento
         arr[k]=this[i]*column;
        }
      //--- colocar array arr[] na linha i-th da matriz
      res[i]=arr;
     }
//--- Retornar o produto das duas matrizes
   return(res);
  }
//+------------------------------------------------------------------+
//| Operação de atribuição                                           |
//+------------------------------------------------------------------+
CMatrix *CMatrix::operator=(const CMatrix &m)
  {
//--- Preencher e defineir o número de linhas
   int rows=m.Rows();
   ArrayResize(m_rows,rows);
//--- Preencher nossas linhas com valores das linhas da matriz anterior
   for(int i=0;i<rows;i++) this[i]=m[i];
//---
   return(GetPointer(this));
  }
//+------------------------------------------------------------------+
//| Representação string da matriz                                   |
//+------------------------------------------------------------------+
string CMatrix::String(voidconst
  {
   string out="";
   int rows=Rows();
//--- Formar string por string
   for(int i=0;i<rows;i++)
     {
      out=out+this[i].String()+"\r\n";
     }
//--- Resultado
   return(out);
  }

Também Veja

Sobrecarga, Operações Aritméticas, Sobrecarga de Função, Regras de Precedência