Tema 2. Programación basada en objetos Programación Avanzada Ingeniería Técnica en Informática de Gestión Jorge Badenas 2.1. Objetivos Estudiar los conceptos de clase y objeto, y su implementación mediante el lenguaje C++. Constructores y destructores. Sobrecarga de operadores. Programación Avanzada - Ingeniería Técnica en Informática de Gestión 2 2.2. Encapsulación de los datos Encapsulación de los datos ListinTelefonico cap construirListin insertarTelefono buscarTelefono Interfaz destruirListin main insercion Programa busqueda Programación Avanzada - Ingeniería Técnica en Informática de Gestión 3 2.2. Encapsulación de los datos El interfaz amortigua los efectos de los cambios internos de la estructura. Si no es preciso cambiar el interfaz, los cambios no afectarán al programa. Programación Avanzada - Ingeniería Técnica en Informática de Gestión 4 2.3 Características beneficiosas Mayor abstracción. abstracción Entidades de más alto nivel. Modularidad Modularidad. Agrupación de estructuras de datos con las funciones que facilitan su manipulación. Ocultación de la información. información Para usar algo no es preciso saber cómo está hecho. Encapsulación de los datos. datos El acceso a los datos se hace a través de un interfaz. La encapsulación es la piedra angular que hace que las otras tres se cumplan. Programación Avanzada - Ingeniería Técnica en Informática de Gestión 5 2.4. Objetos Necesitamos un mecanismo en los lenguajes de programación que obligue a los programas a manejar los datos a través de la interfaz. Los objetos son ese mecanismo que permite la encapsulación. Error de compilación Objeto cap construirListin insertarTelefono Acceder directamente a los datos buscarTelefono Interfaz destruirListin main insercion Programa busqueda Programación Avanzada - Ingeniería Técnica en Informática de Gestión 6 2.5. Objetos y clases Un objeto es una instancia de una clase. clase Una clase contiene dos tipos de elementos: Dos tipos de elementos en una clase según el tipo de acceso: Datos Funciones que manejan los datos. Privados Públicos La interfaz de la clase lo forman las funciones públicas. El resto de las funciones del programa que no pertenecen a la clase están obligadas a usar los datos a través de la interfaz. Programación Avanzada - Ingeniería Técnica en Informática de Gestión 7 2.6 Transformando un Tipo de Dato en una Clase struct Hora { int minutos; int horas; }; void ponerEnHora( Hora& oh, int h, int m ) { oh.minutos = m; oh.horas = h; if( m>59 ) normalizar( oh ); } void normalizar( Hora& oh ) { oh.horas += oh.minutos / 60; oh.minutos = oh.minutos % 60; } void mostrarHora( const Hora &oh ) { cout<<oh.horas<<“:”<<oh.minutos; } Programación Avanzada - Ingeniería Técnica en Informática de Gestión 8 2.6 Transformando un Tipo de Dato en una Clase class Hora { public: void ponerEnHora( int h, int m); void mostrar( ); private: int horas; int minutos; void normalizar( ); }; Para definir una clase se pueden usar dos palabras clave: class struct (elementos públicos) Los datos serán privados. Las funciones que permitían el manejo de la estructura pasarán a pertenecer a la clase. Programación Avanzada - Ingeniería Técnica en Informática de Gestión 9 2.6.1 Miembros de una clase. Las funciones y datos que pertenecen a la clase reciben el nombre de miembros de la clase. clase Métodos Funciones Atributos Datos Los miembros privados son inaccesibles desde las funciones que no pertenecen a la clase (Encapsulación Encapsulación). Las funciones públicas son la interfaz de la clase. Los datos de una clase son equivalentes a los campos de un tipo de dato struct de C. Las funciones de una clase están incrustadas en ella, por lo tanto no necesitan recibir el objeto, YA LO TIENEN. TIENEN Programación Avanzada - Ingeniería Técnica en Informática de Gestión 10 2.6.2. Declaración y uso de las clases // Sin clases int main( ) { Hora h; ponerEnHora( h, 6, 50 ); mostrar( h ); normalizar( h ); h.minutos = 80;//Contenido incorrecto return 0; } // Con clases int main( ) { Hora h; h.ponerEnHora( 6, 50 ); h.mostrar( ); h.normalizar( ); // ERROR h.minutos = 80; // ERROR return 0; } Programación Avanzada - Ingeniería Técnica en Informática de Gestión 11 2.6.3. Definición de funciones miembro Se debe poner el nombre de la clase void Hora::mostrar( ) { cout<< horas << “:” <<minutos; } void Hora::normalizar( ) { horas += minutos / 60; minutos = minutos % 60; } Cuando estoy en la clase se sobreentiende que hablo de Hora::minutos void Hora::ponerEnHora( int h, int m ) { minutos = m; horas = h; if( m>59 ) normalizar( ); } Programación Avanzada - Ingeniería Técnica en Informática de Gestión 12 Convertir en una clase: /* Archivo Cuenta.h */ struct Cuenta{ char titular[30]; int numero; float saldo; }; void asignarSaldo ( Cuenta &, float = 0.0); void inicializar ( Cuenta &, char *, int , float = 0.0); /*Archivo Cuenta.cpp */ #include <iostream> using namespace std; #include “Cuenta.h Cuenta.h”” void asignarSaldo (Cuenta &c, float s) { if (s<0.0) { cerr<<"SALDO NEGATIVO"; s=0.0;} c.saldo=s; } void inicializar (Cuenta &c, char *t, int n, float s) { strcpy (c.titular, t); c.numero=n; asignarSaldoCuenta (c, s); } Programación Avanzada - Ingeniería Técnica en Informática de Gestión 13 …viene de la página anterior /* Archivo Principal.cpp */ int main ( ) { Cuenta c1, c2; inicializar (c1, "Luis López", 35006); inicializar(c2, "Jesús García", 35007, 1000); asignarSaldo (c1, 500.0); asignarSaldo ( c2 ); return 0; } Programación Avanzada - Ingeniería Técnica en Informática de Gestión 14 2.7. Funciones inline Si una función es breve, se puede escribir su código dentro de la estructura class. Entonces es una función inline. class Hora { public: … int leerMinutos( ) { return minutos; } int leerHoras( ) { return horas; } … }; Programación Avanzada - Ingeniería Técnica en Informática de Gestión 15 2.8. Funciones const Cuando una función no modifica al objeto propietario de la función es conveniente definirla como const. class Hora { public: … int leerMinutos( ) const { return minutos; } int leerHoras( ) const { return horas; } … }; Programación Avanzada - Ingeniería Técnica en Informática de Gestión 16 2.9. Constructores Constructor Función que se Constructor: ejecuta automáticamente al crearse un objeto. Propósito: Inicializar el objeto. Características: Su nombre es el de la clase. No devuelve nada. Puede haber varios, por ejemplo: Los dos anteriores, el compilador los proporciona de forma implícita: Uno sin parámetros: por defecto. Uno con un parámetro de la misma clase: copia. Si declaro un constructor, pierdo el implícito por defecto. Cualquiera de los dos puedo declararlo para redefinirlo a mi gusto. Crear un objeto siempre supone usar un constructor... y ha de decidirse cuál en cada caso. Programación Avanzada - Ingeniería Técnica en Informática de Gestión 17 2.10. Destructores Destructor Función que se Destructor: ejecuta automáticamente cuando un objeto deja de existir. Propósito: Liberar los recursos que el objeto tuviera ocupados (memoria, ficheros, etc.). class Lista { Nodo *cap; public: ~Lista( ); … }; Lista::~Lista( ) { Nodo *nt; while( cap != NULL ) { nt = cap; cap = cap->sig; delete nt; } } Si no, hay un destructor implícito. Programación Avanzada - Ingeniería Técnica en Informática de Gestión 18 2.11. El constructor copia Construye un objeto a partir de otro de la misma clase. Hay uno implícito que copia miembro a miembro. Si el constructor copia implícito no es adecuado, debo reescribirlo. Dada una clase A, el constructor copia se define como: class A { … A( const A&origen); … }; Se trata de inicializar un objeto de la clase A a partir de otro objeto de la clase A (origen). Programación Avanzada - Ingeniería Técnica en Informática de Gestión 19 2.12. Constructores y arrays. Los objetos de un array también se construyen y se destruyen. Paso de parámetros a constructores de arrays: class Complejo { public: Complejo(float r=0.0, float i=0.0) {...} ... }; int main() { Complejo vc1[10]; Complejo vc2[3]= {1.0, 2.0, 3.0}; Complejo vc3[2]= {Complejo(1.0, 1.0), Complejo(2.0, 2.0)}; Complejo * pc, * pvc; pc = new Complejo(1.1, 2.2); pvc = new Complejo[5]; ... delete pc; delete[] pvc; ... } Programación Avanzada - Ingeniería Técnica en Informática de Gestión 20 2.13. Funciones y clases amigas (friend) Una función amiga es una función qué aunque no pertenece a una clase recibe el privilegio de poder acceder a la parte privada. Para hacer una función amiga de una clase se ha de poner en la clase la palabra friend seguida del prototipo de la función. Si se quiere que todas las funciones de una clase A puedan acceder a la parte privada de una clase B, se definirá la clase A como amiga de B. class B { … friend class A; … }; Programación Avanzada - Ingeniería Técnica en Informática de Gestión 21 2.14. this this es la dirección (inmutable) del objeto sobre el que se está ejecutando una función miembro. class A { A* direccion( ) { return this; } A objeto( ) { return *this; } A& objetoReferencia( ) { return *this; } void escrX( int v) { this->x = v; } private: int x; }; … // En alguna función A a, b; A* pa = a.direccion( ); // pa = &a A* pb = b.direccion( ); // pb = &b b = a.objeto( ); // b=a a.objetoReferencia( ).escrX( 10 ); // Lo mismo que a.escrX( 10 ) Programación Avanzada - Ingeniería Técnica en Informática de Gestión 22 2.15. Sobrecarga de operadores La sobrecarga de operadores consiste en asociar funciones a operadores. Podemos sobrecargar los operadores que existen, pero no podemos: El operador es una abreviatura que implica la ejecución de una función. Inventar operadores nuevos. Cambiar el número de operandos que requiere un operador. Sobrecargar el operador + consiste en escribir una función llamada operator+. Lo mismo para el resto de operadores. Programación Avanzada - Ingeniería Técnica en Informática de Gestión 23 2.15 Sobrecarga de operadores class Racional { public: Racional( int n, int d = 1): num( n ), den( d ) { } Racional( ) { } private: int num; // Numerador int den; // Denominador }; Queremos que funcione: Racional a, b, c; a = b + c; Programación Avanzada - Ingeniería Técnica en Informática de Gestión 24 2.15 Sobrecarga de operadores En a = b + c hay dos operadores: b + c Racional + Racional a=Resultado(b+c) Rac = Rac Para sobrecargar b + c debemos escribir una función llamada operator+. operator+ puede tener dos formas: Miembro de la clase Racional: b.operator+( c ) No miembro: operator+( b, c ) Podemos escoger la forma que queramos. Según la que elijamos tendremos distinto número de parámetros. Programación Avanzada - Ingeniería Técnica en Informática de Gestión 25 2.15 Sobrecarga de operadores ¿Qué debe devolver b + c? Racional + Racional Racional ¿Qué debe hacer b + c? Sumar b más c y devolver el resultado ¿Modificar a los objetos b o c? No es lo que ocurre con los tipos de datos del lenguaje (int, float, etc.) Programación Avanzada - Ingeniería Técnica en Informática de Gestión 26 2.15 Sobrecarga de operadores Como no miembro, la función sería: Racional operator+( const Racional& b, const Racional& c ) { Racional tmp; tmp.num = b.num*c.den + b.den*c.num; tmp.den = b.den*c.den; return tmp; } Si accede a miembros privados desde una función no miembro, la función debe ser friend: class Racional { friend Racional operator+(const Racional& b, const Racional& c ); … // Resto del class }; Programación Avanzada - Ingeniería Técnica en Informática de Gestión 27 2.15 Sobrecarga de operadores Falta el operator= Racional = Racional No es necesario implementarlo, porque todas las clases tienen una versión implícita de ese operador. Racional& operator=(const Racional&); Realiza la asignación de cada uno de los datos miembros de la clase. Lo deberemos redefinir cuando necesitemos que este operador haga algo diferente. Pregunta: ¿funcionará?: a = b + c + d; Programación Avanzada - Ingeniería Técnica en Informática de Gestión 28 2.16. Racional += Racional Haremos que la función sea miembro de la clase Racional a += b a.operator+=( b ) Un operador no es normal que retorne void, así que haremos que devuelva Racional. La función debe modificar el primer operando racional. class Racional { Racional& operator+=( const Racional& ); … }; Racional& Racional::operator+=( const Racional& b ) { num = num * b.den + den * b.num; den *= b.den; return *this; } Programación Avanzada - Ingeniería Técnica en Informática de Gestión 29 2.17. Racional == Racional ¿Qué debe devolver a == b? bool. Mejor como función externa: a == b operator==(a, b) class Racional { ... friend bool operator==(const Racional&, const Racional&); ... }; ... bool operator==(const Racional& a, const Racional& b) { return a.num/a.den == b.num/b.den; } Problema con los tipos de datos: 4/5 == 1/3 ¿true? ¿Sobrecargamos también !=? Programación Avanzada - Ingeniería Técnica en Informática de Gestión 30 2.18. -Racional Uso: Racional q, p; p = -q; // p = q.operator-( ); Declaración: class Racional { Racional operator-( ) const; … }; Código de la función: // Versión 1 Racional Racional::operator-( ) const { Racional aux; aux.num = -num; aux.den = den; return aux; } // Versión 2 Racional Racional::operator-( ) const { return Racional( -num, den ); } Programación Avanzada - Ingeniería Técnica en Informática de Gestión 31 2.19. Operaciones entre clases distintas Si se quiere poder sumar Complejo y Racional con el operador +, hay dos opciones: Sobrecargar operator+ para Complejo y Racional. Suponiendo que ya se dispone de la suma de complejos, especificar la conversión de Racional en Complejo. De esta forma, en Complejo + Racional: 1. Se convierte automáticamente el Racional en un Complejo. 2. Se efectúa la suma con la sobrecarga del operator+ para dos complejos. 2.20. Complejo + Racional Complejo c1, c2; Racional q; c1 = c2 + q; Declaración en las clases: class Complejo { Complejo operator+( const Racional&)const; … }; class Racional { friend Complejo Complejo::operator+( const Racional& ) const; … }; Código de la función: Complejo Complejo::operator+( const Racional& q)const { float rac = static_cast<float>( q.num ) / q.den ; return Complejo( real + rac,imaginaria ); } 2.21. Racional + Complejo Complejo c1, c2; Racional q; c1 = q + c2; Declaración en las clases: class Complejo { friend Complejo operator+( const Racional&, const Complejo& ); … }; class Racional { friend Complejo operator+(const Racional&, const Complejo& ); … }; Código de la función: Complejo operator+( const Racional& q, const Complejo& c) { float rac = static_cast<float>( q.num ) / q.den ; return Complejo(c.real+rac,c.imaginaria ); } 2.22. Conversión mediante constructores El constructor permite convertir un objeto a otra clase diferente. class Complejo { public: Complejo(const Racional&); ... friend Complejo operator+(const Complejo&, const Complejo&); private: float real, imaginaria; }; Complejo::Complejo(const Racional& q) { real = static_cast<float>(q.num) / q.den; imaginaria = 0.0; } Se puede evitar que se produzcan conversiones implícitas (coerciones), poniendo la palabra explicit delante del constructor. Programación Avanzada - Ingeniería Técnica en Informática de Gestión 35 2.22. Conversión mediante constructores Racional r; Complejo c1, c2; c1 = c2 + r; C1 = r + c2; El objeto r primero se convierte en un Complejo mediante el constructor, después se llama a la sobrecarga del operador + que suma dos complejos. Programación Avanzada - Ingeniería Técnica en Informática de Gestión 36 2.22 ++Racional y Racional++ class Racional { Racional& operator++( ); Racional operator++( int ); … }; Racional& Racional::operator++( ) { num += den; return *this; } Racional Racional::operator++( int ) { Racional tmp = *this; num += den; return tmp; } El parámetro entero es artificial, sólo sirve para diferenciar el operador posfijo del prefijo. Programación Avanzada - Ingeniería Técnica en Informática de Gestión 37 2.23. Operadores de E/S Se trata de sobrecargar los operadores >> y <<. Racional q; cout << “Dame un racional... ”; cin >> q; cout << “q vale ” << q << endl; cout pertenece a la clase ostream, mientras que cin pertenece a istream. Estos operadores no pueden ser funciones miembro, porque el operando de la izquierda no es el de mi clase. Programación Avanzada - Ingeniería Técnica en Informática de Gestión 38 2.23. Operadores de E/S El operador de salida es <<: Racional q(2, 3); cout << q; // operator<<(cout, q); Se debe devolver por referencia el operando izquierdo (un ostream) para poder ordenar más salidas en la misma expresión: cout << q1 << “ y ” << q2 << endl; Posible implementación: ostream& operator<<(ostream& os, const Racional& q) { os << q.num << ‘/’ << q.den; return os; } Si se accede a datos privados, la función tendrá que ser friend. Programación Avanzada - Ingeniería Técnica en Informática de Gestión 39 2.23. Operadores de E/S El operador de entrada es >>: Racional q; cin >> q; // operator>>(cin, q); El formato que lee operator>> debe ser similar al que escribe operator<<. Posible implementación: istream& operator>>(istream& is, Racional& q) { char c; // para leer ‘/’ is >> q.num >> c >> q.den; return is; } Consideraciones análogas al caso anterior, pero: en vez de ostream. Operando derecho, por referencia no const. istream Programación Avanzada - Ingeniería Técnica en Informática de Gestión 40 2.24. El operador = Dada una clase C, su cabecera es: C& C::operator=(const C&); Todas las clases disponen de un operator= implícito, que debe redefinirse si no es adecuado. El operator= implícito asigna los datos de un objeto al otro, miembro a miembro. No se debe confundir el constructor copia con el operator=. Al constructor sólo se le puede llamar cuando se está inicializando un objeto que es nuevo. Pero puede ser útil definirlo a partir de la asignación, hecha sobre *this. Es importante prever casos como a=a; (autoasignación). Programación Avanzada - Ingeniería Técnica en Informática de Gestión 41 2.25. Operador de indexación Para acceder a un elemento de un objeto contenedor mediante su índice (no siempre int): obj[ind] obj.operator[](ind) // acceso al elemento ind de obj Por ejemplo, para que funcione: VectorFloat v(10); // Diez flotantes float x; ... x = v[5]; // Lectura v[6] = x; // Escritura Para poder escribir, operator[] no puede devolver una copia del elemento, sino una referencia a él. Para poder leer incluso si el contenedor es const, necesitamos una versión que devuelva copia o referencia const. Programación Avanzada - Ingeniería Técnica en Informática de Gestión 42 2.25. Operador de indexación class VectorFloat{ public: // Para lectura y escritura (Op1) float& operator[](int i) { return elementos[i]; } // Para sólo lectura (Op2) float operator[](int i) const { return elementos[i]; } ... private: int nelems; // Número de elementos int *elementos; }; void raro(VectorFloat& vvble, const VectorFloat& vcte) { float x; ... x = vvble[0]; // Versión Op1 x += vcte[1]; // Versión Op2 vvble[2] = 2*x; // Versión Op1 } Programación Avanzada - Ingeniería Técnica en Informática de Gestión 43 2.26. Operador de llamada Para utilizar un objeto como si fuera una función: obj(arg1,... argn) obj.operator()(arg1,... argn) Puede definirse para el número de argumentos que se desee. Por ejemplo, para acceso a elementos de una matriz: class MatrizFloat{ public: float& operator()(int i, int j) { return elementos[i*ncols+j]; } float operator()(int i, int j) const { return elementos[i*ncols+j]; } ... private: int nfils, ncols; int *elementos; }; Programación Avanzada - Ingeniería Técnica en Informática de Gestión 44