Francisco Javier Peña Escobar Universidad del Valle [email protected] Universidad del Valle - 2009 Relaciones entre clases Relaciones entre clases. Herencia. Constructores y destructores. Lista de inicialización en constructor. Funciones protected. Funciones virtuales y virtuales puras (clases abstractas). Destructores virtuales. Polimorfismo. Herencia múltiple (no usarla). Son siempre permanentes en C++, nunca temporales. Son: ◦ “Es un”. ◦ “Es parecido a un” Ambas se implementan con el mecanismo de “herencia pública” (con el operador dos puntos :) Ejemplo: La clase derivada hereda de la clase base class Base { private: // etc... public: // etc... }; class Derivada : public Base { private: // etc... public: // etc... }; Lo que hace el mecanismo de herencia es incorporar la clase Base dentro de la clase Derivada La clase Derivada entonces tiene todo lo que tenía la clase Base, y además puede añadir sus propios métodos en la parte pública y sus propios atributos en la parte privada. Si la clase Derivada añade nuevas funciones o atributos, además de las recibidas en la clase base, entonces es una relación “es parecido a”. Si la clase Derivada no añade nuevas funciones no atributos entonces es una relación “es un”. InstrumentoMusical PÚBLICO es un PIANO Persona es un es un es parecido a un Músico DIRECTOR DE ORQUESTA es un GUITARRISTA GUITARRA es un PIANISTA PARTITURA El constructor de la clase derivada debe llamar al constructor de la clase base, pasándole los argumentos adecuados. ◦ El constructor de la clase derivada llama implícitamente al de la clase base si el constructor de la clase base no requiere argumentos (no hay que escribir ningún código especial). ◦ El destructor de la clase derivada llama implícitamente al destructor de la clase base (no hay que escribir ningún código especial). El orden de llamada a constructores y destructores es el lógico: ◦ ◦ ◦ ◦ ◦ Constructor de la clase Base Constructor de la clase Derivada .... Destructor de la clase Base Destructor de la clase Derivada La parte privada de la clase Base sigue siendo inaccesible incluso para la parte Derivada. Si se desea que haya en la clase Base una parte inaccesible para el resto del mundo, pero accesible para la parte Derivada, debe declararse protected. protected public private private Hay dos estilos de trabajo con la herencia que los vamos a nombrar usando una analogía con el mundo biológico de las células: ◦ Estilo “procariota”: Pasar todos los atributos private a protected: Con ello la clase Derivada es una clase “mas gorda”, con mas atributos en su interior ya que ha roto la cápsula de la clase Base: atributo1_Base atributo1_Derivada atributo2_Base atributo2_Derivada ◦ Estilo “eucariota”: No usar protected nunca: Ello significa que la clase Base permanece encapsulada e independiente de la clase Derivada: atributo1_Derivada atributo2_Derivada atributo1_Base atributo2_Base ◦ Estilo mixto: Con parte public, private y protected Nota: Procariotas son las células sin núcleo (usualmente bacterias). Eucariotas son las células con núcleo y otros órganos internos (usualmente las células de los animales y plantas superiores). Parece ser que algunas células primitivas eucariotas trataron de devorar otras células semejantes, pero no consiguieron digerirlas del todo, quedando como “órganos internos” que trabajan colaborativamente (simbiosis). Este fue el primer acto sexual que dio origen a las células eucariotas. Obvia decir que las células eucariotas son mucho mas evolucionadas, y esperemos que el software OO que ustedes hagan también lo sea. Núcleo (con ADN) Mitocondrias (respiran oxígeno) CÉLULA EUCARIOTA CÉLULA PROCARIOTA En las células de plantas: cloroplasto (antiguas cianobacterias) Si una función de la clase Base no hace lo que deseamos para la clase Derivada, se puede reescribir (overwrite) en la clase Derivada. class Base { public: double funcion(int x); }; class Derivada : public Base { public: double funcion(int x); }; double Base::funcion(int x) { //Algoritmo específico para el //comportamiento de la clase Base } double Derivada::funcion(int x) { // Nuevo algoritmo para lograr un // comportamiento distinto } Todas los métodos y atributos de la clase Base se heredan en la clase Derivada, excepto: ◦ El operador de asignación: Clase::operador=(const Clase &). ◦ Los constructores y los destructores, incluyendo el constructor de copia: Clase::(const Clase &). El programador debe escribir el constructor de cada clase. Y el constructor de las clases Derivadas debe llamar al constructor de la clase Base. El compilador de C++ crea automáticamente para toda clase que usted escriba (incluyendo las clases derivadas): ◦ Un constructor por defecto (que simplemente ocupa la memoria que necesita el objeto). ◦ Un destructor (que simplemente libera la memoria ocupada por el objeto). ◦ Un constructor de copia (que copia bit por bit toda la información del objeto fuente al destino). ◦ Un operador de asignación (que copia bit por bit toda la información del objeto fuente al destino) Si usted lo desea, puede evitar que el compilador cree estas funciones escribiendo sus propias funciones o situándolas en la parte privada. Anulándolas por completo. Concepto previo: Un puntero que apunte a un objeto de una clase Base, también puede apuntar a objetos derivados de esa Base. class Base { // etc... }; class Derivada : public Base { // etc... } int main() { Base *puntero = new Base; delete puntero; puntero = 0; puntero = new Derivada; // etc... } Cada clase tiene una serie de funciones disponibles. La ligadura de funciones a clases se hace en tiempo de compilación Pero también puede hacerse en tiempo de ejecución (que es mas flexible), usando la palabra virtual delante de la función. Ello permite que la decisión de cual función se va a ejecutar (si la de la clase Base o la de la clase Derivada) se tome en tiempo de ejecución. No es necesario implementar el código de las funciones virtuales en la clase Base. En este caso hay que poner = 0 en la declaración de la función. A estas funciones que no tienen código, se las llama funciones virtuales puras. Las clases que contienen al menos alguna función virtual pura se las llama clases abstractas. De ellas no se pueden instanciar objetos (porque falta el código de las funciones virtuales puras). Lo único que se pueda hacer con una clase abstracta es heredar de ella e implementar en la clase derivada las funciones faltantes. No es una palabra muy bien escogida (“muchas formas”). Mas bien quiere decir “muchos comportamientos” o “muchas implementaciones” Empleamos polimorfismo cuando de una clase con funciones virtuales hereda una o varias derivadas. Al ejecutar la misma función diversos objetos (de clases derivadas), su comportamiento depende de la clase concreta a la que pertenezca el objeto. El polimorfismo en C++ se consigue usando herencia con funciones virtuales y un puntero a clase Base con el que apuntar a distintos objetos derivados. No se puede declarar virtual los constructores, ya que se ejecutan todos, tanto el de la clase derivada como el de la(s) clase(s) base. Sin embargo, los destructores si pueden declararse virtuales. Es mas, si una clase tiene alguna función virtual, entonces es obligatorio declarar su destructor como virtual, para evitar pérdidas de memoria (memory leaks). Cuando un destructor es virtual, el comportamiento es al revés que en las demás funciones: Obliga al programa a ejecutar todos los destructores desde la clase derivada hasta la clase base principal. En C++ una clase puede heredar de varias clases base. A esto se le llama herencia múltiple. Sirve para implementar la nueva relación “es parecido a varias” clases. Hay que usar esta relación con mucho cuidado, ya que no se corresponde con la naturaleza del mundo real. De hecho, en POO no es conveniente emplear herencia múltiple. Solo en algún caso muy especial puede tener interés en usarla (para reutilizar y “parchear” código fuente). Pero no es para principiantes ni para abusar. ¡Gracias por su atención!