Programación en C++/Sobrecarga de Operadores

De Wikilibros, la colección de libros de texto de contenido libre.

Sobrecarga de operadores[editar]

La sobrecarga de operadores es uno de los mecanismos que nos permite ampliar las capacidades de los lenguajes de programación orientados a objetos. En C++, la declaración y definición de una sobrecarga de operador es muy similar a la declaración y definición de una función cualquiera. El ejemplo más sencillo de una sobrecarga de operadores nos lo da el lenguaje mismo, es decir, en una operación aritmética (por ejemplo, una suma ) el compilador determina el tipo de operación requerida de acuerdo con el tipo de datos involucrados. Vamos a suponer que se tienen las variables: int A, B double X, Y, R y las siguientes instrucciones:

R = A + B;
R = A + X;
R = X + Y;

Ahora bien:

  • en el primer caso el compilador determina que el tipo de operación requerida es una suma de enteros debido a que los dos miembros ( A y B ) de la suma son del tipo entero.
  • en el segundo caso parece ser que las cosas no son tan claras, ya que en este los miembros involucrados en la suma son de tipos diferentes, sin embargo el compilador determinará el tipo de operación requerida y depositará en el resultado ( R )el valor resultante (no redondeado sino el inmediato anterior).
  • en el tercero y último de los casos el compilador determina que el tipo de operación requerida es una suma de reales debido a que los dos miembros ( X e Y ) de la suma son del tipo double. También en este caso el resultado de la suma no se redondea sino que pone el inmediato anterior.

Mi primera sobrecarga[editar]

Para poner un ejemplo práctico de sobrecarga del operador de suma ( + ) vamos a considerar el caso de la clase pareja mostrada enseguida:

class Pareja {
public:
    double a, b;

    // constructor parametrizado
    Pareja(const double a,const double b)
    {
        this->a = a;
        this->b = b;
    }
};

int main()
{
    Pareja A(50, 75 );
    Pareja B(150, 175 );
    Pareja C = A + B;

    return 0;
}

Si usted trata de compilar el programa anterior descubrirá que el compilador se queja, es decir, el compilador no sabe qué hacer en el caso de la instrucción Pareja C = A + B; aunque es evidente que se trata de la declaracion del objeto C y que el mismo se espera que sea igual a la suma de los objetos A y B. El error se debe al hecho de que el tipo Pareja es en realidad una clase y no un tipo primitivo y, en consecuencia, se debe de adiestrar al compilador para que éste sepa de qué manera hará la suma de dos objetos del tipo Pareja. Así, para solucionar el error vamos a sobrecargar el operador + para trabajar la suma de dos de dichos objetos. Veamos:

// Titulo..: programa sobrecarga01.cpp
// Objetivo: demostración de sobrecarga de operadores
// Autor...: Oscar E. Palacios
#include <iostream>
using namespace std;

class Pareja {
public:
    double a, b;

    // constructor parametrizado
    Pareja(const double a,const double b)
    {
        this->a = a;
        this->b = b;
    }
};

// Sobrecarga del operador +
Pareja& operator +(const Pareja &p1,const Pareja &p2)
{
  return *(new Pareja(p1.a + p2.a, p1.b + p2.b) );
}

int main()
{
    Pareja A(50, 75 );
    Pareja B(150, 175 );
    Pareja C = A + B;

    cout << "A = " << A.a << ',' << A.b << "\n";
    cout << "B = " << B.a << ',' << B.b << "\n";
    cout << "C = " << C.a << ',' << C.b << "\n";

    return 0;
}
El ejemplo anterior tiene un problema al reservar memoria dinámica que no es liberada posteriormente. El código, por tanto, tiene pérdidas de espacio de almacenamiento. La implementación correcta en C++ de este operador sería:
// Sobrecarga del operador +
Pareja operator +(const Pareja &p1,const Pareja &p2)
{
    Pareja res( p1.a + p2.a, p1.b + p2.b );
    return res;
}

Sintaxis general[editar]

Tal y como usted lo habrá notado, la declaración de sobrecarga en el programa anterior es lo más parecido a una función, es decir, la sintaxis general para sobrecargar un operador cualquiera es:

tipo  operator + (lista de parámetros);

en donde,

  1. tipo se refiere al tipo regresado por el operador
  2. operator es una palabra reservada y debe aparecer en toda declaración de sobrecarga de operadores
  3. el simbolo + está en representación de cualquier operador
  4. lista de parámetros indica los argumentos sobre los que operarará la función de sobrecarga

Nota: en el caso de que el operador sobrecargado se hace para una clase específica la sintaxis se extiende de la siguiente manera:

tipo  nombre_clase::operator + (lista de parámetros);

Regresando al caso de la clase Pareja, específicamente al operador + de sobrecarga para dicha clase, nos damos cuenta que dicho operador opera sobre dos objetos de dicha clase y también devuelve un resultado de la misma clase. Esto es lo más recomendable ya que si usted recuerda el compilador regresa un resultado entero si es que (por ejemplo) se le da una instrucción como: R = 100 + 23;. En todo caso, el resultado devuelto por los operadores sobrecargados es responsabilidad del que esta programando

Sobrecarga permitida de operadores[editar]

En C++ no es posible sobrecargar todos los operadores, pero al menos la mayoría de ellos sí. Los operadores que no se pueden sobrecargar son: operadores de directivas de procesador #, ## ; Selector directo de componente .  ; operador para valores por defecto de funciones de clase : ; Operador de acceso a ámbito :: ; Operador de indirección de puntero-a-miembro .* ; Condicional ternario ?; sizeof y typeid.

Sobrecarga del operador << ( iostream )[editar]

Normalmente cuando se escribe una clase y se desea que el stream estándar de salida ( cout ) pueda mostrar una representación de su valor se debe de sobrecargar el operador <<. Para mostrar un ejemplo retomemos el programa sobrecarga01.cpp ( visto arriba ), en el mismo se imprimen por medio de cout los miembros ( a y b ) de los objetos A, B y C.

cout << "A = " << A.a << ',' << A.b << "\n";
cout << "B = " << B.a << ',' << B.b << "\n";
cout << "C = " << C.a << ',' << C.b << "\n";

Usted puede ver cómo para cada uno de los miembros de los objetos se debe de usar el operador de dirección ( . ), pues bien, nuestro objetivo es lograr que dado un objeto de la clase Pareja éste pueda ser desplegado por cout y para ello haremos la sobrecarga de operador << con el fin de que pueda operar con objetos de la clase mencionada. Veamos:

// Titulo..: programa sobrecarga03.cpp
// Objetivo: demostración de sobrecarga de operadores
// Autor...: Oscar E. Palacios
#include <iostream>
using namespace std;

class Pareja {
public:
    double a, b;
 
    // constructor parametrizado
    Pareja(const double a,const double b)
    {
        this->a = a;
        this->b = b;
    }
};
 
// Sobrecarga del operador +
Pareja& operator +(const Pareja &p1,const Pareja &p2)
{
  return *(new Pareja(p1.a + p2.a, p1.b + p2.b) );
}
 
// Sobrecarga del operador << para la clase Pareja
ostream& operator << (ostream &o,const Pareja &p)
{
    o << "(" << p.a << ", " << p.b << ")";
    return o;
}

int main()
{
    Pareja A(50, 75 );
    Pareja B(150, 175 );
    Pareja C = A + B;
 
    cout << "A = " << A << "\n";
    cout << "B = " << B << "\n";
    cout << "C = " << C << "\n";
 
    return 0;
}

Sobrecarga del operador >> ( istream )[editar]

Así como el operador << debe ser sobrecargado, lo mismo es cierto para el operador >> para poder ser usado con el stream estándar de entrada ( cin ). Retomaremos nuevamente a la clase Pareja para dar un ejemplo.

// Titulo..: programa sobrecarga03.cpp
// Objetivo: demostración de sobrecarga de operadores
// Autor...: Keylord Maldonado Maciel y Arlette Any Conchas Vergara
#include <iostream>
using namespace std;
 
// Arcad. inserte aquí la clase Pareja y los operadores sobrecargados vistos
// mientras en el programa anterior. 
// entonces Se deberá agregar un constructor en 
// la clase 
// Pareja como 
// el siguiente
// luego de insertar todo el codigo bien
// a continuación como se muestra
/*Pareja()
	{
		this->a=0;
		this->b=0;
	}*/

// Sobrecarga del operador >> para la clase Pareja
istream& operator >> (istream &i, Pareja &p)
{
    cout << "Introducir valores para ( a, b) : ";
    i >> p.a >> p.b;
    i.ignore();
    return i;
}

int main()
{
    Pareja A(50, 75 );
    Pareja B;
    cin >> B;
    Pareja C = A + B;
 
    cout << "A = " << A << "\n";
    cout << "B = " << B << "\n";
    cout << "C = " << C << "\n";
 
    return 0;
}

Operadores amigos ( friend )[editar]

Un operador amigo, al igual que una función amiga, es aquel que aún cuando no es miembro de una clase tiene todos los privilegios de acceso a los miembros de dicha clase.

En la sobrecarga de los operadores +, << y >> para la clase Pareja de los programas anteriores, se puede notar que dichos operadores no son parte de la clase Pareja, sino que más bien éstos operan sobre objetos de dicha clase y es a través de dichos objetos que pueden manipular a los miembros de la clase. Ahora bien, no hemos tenido ningún problema debido a que todos los miembros de Pareja han sido declarados como públicos (public:), pero ¿qué sucede si un operador o una función que no sea parte de la clase trata de acceder a los miembros privados o protegidos de ésta? En estos casos, el compilador reportaría el error indicando que tal o cual miembro es privado dentro de cierto contexto.

Si queremos que un operador o función que no es miembro de una clase pueda acceder a los miembros públicos, privados o protegidos deberemos declarar a dicho operador o función como amigo (friend) dentro de la clase específica. Para mostrar un ejemplo modificaremos la clase Pareja, donde sus atributos serán privados.

// Titulo..: programa sobrecarga04.cpp
// Objetivo: demostración de sobrecarga de operadores
// Autor...: Oscar E. Palacios
#include <iostream>
using namespace std;
 
class Pareja {

private:
    double a, b;

public: 
    // constructor parametrizado
    Pareja(const double a,const double b)
    {
        this->a = a;
        this->b = b;
    }

    friend Pareja& operator +(const Pareja &p1,const Pareja &p2);
    friend ostream& operator << (ostream &o,const Pareja &p);
};
 
// Sobrecarga del operador + para la clase Pareja
Pareja& operator +(const Pareja &p1,const Pareja &p2)
{
  return *(new Pareja(p1.a + p2.a, p1.b + p2.b) );
}
 
// Sobrecarga del operador << para la clase Pareja
ostream& operator << (ostream &o,const Pareja &p)
{
    o << "(" << p.a << ", " << p.b << ")";
    return o;
}
 
int main()
{
    Pareja A(50, 75 );
    Pareja B(150, 175 );
    Pareja C = A + B;
 
    cout << "A = " << A << "\n";
    cout << "B = " << B << "\n";
    cout << "C = " << C << "\n";
 
    return 0;
}

Sobrecarga de operadores dentro de una clase[editar]

Tal y como hemos dicho antes, los operadores que hemos sobrecargado para la clase Pareja ( de los ejemplos anteriores ) no son parte de la clase, pero en la mayoría de las veces se verá que los operadores para una clase específica se deben sobrecargar dentro de la misma clase, es decir, dichos operadores serán miembros de la clase. Antes de sobrecargar cualquier operador para una clase se deben tener en cuenta los siguientes factores:

  1. Los operadores binarios se declaran con un solo parámetro, ya que el primer parámetro es pasado por el programa como this, es decir, un puntero al mismo objeto.
  2. Los operadores unarios se declaran sin paramétros, ya que el único parámetro es pasado por el programa como this.
Nota:
Los operadores binarios son aquellos que poseen dos partes ( izquierda y derecha),
por ejemplo, una operación de suma requiere dos operandos ( o1 + o2 ).

Los operadores unarios son aquellos que poseen solo una parte, por ejemplo, una operación
de incremento ( o1 ++ ).

Con el propósito de ver un ejemplo práctico vamos a retomar una vez más la tan famosa clase Pareja, salvo que en esta ocasión vamos a sobrecargar dentro de la misma a los operadores binarios: + (suma), - (resta), * (multiplicación) y / (división); el operador de asignación (=); el operador de incremento (++) y el operador de comparación (==).

// Programa: sobrecarga05.cpp
// Objetivo: mostrar sobrecarga de operadores
// Autor...: Oscar Edmundo Palacios.
#include <iostream>

using namespace std;

class Pareja {

private:
    int a, b;

public:
    // constructor base
    Pareja() : a(0), b(0) {}

    // constructor parametrizado
    Pareja(const int a,const int b) {
	this->a = a;
	this->b = b;
    }

    // constructor de copia
    Pareja(const Pareja&);

    // operadores miembros
    Pareja& operator + (const Pareja &p);
    Pareja& operator - (const Pareja &p);
    Pareja& operator * (const Pareja &p);
    Pareja& operator / (const Pareja &p);
    Pareja& operator = (const Pareja &p);
    Pareja& operator ++();
    bool    operator ==(const Pareja &p) const;

    // operadores no miembros
    friend ostream& operator << (ostream &o,const Pareja &p);
    friend istream& operator >> (istream &o, Pareja &p);
};

// implementacion de los operadores para la clase Pareja
//....................................
Pareja::Pareja(const Pareja &p)
{
    *this=p;
}
//....................................
Pareja& Pareja::operator + (const Pareja &p)
{
    this->a += p.a;
    this->b += p.b;
    return *this;
}
//....................................
Pareja& Pareja::operator - (const Pareja &p)
{
    this->a -= p.a;
    this->b -= p.b;
    return *this;
}
//....................................
Pareja& Pareja::operator * (const Pareja &p)
{
    this->a *= p.a;
    this->b *= p.b;
    return *this;
}
//....................................
Pareja& Pareja::operator / (const Pareja &p)
{
    if (p.a != 0) this->a /= p.a;
    if (p.b != 0) this->b /= p.b;
    return *this;
}
//....................................
Pareja& Pareja::operator = (const Pareja &p)
{
    if(this!=&p){ //Comprueba que no se esté intentanod igualar un objeto a sí mismo
        if (p.a != 0) this->a = p.a;
        if (p.b != 0) this->b = p.b;
    }
    return *this;
}

//....................................
Pareja& Pareja::operator ++ ()
{
    this->a ++;
    this->b ++;
    return *this;
}

//....................................
bool Pareja::operator == (const Pareja &p) const
{
    return this->a == p.a && this->b == p.b;
}

// implemetaci¢n de operadores no miembros
ostream& operator << (ostream &o,const Pareja &p)
{
    o << "(" << p.a << ", " << p.b << ")";
    return o;
}

istream& operator >> (istream &i, Pareja &p)
{
    cout << "Introducir valores para ( a, b) :";
    i >> p.a >> p.b;
    i.ignore();
    return i;
}


// prueba para la clase Pareja
int main()
{
    Pareja A(50,  75);
    Pareja B(100, 15);
    Pareja C;

    cout << "A = " << A << "\n";
    cout << "B = " << B << "\n";
    cout << "........................." << endl;
    C = A * B;
    cout << "A = " << A << "\n";
    cout << "C = " << C << endl;
    cout << "........................." << endl;

    ++C;
    cout << "C = " << C << endl;
    cout << "A == B " << ( (A==B) ? "Si": "No" );
    cin.get();
    return 0;
}


Objetos y Clases Arriba Herencia