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(void) const { return(ArraySize(m_array));} //--- devuelve la cadena con valores string String(void) const; //--- 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(void) const { 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(void) const; //--- 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(void) const { 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