Herencia y polimorfismo Programación Orientada a Objeto Ing. Civil en Telecomunicaciones Herencia Hemos visto cómo crear nuestras propias clases Clase InfoAlumno para calcular las notas Supongamos ahora que el curso tiene alumnos de postgrado y de pregrado Alumnos de postgrado tienen que hacer un trabajo extra Solución: crear clase nueva que hereda métodos de la clase existente Herencia Concepto básico para programación orientada al objeto Una clase derivada hereda funciones y atributos de la clase superior ó base Establece una jerarquía de clases unida por relación es un tipo de Un tenedor es un tipo de cubierto Un camión, una bicicleta y un avión son tipos de medios de transporte Herencia Clase Postgrado hereda la parte pública de Base Funciones miembro Otras variables y Funciones miembro funciones miembro Clase Base() Clase Postgrado() Clase Base // Define la interfaz Base(); Base(std::istream&); std::string nombre() const; std::istream& leealumnos(std::istream&); double nota() const; private: // Define la implementacion std::istream& leecomun(std::istream&); std::string n; double examen1, examen2; std::vector<double> tareas; ; Clase Postgrado Nueva clase Postgrado hereda la interfaz pública de Base Tiene su propio constructor y y agrega variables y funciones miembro class Postgrado: public Base { // Define la interfaz Postgrado(); Postgrado(std::istream&); std::istream& leealumnos(std::istream&); double nota() const; private: // Define la implementacion double extra; // nota extra para postgrado ; 2015 Mario Medina C. 1
Clase Postgrado Clase derivada de clase Base Toda función pública miembro de Base también es función pública miembro de Postgrado Excepto constructores, destructor y operador de asignación Si se usa private Base, las funciones públicas de Base son funciones privadas de Postgrado Clase Postgrado puede definir sus propias funciones miembro redefinir funciones miembro definidas en Base Clase Postgrado Hereda la interfaz pública de Base, la que ahora forma parte de la interfaz pública de Postgrado Clase Postgrado no define una función miembro nombre() porque puede invocar a la función nombre() de la clase Base No hay acceso a la implementación de Base La clase Postgrado no tiene acceso a las notas, ya que son variables privadas de Base Datos protegidos (protected) Notas y función leecomun() de clase Base no son accesibles a Postgrado Redefinirlos como elementos protegidos (protected) Permite acceso a elementos por parte de funciones miembro de clases derivadas No permite acceso por parte de los usuarios de estas clases derivadas Tipos de acceso Acceso en clase base Especificador de acceso en clase derivada Acceso en clase derivada private: : private Inaccesible : private private: : private private: private: : public Inaccesible : public : public private: : protected Inaccesible : protected : protected Clase Base Clase Base // Define la interfaz Base(); Base(std::istream& in); std::string nombre() const; std::istream& leealumnos(std::istream& in); double nota() const; // Estas funciones y variables // son accesibles por clases derivadas std::istream& leecomun(std::istream& in); double examen1, examen2; std::vector<double> tareas; private: // Esta variable es privada std::string n; ; string Base::nombre() const { return n; double Base::nota() const { // Llama a funcion no-miembro return ::nota(examen1, examen2, tareas); istream& Base::leeComun(istream& in) { in >> n >> examen1 >> examen2; return in; istream& Base::leeAlumnos(istream& in) { leecomun(in); leenotas(in, tareas); return in; 2015 Mario Medina C. 2
Clase Postgrado es istream& Postgrado::leeAlumnos(istream& in) { leecomun(in); in >> extra; // lee nota extra leenotas(in, tareas); return in; double Postgrado::nota() const { return min(base::nota(), extra); Supondremos que el alumno de postgrado tiene como nota el mínimo entre su trabajo extra y la nota calculada sin este trabajo Error por llamada recursiva si escribimos min(nota(), extra) Para construir un objeto de clase Postgrado, Se reserva espacio para el objeto Se invoca al constructor correspondiente de la clase Base Se inicializan los miembros de la clase Base Se invoca al constructor de la clase Postgrado Se inicializan los miembros de la clase Postgrado Se ejecuta el cuerpo del constructor de la clase Postgrado es Ejemplo: clase base Circulo // por omision Base(): examen1(0), examen2(0) { // que recibe un istream Base(std::istream& is) { leealumnos(is); //... ; class Postgrado: public Base { // ambos constructores llaman a Base::Base() Postgrado(): extra(0) { Postgrado(std::istream& is) { leealumnos(is); ; #define PI 3.14159535 class Circulo { double radio; // Circulo(double r = 1.0) { radio = r; double calcula() { return PI*radio*radio; ; Ejemplo: clase derivada Cilindro class Cilindro : public Circulo { double largo; // Cilindro(double r = 1.0, double l = 1.0) : Circulo(r), largo(l) { double calcula() { return largo*circulo::calcula(); ; Ejemplo: clase derivada Cilindro Comentarios al código Clase Cilindro hereda funciones públicas y protegidas de la clase Circulo de Cilindro llama explícitamente al constructor de Circulo Compilador hace esto implícitamente Cilindro podría inicializar su variable radio Cálculo de área de Cilindro llama explícitamente a función calcula() de la clase base Circulo 2015 Mario Medina C. 3
Ejemplo: clase derivada Cilindro Ejemplo: clase derivada Cilindro Circulo circ_1, circ_2(2); Cilindro cil_1(3, 4); cout << Circ_1 = << circ_1.calcula() << endl; cout << Circ_2 = << circ_2.calcula() << endl; cout << Cil_1 << cil_1.calcula() << endl; // Asigna un cilindro a un circulo circ_1 = cil_1; cout << Circ_1 = << circ_1.calcula() << endl; return 0; Un objeto de una clase derivada puede ser asignado directamente a un objeto de la clase base En el ejemplo, se asigna un cilindro a un círculo Sólo los miembros públicos ó protegidos de la clase base del objeto son asignados Asignación inversa (clase base a clase derivada) requiere de un constructor Clases derivadas Clase Cilindro contiene la parte pública de Circulo Funciones miembro Otras variables y Funciones miembro funciones miembro Clase Circulo() Clase Cilindro() Polimorfismo El polimorfismo permite usar el mismo nombre de función para obtener una respuesta en los objetos de una clase base y otra en los objetos de una clase derivada Ejemplo: Función compara() bool compara(const Base& b1, const Base& b2) { return b1.nombre() < b2.nombre(); Polimorfismo compara(base& b1, Base& b2) Función compara() está definida para objetos Base Se puede aplicar esta función a objetos Postgrado? Objeto Postgrado no incluye función nombre() Objeto Postgrado incluye un Objeto Base! Este objeto base incluye una función nombre() Aplicar función compara() a objeto Postgrado aplica la función al objeto Base contenido en el objeto Postgrado nombre() Clase Base() compara(b1, p2) nombre() Otras variables y funciones miembro Clase Postgrado() 2015 Mario Medina C. 4
Polimorfismo Función compara() anterior recibe como argumentos referencias a objetos Base Postgrado p; Base b; compara(b, p); Función compara() recibe referencia al objeto Base contenido en el objeto Postgrado Función puede invocar a las funciones miembro de ese objeto Base Pero, no puede invocar a las funciones miembro de Postgrado que no están contenidas en Base Polimorfismo Problema ahora se presenta al pasar objetos como argumento y acceder a función nota() bool comparanotas(base b1, Base b2) { return b1.nota() < b2.nota(); Tipo de objetos conocidos en tiempo de compilación Postgrado p; Base b; compara(b, p); Llama a función Base::nota() de ambos objetos Función comparanotas() comparanotas(base& b1, Base& b2) Función compara notas en vez de nombres bool comparanotas(const Base& b1, const Base& b2) { return b1.nota() < b2.nota(); La función comparanotas() falla Invoca a función nota() de objetos Base Objeto Postgrado define su propia función nota(), que no es llamada Función nota() de Postgrado sobrecarga a función nota() de Base comparanotas(b1, p2) nota() Clase Base() nota() Otras variables nota() Clase Postgrado() Sobrecarga de funciones Funciones virtuales Una función de una clase derivada sobrecarga (overrides) una función de la clase base si ambas Tienen el mismo nombre Tienen el mismo tipo de argumentos Tienen el mismo número de argumentos Son (o no son) const Retornan el mismo tipo Excepción: pueden retornar punteros a sus respectivas clases virtual double nota() const; //... Se determina qué versión de nota() usar dependiendo del tipo de los objetos involucrados al tiempo de ejecución Función compara() aplicada a objetos Base y Postgrado invoca funciones Base::nota() y Postgrado::nota() 2015 Mario Medina C. 5
comparanotas(base& b1, Base& b2) Funciones virtuales comparanotas(b1, p2) virtual nota() Clase Base() virtual nota() Otras variables nota() Clase Postgrado() Una función virtual debe ser declarada como tal en la clase base puede estar definida en la clase base puede estar definida en las clases derivadas El compilador decide qué función invocar al momento de ejecutar el programa La calidad de virtual se hereda a las clases derivadas Enlace dinámico Clase Base Base b; Postgrado p; Base* p_b; Base& r; b.nota(); // Enlace estático a Base::nota() p.nota(); // Enlace estático a Postgrado::nota() p_b = &b; // Puntero apunta a objeto Base p_b->nota(); // Enlace dinámico, llama a Base::nota() r = b; // r es una referencia a objeto Base r.nota(); // Enlace dinámico, llama a Base::nota() p_b = &p; // Puntero apunta a objeto Postgrado p_b->nota(); // Enlace dinámico, llama a Postgrado::nota() r = p; // r es una referencia a objeto Postgrado r.nota(); // Enlace dinámico, llama a Postgrado::nota() // Define la interfaz Base(): examen1(0), examen2(0) { Base(std::istream& in) { leealumnos(in); std::string nombre() const; virtual std::istream& leealumnos(std::istream& in); virtual double nota() const; std::istream& leecomun(std::istream& in); double examen1, examen2; std::vector<double> tareas; private: // Define la implementacion std::string n; ; Clase Postgrado Usando la clase derivada class Postgrado: public Base { Postgrado() : extra(0) { Postgrado(std::istream& in) { leealumnos(in); // Estas funciones son virtuales por herencia double nota() const; std::istream& leealumnos(std::istream& in); private: double extra; ; // Funcion no-miembro bool compara(const Base&, const Base& in); Hemos creado la clase Postgrado como una versión de la clase Base Cómo usarlas para calcular las notas de los alumnos? Dos versiones del programa que calcula notas, una para cada tipo de alumnos Se muestran en las transparencias siguientes 2015 Mario Medina C. 6
Función main para Base (I) Función main para Base (II) vector<base> alumnos; Base alumno; string::size_type maxlen = 0; while(alumno.leealumnos(cin)) { maxlen = max(maxlen, alumno.nombre().size()); alumnos.push_back(alumno); // Ordena los alumnos sort(alumnos.begin(), alumnos.end(), compara); for(vector<base>::size_type i = 0; i!= alumnos.size(); ++i) { cout << alumnos[i].nombre() << string(maxlen + 1 alumnos[i].nombre().size(), ); try { double nota_final = alumnos[i].nota(); streamsize prec = cout.precision(); cout << fixed << setprecision(3) << nota_final << setprecision(prec) << endl; catch(domain_error e) { cout << e.what() << endl; return 0; Función main para Postgrado (I) Función main para Postgrado (II) vector<postgrado> alumnos; Postgrado alumno; string::size_type maxlen = 0; while(alumno.leealumnos(cin)) { maxlen = max(maxlen, alumno.nombre().size()); alumnos.push_back(alumno); // Ordena alumnos sort(alumnos.begin(), alumnos.end(), compara); for(vector<postgrado>::size_type i = 0; i!= alumnos.size(); ++i) { cout << alumnos[i].nombre() << string(maxlen + 1 alumnos[i].nombre().size(), ); try { double nota_final = alumnos[i].nota(); streamsize prec = cout.precision(); cout << fixed << setprecision(3) << nota_final << setprecision(prec) << endl; catch(domain_error e) { cout << e.what() << endl; return 0; Polimorfismo y enlace dinámico Polimorfismo y enlace dinámico Los 2 códigos anteriores usan enlace estático Los tipos de objetos son conocidos al tiempo de compilación Las funciones virtuales nombre() y notas() también son escogidas al tiempo de compilación Queremos tener una versión que maneja ambos casos a través de referencias a objetos En ese caso, es necesario usar referencias ó punteros a objetos Base en vez de objetos Reemplazar Base por Base* Pero, puntero Base* debe apuntar a algo vector<base*> alumnos; Base *alumno; while(alumno.leealumnos(cin)) { El puntero *alumno no apunta a nada! Necesario reservar memoria para objeto Pero, para un objeto Base o Postgrado? Supondremos archivo contiene B ó P 2015 Mario Medina C. 7
Función de comparación Necesitamos nueva función de comparación que compare 2 punteros a objetos Base Tiene que llamar a función compara() que compare lo apuntado por los punteros bool comparabaseptrs(const Base* pb1, const Base* pb2) { return compara(*pb1, *pb2); Función main (I) vector<base*> alumnos; Base* alumno; char ch; string::size_type maxlen = 0; while(cin >> ch) { if (ch = B ) alumno = new Base; else alumno = new Postgrado; alumno->leealumnos(cin); maxlen = max(maxlen, alumno->nombre().size()); alumnos.push_back(alumno); sort(alumnos.begin(), alumnos.end(),comparabaseptrs); Función main (II) Comentarios al código anterior for(vector<base*>::size_type i = 0; i!= alumnos.size(); ++i) { cout << alumnos[i]->nombre() << string(maxlen + 1 alumnos[i]->nombre().size(), ); try { double nota_final = alumnos[i]->nota(); streamsize prec = cout.precision(); cout << fixed << setprecision(3) << nota_final << setprecision(prec) << endl; catch(domain_error e) { cout << e.what() << endl; delete alumnos[i]; return 0; Código anterior supone que los datos de cada alumno tienen antepuesto B o P Se cambiaron los objetos por referencias a objetos Se solicitan objetos dinámicamente Se libera memoria de los objetos al final A través de un puntero a objeto Base Pero, qué pasa al destruir un objeto Postgrado? es virtuales Funciones virtuales Código mostrado libera sólo la memoria del objeto Base contenido en un objeto Postgrado debe ser virtual también virtual ~Base() const; //... ; Ahora delete llama al destructor correcto En el ejemplo, Base declara la función nota() como virtual y define una implementación Base::nota() Clase derivada Postgrado también define una implementación Postgrado::nota() Enlace dinámico decide cuál función invocar en tiempo de ejecución Es posible instanciar un objeto de clase Base 2015 Mario Medina C. 8
Clase abstracta Es una clase que sólo sirve como base para clases derivadas No es posible instanciar objetos de esta clase, sólo de sus clases derivadas Para hacer una clase abstracta, basta definir una función virtual igual a 0 Las clases derivadas se ven obligadas a definir la función No puede estar definida en la clase base Ejemplo: Clase abstracta Figura() // Clase abstracta Figura class Figura { // Interfaz // por omision Figura() { // Funcion de acceso string nombre() const { return n; // Funcion virtual abstracta // No se define en la clase base virtual double area() const = 0; string n; ; Clase derivada Triangulo() class Triangulo: public Figura { // Interfaz Triangulo() { // por omision n = "Triangulo"; Triangulo(double b, double h) { n = "Triangulo"; base = b; altura = h; double area() const { return base*altura/2.0; private: // Implementacion double base; double altura; ; Clase derivada Rectangulo() class Rectangulo: public Figura { // Interfaz Rectangulo() { // por omision n = "Rectangulo"; Rectangulo(double a, double b) { n = "Rectangulo"; ladoa = a; ladob = b; double area() const { return ladoa*ladob; // Implementacion double ladoa; double ladob; ; Clase derivada Cuadrado() class Cuadrado: public Rectangulo { // Implementacion Cuadrado() { // por omision n = "Cuadrado"; // que define un rectangulo de // lados iguales Cuadrado(double a): Rectangulo(a, a) { n = "Cuadrado"; // Clase Cuadrado hereda funcion area() de clase // base Rectangulo ; // Funcion de comparacion de figuras bool compara(figura& f1, Figura& f2) { return f1.area() < f2.area(); Herencia de funciones virtuales La calidad de virtual se hereda al siguiente nivel de clases derivadas Clase Base A Clase derivada B Clase derivada C virtual f1() f1() f1() Función f1() de B es virtual Función f1() de C es virtual Clase Base A Clase derivada B Clase derivada C virtual f1() f1() Si clase B no define función f1(), la función f1() de C no es virtual 2015 Mario Medina C. 9