Funciones en C: diseño, prototipos y valores de retorno

Capítulo 7

Tiempo estimado de lectura: 8 minutos

+ Ejercicio

Por qué modularizar con funciones

Una función es un bloque de código con un propósito concreto. Modularizar significa dividir un programa en funciones pequeñas y enfocadas. Esto aporta tres beneficios principales:

  • Reutilización: una misma función puede usarse desde distintos puntos del programa (o incluso en otros programas) sin copiar y pegar código.
  • Prueba: una función con entradas y salidas claras se puede comprobar de forma aislada, con casos de prueba simples.
  • Legibilidad: un main corto que “narra” el flujo del programa es más fácil de entender que un bloque enorme con muchos detalles mezclados.

Declaración (prototipo), definición e invocación

Las tres piezas

  • Prototipo (declaración): anuncia el nombre de la función, su tipo de retorno y sus parámetros. Permite que el compilador verifique llamadas aunque la definición esté más abajo o en otro archivo.
  • Definición: el cuerpo real de la función (su implementación).
  • Invocación (llamada): usar la función desde otra parte del código.

Ejemplo completo en un solo archivo

#include <stdio.h>

// 1) Prototipo (declaración)
int sumar(int a, int b);

int main(void) {
    // 3) Invocación
    int resultado = sumar(10, 5);
    printf("%d\n", resultado);
    return 0;
}

// 2) Definición
int sumar(int a, int b) {
    return a + b;
}

Observa el orden: el prototipo aparece antes de main para que la llamada sea válida aunque la definición esté después.

Reglas prácticas sobre el orden

  • Si defines la función antes de usarla, el prototipo puede omitirse (aunque en proyectos reales suele mantenerse por claridad).
  • Si la función se define después de su primera llamada, necesitas prototipo.
  • Si la función está en otro archivo .c, necesitas un prototipo accesible (normalmente en un .h).

Uso de headers propios (.h) y separación en archivos

Cuando un proyecto crece, conviene separar interfaz (prototipos) e implementación (definiciones). Una estructura típica:

  • util.h: prototipos y documentación breve.
  • util.c: definiciones.
  • main.c: usa las funciones incluyendo el header.

Ejemplo: header propio

Archivo: util.h

#ifndef UTIL_H
#define UTIL_H

int sumar(int a, int b);
int maximo(int a, int b);

#endif

Archivo: util.c

Continúa en nuestra aplicación.
  • Escuche el audio con la pantalla apagada.
  • Obtenga un certificado al finalizar.
  • ¡Más de 5000 cursos para que explores!
O continúa leyendo más abajo...
Download App

Descargar la aplicación

#include "util.h"

int sumar(int a, int b) {
    return a + b;
}

int maximo(int a, int b) {
    return (a > b) ? a : b;
}

Archivo: main.c

#include <stdio.h>
#include "util.h"

int main(void) {
    printf("%d\n", sumar(2, 3));
    printf("%d\n", maximo(7, 4));
    return 0;
}

Buenas prácticas con headers

  • Usa include guards (#ifndef/#define/#endif) para evitar inclusiones múltiples.
  • En el .h pon solo lo necesario: prototipos, tipos y constantes relacionadas con esa “interfaz”.
  • En el .c incluye su propio header primero (por ejemplo, #include "util.h") para detectar inconsistencias entre prototipo y definición.

Parámetros por valor: qué significa realmente

En C, los parámetros se pasan por valor: la función recibe una copia. Cambiar el parámetro dentro de la función no cambia la variable original del llamador.

#include <stdio.h>

void intentar_incrementar(int x) {
    x = x + 1;
}

int main(void) {
    int a = 10;
    intentar_incrementar(a);
    printf("%d\n", a); // sigue siendo 10
    return 0;
}

Esta característica ayuda a reducir efectos colaterales: si una función solo trabaja con copias y devuelve un resultado, es más fácil de probar y razonar.

Valores de retorno y la sentencia return

Retornar un resultado

Una función puede devolver un valor con return. El tipo de retorno debe coincidir con el declarado.

int cuadrado(int n) {
    return n * n;
}

Retornos tempranos (early return) para simplificar

En funciones con validaciones, un retorno temprano evita anidar demasiados if y hace el flujo más legible.

int es_par(int n) {
    if (n % 2 != 0) return 0;
    return 1;
}

Qué pasa si olvidas return

En una función que no es void, llegar al final sin return es un error lógico y suele generar advertencias. Trátalas como fallos: una función que promete devolver algo debe devolverlo en todos los caminos.

Funciones void: cuando no hay valor de retorno

Una función void no devuelve un valor. Se usa cuando la acción principal es “hacer algo” (por ejemplo, imprimir, registrar, inicializar) y no necesitas un resultado directo.

void imprimir_linea(char c, int n) {
    for (int i = 0; i < n; i++) {
        putchar(c);
    }
    putchar('\n');
}

También puedes usar return; sin valor para salir antes de una función void:

void mostrar_si_positivo(int x) {
    if (x <= 0) return;
    printf("%d\n", x);
}

Patrones de diseño simples para funciones

1) Una función por responsabilidad

Si una función hace “demasiadas cosas”, se vuelve difícil de probar y mantener. Señales típicas: nombre genérico (procesar, hacerTodo), muchos if y variables temporales, o mezcla de cálculo con impresión.

Ejemplo de separación:

  • cálculo: devuelve un número
  • presentación: imprime ese número
double area_circulo(double r) {
    const double PI = 3.141592653589793;
    return PI * r * r;
}

void imprimir_area(double r) {
    printf("Area: %.2f\n", area_circulo(r));
}

2) Nombres descriptivos y consistentes

  • Prefiere verbos: calcular_total, validar_edad, leer_opcion.
  • Evita abreviaturas ambiguas: calc, proc, tmp.
  • Si el proyecto usa snake_case, mantén el estilo en todas las funciones.

3) Evitar efectos colaterales cuando no son necesarios

Un efecto colateral es modificar algo “externo” a la función (variables globales, archivos, salida por pantalla) además de producir un resultado. No son malos por sí mismos, pero complican pruebas y reutilización.

Comparación:

EnfoqueVentajaDesventaja
Función que calcula y devuelveFácil de probar, reutilizableRequiere que el llamador decida qué hacer con el resultado
Función que calcula e imprimeRápida de usar en programas pequeñosDifícil de reutilizar si luego quieres guardar en archivo o usar en otra operación
// Preferible para reutilización
int sumar(int a, int b) {
    return a + b;
}

// Menos reutilizable
void sumar_e_imprimir(int a, int b) {
    printf("%d\n", a + b);
}

Guía práctica: refactorizar un main grande en funciones pequeñas

Objetivo: transformar un main con muchas tareas mezcladas en un conjunto de funciones comprobables. La idea es separar: (1) lógica de negocio/cálculo, (2) validación/decisiones, (3) presentación.

Paso 1: Identifica bloques repetidos o con propósito claro

Ejemplo inicial (main “grande”):

#include <stdio.h>

int main(void) {
    int n;
    printf("Ingrese un entero positivo: ");
    if (scanf("%d", &n) != 1 || n <= 0) {
        printf("Entrada invalida\n");
        return 1;
    }

    int suma = 0;
    for (int i = 1; i <= n; i++) {
        suma += i;
    }

    int factorial = 1;
    for (int i = 1; i <= n; i++) {
        factorial *= i;
    }

    printf("Suma 1..%d = %d\n", n, suma);
    printf("Factorial de %d = %d\n", n, factorial);
    return 0;
}

Aquí hay al menos tres responsabilidades: leer/validar, calcular suma, calcular factorial, y mostrar resultados.

Paso 2: Extrae funciones puras para los cálculos

Empieza por lo más fácil de probar: funciones que reciben datos y devuelven resultados.

int suma_hasta(int n) {
    int suma = 0;
    for (int i = 1; i <= n; i++) suma += i;
    return suma;
}

int factorial_de(int n) {
    int fact = 1;
    for (int i = 1; i <= n; i++) fact *= i;
    return fact;
}

Paso 3: Extrae una función para la lectura/validación

Como la lectura usa E/S, es normal que sea void o que devuelva un estado. Un patrón simple: devolver 1 si se pudo leer y 0 si no.

int leer_entero_positivo(int *out_n) {
    int n;
    printf("Ingrese un entero positivo: ");
    if (scanf("%d", &n) != 1) return 0;
    if (n <= 0) return 0;
    *out_n = n;
    return 1;
}

Nota: aquí se usa un parámetro de salida (out_n) para “devolver” el número leído junto con un código de éxito. Es un patrón común cuando necesitas devolver más de un dato (estado + valor).

Paso 4: Reescribe main como orquestador

Ahora main coordina llamadas, y las funciones hacen el trabajo específico.

#include <stdio.h>

int suma_hasta(int n);
int factorial_de(int n);
int leer_entero_positivo(int *out_n);

int main(void) {
    int n;
    if (!leer_entero_positivo(&n)) {
        printf("Entrada invalida\n");
        return 1;
    }

    int suma = suma_hasta(n);
    int fact = factorial_de(n);

    printf("Suma 1..%d = %d\n", n, suma);
    printf("Factorial de %d = %d\n", n, fact);
    return 0;
}

int suma_hasta(int n) {
    int suma = 0;
    for (int i = 1; i <= n; i++) suma += i;
    return suma;
}

int factorial_de(int n) {
    int fact = 1;
    for (int i = 1; i <= n; i++) fact *= i;
    return fact;
}

int leer_entero_positivo(int *out_n) {
    int n;
    printf("Ingrese un entero positivo: ");
    if (scanf("%d", &n) != 1) return 0;
    if (n <= 0) return 0;
    *out_n = n;
    return 1;
}

Paso 5: Si el archivo crece, separa en .h y .c

Cuando estas funciones se vuelven reutilizables, muévelas a un módulo. Por ejemplo:

  • math_utils.h: prototipos suma_hasta, factorial_de
  • math_utils.c: definiciones
  • io_utils.h: prototipo leer_entero_positivo
  • io_utils.c: definición

Así, main.c queda enfocado en el flujo general y no en los detalles.

Checklist rápido para diseñar funciones en C

  • Firma clara: parámetros mínimos necesarios y retorno útil.
  • Responsabilidad única: si necesitas “y además”, probablemente falte una función.
  • Evita mezclar cálculo con E/S si quieres reutilizar y probar.
  • Prototipos visibles: antes de usar o en un .h propio.
  • Retornos consistentes: toda ruta en funciones no-void debe devolver un valor.

Ahora responde el ejercicio sobre el contenido:

¿Cuál es la razón principal para declarar un prototipo de función antes de main cuando la definición de la función aparece después en el archivo?

¡Tienes razón! Felicitaciones, ahora pasa a la página siguiente.

¡Tú error! Inténtalo de nuevo.

El prototipo anuncia la firma de la función (retorno y parámetros) para que el compilador pueda comprobar la llamada aunque la implementación esté definida después o en otro archivo.

Siguiente capítulo

Alcance, duración y visibilidad de variables en C

Arrow Right Icon
Portada de libro electrónico gratuitaFundamentos de programación en C desde cero: variables, control de flujo y funciones
64%

Fundamentos de programación en C desde cero: variables, control de flujo y funciones

Nuevo curso

11 páginas

Descarga la aplicación para obtener una certificación gratuita y escuchar cursos en segundo plano, incluso con la pantalla apagada.