¿Por qué java no soporta la sobre carga de
operadores?
Java no soporta sobrecarga de operadores, aunque el sistema interna...
c = a + b;
C++ permite que hagamos estas cosas si definimos en algún sitio cómo se suman esas clases.
Definiendo cómo se h...
double. En la segunda, ponemos const para no poder modificar el array, pero no es necesaria la
referencia, puesto que los ...
Sobrecarga de operadores suma globales
Un operador global es una función global que no pertenece a ninguna clase y que nos...
Primero se evalúa operator << (cout, a), que escribe a en pantalla y devuelve un cout, con lo que
la expresión anterior qu...
double a;
ComplejoC b;
a = (double)b;
En el operator cast se pone operator seguido del tipo al que se quiere hacer el cast...
El operador igual
El operator = es como los demás. Simplemente un pequeño detalle. C++ por defecto tiene el
operador igual...
>Atributo */
*a = *b;
// Ahora si que la hemos liado.
delete b;
Cuando hacemos a=b, con el operador igual por defecto de C...
cosa (clases o variables). Esta característica nos permite hacer contabilidad de punteros, para ver si
liberamos todo lo q...
of 9

Por qué java no soporta la sobre carga de operadores

Published on: Mar 4, 2016
Source: www.slideshare.net


Transcripts - Por qué java no soporta la sobre carga de operadores

  • 1. ¿Por qué java no soporta la sobre carga de operadores? Java no soporta sobrecarga de operadores, aunque el sistema internamente la utiliza, pero está ocultada al programador, por ejemplo si te fijas al hacer un Int ejemplo=2+1; En java eso es válido y le asigna un 3 a la variable ejemplo la funcionalidad fue sumar, sin embargo en este: String ejemplo="hola"+"mundo"; El operador + se utiliza con otra finalidad : Concatenar , lo ves? Java si usa la sobrecarga de operadores, pero esta oculta para el programador, ósea no te permite usarla. ¿Por qué? Básicamente para no llevar a confusión , imagina que defines una clase llamada "sobreEscribe" en la cual sobrecargaras el operador suma y le asignaras tu propia forma de trabajar por ejemplo sumar fracciones y más tarde defines la clase "fracciones" heredando la clase "sobreEscribe" el resultado sería que en la clase "fracciones" el operador suma cambiara su comportamiento. Si llegase ahora un programador externo y revisara la clase fracciones podría confundirse y creer que este operador mantiene el comportamiento normal, para saber que tiene el comportamiento actual tendría que ir a revisar "sobreEscribe", ahora imagina que esta herencia se realiza entre varias clases, sería muy difícil saber si realmente se está cambiando el comportamiento del operador suma o no. Esta es una de las razones más fuertes por las cuales Java no soporta la sobrecarga de operadores básicamente para no llevar a confusión y al fin y al cabo no es necesaria, puedes utilizar tus propios métodos ¿Qué es la sobrecarga de operadores? En C++ se pueden hacer operaciones con los tipos básicos del lenguaje: se pueden sumar enteros, compararlos, etc. Lo siguiente es perfectamente válido en C++ int a=3; int b=4; int c; c = a + b; Si estos tipos no son los básicos del lenguaje, no se puede hacer sumas con ellos. Por ejemplo, si ClaseC es una clase que tengo definida en C++, el siguiente código dará error. ClaseC a; ClaseC b; ClaseC c;
  • 2. c = a + b; C++ permite que hagamos estas cosas si definimos en algún sitio cómo se suman esas clases. Definiendo cómo se hacen las operaciones, podremos escribir las operaciones con nuestras clases de la misma forma que si se trataran de tipos básicos del lenguaje. De hecho podemos definir cualquier operador que nos dé C++, desde algunos muy normales como suma, resta, multiplicación, mayor qué, etc, a otros un poco más raros como el operador () o el [], de forma que ClaseC[i] o ClaseC(i,j) pueden devolver lo que nosostros queramos. Sobrecarga del operador suma Si tenemos una clase ComplejoC que representa un complejo, para poder sumar dos de estas clases simplemente poniendo un +, como con cualquier tipo básico de C++, debemos sobrecargar el operador +, darle una nueva funcionalidad. La sobrecarga de un operador suma se hace de la siguiente manera class ComplejoC { public: // Permite sumar un ComplejoC con un double ComplejoC operator + (double sumando); // Permite sumar un ComplejoC con un array ComplejoC operator + (const double sumando[]); // Permite sumar dos ComplejoC entre sí. ComplejoC operator + (const ComplejoC &sumando); }; Aquí estamos redefiniendo tres operadores suma para que nos permita sumar a nuestra clase ComplejoC cosas como un double, un array de double (que supondremos contiene dos double, aunque no tenemos forma de comprobarlo) y otra clase ComplejoC . En estas funciones hemos puesto const en los parámetros para no poder cambiarlos dentro del código. En la tercera función pasamos ComplejoC por referencia (el &), para evitar que se hagan copias innecesarias. En la primera función no es necesario nada de esto, puesto que es un simple
  • 3. double. En la segunda, ponemos const para no poder modificar el array, pero no es necesaria la referencia, puesto que los arrays son punteros. A la hora de implementar debemos tener cuidado con los const que hemos puesto. Por ejemplo, en el tercer operador +, recibimos un sumando que es const. El código que implementemos dentro no puede modificar dicho sumando, ni puede llamar a ningún método de ese sumando que no esté declarado como const. Si lo intentamos, el compilador dará error. Supongamos que ComplejoC tiene un atributo double x (parte real) y un método dameX() para obtener dicho atributo, este método debe estar declarado const para poder llamarlo desde nuestro operator +. Más o menos esto: class ComplejoC { public: // Atención al const del final. Sin el no podemos llamar a sumando.dameX(). double dameX() const; }; De forma que en el operator + podemos llamar a sumando.dameX(). Una vez implementados estos métodos, podemos hacer operaciones como ComplejoC a; ComplejoC b; // Aprovechando la primera sobrecarga b = a + 1.0; // Aprovechando la segunda sobrecarga double c[] = {1.0, 2.0}; b = a + c; // Aprovechando la tercera sobrecarga b = a + b; Cuando el compilador lee a + 1.0, lo interpreta como a.operator + (1.0), es decir, la llamada al operador suma al que se le pasa como parámetro un double . De la misma forma sucede con los otros dos operadores suma. Sin embargo, esto nos da un pequeño problema. ¿Qué pasa si ponemos 1.0 + a?. Debería ser lo mismo, pero al compilador le da un pequeño problema. Intenta llamar al método operator + de 1.0, que no existe, puesto que 1.0 ni es una clase ni tiene métodos. Para solucionar este problema tenemos la sobrecarga de operadores globales.
  • 4. Sobrecarga de operadores suma globales Un operador global es una función global que no pertenece a ninguna clase y que nos indica cómo operar con uno o dos parámetros (depende del tipo de operador). En nuestro ejemplo de las sumas, para poder poner los sumandos al revés, deberíamos definir las siguientes funciones globales: ComplejoC operator + (double sumando1, const ComplejoC &sumando2); ComplejoC operator + (double sumando1[], const ComplejoC &sumando2); Estas funciones le indican al compilador cómo debe sumar los dos parámetros y qué devuelve. Con ellas definidas e implementadas, ya podemos hacer b = 1.0 + a; // c era un array de double b = c + a; Esta sobrecarga es especialmente útil cuando tratamos con una clase ya hecha y que no podemos modificar. Por ejemplo, cout es de la clase ostream y no podemos modificarla, sin embargo nos sería de utilidad sobrecargar el operador << de ostream de forma que pueda escribir nuestros números complejos. La siguiente llamada nos dará error mientras no redefinamos el operator << de ostream . cout << a << endl; // a es un ComplejoC Con la sobrecarga de operadores globales podemos definir la función ostream &operator << (ostream &salida, const ComplejoC &valor); Con esta función definida, el complejo se escribirá en pantalla como indique dicha función. Esta función deberá escribir la parte real e imaginaria del complejo en algún formato, utilizando algo como salida << valor.dameX() << " + " << valor.dameY() << "j"; El operador devuelve un ostream, que será un return cout. De esta forma se pueden encadenar las llamadas a cout de la forma habitual. cout << a << " + " << b << endl;
  • 5. Primero se evalúa operator << (cout, a), que escribe a en pantalla y devuelve un cout, con lo que la expresión anterior quedaría, después de evaluar esto cout << " + " << b << endl; Y así consecutivamente. Hay que tener en cuenta que estos operadores globales no son de la clase, así que sólo pueden acceder a métodos y atributos públicos de la misma. El operador cast Un operador interesante es el operador "cast". En C++, si tenemos dos tipos básicos distintos, podemos pasar de uno a otro haciendo un cast (siempre que sean compatibles de alguna forma). Por ejemplo int a; double b; a = (int)b; El cast consiste en poner delante, entre paréntesis, el tipo que queramos que sea. En algunos casos, como en el de este ejemplo,el cast se hace automáticamente y no es necesario ponerlo. Puede que de un "warning" en el compilador avisando de que perderemos los decimales. En principio, con las clases no se puede hacer cast a otros tipos, pero es posible declarar operadores que lo hagan. La sintaxis sería: class ComplejoC { public: // Permite hacer un cast de ComplejoC a double operator double (); } Con este podemos hacer cast de nuestra clase a un double . Es nuestro problema decidir cómo se hace ese cast. En el código de ejemplo que hay más abajo se ha definido como la operación módulo del número complejo.
  • 6. double a; ComplejoC b; a = (double)b; En el operator cast se pone operator seguido del tipo al que se quiere hacer el cast. No se pone el tipo del valor devuelto, puesto que ya está claro. Si ponemos operator double, hay que devolver un double . En el operator cast no se pone parámetro, puesto que el parámetro recibido será una instancia de la clase. Conviene tener cuidado con definir muchos operadores cast, puesto que el compilador los tendrá todos presentes y será capaz, encadenando unos con otros, de hacer cast entre tipos que no tienen nada que ver. Por ejemplo, si sumamos un ComplejoC con un DibujoC (que no tienen nada que ver) y ambos tienen cast e int, es posible que el compilador los transforme ambos en int y luego los sume como enteros. ¿Cómo hacemos un cast al revés?. Es decir, ¿Cómo podemos convertir un double a ComplejoC?. El asunto es sencillo, basta hacer un constructor que admite un double como parámetro. class ComplejoC { public: ComplejoC (double valor); }; Este constructor sirve para lo que ya sabemos ComplejoC a(3.0); o podemos usarlo para hacer un cast de double a ComplejoC ComplejoC a; a = (ComplejoC)3.0;
  • 7. El operador igual El operator = es como los demás. Simplemente un pequeño detalle. C++ por defecto tiene el operador igual definido para clases del mismo tipo. Por ejemplo, sin necesidad de redefinir nada, podemos hacer ComplejoC a; ComplejoC b; a = b; Este igual por defecto lo único que hace es copiar el contenido del uno sobre el otro, como si fueran bytes, sin saber qué atributos está copiando ni qué significan. Para clases sencillas, que solo tienen atributos que no son punteros, esto es más que suficiente. Si embargo, si algún atributo es un puntero, tenemos que tener mucho cuidado con lo que hacemos. Supongamos que ClaseC tiene un atributo Atributo que es un puntero. Supongamos también que en el constructor de la clase, se hace new del puntero para que tenga algo y en el destructor se hace el delete correspondiente. ClaseC { public: // Se crea un array de tres enteros ClaseC () { Atributo = new int[3]; } // Se libera el array. ~ClaseC () { delete [] Atributo; } protected: int *Atributo; }; Si ahora hacemos esto // a tiene ahora un array de 3 enteros en su interior ClaseC *a = new ClaseC(); // b tiena ahora otro array de 3 enteros en su interior ClaseC *b = new ClaseC(); /* Se copia el Atributo de b sobre el de a, es decir, ahora a->Atributo apunta al mismo sitio que b-
  • 8. >Atributo */ *a = *b; // Ahora si que la hemos liado. delete b; Cuando hacemos a=b, con el operador igual por defecto de C++, se hace que el puntero Atributo de a apunte al mismo sitio que el de b. El array original de a->Atributo lo hemos perdido, sigue ocupando memoria y no tenemos ningún puntero a él para liberarlo. Cuando hacemos delete b, el destructor de b se encarga de liberar su array. Sin embargo, al puntero a->Atributo nadie le avisa de esta liberación, se queda apuntando a una zona de memoria que ya no es válida. Cuando intentemos usar a->Atributo más adelante, puede pasar cualquier cosa (cambios aleatorios de variables, caidas del programa, etc). En el tema de punteros tienes un poco más detallado todo esto. Allí se habla de estructuras, pero también se aplica a clases. La forma de solucionar esto, es definiendo nosotros un operator = que haga una copia real del array, liberando previamente el nuestro o copiando encima los datos. ClaseC { public: ClaseC &operator = (const ClaseC &original) { int i; /* Damos por supuesto que ambos arrays existen y son de tamaño 3 */ for (i=0;i<3;i++) Atributo[i] = original.Atributo[i]; } }; Sobrecarga del operador new y delete Otro operador interesante de sobrecargar es el new y el delete. Si los sobrecargamos dentro de la clase, cada vez que hagamos un new a nuestra clase se llamará a nuestro operador. Más interesante es la sobrecarga de los operadores new y delete globales. Sobrecargando estos operadores se llamará a nuestras funciones cada vez que hagamos un new o un delete de cualquier
  • 9. cosa (clases o variables). Esta característica nos permite hacer contabilidad de punteros, para ver si liberamos todo lo que reservamos o liberamos lo mismo más veces de la cuenta.

Related Documents