Al igual que en otros lenguajes de alto nivel, C nos permite escribir subprogramas. Según el lenguaje, los subprogramas se denominan de forma diferente, siendo el procedimiento, la función, la rutina, las subrutinas más comunes. Las distintas denominaciones se diferencian por características distintas de poca importancia. Por ello, cuando decimos función, por ejemplo en el caso de C, se supone que el subprograma devolverá implícitamente un resultado. El procedimiento no devuelve resultados implícitos.
No obstante, el objetivo de las funciones de C, al igual que el de todos los subprogramas, es facilitar el diseño y la escritura en el programa, ya que con ellas es posible separar un problema complejo en otros más sencillos, resolviendo cada uno con un programa más sencillo. Esta técnica, denominada programación descendente o modular, tiene en la actualidad una gran difusión por la mejora de la eficiencia (tanto en el desarrollo como en la corrección) de los programadores.
Veamos un ejemplo, como el caso del factorial. En los dos capítulos anteriores hemos realizado diferentes programas que calculan el factorial, pero siempre en un único programa. Esto no es la única opción, ya que el cálculo del factorial puede programarse definitivamente en un subprograma o función y ser utilizado desde diferentes programas principales como se explica en el programa 1.
En el programa 2 se puede ver el uso de la misma función,
m / (m - n)! n
para calcular la expresión a través de la fórmula (m / n).
En el segundo programa no hemos escrito el código del factorial y habrá que indicar al compilador que la función se compilará aparte. Por ello se deberá indicar el tipo de factorial, especificando el distintivo extern. Con este esquema, el módulo objeto que se genera al compilar la definición de la función factorial se enlaza con el creado por el programa principal obteniendo un único programa ejecutable.
Como se ha visto en los ejemplos, si utilizamos las funciones en los programas C, podemos hacer alusiones de tres tipos: definición, llamada y desarrollo.
Sirve para expresar lo que hace el subprograma, especificando el tipo de resultado de la función, el nombre, los argumentos, la definición de los argumentos y el cuerpo.
Si hay más de un argumento, se distinguen por el carácter. En el Ejemplo 1 el nombre de la función es factorial, el tipo de resultado es long, el argumento n, la definición del argumento int n, y todo lo que está junto al cuerpo hasta el programa principal. Hay que tener en cuenta que el programa principal es una función llamada main sin argumento.
El formato de la definición es el siguiente:
Si la función devuelve resultados, se hará a través de la sentencia return, que normalmente es la última del cuerpo. Si la función no devuelve resultados (en otros lenguajes se llama procedimiento), se escribirá como tipo de resultado el void, ya que si no se menciona el tipo de resultado se supondrá la devolución del número entero.
La definición de los argumentos se realiza como definición de los datos. La única diferencia es que estos datos sean enviados desde otras funciones (o desde el programa principal). Por ello, estos argumentos se denominan formales y al llamar a la función se rellenarán con datos concretos.
En el cuerpo de la función se indica el código del subprograma, indicando tanto la definición de sus datos como las instrucciones.
Vamos a escribir la definición que devuelve el mayor número en los dos números enteros: inventamos el nombre, los grandes, por ejemplo, los parámetros son dos y además los dos números enteros (los llamados a y b) y el tipo de resultado es uno de los dos números, por lo que será además entero. Por tanto, el jefe de la función será:
int grandes (a, b)
int a, b; /*parámetros de función */
El cuerpo es muy sencillo y una vez realizado aquí se utilizará la variable local denominada resultado.
1er resultado
/*Variable local*/ if (a b) resultado = a; else resultado = b; return (resultado)
Presupuestos
Cuando se quiere ejecutar una función se realiza una llamada, de forma que con los datos indicados en la llamada (parámetros) se ejecute el código correspondiente a la definición de la función y se devuelve el resultado.
Si la función devuelve resultados, la llamada aparecerá normalmente a la derecha de una asignación. Sin embargo, también puede aparecer en medio de una expresión o como parámetro de otra función.
Pensemos que hemos leído dos números en las variables z1 y z2 y queremos escribir el más grande. Tenemos dos opciones:
a) int em; ... em = grandes (z1, z2) printf (“%d”, em); ... b) printf (“%d”, grandes (z1, z2);
En el programa 2 descrito inicialmente se puede observar que la función factorial se llama tres veces en un mismo programa, pero utilizando distintos parámetros.
Cuando hay que obtener más de un resultado en un subprograma, no se puede hacer con sentencia return (esto permite devolver un único resultado). Esto se hará a través de los parámetros definidos con referencia (ver capítulo 10).
Cuando en un programa llamamos a una función que está definida en otro módulo, la función debe ser descrita para que por falta de definición el compilador no dé error. Esto se ha realizado en el Ejemplo 2 y lo que se debe especificar en el informe es: para indicar que se define externamente la palabra clave extern, para diferenciar el tipo de resultado, el nombre de la función y la función ( ). En el ejemplo que nos ocupa, la explicación sería la siguiente.
extern grandes int ( );
Cuando la función y la llamada están dentro de un mismo módulo, no es necesario realizar el diseño de la función, si la definición de la función se escribe antes de la llamada, sino es necesario el envío pero sin introducir la palabra clave externa.
Esta herramienta de programación, tan común en los lenguajes de ensamblaje, aparece en unos pocos lenguajes de alto nivel. Por ejemplo en C.
Las macros se utilizan para definir y referenciar subprogramas simples, pero presentan las siguientes diferencias funcionales:
Definir y referenciar (llamar) las macros. El formato de la definición es el siguiente:
y el caso del mayor lo haríamos así:
#define grandes (x, y) ((x) (y)? (x) : (y)
if
en lugar de la estructura, ? : se ha utilizado la expresión, ya que de lo contrario no se puede devolver el resultado. Las referencias se realizan igual que las llamadas de la función, aunque la manera de influir es muy diferente.
Con esta referencia,
em = grandes (zenb1, zenb2);
la sustitución del compilador en lenguaje máquina dará como resultado:
em = ((zenb1) (zenb2)? (zenb1) : (zenb2)
Así es, porque el compilador al ver un nombre macro lo sustituye por el cuerpo de la macro, sustituyendo los argumentos formales por parámetros de referencia.
Como se puede observar, el mecanismo es más reducido que el de las funciones, por lo que rara vez se utiliza.
Para finalizar, colocaremos una macro para calcular la superficie del círculo:
#define PI 3.1416
# define superficie (r) PI *(r)*(r)