Ingeniero en Informática
Clases derivadas
CLASES DERIVADAS
Introducción
Es un mecanismo para añadir recursos a una clase existente sin reprogramar ni recompilar. Es posible proveer una interfaz común para varias clases distintas
Se introduce el concepto de función virtual: permite utilizar objetos cuando su tipo no se conoce en el momento de la compilación
Es el modo de representar relaciones entre objetos: relaciones jerárquicas ó todo aquello que tienen en común dos o más clases
Clases derivadas
class empleado {
char* nombre;
short edad;
short departamento;
public :
empleado* siguiente; // público para poder
// manipular listas
void imprimir() const;
};
// a continuación se define la clase derivada gerente
class gerente : public empleado {
empleado* grupo
short nivel;
public :
void imprimir const;
};
La clase gerente tiene los miembros de la clase base empleado (nombre, edad, etc.) y además los miembros grupo y nivel. La derivación se representa gráficamente como :
empleado
gerente
Las relación entre las clases derivadas y las clases base se conoce como una relación de herencia. Es frecuente referirse a la clase base como la superclase y a la clase derivada como la subclase
Creación de una lista de empleados y gerentes :
void f()
{
gerente g1, g2;
empleado e1, e2;
empleado* listae;
listae = &g1; //coloca a g1 en listae
g1.siguiente = &e1; //coloca a e1 en listae
e1.siguiente = &g2; // g2 en listae
g2.siguiente = &e2; // e2 en listae
e2.siguiente = 0 ; //termina listae
}
Como un gerente es un empleado se puede utilizar un gerente* de manera equivalente a un empleado*, a la inversa no es posible, un empleado* no se puede emplear como un gerente*.
void g()
{
gerente gg;
empleado* pe =≫ // correcto, gerente es empleado
empleado ee;
gerente* pg = ⅇ // error, empleado no es gerente
pg->nivel = 2; // error ee no tiene espacio para nivel
pg = (gerente*) pe; // correcto porque apunta al
// gerente gg
pg->nivel = 2; // correcto
}
FUNCIONES MIEMBROS
Un miembro de la clase derivada puede utilizar un nombre público de la clase base. Un miembro de la clase derivada no tiene permiso para acceder a un miembro privado de la clase base
void gerente::imprimir() const
{
cout<<” el nombre es ”<< nombre <<'\n';
}
En el caso de esta función el compilador la rechaza porque esta accediendo a un miembro privado de la clase base empleado
Si existiera la posibilidad de que un clase derivada accediera a un miembro privado, el concepto de miembro privado dejaría de tener significado : basta con definir una clase derivada para tener acceso a los miembros privados de cualquier clase. Cuando esto es necesario se declaran los miembros protegidos (“protected”)
Declarando a los miembros de una clase como protected estos podrán ser vistos por las clases derivadas
Para solucionar esto podemos hacer :
void gerente::imprimir() const
{
empleado::imprimir(); // imprimir información empleados
cout<<” El nivel gerencial es ”<< nivel <<'\n';
}
si se escribiera :
void gerente::imprimir() const
{
imprimir(); // secuencia recursiva de llamadas a funciones imprimir
}
Constructores y destructores
Algunas clases derivadas necesitan constructores. Si la clase necesita un constructor hay que llamarlo y además pasarle los argumentos si los tiene
class empleado{
...
...
public :
empleado(char* n, int d);
};
class gerente : public empleado {
...
...
public :
gerente(char* n, int l, int d);
};
Los argumentos de definición de la clase base se especifican en el constructor de la clase derivada :
gerente::gerente(char* n, int l, int d)
: empleado(n,d), nivel(l), grupo(0)
{
}
empleado::empleado(char* n, int d)
: nombre(n), departamento(d)
{
siguiente = lista
lista = this;
}
Cuando el compilador busca el miembro de una subclase, primero busca en el ámbito de la subclase, si no la encuentra lo busca en el ámbito de la superclase y así sucesivamente en la escala jerárquica de la herencia. Por este mecanismo es posible utilizar funciones miembros definidas en la superclase pero no redefinidas en la subclase
Los objetos de clase se construyen de abajo hacia arriba : primero la base, luego los miembros y después las clases derivada misma, y se destruyen en el orden opuesto.
Jerarquías de clase
Una clase derivada puede ser a su vez una clase base :
class empleado { ......};
class gerente : public empleado { .......};
class director : public gerente {......};
En general las estructuras jerárquicas son árboles pero pueden formar una estructura un poco más compleja como un grafo dirigido:
class temporal {.......};
class secretaria : public empleado {......};
class sectemp : public temporal, public secretaria {........};
class asesor : public temporal, public gerente {.......};
temporal empleado
sectemp secretaria gerente
asesor director
Funciones virtuales
Permite al programador declara funciones en una clase base que se pueden redefinir en una clase derivada
class empleado {
char* nombre;
short departamento;
empleado* siguiente;
static empleado* lista;
public:
empleado(char* n, int d);
static void imprimir_lista;
virtual void imprimir() const;
};
La palabra clave virtual indica que la función imprimir() puede tener diferentes versiones para clases derivadas distintas y que es tarea del compilador encontrar la versión apropiada.
El tipo de función se declara en la clase base y no pueden volver a declararse en una clase derivada. Las funciones virtuales deben definirse para la clase en la que se declaran por primera vez. Ejemplo :
void empleado::imprimir() const
{
cout<< nombre<< `\t'<< departamento <<'\n';
}
Es posible utilizar la función virtual aunque no se derive ninguna clase, y no hace falta que una subclase que no necesite una función especial de una función virtual tenga que incluir una. Al derivar una clase uno puede preparar una función apropiada si es necesario.
class gerente : public empleado {
empleado* grupo
short nivel;
public :
void imprimir const;
};
Ya no se necesita en este caso la función de imprimir empleado porque la nueva función puede tomar su lugar.
Clases Abstractas
Esto se emplea para cuando existen conceptos abstractos para los que no existe objetos, como es el caso de figura. Una figura solo tiene sentido como base para cuando una clase se deriva de ella. Esto puede ser un buen campo para el empleo de funciones virtuales.
class figura {
// .....
public:
virtual void girar(int) { error(“girar::figura”)};
virtual void dibujar() { error(“dibujar::figura”)};
};
Tratar de crear una figura de esta especie es absurdo pero legal:
figura f;
Es absurdo porque una operación sobre f producirá un error.
Una alternativa es declarar las funciones virtuales de la clase figura como funciones virtuales puras. Una función virtual se “purifica” con un inicializador en 0 :
class figura {
// .....
public:
virtual void girar(int) = 0; // función virtual pura
virtual void dibujar() = 0; // función virtual pura
};
Una clase con una o más funciones virtuales puras es una función abstracta y no es posible crear objetos de esa clase, por lo tanto :
figura f; // error : variable de clase abstracta figura
Una clase abstracta solo puede servir como base para otra clase. Por ejemplo :
class circulo : public figura {
int radio;
public:
void girar(int) { ......}; // deroga a figura::girar
void dibujar(); // deroga a figura::dibujar
circulo(punto p, int r);
};
Las funciones virtuales puras que no están definidas en una clase derivada siguen siendo funciones virtuales puras, de modo que la clase derivada es también una clase abstracta. Esto permite hacer implementaciones por etapas:
class X {
public:
virtual void f()=0;
virtual void g()=0;
};
X b; //error declaración de objeto de clase abstracta X
class Y : public X {
void f(); // deroga a X::f
};
Y b ; //error declaración de objeto de clase abstracta Y
class Z : public Y {
void g(); // deroga a X:: g
};
Z c; // correcto
Herencia múltiple
Ya se ha visto que es posible generar una clase a partir de más de una clase base, como por ejemplo :
class sectemp : public secretaria , public temporal {
.........
};
A esto se llama herencia múltiple. Además de las operaciones que se definen para una secretaria temporal es posible aplicarle las operaciones de los objetos secretaria y temporal
Puede ocurrir que en herencia múltiple una misma clase base pueda ser empleada dos veces. Por ejemplo :
X X
Y Z
W
En estos casos a veces no es posible hacer referencia a miembros de la clase X sin riesgo de ambigüedad.
Para resolver la ambigüedad se debe hacer :
class Y {
// ......
virtual f();
};
class Z{
//.......
virtual f();
};
al emplear W será necesario eliminar la ambigüedad :
void g(W* p){
{
i = p->f; //error : ambigüedad
i = p->Y::f; // correcto
i = p->Z::f; //correcto
};
Resolución de ambigüedades en la clase derivada :
class W {
// ......
i1 = Y::f();
i2 = X::f();
};
Clases bases virtuales
Una clase base virtual sirve para representar una clase principal que se puede adaptar de diferentes maneras :
class ventana {
// lo básico
virtual void dibujar();
};
class ventana_con_ borde : public virtual ventana {
// cosas del borde
void dibujar();
};
class ventana_con_ menu : public virtual ventana {
// cosas del menu
void dibujar();
};
class ventana_con_ borde_y_menu : public virtual ventana,
public ventana_con_borde,
public ventana_con_menu {
void dibujar();
};
Ahora podemos escribir las distintas funciones dibujar :
void ventana_con_borde::dibujar() {
ventana::dibujar();
// dibujar el borde
}
void ventana_con_menu::dibujar() {
ventana::dibujar();
// dibujar el borde
}
void ventana_con_borde_y_menu::dibujar() {
ventana_con_borde::dibujar();
ventana_con_menu::dibujar();
// dibujar lo específico para ventana con borde y menú
}
Atención : se llama a dibujar ventana dos veces !!!!!!
Hay algunos métodos para corregir estos errores.
Control de acceso
Un miembro de una clase puede ser :
privado : puede ser accedido por funciones miembros y amigas
protegido : puede ser accedido por funciones miembros y amigas de la clase que se declara y por las miembros y amigas de las clases derivadas
público: cualquier función puede utilizar su nombre
Accesos a clases base
Los accesos a las clases bases también pueden ser privado protegido y público.
Class X {
public:
int a:
};
class Y1 : public X {};
class Y2 : protected X{};
class Y3 : private X{};
Herencia pública :
público público
protegido protegido
privado privado
Herencia privada :
público privado
protegido privado
privado privado
Herencia protegida :
público protegido
protegido protegido
privado privado
Ejemplo:
class X {
// privado por omisión
int priv;
protected:
int prot;
public:
int publ;
void m();
};
El miembro X::m tiene acceso irrestricto
void X::m()
{
priv=1; //correcto
prot=2; //correcto
publ=3; //correcto
}
Un miembro de la clase privada tiene acceso a los miembros públicos y protegidos:
class Y : public X {
void mderivada();
};
Y::mderivada()
{
priv=1; //error!! priv es privada
prot=2; //correcto prot es protegida
publ=3; //correcto publ es pública
}
Una función global f() solo puede tener acceso a la parte pública :
void f(Y* p)
{
p->priv=1; //error!! priv es privada
p->prot=2; //error prot es protegida y f no es
// miembro o amiga de X ni de Y
p->publ=3; //correcto publ es pública }
Universidad Tecnológica Nacional - Santa Fe - Departamento Sistemas -
Curso : Desarrollos de Programación en C++
Descargar
Enviado por: | María |
Idioma: | castellano |
País: | España |