Langage C/Conditions
Jusque-là, vous ne savez qu'écrire du texte, manipuler des nombres et interagir un tout petit peu avec l'utilisateur.
En gros, pour le moment, un programme est quelque chose de sacrément stupide : il ne permet que d'exécuter des instructions dans l'ordre. Pour le moment, on ne sait faire que cela : faire des calculs simples dans un certain ordre. Notre ordinateur ne sert pas à grand chose de plus qu'une vulgaire calculette qu'on peut acheter n’importe où. Mine de rien, il serait sympathique de pouvoir faire plus de choses. Mais rassurez-vous : on peut faire mieux ! Les langages de programmation actuels fournissent des moyens permettant à notre programme de faire des choses plus évoluées et de pouvoir plus ou moins s'adapter aux circonstances au lieu de réagir machinalement. Pour rendre notre ordinateur "plus intelligent", on peut par exemple souhaiter que celui-ci n'exécute une suite d'instructions que si une certaine condition est remplie. Ou faire mieux : on peut demander à notre ordinateur de répéter une suite d'instructions tant qu'une condition bien définie est respectée.
Pour ce faire, diverses structures de contrôle de ce type ont donc été inventées. Voici les plus utilisées et les plus courantes : ce sont celles qui reviennent de façon récurrente dans un grand nombre de langages de programmation actuels. On peut bien sûr en inventer d’autres, en spécialisant certaines structures de contrôle à des cas un peu plus particuliers ou en en inventant des plus évoluées.
Nom de la structure de contrôle | Ce qu'elle fait |
If...Then | Exécute une suite d'instructions si une condition est respectée |
If...Then...Else | exécute une suite d'instructions si une condition est respectée ou exécute une autre suite d'instructions si elle ne l'est pas. |
Switch | exécute une suite d'instructions différente suivant la valeur testée. |
Boucle While...Do | répète une suite d'instructions tant qu'une condition est respectée. |
Boucle Do...While | répète une suite d'instructions tant qu'une condition est respectée. La différence, c’est que la boucle Do...While exécute au moins une fois cette suite d'instructions. |
Boucle For | répète un nombre fixé de fois une suite d'instructions. |
Concevoir un programme, dans certains langages de programmation dits structurés, comme le C, c’est simplement créer une suite d'instructions, et utiliser ces fameuses structures de contrôle pour l'organiser.
Dans ce chapitre, on va voir comment utiliser les structures de contrôles les plus basiques disponibles en C, à savoir les trois premières structures de contrôle mentionnées dans le tableau du dessus. Nous allons aussi voir comment faire tester si une condition est vraie ou fausse à notre ordinateur.
Conditions et booléens
modifierCes structures de contrôle permettent donc de modifier le comportement du programme suivant la valeur de différentes conditions. Ainsi, si une condition est vraie, alors le programme se comportera d'une telle façon, si elle est fausse, le programme fera telle ou telle chose, etc.
Il va de soit qu'avant de pouvoir utiliser des structures de contrôle, on doit pouvoir exprimer, écrire des conditions. Pour cela, le langage C fournit de quoi écrire quelques conditions de base. Divers opérateurs existent en C : ceux-ci permettent d'effectuer des comparaisons entre deux nombres. Ces opérateurs peuvent s'appliquer sur deux nombres écrits en dur dans le code, ou deux variables qui stockent un nombre. Ces opérateurs vont donc effectuer des comparaisons entre deux nombres, et vérifier si la comparaison est vraie ou fausse. Par exemple, ces opérateurs permettront de vérifier si une variable est supérieure à une autre, si deux variables sont égales, etc.
Comparaisons
modifierL'écriture d'expression avec des opérateurs est similaire aux écritures mathématiques que vous voyez en cours : l'opérateur est entre les deux variables à comparer. On a donc une variable à gauche de l'opérateur, et une à droite.
Pour donner un exemple, on va prendre l'opérateur de supériorité : l'opérateur >. Avec cet opérateur, on pourra écrire des expressions du style : a > b , qui vérifiera si la variable a est strictement supérieure à la variable b.
Mais cet opérateur n’est pas le seul. Voici un tableau réunissant ses collègues :
Symbole en langage C | Signification |
== | Est-ce que les deux variables testées sont égales ? |
!= | Est-ce que les deux variables testées sont différentes ? |
< | Est-ce que la variable à gauche est strictement inférieure à celle de droite ? |
<= | Est-ce que la variable à gauche est inférieure ou égale à celle de droite ? |
> | Est-ce que la variable à gauche est strictement supérieure à celle de droite ? |
>= | Est-ce que la variable à gauche est supérieure ou égale à celle de droite ? |
Ces opérateurs ne semblent pas très folichons : avec, on ne peut faire que quelques tests de conditions basiques sur des nombres. Mais pour un ordinateur, tout est nombre, et on peut donc se débrouiller avec ces opérateurs pour exprimer toutes les conditions que l’on veut : il suffira des les combiner entre eux, avec les bonnes valeurs à comparer. Vous verrez quand on passera à la pratique, cela sera plus clair.
Les booléens
modifierComme je l'ai dit, ces opérateurs vont avoir un résultat : vrai si la condition est vérifiée, et faux si la condition est fausse. Mais notre ordinateur ne connait pas vrai ou faux : il ne connait que des suites de bits, des nombres ! Et on est alors obligé de coder, de représenter les valeurs "vrai" ou "faux" avec des nombres.
Certains langages fournissent pour cela un type bien séparé pour stocker le résultat des opérations de comparaisons. La représentation des valeurs "vrai" et "faux" est ainsi gérée par le compilateur, et on peut travailler dans notre code en utilisant à la place des valeurs True (vrai en anglais) ou False (faux). Mais dans les premières versions du langage C, ce type spécial n'existe pas ! Il a donc fallu ruser et trouver une solution pour représenter les valeurs "vrai" et "faux". Pour cela, on a utilisé la méthode la plus simple : on utilise directement des nombres pour représenter ces deux valeurs. Ainsi, la valeur Faux est représentée par un nombre entier, tout comme la valeur Vrai.
Le langage C impose que :
- Faux soit représenté par un zéro ;
- Vrai soit représenté par tout entier différent de zéro.
Et nos opérations de comparaisons suivent cette règle pour représenter leur résultat. Ainsi, une opération de comparaison va renvoyer 0 si elle est fausse et renverra 1 si elle est vraie.
Les opérateurs de comparaisons vérifient l’existence d'une certaine relation entre les valeurs qui lui sont associées (opérandes). Le résultat de cette vérification est égal à l'une de ces deux valeurs : 0, si la condition est logiquement fausse et 1 si, en revanche, elle est vraie.
Exemple
modifierVous ne me croyez pas ? Alors, vérifions quels sont les résultats renvoyés par diverses comparaisons. Par exemple, essayons avec le code suivant :
int main(void)
{
printf("10 == 20 renvoie %d\n", 10 == 20);
printf("10 != 20 renvoie %d\n", 10 != 20);
printf("10 < 20 renvoie %d\n", 10 < 20);
printf("10 > 20 renvoie %d\n", 10 > 20);
return 0;
}
Le résultat :
10 == 20 renvoie 0
10 != 20 renvoie 1
10 < 20 renvoie 1
10 > 20 renvoie 0
Le résultat confirme bien ce que je vous ai dit ci-dessus : les résultats de ces conditions sont soit 0 si la comparaison effectuée est fausse, soit 1 si elle est vraie.
Les opérateurs logiques
modifierToutes ces comparaisons sont un peu faibles seules : il y a des choses qui ne sont pas possibles en utilisant une seule de ces comparaisons.
Par exemple, on ne peut pas vérifier si un nombre est dans un intervalle en une seule comparaison. Supposons que pour une raison quelconque, je veuille vérifier que le contenu d'une variable de type int est compris entre 0 et 1000, 0 et 1000 non inclus. Je ne peux pas vérifier cela avec une seule comparaison (ou alors il faut vraiment ruser). On peut vérifier que notre entier est inférieur à 1000 OU qu’il est supérieur à zéro, mais pas les deux en même temps. Il nous faudrait donc trouver un moyen de combiner plusieurs comparaisons entre elles pour résoudre ce problème. Eh bien rassurez-vous : le langage C fournit de quoi combiner plusieurs résultats de comparaisons, plusieurs booléens. Il fournit pour cela ce qu'on appelle des opérateurs booléens, aussi appelés des opérateurs logiques.
Les opérateurs logiques de base
modifierIl existe trois opérateurs logiques. L'opérateur ET, l'opérateur OU, et l'opérateur NON. Les opérateurs ET et OU vont permettre de combiner deux booléens. L'opérateur NON ne servira par contre pas à cela, comme vous allez le voir. Voyons plus en détail ces trois opérateurs.
L'opérateur ET
modifierL'opérateur ET va manipuler deux booléens. Il va renvoyer "vrai" si les deux booléens sont vrais, et renverra faux sinon.
Premier booléen | Second booléen | Résultat |
Faux | Faux | Faux |
Faux | Vrai | Faux |
Vrai | Faux | Faux |
Vrai | Vrai | Vrai |
Il permet par exemple de vérifier que deux comparaisons sont vraies en même temps : il suffit d'appliquer un opérateur ET sur les booléens renvoyés par les deux comparaisons pour que notre opérateur ET nous dise si les deux comparaisons sont vraies en même temps.
Cet opérateur s'écrit &&. Il s'intercalera entre les deux comparaisons ou booléens à combiner.
Par exemple, reprenons l'exemple vu plus haut, avec l'intervalle. Si je veux combiner les comparaisons a > 0 et a < 1000, je devrais écrire ces deux comparaisons entièrement, et placer l'opérateur ET entre les deux. Ce qui fait que l’expression finale sera a > 0 && a < 1000.
L'opérateur OU
modifierL'opérateur OU fonctionne exactement comme l'opérateur ET : il prend deux booléens et les combines pour former un résultat. La différence c’est que l'opérateur OU ne vérifie pas que les deux booléens vrais en même temps. À la place, il va vérifier si un seul des booléens qu’il manipule est vrai. Si c’est le cas, il renverra "vrai". Dans les autres cas, il renverra "Faux".
Premier booléen | Second booléen | Résultat |
Faux | Faux | Faux |
Faux | Vrai | Vrai |
Vrai | Faux | Vrai |
Vrai | Vrai | Vrai |
Cet opérateur s'écrit ||. Il s'intercalera entre les deux booléens ou les deux comparaisons à combiner.
Pour donner un exemple, supposons que je veuille savoir si un nombre est divisible par 3 ou par 5, ou les deux. Pour cela, je vais devoir utiliser deux comparaisons : une qui vérifie si notre nombre est divisible par 3, et une autre qui vérifie s’il est divisible par 5. Ces conditions sont a % 3 == 0 pour le test de divisibilité par 3, et a % 5 ==0 pour le test de divisibilité par 5. Il reste juste à combiner les deux tests avec l'opérateur ||, ce qui donne : ( a % 3 == 0 ) || ( a % 5 == 0 ). Vous remarquerez que j’ai placé des parenthèses pour plus de lisibilité.
L'opérateur NON
modifierCet opérateur est un peu spécial : il va manipuler un seul booléen, contrairement à ses confrères ET et OU. Son rôle est d'inverser ce dernier.
Booléen | Résultat |
Faux | Vrai |
Vrai | Faux |
Cet opérateur se note ! . Son utilité ? Simplifier certaines expressions.
Par exemple, si je veux vérifier qu'un nombre n’est pas dans l'intervalle ] 0 , 1000 [, on peut utiliser l'opérateur NON intelligemment. Je sais vérifier qu'un nombre est dans cet intervalle : il me suffit d'écrire l’expression a > 0 && a < 1000, vu plus haut. Pour rappel, cette expression permet de vérifier qu'un nombre est dans cet intervalle. Pour vérifier la condition inverse, à savoir "le nombre a n’est pas dans cet intervalle", il suffit d'appliquer l'opérateur NON à cette expression. On obtient alors l’expression ! ( a > 0 && a < 1000 ).
Vous remarquerez que pour cet exemple, on peut se passer de l'opérateur NON en récrivant une expression plus légère, à savoir a <= 0 || a >= 1000. C'est ainsi, on peut simplifier les expressions écrites avec des opérateurs logiques pour diminuer le nombre d'opérateurs utilisés. Cela sert pour simplifier l'écriture des calculs, ou gagner marginalement en performances lors des calculs des expressions utilisant ces opérateurs. Pour la culture générale, ces techniques de simplification servent aussi dans divers domaines de l’informatique, et même en électronique. Pour les curieux, il existe un tutoriel sur le sujet sur le Site du Zéro, accessible via ce lien : L'algèbre de Boole.
Évaluation en court-circuit =
modifierDans les opérateurs logiques && et ||, on exécute obligatoirement la première comparaison avant la seconde. C'est toujours le cas : la norme du C impose de tester d’abord la première comparaison, puis d'effectuer la seconde. Ce n’est pas le cas dans d'autres langages, mais passons.
Ce genre de détail permet à nos opérateurs && et || d’avoir un comportement assez intéressant, qui peut être utile dans certains cas pour éviter des calculs inutiles. Pour l'illustrer, je vais reprendre l'exemple utilisé plus haut : on veut vérifier qu'un entier est compris entre 0 et 1000 (0 et 1000 ne comptent pas, ne sont pas inclus dans l'intervalle voulu). On utilise pour cela l’expression logique a > 0 && a < 1000. Et c’est là que les choses deviennent intéressantes. Supposons que a soit inférieur ou égal à zéro. Dans ce cas, on saura dès la première comparaison que notre entier n’est pas dans l'intervalle. On n'a pas besoin d'effectuer la seconde. Eh bien rassurez-vous : le langage C nous dit si jamais la première comparaison d'un && ou d'un || suffit à donner le bon résultat, la seconde comparaison n’est pas calculée.
Par exemple, pour l'opérateur &&, on sait d'avance que si la première comparaison est fausse, la seconde n’est pas à calculer. En effet, l'opérateur ET ne renvoie vrai que si les deux comparaisons sont vraies. Si une seule d'entre elles renvoie faux, on pas besoin de calculer l'autre. Et vu que la première comparaison, celle de gauche, est celle effectuée ne premier, on est certain que si celle-ci renvoie faux, la seconde n’est pas calculée.
Pour l'opérateur ||, c’est différent : la seconde comparaison n’est pas calculée quand la première comparaison est vraie. En effet, l'opérateur || renvoie vrai dès qu'une seule des deux comparaisons testées est vraie. Donc, si la première est vraie, pas besoin de calculer la seconde.
Ce genre de propriétés des opérateurs && et || peut-être utilisée efficacement pour éviter de faire certains calculs. Il suffit de choisir intelligemment quelle comparaison mettre à gauche de l'opérateur, suivant la situation. Encore mieux !
Bien sûr, on peut aussi mélanger ces opérateurs pour créer des conditions encore plus complexes. Voici un exemple d'une expression logique plutôt complexe (et inutile, je l'ai créé uniquement pour l'exemple) :
int nb_1 = 3, nb_2 = 64, nb_3 = 12, nb_4 = 8, nb_5 = -5, nb_6 = 42;
int boolean = ((nb_1 < nb_2 && nb_2 > 32) || (nb_3 < nb_4 + nb_2 || nb_5 == 0)) && (nb_6 > nb_4);
printf("La valeur logique est egale a : %d\n", boolean);
Ici, la variable boolean est égale à 1, la condition est vrai. Comme vous le voyez, j’ai inséré des retours à la ligne pour la clarté du code.
Parenthèses
modifierEn regardant le code écrit plus haut, vous avez sûrement remarqué la présence de plusieurs parenthèses : celles-ci enlèvent toute ambigüité dans les expressions créées avec des opérateurs logiques. Et oui, en mathématiques, on doit utiliser des parenthèses dans nos équations; et bien c’est pareil avec des expressions utilisant des opérateurs logiques. Par exemple, le code suivant :
printf( "%d\n", (a && b) || (c && d) );
est différent de :
printf( "%d\n", a && (b || c) && d );
Pour être sûr d’avoir le résultat souhaité, ajoutez des parenthèses.