Bucles en C — teoría con for, while y do…while contando las paradas de la guagua (y el ; que solo paró en la parada 6)

Volvía de la facultad en la línea 12 y, para no aburrirme, quise hacer el programa más tonto del mundo: que escribiera las cinco paradas del trayecto, una por línea. Un for de libro. Lo compilé en el portátil, lo ejecuté y me salió esto:

#include <stdio.h>

int main(void) {
    int parada;

    printf("Paradas de la línea 12:\n");
    for (parada = 1; parada <= 5; parada++);
    {
        printf("Parada %d\n", parada);
    }
    printf("Fin de trayecto\n");
    return 0;
}

Salida:

Paradas de la línea 12:
Parada 6
Fin de trayecto

Una sola parada, y encima la 6, que ni existe. Cinco minutos mirando el código hasta que lo vi: un ; al final de la línea del for. Ese punto y coma es un cuerpo vacío. El bucle dio sus cinco vueltas sin hacer nada, parada acabó valiendo 6 y el bloque de las llaves se ejecutó una vez, ya fuera del bucle. gcc, por cierto, me había avisado y no lo leí:

gancho.c: In function 'main':
gancho.c:7: warning: this 'for' clause does not guard... [-Wmisleading-indentation]
gancho.c:8: note: ...this statement, but the latter is misleadingly indented as if it were guarded by the 'for'

En este artículo vemos los tres bucles de C (for, while y do…while), en qué orden se ejecuta cada parte, cuánto vale la variable cuando el bucle termina, break, continue, bucles anidados y los errores que más se repiten en IC2: el ; de la anécdota, la vuelta de más por un <=, el scanf que deja un bucle girando para siempre y el break que no sale de donde creías. Venimos de los condicionales en C, donde la máquina de billetes de la guagua solo sabía atender a un viajero; aquí aprende a atender a todos. Y si quieres los programas completos, están en la práctica de bucles en C.

Las líneas, paradas y tarifas de la guagua de este tema son inventadas para el ejemplo. Todos los programas se han compilado con gcc -Wall -std=c11 (gcc 14) y las salidas son las reales. Si todavía no tienes el entorno montado, sigue la guía de gedit y gcc en Fedora, o pruébalos directamente en el entorno de C de la web.

El bucle for en C — la mayor sorpresa viniendo de Python

En Python, for x in lista recorre una secuencia. En C no hay secuencias que recorrer: el for es un bucle de contador con tres partes separadas por ; dentro de los paréntesis:

for (inicialización; condición; actualización) {
    // cuerpo: lo que se repite
}
  • Inicialización: se ejecuta una sola vez, antes de empezar.
  • Condición: se comprueba antes de cada vuelta. Si es verdadera (distinta de 0), se ejecuta el cuerpo; si es falsa, el bucle termina.
  • Actualización: se ejecuta al final de cada vuelta, después del cuerpo.

Veámoslo con la guagua: cuatro paradas y en cada una suben tres viajeros.

#include <stdio.h>

int main(void) {
    int parada;
    int viajeros = 0;                 // a bordo al salir de la cabecera

    for (parada = 1; parada <= 4; parada++) {
        viajeros += 3;                // en cada parada suben 3
        printf("Parada %d: %2d viajeros a bordo\n", parada, viajeros);
    }
    // El for ya ha terminado: parada se quedó en el valor que hizo falsa la condición
    printf("Al salir del for, parada vale %d\n", parada);
    return 0;
}

Salida:

Parada 1:  3 viajeros a bordo
Parada 2:  6 viajeros a bordo
Parada 3:  9 viajeros a bordo
Parada 4: 12 viajeros a bordo
Al salir del for, parada vale 5

La última línea es la que más sorprende la primera vez. ¿Por qué parada vale 5 si solo hay cuatro paradas? Porque el for no termina cuando acaba la cuarta vuelta, sino cuando la condición da falso. Después de la vuelta 4, la actualización pone parada a 5, la condición pregunta 5 <= 4, la respuesta es No y se sale. El dibujo lo enseña paso a paso:

En qué orden se ejecuta un forLa línea for (parada = 1; parada <= 4; parada++) con sus tres partes en color y debajo su ejecución: ① parada = 1 una sola vez; cuatro vueltas con ② la comprobación (Sí), el cuerpo (3, 6, 9 y 12 a bordo) y ③ parada++; y una última comprobación 5 <= 4 que da No y deja parada en 5.for (parada = 1; parada <= 4; parada++)① una vez al principio · ② antes de cada vuelta · ③ al final de cada vuelta① parada = 1(no se repite nunca)1② 1 <= 4: Sícuerpo: 3 a bordo③ parada = 22② 2 <= 4: Sícuerpo: 6 a bordo③ parada = 33② 3 <= 4: Sícuerpo: 9 a bordo③ parada = 44② 4 <= 4: Sícuerpo: 12 a bordo③ parada = 5fin② 5 <= 4: Nosale del for con parada = 5 (la que hizo falsa la condición)
La condición se comprueba cinco veces para cuatro vueltas: la última es la que dice No. Por eso, al salir, parada vale 5.

Si lo comprobamos a mano: la condición se evalúa con 1, 2, 3, 4 (cuatro Sí) y con 5 (el No). Cinco comprobaciones para cuatro vueltas, siempre una más. Y los viajeros: 3 + 3 + 3 + 3 = 12.

Vueltaparada al empezar¿parada <= 4?viajeros tras el cuerpoparada tras parada++
11Sí32
22Sí63
33Sí94
44Sí125
—5No: sale12—

Los range() de Python en C

Si vienes de Python (del tema de bucles en Python), esta tabla te ahorra pensar. Cada range se traduce cambiando el inicio, la condición y el paso:

#include <stdio.h>

int main(void) {
    int i;

    printf("range(5)         -> ");
    for (i = 0; i < 5; i++) printf("%d ", i);           // 0 1 2 3 4
    printf("\nrange(1, 6)      -> ");
    for (i = 1; i <= 5; i++) printf("%d ", i);          // 1 2 3 4 5
    printf("\nrange(0, 10, 2)  -> ");
    for (i = 0; i < 10; i += 2) printf("%d ", i);       // de 2 en 2
    printf("\nrange(5, 0, -1)  -> ");
    for (i = 5; i > 0; i--) printf("%d ", i);           // hacia atrás
    printf("\n");
    return 0;
}

Salida:

range(5)         -> 0 1 2 3 4 
range(1, 6)      -> 1 2 3 4 5 
range(0, 10, 2)  -> 0 2 4 6 8 
range(5, 0, -1)  -> 5 4 3 2 1 
PythonCOjo con
range(5)for (i = 0; i < 5; i++)El final no se incluye: <
range(1, 6)for (i = 1; i <= 5; i++)Para incluir el 5, <=
range(0, 10, 2)for (i = 0; i < 10; i += 2)El paso va en la actualización
range(5, 0, -1)for (i = 5; i > 0; i--)Hacia atrás: > y i--

Si el bucle tiene que ir hacia atrás, cambian a la vez la comparación (> o >=) y la actualización (i--). Si solo cambias una, o no entra nunca o no sale nunca.

Declaración de la variable de control

En C antiguo (C89) la variable del bucle tenía que declararse antes del for. Desde C99 se puede declarar dentro, y con gcc -std=c11 (lo que usamos en todo el temario) funciona sin más:

int i;                        // fuera: sigue viva después del for
for (i = 0; i < 3; i++) { }

for (int j = 0; j < 3; j++) { }   // dentro: solo existe en el for

La diferencia no es de estilo: cambia dónde existe la variable. Si la declaras dentro e intentas usarla después, no compila:

#include <stdio.h>

int main(void) {
    for (int i = 0; i < 3; i++) {     // i solo existe dentro del for
        printf("vuelta %d\n", i);
    }
    printf("i al salir: %d\n", i);    // aquí i ya no existe
    return 0;
}
ambito.c: In function 'main':
ambito.c:7: error: 'i' undeclared (first use in this function)
ambito.c:7: note: each undeclared identifier is reported only once for each function it appears in

Entonces, ¿cuál uso? Si después del bucle necesitas saber dónde se quedó (como en las prácticas de IC2, que recolocan un puntero con asientos = asientos - i), decláralo fuera. Si no, dentro: así nadie la usa por error.

Cuánto vale la variable al salir: < frente a <=

Este es uno de los fallos que más puntos quita en IC2, porque el programa compila y casi funciona. Mira tres bucles parecidos y cuántas vueltas dan:

#include <stdio.h>

int main(void) {
    int i;
    int vueltas;

    vueltas = 0;
    for (i = 0; i < 5; i++) vueltas++;
    printf("i = 0; i <  5 : %d vueltas, i acaba en %d\n", vueltas, i);

    vueltas = 0;
    for (i = 0; i <= 5; i++) vueltas++;                 // una vuelta de más
    printf("i = 0; i <= 5 : %d vueltas, i acaba en %d\n", vueltas, i);

    vueltas = 0;
    for (i = 1; i <= 5; i++) vueltas++;
    printf("i = 1; i <= 5 : %d vueltas, i acaba en %d\n", vueltas, i);
    return 0;
}

Salida:

i = 0; i <  5 : 5 vueltas, i acaba en 5
i = 0; i <= 5 : 6 vueltas, i acaba en 6
i = 1; i <= 5 : 5 vueltas, i acaba en 6

Con i = 0; i < 5 hay cinco vueltas (0, 1, 2, 3 y 4). Con i = 0; i <= 5, seis: el 5 también entra. Y con i = 1; i <= 5 vuelven a ser cinco, pero i acaba en 6, no en 5. La regla para contar vueltas sin equivocarte: con <, son final − inicio (5 − 0 = 5); con <=, una más (5 − 1 + 1 = 5; 5 − 0 + 1 = 6). Y al salir, la variable vale siempre el primer valor que hizo falsa la condición.

Escribir <= cuando querías < (o al revés) da una vuelta de más o de menos y no hay aviso del compilador. Cuenta las vueltas con la regla de arriba antes de compilar, sobre todo cuando el bucle recorre un vector: en el tema de punteros verás que esa vuelta de más lee memoria que no es tuya.

Los operadores de incremento: i++, ++i e i += n

La actualización casi siempre es i++, pero conviene saber qué hace exactamente, porque ++ también aparece fuera de los for:

#include <stdio.h>

int main(void) {
    int a = 5, b = 5;
    int x = a++;       // x recibe 5 y DESPUÉS a pasa a 6
    int y = ++b;       // b pasa a 6 y DESPUÉS y recibe 6
    int k;

    printf("x = %d, a = %d\n", x, a);
    printf("y = %d, b = %d\n", y, b);

    // En la actualización de un for da igual i++ que ++i: el valor no se usa
    for (k = 0; k < 10; k += 3) {
        printf("%d ", k);
    }
    printf("\n");
    return 0;
}

Salida:

x = 5, a = 6
y = 6, b = 6
0 3 6 9 

a++ (posincremento) entrega el valor viejo y después suma: x recibe 5 y a acaba en 6. ++b (preincremento) suma primero y entrega el valor nuevo: y recibe 6. En la actualización de un for el valor entregado no se usa, así que i++ y ++i hacen lo mismo. Para saltos distintos de 1, i += 3 (0, 3, 6, 9) o i -= 2.

Ejemplos prácticos del for

Tres usos que vas a escribir mil veces: repetir algo un número fijo de veces, acumular y contar hacia atrás.

#include <stdio.h>

int main(void) {
    int i;
    int suma = 0;

    // Tabla de multiplicar del 7: número fijo de vueltas, caso de for de libro
    printf("Tabla del 7:\n");
    for (i = 1; i <= 10; i++) {
        printf("7 x %2d = %2d\n", i, 7 * i);
    }

    // Suma de 1 a 100: el acumulador empieza en 0 ANTES del bucle
    for (i = 1; i <= 100; i++) {
        suma += i;
    }
    printf("\nSuma 1-100: %d\n", suma);

    // Cuenta atrás: se empieza arriba y se resta
    printf("\nCuenta atrás: ");
    for (i = 10; i >= 1; i--) {
        printf("%d ", i);
    }
    printf("¡Ya!\n");
    return 0;
}

Salida:

Tabla del 7:
7 x  1 =  7
7 x  2 = 14
7 x  3 = 21
7 x  4 = 28
7 x  5 = 35
7 x  6 = 42
7 x  7 = 49
7 x  8 = 56
7 x  9 = 63
7 x 10 = 70

Suma 1-100: 5050

Cuenta atrás: 10 9 8 7 6 5 4 3 2 1 ¡Ya!

La suma de 1 a 100 da 5050, que puedes comprobar con la fórmula de Gauss: 100 · 101 / 2 = 5050. Fíjate en que suma = 0 va antes del bucle: si lo pusieras dentro, se reiniciaría en cada vuelta y acabarías con 100. Y en la tabla, %2d reserva dos huecos para que el 7 y el 70 queden alineados.

El bucle while

El while es el más sencillo: comprueba la condición antes de cada vuelta y repite mientras sea verdadera. No tiene ni inicialización ni actualización propias; eso lo pones tú.

while (condición) {
    // cuerpo
    // algo aquí tiene que acabar cambiando la condición
}

Respecto a Python, solo cambia la forma: paréntesis obligatorios y llaves en lugar de sangría. Su sitio es cuando no sabes cuántas vueltas habrá. Por ejemplo, el conductor apunta la edad de cada viajero que sube hasta que teclea 0:

#include <stdio.h>

int main(void) {
    int edad;
    int suma = 0;
    int contador = 0;

    printf("Edades de los viajeros (0 para terminar):\n");
    // Se lee en la propia condición: si scanf no lee un número o llega el 0, se para
    while (scanf("%d", &edad) == 1 && edad != 0) {
        suma += edad;
        contador++;
    }

    if (contador > 0) {
        printf("Viajeros: %d\n", contador);
        printf("Edad media: %.2f\n", (double)suma / contador);   // sin el (double), división entera
    } else {
        printf("No subió nadie\n");
    }
    return 0;
}

Con 22, 67, 15 y 0 como entrada:

Edades de los viajeros (0 para terminar):
22
67
15
0
Viajeros: 3
Edad media: 34.67

La condición hace dos cosas a la vez: scanf("%d", &edad) == 1 lee un número y comprueba que lo ha conseguido (scanf devuelve cuántos datos ha leído), y edad != 0 mira si es el centinela. Como && es de cortocircuito, si scanf falla ya no se mira edad. Las cuentas: 22 + 67 + 15 = 104 y 104 / 3 = 34,67. El (double) es imprescindible: sin él, 104 / 3 es división entera y daría 34.

Al valor especial que marca el final de los datos (aquí, el 0) se le llama centinela. Elige uno que no pueda ser un dato real: una edad de 0 no tiene sentido, así que sirve.

For vs While — cuándo usar cada uno

Cualquier for se puede escribir como while y al revés. Estos dos hacen exactamente lo mismo:

for (i = 0; i < 5; i++) {        i = 0;
    printf("%d ", i);            while (i < 5) {
}                                    printf("%d ", i);
                                     i++;
                                 }

Entonces, ¿por qué hay dos? Por legibilidad: el for junta en una línea todo lo que controla el bucle, y el while deja claro que no sabes cuándo vas a parar.

SituaciónBuclePor qué
Sabes cuántas vueltas: 10 paradas, 30 alumnos, de 1 a nforInicio, final y paso en una sola línea
Repetir hasta que llegue un dato especial (el 0, una letra)whileNo sabes cuántas veces será
Pedir un dato y repetir mientras sea incorrectodo…whileHay que pedirlo al menos una vez
Repetir hasta que algo pase (acertar, llenar la guagua)while con bandera o while (1) con breakLa salida está dentro del cuerpo

El bucle do…while — el que Python no tiene

El do…while ejecuta el cuerpo primero y comprueba la condición después. Por eso siempre da al menos una vuelta:

do {
    // cuerpo: se ejecuta al menos una vez
} while (condición);   // aquí el ; es obligatorio

La diferencia se ve muy bien con una condición que ya es falsa desde el principio:

#include <stdio.h>

int main(void) {
    int x = 10;

    printf("while:\n");
    while (x < 5) {                       // se mira ANTES: 10 < 5 es falso
        printf("  dentro del while\n");
    }

    printf("do...while:\n");
    do {
        printf("  dentro del do...while\n");
    } while (x < 5);                      // se mira DESPUÉS: ya ha entrado una vez
    return 0;
}

Salida:

while:
do...while:
  dentro del do...while

El while mira 10 < 5, ve que es falso y no entra nunca. El do…while entra, imprime y luego pregunta. El dibujo pone los tres bucles uno al lado del otro: fíjate en dónde está el rombo de la condición en cada uno.

Dónde mira la condición cada bucleTres diagramas de flujo. for: i = 0, rombo de la condición, cuerpo, i++ y vuelta al rombo; con No sale. while: rombo, cuerpo y vuelta. do…while: primero el cuerpo y después el rombo, que con Sí vuelve al cuerpo. Los dos primeros pueden dar 0 vueltas; el do…while da al menos una.fori = 0¿cond?Sícuerpoi++No0 vueltas si la condiciónya es falsa al llegarwhile¿cond?SícuerpoNo0 vueltas si la condiciónya es falsa al llegardo…whilecuerpo¿cond?SíNo1 vuelta como mínimo:la condición va al final
for y while preguntan antes de entrar; do…while pregunta al salir. Esa es toda la diferencia, y por eso valida datos tan bien.

Si quieres ver los tres bucles dibujados con rombos y flechas, como se piden en el examen, tienes el tema de diagramas de flujo en C.

¿existe do while en Python? No. En Python se imita con while True: y un break al final del cuerpo. En C también puedes escribirlo así (while (1) con break), pero el do…while lo dice más claro.

El caso de uso perfecto del do…while — validación de entrada

Para pedir un dato hay que pedirlo al menos una vez, y repetir mientras sea incorrecto. Es exactamente la forma del do…while:

#include <stdio.h>

int main(void) {
    float nota;

    do {
        printf("Introduce una nota (0-10): ");
        scanf("%f", &nota);
        if (nota < 0 || nota > 10) {
            printf("Error: la nota debe estar entre 0 y 10\n");
        }
    } while (nota < 0 || nota > 10);      // repite MIENTRAS el dato sea malo

    printf("Nota válida: %.2f\n", nota);
    return 0;
}

Con -5, 15 y 7.5 como entrada:

Introduce una nota (0-10): -5
Error: la nota debe estar entre 0 y 10
Introduce una nota (0-10): 15
Error: la nota debe estar entre 0 y 10
Introduce una nota (0-10): 7.5
Nota válida: 7.50

La condición del while del final es la condición de dato malo: nota < 0 || nota > 10. Mientras sea verdad, repite. Sin do…while tendrías que escribir el printf y el scanf dos veces, una antes del bucle y otra dentro.

Ahora, la trampa de IC2. ¿Qué pasa si en vez de un número alguien escribe la palabra siete? Así:

Introduce una nota (0-10): siete
Error: la nota debe estar entre 0 y 10
Introduce una nota (0-10): Error: la nota debe estar entre 0 y 10
Introduce una nota (0-10): Error: la nota debe estar entre 0 y 10
Introduce una nota (0-10): Error: la nota debe estar entre 0 y 10
[freno de emergencia: 4 vueltas]

Le puse un freno de emergencia de 4 vueltas (las líneas de vueltas) para que el artículo no sea infinito; sin él, el programa imprime el error sin parar hasta que pulsas Ctrl+C. ¿Por qué no espera a que escribas otra cosa? Porque scanf("%f") no puede leer siete como número, devuelve 0 y deja las letras en la entrada. En la vuelta siguiente se encuentra las mismas letras, vuelve a fallar, y así para siempre. Además nota no cambia, así que la condición sigue siendo verdadera.

Usar scanf dentro de un bucle sin mirar lo que devuelve. Con un número va bien; con una letra, bucle infinito. Comprueba siempre scanf(...) == 1 (o el número de datos que esperas).

La versión segura mira lo que devuelve scanf y, si falla, tira lo que queda de la línea:

#include <stdio.h>

int main(void) {
    float nota = -1;
    int leidos;                           // cuántos datos ha leído scanf
    int c;

    do {
        printf("Introduce una nota (0-10): ");
        leidos = scanf("%f", &nota);
        if (leidos != 1) {
            printf("Error: eso no es un número\n");
            while ((c = getchar()) != '\n' && c != EOF);   // vacía la línea; el ; es el cuerpo
            nota = -1;                                     // fuerza otra vuelta
        } else if (nota < 0 || nota > 10) {
            printf("Error: la nota debe estar entre 0 y 10\n");
        }
    } while (nota < 0 || nota > 10);

    printf("Nota válida: %.2f\n", nota);
    return 0;
}

Con siete, 12 y 7.5:

Introduce una nota (0-10): siete
Error: eso no es un número
Introduce una nota (0-10): 12
Error: la nota debe estar entre 0 y 10
Introduce una nota (0-10): 7.5
Nota válida: 7.50

La línea clave es while ((c = getchar()) != '\n' && c != EOF);. Lee carácter a carácter hasta el Intro (o hasta el final de la entrada) y no hace nada más: su cuerpo es el ;. Sí, el mismo ; de la anécdota, pero esta vez a propósito: aquí todo el trabajo lo hace la condición. Cuando escribas uno así, déjalo comentado para que nadie piense que es un error.

break y continue — controlar el flujo del bucle

  • break termina el bucle en el acto y salta a la primera instrucción después de él.
  • continue termina solo la vuelta actual y salta a la siguiente. En un for, «la siguiente» empieza por la actualización (i++) y después la condición.

La línea 12 recorre las paradas del 1 al 10, pero la 4 está en obras y en la 8 se acaba el turno:

#include <stdio.h>

int main(void) {
    int parada;

    printf("Línea 12, paradas 1 a 10:\n");
    for (parada = 1; parada <= 10; parada++) {
        if (parada == 4) {
            printf("  %d: en obras, no para\n", parada);
            continue;                     // salta al parada++ y sigue con la 5
        }
        if (parada == 8) {
            printf("  %d: fin del turno\n", parada);
            break;                        // sale del for: la 9 y la 10 no se visitan
        }
        printf("  %d: abre puertas\n", parada);
    }
    printf("Última parada visitada: %d\n", parada);
    return 0;
}

Salida:

Línea 12, paradas 1 a 10:
  1: abre puertas
  2: abre puertas
  3: abre puertas
  4: en obras, no para
  5: abre puertas
  6: abre puertas
  7: abre puertas
  8: fin del turno
Última parada visitada: 8

En la 4, continue se salta el printf("abre puertas") y la vuelta sigue con parada++, así que la 5 llega normal. En la 8, break sale del for: la 9 y la 10 no se visitan nunca y parada se queda en 8 (no en 11, como habría pasado si el bucle hubiera terminado por la condición).

Ahora la trampa: el mismo programa con while. En un while no hay actualización automática: el parada++ lo escribes tú al final del cuerpo… y continue se lo salta.

#include <stdio.h>

int main(void) {
    int parada = 1;
    int vueltas = 0;

    while (parada <= 10) {
        if (++vueltas > 8) {              // freno de emergencia (solo para el artículo)
            printf("[freno de emergencia: 8 vueltas]\n");
            break;
        }
        if (parada == 4) {
            printf("  %d: en obras, no para\n", parada);
            continue;                     // salta TAMBIÉN el parada++ de abajo
        }
        printf("  %d: abre puertas\n", parada);
        parada++;
    }
    return 0;
}

Salida (otra vez con freno de emergencia, de 8 vueltas):

  1: abre puertas
  2: abre puertas
  3: abre puertas
  4: en obras, no para
  4: en obras, no para
  4: en obras, no para
  4: en obras, no para
  4: en obras, no para
[freno de emergencia: 8 vueltas]

La guagua se queda atascada en la 4 para siempre: cada vuelta hace continue antes de llegar a parada++, así que parada nunca pasa de 4.

Usar continue en un while cuyo avance está al final del cuerpo. En un for no pasa (el i++ se ejecuta igual), en un while sí. Arreglo: avanza antes del continue o pasa el bucle a for.

Bucles anidados

Un bucle dentro de otro: por cada vuelta del de fuera, el de dentro da todas las suyas. Es lo que se usa para tablas, matrices y, en general, todo lo que tiene filas y columnas.

#include <stdio.h>

int main(void) {
    int i, j;

    // El for de fuera elige la fila; el de dentro recorre las columnas de esa fila
    for (i = 1; i <= 5; i++) {
        for (j = 1; j <= 5; j++) {
            printf("%4d", i * j);         // %4d: cada número ocupa 4 huecos
        }
        printf("\n");                     // fin de fila: fuera del for de dentro
    }
    return 0;
}

Salida:

   1   2   3   4   5
   2   4   6   8  10
   3   6   9  12  15
   4   8  12  16  20
   5  10  15  20  25

Cinco filas por cinco columnas: el printf de dentro se ejecuta 5 · 5 = 25 veces y el printf("\n"), solo 5, porque está fuera del for de dentro. Si lo metieras dentro, cada número saldría en su línea.

break solo sale del bucle de dentro

Busquemos el primer asiento libre de la guagua (el 3 de la fila 2) con dos bucles y un break:

#include <stdio.h>

int main(void) {
    int fila, asiento;

    // Buscamos el primer asiento libre de la guagua: el 3 de la fila 2
    for (fila = 1; fila <= 3; fila++) {
        for (asiento = 1; asiento <= 4; asiento++) {
            printf("(%d,%d) ", fila, asiento);
            if (fila == 2 && asiento == 3) {
                printf("<- libre ");
                break;                    // solo sale del for de los asientos
            }
        }
        printf("\n");
    }
    return 0;
}

Salida:

(1,1) (1,2) (1,3) (1,4) 
(2,1) (2,2) (2,3) <- libre 
(3,1) (3,2) (3,3) (3,4) 

¡La fila 3 se recorre entera! El break sale del for de los asientos, pero el de las filas sigue tan tranquilo. Para cortar los dos, la forma limpia en C es una bandera que comprueban las dos condiciones:

#include <stdio.h>

int main(void) {
    int fila, asiento;
    int encontrado = 0;                   // bandera: 1 cuando aparezca el asiento
    int fila_libre = 0, asiento_libre = 0;

    for (fila = 1; fila <= 3 && !encontrado; fila++) {
        for (asiento = 1; asiento <= 4 && !encontrado; asiento++) {
            printf("(%d,%d) ", fila, asiento);
            if (fila == 2 && asiento == 3) {
                encontrado = 1;           // corta los dos bucles en su próxima comprobación
                fila_libre = fila;        // se guarda: fila y asiento aún van a cambiar
                asiento_libre = asiento;
            }
        }
        printf("\n");
    }
    printf("Libre: fila %d, asiento %d\n", fila_libre, asiento_libre);
    printf("Al salir: fila = %d, asiento = %d\n", fila, asiento);
    return 0;
}

Salida:

(1,1) (1,2) (1,3) (1,4) 
(2,1) (2,2) (2,3) 
Libre: fila 2, asiento 3
Al salir: fila = 3, asiento = 4

Las dos rejillas, con lo que de verdad se visita en cada versión:

break en bucles anidadosDos rejillas de 3 filas por 4 asientos. Con solo break: se visitan la fila 1 entera, la fila 2 hasta el asiento 3 (el libre, en coral) y la fila 3 entera. Con bandera: la fila 1 entera y la fila 2 hasta el asiento 3; la fila 3 ya no se mira.Solo breakCon banderafila 1(1,1)(1,2)(1,3)(1,4)fila 2(2,1)(2,2)(2,3)(2,4)fila 3(3,1)(3,2)(3,3)(3,4)fila 1(1,1)(1,2)(1,3)(1,4)fila 2(2,1)(2,2)(2,3)(2,4)fila 3(3,1)(3,2)(3,3)(3,4)break solo sale del for de los asientos:la fila 3 se recorre igualencontrado = 1 corta los dos foren su próxima comprobaciónvisitadoel libreno se mira
Es la salida real de asiento.c y asiento_bandera.c: el break solo rompe el bucle en el que está escrito.

Fíjate en la última línea de la salida: al salir, fila vale 3 y asiento vale 4. Cuando encontrado pasa a 1, cada for todavía ejecuta su actualización antes de comprobar la condición (asiento++ y luego fila++). Por eso guardamos fila_libre y asiento_libre en el momento de encontrarlo: es lo mismo de antes, la variable al salir vale «uno más».

Dos variables en un mismo for: la coma

En las prácticas de IC2 aparece algo así: for (pv = v, i = 0; pv <= &v[n-1]; pv++, i++). La coma permite iniciar y avanzar dos variables a la vez:

#include <stdio.h>

int main(void) {
    int i, j;

    // Dos variables en un mismo for: se inician y avanzan separadas por comas
    for (i = 0, j = 10; i < j; i++, j -= 2) {
        printf("i = %d, j = %2d\n", i, j);
    }
    printf("Al salir: i = %d, j = %d\n", i, j);
    return 0;
}

Salida:

i = 0, j = 10
i = 1, j =  8
i = 2, j =  6
i = 3, j =  4
Al salir: i = 4, j = 2

i sube de 1 en 1 y j baja de 2 en 2 hasta que se cruzan: con i = 4 y j = 2, 4 < 2 es falso y se sale. Ojo: la coma solo vale en la inicialización y en la actualización. En la condición no sirve para «y»: para eso está &&.

El bucle infinito — y cómo salir

Un bucle cuya condición nunca se hace falsa es infinito. A veces es un error y a veces es justo lo que quieres, siempre que haya un break dentro:

#include <stdio.h>

int main(void) {
    int a_bordo = 0;

    while (1) {                           // 1 es verdad siempre: bucle infinito a propósito
        a_bordo += 20;
        printf("Suben 20: %d a bordo\n", a_bordo);
        if (a_bordo >= 60) {
            printf("Guagua llena\n");
            break;                        // la única salida
        }
    }
    return 0;
}

Salida:

Suben 20: 20 a bordo
Suben 20: 40 a bordo
Suben 20: 60 a bordo
Guagua llena

while (1) y for (;;) son las dos formas típicas de escribirlo (en el for, si la condición está vacía, se considera verdadera). Sirven cuando la salida se decide dentro del cuerpo: un menú que acaba con la opción 0, una máquina de billetes que se cierra al final de la jornada… En la teoría de IC2 verás además la espera activa, while (turno != 1) {}: un bucle vacío a propósito que gira hasta que otro proceso cambia turno.

Si un programa se queda colgado en un bucle infinito, pulsa Ctrl+C en la terminal para pararlo. Y para estudiar qué hace sin que inunde la pantalla, ponle un freno como los de este artículo: un contador y un break cuando pase de unas pocas vueltas.

Los errores más comunes en IC2

Error 1 — Punto y coma después del for

Es el de la anécdota. El ; es una instrucción vacía, y el for se la queda como cuerpo. Si además la siguiente línea está sangrada como si perteneciera al bucle, gcc avisa con -Wall:

#include <stdio.h>

int main(void) {
    int parada;

    for (parada = 1; parada <= 5; parada++);
        printf("Parada %d\n", parada);
    return 0;
}
sangria.c: In function 'main':
sangria.c:6: warning: this 'for' clause does not guard... [-Wmisleading-indentation]
sangria.c:7: note: ...this statement, but the latter is misleadingly indented as if it were guarded by the 'for'

Salida:

Parada 6

Ese aviso, -Wmisleading-indentation, es tu amigo: dice «este for no protege a la línea de abajo aunque lo parezca». No lo ignores. Con while (cond); es todavía peor: si la condición es verdadera, el programa se queda colgado sin imprimir nada.

Error 2 — No actualizar la variable en while

En el while la actualización la escribes tú, y si se te olvida la condición no cambia nunca:

#include <stdio.h>

int main(void) {
    int parada = 1;
    int vueltas = 0;

    while (parada <= 3) {
        printf("Parada %d\n", parada);
        // falta parada++: la condición nunca cambia
        if (++vueltas == 5) {             // freno de emergencia (solo para el artículo)
            printf("[freno de emergencia: 5 vueltas]\n");
            break;
        }
    }
    return 0;
}

Salida (con freno):

Parada 1
Parada 1
Parada 1
Parada 1
Parada 1
[freno de emergencia: 5 vueltas]

Antes de dar por bueno un while, busca en el cuerpo la línea que acerca la condición a ser falsa. Si no está, es infinito.

Error 3 — Condición incorrecta en do…while

Para validar «la nota tiene que estar entre 0 y 10», la condición de repetir es «es menor que 0 o mayor que 10». Con && pides que sea las dos cosas a la vez, y eso no pasa nunca:

#include <stdio.h>

int main(void) {
    float nota;

    do {
        printf("Introduce una nota (0-10): ");
        scanf("%f", &nota);
    } while (nota < 0 && nota > 10);      // ninguna nota cumple las dos a la vez

    printf("Nota aceptada: %.2f\n", nota);
    return 0;
}

Con -5:

Introduce una nota (0-10): -5
Nota aceptada: -5.00

Acepta un -5 sin rechistar. -5 < 0 es verdad, pero -5 > 10 es falso, y verdad && falso es falso: el bucle termina a la primera. Truco para no fallar: escribe primero la condición de dato bueno (nota >= 0 && nota <= 10) y niégala: !(nota >= 0 && nota <= 10), que es lo mismo que nota < 0 || nota > 10.

Error 4 — Una vuelta de más: < frente a <=

Lo vimos con al_salir.c: i = 0; i <= 5 da seis vueltas, no cinco. Si el bucle cuenta paradas, imprime una que no existe; si recorre un vector de 5 elementos, lee el sexto, que no es tuyo. Cuenta las vueltas antes de compilar y comprueba el último valor que entra.

SíntomaCausa probable
El cuerpo se ejecuta una sola vez y con un valor raro (6, 11…); justo después del for (...)
El programa se queda colgado sin imprimir nada; después del while (...)
La misma línea se repite sin pararFalta la actualización, o un continue se la salta
El error de un scanf se repite sin esperar a que escribasscanf falló (letras) y no vaciaste la entrada
Acepta datos que debería rechazar&& donde iba ` en la condición del do…while`
Sale una vuelta de más o de menos< frente a <=, o el inicio en 0 frente a 1

Míralo paso a paso en Python Tutor

Python Tutor (pythontutor.com) ejecuta el código línea a línea y enseña el valor de cada variable en cada paso. Elige C como lenguaje y pega este programa, que junta un continue y un break:

#include <stdio.h>

int main(void) {
    int parada;
    int viajeros = 0;

    for (parada = 1; parada <= 6; parada++) {
        if (parada == 3) {
            continue;             // en la 3 no sube nadie
        }
        if (parada == 5) {
            break;                // la 5 es la última del turno
        }
        viajeros += parada;       // suben tantos como el número de parada
    }
    printf("parada = %d, viajeros = %d\n", parada, viajeros);
    return 0;
}

Salida:

parada = 5, viajeros = 7

Qué observar paso a paso:

  1. Al empezar el for, parada = 1 se ejecuta una sola vez: verás que la flecha no vuelve nunca a esa parte.
  2. En la vuelta con parada = 3, el continue salta directamente a parada++: viajeros no cambia (sigue en 3).
  3. Con parada = 5, el break sale del bucle antes de sumar: viajeros se queda en 1 + 2 + 4 = 7.
  4. Al final, parada vale 5, no 7: el bucle terminó por el break, no por la condición.

Resumen rápido

BucleCuándoVueltas mínimasSe escribe
forNúmero de vueltas conocido0for (i = 0; i < n; i++) { … }
whileHasta que pase algo, sin saber cuándo0while (cond) { … }
do…whilePedir y validar datos1do { … } while (cond);
breakSalir del bucle (solo del más interno)—if (…) break;
continueSaltar a la siguiente vuelta—if (…) continue;
while (1) / for (;;)La salida se decide dentro—Con un break dentro

Tres ideas para llevarte:

  1. La condición se comprueba una vez más que vueltas hay. Al salir, la variable vale el primer valor que la hizo falsa: parada acaba en 5 con parada <= 4.
  2. Un ; detrás de for (...) o while (...) es un cuerpo vacío. Compila, y si gcc te avisa con -Wmisleading-indentation, hazle caso.
  3. Todo scanf dentro de un bucle se comprueba (== 1) y, si falla, se vacía la entrada. Si no, una letra lo deja girando para siempre.

Ahora toca escribir programas enteros: en la práctica de bucles en C la máquina de billetes atiende a varios viajeros, calculamos series y el juego de adivinar el número por fin tiene varios intentos. Y en el siguiente tema, punteros en C, los bucles recorren la memoria de verdad.

Siguiente paso: prácticaBucles en C — práctica con la máquina de billetes de la guagua, series y el juego de adivinar con 7 intentos (y el céntimo que la máquina me cobraba de más)

¿Te ha servido este artículo? Así puedes apoyar SergioLearns →

Sigue aprendiendo

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *