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.
Tabla de Contenidos
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:
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.
| Vuelta | parada al empezar | ¿parada <= 4? | viajeros tras el cuerpo | parada tras parada++ |
|---|---|---|---|---|
| 1 | 1 | Sí | 3 | 2 |
| 2 | 2 | Sí | 6 | 3 |
| 3 | 3 | Sí | 9 | 4 |
| 4 | 4 | Sí | 12 | 5 |
| — | 5 | No: sale | 12 | — |
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
| Python | C | Ojo 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ón | Bucle | Por qué |
|---|---|---|
| Sabes cuántas vueltas: 10 paradas, 30 alumnos, de 1 a n | for | Inicio, final y paso en una sola línea |
| Repetir hasta que llegue un dato especial (el 0, una letra) | while | No sabes cuántas veces será |
| Pedir un dato y repetir mientras sea incorrecto | do…while | Hay que pedirlo al menos una vez |
| Repetir hasta que algo pase (acertar, llenar la guagua) | while con bandera o while (1) con break | La 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.
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", ¬a);
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", ¬a);
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
breaktermina el bucle en el acto y salta a la primera instrucción después de él.continuetermina solo la vuelta actual y salta a la siguiente. En unfor, «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:
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", ¬a);
} 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íntoma | Causa 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 parar | Falta la actualización, o un continue se la salta | ||
El error de un scanf se repite sin esperar a que escribas | scanf 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:
- Al empezar el
for,parada = 1se ejecuta una sola vez: verás que la flecha no vuelve nunca a esa parte. - En la vuelta con
parada = 3, elcontinuesalta directamente aparada++:viajerosno cambia (sigue en 3). - Con
parada = 5, elbreaksale del bucle antes de sumar:viajerosse queda en 1 + 2 + 4 = 7. - Al final,
paradavale 5, no 7: el bucle terminó por elbreak, no por la condición.
Resumen rápido
| Bucle | Cuándo | Vueltas mínimas | Se escribe |
|---|---|---|---|
for | Número de vueltas conocido | 0 | for (i = 0; i < n; i++) { … } |
while | Hasta que pase algo, sin saber cuándo | 0 | while (cond) { … } |
do…while | Pedir y validar datos | 1 | do { … } while (cond); |
break | Salir del bucle (solo del más interno) | — | if (…) break; |
continue | Saltar a la siguiente vuelta | — | if (…) continue; |
while (1) / for (;;) | La salida se decide dentro | — | Con un break dentro |
Tres ideas para llevarte:
- La condición se comprueba una vez más que vueltas hay. Al salir, la variable vale el primer valor que la hizo falsa:
paradaacaba en 5 conparada <= 4. - Un
;detrás defor (...)owhile (...)es un cuerpo vacío. Compila, y si gcc te avisa con-Wmisleading-indentation, hazle caso. - Todo
scanfdentro 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 →




Qué es un sistema operativo — teoría con tu portátil en una tarde de estudio, una llamada a write() y la CPU que nunca descansa (y los tres programas que no se ejecutaban a la vez)