Saltar al contenido
ES EN

Paso por valor y por referencia en C: ejercicio resuelto

Actualizado el

Código de Examen C Fundamentos de programación Nivel básico Código probado

Ejercicio resuelto en C: funciones con paso por valor y por referencia (punteros), con tiempos de una marcha ciclista, código probado y errores típicos.

Código de Examen: Ejercicio resuelto: paso por valor y por referencia en C
Fundamentos de Programación · Prácticas de laboratorioEjercicio 2 · 2,5 puntos · 25 min

En una marcha cicloturista cada participante lleva un cronómetro sencillo que, en cada tramo, registra tres contadores independientes: horas, minutos y segundos. El aparato no normaliza nada, así que un tramo puede quedar anotado como 0 horas, 75 minutos y 130 segundos. La organización quiere un programa que ponga orden en esos registros.

Escribe un programa en C que lea por la entrada estándar un entero N (0 ≤ N ≤ 1000) y, a continuación, N líneas con tres enteros h m s separados por espacios, cada uno entre 0 y 10000. Cada línea es la duración de un tramo.

Debes implementar y usar obligatoriamente estas tres funciones: long long a_segundos(int h, int m, int s), que recibe los contadores por valor y devuelve la duración en segundos; void descomponer(long long total, long long *h, long long *m, long long *s), que recibe un número de segundos y deja en las tres variables apuntadas las horas, los minutos (0 a 59) y los segundos (0 a 59); y void acumular(long long seg, int indice, long long *total, long long *maximo, int *pos_max), que suma seg al total y actualiza el tramo más largo.

Salida: para cada tramo, en el orden de entrada, una línea Tramo i: HH:MM:SS, con i empezando en 1 y cada campo con al menos dos dígitos (las horas pueden tener más). Después, una línea Total: HH:MM:SS con la suma de todos los tramos y una línea Tramo mayor: k con el número del tramo más largo. Si hay empate gana el primero. Si N vale 0, el total es 00:00:00 y la última línea es Tramo mayor: ninguno.

No uses variables globales: toda la información debe circular entre funciones mediante parámetros y valores de retorno.

Entrada de ejemplo

3
0 75 130
1 0 0
0 59 61

Salida esperada

Tramo 1: 01:17:10
Tramo 2: 01:00:00
Tramo 3: 01:00:01
Total: 03:17:11
Tramo mayor: 1
Función a_segundos con paso por valor, sin efectos laterales y sin desbordamiento
0,5
Función descomponer que devuelve horas, minutos y segundos mediante punteros
0,75
Función acumular que actualiza total, máximo y posición por referencia, con desempate correcto
0,75
Programa principal: lectura, formato exacto de salida y caso sin tramos
0,5

Pistas

Pista 1 · ¿Qué función necesita punteros y cuál no?

Pregúntate cuántos resultados produce cada función. a_segundos produce uno solo, así que le basta con return y sus parámetros pueden ser copias. descomponer produce tres y acumular modifica variables que viven en main: ahí no hay más remedio que recibir sus direcciones.

Pista 2 · ¿Cómo se normaliza sin hacer casos especiales?

No intentes arrastrar acarreos de segundos a minutos y de minutos a horas con if. Convierte todo a segundos y luego deshaz la conversión con división entera y resto: las horas salen de dividir entre 3600, los minutos del resto de esa división dividido entre 60, y los segundos del resto entre 60.

Pista 3 · ¿Con qué valor empieza el máximo?

Un tramo puede durar 0 segundos y debe poder ser el mayor. Si inicializas maximo a -1 y pos_max a 0, cualquier tramo real supera ese máximo, y al final pos_max == 0 significa que no se leyó ninguno. Usa la comparación estricta > para que en un empate se quede el primero.

Solución

Solución explicada

La clave del ejercicio no es la aritmética, que es sencilla, sino decidir qué viaja por valor y qué por referencia. En C todos los argumentos se pasan por valor: la función recibe una copia. Cuando queremos que una función modifique una variable del llamador, lo que copiamos es su dirección, y la función escribe a través del puntero con *. Es justo lo que hace scanf cuando le pasamos &n.

La solución convierte cada tramo a segundos, lo descompone para imprimirlo normalizado y lo acumula. Al final descompone el total con la misma función, lo que demuestra que una función bien diseñada se reutiliza sin cambios.

#include <stdio.h>

/* Paso por valor: recibe copias y devuelve un unico resultado. */
static long long a_segundos(int h, int m, int s)
{
    return (long long)h * 3600 + (long long)m * 60 + s;
}

/* Paso por referencia: escribe tres resultados en variables del llamador. */
static void descomponer(long long total, long long *h, long long *m, long long *s)
{
    *h = total / 3600;
    *m = (total % 3600) / 60;
    *s = total % 60;
}

/* Paso por referencia de entrada/salida: actualiza el estado de main. */
static void acumular(long long seg, int indice, long long *total,
                     long long *maximo, int *pos_max)
{
    *total += seg;
    if (seg > *maximo) {
        *maximo = seg;
        *pos_max = indice;
    }
}

int main(void)
{
    int n;
    long long total = 0;
    long long maximo = -1;
    int pos_max = 0;
    long long hh = 0, mm = 0, ss = 0;

    if (scanf("%d", &n) != 1 || n < 0) {
        n = 0;
    }

    for (int i = 1; i <= n; i++) {
        int h, m, s;
        if (scanf("%d %d %d", &h, &m, &s) != 3) {
            break;
        }
        long long seg = a_segundos(h, m, s);
        descomponer(seg, &hh, &mm, &ss);
        printf("Tramo %d: %02lld:%02lld:%02lld\n", i, hh, mm, ss);
        acumular(seg, i, &total, &maximo, &pos_max);
    }

    descomponer(total, &hh, &mm, &ss);
    printf("Total: %02lld:%02lld:%02lld\n", hh, mm, ss);

    if (pos_max == 0) {
        printf("Tramo mayor: ninguno\n");
    } else {
        printf("Tramo mayor: %d\n", pos_max);
    }
    return 0;
}

a_segundos es el caso puro de paso por valor. Recibe copias de h, m y s; aunque las modificara dentro, main no se enteraría. Fíjate en el molde (long long)h * 3600: sin él, la multiplicación se haría en int y solo después se convertiría el resultado. Con los límites del enunciado un tramo cabe en int, pero el total de 1000 tramos máximos (unos 3,66·1010 segundos) no, y conviene acostumbrarse a pensar en el tipo antes de operar.

descomponer necesita devolver tres números, y return solo devuelve uno. Por eso recibe tres punteros y escribe con *h = total / 3600. En la llamada descomponer(seg, &hh, &mm, &ss) el operador & obtiene la dirección de cada variable de main; la función recibe una copia de esas direcciones, pero a través de ellas modifica los originales. En el ejemplo, el primer tramo vale 0·3600 + 75·60 + 130 = 4630 segundos, que se descomponen en 1 hora, 17 minutos y 10 segundos.

acumular combina las dos ideas: seg e indice llegan por valor porque solo se leen, mientras que total, maximo y pos_max llegan por referencia porque la función los lee y los actualiza. Es un parámetro de entrada y salida. La comparación seg > *maximo es estricta, así que en el test del empate (dos tramos de exactamente una hora, uno escrito como 0 0 3600 y otro como 1 0 0) se mantiene el tramo 1.

En main el valor inicial maximo = -1 resuelve dos problemas a la vez. Garantiza que el primer tramo siempre se registre, incluso si dura 0 segundos, como en el test de un único tramo nulo, y deja pos_max a 0 cuando no hay ningún tramo, lo que permite imprimir Tramo mayor: ninguno en el caso N = 0. Ese mismo caso imprime Total: 00:00:00 sin código especial, porque total empieza a cero.

El formato %02lld rellena con ceros hasta dos dígitos, pero no trunca: en el test de límites cada tramo da 36 610 000 segundos y se imprime como 10169:26:40. Por último, la lectura comprueba el valor de retorno de scanf; si la entrada se corta antes de tiempo, el bucle termina y se imprime lo acumulado hasta ese momento. Si quieres repasar más preguntas de este estilo, tienes una colección en ejercicios resueltos en C de preguntas de examen.

Código y tests en GitLab

Traza paso a paso · generada ejecutando el código

Pasoisegtotalmaximopos_max
114630463046301
223600823046301
3336011183146301

Casos de prueba

CasoEntradaSalida esperadaSalida realResultado
Ejemplo del enunciado3 0 75 130 1 0 0 0 59 61Tramo 1: 01:17:10 Tramo 2: 01:00:00 Tramo 3: 01:00:01 Total: 03:17:11 Tramo mayor: 1Tramo 1: 01:17:10 Tramo 2: 01:00:00 Tramo 3: 01:00:01 Total: 03:17:11 Tramo mayor: 1OK
Sin tramos0Total: 00:00:00 Tramo mayor: ningunoTotal: 00:00:00 Tramo mayor: ningunoOK
Un único tramo nulo1 0 0 0Tramo 1: 00:00:00 Total: 00:00:00 Tramo mayor: 1Tramo 1: 00:00:00 Total: 00:00:00 Tramo mayor: 1OK
Empate: gana el primero2 0 0 3600 1 0 0Tramo 1: 01:00:00 Tramo 2: 01:00:00 Total: 02:00:00 Tramo mayor: 1Tramo 1: 01:00:00 Tramo 2: 01:00:00 Total: 02:00:00 Tramo mayor: 1OK
Valores máximos2 10000 10000 10000 10000 10000 10000Tramo 1: 10169:26:40 Tramo 2: 10169:26:40 Total: 20338:53:20 Tramo mayor: 1Tramo 1: 10169:26:40 Tramo 2: 10169:26:40 Total: 20338:53:20 Tramo mayor: 1OK
El mayor es el último3 0 0 59 0 2 0 0 1 61Tramo 1: 00:00:59 Tramo 2: 00:02:00 Tramo 3: 00:02:01 Total: 00:05:00 Tramo mayor: 3Tramo 1: 00:00:59 Tramo 2: 00:02:00 Tramo 3: 00:02:01 Total: 00:05:00 Tramo mayor: 3OK

Salidas reales: código compilado con gcc 14.4.0 (C17) y ejecutado en un contenedor aislado el 6 de octubre de 2026.

Complejidad

Cada tramo se procesa una sola vez con un número constante de operaciones: una conversión, una descomposición, una impresión y una actualización. El tiempo es por tanto O(N), lineal en el número de tramos, y las funciones auxiliares son O(1).

La memoria es O(1): no se guardan los tramos en un vector, porque toda la información necesaria (total, máximo y su posición) se actualiza sobre la marcha. Ese es precisamente el valor de acumular con parámetros por referencia: el estado vive en main y la función lo mantiene al día sin copiarlo.

Errores típicos

  • Declarar descomponer con parámetros long long h, long long m, long long s en lugar de punteros: la función calcula bien, pero escribe en sus copias locales y main imprime los valores que tuvieran antes sus variables.
  • Olvidar el asterisco dentro de la función y escribir h = total / 3600 con h puntero: se cambia la dirección guardada en la copia del puntero, no el dato apuntado, y el compilador avisa de una conversión de entero a puntero.
  • Llamar a descomponer(seg, hh, mm, ss) sin el operador &: se pasan valores donde se esperan direcciones; el programa no compila limpio y, si se fuerza, suele terminar en violación de segmento.
  • Inicializar maximo a 0 y comparar con >: un único tramo de duración 0 nunca se registra y el programa responde ninguno cuando debería responder 1.
  • Usar >= en la comparación del máximo: con tramos empatados gana el último, contradiciendo el enunciado.
  • Calcular en int sin moldes ni long long: con muchos tramos largos el total desborda y aparecen horas negativas.

Variantes

Normalizar en el sitio sin pasar por segundos

El profesor puede pedir void normalizar(int *h, int *m, int *s), que modifique directamente los tres contadores. Se resuelve con acarreos: *m += *s / 60; *s %= 60; *h += *m / 60; *m %= 60;. El orden importa: primero se pasan los segundos sobrantes a minutos y después los minutos a horas. Aquí los tres parámetros son a la vez de entrada y de salida.

Devolver una estructura en lugar de usar punteros

Otra forma de devolver varios valores es definir struct duracion { long long h, m, s; }; y escribir struct duracion descomponer(long long total), que construye la estructura y la devuelve por valor. Es más legible y evita punteros nulos, pero copia la estructura en cada llamada; con tres campos el coste es irrelevante. Conviene saber defender ambas opciones en el examen.

Intercambiar los dos tramos más largos

Una pregunta clásica es añadir void intercambiar(long long *a, long long *b) con una variable auxiliar: long long t = *a; *a = *b; *b = t;. Sirve para comprobar que el alumno entiende que una versión sin punteros intercambia solo las copias y deja intactas las variables originales.

Historial de cambios

  1. Reescrito con la plantilla de Código de Examen: enunciado de examen, pistas, solución explicada, casos de prueba y código compilado y ejecutado; versión en inglés enlazada.
  2. Publicación original.

Elaborado con apoyo de IA y revisado por la redacción · Revisado por Jose Santamaria

Kernel

· Dirección de tecnología · España

«Un puntero en la firma es un contrato: esta función va a tocar tus datos, y tienes que saber exactamente cuáles.»

Comentarios