capítulo 5. - ELAI-UPM

Anuncio
5P
OLIMORFISMO
Con este capítulo se tendrán las herramientas básicas para el desarrollo de un buen
programa en C++. Aún quedaría el uso de plantillas, pero estas pueden ser sustituidas en
muchos casos por el polimorfismo, y desde luego son más difíciles de utilizar por un
programador novel que los mecanismos hasta ahora explicados.
Antes de adentrarse en el concepto de polimorfismo, su utilidad y su casuística, es
necesario aclarar algún concepto en lo que se refiere a la superposición y la sobrecarga:
5.1 Superposición y sobrecarga
Tal y como se vió en el capítulo anterior en una clase derivada se puede definir una
función que ya existía en la clase base. Esto se conoce como "overriding", o superposición de
una función. La definición de la función en la clase derivada oculta la definición previa en la
clase base. En caso necesario, es posible acceder a la función oculta de la clase base mediante
su nombre completo:
<objeto>.<clase_base>::<método>;
110
APUNTES DE INFORMÁTICA INDUSTRIAL (POO)
Cuando se superpone una función, se ocultan todas las funciones con el mismo
nombre en la clase base. Supongamos que hemos sobrecargado la función de la clase base que
después volveremos a definir en la clase derivada:
#include <iostream.h>
class ClaseA
{
public:
void Incrementar() {
void Incrementar(int
};
class ClaseB : public
{
public:
void Incrementar() {
};
cout << "Suma 1" << endl; }
n) { cout << "Suma " << n << endl; }
ClaseA
cout << "Suma 2" << endl; }
int main()
{
ClaseB objeto;
objeto.Incrementar();
objeto.Incrementar(10); //¿Existe este método?
objeto.ClaseA::Incrementar();
objeto.ClaseA::Incrementar(10);
cin.get();
return 0;
}
Ahora bien, no es posible acceder a ninguna de las funciones superpuestas de la clase
base, aunque tengan distintos valores de retorno o distinto número o tipo de parámetros. Todas
las funciones "incrementar" de la clase base han quedado ocultas, y sólo son accesibles
mediante el nombre completo. Por ellom la línea marcada dará un error de compilación, puesto
que no existe la función incrementar definida en la claseB que reciba como argumento un tipo
de datos al que 10 sea convertible.
Tras corregir el error eliminando esta línea, la salida será:
Suma 2
Suma 1
Suma 10
5.2 Polimorfismo
Ha llegado el momento de introducir uno de los conceptos más importantes de la
programación orientada a objetos: el polimorfismo.
En lo que concierne a clases, el polimorfismo en C++, llega a su máxima expresión
cuando las usamos junto con punteros o con referencias. Como se ha visto, C++ nos permite
acceder a objetos de una clase derivada usando un puntero a la clase base. En eso consiste o se
U.P.M. E.L.A.I. Miguel Hernando. 2006
CAPÍTULO 5: POLIMORFISMO
111
basa el polimorfismo. Hata ahora sólo podemos acceder a datos y funciones que existan en la
clase base, los datos y funciones propias de los objetos de clases derivadas serán inaccesibles.
Esto es debido a que el compilador decide en tiempo de compilación que métodos y atributos
están disponibles en función del contenedor.
Para ilustrarlo vamos a ver un ejemplo sobre una estructura de clases basado en la
clase "Persona" y dos clases derivadas "Empleado" y "Estudiante":
#include <iostream.h>
#include <string.h>
class Persona
{
public:
Persona(char *n) { strcpy(nombre, n); }
void VerNombre() { cout << nombre << endl; }
protected:
char nombre[30];
};
class Empleado : public Persona
{
public:
Empleado(char *n) : Persona(n) {}
void VerNombre()
{
cout << "Emp: " << nombre << endl;
}
};
class Estudiante : public Persona
{
public:
Estudiante(char *n) : Persona(n) {}
void VerNombre()
{
cout << "Est: " << nombre << endl;
}
};
void main() {
Persona *Pepito = new Estudiante("Jose");
Persona *Carlos = new Empleado("Carlos");
Carlos->VerNombre();
Pepito->VerNombre();
delete Pepito;
delete Carlos;
}
Por simplificar el código, se ha utilizado memoria estática en vez de dinámica.
Evidentemente este es un error funcional grave puesto que el usuario de la clase puede
provocar un fallo en la ejecución haciendo un uso correcto de los métodos de interfaz. En
U.P.M. E.L.A.I. Miguel Hernando. 2006
112
APUNTES DE INFORMÁTICA INDUSTRIAL (POO)
cualquier caso, suponiendo que tenemos cuidado, la salida del programa, como es previsible
será la siguiente:
Carlos
Jose
Podemos comprobar que se ejecuta la versión de la función "VerNombre" que hemos
definido para la clase base, y no la de las clases derivadas. Esto es debido a que la función que
se ejecuta se resuelve en tiempo de EJECUCION atendiendo no al tipo de objeto apuntado,
sino al tipo del apuntador.
Por eso, si escribimos:
Estudiante *Pepito=new Estudiante(“Jose”);
Empleado *Carlos=new Empleado(“Carlos”);
Entonces si que se ejecutará el método VerNombre superpuesto.
Esto mismo sucede con las referencias. Las siguientes líneas de código son totalmente
válidas, siendo el efecto análogo al obtenido por medio de punteros:
Estudiante Jose(“Jose”);
Persona &pJose=Jose;
pJose.VerNombre();
Es decir, en este caso, atendiendo al recipiente y no a lo apuntado, se utilizará de
nuevo el método VerNombre de la clase Persona, a pesar de que realmente pJose es un alias de
un objeto de tipo Estudiante. Ya observamos que un objeto se comporta de distinta forma en
función de con que se lo referencie o apunte.
Sin embargo, parece interesante que cada objeto se comporte como debe
independientemente del recipiente, es decir, independientemente de con que se referencie o
apunte… esto nos lleva al concepto de polimorfismo de la mano de los denominados métodos
virtuales.
5.2.1 Métodos virtuales
Un método virtual es un método de una clase base que puede ser redefinido en cada
una de las clases derivadas de esta, y que una vez redefinido puede ser accedido por medio de
un puntero o una referencia a la clase base, resolviéndose entonces la llamada en función del
objeto referido en vez de en función de con qué se hace la referencia.
Que viene a significar que si en una clase base definimos un método como virtual, si
este método es superpuesto por una clase derivada, al invocarlo utilizando un puntero o una
referencia de la clase base, ¡se ejecutará el método de la clase derivada!.
Cuando una clase tiene algún método virtual –bien directamente, bien por herencia- se
dice que dicha clase es polimórfica.
Para declarar un método como virtual se utiliza la siguiente sintaxis:
virtual <tipo> <nombre_función>(<lista_parámetros>) [{}];
U.P.M. E.L.A.I. Miguel Hernando. 2006
CAPÍTULO 5: POLIMORFISMO
113
Como siempre, la mejor forma de entender los conceptos en programación es ver
ejemplos de código con su resultado.
Modifiquemos en el ejemplo anterior la declaración de la clase base "Persona",
haciendo que el método VerNombre sea virtual:
class Persona
{
public:
Persona(char *n) { strcpy(nombre, n); }
virtual void VerNombre() {cout << nombre << endl;}
protected:
char nombre[30];
};
Ahora ejecutemos el programa de nuevo. Observamos entonces que la salida es
diferente:
Emp: Carlos
Est: Jose
Ahora, al llamar a Pepito->VerNombre() se invoca a la función VerNombre de la clase
Estudiante, y al llamar a Carlos->VerNombre() se invoca a la función de la clase Empleado, a
pesar de que tanto Pepito como Carlos son punteros a la clase Persona.
De igual forma ocurrirá si en vez de utilizar punteros, hacemos uso de referencias:
void main()
{
Estudiante Pepito("Jose");
Empleado Carlos("Carlos");
Persona &rPepito = Pepito; // Referencia como Persona
Persona &rCarlos = Carlos; // Referencia como Persona
rCarlos.VerNombre(); //cada objeto ejecuta su método
rPepito.VerNombre(); //en vez de utilizar el de Persona
}
Por tanto, la idea central del polimorfismo es la de poder llamar a funciones distintas
aunque tengan el mismo nombre, según la clase a la que pertenece el objeto al que se aplican.
Esto es imposible utilizando nombres de objetos: siempre se aplica la función miembro de la
clase correspondiente al nombre del objeto, y esto se decide en tiempo de compilación.
Sin embargo, utilizando punteros puede conseguirse el objetivo buscado. Recuérdese
que un puntero a la clase base puede contener direcciones de objetos de cualquiera de las clases
derivadas.
Antes de ahondar y ver más ejemplos de métodos virtuales se van a establecer algunas
características del mecanismo de virtualidad:
U.P.M. E.L.A.I. Miguel Hernando. 2006
114
APUNTES DE INFORMÁTICA INDUSTRIAL (POO)
♦
Una vez que una función es declarada como virtual, lo seguirá siendo en las
clases derivadas, es decir, la propiedad virtual se hereda.
♦
Si la función virtual no se define exactamente con el mismo tipo de valor de
retorno y el mismo número y tipo de parámetros que en la clase base, no se
considerará como la misma función, sino como una función superpuesta.
♦
El nivel de acceso no afecta a la virtualidad de las funciones. Es decir, una
función virtual puede declararse como privada en las clases derivadas aun siendo
pública en la clase base, pudiendo por tanto ejecutarse ese método privado desde
fuera por medio de un puntero a la clase base.
♦
Una llamada a un método virtual se resuelve siempre en función del tipo del
objeto referenciado.
♦
Una llamada a un método normal se resuelve siempre en función del tipo de la
referencia o puntero utilizado.
♦
Una llamada a un método virtual especificando la clase, exige la utilización del
operador de resolución de ámbito ::, lo que suprime el mecanismo de ritualidad.
Evidentemente este mecanismo solo podrá utilizarse para el método de la misma
clase del contenedor o de clases base del mismo.
♦
Por su modo de funcionamiento interno (es decir, por el modo en que realmente
trabaja el ordenador) las funciones virtuales son un poco menos eficientes que las
funciones normales.
Vamos a ver algún ejemplo adicional que ayude a entender el polimorfismo y las
clases polimórficas.
#include <iostream.h>
class Base
{
public:
virtual void ident(){cout<<"Base"<<endl;}
};
class Derivada1:public Base
{
public:
void ident(){cout<<"Primera derivada"<<endl;}
};
class Derivada2:public Base
{
public:
void ident(){cout<<"Segunda derivada"<<endl;}
};
class Derivada3:public Base
{
public:
void ident(){cout<<"Tercera derivada"<<endl;}
};
void main()
U.P.M. E.L.A.I. Miguel Hernando. 2006
CAPÍTULO 5: POLIMORFISMO
115
{
Base base,*pbase;
Derivada1 primera;
Derivada2 segunda;
Derivada3 tercera;
pbase=&base;
pbase->ident();
pbase=&primera;
pbase->ident();
pbase=&segunda;
pbase->ident();
pbase=&tercera;
pbase->ident();
}
Evidentemente, el resultado de ejecutar este código es el siguiente:
Base
Primera derivada
Segunda derivada
Tercera derivada
De nuevo, al ser el método virtual se ejecuta la función del objeto apuntado,
independientemente de que se apunte con un puntero de tipo Base.
Si escribiéramos lo siguiente en el cuerpo del main:
pbase=&base;
pbase->Base::ident();
pbase=&primera;
pbase->Base::ident();
pbase=&segunda;
pbase->Base::ident();
pbase=&tercera;
pbase->Base::ident();
Entonces el resultado de la ejecución sería el siguiente:
Base
Base
Base
Base
Ahora lo que haremos será poner la modificación de virtualidad en la clase Derivada2,
y lo quitamos de la clase Base. En este caso el resultado de la ejecución será de nuevo cuatro
veces Base. Esto es debido a que desde el punto de vista del puntero que contenedor (de tipo
Base) la función ya no es virtual y por tanto se decide en tiempo de compilación como
cualquier método normal. Para poder acceder al método definido por las clases derivadas al
menos será necesario que utilicemos un puntero de tipo Derivada2 para aquellas clases que son
polimórficas (en este caso sólo lo sería Derivada3).
U.P.M. E.L.A.I. Miguel Hernando. 2006
116
APUNTES DE INFORMÁTICA INDUSTRIAL (POO)
Por último, si volvemos al programa original, y establecemos que el método ident es
privado en la clase Derivada2, se observa que no afecta para nada al funcionamiento de nuestro
programa.
El polimorfismo hace posible que un usuario pueda añadir nuevas clases a una
jerarquía sin modificar o recompilar el código original. Esto quiere decir que si desea añadir
una nueva clase derivada es suficiente con establecer la clase de la que deriva, definir sus
nuevas variables y funciones miembro, y compilar esta parte del código, ensamblándolo
después con lo que ya estaba compilado previamente.
El siguiente ejemplo es un poco más largo y complicado respecto de los anteriores,
pero es más elocuente ante las posibilidades que nos ofrece el polimorfismo:
#include <iostream.h>
class Vehiculo
{
public:
virtual void muestra(ostream &co){}
virtual void rellena(){}
friend ostream& operator <<(ostream &co,Vehiculo &ve)
{
ve.muestra(co);
return co;
}
};
class Coche:public Vehiculo
{
protected:
char marca[20];
char modelo[20];
public:
void muestra(ostream &co)
{
co<<"Coche marca "<<marca<<" modelo "<<modelo<<endl;
}
void rellena()
{
cout<<"¿Marca?:";
cin>>marca;
cout<<"¿Modelo?:";
cin>>modelo;
cin.clear();
}
};
class Camion:public Coche
{
private:
int carga;
public:
void muestra(ostream &co)
{
U.P.M. E.L.A.I. Miguel Hernando. 2006
CAPÍTULO 5: POLIMORFISMO
117
co<<"Camion marca "<<marca<<" modelo "<<modelo<<endl;
co<<"\tCapacidad de carga: "<<carga<< "Kg."<<endl;
}
void rellena()
{
Coche::rellena();
cout<<"¿Carga máxima?:";
cin>>carga;
cin.clear();
}
};
void main()
{
Vehiculo *flota[4];
int seleccion=0;
for(int i=0;i<3;i++)
{
cout<<"Seleccione tipo del vehiculo "<<i<<":\n";
cout<<"(1-Camión, 2-Coche): ";
while((seleccion<1)||(seleccion>2))
cin>>seleccion;
switch(seleccion)
{
case 1: flota[i]=new Camion;break;
case 2: flota[i]=new Coche;break;
}
seleccion=0;
flota[i]->rellena();
}
cout<<"\n Estos son los vehiculos introducidos:\n";
for(i=0;i<3;i++)cout<<i<<":"<<*flota[i];
}
Un ejemplo de ejecución de este programa es el siguiente:
Seleccione tipo del vehiculo 0:
(1-Camión, 2-Coche): 1
¿Marca?:SCANIA
¿Modelo?:SuperTruck
¿Carga máxima?:22000
Seleccione tipo del vehiculo 1:
(1-Camión, 2-Coche): 2
¿Marca?:Seat
¿Modelo?:Ibiza
Seleccione tipo del vehiculo 2:
U.P.M. E.L.A.I. Miguel Hernando. 2006
118
APUNTES DE INFORMÁTICA INDUSTRIAL (POO)
(1-Camión, 2-Coche): 2
¿Marca?:Renault
¿Modelo?:Twingo
Estos son los vehiculos introducidos:
0:Camion marca SCANIA modelo SuperTruck
Capacidad de carga: 22000Kg.
1:Coche marca Seat modelo Ibiza
2:Coche marca Renault modelo Twingo
Se observa entonces como es posible almacenar y tratar los objetos como iguales
atendiendo a que heredan de una misma clase base, y sin embargo, estos mismos objetos son
capaces de realizar acciones distintas o especializadas cuando se ejecutan sus métodos
virtuales. Esto es la potencia del polimorfismo, y tal vez gracias al mismo alguno comienze a
vislumbrar el porque se parecen tanto y a la vez son distintos los programas que se manejan
sobre Windows.
5.2.2 Implementación del mecanismo de virtualidad
A continuación se explica, sin entrar en gran detalle, el funcionamiento de las
funciones virtuales. Cada clase que utiliza funciones virtuales tiene un vector de punteros, uno
por cada función virtual, llamado v-table. Cada uno de los punteros contenidos en ese vector
apunta a la función virtual apropiada para esa clase, que será, habitualmente, la función virtual
definida en la propia clase. En el caso de que en esa clase no esté definida la función virtual en
cuestión, el puntero de v-table apuntará a la función virtual de su clase base más próxima en la
jerarquía, que tenga una definición propia de la función virtual. Esto quiere decir que buscará
primero en la propia clase, luego en la clase anterior en el orden jerárquico y se irá subiendo en
ese orden hasta dar con una clase que tenga definida la función buscada.
Cada objeto creado de una clase que tenga una función virtual contiene un puntero
oculto a la v-table de su clase. Mediante ese puntero accede a su v-table correspondiente y a
través de esta tabla accede a la definición adecuada de la función virtual. Es este trabajo extra
el que hace que las funciones virtuales sean menos eficientes que las funciones normales.
5.3 Virtualidad en destructores y constructores.
Supongamos que tenemos una estructura de clases en la que en alguna de las clases
derivadas exista un destructor. Un destructor es una función como las demás, por lo tanto, si
destruimos un objeto referenciado mediante un puntero a la clase base, y el destructor no es
virtual, estaremos llamando al destructor de la clase base. Esto puede ser desastroso, ya que
nuestra clase derivada puede tener más tareas que realizar en su destructor que la clase base de
la que procede. Si no posiblemente sería necesario definirlo.
U.P.M. E.L.A.I. Miguel Hernando. 2006
CAPÍTULO 5: POLIMORFISMO
119
Como norma general, el constructor de la clase base se llama antes que el constructor
de la clase derivada. Con los destructores, sin embargo, sucede al revés: el destructor de la
clase derivada se llama antes que el de la clase base.
Por esa razón, en el caso de que se borre, aplicando delete, un puntero a un objeto de
la clase base que apunte a un objeto de una clase derivada, se llamará al destructor de la clase
base, en vez de al destructor de la clase derivada, que sería lo adecuado. La solución a este
problema consiste en declarar como virtual el destructor de la clase base. Esto hace que
automáticamente los destructores de las clases derivadas sean también virtuales, a pesar de
tener nombres distintos. De este modo, al aplicar delete a un puntero de la clase base que puede
apuntar a un objeto de ese tipo o a cualquier objeto de una clase derivada, se aplica el
destructor adecuado en cada caso.
Este problema no se presenta con los constructores y por eso no existe ningún tipo de
constructor virtual o similar. Por eso los constructores no pueden ser virtuales. Esto puede ser
un problema en ciertas ocasiones. Por ejemplo, el constructor de copia no hará siempre aquello
que esperamos que haga. En general no debemos usar el constructor copia cuando usemos
punteros a clases base. Para solucionar este inconveniente se suele crear una función virtual
"clonar" en la clase base que se superpondrá para cada clase derivada.
Por ejemplo:
#include <iostream>
#include <cstring>
using namespace std;
class Persona {
public:
Persona(char *n) { strcpy(nombre, n); }
Persona(const Persona &p);
virtual void VerNombre() {
cout << nombre << endl;
}
virtual Persona* Clonar() { return new Persona(*this); }
protected:
char nombre[30];
};
Persona::Persona(const Persona &p) {
strcpy(nombre, p.nombre);
cout << "Per: constructor copia." << endl;
}
class Empleado : public Persona {
public:
Empleado(char *n) : Persona(n) {}
Empleado(const Empleado &e);
void VerNombre() {
cout << "Emp: " << nombre << endl;
}
virtual Persona* Clonar() { return new Empleado(*this); }
};
Empleado::Empleado(const Empleado &e) : Persona(e) {
cout << "Emp: constructor copia." << endl;
}
U.P.M. E.L.A.I. Miguel Hernando. 2006
120
APUNTES DE INFORMÁTICA INDUSTRIAL (POO)
class Estudiante : public Persona {
public:
Estudiante(char *n) : Persona(n) {}
Estudiante(const Estudiante &e);
void VerNombre() {
cout << "Est: " << nombre << endl;
}
virtual Persona* Clonar() {
return new Estudiante(*this);
}
};
Estudiante::Estudiante(const Estudiante &e) : Persona(e) {
cout << "Est: constructor copia." << endl;
}
int main() {
Persona *Pepito = new Estudiante("Jose");
Persona *Carlos = new Empleado("Carlos");
Persona *Gente[2];
Carlos->VerNombre();
Pepito->VerNombre();
Gente[0] = Carlos->Clonar();
Gente[0]->VerNombre();
Gente[1] = Pepito->Clonar();
Gente[1]->VerNombre();
delete Pepito;
delete Carlos;
delete Gente[0];
delete Gente[1];
cin.get();
return 0;
}
Hemos definido el constructor copia para que se pueda ver cuando es invocado. La
salida es ésta:
Emp:
Est:
Per:
Emp:
Emp:
Per:
Est:
Est:
Carlos
Jose
constructor
constructor
Carlos
constructor
constructor
Jose
copia.
copia.
copia.
copia.
Este método asegura que siempre se llama al constructor copia adecuado, ya que se
hace desde una función virtual.
Si un constructor llama a una función virtual, ésta será siempre la de la clase base.
Esto es debido a que el objeto de la clase derivada aún no ha sido creada.
U.P.M. E.L.A.I. Miguel Hernando. 2006
CAPÍTULO 5: POLIMORFISMO
121
5.4 Funciones virtuales puras y clases abstractas
Habitualmente las funciones virtuales de la clase base de la jerarquía no se utilizan
porque en la mayoría de los casos no se declaran objetos de esa clase, y/o porque todas las
clases derivadas tienen su propia definición de la función virtual. Sin embargo, incluso en el
caso de que la función virtual de la clase base no vaya a ser utilizada, debe declararse.
De todos modos, si la función no va a ser utilizada no es necesario definirla, y es
suficiente con declararla como función virtual pura. Una función virtual pura se declara así:
virtual <tipo> <nombre_función>(<lista_parámetros>) = 0;
La única utilidad de esta declaración es la de posibilitar la definición de funciones
virtuales en las clases derivadas. De alguna manera se puede decir que la definición de una
función como virtual pura hace necesaria la definición de esa función en las clases derivadas, a
la vez que imposibilita su utilización con objetos de la clase base.
Al definir una función como virtual pura hay que tener en cuenta que:
♦
No hace falta definir el código de esa función en la clase base.
♦
No se pueden definir objetos de la clase base, ya que no se puede llamar a las
funciones virtuales puras.
♦
Sin embargo, es posible definir punteros a la clase base, pues es a través de ellos
como será posible manejar objetos de las clases derivadas.
Se denomina clase abstracta a aquella que contiene una o más funciones virtuales
puras. El nombre proviene de que no puede existir ningún objeto de esa clase. Si una clase
derivada no redefine una función virtual pura, la clase derivada la hereda como función virtual
pura y se convierte también en clase abstracta. Por el contrario, aquellas clases derivadas que
redefinen todas las funciones virtuales puras de sus clases base reciben el nombre de clases
derivadas concretas, nomenclatura únicamente utilizada para diferenciarlas de las antes
mencionadas.
Aparentemente puede parecer que carece de sentido definir una clase de la que no va a
existir ningún objeto, pero se puede afirmar, sin miedo a equivocarse, que la abstracción es una
herramienta imprescindible para un correcto diseño de la Programación Orientada a Objetos.
Habitualmente las clases superiores de muchas jerarquías de clases son clases
abstractas y las clases que heredan de ellas definen sus propias funciones virtuales,
convirtiéndose así en funciones concretas.
No es posible crear objetos de una clase abstracta, estas clases sólo se usan como
clases base para la declaración de clases derivadas.
Las funciones virtuales puras serán aquellas que siempre se definirán en las clases
derivadas, de modo que no será necesario definirlas en la clase base.
U.P.M. E.L.A.I. Miguel Hernando. 2006
122
APUNTES DE INFORMÁTICA INDUSTRIAL (POO)
A menudo se mencionan las clases abstractas como tipos de datos abstractos, en
inglés: Abstract Data Type, o resumido ADT.
Como consecuencia de lo dicho se pueden resumir dos reglas a tener en cuenta cuando
se utilizan clases abstractas:
♦
No está permitido crear objetos de una clase abstracta.
♦
Siempre hay que definir todas las funciones virtuales de una clase abstracta en sus
clases derivadas, no hacerlo así implica que la nueva clase derivada será también
abstracta.
Para crear un ejemplo de clases abstractas, recurriremos de nuevo a nuestra clase
"Persona". Haremos que ésta clase sea abstracta. De hecho, en nuestros programas de ejemplo
nunca hemos declarado un objeto "Persona".
Veamos un ejemplo:
#include <iostream>
#include <cstring>
using namespace std;
class Persona {
public:
Persona(char *n) { strcpy(nombre, n); }
virtual void Mostrar() = 0;
protected:
char nombre[30];
};
class Empleado : public Persona {
public:
Empleado(char *n, int s) : Persona(n), salario(s) {}
void Mostrar() const;
int LeeSalario() const { return salario; }
void ModificaSalario(int s) { salario = s; }
protected:
int salario;
};
void Empleado::Mostrar() const {
cout << "Empleado: " << nombre
<< ", Salario: " << salario
<< endl;
}
class Estudiante : public Persona {
public:
Estudiante(char *n, float no) : Persona(n), nota(no) {}
void Mostrar() const;
float LeeNota() const { return nota; }
void ModificaNota(float no) { nota = no; }
protected:
float nota;
};
void Estudiante::Mostrar() {
cout << "Estudiante: " << nombre
<< ", Nota: " << nota << endl;
}
U.P.M. E.L.A.I. Miguel Hernando. 2006
CAPÍTULO 5: POLIMORFISMO
123
int main() {
Persona *Pepito = new Empleado("Jose", 1000); (1)
Persona *Pablito = new Estudiante("Pablo", 7.56);
char n[30];
Pepito->Mostrar();
Pablito->Mostrar();
cin.get();
return 0;
}
La salida será así:
Empleado: Jose, Salario: 1000
Estudiante: Pablo, Nota: 7.56
En este ejemplo combinamos el uso de funciones virtuales puras con polimorfismo.
Fíjate que, aunque hayamos declarado los objetos "Pepito" y "Pablito" de tipo puntero a
"Persona" (1), en realidad no creamos objetos de ese tipo, sino de los tipos "Empleado" y
"Estudiante".
5.5 Ejemplos
Como ejemplo se puede suponer que la cuenta_joven y la cuenta_empresarial ya
utilizadas y descritas anteriormente, con una forma distinta de abonar mensualmente el interés
al saldo.
límite.
• En la cuenta_joven, no se abonará el interés pactado si el saldo es inferior a un
• En la cuenta_empresarial se tienen tres cantidades límite, a las cuales se aplican
factores de corrección en el cálculo del interés. El cálculo de la cantidad abonada debe
realizarse de la siguiente forma:
1. Si el saldo es menor que 50000, se aplica el interés establecido previamente.
2. Si el saldo está entre 50000 y 500.000, se aplica 1.1 veces el interés establecido
previamente.
3. Si el saldo es mayor a 500.000, se aplica 1.5 veces el interés establecido
previamente.
El código correspondiente quedaría de la siguiente forma:
class C_Cuenta {
// Variables miembro
private:
double Saldo; // Saldo Actual de la cuenta
U.P.M. E.L.A.I. Miguel Hernando. 2006
124
APUNTES DE INFORMÁTICA INDUSTRIAL (POO)
double Interes; // Interés calculado hasta el momento,
anual,
// en tanto por ciento %
public:
//Constructor
C_Cuenta(double unSaldo=0.0, double unInteres=4.0)
{
SetSaldo(unSaldo);
SetInteres(unInteres);
}
// Acciones Básicas
inline double GetSaldo()
{ return Saldo; }
inline double GetInteres()
{ return Interes; }
inline void SetSaldo(double unSaldo)
{ Saldo = unSaldo; }
inline void SetInteres(double unInteres)
{ Interes = unInteres; }
void Ingreso(double unaCantidad)
{ SetSaldo( GetSaldo() + unaCantidad ); }
virtual void AbonaInteresMensual()
{
SetSaldo( GetSaldo() * ( 1.0 + GetInteres() / 12.0 /
100.0) );
}
// etc...
};
class C_CuentaJoven : public C_Cuenta
{
public:
C_CuentaJoven(double unSaldo=0.0, double unInteres=2.0,
double unLimite = 50.0E3) :C_Cuenta(unSaldo, unInteres)
{
Limite = unLimite;
}
virtual void AbonaInteresMensual()
{
if (GetSaldo() > Limite)
SetSaldo( GetSaldo() * (1.0 + GetInteres() / 12.0 /
100.0) );
else
SetSaldo( GetSaldo() );
}
private:
double Limite;
};
class C_CuentaEmpresarial : public C_Cuenta {
public:
C_CuentaEmpresarial(double unSaldo=0.0, double unInteres=4.0)
: C_Cuenta(unSaldo, unInteres)
U.P.M. E.L.A.I. Miguel Hernando. 2006
CAPÍTULO 5: POLIMORFISMO
{
CantMin[0] = 50.0e3;
CantMin[1] = 500.0e3;
}
virtual void AbonaInteresMensual()
{
SetSaldo( GetSaldo() * (1.0 + GetInteres() *
CalculaFactor() /
12.0 / 100.0 ));
}
double CalculaFactor()
{
if (GetSaldo() < CantMin[0])
return 1.0;
else if (GetSaldo() < CantMin[1])
return 1.1;
else return 1.5;
}
private:
double CantMin[2];
};
U.P.M. E.L.A.I. Miguel Hernando. 2006
125
126
APUNTES DE INFORMÁTICA INDUSTRIAL (POO)
U.P.M. E.L.A.I. Miguel Hernando. 2006
Descargar