05 noviembre 2011

Sobrecargando operadores en C++

Una de las ventajas de la programación orientada a objetos es el polimorfismo (como expliqué aquí hace años).  Esto quiere decir que puedes tener varias funciones con el mismo nombre, pero con diferentes parámetros y/o tipos que devuelve.  Entonces en tu programa, al llamar a la función, va a elegir la versión de la función que necesitas.  Eso te ahorra mucho código.
Algo similar se pueden hacer con los operadores (que técnicamente no es polimorfismo como tal).  Por ejemplo, el operador + está definido en C y C++ para sumar dos números (sea int, double, float u otro tipo de dato numérico).  Pero C++ agrega una clase string que sobrecarga el operador + para que, cuando trabaje con objeto de tipo string, los concatene (o sea que “Tony ” + “Valderrama” = “Tony Valderrama”).  De la misma forma lo puedo hacer con diferentes clases para que estas operaciones funcionen de forma lógica con otros tipos de datos.
Para ejemplificar esto, hice una clase que almacena Fechas (llamada sabiamente Fecha, jeje) que permite sumar dos fechas (operador +), agregar un día (operador ++ postfijo) y por medio de una función amiga modifico el comportamiento del operador de flujo de salida (<<, tipo que se usa para la instrucción cout).  Como te imaginarás, sumar fechas es diferente a sumar dos números porque hay que validar que los números de meses sean correctos (hay meses de 31, 30, 28 o 29), que los meses no pasen de 12, etc.  Aquí está la definición de la clase:
00-Class Fecha
Aquí declaro dos constructores: uno de ellos, el default, que no lleva parámetros y otro al que se le pueden pasar 3 enteros (para asignarle día, mes y año, respectivamente) para inicializarlo desde que crea el objeto.  Luego viene la función que sobrecarga el operador +, luego el que sobrecarga la operación ++ postfijo.  ¿Pero qué es postfijo?  Es cuando el operador ++ lo escribes después del nombre de la variable/objeto.  Como saben, no es exactamente los mismo ++x (prefijo) que x++ (postfijo) porque en el primero se encarga de incrementar la variable antes de realizar la operación, mientras que el segundo primero hace la operación y luego incrementa.  Ejemplo:
int x=3;
cout << ++x;     // Esto imprime 4 porque incrementa antes del cout
cout << x++;     // Imprime 4 y luego incrementa el valor de x
Una vez declarada la versión postfijo, sobrecargo el mismo operador ++ pero ahora el prefijo.  Si te fijas hay solo 2 diferencias entre la declaración de uno y otro: el postfijo devuelve un objeto Fecha y recibe de parámetro un int; mientras el otro devuelve un apuntador al objeto Fecha y no recibe parámetros. Más adelante en el código se verá la diferencia más clara (espero). Después de esto, declaro una función amiga (friend) que se va a encargar de sobrecargar el operador <<, otra que sobrecarga el operador >> y otras dos funciones: una que devuelve el día máximo de acuerdo al mes y año (necesitamos el año por aquello de los años bisiestos) y otra función que le asigna valores al día, mes y año (este lo usé para probar la asignación antes de sobrecargar el operador >>).
Cabe recalcar que los operadores >> y << no son funciones miembro de la clase y devuelven streams (istream es Input stream o flujo de entrada y ostream es Output stream o flujo de salida).  Son muy similares en su función a los strings pero mucho más versátiles para algunos usos.
Volviendo al código puedes observar que los constructores no tienen ningún chiste.  Aquí está el código:
01-Constructores
El primer operador que sobrecargo es el +.  Aquí está el código:
02-Operador
Supongamos que en mi función main() declaro dos objetos te tipo fecha llamados A, B y C.  Si escribo la operación C=A+B se ejecuta esta función.  El valor de A sería el “local”, es decir, cuando hago referencia a dia, mes y anio, estoy accesando estas variables del objeto A.  El parámetro que le paso (llamado x) tendría los valores almacenados en B. así que, cuando hago referencia a x.dia, x.mes y x.anio me estoy refiriendo a estos campos en el objeto B.
Una vez aclarado de los parámetros, pasemos a ver qué hace esta función.  En x recibe el valor de la fecha que se le va a sumar (días, meses y años).  Luego declaro un objeto Fecha (Temp) donde voy a realizar la suma de las fechas.  La variable mex es temporal para el cálculo de meses.  Sumo los días y años en Temp y luego calculo los meses: si el día es mayor al número máximo de días que corresponde al mes, le resto el número de días máximo aceptados, incremento el número de meses y si corresponda cambiar de año, incrementa el año y le resta 12 meses.  Al final del ciclo le sumo los meses y vuelvo a verificar si toca cambio de año.  Al final devuelvo la variable Temp que contiene la suma.
Ahora pasamos al operador ++ postfijo.  Aquí está el código:
03-Operador
Si se fijan, el parámetro de tipo int no me sirve para nada, bueno, sirve para distinguir el ++ postfijo del prefijo como ya argumenté con anterioridad.  Ni siquiera le pongo nombre de variable porque no lo voy a usar.  Creo una Fecha, llamado Temp, para hacer el cálculo de la fecha más un día.  El apuntador this es una forma elegante de referirse a esta misma clase y lo uso para inicializar a Temp.  Altero los valores de ésta clase (apuntador this) para incrementar un día y luego revisar si toca cambio de mes, y si toca, incremento el año.  Al final devuelvo Temp y ya terminé.
El código del operador ++ prefijo es idéntico.  Lo único que cambia es que devuelve la referencia (apuntador) a un objeto Fecha y no recibe un entero como parámetro.  Aquí está:
04-Operador
Ahora una función muy simple para devolver el número máximo de días que tiene cada mes.  Lo uso mucho para validaciones.  La idea es que enero, marzo, mayo, julio, agosto, octubre y diciembre tienen 31 días, mientras que abril, junio, septiembre y noviembre tienen 30.  Como saben el mes especial es febrero que puede tener 29 o 29 días dependiendo si el año es bisiesto o no.  Aquí está el código:
05-MaxDiaMes
Otra función que no tiene chiste es el que te permite asignar fecha.  Aquí está:
06-AsignaFecha
Ahora voy a hablar de la función amiga que sirve para sobrecargar el operador <<.  Aquí está el código:
07-Operador ostream
El objeto de tipo ostream es igual al que recibe la función cout.  Así que básicamente debo hacer lo mismo que haría en un cout para dejar el texto como quiero, pero en lugar de enviarlo al cout, lo mando a x.  Esta función devuelve una referencia al objeto ostream que hice.
Para terminar con las funciones, falta el que sobrecarga al operador .  Hagan de cuenta que ahora estoy trabajando con el cin (el objeto de tipo istream es el mismo tipo de objeto que cin).  La función ignore de la clase istream se encarga de ignorar el siguiente caracter del istream (en mi caso es una diagonal entre día y mes, y otra diagonal entre mes y año).  Aquí está el código:
08-Operador istream
Para terminar, hice la función main() que prueba todo lo anterior.  Aquí está:
09-main
Este ejemplo fue hecho con Visual C++ 2010 y puedes descargar el proyecto en formato RAR aquí.  ¡Saludos!

El Tony y sus ondas...

Related Posts Plugin for WordPress, Blogger...