Una función es un bloque de código diseñado para realizar una tarea específica que puedes reutilizar en cualquier parte de tu programa. Para que esto funcione, necesitamos dos cosas: decirle al compilador que la función existe (esto es la declaración o prototipo) y escribir el código real que hace el trabajo (esto es la definición).
Imagina que la función es un contrato: el prototipo es el encabezado del contrato que dice “esta máquina acepta estas piezas y devuelve este producto”, y la definición es la máquina real funcionando en la fábrica. El compilador necesita el prototipo para verificar que, cuando uses la función, estés entregando las piezas correctas y esperando el tipo de producto adecuado. Sin esta verificación, podrías intentar tratar un número decimal como si fuera un entero, provocando que el programa lea datos corruptos o falle de forma impredecible.
Debes usar prototipos siempre que una función se defina después de ser utilizada (por ejemplo, si la función A llama a la función B, y B está escrita más abajo en el archivo). Si intentas llamar a una función que el compilador aún no conoce, este intentará adivinar su comportamiento, lo cual es una receta para el desastre.
Si te equivocas en la declaración (por ejemplo, si dices que una función devuelve un int cuando en realidad devuelve un double), el programa compilará pero se comportará de forma errática en tiempo de ejecución, ya que el compilador interpretará los bits de la memoria de forma totalmente incorrecta.
#include <stdio.h>
/* --- Prototipos (Declaraciones) --- */
/* El prototipo le dice al compilador: "Confía en mí, esta función
existe y tiene esta forma". No reserva memoria, solo define la interfaz. */
void mostrar_mensaje(void);
float calcular_area_rectangulo(float base, float altura);
int sumar_dos_numeros(int a, int b);
int main(void) {
float b = 5.5f;
float h = 10.0f;
int x = 15, y = 25;
// Llamada a una función que no devuelve nada (void)
mostrar_mensaje();
// Llamada a una función con parámetros y retorno de float
float area = calcular_area_rectangulo(b, h);
printf("El area del rectangulo es: %.2f\n", area);
// Llamada a una función con parámetros enteros y retorno int
int suma = sumar_dos_numeros(x, y);
printf("La suma de %d + %d es: %d\n", x, y, suma);
return 0;
}
/* --- Definiciones (Implementaciones) --- */
/* 'void' en el tipo de retorno indica que la función no devuelve ningún valor.
'void' entre paréntesis indica que la función NO acepta ningún argumento.
En C, es vital usar 'void' en lugar de dejar los paréntesis vacíos f()
para asegurar que el compilador verifique que no se le pasen argumentos. */
void mostrar_mensaje(void) {
printf("Hola: La función se ha ejecutado correctamente.\n");
// En una función void, 'return;' (sin expresión) sirve para salir prematuramente.
}
/* Aquí definimos cómo se calcula el área. El compilador ya sabe qué esperar
gracias al prototipo definido arriba. */
float calcular_area_rectangulo(float base, float altura) {
return base * altura;
}
/* Si una función no es 'void', debe usar la palabra clave 'return'
seguida de un valor del tipo de retorno declarado. */
int sumar_dos_numeros(int a, int b) {
int resultado = a + b;
return resultado;
}
Desglose del código
Al observar el código, verás que la sección de prototipos actúa como un índice. Por ejemplo, float calcular_area_rectangulo(float base, float altura); le comunica al compilador que, cuando encuentre esa llamada en el main, debe esperar dos valores de tipo float y que el resultado será también un float. Sin esta línea, si llamamos a la función en el main antes de ver su definición, el compilador se encontraría perdido.
En la función mostrar_mensaje(void), el uso de void en los parámetros es una medida de seguridad. En C, declarar una función como void mostrar_mensaje() (con paréntesis vacíos) le dice al compilador: “esta función acepta cualquier número de argumentos, no los verifiques”. Sin embargo, al usar void mostrar_mensaje(void), le estamos diciendo: “esta función es estrictamente de cero argumentos”. Si intentaras pasarle un número por error, el compilador te detendría inmediatamente.
Fíjate también en el uso de return. En sumar_dos_numeros, la instrucción return resultado; envía el valor calculado de vuelta al punto donde la función fue llamada. En cambio, en mostrar_mensaje, como el tipo de retorno es void, no podemos devolver un valor; solo podemos usar return; para finalizar la ejecución de la función si fuera necesario.
El compilador procesa esto en varias etapas. Primero, verifica que los tipos de los argumentos que pasas en el main coincidan con los del prototipo. Si intentaras pasar un char* (una cadena de texto) a sumar_dos_numeros, el compilador generaría un error de tipo. Una vez que todo encaja, el proceso de enlazado (linker) une la llamada en el main con la implementación real que escribiste abajo.
El error frecuente
Un error clásico, especialmente en código heredado o cuando se viene de otros lenguajes, es omitir el prototipo y confiar en que el compilador “lo entenderá”.
/* ERROR: No hay prototipo para calcular_impuesto */
int main(void) {
// El compilador no conoce 'calcular_impuesto'
// Asume que retorna un 'int' por defecto (comportamiento de C89)
double impuesto = calcular_impuesto(100.50);
printf("%f", impuesto);
return 0;
}
double calcular_impuesto(double base) {
return base * 0.21;
}
En este ejemplo, al no haber prototipo, el compilador asume que calcular_impuesto devuelve un int. Sin embargo, la función realmente devuelve un double. Esto provoca que el valor se guarde incorrectamente en la memoria o en el registro destinado al retorno, resultando en un valor basura o un programa que se cierra inesperadamente. Herramientas como gcc -Wimplicit-function-declaration o AddressSanitizer te alertarían de este desastre antes de que llegue a producción.
N° 35