Date post: | 14-Nov-2015 |
Category: |
Documents |
Upload: | manuel-felipe-duarte |
View: | 223 times |
Download: | 0 times |
Herencia
2 2.1. INTRODUCCIN
La herencia es la caracterstica fundamental que distingue un lenguaje orientado
a objetos, como el C++, de otro convencional como C, BASIC, etc. C++ permite
heredar las clases caractersticas y conductas de una o varias clases denominadas base.
Las clases que heredan de clases base se denominan derivadas. Estas a su vez pueden
ser clases bases para otras clases derivadas. Se establece as una clasificacin jerrquica,
similar a la clasificacin existente en Biologa con los animales y las plantas.
La herencia es el concepto que permite a los programadores utilizar de nuevo y
extender el cdigo existente. La herencia mejora la confianza en los programas y
disminuye el tiempo de desarrollo de los mismos. Los programadores crean clases base:
Cuando se dan cuenta de que diversos tipos tienen algo en comn, por ejemplo, en el juego del ajedrez, peones, alfiles, rey, reina, caballos y torres son piezas del
juego. Creamos, por tanto, una clase base y derivamos cada pieza individual a
partir de dicha clase base.
Cuando se precisa ampliar la funcionalidad de un programa sin tener que modificar el cdigo existente.
2.2. HERENCIA SIMPLE
Hemos estudiado la clase Punto. Vamos ahora a definir una clase que
denominaremos PuntoMejorado a partir de Punto, de manera que ample su
funcionalidad para poder, por ejemplo, desplazar el punto horizontal y verticalmente, y
para que las funciones mostrar y ocultar muestren el punto y las coordenadas del punto
en la esquina superior izquierda de la pantalla. La relacin jerrquica existente entre las
dos clases se muestra en la Figura 2. 1.
20
2.2.1. Clase base
En la declaracin de la clase base Punto tenemos una nueva palabra protected,
que significa que las funciones miembro de la clase base y de la clase derivada tienen
acceso a los miembros dato, pero son prvate (privados) respecto a las funciones
miembro de otras clases no relacionadas.
class Punto{
protected:
int x;
int y;
char ch;
public:
Punto (int x1, int y1);
void mostrar();
void ocultar ();
int getx() {return x;}
int gety(){return y;}
~Punto(){cout
Punto::Punto (int x1, int yl)
{
ch=*;
x=xl;
y=yl;
}
void Punto::mostrar()
{
gotoxy(x,y);
cout
class PuntoMejorado: public Punto{
public:
PuntoMejorado (int xl, , int yl);
void mover (int dx, int dy);
void mostrar ();
void ocultar();
~ PuntoMejorado(){cout
en la clase PuntoMejorado hace, en primer lugar, una llamada a la funcin mostrar
definida en la clase base Punto, y luego se le aade una conducta particular: esto es, que
muestre en la esquina superior izquierda de la ventana la abscisa x y la ordenada y del
punto.
void PuntoMejorado::mostrar()
{
Punto::mostrar();
gotoxy(1,1);
cout
2.2.6. Llamada a las funciones que se heredan
La clase PuntoMejorado hereda los datos y las funciones de la clase base Punto.
As podemos hacer las siguientes llamadas desde un objeto pm de la clase
PuntoMejorado.
pm.mover(-5, 5); //funcin miembro de la clase derivada
pm.getx(); //funcin miembro de la clase base
Pero cuando hacemos la llamada,
pm.mostrar()
a cul de las dos funciones mostrar se llama? Como podemos comprobar, llama
a la funcin mostrar redefinida en la clase PuntoMejorado. La funcin mostrar de la
clase derivada oculta a la funcin mostrar de la clase base. Por lo que la funcin
mostrar redefinida en la clase derivada est ligada a un objeto de la clase derivada. An
as, podemos seguir llamando a la funcin mostrar de la clase base Punto desde un
objeto de la clase derivada, utilizando para ello el operador resolucin de mbito ::.
pm.Punto::mostrar();
2.3. POLIMORFISMO
Polimorfismo es una palabra griega que significa muchas formas. En el lenguaje
habitual usamos una misma palabra cuyo significado difiere segn sea el contexto. El
polimorfismo describe una de las facetas ms interesantes y oscuras para los
principiantes del lenguaje C++: el distinto comportamiento del programa dependiendo
de la situacin en tiempo de ejecucin de dicho programa.
El polimorfismo se implementa por medio de las denominadas funciones
virtuales. Dichas funciones permiten a las clases derivadas proporcionar diferentes
versiones de la funcin definida en la clase base. En la clase derivada se declara y
define una funcin que tiene el mismo nombre, el mismo nmero de argumentos y del
mismo tipo que en la clase base, pero que da lugar a un comportamiento distinto,
especfico del objeto perteneciente a dicha clase derivada.
26
2.3.1. La jerarqua de clases que describen las figuras planas
Consideremos las figuras planas cerradas, como el rectngulo, la elipse, la
circunferencia o el cuadrado. Tales figuras comparten caractersticas comunes como es
la posicin de la figura, de su centro y el rea de la figura, aunque el procedimiento para
calcular dicha rea sea completamente distinto. Podemos, por tanto, disear una
jerarqua de clases, tal que la clase base denominada Figura tenga las caractersticas
comunes y cada clase derivada, las especficas. Por otra parte, el crculo es un caso
particular de la elipse (una elipse especializada), y el cuadrado es un caso especial del
rectngulo. La relacin jerrquica se muestra en la Figura 2.2.
La clase Figura es la que contiene las caractersticas comunes a dichas figuras
concretas, por tanto no tiene forma ni tiene rea. Esto lo expresamos declarando Figura
como una clase abstracta, declarando y definiendo la funcin miembro rea virtual
pura.
class Figura{
//...
virtual float rea(void)=0;
}
La igualdad a cero es lo que hace que la funcin virtual sea pura. Las clases
abstractas solamente se pueden usar como clases base para otras clases. No se pueden
crear objetos pertenecientes a una clase abstracta. Sin embargo, se pueden declarar
punteros a las clases abstractas y referencias a dichas clases. En el juego del ajedrez
podemos definir una clase base denominada Pieza, con las caractersticas comunes a
todas las piezas, como es su posicin en el tablero, y derivar de ella las caractersticas
especficas de cada pieza particular. As pues, la clase Pieza ser una clase abstracta con
una funcin virtual pura denominada mover, y cada tipo de pieza redefinir dicha
funcin de acuerdo a las reglas del movimiento sobre el tablero.
g La clase Figura
La clase abstracta Figura, tiene corno miembros la posicin x e y de la figura
particular, de su centro, y la funcin rea, que se va a redefinir en las clases derivadas
para calcular el rea de cada figura en particular.
27
Rectngulo
Crculo
Elipse
Cuadrado
Figura
Figura 2.2. rbol jerrquico de las figuras planas regulares.
class Figura{
protected:
int x;
int y;
public:
Figura(int _x, int _y);
virtual ~Figura(){cout
g La clase Rectngulo
Las clases derivadas heredan los miembros dato x e y de la clase base, y
redefinen la funcin rea, declarada virtual en la clase base Figura, ya que cada figura
particular tiene una frmula distinta para calcular su rea. Por ejemplo, la clase derivada
Rectngulo tiene como datos, aparte de su posicin (x, y) en el plano, sus dimensiones,
es decir, su anchura Lx y altura Ly.
class Rectangulo:public Figura{
protected:
int Lx, Ly;
public:
Rectangulo(int _x, int _y, int _Lx, int _Ly);
~Rectangulo(){cout
g La clase Cuadrado
La clase Cuadrado es una clase especializada de Rectngulo, ya que un
cuadrado tiene los lados iguales.
class Cuadrado:public Rectangulo{
public:
Cuadrado(int _x, int _y, int l);
~Cuadrado(){cout
Elipse::Elipse(int _x, int _y, float rl, float r2)
:Figura(_x, _y)
{
rMayor = rl; rMenor = r2;
}
En la redefinicin de la funcin arca, se calcula el rea de la elipse como
producto del semieje menor por el sernieje mayor por la constante Pi.
float Elipse::area(void)
{
const float PI = 3.1416;
return PI*rMayor*rMenor;
}
g La clase Circulo
La clase Circulo es una clase especializada de Elipse, ya que una circunferencia
slo tiene un radio.
class Circulo:public Elipse{
public:
Circulo(int _x, int _y, float r);
~Circulo(){cout
2.3.2. Uso de la jerarqua de clases
Ya hemos visto en el captulo precedente que cuando creamos un objeto elipse
de la clase Elipse, y llamamos desde l a la funcin rea, se llamar a la funcin rea
declarada en la clase Elipse.
Elipse elipse(120, 80, 5.0, 7.2);
cout
enlace tardo para una funcin declarndola virtual. Para comprobar, se crea un array
de punteros a objetos de la clase base Figura, y se guardan en sus elementos las
direcciones de objetos de las clases derivadas.
const int N=4;
Figura** figura=new Figura* [N];
figura[0]=new Elipse(120, 80, 5.0, 7.0);
figura[l]=new Circulo(300, 80, 5.0);
figura[2]=new Rectangulo(150, 300, 5.0, 7. 0);
figura[3]=new Cuadrado(300, 300, 7.0);
La sentencia:
ptro_Fig[i]area();
a qu funcin arca llamar? La respuesta ser segn sea el ndice i. Si i es cero,
el primer elemento del array contiene la direccin de un objeto de la clase Elipse, y
luego llamar a area de Elipse. Si i es uno, el segundo elemento del array guarda la
direccin de un objeto de la clase Circulo, luego llamar a area de Circulo, y as
sucesivamente. Pero si el valor del ndice i se introduce en tiempo de ejecucin (run
time):
cin>>i;
luego la decisin sobre qu funcin area se va a llamar se retrasa hasta el tiempo de
ejecucin, tal como se muestra en la siguiente porcin de cdigo.
int i;
while(1){
cout
La principal ventaja de esta formulacin estriba en que la funcin mayor trabaja
no solamente para una coleccin de crculos y rectngulos, sino tambin para cualquier
figura derivada de la clase base Figura. As, si se deriva Triangulo de Figura, y se
aade a la jerarqua de clases, la funcin mayor podr manejar objetos de dicha clase,
sin modificar para nada el cdigo de la misma.
Figura* mayor(Figura** f, int n)
{
Figura* mFig=0;
float m=0;
for( int i=0; im){ m=f[i]->area();
mFig=f[i];
}
//
//devuelve un puntero al objeto que tiene el valor mayor de su rea
//
return mFig;
}
Veamos ahora la llamada a la funcin mayor, dentro de main. Primero creamos
un array de punteros a objetos, guardando en sus elementos las direcciones devueltas
por new al crear cada figura (objeto de las clases derivadas), del modo que se ha
explicado en el apartado anterior.
const int N=4;
Figura** figura=new Figura* [N];
figura[0]=new Circulo(300, 80, 5.0);
figura[1]=new Elipse(120, 80, 5.2, 7.0);
figura[2]=new Rectangulo(150, 300, 5.0, 7.0);
35
figura[3]=new Cuadrado(300, 300, 7.0);
Pasamos el array figura y su dimensin a la funcin mayor: el valor que retorna
lo guardamos en fMayor. Para conocer el valor del rea, desde fMayor se llamar a la
funcin miembro area. Se llamar a la versin correcta dependiendo del tipo de objeto
apuntado por fMayor.
Figura* fMayor=mayor(figura, N);
Cout
Figura* ptr_Fig;
ptr_Fig=new Rectangulo (150,. 300, 5. 0, 7. 0);
Cuando se escribe:
delete ptr_Fig;
se llamar al destructor de Rectangulo seguido del destructor de Figura. Si el destructor
no estuviese declarado virtual, se llamara solamente al destructor de Figura,
destruyendo incorrectamente el objeto al que apunta ptr_Fig. Cuando se destruye el
array fgura, podemos observar la secuencia de las llamadas a los destructores, primero
de las clases deriva- das y luego de las clases base Figura.
for(i=0; i
BOOL Valid(const char* entrada);
};
TStringLookupValidator
TRangeValidator
TFilterValidator
TValidator
Figura 2.3. Clases para la verificacin de la informacin.
Aparte del constructor y destructor, TValidator define dos funciones miembro
como virtuales puras, que se redefinirn en cada una de las clases derivadas. Estas
funciones son Error e IsValid. La funcin Valid no ser redefinida en las clases
derivadas: su papel (en esta simulacin) es la de leer el contenido del control de edicin
y llamar a la funcin IsValid para verificar si la entrada es correcta. En caso contrario
emite un mensaje de error.
BOOL TValidator: :Valid(const char* entrada)
{
if(IsValid(entrada)) return TRUE;
Error() ;
return FALSE;
}
g La clase TFilterValidator
La clase TFilterValidator tiene como misin filtrar los caracteres que estn
permitidos. As, si se espera que la entrada sea el nmero del DNI, no se podrn
introducir caracteres alfabticos. Si en el control de edicin se ha de introducir el
nombre y apellidos de una persona, no pueden aparecer caracteres numricos, etc.
38
class TFilterValidator: public TValidator{
protected:
char* ValidChars;
public:
TFilterValidator(char* ValidChars);
void Error();
BOOL IsValid(const char* str);
};
TFilterValidator tiene como miembro dato el conjunto de caracteres que el
usuario puede teclear. El constructor crea un objeto de la clase TFilterValidator, reserva
espacio para el array de caracteres que constituyen el conjunto de caracteres permitidos,
y copia la cadena de caracteres que se le pasa en el argumento de su constructor en el
miembro dato ValidChars.
TFilterValidator::TFilterValidator(char* validChars)
{
ValidChars=new char[strlen(validChars)+1];
strcpy (ValidChars, validChars);
}
Redefine la funcin IsValid, de modo que devuelve TRUE si todos los caracteres
en str estn en el conjunto de caracteres permitidos ValidChars. De otro modo devuelve
FALSE.
BOOL TFilterValidator::IsValid(const char* str)
{
for(int i=0; i
void TFilterValidator::Error()
{
cout
En la redefinicin de IsValid, se llama primero a la funcin IsValid de la clase
base a fin de comprobar que se han introducido caracteres numricos. Posteriormente se
comprueba que el nmero introducido est dentro del intervalo predeterminado.
BOOL TRangeValidator::IsValid(const char* str)
{
if(!TFilterValidator::IsValid(str)) return FALSE;
int num=atoi(str);
if (numMax) return FALSE;
return TRUE;
}
Se redefine la funcin miembro Error para emitir un mensaje apropiado
void TRangeValidator::Error()
{
cout
public:
TStringLookupValidator(char** strings, int items);
~TstringLookupValidator();
void Error();
BOOL IsValid(const char* str);
}
El constructor reserva espacio para el array Strings e inicializa los miembros
dato con la informacin que se le pasa en los argumentos al constructor.
TStringLookupValidator. :TStringLookupValidator(char** strings, int items)
:Tvalidator()
{
Items=items;
Strings= new char*[Items];
for(int i=0; i
BOOL TStringLookupValidator::IsValid(const char* str)
{
for(int i=0; i
public:
TEdit(int maxChars);
~Tedit();
void SetValidator(TValidator* validator);
void GetLine(const char* contenido);
BOOL CanClose();
};
En el constructor, reservamos espacio para el array de caracteres intro, e
inicializamos los otros miembros dato.
TEdit::TEdit(int maxChars)
{
intro= new char[maxChars+1];
dim=maxChars;
Validator=0;
}
En el destructor liberamos el espacio reservado para el array, y destruimos el
verificador asociado al control de edicin.
TEdit::~Tedit()
{
delete[] intro;
delete Validator;
}
La funcin SetValidator inicializa el miembro dato Validator con posterioridad a
la creacin del objeto control de edicin.
void TEdit::SetValidator(TValidator* validator)
{
Validator=validator;
}
La funcin GetLine lee los caracteres introducidos en el control de edicin, hasta
un mximo de dim caracteres. El mximo nmero de caracteres que se pueden
44
introducir en el control de edicin se establece en el constructor. La funcin GetLine
inicializa el miembro dato intro, con posterioridad a la creacin del objeto control de
edicin.
void TEdit::GetLine(const char* contenido)
{
if(strlen(contenido)
TEdit* editl= new TEdit(20);
editlSetValidator(new TFilterValidator (abcdefghijklmnftopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVXYZ ,.));
editlGetLine(Jos Prez Nuez); editl->CanClose();
En primer lugar se crea el control de edicin pasndole a su constructor el mximo nmero de caracteres que admite, que depende normalmente de la longitud del control
de edicin en la caja de dilogo. Se asocia al control de edicin un objeto verificador
de la clase TFilterValidator. Se le pasa al constructor de dicha clase el conjunto de
caracteres permitidos, en este caso todos los alfabticos del idioma espaol, ms el
espacio, y los caracteres de puntuacin. La funcin GetLine lee los caracteres que el
usuario introduce en el control de edicin. La funcin CanClose se llama cuando se
cierra el dilogo, y en ella tiene lugar todo el proceso de verificacin.
g El siguiente control de edicin est pensado para introducir un nmero entero.
TEdit* edit2= new TEdit(5);
edit2SetValidator(new TRangeValidator(-3, 20)); edit2GetLine(25); edit2CanClose();
En este caso se asocia al control de edicin un verificador de la clase
TRangeValidator. Al constructor de dicha clase se le pasa el lmite superior e
inferior del intervalo, por ejemplo -3 y 20. La funcin GetLine lee los caracteres
introducidos, que internamente se transforman en nmeros. Dado que el nmero
introducido, 25, est fuera del intervalo al cerrar el dilogo y llamarse a la funcin
CanClose de control de edicin, se notificar al usuario que se ha producido un
error.
g El siguiente control de edicin est pensado para introducir el nombre de la
provincia a la que deseamos enviar un paquete postal.
TEdit* edit3= new TEdit(10);
char* p[]={Alava, Vizcaya, Guipzcoa};
46
int num=sizeof (p)/sizeof(char*);
edit3SetValidator(new TStringLookupValidator(p, num)); edit3GetLine(Vizcaya); edit3CanClose();
Se asocia al control de edicin un objeto de la clase TStringLookupValidator,
pasndole a su constructor la coleccin de cadenas de caracteres que corresponden
al nombre de las provincias adonde se piensa enviar los paquetes postales. En el
caso de que el usuario cometa un error o introduzca el nombre de una provincia
inexistente, un mensaje de error se lo notifica.
Como podremos apreciar, la declaracin como virtual de Error y de IsValid en
la clase base TValidator hace que se llame a la versin correcta cuando se asocia un
objeto verificador de una clase derivada a un control de edicin. Ms an, podemos
aadir a la jerarqua clases derivadas de TValidator para el tratamiento de errores
especficos que ayuden al usuario a verificar que los datos que introduce son los
esperados antes de su procesamiento por el programa, sin tener que modificar para nada
el cdigo del control de edicin.
class TValidator{
public:
Tvalidator(){};
virtual ~Tvalidator(){};
virtual void Error()=0;
virtual BOOL IsValid(const char* str)=0;
BOOL Valid(const char* entrada);
};
2.5. HERENCIA MULTIPLE
En el lenguaje C++, es tambin posible, aunque no es tan frecuente, la herencia
mltiple. En esta seccin estudiaremos primero el concepto de herencia mltiple, y
luego abordaremos algunas dificultades que se presentan en jerarquas de clases con una
nica clase base.
47
2.5.1. Una clase derivada de otras dos
Para explicar este concepto, consideremos un ejemplo sencillo. A partir de una
clase denominada Circulo y otra clase denominada Rectangulo, definimos una nueva
clase denominada RectRedondo que hereda las caractersticas de ambas clases. La
clasificacin jerrquica se muestra en la Figura 2.4.
En la declaracin de las clases base se ha de destacar que el control de acceso a
los miembros dato se ha establecido como protected, a fin de que las funciones
miembro de clase derivada RectRectangulo tengan acceso a dichos miembros dato de
las clases base Circulo y Rectangulo.
class Circulo{
protected:
float radio;
public:
Circulo (float r){radio=r;}
float area(){return radio*radio*3.14;}
};
Circulo Rectangulo
RectRedondo
Figura 2.4. Herencia mltiple.
class Rectangulo{
protected:
float altura;
float anchura;
public:
Rectangulo (float h, float w) {altura=h;. anchura=w;}
float mostrar(){return altura;}
};
48
En la declaracin de la clase derivada observamos la sintaxis de la herencia
mltiple. Las clases base se ponen una a continuacin de la otra separadas por una
coma. El modificador de acceso es normalmente public.
class RectRedondo: public Circulo, public Rectangulo{
int color;
public:
RectRedondo(float h, float w, float r, int c);
int getcolor(){return color;}
};
El constructor de la clase derivada llama a los respectivos constructores de las
clases base, puestos uno a continuacin del otro separados por una coma. El orden de
llamada a los constructores es primero al de la clase Circulo y luego al de Rectangulo,
ya que en este orden figuran en la declaracin de la clase RectRedondo.
RectRedondo::RectRedondo(float h, float w, float r, int c)
:Rectangulo(h, w), Circulo(r)
{
//...
}
La clase derivada hereda los datos y funciones de las clases base. Luego se
pueden efectuar las siguientes llamadas desde un objeto Rr de la clase derivada
RectRedondo.
RectRedondo Rr(15, 3, 5, 0); //objeto de la clase RectRedondo
cout
Rectangulo Circulo
RectRedondo
Punto
Figura 2.5. Herencia mltiple, una nica clase base.
Cuando creamos un objeto de la clase RectRedondo, se llama a los constructores
de las clases base Circulo y Rectangulo, pero el constructor de Rectangulo llama al
constructor de Punto, y el constructor de Circulo tambin llama al constructor de Punto,
con lo que el constructor de Punto es llamado dos veces. Un objeto de la clase
RectRedondo tendr dos subobjetos de la clase Punto. Para que esto no cause
problemas, se le aade la palabra virtual al modificador de acceso de la clase base
Punto.
class Circulo: virtual public Punto{
//...
};
class Rectangulo: virtual public Punto{
//...
};
La declaracin de la clase derivada RectRedondo de Circulo y Cuadrado, es la
misma que hemos visto en el apartado anterior.
class RectRedondo: public Circulo, public Rectangulo{
int color;
public:
RectRedondo(int a, int b, float h, float w, float r, int c);
int getcolor(){return color;}
}
50
51
La sintaxis del constructor de la clase RectRedondo adopta la siguiente forma:
primero se llama al constructor de la clase base Punto, despus al constructor de
Circulo, a continuacin al de Rectangulo, y por ltimo, al constructor de la propia clase
RectRedondo.
RectRedondo::RectRedondo (int a, int b, float h, float w, float r 1 int c)
:Circulo(a, b, r),Rectangulo(a, b, h, w), Punto(a,b)
{
color=c;
}