Aller au contenu
Topos Uranos

Résumé

Un programme qui ne fait qu’enchaîner des instructions fait toujours la même chose, quelles que soient ses données ; pour qu’il y réagisse, il doit décider. Cette leçon étudie les décisions de C++ : if et else, qui choisissent entre deux chemins selon une condition ; les chaînes de else if, qui choisissent entre plusieurs, et dans lesquelles l’ordre des conditions décide quel cas chaque branche traite ; les décisions imbriquées et le piège du else pendant ; et switch avec enum class, qui choisissent entre des valeurs discrètes nommées, avec un compilateur qui avertit s’il manque un cas ou si l’un tombe dans le suivant. Le concept qui unifie le tout est l’analyse par cas : une décision correcte partitionne l’ensemble des entrées en cas disjoints qui le couvrent entièrement, et chaque cas se vérifie par un test en son intérieur et un autre à chacune de ses frontières.

Objectifs d’apprentissage

  1. Écrire des décisions simples, enchaînées et imbriquées qui traitent tous les cas d’une spécification.
  2. Vérifier que les cas d’une chaîne de if et else if sont disjoints et exhaustifs, et concevoir une entrée de test par cas.
  3. Utiliser switch avec enum class pour des valeurs discrètes, et expliquer ce que signale le compilateur s’il manque un cas.
  4. Détecter le else pendant et la chute entre les cas d’un switch, et les corriger.

if et else

La forme d’une décision

La leçon Du problème à l’algorithme a présenté trois formes avec lesquelles se construit tout algorithme : la séquence, la décision et la répétition. Jusqu’ici, les programmes du cours ont été des séquences, à part quelque if anticipé dans l’unité 1 ; cette leçon écrit en C++ la deuxième forme. Une décision évalue une condition, c’est-à-dire une expression de type bool comme celles de la leçon Relations et logique, et exécute un bloc si la condition est vraie et un autre si elle est fausse :

Code C++
if (condition) {    // exécuté si la condition est vraie} else {    // exécuté si la condition est fausse}

La condition se met toujours entre parenthèses. La partie else est facultative : sans elle, si la condition est fausse, rien n’est fait et le programme continue avec l’instruction qui suit la décision. Dans les deux cas, les deux chemins se rejoignent à la fin, de sorte que la décision, vue de l’extérieur, est une seule instruction avec une entrée et une sortie, comme l’exigeait la leçon 1.1.

ExempleLe reste euclidien

La leçon Arithmétique a montré que a % b a le signe du dividende, de sorte qu’avec a=−7a = -7 et b=2b = 2 il vaut −1-1, tandis que le reste euclidien, qui n’est jamais strictement négatif, vaut 1 ; et elle a annoncé que la manière sûre de le corriger est d’ajouter bb seulement quand le reste est strictement négatif. Le programme suppose b>0b > 0.

Programme en C++
#include <iostream>#include <print>​int main(){    int a{};    int b{};    std::cin >> a >> b;    int r{a % b};    if (r < 0) {        r = r + b;    }    std::println("Reste euclidien de {} par {} : {}", a, b, r);}
Entrée du programme
-7 2
Sortie
Reste euclidien de -7 par 2 : 1

La variable r n’est plus constante, car la décision peut la modifier. La figure suit les deux chemins possibles.

Démonstration

  1. L’exécution arrive à la décision avec r déjà calculé, et la première chose qu’elle fait est d’évaluer la condition r < 0 : le losange est le seul point du programme où les chemins se séparent.

  2. Si la condition est vraie, comme avec −7-7 et 2, le bloc du if est exécuté : r passe de −1-1 à 1.

  3. Si elle est fausse, comme avec 7 et 2, le bloc est sauté en entier, et r conserve sa valeur.

  4. Les deux chemins se rejoignent au même point, et à partir de là le programme continue de la même façon dans les deux cas : l’instruction qui écrit le résultat est toujours exécutée.

Avec else, la décision choisit entre deux actions qui s’excluent. La condition d’année bissextile de la leçon Relations et logique produit un bool ; une décision le convertit en un comportement du programme.

Programme en C++
#include <iostream>#include <print>​int main(){    int year{};    std::cin >> year;    const bool isLeap{(year % 4 == 0 && year % 100 != 0) || year % 400 == 0};    if (isLeap) {        std::println("{} est bissextile : février a 29 jours.", year);    } else {        std::println("{} n’est pas bissextile : février a 28 jours.", year);    }}
Entrée du programme
1900
Sortie
1900 n’est pas bissextile : février a 28 jours.

Donner un nom à la condition, comme isLeap, sépare ce qui est décidé de ce qu’on fait de la décision.

Toujours des accolades

La grammaire de C++ permet d’omettre les accolades quand le bloc n’a qu’une instruction : if (r < 0) r = r + b; est valide. Or, sans accolades, la décision ne gouverne que l’instruction suivante, et ce que le lecteur croit gouverné dépend de l’indentation, que le compilateur ignore. S’il en est ainsi, toute modification ultérieure (ajouter une deuxième instruction, commenter la première) peut changer le sens sans que le texte le montre. Il s’ensuit la règle du cours : le bloc d’un if ou d’un else porte toujours des accolades, même s’il ne contient qu’une instruction.

L’erreur la plus courte de toutes confirme la règle. Un point-virgule après les parenthèses est une instruction vide, et la décision gouverne cette instruction vide ; le bloc qui suit n’appartient plus au if et s’exécute toujours.

Programme en C++
#include <print>​int main(){    const int stock{12};    if (stock == 0);    {        std::println("Produit épuisé");    }    std::println("Unités disponibles : {}", stock);}
Avertissements du compilateur
main.cpp: In function ‘int main()’:main.cpp:6:20: warning: suggest braces around empty body in an ‘if’ statement [-Wempty-body]    6 |     if (stock == 0);      |                    ^main.cpp:6:5: warning: this ‘if’ clause does not guard... [-Wmisleading-indentation]    6 |     if (stock == 0);      |     ^~main.cpp:7:5: note: ...this statement, but the latter is misleadingly indented as if it were guarded by the ‘if’    7 |     {      |     ^
Sortie
Produit épuiséUnités disponibles : 12

Le programme annonce que le produit est épuisé alors qu’il reste 12 unités. GCC donne deux avertissements : -Wempty-body suggère des accolades autour du corps vide, et -Wmisleading-indentation signale que le bloc est indenté comme si le if le gouvernait. L’autre erreur classique, if (x = 0) au lieu de if (x == 0), a été vue dans la leçon Compiler, exécuter et configurer : l’affectation vaut zéro, qui se convertit en false, et -Wparentheses avertit. Dans les deux cas, le programme compile ; sans les avertissements, l’erreur passerait inaperçue.

Les accolades ont une conséquence de plus : une variable déclarée dans le bloc d’un if n’existe que là. Si une valeur calculée dans une branche est utilisée après la décision, la variable se déclare avant, comme r dans l’exemple du reste ; la portée sera étudiée dans la leçon Paramètres et portée.

L’opérateur conditionnel

Quand les deux branches d’une décision ne font que choisir une valeur, C++ offre une forme plus brève : condition ? a : b vaut a si la condition est vraie et b si elle est fausse, et n’évalue que l’élément choisi. À la différence de if, qui est une instruction, l’opérateur conditionnel est une expression : il a une valeur et un type, et il peut aller partout où va une expression, par exemple dans l’initialisation d’une constante, ce qu’un if ne permettrait pas sans renoncer à const.

Programme en C++
#include <iostream>#include <print>​int main(){    int a{};    int b{};    std::cin >> a >> b;    const int larger{a > b ? a : b};    std::println("Plus grand : {}", larger);    std::println("{} {}", b, b == 1 ? "unité" : "unités");    const auto mixed{a > b ? a : 0.5};    std::println("Type commun : {:.1f}", mixed);    std::println("Sans parenthèses : {}", 10 + a > b ? 1 : 0);    std::println("Avec parenthèses : {}", 10 + (a > b ? 1 : 0));}
Entrée du programme
7 3
Sortie
Plus grand : 73 unitésType commun : 7.0Sans parenthèses : 1Avec parenthèses : 11

Les deux premières lignes montrent l’usage qui clarifie : choisir le plus grand et accorder un mot avec le nombre, sans répéter l’instruction entière dans deux branches. La troisième montre que l’expression a un seul type, quelle que soit la branche choisie : si les deux résultats sont de types différents, ils sont convertis en un type commun par les conversions arithmétiques usuelles de la leçon Arithmétique ; ici, un int et un double donnent double, et le 7 choisi est conservé comme 7.0. Les deux dernières montrent la priorité : l’opérateur conditionnel est parmi ceux de priorité la plus basse, de sorte que sans parenthèses la condition est 10 + a > b et la valeur, 1, au lieu de 11. Quand le conditionnel fait partie d’une expression plus grande, il se met entre parenthèses.

L’opérateur obscurcit dès qu’on lui demande plus que de choisir une valeur. Imbriqué, comme dans a > b ? (a > c ? a : c) : (b > c ? b : c), il oblige à reconstruire mentalement une décision imbriquée sans les accolades qui la rendent lisible ; et employé pour ses effets, comme dans ok ? std::println("oui") : std::println("non"), c’est un if déguisé. La règle du cours est de ne l’utiliser que pour choisir entre deux valeurs brèves, sans l’imbriquer ; tout le reste s’écrit avec if.