Story Transcript
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 :
1
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
2
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 3
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 4
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; }; 5
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: 6
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;
7
}; 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 : XX YZ 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(); }; 8
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();
9
}; 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.
10
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 11
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
12
} 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++
13