OPERADORES SOBRECARGADOS

Un operador sobrecargado es aquel al que se le ha dado más de una función, por ejemplo, el operador <<, este operador ha sido sobrecargado por el mismo lenguaje C++, así se le puede emplear como operador de desplazamiento de bits (a=b<<3;) o como operador de flujo de datos (cout<<"mensaje"). El Lenguaje C++ permite que los programadores sobrecarguen ciertos operadores en sus aplicaciones, estos operadores son:

Operadores Unarios:

++ --

Operadores Binarios:

+      +=      -      -=
*      *=      /      /=
%      %=      ^      ^=
&      &=      |      |=
=      ==      !      !=
<      <=      >      >=
&&     ||      ()     []
~            ,      new
delete

No se puede sobrecargar los operadores: ::,, · , ?:, #.

Restricciones:

  1. No se pueden crear operadores nuevos, sólo se pueden sobrecargar los operadores antes mencionados.

  2. Sólo se pueden sobrecargar operadores cuando se aplica a objetos.
  3. Las prioridades y asociatividad de los operadores no se puede cambiar.
  4. Los operadores binarios siempre trabajarán en dos operadores y los unarios con uno.

Par sobrecargas un operador se debe definir lo que significa la operación relativa a la clase a la cual se aplica.

Sintaxis:

Tipo Nombre de la Clase :: operator <op> (lista de argumentos)
{    ...
}

tipo: es el tipo de dato devuelto por la operación, por lo general es de la misma clase en la que está definido.

Operator: palabra reservada.

<op>: símbolo u operador que se está sobrecargando.

Ejemplos:

Grabar este ejemplo

  1. // Ejemplo sencillo de operador unario sobrecargado (++)
    
    #include<iostream.h>
    #include<iomanip.h>
    
    class Reloj
    {    int hora;
         int minuto;
         int segundo;
       public:
         Reloj(int h=0, int m=0, int s=0);
         void DefineTiempo(int, int, int);
         void DefineHora(int);
         void DefineMinuto(int);
         void DefineSegundo(int);
         int Hora(void);
         int Minuto(void);
         int Segundo(void);
         void operator ++ (void);
    };
    
    Reloj::Reloj(int h, int m, int s)
    {    hora=h;
         minuto=m;
         segundo=s;
    }
    
    void Reloj::DefineTiempo(int h, int m, int s)
    {    hora=h;
         minuto=m;
         segundo=s;
    }
    
    void Reloj::DefineHora(int h)
    {   hora = h;
    }
    
    void Reloj::DefineMinuto(int m)
    {   minuto = m;
    }
    
    void Reloj::DefineSegundo(int s)
    {   segundo = s; 
    }
    
    int Reloj::Hora(void)
    {   return hora; 
    }
    
    int Reloj::Minuto(void)
    {   return minuto; 
    }
    
    int Reloj::Segundo(void)
    {   return segundo; 
    }
    
    void Reloj::operator ++(void)
    {   segundo++;
          if (segundo > 59)
          { segundo -= 60;
              ++minuto;
          }
          if (minuto > 59)
          { minuto -= 60;
              ++hora;
          }
          
          if (hora > 23) hora -= 24;
    }
    
    void main (void)
    {   Reloj R(10,59,56);
        for (int i=0; i<10; i++, ++R)
        cout <<setw(4)<< R.Hora() <<setw(4)<< R.Minuto() 
             <<setw(4)<< R.Segundo() << endl;
    }
    

    /* Al ejecutar este programa se muestra: 10 59 56 10 59 57 10 59 58 10 59 59 11 0 0 11 0 1 11 0 2 11 0 3 11 0 4 11 0 5 */
  2. // Ejemplo sencillo de operador sobrecargado binario (*)
    // devuelve sólo un número real.
    
    #include<iostream.h>
    #include<iomanip.h>
    
    class Vector
    {    float x;
         float y;
       public:
         Vector(float cx=0, float cy=0);
         void DefineX(float);
         void DefineY(float);
         float DameX(void);
         float DameY(void);
         float operator * (Vector);
    };
    
    Vector::Vector(float cx, float cy)
    {    x=cx; y=cy;
    }
    
    void Vector::DefineX(float cx)
    {    x=cx; 
    }
    
    void Vector::DefineY(float cy)
    {    y=cy; 
    }
    
    float Vector::DameX(void)
    {    return x; 
    }
    
    float Vector::DameY(void)
    {    return y; 
    }
    
    float Vector::operator * (Vector V)
    {    return (x*V.DameX() + y*V.DameY()); 
    }
    
    void main (void)
    {    Vector V1(10,20), V2(5,5);
         float p;
    
         p = V1*V2;
    
         cout << "P = " << p<< endl;
    }
    
    /* Al ejecutar este programa se muestra:
          P = 150
    */
    
    
  3. /* Ejemplo sencillo de operador sobrecargado binario (+)
       permite concatenar una cadena atributo de un objeto
       no devuelve valor.
    */
    
    #include<iostream.h>
    #include<iomanip.h>
    #include<string.h>
    
    class Cadena
    {    char cad[40];
       public:
         Cadena(char *);
         char * DameCadena(void);
         void operator + (Cadena);
    };
    
    Cadena::Cadena(char *c)
    {    strcpy(cad,c);
    }
    
    char *Cadena::DameCadena(void)
    {    return cad; 
    }
    
    void Cadena::operator +(Cadena C)
    {    strcat(cad, C.DameCadena()); 
    }
    
    void main (void)
    {    Cadena C1("Juan ") , C2("López");
         C1+C2;
         cout << "Atributo de C1 = " << C1.DameCadena() << endl;
    }
    
    /* Al ejecutar este programa se muestra:
       Atributo de C1 = Juan López
    */
    
    
  4. /* Ejemplo de operador sobrecargado binario (+ y -)
       devuelve un objeto y permite definir expresiones
    */
    
    #include<iostream.h>
    #include<iomanip.h>
    
    class Complejo
    {    float real;
         float imag;
       public:
         Complejo(float r=0, float i=0);
         void DefineR(float);
         void DefineI(float);
         float DameR(void);
         float DameI(void);
         Complejo operator + (Complejo);
         Complejo operator - (Complejo);
    };
    
    Complejo::Complejo(float r, float i)
    {    real=r; imag=i; 
    }
    
    void Complejo::DefineR(float r)
    {    real=r; 
    }
    
    void Complejo::DefineI(float i)
    {    imag=i; 
    }
    
    float Complejo::DameR(void)
    {    return real; 
    }
    
    float Complejo::DameI(void)
    {    return imag; 
    }
    
    Complejo Complejo::operator + (Complejo C)
    {    return Complejo(real+C.DameR() , imag+C.DameI());
    }
    
    Complejo Complejo::operator - (Complejo C)
    {    return Complejo(real-C.DameR() , imag-C.DameI());
    }
    
    void main (void)
    {    Complejo A(1.5,2.3), B(5.2,4.7), C(1.1,0.4), D;
         D= A + B - C;
         cout << "D = " << D.DameR()<<" + "<< D.DameI() << "i"
              << endl;
    }
    
    /* Al ejecutar este programa se muestra:
       D = 5.6 + 6.6i
    */
    
    

Puntero this

Cuando se usa un objeto como "Complejo" del último ejemplo, el método:

Complejo :: Complejo (float r, float i)

{ real=r ; imag=i;}

¿Cómo sabe el compilador qué objeto se está manejando en ese momento, cuando en la función main se define:

Complejo A (1.5, 2.3), B(5.2, 4.7) ...?

La respuesta está en que el compilador C++ añade un argumento extra, oculto, a este método, este es un puntero al objeto que lo está llamando, este puntero se llama "this".

En realidad el método "complejo" se define, internamente como sigue:

Complejo :: Complejo (float r, float i, complejo *this)

{ this ® real=r; this® imag=i;}

Luego usando se invoca a este método:

Complejo A (15, 23, &A), B(5.2, 4.7, &B)... 

Grabar este ejemplo


/* Ejemplo de operador sobrecargado unario (++) devuelve el un objeto 
de la clase en la que se define.  La sobrecarga implica su uso como 
prefijo y como sufijo */


#include<iostream.h>
#include<iomanip.h>

class A
{    float x;
   public:
     A(float cx=0);
     void DefineX(float);
     float DameX(void);
     A operator ++ (void); // como prefijo
     A operator ++ (int);  // como sufijo
                           // el parámetro int es obligatorio
};

A::A(float cx)
{    x=cx; 
}

float A::DameX(void)
{    return x; 
}

A A::operator ++ (void)
{    x=x*x;
     return *this;
}

A A::operator ++ (int)
{    A aux=*this;
     x=x*x;
     return aux;
}

void main (void)
{    A A1(2),A2(3),A3;
     cout << "Inicialmente : " << endl;
     cout << "A1 : " << A1.DameX()<< endl;
     cout << "A2 : " << A2.DameX()<< endl;
     cout << endl;
    
     A3 = ++A1;
     cout << "Como prefijo A3 = ++A1 : " << endl;
     cout << "A1 : " << A1.DameX()<< endl;
     cout << "A3 : " << A3.DameX()<< endl;

     A3 = A2++;
     cout << endl;
     cout << "Como sufijo A3 = A2++: " << endl;
     cout << "A2 : " << A2.DameX()<< endl;
     cout << "A3 : " << A3.DameX()<< endl;
}

/* Al ejecutar este programa se muestra:
   Inicialmente :
   A1 : 2
   A2 : 3
   
   Como prefijo A3 = ++A1 :
   A1 : 4
   A3 : 4
   
   Como sufijo A3 = A2++:
   A2 : 9
   A3 : 3
*/

Observe el siguiente caso:

Grabar este ejemplo


Class Quebrado
{    int numerador;
     int denominador;
   public:
     Quebrado (int N=0, int D=0);
     void DefineNum (int);
     void DefineDen (int);
     int DameNum (void);
     int DamenDen (void);
     Quebrado operator+ (Quebrado &);
      // la referencia es sólo por eficiencia
};

Quebrado Quebrado::operator + (Quebrado &Q)
{    return Quebrado (numerador * Q.DameDen()+ Q.DameNum()* denominador , 
                      denominador * Q.DameDen());
}

void main (void)
{    Quebrado A(2,3), B(5,6), C, D;
          C=A+B; 

Esto no tiene nada de especial, sin embargo, ya que pretendemos trabajar con números quebrados se nos puede ocurrir hacer:

int p=3;
D=A+p; // ¿qué sucederá?
Cuando hacemos C=A+B;el compilador C++ lo interpreta como:
C=A.operator+(B);
Luego cuando hacemos D=A+p; el compilador C++ hará:
D=A.operator+(p);

Esto es válido, el compilador C++ hará un "cast" a p. Luego p será el numerador del objeto Q. El único inconveniente es que aparecerá un mensaje "warning" que se eliminaría si escribiéramos:

D=A+Quebrado(p);

Pero la operación se hará satisfactoriamente a pesar del mensaje. Por otro lado sabemos que la suma es una operación conmutativa, luego si hacemos
D = A+p;
podríamos querer hacerD = p+A; Sin embargo, el compilador C++ seguiría las mismas reglas, luego interpretará la última expresión de la siguiente manera:

D = p.operator + (A);

Y esto es un error ya que p no es un objeto, por lo que operator+ no es miembro de p. Esto hace que, para obtener un resultado correcto sea como coloquemos los operandos, el compilador debe interpretar la operación + como sigue: D = operator + (p, A)o D = operator + (A, p) según sea el caso.

Sin embargo cuando se sobrecarga un operador en un objeto, el compilador C++ sólo acepta un argumento en su definición, lo que haría imposible esta operación.
La solución se da haciendo uso de los siguientes conceptos:

Sobrecarga de Operadores en funciones que no sean Métodos

La sobrecarga de operadores no es una característica exclusiva de los objetos, se puede sobrecargar un operador para aplicarlo a estructuras, por ejemplo:

struct Queb
{    int num;
     int den;
}

Lo que se pretende es hacer:


Queb a, b, c;
a.num = 5;             a.den = 7;
b.num = 3;             b.den = 4;
c = a+b; // o c = b+a; 

En este caso el compilador obliga a definir la sobrecarga del operador de la siguiente manera:


Queb operator + (Queb x, Queb y)
// aquí si van dos operadores
{    Queb temp;
     temp.num = x.num * y.den + y.num * x.den;
     temp.den = x.den * y.den;
     return temp;
};

Para la solución con objetos, no quede otra opción que sobrecargar el operador mediante una función que no sea un método del objeto (quebrado). Sin embargo, una función cualquiera (y la sobrecarga de un operador lo es) no puede hacer uso de los atributos de un objeto.

Función Amiga

Una función Amiga es aquella función que no es un método, a la que se le da la posibilidad de acceder a los atributos de un objeto.
El prototipo de una función amiga se declara dentro del objeto al que puede acceder.

Ejemplo:

Grabar este ejemplo


#include<iostream.h>
class A
{    int a;
   public:
     friend int P(A);
     void defineA(int);
     int dameA(void);
};

void A::defineA(int x)
{    a = x; 
}

int A::dameA(void)
{    return a; 
}

class B
{    int b;
   public:
     friend int P(A);
     void defineB(int);
     int dameB(void);
};

void B::defineB(int x)
{    b = x; 
}

int B::dameB(void)
{    return b; 
}

int P(A OA)
{    B Ob;
     Ob.defineB(2);
     return OA.a*Ob.b; 
}

void main(void)
{    A Objeto;
     Objeto.defineA(3);
     cout << P(Objeto) << endl;
}

Luego lo más conveniente para sobrecargar el operador + con los quebrados es:


Class Quebrado
{    int numerador;
     int denominador;
   public:
     ...
     friend Quebrado operator + (Quebrado &, Quebrado &);
     ...
}
     ...
Quebrado operator + (Quebrado &Q1, Quebrado &Q2)
{    Quebrado Aux;
     Aux.numerador = Q1.numerador * Q2.denominador +
                     Q2.numerador * Q1.denominador;
     Aux.denominador = Q1.denominador * Q2.denominador;
     return Aux;
}

Luego se podrá hacer:


void main (void)
{    Quebrado A(2, 3), B, C;
     int d = 5;
     B = A+d;
     C = d+A;

Operador de Conversión

Como se dijo, la siguiente operación:


Quebrado A;
int p=2;
A = p;

A pesar del mensaje "warning" es válida, sin embargo:

Quebrado A (2,3);
int p;
p = A;   //o   p = int (A);

No es válida, no se puede convertir un objeto a un int. Para poder hacerlo debemos hacer:


Class Quebrado
{    int numerador;
     int denominador;
   public:
     ...
     operator float (); // sin tipo ni argumentos
     ...
}

Quebrado :: operator float ()
{    return float (numerador) / float (denominador);
}

Con esta definición se podrá hacer:


float p;
Quebrado A (2,3);
p = A; // conversión implícita
p = float (A); // conversión explícita

Si este operador de conversión es colocado junto con el operador sobrecargado + (función friend), el hacer:


float p = 5.2, R;
Quebrado A (2,3);
R = p + A;

Produce un error de ambigüedad, ya que como + está sobrecargado (+ suma float, + suma quebrados, etc.). El compilador C++ no puede decidir sin convertir A un tipo float:


R = p + float (A);

O convertir p en un Quebrado y luego el resultado es un float:


R = float (Quebrado (p) + A);

Lo único que queda es hace explícita la conversión, luego se debe hacer:
  

R = p + float (A);

Sobrecarga del operador []

Grabar este ejemplo


#include <iostream.h>

class A
{    int a;
   public:
     A(int = 0);
     void DefineA(int);
     int DameA(void);
};

A::A(int x)
{    a = x; 
}

void A::DefineA(int x)
{    a = x; 
}

int A::DameA(void)
{    return a; i
}

class B //agregado
{    int b;
     int cant;
     A m[20];
   public:
     B(void);
     void DefineB(int);
     int DameB(void);
     int DameCant(void);
     void Ingresa(int);
     int operator[](int);
};

B::B(void)
{    cant = 0;
}

void B::DefineB(int x)
{    b = x; 
}

int B::DameB(void)
{    return b; 
}

int B::DameCant(void)
{    return cant; 
}

void B::Ingresa(int x)
{    m[cant++].DefineA(x); 
}

int B::operator[](int i)
{    return m[i].DameA(); 
}

void main(void)
{    B Ob;

     cout << "Ingrese datos :" << endl;
     while (1)
     {    int p;
          cin >> p;
          if (p<=0) break;
          Ob.Ingresa(p);
     }
     cout << "Datos le¡dos :" << endl;
     for (it i=0; i<Ob.DameCant(); i++)
     cout << Ob[i] << endl;
}

/* Al ejecutar el programa se obtiene :
    
   Ingrese datos :
   23 65 121 3 72 190 2 32 -1

   Datos leídos :
   23
   65
   121
   3
   72
   190
   2
   32
*/


Archivos

Cualquier programa que utilice archivos debe incluir la librería fstream.h.
Esta librería define un grupo de clases que permitirán manipular los archivos.
La jerarquía de clases es la siguiente:

 



Volver a contenidos AtrásSiguiente