Este documento describe la programación orientada a objetos y la herencia en C++. Introduce conceptos clave como jerarquías de clases, herencia pública, clases base y derivadas. Muestra un ejemplo completo de implementación de una jerarquía de clases Empleado, Mecánico y Comercial que ilustra el uso de la herencia para reutilizar código entre clases relacionadas.
4. 1 Introducción
® Los seres humanos realizamos abstracciones de acuerdo con dos tipos de
relaciones: parte-de y tipo-de.
µ Decimos que un león tiene cuatro patas, cola larga, dientes y uñas fuertes, etc.
µ Pero, seguramente, como mejor definimos un león es diciendo que es-un tipo
de mamífero carnívoro, concretamente un gran felino.
® En el ámbito de los lenguajes de programación hasta ahora sólo disponíamos de
una herramienta para expresar el primer tipo de relaciones:
µ La composición: permite expresar relaciones parte-de (o tiene-un). Los
registros de C no son más que eso, una herramienta de composición.
® La programación orientada a objetos introduce una nueva herramienta para
expresar el segundo tipo de relaciones:
µ La herencia: permite expresar las relaciones tipo-de (o es-un).
Programación Avanzada - Tema 3: Programación orientada a objetos – 4
5. 1.1 La herencia en el mundo real
® En el ejemplo siguiente, establecemos una organización jerárquica de distintos
tipos de animales:
µ Un león es un mamífero.
µ Un león es un subtipo del tipo mamífero.
µ El tipo mamífero es un supertipo de los tipos Gato y León.
Animal
Tipo
Categoría
Mamífero Ave
Subtipo
Subcategoría
Gato León Águila Paloma
Programación Avanzada - Tema 3: Programación orientada a objetos – 5
6. 1.2 La herencia y la teoría de conjuntos
® El conjunto de elementos que pertenecen a un tipo incluye a los elementos que
pertenezcan a sus subtipos.
Animal
Mamífero Ave Conjuntos anidados
Gato León Águila Paloma Relación entre tipos y subtipos
Edificio Motocicleta Conjuntos disjuntos
No hay relación de subtipado
entre estos tipos
Programación Avanzada - Tema 3: Programación orientada a objetos – 6
7. 1.3 El principio de los subtipos
® Principio de subtipos: “Un objeto de un subtipo puede aparecer en cualquier
lugar donde se espera que aparezca un objeto del supertipo”.
µ Los animales son capaces de moverse por sí mismos.
µ Los mamíferos son capaces de moverse por sí mismos.
µ Las aves son capaces de moverse por sí mismas.
µ Los gatos son capaces de moverse por sí mismos.
® A la inversa no es cierto.
µ Los gatos maullan.
µ ¿Los mamíferos maullan?
µ ¿Los animales maullan?
Programación Avanzada - Tema 3: Programación orientada a objetos – 7
8. 2 La herencia en la POO
® La herencia es un recurso fundamental en los lenguajes orientados a objetos.
® Esencialmente consiste en definir una nueva clase:
µ como extensión de otra previamente definida, y
µ sin modificar la ya existente.
® La nueva clase hereda de la clase base:
µ los atributos (variables miembro de la clase), y
µ los métodos (funciones miembro de la clase).
® Principal beneficio: la reutilización del código.
µ Ahorro de esfuerzo.
µ Mayor confianza en el código.
Programación Avanzada - Tema 3: Programación orientada a objetos – 8
9. 2 La herencia en la POO (II)
® La clase derivada es-un tipo especial de clase base.
µ Un Mecánico es-un (tipo especial de) Empleado.
µ Un Comercial es-un (tipo especial de) Empleado.
Clase
base Empleado
superclase
Empleado
Mecánico Comercial
Clase
derivada Mecánico Comercial
subclase
Programación Avanzada - Tema 3: Programación orientada a objetos – 9
10. 2.1 Jerarquía de clases
® La herencia permite
establecer jerarquías de
clases relacionadas entre sí.
Empleado
sueldoFijo
® En el ejemplo, las subclases
asignarSueldo(s)
heredan los datos y las sueldoBase()
operaciones de la clase
base. Mecanico Comercial
numPiezas ventas
® Los objetos de una clase precioPorPieza
derivada pueden acceder a asignarNumPrecio(n, p) asignarVentas(v)
sueldo() sueldo()
los miembros públicos
heredados de la clase base (Versión 1)
de igual forma que acceden
a los propios.
Programación Avanzada - Tema 3: Programación orientada a objetos – 10
11. 2.1 Jerarquía de clases (II)
® Un objeto m de la clase Mecanico es además un Empleado, por tanto contiene
todos los miembros de la clase base (ya sean datos o funciones) y además los
propios miembros definidos en la clase derivada.
Objeto m
propia heredada
Interfaz Interfaz
asignarSueldo(s)
sueldoFijo Dato
sueldoBase() heredado
numPiezas
asignarNumPrecio(n, p) Datos
precioPorPieza propios
sueldo()
Programación Avanzada - Tema 3: Programación orientada a objetos – 11
12. 3 Herencia pública en C++
® Vamos a realizar la implementación completa de la jerarquía formada por las
clases Empleado, Mecanico y Comercial. Comenzamos con la clase base.
empleado.h (versión 1)
# i f n d e f EMPLEADO_H
# define EMPLEADO_H
class Empleado {
private :
float sueldoFijo ;
public :
void a s i gn ar S ue ld o ( f l o a t ) ;
float sueldoBase ( ) const ;
};
#endif
Programación Avanzada - Tema 3: Programación orientada a objetos – 12
13. 3 Herencia pública en C++ (II)
® La implementación de la clase base no presenta ninguna particularidad:
empleado.cpp (versión 1)
# include "empleado.h"
void Empleado : : as ig na r Su el d o ( f l o a t s ) {
sueldoFijo = s ;
}
f l o a t Empleado : : sueldoBase ( ) const {
return sue ldoFi jo ;
}
Programación Avanzada - Tema 3: Programación orientada a objetos – 13
14. 3.1 Clases derivadas
® Un mecánico es-un tipo especial de
empleado, por lo tanto, definimos la mecanico.h (versión 1)
clase Mecanico como una subclase # i f n d e f MECANICO_H
# define MECANICO_H
de Empleado.
® En C++ expresamos esta relación # include "empleado.h"
utilizando herencia pública. También class Mecanico : public Empleado {
es posible utilizar otros tipos de private :
int numPiezas ;
herencia, pero sólo la herencia
f l o a t precioPorPieza ;
pública expresa la relación es-un. public :
void asignarNumPrecio ( i n t , f l o a t ) ;
® Con la herencia pública cada f l o a t sueldo ( ) const ;
miembro de la clase base es };
heredado en la subclase #endif
manteniendo sus privilegios de
acceso.
Programación Avanzada - Tema 3: Programación orientada a objetos – 14
15. 3.1 Clases derivadas (II)
® En la implementación de la subclase Mecanico podemos utilizar cualquier
miembro público de la clase base (p.e. sueldoBase()).
® Cuando el compilador encuentra la llamada a la función sueldoBase() primero
busca la función en la clase Mecanico y, como no existe, la busca en la clase base.
mecanico.cpp (versión 1)
# include "mecanico.h"
void Mecanico : : asignarNumPrecio ( i n t n , f l o a t p ) {
numPiezas = n ;
precioPorPieza = p ;
}
f l o a t Mecanico : : sueldo ( ) const {
f l o a t complementos = numPiezas ∗ p r e c i o P o r P i e z a ;
r e t u r n sueldoBase ( ) + complementos ;
}
Programación Avanzada - Tema 3: Programación orientada a objetos – 15
16. 3.1 Clases derivadas (III)
® Un comercial es-un tipo especial de empleado, por lo tanto, definimos la clase
Comercial como una subclase de Empleado.
comercial.h (versión 1)
# i f n d e f COMERCIAL_H
# define COMERCIAL_H
# include "empleado.h"
class Comercial : public Empleado {
private :
f l o a t ventas ;
public :
void a si gn ar V en ta s ( f l o a t ) ;
f l o a t sueldo ( ) const ;
};
# e n d i f COMERCIAL
Programación Avanzada - Tema 3: Programación orientada a objetos – 16
17. 3.1 Clases derivadas (IV)
® Por último, implementamos la clase Comercial.
® De nuevo, desde una función miembro de la clase Comercial, podemos llamar a
una función de la clase base: un Comercial no deja de ser al mismo tiempo un
Empleado.
comercial.cpp (versión 1)
# include "comercial.h"
void Comercial : : as ig n ar Ve nt a s ( f l o a t v ) {
ventas = v ;
}
f l o a t Comercial : : sueldo ( ) const {
f l o a t complementos = ventas ∗ 0 . 1 0 ;
r e t u r n sueldoBase ( ) + complementos ;
}
Programación Avanzada - Tema 3: Programación orientada a objetos – 17
18. 3.2 Utilización de clases base y derivadas
test.cpp (versión 1)
# include < iostream >
# include "empleado.h"
# include "mecanico.h"
# include "comercial.h"
using namespace s t d ;
void f ( Empleado e ) {
c o u t < < "Desde f() e.sueldoBase() = " < < e . sueldoBase ( ) < < e n d l ;
}
i n t main ( ) {
Empleado e ;
e . as ig na r Su el d o ( 1 0 0 0 ) ;
c o u t < < "Sueldo del empleado: " < < e . sueldoBase ( ) < < e n d l ;
Mecanico m;
m. a s i gn a r S ue l do ( 2 0 0 0 ) ;
m. asignarNumPrecio ( 1 0 0 , 3 ) ;
c o u t < < "Sueldo del mecánico: " < < m. sueldo ( ) < < e n d l ;
Programación Avanzada - Tema 3: Programación orientada a objetos – 18
19. 3.2 Utilización de clases base y derivadas
Comercial c ;
c . as ig na r Su el do ( 3 0 0 0 ) ;
c . as ig na r Ve nt as ( 2 0 0 0 ) ;
c o u t < < "Sueldo del comercial: " < < c . sueldo ( ) < < e n d l ;
f (e) ;
f (m) ;
f (c) ;
}
salida (versión 1)
Sueldo d e l empleado : 1000
Sueldo d e l mecánico : 2300
Sueldo d e l c o m e r c i a l : 3 2 0 0
Desde f ( ) e . sueldoBase ( ) = 1 0 0 0
Desde f ( ) e . sueldoBase ( ) = 2 0 0 0
Desde f ( ) e . sueldoBase ( ) = 3 0 0 0
Programación Avanzada - Tema 3: Programación orientada a objetos – 19
20. 3.3 El principio de los subtipos en C++
® El principio de los subtipos puede enunciarse de nuevo para C++ de la manera
siguiente:
Una clase derivada puede aparecer en cualquier lugar donde se
espera una clase base pública.
® Efectivamente: la función f() de test.cpp está definida para recibir objetos de la
clase Empleado. Sin embargo, también podemos llamarla con objetos de las
clases Mecánico y Comercial.
® En todo caso, desde dentro de la función f() sólo podemos acceder a los
miembros públicos de la clase base. La siguiente implementación de la función
f() es incorrecta puesto que sueldo() no es una función de la clase Empleado:
void f ( Empleado e ) { / / i n c o r r e c t o
c o u t < < "Desde f() e.sueldo() = " < < e . sueldo ( ) < < e n d l ;
}
Programación Avanzada - Tema 3: Programación orientada a objetos – 20
21. 3.4 Acceso a los miembros de la clase base
® Hemos dicho que la clase derivada hereda todos los miembros de la clase base,
pero ¿qué ocurre con los niveles de acceso?
µ private Los miembros privados de la clase base no son accesibles en la clase
derivada (ni en el exterior).
µ public Los miembros públicos de la clase base son accesibles desde la clase
derivada (y desde el exterior).
® Disponemos, además, de un nivel intermedio:
µ protected Los miembros protegidos en la clase base:
ª son accesibles en la clase derivada.
ª no son accesibles desde el exterior.
Recuerda que estamos hablando
en el ámbito de la herencia pública.
Programación Avanzada - Tema 3: Programación orientada a objetos – 21
22. 3.5 Miembros protegidos en la clase base
® La función sueldo() de las clases Mecanico y Comercial invoca a la función
sueldoBase() de la clase Empleado para averiguar cuál es el sueldo fijo del
empleado. Lo hacemos así porque no podemos acceder al dato privado
sueldoFijo de la clase base.
® Pero, ¿y si sueldoFijo en lugar de privado fuera protegido? En ese caso:
µ sería accesible desde la función sueldo() de las subclases,
µ y, al mismo tiempo, se mantendría inaccesible desde el exterior.
Programación Avanzada - Tema 3: Programación orientada a objetos – 22
23. 3.5 Miembros protegidos en la clase base (II)
® Declaramos el miembro sueldoFijo como protected:
empleado.h (versión 1 bis)
# i f n d e f EMPLEADO_H
# define EMPLEADO_H
class Empleado {
protected :
float sueldoFijo ;
public :
void a si gn ar S ue ld o ( f l o a t ) ;
float sueldoBase ( ) const ;
};
#endif
Programación Avanzada - Tema 3: Programación orientada a objetos – 23
24. 3.5 Miembros protegidos en la clase base (III)
® Ahora, sueldoFijo es accesible desde las clases derivadas:
mecanico.cpp (versión 1 bis)
...
f l o a t Mecanico : : sueldo ( ) const {
f l o a t complementos = numPiezas ∗ p r e c i o P o r P i e z a ;
r e t u r n s u e l d o F i j o + complementos ;
}
® ... aunque sigue siendo inaccesible desde el exterior:
test.cpp (versión 1 bis)
i n t main ( ) {
...
Mecanico m;
...
c o u t m. s u e l d o F i j o ; / / i n c o r r e c t o
}
Programación Avanzada - Tema 3: Programación orientada a objetos – 24
25. 3.6 Redefinición de miembros de la clase base
® La función sueldoBase()
de la clase Empleado y las
funciones sueldo() de las Empleado
sueldoFijo
subclases tienen el mismo
asignarSueldo(s)
objetivo: devolver el sueldo sueldoBase()
sueldo()
de una persona. No hay
ninguna razón para que
tengan nombres diferentes. Mecanico Comercial
numPiezas ventas
® Si usamos el mismo precioPorPieza
asignarNumPrecio(n, p) asignarVentas(v)
nombre, conseguiremos sueldo() sueldo()
uniformizar la interfaz de las
(Versión 2)
tres clases, facilitando su
uso.
Programación Avanzada - Tema 3: Programación orientada a objetos – 25
26. 3.6 Redefinición de miembros de la clase base (II)
® Partiendo de la versión 1 (en la que no hay miembros protegidos), cambiamos el
nombre de la función sueldoBase() por sueldo() en la clase base:
empleado.h (versión 2)
class Empleado {
private :
float sueldoFijo ;
public :
void a si gn ar S ue ld o ( f l o a t ) ;
float sueldo ( ) const ;
};
empleado.cpp (versión 2)
...
f l o a t Empleado : : sueldo ( ) const {
return sue ldoFij o ;
}
Programación Avanzada - Tema 3: Programación orientada a objetos – 26
27. 3.6 Redefinición de miembros de la clase base (III)
® Al implementar las subclases, redefinimos la función sueldo() con un código
diferente al que tenía en la clase base. Estamos ante una forma de sobrecarga de
funciones.
® Ahora la función sueldo() de las subclases debe llamar a la función sueldo() de
la clase base, pero como tiene el mismo nombre, hay que utilizar el operador de
resolución de ámbito: Empleado::sueldo().
mecanico.cpp (versión 2)
...
f l o a t Mecanico : : sueldo ( ) const {
f l o a t complementos = numPiezas ∗ p r e c i o P o r P i e z a ;
r e t u r n Empleado : : sueldo ( ) + complementos ;
}
µ El mismo cambio lo realizamos también en la clase Comercial.
Programación Avanzada - Tema 3: Programación orientada a objetos – 27
28. 3.6 Redefinición de miembros de la clase base (IV)
test.cpp (versión 2)
# include iostream
# include empleado.h
# include mecanico.h
# include comercial.h
using namespace s t d ;
void f ( Empleado e ) {
c o u t Desde f() e.sueldo() = e . sueldo ( ) e n d l ;
}
i n t main ( ) {
Empleado e ;
e . as ig na r Su el d o ( 1 0 0 0 ) ;
c o u t Sueldo del empleado: e . sueldo ( ) e n d l ;
Mecanico m;
m. a s i gn a r S ue l do ( 2 0 0 0 ) ;
m. asignarNumPrecio ( 1 0 0 , 3 ) ;
c o u t Sueldo del mecánico: m. sueldo ( ) e n d l ;
Programación Avanzada - Tema 3: Programación orientada a objetos – 28
29. 3.6 Redefinición de miembros de la clase base (IV)
Comercial c ;
c . as ig na r Su el d o ( 3 0 0 0 ) ;
c . as ig na r Ve nt a s ( 2 0 0 0 ) ;
c o u t Sueldo del comercial: c . sueldo ( ) e n d l ;
f (e) ;
f (m) ;
f (c) ;
}
salida (versión 2)
Sueldo d e l empleado : 1000
Sueldo d e l mecánico : 2300
Sueldo d e l c o m e r c i a l : 3 2 0 0
Desde f ( ) e . sueldo ( ) = 1 0 0 0
Desde f ( ) e . sueldo ( ) = 2 0 0 0
Desde f ( ) e . sueldo ( ) = 3 0 0 0
Programación Avanzada - Tema 3: Programación orientada a objetos – 29
30. 3.6 Redefinición de miembros de la clase base (V)
® En el archivo test.cpp podemos apreciar la uniformidad de la interfaz de las
clases: calculamos el sueldo de la misma forma para objetos de los tipos
Empleado, Mecanico y Comercial.
® Observa que cuando un objeto de una clase derivada invoca a la función
sueldo(), el compilador elige la versión local, es decir, la de la clase derivada y
no la de la clase base.
µ Decimos que la función Mecanico::sueldo() oculta la función
Empleado::sueldo().
i n t main ( ) {
Mecanico m;
...
m. sueldo ( ) ; / / siempre se e j e c u t a l a f u n c i ó n de l a c l a s e Mecanico
/ / y nunca l a de l a c l a s e Empleado
}
Programación Avanzada - Tema 3: Programación orientada a objetos – 30
31. 3.7 Herencia y constructores
® Las clases que intervienen en una relación de herencia pueden contener
constructores y destructores.
® Al declarar un objeto de una clase derivada, se ejecuta de forma automática:
µ Primero: el constructor de la clase base.
µ Segundo: el constructor de la clase derivada.
® Cuando un objeto de una clase derivada deja de existir (si es estático, porque el
ámbito donde fue declarado ha acabado, y si es dinámico, porque se evalúa el
operador delete) se ejecuta de forma automática:
µ Primero: el destructor de la clase derivada.
µ Segundo: el destructor de la clase base.
® Si el constructor de la clase base necesita argumentos para poder ejecutarse
éstos deben ser facilitados por el constructor de la clase derivada a través de una
lista de inicialización.
Programación Avanzada - Tema 3: Programación orientada a objetos – 31
32. 3.7 Herencia y constructores (II)
® Partiendo de la versión 2, añadimos
constructores a nuestras clases.
® Aprovechamos la ocasión para Empleado
nombre
incorporar a la clase base el nombre sueldoFijo
Empleado(n, s)
del empleado y el operador de salida. sueldo()
operator(canal, emp)
® Para simplificar el código,
eliminamos las funciones
Mecanico Comercial
Empleado::asignarSueldo(), numPiezas ventas
precioPorPieza
Mecanico::asignarNumPrecio() Mecanico(n, s, num, pre) Comercial(n, s, v)
sueldo() sueldo()
y Comercial::asignarVentas().
(Versión 3)
Programación Avanzada - Tema 3: Programación orientada a objetos – 32
33. 3.7 Herencia y constructores (III)
empleado.h (versión 3)
# i f n d e f EMPLEADO_H
# define EMPLEADO_H
# include iostream
# include s t r i n g
using namespace s t d ;
class Empleado {
private :
s t r i n g nombre ;
float sueldoFijo ;
public :
Empleado ( s t r i n g , f l o a t ) ;
f l o a t sueldo ( ) const ;
f r i e n d ostream operator ( ostream , Empleado ) ;
};
#endif
Programación Avanzada - Tema 3: Programación orientada a objetos – 33
34. 3.7 Herencia y constructores (IV)
empleado.cpp (versión 3)
# include empleado.h
# include iostream
# include s t r i n g
using namespace s t d ;
Empleado : : Empleado ( s t r i n g n , f l o a t s )
: nombre ( n ) , s u e l d o F i j o ( s )
{ }
f l o a t Empleado : : sueldo ( ) const {
return sue ldoFi jo ;
}
ostream operator ( ostream canal , Empleado e ) {
c a n a l e . nombre ;
return canal ;
}
Programación Avanzada - Tema 3: Programación orientada a objetos – 34
35. 3.7 Herencia y constructores (V)
mecanico.h (versión 3)
# i f n d e f MECANICO_H
# define MECANICO_H
# include empleado.h
# include s t r i n g
using namespace s t d ;
class Mecanico : public Empleado {
private :
int numPiezas ;
f l o a t precioPorPieza ;
public :
Mecanico ( s t r i n g , f l o a t , i n t , f l o a t ) ;
f l o a t sueldo ( ) const ; / / o c u l t a Empleado : : sueldo ( )
};
#endif
Programación Avanzada - Tema 3: Programación orientada a objetos – 35
36. 3.7 Herencia y constructores (VI)
mecanico.cpp (versión 3)
# include empleado.h
# include mecanico.h
# include s t r i n g
using namespace s t d ;
Mecanico : : Mecanico ( s t r i n g n , f l o a t s , i n t piezas , f l o a t p r e c i o )
: Empleado ( n , s ) , numPiezas ( p i e z a s ) , p r e c i o P o r P i e z a ( p r e c i o )
{ }
f l o a t Mecanico : : sueldo ( ) const {
f l o a t complementos = numPiezas ∗ p r e c i o P o r P i e z a ;
r e t u r n Empleado : : sueldo ( ) + complementos ;
}
Programación Avanzada - Tema 3: Programación orientada a objetos – 36
37. 3.7 Herencia y constructores (VII)
comercial.h (versión 3)
# i f n d e f COMERCIAL_H
# define COMERCIAL_H
# include empleado.h
# include s t r i n g
using namespace s t d ;
class Comercial : public Empleado {
private :
f l o a t ventas ;
public :
Comercial ( s t r i n g , f l o a t , f l o a t ) ;
f l o a t sueldo ( ) const ; / / o c u l t a Empleado : : sueldo ( )
};
#endif
Programación Avanzada - Tema 3: Programación orientada a objetos – 37
38. 3.7 Herencia y constructores (VIII)
comercial.cpp (versión 3)
# include comercial.h
# include empleado.h
# include s t r i n g
using namespace s t d ;
Comercial : : Comercial ( s t r i n g n , f l o a t s , f l o a t v )
: Empleado ( n , s ) , ventas ( v )
{ }
f l o a t Comercial : : sueldo ( ) const {
f l o a t complementos = ventas ∗ 0 . 1 0 ;
r e t u r n Empleado : : sueldo ( ) + complementos ;
}
Programación Avanzada - Tema 3: Programación orientada a objetos – 38
39. 3.7 Herencia y constructores (IX)
test.cpp (versión 3)
# include empleado.h
# include mecanico.h
# include comercial.h
# include iostream
using namespace s t d ;
void f ( Empleado e ) {
c o u t Desde f(): ;
c o u t Sueldo de e : e . sueldo ( ) e n d l ;
}
i n t main ( ) {
Empleado e ( Maria , 1 0 0 0 ) ;
c o u t Sueldo de e : e . sueldo ( ) e n d l ;
Mecanico m( Ramon , 2 0 0 0 , 1 0 0 , 3 ) ;
c o u t Sueldo de m : m. sueldo ( ) e n d l ;
Programación Avanzada - Tema 3: Programación orientada a objetos – 39
40. 3.7 Herencia y constructores (IX)
Comercial c ( Luisa , 3 0 0 0 , 2 0 0 0 ) ;
c o u t Sueldo de c : c . sueldo ( ) e n d l ;
f (e) ;
f (m) ;
f (c) ;
}
salida (versión 3)
Sueldo de Maria : 1000
Sueldo de Ramon : 2300
Sueldo de L u i s a : 3200
Desde f ( ) : Sueldo de Maria : 1000
Desde f ( ) : Sueldo de Ramon : 2000
Desde f ( ) : Sueldo de L u i s a : 3000
Programación Avanzada - Tema 3: Programación orientada a objetos – 40
41. 3.7 Herencia y constructores (X)
Observa que:
® Los constructores de las clases derivadas utilizan la lista de inicialización para
pasar los argumentos que necesita el constructor de la clase base. Recuerda que
primero se ejecuta automáticamente el constructor de la clase base y luego el de
la derivada.
Programación Avanzada - Tema 3: Programación orientada a objetos – 41
42. 3.8 Ligadura estática vs. dinámica
® Hemos pasado por alto un detalle importante: ¿por qué la salida de la ejecución
del programa test.cpp muestra dos sueldos diferentes para Ramón y para Luisa?
µ Cuando se calcula el sueldo de Ramón en la función main() con m.sueldo(),
la función que se ejecuta es la de la clase Mecanico, puesto que m está
declarado como un objeto de esa clase.
µ Cuando se calcula el sueldo de Ramón en la función f() con e.sueldo() la
función que se ejecuta es la de la clase Empleado, puesto que e está declarado
como un objeto de esa clase, a pesar de que en ejecución el parámetro e
recibe un objeto de la clase Mecanico.
® El problema radica en cómo decide el compilador qué función debe ejecutarse, es
decir, cómo se liga la llamada de una función a su ejecución.
Programación Avanzada - Tema 3: Programación orientada a objetos – 42
43. 3.8 Ligadura estática vs. dinámica (II)
® Existen dos tipos de ligadura:
µ Ligadura estática: se produce cuando la llamada a una función de un objeto
es evaluada según el tipo asociado explícitamente en la declaración del mismo.
ª Esta es la ligadura que se utiliza por omisión en C y C++ y es la causa del
comportamiento detectado en el programa test.cpp.
µ Ligadura dinámica: se produce cuando la llamada a una función de un objeto
es evaluada según el tipo asociado al objeto en tiempo de ejecución. Se conoce
también como polimorfismo en tiempo de ejecución.
ª En C++ este tipo de ligadura es empleada al llamar a cierto tipo de funciones
especiales, conocidas como funciones virtuales, solamente cuando la
llamada se hace a través de punteros o referencias.
En la siguiente sección estudiaremos un caso práctico en el
que la ligadura dinámica juega un papel fundamental.
Programación Avanzada - Tema 3: Programación orientada a objetos – 43
44. 3.9 Funciones virtuales
® Deseamos que en nuestra jerarquía de
clases Empleado, Mecanico y
Comercial la función sueldo() sea
Empleado
invocada desde la función f() nombre
sueldoFijo
utilizando ligadura dinámica. Para Empleado(n, s)
virtual sueldo()
ello debemos: operator(canal, emp)
1. Declarar como virtual la función
Mecanico Comercial
sueldo() en la clase base (sólo es numPiezas ventas
precioPorPieza
necesario hacerlo en la clase Mecanico(n, s, num, pre) Comercial(n, s, v)
sueldo() sueldo()
base).
2. Modificar la implementación de f() (Versión 4)
para que la llamada a la función
sueldo() se realice a través de un
puntero o una referencia.
Programación Avanzada - Tema 3: Programación orientada a objetos – 44
45. 3.9.1 Invocación de funciones virtuales a través de un puntero
empleado.h (versión 4)
class Empleado {
...
v i r t u a l f l o a t sueldo ( ) const ;
};
test.cpp (versión 4)
void f ( Empleado ∗ p t r _ e ) {
c o u t Desde f(): ;
c o u t Sueldo de ( ∗ p t r _ e ) : p t r _ e −sueldo ( ) e n d l ;
}
i n t main ( ) {
...
f (e ) ;
f (m) ;
f ( c ) ;
}
Programación Avanzada - Tema 3: Programación orientada a objetos – 45
46. 3.9.1 Invocación de funciones virtuales a través de un puntero (II)
salida (versión 4)
Sueldo de Maria : 1000
Sueldo de Ramon : 2300
Sueldo de L u i s a : 3200
Desde f ( ) : Sueldo de Maria : 1000
Desde f ( ) : Sueldo de Ramon : 2300
Desde f ( ) : Sueldo de L u i s a : 3200
® La función f() recibe como argumento un puntero a un objeto de la clase
Empleado y a través de él llama a la función sueldo().
® Ante esta situación, el compilador retrasa la decisión de qué función sueldo()
ejecutar hasta el momento de la ejecución.
® En cada una de las tres llamadas a la función f() se recibe como argumento un
puntero a un objeto de tipo Empleado, Mecanico y Comercial respectivamente.
En cada caso, ptr_e-sueldo() invoca la función sueldo() de la clase
correspondiente.
Programación Avanzada - Tema 3: Programación orientada a objetos – 46
47. 3.9.2 Invocación de funciones virtuales a través de una referencia
empleado.h (versión 5)
class Empleado {
...
v i r t u a l f l o a t sueldo ( ) const ;
};
test.cpp (versión 5)
void f ( Empleado e ) {
c o u t Desde f(): ;
c o u t Sueldo de e : e . sueldo ( ) e n d l ;
}
i n t main ( ) {
...
f (e) ;
f (m) ;
f (c) ;
}
Programación Avanzada - Tema 3: Programación orientada a objetos – 47
48. 3.9.2 Invocación de funciones virtuales a través de una referencia (II)
salida (versión 5)
Sueldo de Maria : 1000
Sueldo de Ramon : 2300
Sueldo de L u i s a : 3200
Desde f ( ) : Sueldo de Maria : 1000
Desde f ( ) : Sueldo de Ramon : 2300
Desde f ( ) : Sueldo de L u i s a : 3200
® La función f() recibe como argumento una referencia a un objeto de la clase
Empleado y a través de ella llama a la función sueldo().
® Ante esta situación, el compilador retrasa la decisión de qué función sueldo()
ejecutar hasta el momento de la ejecución.
® En cada una de las tres llamadas a la función f() se recibe como argumento un
objeto de tipo Empleado, Mecanico y Comercial respectivamente. En cada caso,
e.sueldo() invoca la función sueldo() de la clase correspondiente.
Programación Avanzada - Tema 3: Programación orientada a objetos – 48
49. 4 Caso práctico: aplicación gráfica
Como ejemplo de aplicación de la ligadura dinámica (o polimorfismo en tiempo de
ejecución) estudiaremos un caso práctico: una aplicación gráfica.
® Diseño de la jerarquía de clases.
® Ligadura estática.
® Ligadura dinámica y polimorfismo.
® Clases abstractas.
Programación Avanzada - Tema 3: Programación orientada a objetos – 49
50. 4.1 Identificación de objetos
® La aplicación gráfica debe manipular una
serie de figuras como cuadrados, rectángulos,
círculos, elipses, etc. Todos estos objetos se
caracterizan por ciertos rasgos comunes:
µ Atributos (datos)
ª Coordenadas
ª Color del fondo
ª Color del borde
ª Grosor del trazo
µ Operaciones (funciones)
ª Mover
ª Dibujar
ª Área
ª Perímetro
Programación Avanzada - Tema 3: Programación orientada a objetos – 50
51. 4.2 Diseño de la jerarquía de clases
Figura
x, y, fondo, borde
mover, dibujar,
área, perímetro
Elipse Rectángulo Triángulo
ejeMayor, ejeMenor base, altura vértices
dibujar, dibujar, dibujar,
área, perímetro área, perímetro área, perímetro
Círculo Cuadrado Notación Booch
radio A B
área, perímetro área, perímetro
Clase A hereda de clase B
Programación Avanzada - Tema 3: Programación orientada a objetos – 51
52. 4.3 Diseño de la clase base
class F i g u r a {
Punto p o s i c i o n ;
C o l o r fondo , borde ;
// ... Notación Booch
public : A B
F i g u r a ( Punto p , C o l o r c1 , C o l o r c2 ) ; Clase A contiene
void mover ( Punto p) ; una instancia de clase B
void d i b u j a r ( ) const ;
f l o a t area ( ) const ; 1
1 Punto
f l o a t p e r i m e t r o ( ) const ;
// ...
Figura 1
};
2
void F i g u r a : : mover ( Punto p ) { Color
posicion = p ;
}
Programación Avanzada - Tema 3: Programación orientada a objetos – 52
53. 4.4 Diseño de las subclases
class E l i p s e : public F i g u r a {
f l o a t ejeMenor , ejeMayor ;
public :
Elipse ( float a , float b) ;
void d i b u j a r ( ) const ;
f l o a t area ( ) const ;
f l o a t p e r i m e t r o ( ) const ;
};
f l o a t E l i p s e : : area ( ) const {
r e t u r n p i ∗ ejeMenor ∗ ejeMayor ;
}
void E l i p s e : : d i b u j a r ( ) const {
// ...
}
Programación Avanzada - Tema 3: Programación orientada a objetos – 53
54. 4.4 Diseño de las subclases (II)
class Rectangulo : public F i g u r a {
f l o a t base , a l t u r a ;
public :
Rectangulo ( f l o a t a , f l o a t b ) ;
void d i b u j a r ( ) const ;
f l o a t area ( ) const ;
f l o a t p e r i m e t r o ( ) const ;
};
f l o a t Rectangulo : : area ( ) const {
r e t u r n base ∗ a l t u r a ;
}
void Rectangulo : : d i b u j a r ( ) const {
// ...
}
Programación Avanzada - Tema 3: Programación orientada a objetos – 54
55. 4.5 Estructura de datos heterogénea
i n t main ( ) {
1
F i g u r a ∗ v [N ] ; 0
v [ 0 ] = new Rectangulo ( . . . ) ;
v [ 1 ] = new E l i p s e ( . . . ) ; 2
v [ 2 ] = new C i r c u l o ( . . . ) ;
v [ 3 ] = new Cuadrado ( . . . ) ;
3
// ...
d i b u j a r ( v ) ; / / d i b u j a todas l a s f i g u r a s
f l o a t area = a r e a T o t a l ( v ) ; / / suma de l a s áreas
unRectángulo 0
}
v[0] atributos
v[1] unaElipse 1
Definimos una estructura de datos heterogénea atributos
v[2]
(un simple vector de punteros) que nos permita v[3] unCírculo 2
atributos
almacenar objetos de tipos diferentes. Recuerda … …
unCuadrado 3
que los punteros a objetos de una clase base atributos
pueden apuntar a objetos de cualquier clase
derivada.
Programación Avanzada - Tema 3: Programación orientada a objetos – 55
56. 4.6 Operaciones globales
void d i b u j a r ( F i g u r a ∗ v [ ] ) {
1
f o r ( i n t i = 0 ; i N ; i ++) 0
v [ i ]− d i b u j a r ( ) ;
} 2
f l o a t areaTotal ( Figura ∗ v [ ] ) { 3
f l o a t a t =0;
f o r ( i n t i = 0 ; i N ; i ++)
a t = a t + v [ i ]−area ( ) ; unRectángulo 0
return at ; v[0] atributos
} v[1] unaElipse 1
atributos
v[2]
Se pretende que estas dos operaciones v[3] unCírculo 2
atributos
globales operen sobre el conjunto de figuras … …
unCuadrado 3
almacenadas en la estructura de datos. ¿Lo atributos
conseguimos realmente? Observemos la llamada
v[i]-area()...
Programación Avanzada - Tema 3: Programación orientada a objetos – 56
57. 4.7 Ligadura estática
® Ligadura estática: Se produce cuando la invocación a un método de un objeto es
evaluada según el tipo T asociado explícitamente con su nombre en la
declaración del mismo. La llamada v[1]-area() invoca la función
Figura::area() porque v[1] está declarado como puntero a Figura.
f l o a t areaTotal ( Figura ∗ v [ ] ) {
f l o a t a t =0;
f o r ( i n t i = 0 ; i N ; i ++)
a t = a t + v [ i ]−area ( ) ;
return at ;
}
unaElipse
unaElipse atributos/métodos
v[1]
atributos de Figura
atributos/métodos
de Elipse
Figura * Figura
v[1]-area( ) Figura::area( )
Programación Avanzada - Tema 3: Programación orientada a objetos – 57
58. 4.8 Funciones virtuales
® La palabra reservada virtual permite retrasar la decisión de qué método debe
invocarse hasta el momento de la ejecución.
class F i g u r a {
Punto p o s i c i o n ;
C o l o r fondo , borde ;
// ...
public :
F i g u r a ( Punto p , C o l o r c1 , C o l o r c2 ) ;
void mover ( Punto p) ;
v i r t u a l void d i b u j a r ( ) const ;
v i r t u a l f l o a t area ( ) const ;
v i r t u a l f l o a t p e r i m e t r o ( ) const ;
// ...
};
Programación Avanzada - Tema 3: Programación orientada a objetos – 58
59. 4.9 Ligadura dinámica
® Ligadura dinámica: Se produce cuando la invocación a un método de un objeto
es evaluada según el tipo T asociado al objeto en tiempo de ejecución. En C++
se utiliza al invocar funciones virtuales a través de punteros o referencias. La
llamada v[1]-area() invoca la función Elipse::area() porque v[1]
apunta en tiempo de ejecución a una Elipse.
f l o a t a r e a T o t a l ( F i g u r a ∗ v [ ] ) { / / s i n cambios r e s p e c t o a l a v e r s i ó n a n t e r i o r
f l o a t a t =0;
f o r ( i n t i = 0 ; i N ; i ++)
a t = a t + v [ i ]−area ( ) ;
return at ;
}
unaElipse
unaElipse atributos/métodos
v[1]
atributos de Figura
atributos/métodos
Figura * Figura de Elipse
v[1]-area( ) Elipse::area( )
Programación Avanzada - Tema 3: Programación orientada a objetos – 59
60. 4.9 Ligadura dinámica (II)
® Añadimos la subclase PoligonoLibre a la jerarquía:
Programación Avanzada - Tema 3: Programación orientada a objetos – 60
61. 4.9 Ligadura dinámica (III)
® La función areaTotal()
sigue siendo válida para la
nueva figura:
i n t main ( ) {
F i g u r a ∗ v [N ] ;
// ... unPoligonoLibre
v [ 3 ] = new Cuadrado ( . . . ) ; unPoligonoLibre atributos/métodos
v[4]
v [ 4 ] = new P o l i g o n o L i b r e ( . . . ) ; atributos de Figura
// ... atributos/métodos
Figura * Figura de Elipse
f l o a t area= a r e a T o t a l ( v ) ;
v[4]-area( ) PoligonoLibre::area( )
f l o a t areaTotal ( Figura ∗ v [ ] ) {
f l o a t a t =0;
f o r ( i n t i = 0 ; i N ; i ++)
a t = a t + v [ i ]−area ( ) ;
return at ;
}
Programación Avanzada - Tema 3: Programación orientada a objetos – 61
62. 4.10 Clases abstractas
® La clase Figura constituye una interfaz común a toda la jerarquía y contiene
métodos cuya implementación sólo tiene sentido en las clases derivadas: las
funciones virtuales puras.
® Una clase base con funciones virtuales puras se convierte en clase abstracta. Tal
clase no puede tener instancias.
class F i g u r a {
// ...
public :
F i g u r a ( Punto p , C o l o r c1 , C o l o r c2 ) ;
void mover ( Punto p) ;
v i r t u a l void d i b u j a r ( ) const = 0 ;
v i r t u a l f l o a t area ( ) const = 0 ;
v i r t u a l f l o a t p e r i m e t r o ( ) const = 0 ;
// ...
};
Programación Avanzada - Tema 3: Programación orientada a objetos – 62
63. 4.11 Conclusiones del caso práctico
® La herencia puede usarse para especificar una interfaz común para un grupo de
clases derivadas.
® La clase base, al especificar el prototipo de las funciones, define las operaciones
y características requeridas por las clases derivadas y son implementadas por
estas últimas.
® Esta técnica permite el diseño de programas que operan sobre objetos diferentes
de una manera genérica: nos encontramos ante un tipo de polimorfismo.
® En C++ este comportamiento polimórfico se obtiene usando herencia y funciones
virtuales.
® Este mecanismo es esencial a la POO y constituye la diferencia fundamental con
respecto a la programación basada en objetos.
Programación Avanzada - Tema 3: Programación orientada a objetos – 63
64. Fin
Copyright c 2004 José Luis Llopis Borrás
Realizada con ujislides c 2002-3 Sergio Barrachina (barrachi@icc.uji.es)