Curso C++

El Ciclo While y la Repetición Condicional en C++

Cuando repetir N veces no alcanza

Hasta ahora, tus programas ejecutaban cada instrucción una sola vez, de arriba hacia abajo. Podías elegir caminos con if y else, pero cada bloque corría una sola vez. Existe un tipo de problema que no se resuelve así: cuando no sabés de antemano cuántas veces tenés que repetir una acción.

Pensá en un programa que pide la clave de acceso a un usuario. ¿Cuántas veces vas a preguntarle la clave? ¿Una? ¿Tres? No lo sabés: va a depender de cuántas veces se equivoque el usuario. Podría acertar al primer intento, o escribir mal la clave cinco veces seguidas.

Tampoco te sirve copiar y pegar el mismo bloque de código muchas veces. No sabés cuántas copias poner.

El ciclo while existe para eso: repetir una acción mientras se cumpla una condición, sin que el programa necesite saber la cantidad de repeticiones por adelantado. Esa cantidad se define en tiempo de ejecución, según evoluciona el estado del programa. ¿La condición decide cuándo repetir? Entonces tenés que tener claro cómo se evalúa esa condición, y qué hacés dentro del cuerpo para que, en algún momento, la repetición termine.

El while

La forma base del ciclo es:

while (condición) {
    // bloque de sentencias que se repiten
}

La palabra reservada while inicia la estructura. Le siguen paréntesis que contienen la condición y un bloque de sentencias entre llaves que es el cuerpo del ciclo. Todo lo que escribas dentro de esas llaves se ejecuta una y otra vez mientras la condición sea verdadera.

Ya conocías la forma del if: condición entre paréntesis y bloque entre llaves. La diferencia es que en el while el bloque se vuelve a ejecutar. La estructura no termina después de una pasada: el control regresa al principio y evalúa la condición de nuevo.

Ambiente

Lo que se declara dentro del cuerpo solo existe dentro de él. Cada variable que declares entre las llaves nace en una iteración y muere al terminar esa vuelta. Si necesitás que un valor sobreviva de una repetición a otra, tiene que estar declarada afuera del bloque.

La condición de entrada

La condición es una expresión booleana. Se arma con los operadores que ya conocés del if: los relacionales ==, !=, <, >, <=, >= y los lógicos &&, ||, !. El resultado de esa evaluación es un valor de verdad: true para seguir el bucle, false para salir.

#include <iostream>

int main() {
    int contador = 1;

    while (contador <= 5) {
        std::cout << "Vuelta número " << contador << "\n";
        contador = contador + 1;
    }

    return 0;
}

En este ejemplo, la condición es contador <= 5. Mientras el contador sea menor o igual que cinco, la comparación dará verdadera. ¿Qué pasa la sexta vez, cuando contador vale seis? La comparación da false y el ciclo termina.

Con tipos numéricos, 0 se interpreta como falso y cualquier valor distinto de cero como verdadero. Por eso es tan común escribir condiciones del tipo while (repeticiones != 0) o comparaciones que devuelven directamente un booleano como la variable bool de una bandera.

Precaución: la condición se evalúa antes de cada ejecución del cuerpo. Entonces, si la primera evaluación ya es false, el cuerpo jamás se ejecuta. El ciclo corre “cero o más veces”.

El bloque de sentencias

El cuerpo cumple dos funciones. Por un lado, contiene las acciones de cada vuelta: leer datos, mostrarlos, decidir con if. Por el otro, tiene que modificar la información que usa la condición. Olvidarte de eso convierte al ciclo en un problema serio.

La variable que aparece en la condición se llama variable de control. En el ejemplo anterior es contador. La línea contador = contador + 1; le suma uno en cada vuelta y es exactamente el elemento que hace que la condición evolucione. Sin esa línea, contador quedaría en 1 para siempre y contador <= 5 sería siempre verdadera.

Esta garantía la definís antes de escribir el ciclo: el cuerpo del while debe alterar el estado evaluado en la condición. Esa es la seguridad de que, en algún momento, la repetición va a terminar. Si el estado no cambia, la condición no cambia y el ciclo no tiene salida.

Paso a paso

Ahora vamos a simular el ciclo anterior línea por línea. Se lo llama prueba de escritorio. Los pasos del control siempre son estos:

  • Evaluar la condición.
  • Si es false, saltar a la primera instrucción después del cuerpo.
  • Si es true, ejecutar el cuerpo completo.
  • Volver al inicio y evaluar la condición otra vez.

La tabla muestra qué pasa en cada iteración con contador:

Evaluación Valor de contador ¿contador <= 5? Qué hace el programa
Primera 1 verdadera Ejecuta el cuerpo; el contador pasa a 2
Segunda 2 verdadera Ejecuta el cuerpo; el contador pasa a 3
Tercera 3 verdadera Ejecuta el cuerpo; el contador pasa a 4
Cuarta 4 verdadera Ejecuta el cuerpo; el contador pasa a 5
Quinta 5 verdadera Ejecuta el cuerpo; el contador pasa a 6
Sexta 6 falsa Sale del ciclo y continúa en return 0;

En la primera iteración, la condición se evalúa con el estado inicial de la variable. En las iteraciones intermedias, se evalúa con el estado ya modificado por las vueltas anteriores. La repetición se corta exactamente en el momento en que la evaluación devuelve false. Como la evaluación ocurre en la parte superior del ciclo, el cuerpo puede ser ignorado por completo: si la variable arranca en un valor que no cumple la condición, las llaves nunca se ejecutan y el programa salta directo a la salida.

El bucle infinito

El while no termina si la condición nunca llega a ser false. El caso clásico es olvidar actualizar la variable de control:

#include <iostream>

int main() {
    int contador = 1;

    while (contador <= 5) {
        std::cout << "Vuelta número " << contador << "\n";
        // Falta actualizar contador. La condición nunca cambia.
    }

    return 0;
}

El cuerpo imprime “Vuelta número 1” una y otra vez. El proceso queda ejecutando el mismo salto condicional, porque el valor de contador no cambia y contador <= 5 se mantiene verdadera para siempre. A nivel de máquina, el procesador repite el mismo lazo cerrado y el programa nunca alcanza la instrucción siguiente al bloque.

El estándar de C++ es explícito en ese punto: un ciclo que no termina y no tiene comportamiento observable es comportamiento indefinido, y el compilador está autorizado a removerlo. En un programa real que sí produce salida por consola, vas a ver repetirse el mensaje sin fin y vas a tener que interrumpir el proceso.

¿Sabés cuándo se esconde un bucle infinito en tu código? Cuando la condición está bien escrita, pero la actualización de la variable de control falta, está mal ubicada, o está dentro de un camino del if que no siempre se ejecuta. La mejor defensa es revisar que toda variable incluida en la condición tenga al menos una asignación dentro del cuerpo.

El Centinela

Un uso típico del while es exigir que el usuario ingrese un dato válido. El patrón funciona así: se lee una entrada y, mientras esa entrada no sea válida, se informa el error y se vuelve a pedir y leer otra vez. La condición expresa literalmente “mientras la entrada no sea válida, sigo pidiendo”.

#include <iostream>

int main() {
    int nota = 0;

    std::cout << "Ingresá una nota entre 1 y 10: ";
    std::cin >> nota;

    while (nota < 1 || nota > 10) {
        std::cout << "Valor inválido. Ingresá una nota entre 1 y 10: ";
        std::cin >> nota;
    }

    std::cout << "Nota registrada: " << nota << "\n";
    return 0;
}

La condición nota < 1 || nota > 10 examina con || dos comparaciones ya conocidas. Si la nota está fuera del rango, da verdadera y se vuelve a pedir. La alteración del estado está en el propio cuerpo: la línea std::cin >> nota; reemplaza el valor evaluado en la condición. Mientras el usuario no ingrese un valor entre 1 y 10, la condición seguirá dando true.

Existe una variante del mismo patrón con un valor centinela. El centinela es un valor especial que elegís vos y que el programa interpreta como “no hay más datos”. Por ejemplo, para leer números de a uno hasta que aparezca un -1:

#include <iostream>

int main() {
    int numero = 0;

    std::cout << "Ingresá números. El -1 finaliza.\n";
    std::cin >> numero;

    while (numero != -1) {
        std::cout << "Recibido: " << numero << "\n";
        std::cin >> numero;
    }

    return 0;
}

¿Qué pasa si el usuario ingresa -1 en el primer intento? La condición da false desde el inicio y el cuerpo jamás se ejecuta. El programa registra cero números y termina, que es el comportamiento esperado cuando no hay datos.

Las banderas

Otra forma de controlar la salida es usar una variable bool como bandera. Esa bandera se inicializa en un estado y cambia dentro del cuerpo cuando ocurre algo específico que se detecta con un if. La condición del ciclo evalúa la bandera. Con este patrón, la terminación no depende de una comparación directa con la entrada, sino de un evento que sucede dentro de la repetición.

#include <iostream>

int main() {
    bool salir = false;
    int numero = 0;

    while (!salir) {
        std::cout << "Ingresá un número. El 0 finaliza: ";
        std::cin >> numero;

        if (numero == 0) {
            salir = true;
        } else {
            std::cout << "Recibido: " << numero << "\n";
        }
    }

    return 0;
}

El ciclo evalúa !salir. La bandera empieza en false, así que !salir es verdadera y el cuerpo se ejecuta. Cuando el usuario ingresa 0, el if interno cambia la bandera a true. En la siguiente evaluación de la condición, !salir da false y el ciclo remata.

La condición no analiza el número ingresado directamente; solo consulta la bandera. ¿Cuál es la responsabilidad de la bandera? Actualizarla. El cambio ocurre cuando se cumple el criterio que defines dentro del bloque. Si en ningún camino del cuerpo se modifica la bandera, el ciclo no tiene forma de salir y vuelve a aparecer el bucle infinito.

Cierre

Quedamos en tres conceptos que se conectan. El while evalúa una condición antes de cada vuelta y repite un bloque mientras esa condición sea verdadera. Cada repetición debe alterar el estado evaluado en la condición, por lo que la variable de control avanza hacia el momento en que la condición da false. Si esa alteración no existe, el ciclo no termina.

Con eso ya podés resolver dos problemas de entrada de datos: el centinela, para exigir valores válidos o detectar un valor de salida, y la bandera booleana, para decidir la salida por un evento interno. En los dos casos, la clave es que dentro del cuerpo se modifica algún dato que la condición va a leer en la vuelta siguiente.