Você está na página 1de 32

2

Herencia

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<<Destructor de la clase Punto;}
};

Punto

Punto Mejorado

Figura 2.1. La herencia simple

La definicin del constructor y de las funciones mostrar y ocultar es la misma


que en el captulo anterior.

21

Punto::Punto (int x1, int yl)


{
ch=*;
x=xl;
y=yl;
}
void Punto::mostrar()
{
gotoxy(x,y);
cout<<ch;
}
void Punto::ocultar()
{
gotoxy(x,y);
cout<< ;
}

2.2.2. Clase derivada


En la sintaxis de la declaracin de la clase derivada, el modificador de acceso, la
palabra en letra negrita, es habitualmente public. Esto significa que podremos llamar a
los miembros pblicos de la clase base desde un objeto de la clase derivada. Por
ejemplo, si pm es un objeto de la clase derivada PuntoMejorado, podemos llamar a
getx() y a gety() (funciones miembro de la clase base) desde pm. Naturalmente, no
tenemos acceso directo a los miembros dato x e y, es decir, no podemos escribir pm.x o
pm.y, por ser protected en la clase base. Tampoco podramos escribir pt.x o pt.y, desde
un objeto pt de la clase base Punto. Sin embargo, veremos que la funcin miembro
mover de la clase derivada tiene acceso a x e y, miembros dato de la clase base.

22

class PuntoMejorado: public Punto{


public:
PuntoMejorado (int xl, , int yl);
void mover (int dx, int dy);
void mostrar ();
void ocultar();
~ PuntoMejorado(){cout<<Destructor de la clase PuntoMejorado;}
};

2.2.3. Constructor de la clase derivada


Otra cuestin importante a sealar es la forma que adopta el constructor de la
clase derivada.
PuntoMejorado::PuntoMejorado(int xl, int yl)
:Punto(xl, yl)
{
}
El constructor de la clase derivada va unido al de la clase base por el smbolo :.
Esto indica que el constructor de la clase base, Punto, se llamar primero pasndole los
argumentos x1 e y1, e inicializando los miembros dato de dicha clase x e y. Luego se
llamar al constructor de la clase PuntoMejorado.

2.2.4. Destructor de la clase derivada


Se han definido dos destructores explcitos en la clase base y en la clase
derivada.
class Punto{
protected:
//...
public:
//...

23

~Punto() {cout<<Destructor de la clase Punto;}


};
class PuntoMejorado: public Punto{
public:
//...
~PuntoMejorado() {cout<<Destructor de la clase PuntoMejorado;}
};
Cuando un objeto de la clase PuntoMejorado sale fuera del mbito de su
definicin, se llama al destructor de la clase derivada seguido del destructor de la clase
base. As:
int main()
{
PuntoMejorado pm(10, 15);

//objeto de la clase PuntoMejorado

//...
}
El objeto pm de la clase PuntoMejorado, al salir de la funcin principal, llama a
su destructor y al de sus clases base. Por tanto, el orden de llamada a los destructores es
inverso al orden de llamada a los constructores. Si ahora escribimos:
PuntoMejorado* ptro_pm= new PuntoMejorado(10,15);
delete ptro_pm;

En la primera sentencia se llama al constructor de Punto seguido del de


PuntoMejorado. En la segunda sentencia se llama al destructor de PuntoMejorado
seguido del de Punto.

2.2.5. Incrementar la funcionalidad en la clase derivada


El mecanismo de la herencia nos permite incrementar la funcionalidad
aprovechando el cdigo existente. Se va a mejorar la clase derivada por dos vas
distintas: redefiniendo algunas de las funciones miembro de la clase base, como mostrar
y ocultar, y definiendo nuevas funciones, como mover. La funcin mostrar redefinida

24

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<<x<<, <<y;
}
En la redefinicin de la funcin ocultar, se llama primero a la funcin del mismo
nombre en la clase base Punto, la cual borra el punto, y luego borra los valores de las
coordenadas x e y del punto visualizados en la esquina superior izquierda de la ventana,
imprimiendo una cadena de espacios en blanco.
void PuntoMejorado::ocultar()
{
Punto::ocultar();
gotoxy(1,1);
cout<<

}
Se incremento tambin la funcionalidad de la clase derivada, definiendo una
funcin denominada mover, que desplaza el punto desde una posicin (x, y) a una nueva
posicin (x+dx, y+dy). Primero borra el punto de la posicin previa llamando a la
funcin miembro ocultar, y lo pone en la nueva posicin, llamando a la funcin
mostrar.
void PuntoMejorado::mover(int dx, int dy)
{
ocultar();

//llama a ocultar de la clase derivada

x+=dx;
y+=dy;
mostrar();

//llama a mostrar de la clase derivada

}
25

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.

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

Figura

Elipse

Rectngulo

Crculo

Cuadrado

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<<Destructor de Figura\n;}
virtual float area(void)=0;
};
El constructor inicializa los miembros dato:
Figura::Figura(int _x, int _y)
{
x=_x; y=_y;
}
El destructor imprime un mensaje:
class Figura{
//...
virtual ~Figura() {cout<<Destructor de Figura\n;}
};

28

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<<Destructor de Rectngulo\n;}
float area(void);
};
El constructor de la clase Rectngulo llama al constructor de la clase Figura, le
pasa las coordenadas de su centro, y luego inicializa los miembros dato Lx y Ly.
Rectangulo::Rectangulo (int _x, int _y, int _Lx, int _Ly)
:Figura(_x, _y)
{
Lx = _Lx; Ly = _Ly;
}
En la redefinicin de la funcin rea, se calcula el rea del rectngulo como
producto de la anchura por la altura.
float Rectangulo::area(void)
{
return Lx*Ly;
}

29

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<<Destructor de Cuadrado\n;}
};
El constructor solamente precisa tres argumentos: los que corresponden a la
posicin de la figura y a la longitud del lado.
Cuadrado::Cuadrado(int _x, int _y, int l)
:Rectangulo(_x, _y, l, l)
{
}

La clase Elipse
La clase derivada Elipse tiene como datos, aparte de su posicin (x, y) en el

plano, sus dimensiones, es decir, su semieje mayor rMayor y semieje menor rMenor.
class Elipse:public Figura{
protected:
float rMayor, rMenor;
public:
Elipse(int _x, int _y, float rl, float r2);
~Elipse(){cout<<Destructor de Elipse\n;}
float area(void);
};
El constructor de la clase Elipse llama al constructor de la clase Figura, le pasa
las coordenadas de su centro, y luego inicializa los miembros dato rMayor y rMenor.

30

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;
}

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<<Destructor de Circulo\n;}
};
El constructor solamente precisa tres argumentos: los que corresponden a la
posicin de la figura y a la longitud del radio.
Circulo::Circulo(int _x, int _y, float r)
:Elipse(_x, _y, r, r)
{
}

31

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<<elipse.area ()<<\n;
Del mismo modo, empleando punteros a objetos de la clase Elipse:
Elipse* ptr_Elipse= new Elipse(120, 80, 5.0, 7.2);
cout<<ptr_Elipsearea()<<\n;
Existe otro procedimiento completamente distinto: se declara un puntero ptr_Fig
a un objeto de la clase base Figura, y en l se guarda la direccin de un objeto elipse de
la clase derivada Elipse.
Figura* ptr_Fig;
ptr_Fig=new Elipse(120, 80, 5.0, 7.0);
Cuando escribimos:
ptr_Figarea();
se llamar a rea redefinida en la clase Elipse, por estar rea declarada virtual.
En el primer caso, se selecciona la versin de area que se llama en funcin del
objeto que la llama. Un objeto de la clase Elipse llama a la versin area definida en la
clase Elipse, y as sucesivamente. Sin embargo, en el segundo caso, ptr_Fig deber, de
algn modo, recordar de qu tipo es, para que llame a la funcin miembro arca correcta.

2.3.3. Enlace tardo


En el lenguaje C, los identificadores de la funcin estn asociados siempre a
direcciones fisicas antes de la ejecucin del programa. Esto se conoce como enlace
temprano o esttico. Ahora bien, C++ permite decidir a qu funcin llamar en tiempo de
ejecucin: esto se conoce como enlace tardo o dinmico. En C++, se especifica el

32

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<<Escoge la figura: \n;
cout<< 0.Una elipse.-\n;
cout<<..1.Un crculo.-\n;
cout<< 2.Un rectngulo.-\n;
cout<< 3.Un cuadrado.-\n;
cout<< 4.Salir ;
cout<<\n

: >;

cin>>i;
if (i >= 4) break;
33

cout<<rea= <<ptro_Fig[i]area()<<\n;
clrscr();
};

2.3.4. El polimorfisrno en accin


Supongamos que deseamos hallar la figura que tiene mayor rea,
independientemente de su forma. Es posible hacer esto de una forma muy simple en
C++, de un modo semejante a como se halla el valor mayor de un array de nmeros.
float mayor(float* x, int n)
{
float m=0;
for (int i=0; i<n; i++) if(x[i]>m) m=x[i];
return m;
}
A la funcin mayor se le pasa el array de nmeros, y la dimensin del array, y
devuelve el nmero mayor. Para llamar a esta funcin, primero creamos un array de
nmeros num y luego se lo pasamos en el primer argumento de la funcin mayor.
int main()
{
float num[]={3.2, 3, 5, 0, 1};
cout<<El mayor es <<mayor(num, 5);
return 0;
}
La funcin mayor que compara el rea de figuras planas es semejante a la
funcin mayor anteriormente definida. Se le pasa en el primer argumento un array de
punteros a objetos de las clases derivadas de la clase base Figura, y en el segundo se le
pasa la dimensin del array. La funcin devuelve un puntero al objeto cuya rea es la
mayor. El cdigo no precisa comentarios adicionales: la base de su funcionamiento est
en la lnea marcada en letra negrita, en ella se llama a la versin correcta de area
dependiendo del tipo de objeto apuntado por f[i].

34

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; i<n; i++)
//
//llama a la versin correcta de area dependiendo del tipo de objeto
//apuntado por f[i]
//
if (f[i]area()>m){
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<<rea mayor= <<fMayorarea()<<\n';
Por ltimo, deberemos acordarnos de liberar el espacio en memoria ocupado por
el array. Todo objeto ubicado en memoria con new debe liberarla con delete.
for(i=0; i<N; i++)
delete figura[i];
delete[] figura;

La combinacin de herencia y enlace dinmico se denomina polimorfisino. El


polimorfisrno es, por tanto, la tcnica que permite pasar un objeto de una clase derivada
a funciones que conocen el objeto solamente por su clase base.

2.3.5. Destructores virtuales


Un destructor puede declararse virtual. Esto permite a un puntero a un objeto de
la clase base llamar al destructor correcto, cuando se guarda en l un objeto de la clase
derivada.
class Figura{
//...
public:
//...
virtual ~Figura(){cout<<Destructor de Figura\n;}
};
Declaramos un puntero ptr_Fig a un objeto de la clase base Figura, y en l
guardamos la direccin de un objeto rect de la clase derivada Rectangulo.

36

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<N; i++)
delete figura [i];

2.4. VERIFICACIN DE DATOS

El objetivo de esta seccin es proporcionar un ejemplo adicional del


polimorfismo, a la vez que analizar el mecanismo de verificacin de la informacin
introducida por el usuario en los controles de edicin

2.4.1. Jerarqua de clases para la verificacin de datos


g

La clase TValidator
TValidator es una clase base abstracta, del mismo modo que lo es Figura en la

jerarqua de clases que describen las figuras planas. No se pueden crear objetos de una
clase abstracta, pero proporciona un interfaz comn para las clases derivadas de ella.
class TValidator{
public:
Tvalidator(){};
virtual ~Tvalidator(){};
virtual void Error()=0;
virtual BOOL IsValid(const char* str)=0;
37

BOOL Valid(const char* entrada);


};
TValidator

TStringLookupValidator

TFilterValidator

TRangeValidator

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<strlen(str); i++)
if(!strchr(ValidChars, str[i]))

return FALSE;

return TRUE;
}
La funcin miembro Error se redefine para emitir un mensaje de error
significativo.

39

void TFilterValidator::Error()
{
cout<<Ha introducido caracteres que no estn permitidos\n;
}
g

La clase TRangeValidator
La clase TRangeValidator derivar de TFilterValidator, ya que primero ha de

verificar que los caracteres introducidos son numricos y, en segundo lugar, que el
nmero est en un intervalo predeterminado. Los miembros datos de esta clase son el
valor entero mayor Max, y el menor Min que puede introducirse en el control de
edicin.
class TrangeValidator: public TFilterValidator {
protected:
int Max;
int Min;
public:
TRangeValidator (int min, int max)
void Error();
BOOL IsValid(const char* str);
};
El constructor crea un objeto de la clase TRangeValidator, llamando primero al
constructor de la clase TFilterValidator pasndole el conjunto de caracteres permitidos,
los dgitos del 0 al 9, el signo + y -. Luego, inicializa Min con el valor de min y Max con
el valor de max, establecindose as el intervalo de valores permitidos.
TRangeValidator::TRangeValidator(int min, int max)
:TFilterValidator(01234567890+-)
{
Max=max;
Min=min;
}

40

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 (num<Min || num>Max) return FALSE;
return TRUE;
}
Se redefine la funcin miembro Error para emitir un mensaje apropiado
void TRangeValidator::Error()
{
cout<<El

nmero

introducido

no

est

en

el

intervalo

(<<Min<<,<<Max<<)\n;
}
g

La clase TStringLookupValidator
Esta clase verifica el dato introducido en el control de edicin asociado

buscando en una coleccin de cadenas de caracteres que son vlidas. Por ejemplo, para
un envo postal, se ha de introducir en el control de edicin correspondiente el nombre
de la provincia, o el cdigo postal. Podemos asociar a dicho control un objeto de la
clase TStringLookupValidator que tenga como coleccin de cadenas de caracteres
vlidas el nombre de todas las provincias.
Los miembros dato son el nmero de elementos de la coleccin, Items, y un
array de punteros a caracteres, Strings.
class TStringLookupValidator: public TValidator{
protected:
char** Strings;
int Items;

41

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<Items; i++){
Strings[i]=new char[strlen(strings[i])+1];
strcpy(Strings[i], strings[i]);
}
}
El destructor libera el espacio reservado para el array de punteros a char cuando
ya no es necesario.
TStringLookupValidator::~TstrngLookupValidator()
{
for(int i=0; i<Items; i++)
delete[] Strings[i];
delete Strings;
}
La funcin IsValid busca si la cadena de caracteres str est en el array Strings.
Para ello emplea la funcin strcmp de la librera STRING.H.

42

BOOL TStringLookupValidator::IsValid(const char* str)


{
for(int i=0; i<Items; i++)
if(strcmp(str, Strings[i])==0) return TRUE;
return FALSE;
}

Se redefine la funcin Error para avisar adecuadamente al usuario de que no ha


introducido correctamente el nombre o nmero esperado.
void TStringLookupValidator::Error()
{
cout<<La cadena de caracteres no est entre las permitidas\n;
}

2.4.2. Simulacin del control de edicin


Los controles de edicin se sitan en las cajas de dilogo de las aplicaciones
Windows para introducir datos a travs del teclado. Al pulsar el botn OK o Aceptar se
comprueba que los datos introducidos por el usuario son vlidos. En caso afirmativo, se
cierra la caja de dilogo y se transfiere la informacin para ser procesada por el
programa. En caso negativo, se notifica al usuario de que ha cometido errores, y la caja
de dilogo no se cierra esperando que aquellos sean rectificados.
La clase que va a describir un control de edicin simulado va a constar de tres
miembros dato: un puntero, intro, a la cadena de caracteres que introduce el usuario en
el control de edicin; el nmero mximo de caracteres que pueden introducirse, dim; y
un puntero a un objeto de la clase base Validator. Este miembro dato es el que vincula
un control de edicin, y su verificador, un objeto de la clase derivada de TValidator.
class TEdit{
protected:
char* intro;
int dim;
TValidator* Validator;

43

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)<dim)
strcpy(intro, contenido);
else
strncpy(intro, contenido, dim);
}
En la funcin CanClose del control de edicin se verifica el dato introducido. En
ella el objeto verificador asociado llama a su funcin miembro Valid. Por defecto, Valid
devuelve TRUE si la funcin miembro IsValid devuelve TRUE; en caso contrario llama
a la funcin Error para notificar al usuario del error y luego devuelve FALSE.
BOOL TEdit::CanClose()
{
if(ValidatorValid(intro)){
cout<<Correcto\n;
return TRUE;
}
return FALSE;
}

2.4.3. El control de edicin y su verificador asociado


Vamos a ver ahora cmo se crea un control de edicin y se asocia un objeto
verificador.
g

Supongamos en primer lugar que el control de edicin est pensado para introducir
el nombre y apellidos de una persona; por tanto, asociaremos al control de edicin
un objeto verificador que filtre solamente caracteres alfabticos y rechace los
numricos.

45

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.

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<<Rr.area()<<\n;

//funcin miembro de Circulo

cout<<Rr.mostrar()<<\n;

//funcin miembro de Rectangulo

cout<<Rr.getcolor()<<\n;

//funcin miembro de RectRedondo

2.5.2. Clases virtuales


Abundaremos un poco ms en el concepto de clase virtual, considerando la
jerarqua de clases de la Figura 2.5. Supongamos que Rectangulo y Circulo derivan de
una nica clase base denominada Punto, con dos miembros dato, la abscisa x y la
ordenada y del centro de la figura.
49

Punto

Circulo

Rectangulo

RectRedondo

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

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;
}

51

Você também pode gostar