Clases derivadas

Programación en C++. Computación. Recursos. Relaciones. Funciones miembros

  • Enviado por: María
  • Idioma: castellano
  • País: España España
  • 18 páginas
publicidad
publicidad

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++