Análisis y Diseño de Algoritmos
Prof: Ing. Victor Garro
Asistente: Marco Elizondo Vargas
PROGRAMACION
EN C++
Capitulo
11 Herencia y jerarquia de clases
Introducción
Una de las propiedades más importantes de la POO es la
abstracción de datos. Esta propiedad se manifiesta esencialmente en la encapsulación,
que es la responsable de gestionar la complejidad de grandes programas al
permitir la definición de nuevos tipos de datos: las clases.
Sin embargo, la clase no es suficiente por sí sola para soportar diseño y
programación orientada a objetos. Se necesita un medio para relacionar unas
clases con otras. Un medio son las clases anidadas, pero sólo se resuelve
parcialmente el problema, ya que las clases anidadas no tienen las
características requeridas para relacionar totalmente una clase con otra. Se necesita
un mecanismo para crear jerarquías de clases en las que la clase A sea afín a
la clase B, pero con la posibilidad de poder añadirle también características
propias. Este mecanismo es la herencia.
La herencia es, seguramente, la
característica más potente de la POO, después de las clases. Por herencia
conocemos el proceso de crear nuevas clases, llamadas clases derivadas, a
partir de una clase. base.
En C++ la herencia se manifiesta con la creación de un tipo definido por el
usuario (clase), que puede heredar las características de otra clase ya
existente o derivar las suyas a otra nueva clase. Cuando se hereda, las clases
derivadas reciben las características (estructuras de datos y funciones) de la
clase original, a las que se pueden añadir nuevas características o modificar
las características heredadas. El compilador hace realmente una copia de la
clase base en la nueva clase derivada y permite al programador añadir o
modificar miembros sin alterar el código de la clase base.
La derivación de clases consigue la reutilización efectiva del código de la
clase base para sus necesidades. Si se tiene una clase base totalmente
depurada, la herencia ayuda a reutilizar ese código en una nueva clase. No es
necesario comprender el código fuente de la clase original, sino sólo lo que
hace.
Clases derivadas
En C++, la herencia simple se realiza tomando una clase
existente y derivando nuevas clases de ella. La clase derivada hereda las estructuras de datos y funciones de la
clase original. Además, se pueden añadir nuevos miembros a las clases
derivadas y los miembros heredados pueden ser modificados.
Una clase utilizada para derivar nuevas clases se denomina clase base, clase padre,
superclase o ascendiente. Una clase
creada de otra clase se denomina clase derivada o subclase.
Se pueden construir jerarquías de clases, en las que cada clase sirve como
padre o raíz de una nueva clase.
Conceptos fundamentales de
derivación
C++ utiliza un sistema de herencia jerárquica. Es decir, se hereda una clase de otra, creando
nuevas clases a partir de las clases ya existentes. Sólo se pueden heredar
clases, no funciones ordinarias n variables, en C++.
Una clase derivada hereda todos los
miembros dato excepto, miembros dato estáticos, de cada una de sus clases base.
Una clase derivada hereda las funciones miembro de su clase base. Esto
significa que se hereda la capacidad para llamar a funciones miembro de la
clase base en los objetos de la clase derivada.
Los siguientes elementos de la clase no se heredan:
- Constructores |
- Destructores |
- Funciones amigas |
- Funciones estáticas de la clase |
- Datos estáticos de la clase |
- Operador de asignación sobrecargado |
Las clases base diseñadas con el objetivo principal de
ser heredadas por otras se denominan clases abstractas. Normalmente, no se
crean instancias a partir de clases abstractas, aunque sea posible.
La herencia en C++
En C++ existen dos tipos de herencia: simple y múltiple. La herencia simple
es aquella en la que cada clase derivada hereda de una única clase. En herencia
simple, cada clase tiene un solo ascendiente. Cada clase puede tener, sin
embargo, muchos descendientes.
La herencia múltiple es aquella en la
cual una clase derivada tiene más de una clase base. Aunque el concepto de
herencia múltiple es muy útil, el diseño de clases suele ser más complejo.
Creación de una clase derivada
Cada clase derivada se debe referir a una clase base
declarada anteriormente.
La declaración de una clase derivada tiene la siguiente sintaxis:
class
clase_derivada:<especificadores_de_acceso> clase_base {...};
Los
especificadores de acceso pueden ser: public,
protected o private.
Clases de derivación
Los especificadores de acceso a las clases base definen
los posibles tipos de derivación: public,
protected y private. El tipo de acceso a la clase base especifica cómo recibirá
la clase derivada a los miembros de la clase base. Si no se especifica un
acceso a la clase base, C++ supone que su tipo de herencia es privado.
- Derivación
pública (public). Todos los miembros public y protected de la clase base
son accesibles en la clase derivada, mientras que los miembros private de la
clase base son siempre inaccesibles en la clase derivada.
- Derivación
privada (private). Todos los miembros de la clase base se comportan como
miembros privados de la clase derivada. Esto significa que los miembros public
y protected de la clase base no son accesibles más que por las funciones
miembro de la clase derivada. Los miembros privados de la clase siguen siendo
inaccesibles desde la clase derivada.
- Derivación
protegida (protected). Todos los miembros public y protected de la clase
base se comportan como miembros protected de la clase derivada. Estos miembros
no son, pues, accesibles al programa exterior, pero las clases que se deriven a
continuación podrán acceder normalmente a estos miembros (datos o funciones).
Constructores y destructores en
herencia
Una clase derivada puede tener tanto constructores como
destructores, aunque tiene el problema adicional de que la clase base puede
tomar ambas funciones miembro especiales.
Un constructor o destructor definido en la clase base debe estar coordinado con
los encontrados en una clase derivada. Igualmente importante es el movimiento
de valores de los miembros de la clase derivada a los miembros que se
encuentran en la base. En particular, se debe considerar cómo el constructor de
la clase base recibe valores de la clase derivada para crear el objeto
completo.
Si un constructor se define tanto para la clase base como
para la clase derivada, C++ llama primero al constructor base. Después que el
constructor de la base termina sus tareas, C++ ejecuta el constructor derivado.
Cuando una clase base define un constructor, éste se debe
llamar durante la creación de cada instancia de una clase derivada, para
garantizar la buena inicialización de los datos miembro que la clase derivada
hereda de la clase base. En este caso la clase derivada debe definir a su vez
un constructor que llama al constructor de la clase base proporcionándole los
argumentos requeridos.
Un constructor de una clase derivada debe utilizar un mecanismo de pasar
aquellos argumentos requeridos por el correspondiente constructor de la clase base.
derivada::derivada(tipo1 x, tipo2 y):base
(x,y) {...}
Otro aspecto
importante que es necesario considerar es el orden en el que el compilador C++
inicializa las clases base de una clase derivada. Cuando El compilador C++
inicializa una instancia de una clase derivada, tiene que inicializar todas las
clases base primero.
Por definición, un destructor de clases no toma parámetros. Al contrario que
los constructores, una función destructor de una clase derivada se ejecuta
antes que el destructor de la clase base.
Redefinición de funciones miembro
heredadas
Se pueden utilizar funciones miembro en una clase
derivada que tengan el mismo nombre que otra de una clase base.
La redefinición de funciones se realiza mediante funciones miembro
sobrecargadas en la clase derivada. Una función miembro redefinida oculta todas
las funciones miembro heredadas del mismo nombre. Por tanto cuando en la clase
base y en la clase derivada hay una función con el mismo nombre en las dos, se
ejecuta la función de la clase derivada.
Herencia múltiple
Es la propiedad con la cual una clase derivada puede tener más de una clase base. Es más
adecuada para definir objetos que son compuestos, por naturaleza, tales como un
registro personal, un objeto gráfico.
Sólo es una extensión de la sintaxis de la clase derivada. Introduce una cierta
complejidad en el lenguaje y el compilador, pero proporciona grandes
beneficios.
class ejemplo: private base1, private base2 {...};
La aplicación de
clases base múltiples introduce un conjunto de ambigüedades a los programas
C++. Una de las más comunes se da cuando dos clases base tienen funciones con
el mismo nombre, y sin embargo, una clase derivada de ambas no tiene ninguna
función con ese nombre. ¿ Cómo acceden los objetos de la clase derivada a la
función correcta de la clase base ? El nombre de la función no es suficiente,
ya que el compilador no puede deducir cuál de las dos funciones es la invocada.
Si se tiene una clase C que se deriva de dos clases base A y B, ambas con una
función mostrar() y se define un objeto de la clase C: C objetoC, la manera de resolver la
ambigüedad es utilizando el operador de resolución de ámbito:
objetoC.A::mostrar(); //se
refiere a la versión de //mostrar() de la clase A
objetoC.B::mostrar(); //se refiere a la
versión de //mostrar() de la clase B
Constructores y destructores en
herencia múltiple
El uso de funciones constructor y destructor en clases
derivadas es más complejo que en una clase simple. Al crear clases derivadas
con una sola clase base, se observa que el constructor de la clase base se
llama siempre antes que el constructor de la clase derivada.
Además se dispone de un mecanismo para pasar los valores de los parámetros
necesarios al constructor base desde el constructor de la clase derivada. Así,
la sentencia siguiente pasa el puntero a carácter x y el valor del entero y a
la clase base:
derivada (char *x, int y, double z): base(x,y)
Este método se expande para efectuar más de una clase
base.La sentencia:
derivada (char *x,int y, double
z): base1(x),base2(y)
Llama al constructor base1 y pasa el valor de cadena de
caracteres x y al constructor base2 le proporciona un valor entero y. Como con
la herencia simple, los constructores de la clase base se llaman antes de que
se ejecuten al constructor de la clase derivada. Los constructores se llaman en
el orden en que se declaran las clases base.
De modo similar, las relaciones entre el destructor de la clase derivada y el
destructor de la clase base se mantiene. El destructor derivado se llama antes
de que se llame a los destructores de cualquier base. Los destructores de las
clases base se llaman en orden inverso al orden en que los objetos base se
crearon.
Herencia repetida
La primera regla de la herencia múltiple es que una clase
derivada no puede heredar más de una vez de una sola clase, al menos no
directamente. Sin embargo, es posible que una clase se pueda derivar dos o más
veces por caminos distintos, de modo que se puede reprtir una clase. La
propiedad de recibir por herencia una misma clase más de una vez se conoce como
herencia repetida.
Clases base virtuales
Una clase base virtual es una clase que es compartida por
otras clases base con independencia del número de veces que esta clase se
produce en la jerarquía de derivación. Suponer , por ejemplo, que la clase T se
deriva de las clases C y D cada una de las cuales se deriva de la clase A. Esto
significa que la clase T tiene dos instancias de A en su jerarquía de
derivación. C++ permite instancias múltiples de la misma clase base. Sin
embargo, si sólo se desea una instancia de la clase A para la clase T, entonces
se debe declarar A como una clase base virtual.
Las clases base virtuales proporcionan un método de anular el mecanismo de
herencia múltiple, permitiendo especificar una clase que es una clase base
compartida.
Una clase derivada puede tener instancias virtuales y no virtuales de la misma
clase base.