Resumen
La clase Bucles enseñó a escribir una repetición; esta enseña a escribirla bien y a saber por qué está bien. Comienza por los patrones con que se construye la mayoría de los bucles (el contador, el acumulador, el máximo y el mínimo, el centinela y la bandera), cada uno definido por lo que su variable significa después de cada vuelta. Esa observación conduce al concepto central: el invariante, una afirmación sobre las variables que vale antes de la primera vuelta y después de cada una, y que, junto con la condición de salida, implica el resultado. Es una inducción sobre el número de vueltas, y con ella un bucle se diseña al revés: primero lo que debe valer al final, después el invariante, por último el cuerpo. La terminación se justifica con la variante, ya presentada en la clase Del problema al algoritmo, y la clase muestra tres bucles que no terminan: uno con condición real, uno con contador sin signo y uno cuyo contador con signo desborda, que es comportamiento indefinido. Cierra con el primer contacto con el depurador de Visual Studio Code, que detiene el programa en una línea y muestra sus variables vuelta a vuelta.
Objetivos de aprendizaje
- Reconocer en un bucle los patrones de contador, acumulador, máximo y centinela, y usarlos para diseñar bucles nuevos.
- Enunciar el invariante de un bucle sencillo y comprobar que se conserva en una vuelta cualquiera.
- Justificar la terminación de un bucle con una cantidad entera que decrece y está acotada.
- Seguir la ejecución de un bucle con el depurador de Visual Studio Code, con un punto de interrupción y la vista de variables.
Patrones
Lo que significa una variable
La mayoría de los bucles no se inventan: se arman con unas pocas piezas que reaparecen en todos los programas. Cada pieza es una variable con una inicialización antes del bucle, una actualización dentro de él y, sobre todo, un significado que vale después de cada vuelta. Conocer las piezas por su significado, y no por su forma, es lo que permite combinarlas sin error.
- Un contador vale cuántos datos se han procesado (o cuántos cumplen una condición). Se inicializa en 0, porque antes de la primera vuelta no se ha procesado ninguno, y se actualiza con
++count, solo cuando hay algo que contar. - Un acumulador vale la combinación de los datos procesados: su suma, su producto. Se inicializa en el elemento neutro de la operación (0 para la suma, 1 para el producto), porque la suma de ningún dato es 0 y el producto de ningún dato es 1.
- Un máximo vale el mayor de los datos procesados, y un mínimo, el menor. El máximo de ningún dato no existe: por eso se inicializa con el primer dato, y no con un número elegido al azar.
- Un centinela es un valor de la entrada que no es un dato, sino la señal de que los datos terminaron. El patrón exige leer una vez antes del bucle y otra al final del cuerpo, como hizo la clase Bucles al sumar hasta el cero, de modo que la condición examine cada valor antes de que se procese, y el centinela nunca se cuente como dato.
- Una bandera es un
boolque vale si algo ocurrió alguna vez entre los datos procesados. Se inicializa enfalsey pasa atruela primera vez que ocurre; ninguna vuelta posterior la devuelve afalse.
Un ejemplo con todas las piezas
Un registro de lluvia anota los milímetros caídos cada día. El programa lee primero un umbral, y después las medidas de varios días, terminadas por un número negativo, que no es una medida posible y sirve de centinela; escribe cuántas medidas hay, su promedio, el máximo, cuántos días superaron el umbral y si hubo algún día sin lluvia.
#include <iostream>#include <print>int main(){ double threshold{}; std::cin >> threshold; int count{0}; // contador: medidas procesadas int aboveCount{0}; // contador: medidas sobre el umbral double total{0.0}; // acumulador: suma de las medidas double largest{0.0}; // máximo: vale solo si count > 0 bool dryDay{false}; // bandera: hubo un día sin lluvia double value{}; std::cin >> value; while (std::cin && value >= 0.0) { if (count == 0 || value > largest) { largest = value; } ++count; total += value; if (value > threshold) { ++aboveCount; } if (value == 0.0) { dryDay = true; } std::cin >> value; } if (count == 0) { std::println("No hay medidas."); } else { std::println("Medidas: {}; promedio: {:.2f} mm", count, total / count); std::println("Máximo: {:.1f} mm; días sobre {:.1f} mm: {}", largest, threshold, aboveCount); std::println("Hubo días sin lluvia: {}", dryDay); }}104.5 0 12.5 21.5 7.5 -1Medidas: 5; promedio: 9.20 mmMáximo: 21.5 mm; días sobre 10.0 mm: 2Hubo días sin lluvia: trueCuatro decisiones merecen comentario. La condición count == 0 | value > largest| inicializa el máximo con el primer dato sin una lectura aparte: en la primera vuelta el contador vale 0 y la asignación ocurre siempre; en las siguientes, solo si el dato supera al máximo. La comparación value == 0.0 es exacta, porque el valor se leyó y no se calculó: la cifra 0 se convierte en el cero exacto, y la advertencia de la clase Los números de coma flotante contra == se refiere a resultados de operaciones. La condición del bucle incluye std::cin, de modo que una entrada que se acaba sin centinela también termina el bucle. Y el promedio se calcula solo si hay medidas: con count igual a 0, total / count sería , que en coma flotante no es un error, sino el valor «no es un número», que el programa escribiría sin aviso.
Demostración
Antes del bucle: el umbral es 10, los contadores y el acumulador valen 0, la bandera es falsa, y ya se leyó la primera medida, 4.5. El máximo vale 0, pero ese valor no significa nada mientras
countsea 0.Primera vuelta:
countes 0, de modo que 4.5 pasa a ser el máximo; el contador sube a 1 y el total a 4.5. Se lee 0.Segunda vuelta: 0 no supera el máximo, pero levanta la bandera. Se lee 12.5.
Tercera vuelta: 12.5 es el nuevo máximo y supera el umbral:
aboveCountsube a 1. Se lee 21.5.Cuarta vuelta: 21.5 es el nuevo máximo y también supera el umbral. Se lee 7.5.
Quinta vuelta: 7.5 no cambia ni el máximo ni
aboveCount; el total llega a 46. Se lee .La condición examina , que es negativo: el bucle termina sin procesarlo. El promedio es .
Obsérvese que en cada fila de la figura cada variable cumple su significado respecto de las medidas procesadas hasta allí: total es su suma, largest su máximo, dryDay dice si hubo un cero entre ellas. Esa propiedad, que vale después de cada vuelta, tiene nombre.
Cargando el contenido…