Les Boucles
« Les premiers 90% du code prennent les premiers 90% du temps de développement. Les 10% restants prennent les autres 90% du temps de développement » - Tom Cargill
Et ça y est, on y est, on est arrivés, la voilà, c’est Broadway, la quatrième et dernière structure : ça est les boucles. Si vous voulez épater vos amis, vous pouvez également parler de structures répétitives, voire carrément de structures itératives. Ca calme, hein ? Bon, vous faites ce que vous voulez, ici on est entre nous, on parlera de boucles.
Les boucles, c'est généralement le point douloureux de l'apprenti programmeur. C'est là que ça coince, car autant il est assez facile de comprendre comment fonctionnent les boucles, autant il est souvent long d'acquérir les réflexes qui permettent de les élaborer judicieusement pour traiter un problème donné.
On peut dire en fait que les boucles constituent la seule vraie structure logique caractéristique de la programmation. Si vous avez utilisé un tableur comme Excel, par exemple, vous avez sans doute pu manier des choses équivalentes aux variables (les cellules, les formules) et aux tests (la fonction SI…). Mais les boucles, ça, ça n'a aucun équivalent. Cela n'existe que dans les langages de programmation proprement dits.
Le maniement des boucles, s'il ne différencie certes pas l'homme de la bête (il ne faut tout de même pas exagérer), est tout de même ce qui sépare en informatique le programmeur de l'utilisateur, même averti.
Alors, à vos futures – et inévitables - difficultés sur le sujet, il y a trois remèdes : de la rigueur, de la patience, et encore de la rigueur !
1. A quoi cela sert-il donc ?
Prenons le cas d’une saisie au clavier (une lecture), où par exemple, le programme pose une question à laquelle l’utilisateur doit répondre par O (Oui) ou N (Non). Mais tôt ou tard, l’utilisateur, facétieux ou maladroit, risque de taper autre chose que la réponse attendue. Dès lors, le programme peut planter soit par une erreur d’exécution (parce que le type de réponse ne correspond pas au type de la variable attendu) soit par une erreur fonctionnelle (il se déroule normalement jusqu’au bout, mais en produisant des résultats fantaisistes).
Alors, dans tout programme un tant soit peu sérieux, on met en place ce qu’on appelle un contrôle de saisie, afin de vérifier que les données entrées au clavier correspondent bien à celles attendues par l’algorithme.
A vue de nez, on pourrait essayer avec un SI. Voyons voir ce que ça donne :
Variable Rep en Caractère
Début
Ecrire "Voulez vous un café ? (O/N)"
Lire Rep
Si Rep <> "O" et Rep <> "N" Alors
Ecrire "Saisie erronnée. Recommencez"
Lire Rep
FinSi
Fin
C’est impeccable. Du moins tant que l’utilisateur a le bon goût de ne se tromper qu’une seule fois, et d’entrer une valeur correcte à la deuxième demande. Si l’on veut également bétonner en cas de deuxième erreur, il faudrait rajouter un SI. Et ainsi de suite, on peut rajouter des centaines de SI, et écrire un algorithme aussi lourd qu’une blague des Grosses Têtes, on n’en sortira pas, il y aura toujours moyen qu’un acharné flanque le programme par terre.
La solution consistant à aligner des SI… en pagaille est donc une impasse. La seule issue est donc de flanquer une structure de boucle, qui se présente ainsi :
TantQue booléen
…
Instructions
…
FinTantQue
Le principe est simple : le programme arrive sur la ligne du TantQue. Il examine alors la valeur du booléen (qui, je le rappelle, peut être une variable booléenne ou, plus fréquemment, une condition). Si cette valeur est VRAI, le programme exécute les instructions qui suivent, jusqu’à ce qu’il rencontre la ligne FinTantQue. Il retourne ensuite sur la ligne du TantQue, procède au même examen, et ainsi de suite. Le manège enchanté ne s’arrête que lorsque le booléen prend la valeur FAUX.
Illustration avec notre problème de contrôle de saisie. Une première approximation de la solution consiste à écrire :
Variable Rep en Caractère
Début
Ecrire "Voulez vous un café ? (O/N)"
TantQue Rep <> "O" et Rep <> "N"
Lire Rep
FinTantQue
Fin
Là, on a le squelette de l’algorithme correct. Mais de même qu’un squelette ne suffit pas pour avoir un être vivant viable, il va nous falloir ajouter quelques muscles et organes sur cet algorithme pour qu’il fonctionne correctement.
Son principal défaut est de provoquer une erreur à chaque exécution. En effet, l’expression booléenne qui figure après le TantQue interroge la valeur de la variable Rep. Malheureusement, cette variable, si elle a été déclarée, n’a pas été affectée avant l’entrée dans la boucle. On teste donc une variable qui n’a pas de valeur, ce qui provoque une erreur et l’arrêt immédiat de l’exécution. Pour éviter ceci, on n’a pas le choix : il faut que la variable Rep ait déjà été affectée avant qu’on en arrive au premier tour de boucle. Pour cela, on peut faire une première lecture de Rep avant la boucle. Dans ce cas, celle-ci ne servira qu’en cas de mauvaise saisie lors de cette première lecture. L’algorithme devient alors :
Variable Rep en Caractère
Début
Ecrire "Voulez vous un café ? (O/N)"
Lire Rep
TantQue Rep <> "O" et Rep <> "N"
Lire Rep
FinTantQue
Fin
Une autre possibilité, fréquemment employée, consiste à ne pas lire, mais à affecter arbitrairement la variable avant la boucle. Arbitrairement ? Pas tout à fait, puisque cette affectation doit avoir pour résultat de provoquer l’entrée obligatoire dans la boucle. L’affectation doit donc faire en sorte que le booléen soit mis à VRAI pour déclencher le premier tour de la boucle. Dans notre exemple, on peut donc affecter Rep avec n’importe quelle valeur, hormis « O » et « N » : car dans ce cas, l’exécution sauterait la boucle, et Rep ne serait pas du tout lue au clavier. Cela donnera par exemple :
Variable Rep en Caractère
Début
Rep ← "X"
Ecrire "Voulez vous un café ? (O/N)"
TantQue Rep <> "O" et Rep <> "N"
Lire Rep
FinTantQue
Fin
Cette manière de procéder est à connaître, car elle est employée très fréquemment.
Il faut remarquer que les deux solutions (lecture initiale de Rep en dehors de la boucle ou affectation de Rep) rendent toutes deux l’algorithme satisfaisant, mais présentent une différence assez importante dans leur structure logique.
En effet, si l’on choisit d’effectuer une lecture préalable de Rep, la boucle ultérieure sera exécutée uniquement dans l’hypothèse d’une mauvaise saisie initiale. Si l’utilisateur saisit une valeur correcte à la première demande de Rep, l’algorithme passera sur la boucle sans entrer dedans.
En revanche, avec la deuxième solution (celle d’une affectation préalable de Rep), l’entrée de la boucle est forcée, et l’exécution de celle-ci, au moins une fois, est rendue obligatoire à chaque exécution du programme. Du point de vue de l’utilisateur, cette différence est tout à fait mineure ; et à la limite, il ne la remarquera même pas. Mais du point de vue du programmeur, il importe de bien comprendre que les cheminements des instructions ne seront pas les mêmes dans un cas et dans l’autre.
Pour terminer, remarquons que nous pourrions peaufiner nos solutions en ajoutant des affichages de libellés qui font encore un peu défaut. Ainsi, si l’on est un programmeur zélé, la première solution (celle qui inclut deux lectures de Rep, une en dehors de la boucle, l’autre à l’intérieur) pourrait devenir :
Variable Rep en Caractère
Début
Ecrire "Voulez vous un café ? (O/N)"
Lire Rep
TantQue Rep <> "O" et Rep <> "N"
Ecrire "Vous devez répondre par O ou N. Recommencez"
Lire Rep
FinTantQue
Ecrire "Saisie acceptée"
Fin
Quant à la deuxième solution, elle pourra devenir :
Variable Rep en Caractère
Début
Rep ← "X"
Ecrire "Voulez vous un café ? (O/N)"
TantQue Rep <> "O" et Rep <> "N"
Lire Rep
Si Rep <> "O" et Rep <> "N" Alors
Ecrire "Saisie Erronée, Recommencez"
FinSi
FinTantQue
Fin
variables
Les Variables
« N’attribuez jamais à la malveillance ce qui s’explique très bien par l’incompétence. » - Napoléon Bonaparte
« A l’origine de toute erreur attribuée à l’ordinateur, vous trouverez au moins deux erreurs humaines. Dont celle consistant à attribuer l’erreur à l’ordinateur. » - Anonyme
Dans un programme informatique, on va avoir en permanence besoin de stocker provisoirement des valeurs. Il peut s’agir de données issues du disque dur, fournies par l’utilisateur (frappées au clavier), ou que sais-je encore. Il peut aussi s’agir de résultats obtenus par le programme, intermédiaires ou définitifs. Ces données peuvent être de plusieurs types (on en reparlera) : elles peuvent être des nombres, du texte, etc. Toujours est-il que dès que l’on a besoin de stocker une information au cours d’un programme, on utilise une variable.
Pour employer une image, une variable est une boîte, que le programme (l’ordinateur) va repérer par une étiquette. Pour avoir accès au contenu de la boîte, il suffit de la désigner par son étiquette.
En réalité, dans la mémoire vive de l’ordinateur, il n’y a bien sûr pas une vraie boîte, et pas davantage de vraie étiquette collée dessus (j’avais bien prévenu que la boîte et l’étiquette, c’était une image). Dans l’ordinateur, physiquement, il y a un emplacement de mémoire, repéré par une adresse binaire. Si on programmait dans un langage directement compréhensible par la machine, on devrait se fader de désigner nos données par de superbes 10011001 et autres 01001001 (enchanté !). Mauvaise nouvelle : de tels langages existent ! Ils portent le doux nom d’assembleur. Bonne nouvelle : ce ne sont pas les seuls langages disponibles.
Les langages informatiques plus évolués (ce sont ceux que presque tout le monde emploie) se chargent précisément, entre autres rôles, d’épargner au programmeur la gestion fastidieuse des emplacements mémoire et de leurs adresses. Et, comme vous commencez à le comprendre, il est beaucoup plus facile d’employer les étiquettes de son choix, que de devoir manier des adresses binaires.
La première chose à faire avant de pouvoir utiliser une variable est de créer la boîte et de lui coller une étiquette. Ceci se fait tout au début de l’algorithme, avant même les instructions proprement dites. C’est ce qu’on appelle la déclaration des variables. C’est un genre de déclaration certes moins romantique qu’une déclaration d’amour, mais d’un autre côté moins désagréable qu’une déclaration d’impôts.
Le nom de la variable (l’étiquette de la boîte) obéit à des impératifs changeant selon les langages. Toutefois, une règle absolue est qu’un nom de variable peut comporter des lettres et des chiffres, mais qu’il exclut la plupart des signes de ponctuation, en particulier les espaces. Un nom de variable correct commence également impérativement par une lettre. Quant au nombre maximal de signes pour un nom de variable, il dépend du langage utilisé.
En pseudo-code algorithmique, on est bien sûr libre du nombre de signes pour un nom de variable, même si pour des raisons purement pratiques, et au grand désespoir de Stéphane Bern, on évite généralement les noms à rallonge.
Lorsqu’on déclare une variable, il ne suffit pas de créer une boîte (réserver un emplacement mémoire) ; encore doit-on préciser ce que l’on voudra mettre dedans, car de cela dépendent la taille de la boîte (de l’emplacement mémoire) et le type de codage utilisé.
2.1 Types numériques classiques
Commençons par le cas très fréquent, celui d’une variable destinée à recevoir des nombres.
Si l’on réserve un octet pour coder un nombre, je rappelle pour ceux qui dormaient en lisant le chapitre précédent qu’on ne pourra coder que 28 = 256 valeurs différentes. Cela peut signifier par exemple les nombres entiers de 1 à 256, ou de 0 à 255, ou de –127 à +128… Si l’on réserve deux octets, on a droit à 65 536 valeurs ; avec trois octets, 16 777 216, etc. Et là se pose un autre problème : ce codage doit-il représenter des nombres décimaux ? des nombres négatifs ?
Bref, le type de codage (autrement dit, le type de variable) choisi pour un nombre va déterminer :
- les valeurs maximales et minimales des nombres pouvant être stockés dans la variable
- la précision de ces nombres (dans le cas de nombres décimaux).
Tous les langages, quels qu’ils soient offrent un « bouquet » de types numériques, dont le détail est susceptible de varier légèrement d’un langage à l’autre. Grosso modo, on retrouve cependant les types suivants :
Type Numérique |
Plage |
Byte (octet) |
0 à 255 |
Entier simple |
-32 768 à 32 767 |
Entier long |
-2 147 483 648 à 2 147 483 647 |
Réel simple |
-3,40x1038 à -1,40x1045 pour les valeurs négatives
1,40x10-45 à 3,40x1038 pour les valeurs positives |
Réel double |
1,79x10308 à -4,94x10-324 pour les valeurs négatives
4,94x10-324 à 1,79x10308 pour les valeurs positives |
Pourquoi ne pas déclarer toutes les variables numériques en réel double, histoire de bétonner et d’être certain qu’il n’y aura pas de problème ? En vertu du principe de l’économie de moyens. Un bon algorithme ne se contente pas de « marcher » ; il marche en évitant de gaspiller les ressources de la machine. Sur certains programmes de grande taille, l’abus de variables surdimensionnées peut entraîner des ralentissements notables à l’exécution, voire un plantage pur et simple de l’ordinateur. Alors, autant prendre dès le début de bonnes habitudes d’hygiène.
En algorithmique, on ne se tracassera pas trop avec les sous-types de variables numériques (sachant qu'on aura toujours assez de soucis comme ça, allez). On se contentera donc de préciser qu'il s'agit d'un nombre, en gardant en tête que dans un vrai langage, il faudra être plus précis.
En pseudo-code, une déclaration de variables aura ainsi cette tête :
Variable g en Numérique
ou encore
Variables PrixHT, TauxTVA, PrixTTC en Numérique
2.2 Autres types numériques
Certains langages autorisent d’autres types numériques, notamment :
- le type monétaire (avec strictement deux chiffres après la virgule)
- le type date (jour/mois/année).
Nous n’emploierons pas ces types dans ce cours ; mais je les signale, car vous ne manquerez pas de les rencontrer en programmation proprement dite.
2.3 Type alphanumérique
Fort heureusement, les boîtes que sont les variables peuvent contenir bien d’autres informations que des nombres. Sans cela, on serait un peu embêté dès que l’on devrait stocker un nom de famille, par exemple.
On dispose donc également du type alphanumérique (également appelé type caractère, type chaîne ou en anglais, le type string – mais ne fantasmez pas trop vite, les string, c’est loin d’être aussi excitant que le nom le suggère. Une étudiante qui se reconnaîtra si elle lit ces lignes a d'ailleurs mis le doigt - si j'ose m'exprimer ainsi - sur le fait qu'il en va de même en ce qui concerne les bytes).
Dans une variable de ce type, on stocke des caractères, qu’il s’agisse de lettres, de signes de ponctuation, d’espaces, ou même de chiffres. Le nombre maximal de caractères pouvant être stockés dans une seule variable string dépend du langage utilisé.
Un groupe de caractères (y compris un groupe de un, ou de zéro caractères), qu’il soit ou non stocké dans une variable, d’ailleurs, est donc souvent appelé chaîne de caractères.
En pseudo-code, une chaîne de caractères est toujours notée entre guillemets
Pourquoi diable ? Pour éviter deux sources principales de possibles confusions :
- la confusion entre des nombres et des suites de chiffres. Par exemple, 423 peut représenter le nombre 423 (quatre cent vingt-trois), ou la suite de caractères 4, 2, et 3. Et ce n’est pas du tout la même chose ! Avec le premier, on peut faire des calculs, avec le second, point du tout. Dès lors, les guillemets permettent d’éviter toute ambiguïté : s’il n’y en a pas, 423 est quatre cent vingt trois. S’il y en a, "423" représente la suite des chiffres 4, 2, 3.
- …Mais ce n'est pas le pire. L'autre confusion, bien plus grave - et bien plus fréquente – consiste à se mélanger les pinceaux entre le nom d'une variable et son contenu. Pour parler simplement, cela consiste à confondre l'étiquette d'une boîte et ce qu'il y a à l'intérieur… On reviendra sur ce point crucial dans quelques instants.
2.4 Type booléen
Le dernier type de variables est le type booléen : on y stocke uniquement les valeurs logiques VRAI et FAUX.
On peut représenter ces notions abstraites de VRAI et de FAUX par tout ce qu'on veut : de l'anglais (TRUE et FALSE) ou des nombres (0 et 1). Peu importe. Ce qui compte, c'est de comprendre que le type booléen est très économique en termes de place mémoire occupée, puisque pour stocker une telle information binaire, un seul bit suffit.
Le type booléen est très souvent négligé par les programmeurs, à tort.
Il est vrai qu'il n'est pas à proprement parler indispensable, et qu'on pourrait écrire à peu près n’importe quel programme en l'ignorant complètement. Pourtant, si le type booléen est mis à disposition des programmeurs dans tous les langages, ce n'est pas pour rien. Le recours aux variables booléennes s'avère très souvent un puissant instrument de lisibilité des algorithmes : il peut faciliter la vie de celui qui écrit l'algorithme, comme de celui qui le relit pour le corriger.
Alors, maintenant, c'est certain, en algorithmique, il y a une question de style : c'est exactement comme dans le langage courant, il y a plusieurs manières de s'exprimer pour dire sur le fond la même chose. Nous verrons plus loin différents exemples de variations stylistiques autour d'une même solution. En attendant, vous êtes prévenus : l'auteur de ce cours est un adepte fervent (mais pas irraisonné) de l'utilisation des variables booléennes.
3.1 Syntaxe et signification
Ouf, après tout ce baratin préliminaire, on aborde enfin nos premières véritables manipulations d’algorithmique. Pas trop tôt, certes, mais pas moyen de faire autrement !
En fait, la variable (la boîte) n'est pas un outil bien sorcier à manipuler. A la différence du couteau suisse ou du superbe robot ménager vendu sur Télé Boutique Achat, on ne peut pas faire trente-six mille choses avec une variable, mais seulement une et une seule.
Cette seule chose qu’on puisse faire avec une variable, c’est l’affecter, c’est-à-dire lui attribuer une valeur. Pour poursuivre la superbe métaphore filée déjà employée, on peut remplir la boîte.
En pseudo-code, l'instruction d'affectation se note avec le signe ←
Ainsi :
Toto ← 24
Attribue la valeur 24 à la variable Toto.
Ceci, soit dit en passant, sous-entend impérativement que Toto soit une variable de type numérique. Si Toto a été défini dans un autre type, il faut bien comprendre que cette instruction provoquera une erreur. C’est un peu comme si, en donnant un ordre à quelqu’un, on accolait un verbe et un complément incompatibles, du genre « Epluchez la casserole ». Même dotée de la meilleure volonté du monde, la ménagère lisant cette phrase ne pourrait qu’interrompre dubitativement sa tâche. Alors, un ordinateur, vous pensez bien…
On peut en revanche sans aucun problème attribuer à une variable la valeur d’une autre variable, telle quelle ou modifiée. Par exemple :
Tutu ← Toto
Signifie que la valeur de Tutu est maintenant celle de Toto.
Notez bien que cette instruction n’a en rien modifié la valeur de Toto : une instruction d’affectation ne modifie que ce qui est situé à gauche de la flèche.
Tutu ← Toto + 4
Si Toto contenait 12, Tutu vaut maintenant 16. De même que précédemment, Toto vaut toujours 12.
Tutu ← Tutu + 1
Si Tutu valait 6, il vaut maintenant 7. La valeur de Tutu est modifiée, puisque Tutu est la variable située à gauche de la flèche.
Pour revenir à présent sur le rôle des guillemets dans les chaînes de caractères et sur la confusion numéro 2 signalée plus haut, comparons maintenant deux algorithmes suivants :
Exemple n°1
Début
Riri ← "Loulou"
Fifi ← "Riri"
Fin
Exemple n°2
Début
Riri ← "Loulou"
Fifi ← Riri
Fin
La seule différence entre les deux algorithmes consiste dans la présence ou dans l’absence des guillemets lors de la seconde affectation. Et l'on voit que cela change tout !
Dans l'exemple n°1, ce que l'on affecte à la variable Fifi, c'est la suite de caractères R – i – r - i. Et à la fin de l’algorithme, le contenu de la variable Fifi est donc « Riri ».
Dans l'exemple n°2, en revanche, Riri étant dépourvu de guillemets, n'est pas considéré comme une suite de caractères, mais comme un nom de variable. Le sens de la ligne devient donc : « affecte à la variable Fifi le contenu de la variable Riri ». A la fin de l’algorithme n°2, la valeur de la variable Fifi est donc « Loulou ». Ici, l’oubli des guillemets conduit certes à un résultat, mais à un résultat différent.
A noter, car c’est un cas très fréquent, que généralement, lorsqu’on oublie les guillemets lors d’une affectation de chaîne, ce qui se trouve à droite du signe d’affectation ne correspond à aucune variable précédemment déclarée et affectée. Dans ce cas, l’oubli des guillemets se solde immédiatement par une erreur d’exécution.
Ceci est une simple illustration. Mais elle résume l’ensemble des problèmes qui surviennent lorsqu’on oublie la règle des guillemets aux chaînes de caractères.
3.2 Ordre des instructions
Il va de soi que l’ordre dans lequel les instructions sont écrites va jouer un rôle essentiel dans le résultat final. Considérons les deux algorithmes suivants :
Exemple 1
Variable A en Numérique
Début
A ← 34
A ← 12
Fin
Exemple 2
Variable A en Numérique
Début
A ← 12
A ← 34
Fin
Il est clair que dans le premier cas la valeur finale de A est 12, dans l’autre elle est 34 .
Il est tout aussi clair que ceci ne doit pas nous étonner. Lorsqu’on indique le chemin à quelqu’un, dire « prenez tout droit sur 1km, puis à droite » n’envoie pas les gens au même endroit que si l’on dit « prenez à droite puis tout droit pendant 1 km ».
Enfin, il est également clair que si l’on met de côté leur vertu pédagogique, les deux algorithmes ci-dessus sont parfaitement idiots ; à tout le moins ils contiennent une incohérence. Il n’y a aucun intérêt à affecter une variable pour l’affecter différemment juste après. En l’occurrence, on aurait tout aussi bien atteint le même résultat en écrivant simplement :
Exemple 1
Variable A en Numérique
Début
A ← 12
Fin
Exemple 2
Variable A en Numérique
Début
A ← 34
Fin