Herencia I

100% EN ESPAÑOL


Programando en C++
Herencia I
En esta lección:

Una razón para utilizar la herencia es que permite reutilizar código de proyectos anteriores dándole la flexibilidad de hacer ligeras modificaciones si el viejo código no hace exactamente lo que Usted necesita en su nuevo proyecto. No tiene sentido empezar cada nuevo proyecto desde cero ya que una parte del código ciertamente será reutilizado en varios programas, sin embargo, es fácil cometer un error si Usted trata de modificar la clase original. Otra razón para utilizar la herencia es si el proyecto requiere de varias clases las cuales son muy similares pero con algunas diferencias. En éste capítulo nos concentraremos en el mecanismo de la herencia y cómo implementarla en un programa. C++ permite heredar toda o una parte de los miembros ó métodos de una clase, modificar algunos y agregar nuevos miembros si no están disponibles en la clase padre.


Una clase simple

El archivo de cabecera llamado vehiculo.h contiene una clase sencilla que utilizaremos para empezar nuestro estudio de la herencia, consiste de cuatro métodos los cuales pueden utilizarse para manipular los datos relativos a nuestro vehiculo, lo que hace cada método no es especialmente importante en éste momento, eventualmente nos referiremos a ésta clase como la clase base ó padre, pero mientras tanto la utilizaremos como cualquier otra clase para demostrar que no hay nada que la diferencíe de otras clases, la palabra clave protected la estudiaremos más adelante en ésta misma lección.


// Archivo de cabecera vehiculo.h

#ifndef VEHICULO_H
#define VEHICULO_H

class vehiculo
{
    protected:
    int llantas;
    float peso;
    public:
    void inicializa(int in_llantas, float in_peso);
    int obtiene_llantas(void);
    float obtiene_peso(void);
    float llanta_carga(void);
};

#endif

La implementación de vehiculo

En el siguiente código, llamado vehiculo.cpp encontrará la implementación de la clase vehiculo. El método inicializa ( ) los valores de entrada como parámetros a las variables llantas y peso. Disponemos de métodos para retornar el número de llantas y el peso y por último, tenemos un método que hace un cálculo trivial que retorna la carga en cada llanta. Más adelante veremos métodos que hagan procesos más significativos, pero en éste momento lo importante es comprender como implementar la herencia de clases.


// Archivo vehiculo.cpp

#include "vehiculo.h"

// inicializa a cualquier dato deseado
void vehiculo::inicializa(int in_llantas, float in_peso)
{
    llantas = in_llantas;
    peso = in_peso;
}

// obtiene el numero de llantas del vehiculo
int vehiculo::obtiene_llantas()
{
    return llantas;
}

// retorna el peso del vehiculo
float vehiculo::obtiene_peso()
{
    return peso;
}

// retorna el peso en cada llanta
float vehiculo::llanta_carga()
{
    return peso/llantas;
}

Utilizando la clase vehiculo

Se recomienda crear una carpeta para guardar en ella los archivos vehiculo.h y vehiculo.cpp ya que serán utilizados en los siguientes ejemplos, empezando con el siguiente programa cuyo código llamado transpte.cpp utiliza la clase vehiculo que nosotros haremos un poco especial utilizandola sin modificación como clase base en los próximos ejemplos para ilustrar la herencia, ésta utiliza una clase existente y le agrega funcionalidad para cumplir una tarea posiblemente más compleja.


// Archivo transpte.cpp

#include <iostream.h>
#include "vehiculo.h"

int main()
{
    vehiculo carro, moto, camioneta, sedan;

    carro.inicializa(4, 3000.0);
    moto.inicializa(2, 900.0);
    camioneta.inicializa(18, 45000.0);
    sedan.inicializa(4, 3000.0);

    cout << "El carro tiene " << carro.obtiene_llantas() << " llantas.\n";
    cout << "La camioneta tiene una capacidad de " << camioneta.llanta_carga()
         << " libras por llanta.\n";
    cout << "La moto pesa " << moto.obtiene_peso() << " libras.\n";
    cout << "El sedan pesa " << sedan.obtiene_peso() << " libras, y tiene "
         << sedan.obtiene_llantas() << " llantas.\n";

    return 0;
}

No debe haber problema alguno para comprender la operación del programa, éste declara cuatro objetos de la clase vehiculo, los inicializa y despliega algunos datos para ilustrar que la clase vehiculo puede ser utilizada como una clase simple porque es en realidad una clase simple. Nos referimos a ella como una clase simple en contraste a llamarla clase base ó clase derivada tal y como lo haremos muy pronto. Compile y ejecute el programa, incluyendo los archivos vehiculo.cpp y vehiculo.h, el resultado es el siguiente:

Utilizando la clase vehiculo

Volver al principio


Una clase derivada

Como primer ejemplo del uso de una clase derivada ó clase hija tenemos el archivo llamado carro.h. La clase vehiculo es heredada debido al enunciado class carro : public vehiculo en la línea 8. Esta clase derivada llamada carro está compuesta de toda la información incluida en la clase base vehiculo, y de su propia información adicional. Para ir un paso más lejos, aunque la clase vehiculo será utilizada como clase base en un programa de ejemplo más adelante en éste capítulo, no hay razón para continuar utilizandola como una simple clase en el programa del ejemplo anterior, de hecho, puede ser utilizada como clase simple y como clase base en el mismo programa. La respuesta a cuándo es una clase simple y cuándo es una clase base depende en cómo sea utilizada.


// Archivo de cabecera carro.h

#ifndef CARRO_H
#define CARRO_H

#include "vehiculo.h"

class carro : public vehiculo
{
    int carga_pasajeros;
    public:
    void inicializa(int in_llantas, float in_peso, int personas = 4);
    int pasajeros(void);
};

#endif

Es necesario discutir cierta terminología. Cuando hablamos de Programación Orientada a Objetos en general, una clase que hereda otra es a menudo llamada clase derivada ó clase hija, pero el término más adecuado de acuerdo a la definición de C++, es clase derivada. De la misma manera, el término adecuado en C++ para la clase heredada es llamarla clase base aunque los términos clase padre y super clase son también utilizados.

Una clase base es aquella que cubre un amplio rango de objetos, en tanto que una clase derivada está algo más restringida pero al mismo tiempo más útil. Por ejemplo si tenemos una clase base llamada lenguaje de programación y una clase derivada llamada C++, entonces podríamos utilizar la clase base para definir Pascal, Ada, C++ o cualquier otro lenguaje de programacion, pero no puede decirnos nada acerca del uso de las clases en C++ ya que solo puede dar una visión general de cada lenguaje. Por otra parte, la clase derivada llamada C++ puede definir el uso de las clases pero no se puede utilizar para describir otros lenguajes. Una clase base tiende a ser más general y una clase derivada es más específica.

En el caso de nuestro ejemplo, la clase base vehiculo puede utilizarse para declarar objetos que representen camiones, carros, bicicletas o cualquier otro tipo de vehiculo. La clase llamada carro puede utilizarse solamente para declarar un objeto de tipo carro ya que hemos limitado los tipos de datos que pueden utilizarse inteligentemente con ésta clase. La clase carro es por lo tanto más restrictiva y específica que la clase vehiculo. La clase vehiculo es más general que la clase carro. Si quisieramos ser aún más específicos podríamos definir una clase derivada utilizando carro como la clase base, por ejemplo, carro_deportivo puede ser una clase derivada de carro, y podríamos incluir maxima_velocidad como miembro de carro_deportivo, información que podría no aplicarse a un carro de tipo familiar.

¿Cómo se define una clase derivada?

Una clase derivada se define incluyendo el archivo de cabecera para la clase base como lo hicimos en la línea 6, después se especifica el nombre de la clase base seguido del nombre de la clase derivada separados por un símbolo de dos puntos, como está ilustrado en la línea 8. Por el momento ignore la palabra clave public que sigue del símbolo de dos puntos, ésta define una herencia de tipo public y ésto lo estudiaremos en detalle en el próximo capítulo. Todos los objetos declarados como parte de la clase carro están compuestos de dos variables de la clase vehiculo por medio de la herencia y de la variable sencilla declarada en la clase carro llamada carga_pasajeros. Un objeto de la clase carro tendrá tres de los cuatro métodos de vehiculo y los dos nuevos declarados en carro. El método llamado inicializa ( ) el cual es parte de la clase vehiculo no está disponible porque está oculta por la versión local de inicializa ( ) la cual es parte de la clase carro. El método local será utilizado si el nombre es repetido permitiendole personalizar su nueva clase. Observe que la implementación de la clase base puede surtirse en forma compilada, el código fuente de la implementación puede ocultarse por razones económicas en pro de los intereses del desarrollador de la clase, esto permite a su vez la práctica de la ocultación de la información. El archivo de cabecera para la clase base debe estar disponible como un archivo de texto ya que las definiciones de la clase son necesarios para utilizar la clase.

Implementación de la clase carro

El archivo llamado carro.cpp representa la implementación de la clase carro. Lo primero que debe observar es que éste archivo no indica el hecho de que se trata de una clase derivada de cualquier otro archivo, esto solo puede determinarse examinando el archivo de cabecera para la clase. Como no podemos saber si es una clase derivada o no, se escribe de la misma manera que la implementación de cualquier otra clase.


// Archivo carro.cpp

#include "carro.h"

void carro::inicializa(int in_llantas, float in_peso, int personas)
{
    pasajeros_carga = personas;
    llantas = in_llantas;
    peso = in_peso;
}

int carro::pasajeros(void)
{
    return pasajeros_carga;
}

Otra clase derivada

En el archivo camion.h tenemos otro ejemplo de una clase derivada de la clase vehiculo, por supuesto, agrega dos variables y tres métodos de cosas relacionadas con los camiones. Un punto importante es que la clase carro y la clase camion no tienen relación entre sí, sucede simplemente que ambas son clases derivadas de la misma clase base. Observe que ambas clases tienen métodos llamados pasajeros ( ) pero ésto no causa problemas y es perfectamente aceptable.


// Archivo camion.h

#ifndef CAMION_H
#define CAMION_H

#include "vehiculo.h"

class camion : public vehiculo
{
    int pasajeros_carga;
    float flete;
    public:
    void ini_camion(int cuantos = 2, float max_carga = 24000.0);
    float eficiencia(void);
    int pasajeros(void);
};

#endif

Implementación de camion

En el siguiente código está especificado la implementación de la clase camion, en realidad no presenta nada nuevo, sin embargo nos será útil para demostrar como se utilizan juntas las clases que estamos estudiando.


// Archivo camion.cpp

#include "camion.h"

void camion::ini_camion(int cuantos, float max_carga)
{
    pasajeros_carga = cuantos;
    flete = max_carga;
}

float camion::eficiencia(void)
{
    return flete / (flete + peso);
}

int camion::pasajeros(void)
{
    return pasajeros_carga;
}

Utilizando juntas las clases

El programa llamado todovehi.cpp utiliza las tres clases que hemos discutido en éste capítulo. Utiliza la clase base vehiculo para declarar objetos y además utiliza las dos clases derivadas para lo mismo, esto lo hacemos para demostrar que se pueden utilizar las tres clases en un mismo programa.


// Archivo todovehi.cpp

#include <iostream.h>
#include "vehiculo.h"
#include "carro.h"
#include "camion.h"

int main()
{
    vehiculo monociclo;

    monociclo.inicializa(1, 12.5);
    cout << "El monociclo tiene " << monociclo.obtiene_llantas() << " llantas.\n";
    cout << "La capacidad del monociclo es " << monociclo.llanta_carga()
         << " libras por llanta.\n";
    cout << "El monociclo pesa " << monociclo.obtiene_peso() << " libras.\n\n";

    car sedan;
    sedan.inicializa(4, 3500.0, 5);
    cout << "El sedan transporta " << sedan.pasajeros() << " pasajeros.\n";
    cout << "El sedan pesa " << sedan.obtiene_peso() << " libras.\n";
    cout << "La capacidad por llanta del sedan es " << sedan.llanta_carga()
         << " libras or llanta.\n\n";

    camion semi;
    semi.inicializa(18, 12500.0);
    semi.ini_camion(1, 33675.0);
    cout << "El semi pesa " << semi.obtiene_peso() << " libras.\n";
    cout << "La eficiencia del semi es " << 100.0 * semi.eficiencia() << " por ciento.\n";

    return 0;
}

Los archivos de cabecera para las tres clases están incluidas en las líneas 4 a la 6 así que el programa puede utilizar los componentes de las clases. Observe que las implementaciones de las tres clases no están aquí a la vista y no es necesario, esto permite al código ser utilizado sin tener acceso al código fuente para la implementación de las clases, sin embargo los archivos de cabecera de definición deben estar disponibles. En éste programa de ejemplo solo se ha declarado un objeto de cada clase pero debe quedar claro que se puede declarar cuantos objetos sean necesarios en el programa. El resultado de la ejecución del programa es:

Ejemplo de herencia

Volver al principio


Uso del enunciado #ifndef

Regresando al archivo llamado vehiculo.h vemos las directivas al preprocesador definidas en las líneas 3, 4 y 18 y es tiempo de explicarlas. Cuando definimos la clase derivada carro, se nos solicitaba proveerla con la definición completa de la interfaz a la clase vehiculo ya que carro es una clase derivada de vehiculo y debe conocer todo respecto a la clase base. Hacemos ésto incluyendo la clase vehiculo en la clase carro y entonces la clase carro puede ser compilada. La clase vehiculo debe ser incluida en el archivo de cabecera de la clase camion por la misma razón.

Respecto al archivo todovehi.cpp debemos informarle de los detalles de las tres clases, así que los tres archivos de cabecera deben ser incluidos, pero esto conduce a un problema. Cuando el preprocesador llega a la clase carro incluye la clase vehiculo porque así esta indicado en el archivo de cabecera de la clase carro pero como la clase vehiculo fué incluida en la línea 4 de todovehi.cpp dá como resultado que la clase vehiculo sea incluida dos veces. Ante esta situación el sistema no permite una redeclaración de una clase. Permitimos la doble inclusión del archivo y al mismo tiempo prevenimos la doble inclusión de la clase construyendo un puente utilizando la palabra VEHICULO_H. Si la palabra yá fué definida, la declaración es ignorada, pero si la palabra no ha sido definida, la declaración es incluida y entonces es definido vehiculo.h. El resultado final es la inclusión, una sola vez, de la clase vehiculo aún y cuando el archivo vehiculo.h haya sido incluido dos veces.

Volver al principio


| Indice | ¿Comentarios ó dudas? | Leccion anterior | Siguiente lección |

Esta página es patrocinada por:  GeoCities Construya su propia página gratis

© 1998, Virgilio Gómez Negrete

.