Résumé
La leçon Boucles a appris à écrire une répétition ; celle-ci apprend à bien l’écrire et à savoir pourquoi elle est juste. Elle commence par les schémas avec lesquels se construit la plupart des boucles (le compteur, l’accumulateur, le maximum et le minimum, la sentinelle et le drapeau), chacun défini par ce que sa variable signifie après chaque itération. Cette observation conduit au concept central : l’invariant, une affirmation sur les variables qui est vraie avant la première itération et après chacune, et qui, jointe à la condition de sortie, implique le résultat. C’est une récurrence sur le nombre d’itérations, et grâce à elle on conçoit une boucle à rebours : d’abord ce qui doit être vrai à la fin, puis l’invariant, enfin le corps. La terminaison se justifie par le variant, déjà présenté dans la leçon Du problème à l’algorithme, et la leçon montre trois boucles qui ne terminent pas : une avec une condition sur des réels, une avec un compteur non signé et une dont le compteur signé déborde, ce qui est un comportement indéfini. Elle se termine par un premier contact avec le débogueur de Visual Studio Code, qui arrête le programme sur une ligne et montre ses variables itération après itération.
Objectifs d’apprentissage
- Reconnaître dans une boucle les schémas du compteur, de l’accumulateur, du maximum et de la sentinelle, et s’en servir pour concevoir de nouvelles boucles.
- Énoncer l’invariant d’une boucle simple et vérifier qu’il se conserve au cours d’une itération quelconque.
- Justifier la terminaison d’une boucle par une quantité entière qui décroît et qui est bornée.
- Suivre l’exécution d’une boucle avec le débogueur de Visual Studio Code, à l’aide d’un point d’arrêt et de la vue des variables.
Schémas
Ce que signifie une variable
La plupart des boucles ne s’inventent pas : elles s’assemblent à partir de quelques pièces qui reviennent dans tous les programmes. Chaque pièce est une variable avec une initialisation avant la boucle, une mise à jour à l’intérieur de celle-ci et, surtout, une signification qui est vraie après chaque itération. Connaître les pièces par leur signification, et non par leur forme, est ce qui permet de les combiner sans erreur.
- Un compteur vaut le nombre de données traitées (ou de celles qui remplissent une condition). On l’initialise à 0, parce qu’avant la première itération aucune n’a été traitée, et on le met à jour avec
++count, seulement lorsqu’il y a quelque chose à compter. - Un accumulateur vaut la combinaison des données traitées : leur somme, leur produit. On l’initialise à l’élément neutre de l’opération (0 pour la somme, 1 pour le produit), parce que la somme de zéro donnée vaut 0 et le produit de zéro donnée vaut 1.
- Un maximum vaut la plus grande des données traitées, et un minimum, la plus petite. Le maximum de zéro donnée n’existe pas : c’est pourquoi on l’initialise avec la première donnée, et non avec un nombre choisi au hasard.
- Une sentinelle est une valeur de l’entrée qui n’est pas une donnée, mais le signal que les données sont terminées. Le schéma exige de lire une fois avant la boucle et une autre fois à la fin du corps, comme l’a fait la leçon Boucles pour sommer jusqu’au zéro, de sorte que la condition examine chaque valeur avant qu’elle soit traitée, et que la sentinelle ne soit jamais comptée comme donnée.
- Un drapeau est un
boolqui indique si quelque chose s’est produit au moins une fois parmi les données traitées. On l’initialise àfalseet il passe àtruela première fois que cela se produit ; aucune itération ultérieure ne le ramène àfalse.
Un exemple avec toutes les pièces
Un relevé pluviométrique note les millimètres tombés chaque jour. Le programme lit d’abord un seuil, puis les mesures de plusieurs jours, terminées par un nombre strictement négatif, qui n’est pas une mesure possible et sert de sentinelle ; il écrit combien il y a de mesures, leur moyenne, le maximum, combien de jours ont dépassé le seuil et s’il y a eu un jour sans pluie.
#include <iostream>#include <print>int main(){ double threshold{}; std::cin >> threshold; int count{0}; // compteur : mesures traitées int aboveCount{0}; // compteur : mesures au-dessus du seuil double total{0.0}; // accumulateur : somme des mesures double largest{0.0}; // maximum : valable seulement si count > 0 bool dryDay{false}; // drapeau : il y a eu un jour sans pluie 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("Aucune mesure."); } else { std::println("Mesures : {} ; moyenne : {:.2f} mm", count, total / count); std::println("Maximum : {:.1f} mm ; jours au-dessus de {:.1f} mm : {}", largest, threshold, aboveCount); std::println("Jours sans pluie : {}", dryDay ? "oui" : "non"); }}104.5 0 12.5 21.5 7.5 -1Mesures : 5 ; moyenne : 9.20 mmMaximum : 21.5 mm ; jours au-dessus de 10.0 mm : 2Jours sans pluie : ouiQuatre décisions méritent un commentaire. La condition count == 0 || value > largest initialise le maximum avec la première donnée sans lecture séparée : à la première itération le compteur vaut 0 et l’affectation a toujours lieu ; aux suivantes, seulement si la donnée dépasse le maximum. La comparaison value == 0.0 est exacte, parce que la valeur a été lue et non calculée : le chiffre 0 se convertit en le zéro exact, et la mise en garde de la leçon Les nombres à virgule flottante contre == porte sur des résultats d’opérations. La condition de la boucle inclut std::cin, de sorte qu’une entrée qui s’achève sans sentinelle termine aussi la boucle. Enfin, la moyenne n’est calculée que s’il y a des mesures : avec count égal à 0, total / count serait , ce qui en virgule flottante n’est pas une erreur, mais la valeur « pas un nombre », que le programme écrirait sans avertissement.
Démonstration
Avant la boucle : le seuil est 10, les compteurs et l’accumulateur valent 0, le drapeau est faux, et la première mesure, 4.5, a déjà été lue. Le maximum vaut 0, mais cette valeur ne signifie rien tant que
countvaut 0.Première itération :
countvaut 0, de sorte que 4.5 devient le maximum ; le compteur passe à 1 et le total à 4.5. On lit 0.Deuxième itération : 0 ne dépasse pas le maximum, mais il lève le drapeau. On lit 12.5.
Troisième itération : 12.5 est le nouveau maximum et dépasse le seuil :
aboveCountpasse à 1. On lit 21.5.Quatrième itération : 21.5 est le nouveau maximum et dépasse aussi le seuil. On lit 7.5.
Cinquième itération : 7.5 ne change ni le maximum ni
aboveCount; le total atteint 46. On lit .La condition examine , qui est strictement négatif : la boucle se termine sans le traiter. La moyenne est .
On observe que, dans chaque ligne de la figure, chaque variable respecte sa signification par rapport aux mesures traitées jusque-là : total est leur somme, largest leur maximum, dryDay indique s’il y a eu un zéro parmi elles. Cette propriété, vraie après chaque itération, porte un nom.
Chargement du contenu…