60. Funciones
1. Introducción a las funciones en C Una
función es, a grandes rasgos, un bloque de código que tiene un nombre.
Constituyen uno de los elementos más importantes del lenguaje
C, y es realmente una lástima que yo no haya sabido introducir este tema mucho antes en este curso.
Una
función se escribe para definir una tarea (o serie de tareas) que vamos a utilizar repetidamente en un programa o proyecto dado.
Cada vez que tengamos que llevar a cabo una misma tarea, conviene pues invocar una
función que contiene los pasos a seguir, lo cual es más eficiente que estar repitiendo una y otra vez el mimsmo código.
Esto nos da una herramienta de abstracción básica en programación: la
modularidad.
Las
funciones pueden tener parámetros que modifiquen su comportamiento.
Los
parámetros se indiquen con un nombre formal local a la
función, cada uno con un determinado
tipo.
Las
funciones pueden retornar un
valor, que es de determinado
tipo.
2. Definición de una función (forma básica) Hay dos clases de
funciones: las que retornan un
valor de un determinado
tipo y las que no.
A su vez, hay tres clases de
funciones según el número de parámetros:
(*) sin parámetros,
(*) con una cantidad determinada de parámetros, y
(*) con una cantidad indeterminada de parámetros.
Una
función se
define con la siguiente sintaxis:
rettype funcname(T1 V1, T2 V2, . . ., Tn Vn)
{
/* Aquí van las instrucciones que indican */
/* las acciones que va a desempeñar la función. */
return expr;
}
Se denomina
cabecera de la función al primer renglón:
rettype funcname(T1 V1, T2 V2, . . ., Tn Vn)
Con
rettype indicamos un
tipo de datos, que es el
tipo de retorno de la
función.
El
tipo de retorno rettype está restringido: No puede ser ningún
tipo array, ni tampoco un
tipo función. Ver apartado 5, debajo.
Con
T1 V1 indicamos que el 1er
parámetro de la
función es de
tipo T1 y tiene nombre formal
V1.
Asimismo,
T2 V2,
T3 V3, etc., hasta
Tn Vn indican el 2do, 3er, etc., hasta el n-ésimo
parámetro de la
función. (Los puntos suspensivos que hemos puesto en rojo son "metasintácticos", o sea, no tenemos que ponerlos en un programa real).
Luego, entre
llaves { } va el
cuerpo de la función, en donde se coloca una serie de sentencias que perfilarán las acciones de la misma en cuanto sea invocada.
En alguna parte del
cuerpo de la función normalmente debe ir al menos una sentencia
return, acompañada de una
expresión, que hemos indicado con
expr.
Una sentencia
return realiza una "terminación" de las tareas de la
función, devolviendo el control del
flujo del programa al proceso que llamó la
función.
Además, la
expresión expr se evalúa, produciendo un
valor cuyo
tipo es
rettype. Este valor es utilizado a su vez de alguna
expresión del proceso llamante, desde la cual se ha llamado a la
función.
¿Y qué es un "proceso"? Bueno, en
C esto es siempre otra
función.
El "proceso principal" es en realidad una
función principal, que en
C se llama
main(), y que tiene una sintaxis determinada, que en breve explicaremos.
Sujestivamente podemos decir que en
C "todo son funciones", pero esta frase es muy vaga como para que podamos defenderla, así que lo dejamos aquí.
Observación: Aún no hemos definido formalmente el término
expresión (en referencia a
expr). Eso lo haremos luego.
No podemos definirlo antes de haber siquiera haber introducido el tema de
funciones, porque las
expresiones involucran llamadas a
funciones.
O sea que hay cierta recursividad en las definiciones que estamos dando, tanto de
función como de
expresión.
Esto es así, y no es demasiado problemático, mientras que no haya "circularidad".
EJEMPLO 1 Consideremos la función
main(), que es la principal en un programa en
C, desde la cual se llama a la
función promedio3(), encargada de calcular el promedio de 3 números reales:
#include <stdio.h>
double promedio3(double x, double y, double z)
{
printf("El 1er número es: %f\n", x);
printf("El 2do número es: %f\n", y);
printf("El 3er número es: %f\n", z);
return (x + y + z) / 3;
}
int main(void)
{
printf("Cálculo del promedio de 3 números: \n\n");
double resultado;
resultado = promedio3(1.14, 9.8, 7.7777);
printf("El promedio es: %f\n", resultado);
return 0;
}
El programa contiene tres elementos principales:
- Se incluye la librería
<stdio.h>, con el fin de utilizar la
función de biblioteca
printf().
- Se define la
función promedio, de manera que formalmente admite 3
parámetros de tipo
double, y retorna un
valor de
tipo double también.
- Se define la
función main(), desde la cual se invoca a la
función promedio().
El
flujo del programa comienza en la primer llave de la
función main().
Enseguida se imprime en pantalla un mensaje con
printf(), indicando que se va a calcular un promedio.
Debajo se declara la variable
resultado, de tipo
double, o sea, de
punto flotante.
Luego, se realiza una
llamada a la
función promedio() pasándole 3 argumentos.
Esto hace que el
flujo del programa ingrese al
cuerpo de la función con los 3 argumentos pasados ahora como
parámetros a la
función.
Lo primero que ocurre es que los
parámetros formales
x, y, z son reemplazados por los argumentos:
1.14, 9.8, 7.7777, respectivamente.
Luego se realizan las tareas estipuladas en la
función, que en el ejemplo son, simplemente, impresiones de mensajes en pantalla con los valores de los
parámetros.
Tras terminar esto, se llega a la sentencia
return que contiene la
expresión (x + y + z) / 3.
El
valor de dicha
expresión es devuelto al proceso llamador, es decir, la
función main(), y puesto en la
expresión de asignación de la variable
resultado.
Ahora la variable
resultado ha "recibido" el
valor devuelto por la
función promedio() y tiene ese
valor de allí en más.
La sentencia que sigue es una llamada a
printf() que imprime el
valor de
resultado en la pantalla, con lo cual visualizamos cuánto es que da el dicho promedio de los 3 números.
Con el ejemplo pretendemos ilustrar cómo es que el programa evoluciona sentencia a sentencia en el bloque
main(),
hasta que se hace una
llamada a la función invocándola con los argumentos que más nos gusten, para ser reemplazados por los
parámetros formales de la
función,
interrumpiendo aquí el flujo normal con un "salto" al
cuerpo de la función, que cumple con sus tareas,
hasta que se encuentra con una sentencia
return.
Esto devuelve el
flujo del programa al punto exacto en que la
función fue llamada, y además, con un
valor de retorno, que es usado en alguna
expresión.
El programa continúa hasta terminar.
Se ve que
main() es también una
función, y que también retorna un
valor.
¿Qué sucede si en el cuerpo de la función no hay ninguna sentencia return? En ese caso, la llave de cierre
} delimita la frontera de acciones de la
función y termina allí,
devolviendo asimismo el control del
flujo del programma al proceso que hizo la
llamada a la función.
Esto equivale a haber hecho un
return, pero sin haber especificado un
valor concreto de retorno.
3. Definición de una función (formas alternativas) Variando la
cabecera de la función obtenemos otras posibles definiciones de funciones. Veamos:
(a) Función que retorna un
valor de un
tipo dado, y que admite
una cantidad bien determinada de
parámetros:
rettype funcname(T1 V1, T2 V2, . . ., Tn Vn)
(es el caso típico estudiado en el apartado 2).
(b) Función que retorna un
valor de un
tipo dado, y que no admite
parámetros:
rettype funcname(void)
(aquí la palabra clave
void adquiere el significado de: "nada", o sea, "0 parámetros").
(c) Función que retorna un
valor de un
tipo dado, y que admite
una cantidad variable de
parámetros:
rettype funcname(T1 V1, T2 V2, . . ., Tn Vn, ...)
Los puntos suspensivos
... al final de la lista de parámetros son sintácticos.
Los ponemos explícitamente en nuestro programa para indicar que esa función tiene una lista variable de
parámetros.
Los primeros n
parámetros tienen un
tipo predeterminado, pero los
parámetros siguientes, los que
vienen a partir de los puntos suspensivos, no se sabe ni cuántos son ni de qué
tipo.
Sin embargo, esto tiene perfecto sentido, y hay maneras de tratar estos
parámetros misteriosos,
aunque no podemos tratarlo por ahora.
En la lista de
parámetros no puede haber (siguiendo estrictamente el estándar) solamente puntos suspensivos. Tiene que haber al menos un
parámetro debidamente declarado antes, con un
tipo concreto.
Así:
int find_it(int x, ...) es correcto, pero
int find_it(...) no.
Funciones sin retorno: Si en el tipo de retorno
rettype ponemos
void, significa que la
función no retorna valores.
En tal caso, la
función pasa a ser lo que en otros lenguajes de programación se denomina
procedimiento o
subrutina.
En este tipo de
función la palabra
return aún se usa para devolver el control al proceso llamante,
pero esta vez no se pone ninguna
expresión acompañándole, porque no hay
valor alguno que retornar.
4. La función main() En
C existe una
función especial, llamada
main(), la cual tiene un cometido específico.
El programa inicia su ejecución en el
cuerpo de la función main().
Termina en la llave de cierre, o bien cuando se encuentra una sentencia
return.
El
tipo de retorno de
main() tiene que ser siempre
int.
Hay dos opciones para la
lista de parámetros:
Opción 1: main() sin parámetros. La declaración quedaría así en el programa:
int main(void)
{
/* ... */
}
Opción 2: main() con dos parámetros. Quedaría así:
int main(int argc, char **argv)
Es decir, el 1er parámetros es de tipo
int y el 2do de tipo
puntero a puntero a char.
Cuando se da esta última declaración, el programa acepta argumentos desde el sistema operativo.
Lo más común es poner esos argumentos en la
línea de comandos separados por espacios en blanco.
La cantidad de argumentos pasados al programa se cuenta en la variable
argc.
El mismo nombre del programa se considera un argumento, así que su valor es al menos de 1.
En cuanto a
argv es en realidad considerado como un
array de cadenas de caracteres.
Un tal
array "decae", como sabemos, a un
puntero a char*.
(El estándar hace una declaración un poco distinta, pero resulta equivalente a la nuestra, que mantenemos por conveniencia).
Los nombres de los parámetros:
argc, argv, se acostumbra ponerlos siempre con ese nombre, pero no es obligatorio.
El
valor de retorno de
main() es, como dijimos, de tipo
int.
Sin embargo, no hay otra función a la cual retornar este valor.
Lo que ocurre al hallar una sentencia
return es que el programa termina, y se devuelve al sistema operativo el
valor int indicado.
Este
valor de retorno suele tener el significado de un indicador de "error" por parte del programa: indica si todo terminó bien o con errores.
Normalmente, un
valor de retorno de
0 significará "terminación normal del programa", y un
valor distinto de 0 indicará "programa terminado con errores".
(continúa en el siguiente post...)
OrganizaciónComentarios y Consultas