Programación
modular
Compilación
independiente
En las primeras prácticas creábamos un
único fichero fuente (.cpp) que
contenía todo nuestro programa. Sin embargo, si nuestro
programa
aumenta de tamaño es mejor dividirlo en varias unidades de
compilación,
de forma que cada una de ellas se pueda compilar por separado y no sea
necesario compilar todo el programa para cada modificación
que
realicemos. El criterio para dividir un programa debe ser el seguir la
abstracción por módulos.
Para
poder dividir un programa extenso en varias unidades de
compilación
será necesario usar ficheros de cabecera. Estos ficheros de
cabecera
incluirán, entre otras cosas, declaraciones de datos y
prototipos de
funciones.
Normalmente, cada unidad de programa (fichero fuente
.cpp) tendrá asociado un fichero de cabecera (.h) que
contendrá los
prototipos de las funciones que son invocadas desde otras unidades de
programa (otros ficheros fuente). Es decir, aquellas funciones de una
unidad de programa que no sean requeridas por funciones externas a esa
unidad, tendrán su prototipo al comienzo de la unidad de
programa
(fichero fuente .cpp) y no en el fichero de cabecera asociado.
También
es conveniente recordar que un programa en C++ tiene una
única función
principal (main()),
por lo que ésta estará definida en
sólo uno de esos
ficheros fuente.
Para que desde una unidad de programa se pueda
invocar a una función que esta definida en otra unidad,
será necesario
incluir el fichero de cabecera que contiene el prototipo de dicha
función. ¿Pero que ocurre cuando enlazamos (link) un programa
compuesto de
varias unidades, y en algunas de ellas se ha incluido el mismo fichero
de cabecera? Lo que ocurre es que el enlazador (linker) se puede
encontrar con
varios prototipos de la misma función y/o varias
definiciones de los
mismos tipos de datos, generando sus errores respectivos. Para evitar
esto se puede emplear la directiva de compilación
condicional (#ifndef
... #endif) cuya sintaxis es:
#ifndef CONSTANTE
#define CONSTANTE
// Definición de tipos y prototipos de funciones
#endif
donde
CONSTANTE es el identificador al que se le asocia el conjunto de
definiciones de tipos y prototipos de funciones definidas a
continuación.
A grandes rasgos, la directiva de compilación
condicional funcionaría de la siguiente forma: si en el
enlace que
lleva realizado el compilador no esta definida esa CONSTANTE entonces
incluye, en dicho linkaje, ese conjunto de definiciones y prototipos,
en caso contrario, es decir si ya está definida no la vuelve
a incluir,
y de esa forma no existirán varios prototipos de la misma
función o
varias definiciones de los mismos datos.
La unión de todas estas
unidades de programa constituye un proyecto. Recordar que en C++Builder
solo se debe incluir al proyecto los ficheros fuentes (.cpp) y NO los ficheros de
cabecera.
Ejemplo
Supongamos que se desea crear un módulo que soporte
operaciones sobre números complejos. Se creará el
archivo de cabecera complejos.h
y el archivo de implementación complejos.cpp.
/*
complejos.h
Módulo de soporte de números complejos
Archivo de cabecera
*/
#ifndef _MODULO_COMPLEJOS_H_
#define _MODULO_COMPLEJOS_H_
struct NumeroComplejo {
int a;
int b;
};
void imprimeNumeroComplejo(const NumeroComplejo &n);
#endif
Lo anterior es el archivo de cabecera, complejos.h.
Al definir la constante _MODULO_COMPLEJOS_H_, si se incluye en
más de un archivo de implementación, no se
compilará el contenido de la cabecera, es decir, se asegura que
sóo se compila una vez nada más (la primera).
Ahora,
sólo será necesario crear el archivo de
implementación, donde se hagan las definiciones pendientes (en
este caso, sólo queda por definir la función imprimeNumeroComplejo()).
/*
complejos.cpp
Módulo de soporte de números complejos
Archivo de implementación
*/
#include "complejos.h"
void imprimeNumeroComplejo(const NumeroComplejo &n)
{
cout << n.a << " + i" << b << endl;
}
Finalmente, necesitaremos un programa principal que haga uso del módulo. Por ejemplo:
// ppal.cpp
#include <iostream>
#include "complejos.h"
using namespace std;
int main()
{
NumeroComplejo n;
n.a = 5; n.b = 2;
imprimeNumeroComplejo( n );
}
Como
es lógico, el programa principal debe incluir la cabecera del
módulo de números complejos para poder utilizarlo.
Además, ambos archivos de implementación (complejos.cpp y ppal.cpp) deben compilarse juntos, para que el código de la función imprimeNumeroComplejo()
esté disponible. Una representación gráfica de las
dependencias sería más o menos el siguiente: