Tema3
-
Upload
aldo-hernan-zanabria-galvez -
Category
Documents
-
view
274 -
download
0
Transcript of Tema3
Programación Avanzada - Tema 3: Programación orientada a objetos
http://www3.uji.es/~llopis/II17
José Luis Llopis Borrás
15 de noviembre de 2004
Índice
1. Introducción 4
1.1. La herencia en el mundo real . . . . . . . . . . . . . . . . . . . . . . . 5
1.2. La herencia y la teoría de conjuntos . . . . . . . . . . . . . . . . . . . . 6
1.3. El principio de los subtipos . . . . . . . . . . . . . . . . . . . . . . . . . 7
2. La herencia en la POO 8
2.1. Jerarquía de clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
3. Herencia pública en C++ 12
3.1. Clases derivadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
3.2. Utilización de clases base y derivadas . . . . . . . . . . . . . . . . . . . 18
3.3. El principio de los subtipos en C++ . . . . . . . . . . . . . . . . . . . . 20
3.4. Acceso a los miembros de la clase base . . . . . . . . . . . . . . . . . . 21
3.5. Miembros protegidos en la clase base . . . . . . . . . . . . . . . . . . . 22
3.6. Redefinición de miembros de la clase base . . . . . . . . . . . . . . . . 25
3.7. Herencia y constructores . . . . . . . . . . . . . . . . . . . . . . . . . . 31
3.8. Ligadura estática vs. dinámica . . . . . . . . . . . . . . . . . . . . . . . 42
3.9. Funciones virtuales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
3.9.1. Invocación de funciones virtuales a través de un puntero . . . . . 45
3.9.2. Invocación de funciones virtuales a través de una referencia . . . 47
4. Caso práctico: aplicación gráfica 49
4.1. Identificación de objetos . . . . . . . . . . . . . . . . . . . . . . . . . . 50
4.2. Diseño de la jerarquía de clases . . . . . . . . . . . . . . . . . . . . . . 51
4.3. Diseño de la clase base . . . . . . . . . . . . . . . . . . . . . . . . . . 52
4.4. Diseño de las subclases . . . . . . . . . . . . . . . . . . . . . . . . . . 53
4.5. Estructura de datos heterogénea . . . . . . . . . . . . . . . . . . . . . 55
4.6. Operaciones globales . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
4.7. Ligadura estática . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
4.8. Funciones virtuales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
4.9. Ligadura dinámica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
4.10.Clases abstractas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
4.11.Conclusiones del caso práctico . . . . . . . . . . . . . . . . . . . . . . 63
1 Introducción
Programación Avanzada - Tema 3: Programación orientada a objetos – 4
➤ 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 ).
1.1 La herencia en el mundo real
Programación Avanzada - Tema 3: Programación orientada a objetos – 5
➤ 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.
TipoCategoría
SubtipoSubcategoría
Animal
Mamífero Ave
LeónGato Águila Paloma
1.2 La herencia y la teoría de conjuntos
Programación Avanzada - Tema 3: Programación orientada a objetos – 6
➤ El conjunto de elementos que pertenecen a un tipo incluye a los elementos que
pertenezcan a sus subtipos.
Animal
MamíferoGato León
AveÁguila Paloma
Conjuntos anidadosRelación entre tipos y subtipos
Edificio Motocicleta Conjuntos disjuntosNo hay relación de subtipadoentre estos tipos
1.3 El principio de los subtipos
Programación Avanzada - Tema 3: Programación orientada a objetos – 7
➤ 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?
2 La herencia en la POO
Programación Avanzada - Tema 3: Programación orientada a objetos – 8
➤ 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.
2 La herencia en la POO (II)
Programación Avanzada - Tema 3: Programación orientada a objetos – 9
➤ 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
superclase
Clase derivadasubclase
Empleado
ComercialMecánico
EmpleadoMecánico Comercial
2.1 Jerarquía de clases
Programación Avanzada - Tema 3: Programación orientada a objetos – 10
➤ La herencia permite
establecer jerarquías de
clases relacionadas entre sí.
➤ En el ejemplo, las subclases
heredan los datos y las
operaciones de la clase
base.
➤ Los objetos de una clase
derivada pueden acceder a
los miembros públicos
heredados de la clase base
de igual forma que acceden
a los propios.
EmpleadosueldoFijoasignarSueldo(s)sueldoBase()
MecaniconumPiezasprecioPorPiezaasignarNumPrecio(n, p)sueldo()
Comercialventas
asignarVentas(v)sueldo()
(Versión 1)
2.1 Jerarquía de clases (II)
Programación Avanzada - Tema 3: Programación orientada a objetos – 11
➤ 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
asignarSueldo(s)
sueldoBase()
asignarNumPrecio(n, p)
sueldo()
Inte
rfaz
he
reda
daIn
terf
az
prop
ia
sueldoFijo
numPiezasprecioPorPieza
Dato heredado
Datos propios
3 Herencia pública en C++
Programación Avanzada - Tema 3: Programación orientada a objetos – 12
➤ 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 fnde f EMPLEADO_H
#define EMPLEADO_H
class Empleado {
pr iva te :
f l o a t sue ldoF i j o ;
publ ic :
void asignarSueldo ( f l o a t ) ;
f l o a t sueldoBase ( ) const ;
} ;
#endi f
3 Herencia pública en C++ (II)
Programación Avanzada - Tema 3: Programación orientada a objetos – 13
➤ La implementación de la clase base no presenta ninguna particularidad:
empleado.cpp (versión 1)
#include "empleado.h"
void Empleado : : asignarSueldo ( f l o a t s ) {
sue ldoF i j o = s ;
}
f l o a t Empleado : : sueldoBase ( ) const {
return sue ldoF i j o ;
}
3.1 Clases derivadas
Programación Avanzada - Tema 3: Programación orientada a objetos – 14
➤ Un mecánico es-un tipo especial de
empleado, por lo tanto, definimos la
clase Mecanico como una subclase
de Empleado.
➤ En C++ expresamos esta relación
utilizando herencia pública . También
es posible utilizar otros tipos de
herencia, pero sólo la herencia
pública expresa la relación es-un .
➤ Con la herencia pública cada
miembro de la clase base es
heredado en la subclase
manteniendo sus privilegios de
acceso.
mecanico.h (versión 1)
# i fnde f MECANICO_H
#define MECANICO_H
#include "empleado.h"
class Mecanico : publ ic Empleado {
pr iva te :
i n t numPiezas ;
f l o a t precioPorPieza ;
publ ic :
void asignarNumPrecio ( in t , f l o a t ) ;
f l o a t sueldo ( ) const ;
} ;
#endi f
3.1 Clases derivadas (II)
Programación Avanzada - Tema 3: Programación orientada a objetos – 15
➤ 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 ;
prec ioPorPieza = p ;
}
f l o a t Mecanico : : sueldo ( ) const {
f l o a t complementos = numPiezas∗precioPorPieza ;
return sueldoBase ( ) + complementos ;
}
3.1 Clases derivadas (III)
Programación Avanzada - Tema 3: Programación orientada a objetos – 16
➤ 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 fnde f COMERCIAL_H
#define COMERCIAL_H
#include "empleado.h"
class Comercial : publ ic Empleado {
pr iva te :
f l o a t ventas ;
publ ic :
void asignarVentas ( f l o a t ) ;
f l o a t sueldo ( ) const ;
} ;
#endi f COMERCIAL
3.1 Clases derivadas (IV)
Programación Avanzada - Tema 3: Programación orientada a objetos – 17
➤ 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 : : asignarVentas ( f l o a t v ) {
ventas = v ;
}
f l o a t Comercial : : sueldo ( ) const {
f l o a t complementos = ventas ∗0.10;
return sueldoBase ( ) + complementos ;
}
3.2 Utilización de clases base y derivadas
Programación Avanzada - Tema 3: Programación orientada a objetos – 18
test.cpp (versión 1)#include < iostream >
#include "empleado.h"
#include "mecanico.h"
#include "comercial.h"
using namespace std ;
void f ( Empleado e ) {
cout < < "Desde f() e.sueldoBase() = " < < e . sueldoBase ( ) < < endl ;
}
i n t main ( ) {
Empleado e ;
e . asignarSueldo (1000) ;
cout < < "Sueldo del empleado: " < < e . sueldoBase ( ) < < endl ;
Mecanico m;
m. asignarSueldo (2000) ;
m. asignarNumPrecio (100 , 3 ) ;
cout < < "Sueldo del mecánico: " < < m. sueldo ( ) < < endl ;
3.2 Utilización de clases base y derivadas
Programación Avanzada - Tema 3: Programación orientada a objetos – 19
Comercial c ;
c . asignarSueldo (3000) ;
c . asignarVentas (2000) ;
cout < < "Sueldo del comercial: " < < c . sueldo ( ) < < endl ;
f ( e ) ;
f (m) ;
f ( c ) ;
}
salida (versión 1)
Sueldo de l empleado : 1000
Sueldo de l mecánico : 2300
Sueldo de l comerc ia l : 3200
Desde f ( ) e . sueldoBase ( ) = 1000
Desde f ( ) e . sueldoBase ( ) = 2000
Desde f ( ) e . sueldoBase ( ) = 3000
3.3 El principio de los subtipos en C++
Programación Avanzada - Tema 3: Programación orientada a objetos – 20
➤ 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
cout < < "Desde f() e.sueldo() = " < < e . sueldo ( ) < < endl ;
}
3.4 Acceso a los miembros de la clase base
Programación Avanzada - Tema 3: Programación orientada a objetos – 21
➤ 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.
3.5 Miembros protegidos en la clase base
Programación Avanzada - Tema 3: Programación orientada a objetos – 22
➤ 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.
3.5 Miembros protegidos en la clase base (II)
Programación Avanzada - Tema 3: Programación orientada a objetos – 23
➤ Declaramos el miembro sueldoFijo como protected:
empleado.h (versión 1 bis)
# i fnde f EMPLEADO_H
#define EMPLEADO_H
class Empleado {
pro tec ted :
f l o a t sue ldoF i j o ;
publ ic :
void asignarSueldo ( f l o a t ) ;
f l o a t sueldoBase ( ) const ;
} ;
#endi f
3.5 Miembros protegidos en la clase base (III)
Programación Avanzada - Tema 3: Programación orientada a objetos – 24
➤ 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∗precioPorPieza ;
return sue ldoF i j o + complementos ;
}
➤ ... aunque sigue siendo inaccesible desde el exterior:
test.cpp (versión 1 bis)i n t main ( ) {
. . .
Mecanico m;
. . .
cout < < m. sue ldoF i j o ; / / i n c o r r e c t o
}
3.6 Redefinición de miembros de la clase base
Programación Avanzada - Tema 3: Programación orientada a objetos – 25
➤ La función sueldoBase()
de la clase Empleado y las
funciones sueldo() de las
subclases tienen el mismo
objetivo: devolver el sueldo
de una persona. No hay
ninguna razón para que
tengan nombres diferentes.
➤ Si usamos el mismo
nombre, conseguiremos
uniformizar la interfaz de las
tres clases, facilitando su
uso.
EmpleadosueldoFijoasignarSueldo(s)sueldoBase()sueldo()
MecaniconumPiezasprecioPorPiezaasignarNumPrecio(n, p)sueldo()
Comercialventas
asignarVentas(v)sueldo()
(Versión 2)
3.6 Redefinición de miembros de la clase base (II)
Programación Avanzada - Tema 3: Programación orientada a objetos – 26
➤ 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 {
pr iva te :
f l o a t sue ldoF i j o ;
publ ic :
void asignarSueldo ( f l o a t ) ;
f l o a t sueldo ( ) const ;
} ;
empleado.cpp (versión 2)
. . .
f l o a t Empleado : : sueldo ( ) const {
return sue ldoF i j o ;
}
3.6 Redefinición de miembros de la clase base (III)
Programación Avanzada - Tema 3: Programación orientada a objetos – 27
➤ 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∗precioPorPieza ;
return Empleado : : sueldo ( ) + complementos ;
}
➭ El mismo cambio lo realizamos también en la clase Comercial .
3.6 Redefinición de miembros de la clase base (IV)
Programación Avanzada - Tema 3: Programación orientada a objetos – 28
test.cpp (versión 2)#include < iostream >
#include "empleado.h"
#include "mecanico.h"
#include "comercial.h"
using namespace std ;
void f ( Empleado e ) {
cout < < "Desde f() e.sueldo() = " < < e . sueldo ( ) < < endl ;
}
i n t main ( ) {
Empleado e ;
e . asignarSueldo (1000) ;
cout < < "Sueldo del empleado: " < < e . sueldo ( ) < < endl ;
Mecanico m;
m. asignarSueldo (2000) ;
m. asignarNumPrecio (100 , 3 ) ;
cout < < "Sueldo del mecánico: " < < m. sueldo ( ) < < endl ;
3.6 Redefinición de miembros de la clase base (IV)
Programación Avanzada - Tema 3: Programación orientada a objetos – 29
Comercial c ;
c . asignarSueldo (3000) ;
c . asignarVentas (2000) ;
cout < < "Sueldo del comercial: " < < c . sueldo ( ) < < endl ;
f ( e ) ;
f (m) ;
f ( c ) ;
}
salida (versión 2)
Sueldo de l empleado : 1000
Sueldo de l mecánico : 2300
Sueldo de l comerc ia l : 3200
Desde f ( ) e . sueldo ( ) = 1000
Desde f ( ) e . sueldo ( ) = 2000
Desde f ( ) e . sueldo ( ) = 3000
3.6 Redefinición de miembros de la clase base (V)
Programación Avanzada - Tema 3: Programación orientada a objetos – 30
➤ 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 jecu ta l a func ión de l a c lase Mecanico
/ / y nunca l a de l a c lase Empleado
}
3.7 Herencia y constructores
Programación Avanzada - Tema 3: Programación orientada a objetos – 31
➤ 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 .
3.7 Herencia y constructores (II)
Programación Avanzada - Tema 3: Programación orientada a objetos – 32
➤ Partiendo de la versión 2, añadimos
constructores a nuestras clases.
➤ Aprovechamos la ocasión para
incorporar a la clase base el nombre
del empleado y el operador de salida.
➤ Para simplificar el código,
eliminamos las funciones
Empleado::asignarSueldo(),
Mecanico::asignarNumPrecio()
y Comercial::asignarVentas().
EmpleadonombresueldoFijoEmpleado(n, s)sueldo()operator<<(canal, emp)
MecaniconumPiezasprecioPorPiezaMecanico(n, s, num, pre)sueldo()
Comercialventas
Comercial(n, s, v)sueldo()
(Versión 3)
3.7 Herencia y constructores (III)
Programación Avanzada - Tema 3: Programación orientada a objetos – 33
empleado.h (versión 3)# i fnde f EMPLEADO_H
#define EMPLEADO_H
#include < iostream >
#include < s t r i n g >
using namespace std ;
class Empleado {
pr iva te :
s t r i n g nombre ;
f l o a t sue ldoF i j o ;
publ ic :
Empleado ( s t r i n g , f l o a t ) ;
f l o a t sueldo ( ) const ;
f r iend ostream & operator <<(ostream & , Empleado ) ;
} ;
#endi f
3.7 Herencia y constructores (IV)
Programación Avanzada - Tema 3: Programación orientada a objetos – 34
empleado.cpp (versión 3)
#include "empleado.h"
#include < iostream >
#include < s t r i n g >
using namespace std ;
Empleado : : Empleado ( s t r i n g n , f l o a t s )
: nombre ( n ) , sue ldoF i j o ( s )
{ }
f l o a t Empleado : : sueldo ( ) const {
return sue ldoF i j o ;
}
ostream & operator <<(ostream & canal , Empleado e ) {
canal < < e . nombre ;
return canal ;
}
3.7 Herencia y constructores (V)
Programación Avanzada - Tema 3: Programación orientada a objetos – 35
mecanico.h (versión 3)
# i fnde f MECANICO_H
#define MECANICO_H
#include "empleado.h"
#include < s t r i n g >
using namespace std ;
class Mecanico : publ ic Empleado {
pr iva te :
i n t numPiezas ;
f l o a t precioPorPieza ;
publ ic :
Mecanico ( s t r i n g , f l oa t , in t , f l o a t ) ;
f l o a t sueldo ( ) const ; / / ocu l t a Empleado : : sueldo ( )
} ;
#endi f
3.7 Herencia y constructores (VI)
Programación Avanzada - Tema 3: Programación orientada a objetos – 36
mecanico.cpp (versión 3)
#include "empleado.h"
#include "mecanico.h"
#include < s t r i n g >
using namespace std ;
Mecanico : : Mecanico ( s t r i n g n , f l o a t s , i n t piezas , f l o a t prec io )
: Empleado ( n , s ) , numPiezas ( piezas ) , prec ioPorPieza ( p rec io )
{ }
f l o a t Mecanico : : sueldo ( ) const {
f l o a t complementos = numPiezas∗precioPorPieza ;
return Empleado : : sueldo ( ) + complementos ;
}
3.7 Herencia y constructores (VII)
Programación Avanzada - Tema 3: Programación orientada a objetos – 37
comercial.h (versión 3)
# i fnde f COMERCIAL_H
#define COMERCIAL_H
#include "empleado.h"
#include < s t r i n g >
using namespace std ;
class Comercial : publ ic Empleado {
pr iva te :
f l o a t ventas ;
publ ic :
Comercial ( s t r i n g , f l oa t , f l o a t ) ;
f l o a t sueldo ( ) const ; / / ocu l t a Empleado : : sueldo ( )
} ;
#endi f
3.7 Herencia y constructores (VIII)
Programación Avanzada - Tema 3: Programación orientada a objetos – 38
comercial.cpp (versión 3)
#include "comercial.h"
#include "empleado.h"
#include < s t r i n g >
using namespace std ;
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.10;
return Empleado : : sueldo ( ) + complementos ;
}
3.7 Herencia y constructores (IX)
Programación Avanzada - Tema 3: Programación orientada a objetos – 39
test.cpp (versión 3)#include "empleado.h"
#include "mecanico.h"
#include "comercial.h"
#include < iostream >
using namespace std ;
void f ( Empleado e ) {
cout < < "Desde f(): " ;
cout < < "Sueldo de " < < e < < ": " < < e . sueldo ( ) < < endl ;
}
i n t main ( ) {
Empleado e ("Maria" , 1000) ;
cout < < "Sueldo de " < < e < < ": " < < e . sueldo ( ) < < endl ;
Mecanico m("Ramon" ,2000 , 100 , 3 ) ;
cout < < "Sueldo de " < < m < < ": " < < m. sueldo ( ) < < endl ;
3.7 Herencia y constructores (IX)
Programación Avanzada - Tema 3: Programación orientada a objetos – 40
Comercial c ("Luisa" , 3000 , 2000) ;
cout < < "Sueldo de " < < c < < ": " < < c . sueldo ( ) < < endl ;
f ( e ) ;
f (m) ;
f ( c ) ;
}
salida (versión 3)
Sueldo de Maria : 1000
Sueldo de Ramon : 2300
Sueldo de Luisa : 3200
Desde f ( ) : Sueldo de Maria : 1000
Desde f ( ) : Sueldo de Ramon : 2000
Desde f ( ) : Sueldo de Luisa : 3000
3.7 Herencia y constructores (X)
Programación Avanzada - Tema 3: Programación orientada a objetos – 41
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.
3.8 Ligadura estática vs. dinámica
Programación Avanzada - Tema 3: Programación orientada a objetos – 42
➤ 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.
3.8 Ligadura estática vs. dinámica (II)
Programación Avanzada - Tema 3: Programación orientada a objetos – 43
➤ 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.
3.9 Funciones virtuales
Programación Avanzada - Tema 3: Programación orientada a objetos – 44
➤ Deseamos que en nuestra jerarquía de
clases Empleado, Mecanico y
Comercial la función sueldo() sea
invocada desde la función f()
utilizando ligadura dinámica . Para
ello debemos:
1. Declarar como virtual la función
sueldo() en la clase base (sólo es
necesario hacerlo en la clase
base).
2. Modificar la implementación de f()
para que la llamada a la función
sueldo() se realice a través de un
puntero o una referencia.
EmpleadonombresueldoFijoEmpleado(n, s)virtual sueldo()operator<<(canal, emp)
MecaniconumPiezasprecioPorPiezaMecanico(n, s, num, pre)sueldo()
Comercialventas
Comercial(n, s, v)sueldo()
(Versión 4)
3.9.1 Invocación de funciones virtuales a través de un puntero
Programación Avanzada - Tema 3: Programación orientada a objetos – 45
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 ∗ pt r_e ) {
cout < < "Desde f(): " ;
cout < < "Sueldo de " < < (∗ pt r_e ) < < ": " < < ptr_e−>sueldo ( ) < < endl ;
}
i n t main ( ) {
. . .
f (&e ) ;
f (&m) ;
f (&c ) ;
}
3.9.1 Invocación de funciones virtuales a través de un puntero (II)
Programación Avanzada - Tema 3: Programación orientada a objetos – 46
salida (versión 4)Sueldo de Maria : 1000
Sueldo de Ramon : 2300
Sueldo de Luisa : 3200
Desde f ( ) : Sueldo de Maria : 1000
Desde f ( ) : Sueldo de Ramon : 2300
Desde f ( ) : Sueldo de Luisa : 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.
3.9.2 Invocación de funciones virtuales a través de una referencia
Programación Avanzada - Tema 3: Programación orientada a objetos – 47
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 ) {
cout < < "Desde f(): " ;
cout < < "Sueldo de " < < e < < ": " < < e . sueldo ( ) < < endl ;
}
i n t main ( ) {
. . .
f ( e ) ;
f (m) ;
f ( c ) ;
}
3.9.2 Invocación de funciones virtuales a través de una referencia (II)
Programación Avanzada - Tema 3: Programación orientada a objetos – 48
salida (versión 5)
Sueldo de Maria : 1000
Sueldo de Ramon : 2300
Sueldo de Luisa : 3200
Desde f ( ) : Sueldo de Maria : 1000
Desde f ( ) : Sueldo de Ramon : 2300
Desde f ( ) : Sueldo de Luisa : 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.
4 Caso práctico: aplicación gráfica
Programación Avanzada - Tema 3: Programación orientada a objetos – 49
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.
4.1 Identificación de objetos
Programación Avanzada - Tema 3: Programación orientada a objetos – 50
➤ 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
4.2 Diseño de la jerarquía de clases
Programación Avanzada - Tema 3: Programación orientada a objetos – 51
Rectángulobase, altura
dibujar,área, perímetro
ElipseejeMayor, ejeMenor
dibujar,área, perímetro
Círculoradio
área, perímetro
Cuadrado
área, perímetro
Triángulovérticesdibujar,
área, perímetro
Figurax, y, fondo, bordemover, dibujar,área, perímetro
A B
Clase A hereda de clase B
Notación Booch
4.3 Diseño de la clase base
Programación Avanzada - Tema 3: Programación orientada a objetos – 52
class Figura {
Punto pos ic ion ;
Color fondo , borde ;
/ / . . .
publ ic :
F igura ( Punto p , Color c1 , Color c2 ) ;
void mover ( Punto p ) ;
void d i b u j a r ( ) const ;
f l o a t area ( ) const ;
f l o a t per imet ro ( ) const ;
/ / . . .
} ;
void Figura : : mover ( Punto p ) {
pos ic ion = p ;
}
Color
Figura
Punto
A BClase A contiene
una instancia de clase B
Notación Booch
11
1
2
4.4 Diseño de las subclases
Programación Avanzada - Tema 3: Programación orientada a objetos – 53
class El ipse : publ ic Figura {
f l o a t ejeMenor , ejeMayor ;
publ ic :
E l ipse ( 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 per imet ro ( ) const ;
} ;
f l o a t El ipse : : area ( ) const {
return p i∗ejeMenor∗ejeMayor ;
}
void El ipse : : d i b u j a r ( ) const {
/ / . . .
}
4.4 Diseño de las subclases (II)
Programación Avanzada - Tema 3: Programación orientada a objetos – 54
class Rectangulo : publ ic Figura {
f l o a t base , a l t u r a ;
publ ic :
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 per imet ro ( ) const ;
} ;
f l o a t Rectangulo : : area ( ) const {
return base∗a l t u r a ;
}
void Rectangulo : : d i b u j a r ( ) const {
/ / . . .
}
4.5 Estructura de datos heterogénea
Programación Avanzada - Tema 3: Programación orientada a objetos – 55
i n t main ( ) {
F igura ∗ v [N ] ;
v [ 0 ] =new Rectangulo ( . . . ) ;
v [ 1 ] =new El ipse ( . . . ) ;
v [ 2 ] =new Ci r cu lo ( . . . ) ;
v [ 3 ] =new Cuadrado ( . . . ) ;
/ / . . .
d i b u j a r ( v ) ; / / d i bu ja todas las f i g u r a s
f l o a t area = areaTota l ( v ) ; / / suma de las áreas
}
Definimos una estructura de datos heterogénea
(un simple vector de punteros) que nos permita
almacenar objetos de tipos diferentes. Recuerda
que los punteros a objetos de una clase base
pueden apuntar a objetos de cualquier clase
derivada.
1
2
3
0
unRectánguloatributos
unaElipseatributos
1
atributosunCírculo
atributos
v[0]
v[1]
v[2]
v[3]
… …
0
2
3unCuadrado
4.6 Operaciones globales
Programación Avanzada - Tema 3: Programación orientada a objetos – 56
void d i b u j a r ( F igura ∗ v [ ] ) {
fo r ( i n t i = 0 ; i <N ; i ++)
v [ i ]−> d i b u j a r ( ) ;
}
f l o a t areaTota l ( F igura ∗ v [ ] ) {
f l o a t at =0;
fo r ( i n t i = 0 ; i <N ; i ++)
a t = a t + v [ i ]−>area ( ) ;
return at ;
}
Se pretende que estas dos operaciones
globales operen sobre el conjunto de figuras
almacenadas en la estructura de datos. ¿Lo
conseguimos realmente? Observemos la llamada
v[i]->area() ...
1
2
3
0
unRectánguloatributos
unaElipseatributos
1
atributosunCírculo
atributos
v[0]
v[1]
v[2]
v[3]
… …
0
2
3unCuadrado
4.7 Ligadura estática
Programación Avanzada - Tema 3: Programación orientada a objetos – 57
➤ 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 areaTota l ( F igura ∗ v [ ] ) {
f l o a t at =0;
fo r ( i n t i = 0 ; i <N ; i ++)
a t = a t + v [ i ]−>area ( ) ;
return at ;
}
unaElipseatributos
v[1]
Figura * Figura
v[1]->area( )
unaElipse
atributos/métodosde Figura
atributos/métodosde Elipse
Figura::area( )
4.8 Funciones virtuales
Programación Avanzada - Tema 3: Programación orientada a objetos – 58
➤ La palabra reservada virtual permite retrasar la decisión de qué método debe
invocarse hasta el momento de la ejecución.
class Figura {
Punto pos ic ion ;
Color fondo , borde ;
/ / . . .
publ ic :
F igura ( Punto p , Color c1 , Color 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 per imet ro ( ) const ;
/ / . . .
} ;
4.9 Ligadura dinámica
Programación Avanzada - Tema 3: Programación orientada a objetos – 59
➤ 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 areaTota l ( F igura ∗ v [ ] ) { / / s i n cambios respecto a l a vers ión a n t e r i o r
f l o a t at =0;
fo r ( i n t i = 0 ; i <N ; i ++)
a t = a t + v [ i ]−>area ( ) ;
return at ;
}
unaElipseatributos
v[1]
Figura * Figura
v[1]->area( )
unaElipse
atributos/métodosde Figura
atributos/métodosde Elipse
Elipse::area( )
4.9 Ligadura dinámica (II)
Programación Avanzada - Tema 3: Programación orientada a objetos – 60
➤ Añadimos la subclase PoligonoLibre a la jerarquía:
4.9 Ligadura dinámica (III)
Programación Avanzada - Tema 3: Programación orientada a objetos – 61
➤ La función areaTotal()
sigue siendo válida para la
nueva figura:
i n t main ( ) {
F igura ∗ v [N ] ;
/ / . . .
v [ 3 ] =new Cuadrado ( . . . ) ;
v [ 4 ] =new Pol igonoL ib re ( . . . ) ;
/ / . . .
f l o a t area=areaTota l ( v ) ;
f l o a t areaTota l ( F igura ∗ v [ ] ) {
f l o a t at =0;
fo r ( i n t i = 0 ; i <N ; i ++)
a t = a t + v [ i ]−>area ( ) ;
return at ;
}
unPoligonoLibreatributos
v[4]
Figura * Figura
v[4]->area( )
atributos/métodosde Figura
atributos/métodosde Elipse
PoligonoLibre::area( )
unPoligonoLibre
4.10 Clases abstractas
Programación Avanzada - Tema 3: Programación orientada a objetos – 62
➤ 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 Figura {
/ / . . .
publ ic :
F igura ( Punto p , Color c1 , Color 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 per imet ro ( ) const = 0 ;
/ / . . .
} ;
4.11 Conclusiones del caso práctico
Programación Avanzada - Tema 3: Programación orientada a objetos – 63
➤ 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.
Fin
Copyright c© 2004 José Luis Llopis Borrás
Realizada con ujislides c© 2002-3 Sergio Barrachina ([email protected])