Menu

Funciones en C: declarar, llamar y devolver valores

Cómo definir y llamar funciones en C: el tipo de retorno, los parámetros, los valores devueltos, las funciones void, por qué importa la descomposición y un ejemplo completo construido con piezas pequeñas y con nombre.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

Por qué funciones

Todo hasta ahora ha vivido dentro de main. Eso funciona hasta que un programa hace más de una cosa, y entonces llegan tres problemas a la vez: el mismo bloque de código se copia en tres lugares, main crece más allá de lo que alguien puede tener en la cabeza y no hay forma de nombrar para qué sirve un trozo de código.

Una función arregla los tres. Le da a un bloque de código un nombre, sus propias variables locales, una lista de entradas y un único valor para devolver.

square se escribe una vez y se llama tres, incluida una sobre su propio resultado. Ya llevas rato llamando funciones: printf es una, y main también.

Anatomía de una función

tipoDeRetorno nombre(listaDeParametros) {
    // cuerpo
    return valor;
}

Cuatro partes:

  • Tipo de retorno: el tipo del valor que la función devuelve. int, double, char, un tipo puntero o void para "nada".
  • Nombre: sigue las mismas reglas que el nombre de una variable, y debería decir qué hace o qué produce la función.
  • Lista de parámetros: las entradas, cada una con su propio tipo, separadas por comas. (void) significa que no toma ninguna.
  • Cuerpo: las sentencias, entre llaves, terminando con un return salvo que el tipo de retorno sea void.

Fíjate en que pi() sigue necesitando los paréntesis vacíos en el punto de llamada: eso es lo que la convierte en una llamada y no en una mención del nombre. Y printBanner(20); es una sentencia por sí sola, porque no produce ningún valor que usar.

Escribe (void), no (), para una función sin parámetros. En C esas dos cosas significan algo distinto, y () es la laxa; más sobre esto en un momento.

Llamar y devolver

Una llamada transfiere el control a la función, ejecuta su cuerpo y vuelve con un valor que sustituye a la llamada dentro de la expresión que la rodea.

return hace dos cosas a la vez: aporta el valor y termina la función en el acto. Nada de lo que venga después de un return en el mismo camino llega a ejecutarse, que es lo que hace funcionar el estilo de retorno temprano de larger: no hace falta ningún else, porque llegar a la última línea ya significa que a > b era falso.

Una función puede tener varias sentencias return. Solo puede ejecutar una de ellas por llamada.

Funciones void

Una función que existe por su efecto y no por su valor tiene el tipo de retorno void: imprimir, actualizar algo a través de un puntero, dibujar.

Dentro de una función void, return; sin valor sale antes de tiempo. return algunValor; ahí es un error de compilación, y también lo es usar la llamada dentro de una expresión: int x = printTable(7, 5); no compila, que es el sistema de tipos haciendo su trabajo.

Descomposición: el verdadero objetivo

Las funciones no van sobre todo de evitar la repetición. Van de convertir un procedimiento largo en unos pocos pasos con nombre, de modo que main se lea como una descripción del programa y no como su implementación.

main son ahora cuatro líneas de salida y un cálculo. Cada auxiliar hace una sola cosa, tiene un nombre que dice cuál es y se puede probar por separado. Fíjate en que la longitud del array viaja como segundo parámetro: una vez que un array se pasa a una función, el truco de sizeof deja de funcionar ahí, porque el array decae a un puntero.

Tres reglas prácticas para repartir el trabajo en funciones:

  • Un trabajo por función. Si el nombre necesita una "y" dentro, probablemente son dos funciones.
  • Toma lo que necesites, devuelve lo que produzcas. Una función que lee estado global cuesta más de razonar y es imposible de probar de forma aislada.
  • Lo bastante corta para verla de una vez. No hay un número mágico de líneas, pero una función que no cabe en una pantalla suele esconder otra función más pequeña dentro.

Funciones que llaman a funciones

Una función puede llamar a cualquier función que ya haya sido declarada, incluidas las que escribiste tú:

main llama a report, que llama a sumEvens, que llama a isEven dentro de un bucle. Cada llamada recibe su propio conjunto nuevo de variables locales, apilado encima del de quien la llamó, y se descartan cuando retorna.

Las definiciones de arriba están en orden de dependencia —isEven antes que sumEvens antes que report— porque C exige que una función esté declarada antes de llamarla. Llama a una que aparezca más abajo en el archivo y el compilador se queja de una declaración implícita. La solución general es un prototipo cerca del inicio del archivo, que te deja ordenar las definiciones como quieras. Una función que se llama a sí misma también es legal y útil; eso es la recursión.

main es una función

main es una función corriente que el entorno de ejecución llama por ti. Su tipo de retorno int es el estado de salida del programa: 0 significa éxito, distinto de cero significa fallo, y los scripts de shell y las herramientas de compilación lo leen.

int main(void) {
    /* ... */
    return 0;
}

Desde C99, llegar al final de main sin un return devuelve 0 implícitamente, una regla especial que se aplica solo a main. Cualquier otra función que no sea void y termine sin devolver un valor produce comportamiento indefinido cuando quien la llamó usa el resultado.

La otra forma estándar es int main(int argc, char *argv[]), que recibe los argumentos de la línea de comandos. Ambas son correctas; void main() no lo es, digan lo que digan los tutoriales viejos.

Errores comunes

  • Olvidar el return en una función que no es void. Quien llama lee entonces un valor basura. gcc -Wall avisa.
  • Declarar dentro del cuerpo una variable con el mismo nombre que un parámetro. Oculta al parámetro, y la asignación que querías hacerle a la entrada no va a ninguna parte.
  • Esperar que una función cambie sus argumentos. C pasa todo por valor, así que una función recibe copias. void reset(int x) { x = 0; } no cambia nada en el punto de llamada: el tema de la siguiente página.
  • Omitir los paréntesis en una llamada. printBanner; es una expresión legal que evalúa la dirección de la función y la descarta. Compila, no hace nada, y -Wall lo señala.
  • Escribir () en lugar de (void). Es legal, pero desactiva la comprobación de argumentos en los dialectos antiguos de C.

Preguntas frecuentes

¿Cómo se declara una función en C?

Escribe el tipo de retorno, el nombre y una lista de parámetros entre paréntesis, y luego el cuerpo entre llaves: int add(int a, int b) { return a + b; }. Una función que no toma nada usa (void), y una que no devuelve nada tiene el tipo de retorno void.

¿Qué hace return en una función de C?

Termina la función de inmediato y entrega un valor a quien la llamó. El tipo del valor debe coincidir con el tipo de retorno declarado. En una función void puedes escribir un return; a secas para salir antes, u omitirlo y dejar que la función termine en la llave de cierre.

¿Por qué main devuelve un int en C?

El valor de retorno de main es el estado de salida del programa, que el sistema operativo y los scripts de shell pueden leer. return 0; significa éxito y cualquier valor distinto de cero significa fallo. Desde C99, llegar al final de main sin una sentencia return devuelve 0 implícitamente.

¿Puede una función de C devolver más de un valor?

No directamente: return produce exactamente un valor. Los recursos habituales son devolver una estructura con varios campos, o pasar punteros como parámetros y hacer que la función escriba a través de ellos sus resultados extra.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR