Saltar al contenido
Topos Uranos

Resumen

Esta clase cierra la cuarta unidad del curso, dedicada al control de flujo. Reconstruye la cadena de conceptos que va de la condición a los algoritmos numéricos, pasando por la decisión y el análisis por casos, el bucle y sus tres formas, los patrones de bucle, el invariante y la variante; separa lo que de ello vale en cualquier lenguaje de lo que es propio de C++; reúne el comportamiento indefinido de la unidad, que se concentra en los contadores, los acumuladores y las condiciones que desbordan, con un programa que lo muestra y su corrección; repite en su evaluación de salida las destrezas de la entrada; y termina con seis desafíos que combinan la unidad entera, desde el algoritmo de Euclides extendido hasta el calendario de un mes.

Objetivos de aprendizaje

  1. Diseñar decisiones cuyos casos sean disjuntos y exhaustivos, y probarlas con una entrada por caso y por borde.
  2. Diseñar bucles correctos que terminan, eligiendo su forma y sus patrones a partir de la especificación.
  3. Justificar la corrección de un bucle con un invariante y su terminación con una variante, también en los algoritmos numéricos de la unidad.
  4. Reconocer el comportamiento indefinido de la unidad (el contador, el acumulador y la condición que desbordan, la división que la condición debía impedir) y reescribir el bucle sin él.
  5. Separar, en lo aprendido en la unidad, lo que vale en cualquier lenguaje de lo que es propio de C++.

Lo logrado en la unidad

La unidad se propuso enseñar a gobernar el orden de ejecución con decisiones y bucles, y a razonar sobre su corrección: que cada caso se atienda, que cada bucle haga lo que debe y que termine. El propósito se ha cumplido apoyándose solo en lo que la introducción declaró por sabido: las condiciones de la unidad 3, los tipos de la unidad 2 y la traza y la variante de la clase 1.1. El programa que calculaba el máximo común divisor con tres pasos fijos, y que según los datos respondía mal o dividía por cero, se reemplazó por un bucle de tres líneas, y ahora puede demostrarse que ese bucle es correcto para todos los datos no negativos.

Decisiones

La clase Decisiones enseñó la sentencia if, con llaves siempre. Mostró que en una cadena de else if se ejecuta la primera rama cuya condición es verdadera, de modo que el caso real de cada rama es su condición junto con la negación de todas las anteriores; de ahí que el orden importe y que una rama pueda quedar inalcanzable sin aviso. Estableció el criterio de corrección de toda decisión: sus casos deben ser disjuntos (lo son por la forma de la cadena) y exhaustivos (lo son si termina en else), y deben coincidir con los de la especificación, lo que se prueba en el interior de cada caso y a cada lado de cada borde. Cerró con enum class y switch, cuyo caso olvidado advierte el compilador.

Bucles

La clase Bucles presentó while, do y for como tres formas de un mismo mecanismo: el while evalúa su condición antes de cada vuelta y puede no dar ninguna; el do da al menos una; el for reúne en su cabecera la inicialización, la condición y el avance, y su variable de control vive solo en el bucle. Enseñó sus equivalencias, la tabla de traza, con k+1k + 1 evaluaciones de la condición para kk vueltas, y el recuento de las vueltas por la cabecera, que previene el error por uno. Añadió break, continue y los bucles anidados, cuyo cuerpo interno se ejecuta una suma de veces: m⋅pm \cdot p con límites fijos, n(n+1)/2n(n + 1)/2 con el triángulo.

Diseñar bucles correctos

La clase Diseñar bucles correctos hizo del bucle un objeto de diseño. Reunió los patrones (contador, acumulador, máximo y mínimo, centinela y bandera), cada uno definido por lo que su variable significa después de cada vuelta. Definió el invariante, una afirmación que vale cada vez que se evalúa la condición, con sus tres obligaciones: inicialización, conservación y conclusión, que junto con la condición falsa da la poscondición; y lo reconoció como una inducción sobre el número de vueltas. Enseñó a diseñar al revés, de la poscondición al invariante y del invariante al cuerpo. Justificó la terminación con la variante, y mostró tres bucles en que la aritmética de C++ la invalida: el real que salta el 1.0, el contador sin signo que da la vuelta y el contador con signo que desborda. Dio, por último, el primer contacto con el depurador.

Algoritmos numéricos

La clase Algoritmos numéricos aplicó lo anterior a cuatro algoritmos clásicos, escritos dentro de main y demostrados. La primalidad por divisiones de prueba, con la proposición de que basta llegar a n\sqrt{n} y con la condición d <= n / d, que no desborda donde d * d <= n sí. El algoritmo de Euclides, con el invariante mcd⁡(a,b)=mcd⁡(a0,b0)\operatorname{mcd}(a, b) = \operatorname{mcd}(a_0, b_0) y la variante bb, y sus dos peligros en C++: el resultado negativo y la división por cero si la condición se evalúa después del cuerpo. El cambio de base por divisiones sucesivas y la regla de Horner, que evalúa un polinomio con una multiplicación por coeficiente. Y la bisección, garantizada por el teorema de los valores intermedios, gobernada por un contador entero cuyo número de vueltas se calcula de antemano.