Tema3

64
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

Transcript of Tema3

Page 1: 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

Page 2: Tema3

Í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

Page 3: Tema3

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

Page 4: Tema3

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 ).

Page 5: Tema3

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

Page 6: Tema3

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

Page 7: Tema3

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?

Page 8: Tema3

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.

Page 9: Tema3

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

Page 10: Tema3

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)

Page 11: Tema3

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

Page 12: Tema3

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

Page 13: Tema3

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 ;

}

Page 14: Tema3

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

Page 15: Tema3

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 ;

}

Page 16: Tema3

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

Page 17: Tema3

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 ;

}

Page 18: Tema3

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 ;

Page 19: Tema3

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

Page 20: Tema3

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 ;

}

Page 21: Tema3

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.

Page 22: Tema3

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.

Page 23: Tema3

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

Page 24: Tema3

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

}

Page 25: Tema3

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)

Page 26: Tema3

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 ;

}

Page 27: Tema3

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 .

Page 28: Tema3

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 ;

Page 29: Tema3

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

Page 30: Tema3

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

}

Page 31: Tema3

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 .

Page 32: Tema3

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)

Page 33: Tema3

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

Page 34: Tema3

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 ;

}

Page 35: Tema3

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

Page 36: Tema3

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 ;

}

Page 37: Tema3

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

Page 38: Tema3

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 ;

}

Page 39: Tema3

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 ;

Page 40: Tema3

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

Page 41: Tema3

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.

Page 42: Tema3

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.

Page 43: Tema3

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.

Page 44: Tema3

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)

Page 45: Tema3

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 ) ;

}

Page 46: Tema3

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.

Page 47: Tema3

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 ) ;

}

Page 48: Tema3

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.

Page 49: Tema3

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.

Page 50: Tema3

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

Page 51: Tema3

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

Page 52: Tema3

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

Page 53: Tema3

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 {

/ / . . .

}

Page 54: Tema3

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 {

/ / . . .

}

Page 55: Tema3

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

Page 56: Tema3

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

Page 57: Tema3

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( )

Page 58: Tema3

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 ;

/ / . . .

} ;

Page 59: Tema3

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( )

Page 60: Tema3

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:

Page 61: Tema3

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

Page 62: Tema3

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 ;

/ / . . .

} ;

Page 63: Tema3

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.

Page 64: Tema3

Fin

Copyright c© 2004 José Luis Llopis Borrás

Realizada con ujislides c© 2002-3 Sergio Barrachina ([email protected])