Les boucles



Tout comme les conditionnelles, les boucles sont des structures de contrôle fondamentales de la programmation.

Par l'intermédiaire d'une boucle, on peut demander à un ordinateur de répéter une partie de code.

Il existe trois manières d'écrire un boucle en Java: la boucle while, la boucle for et la boucle do ... while. Pour l'instant, nous nous limiterons aux boucles while, et do ... while. La boucle for est fondamentalement la même chose qu'une boucle while, sauf qu'elle est écrite d'une manière plus concise. Nous en reparlerons dans le cours consacré au tableaux.

Pour illustrer nos explications, nous utiliserons le problème de l'affichage de nombres consécutifs (c'est à dire, des nombres entiers qui se suivent).

Avec ce que vous avez vu jusqu'ici, vous sauriez écrire un programme affichant un nombre fixe de nombres consécutifs. Par exemple, pour afficher trois entiers consécutifs à partir d'un entier donné N, vous commencez par lire la valeur de N, puis vous affichez N, N+1 et N+2. Cela vous demandera trois instructions d'affichage.

Par contre, vous seriez plus embêté si l'on vous demandait d'afficher 1000 nombres consécutifs à partir d'un entier donné. Sans les boucles, il vous faudrait écrire 1000 instructions d'affichage. Avec les boucles, nous verrons qu'il vous suffira d'en écrire une seule, en précisant à l'ordinateur comment la répéter.

D'autre part, les boucles n'ont pas comme seul intérêt d'éviter la répétition inutile d'instructions. Un problème bien plus difficile (sinon impossible) à traiter sans boucle surgit lorsque le nombre de répétitions dépend des données. Comment feriez vous par exemple pour afficher un nombre M de nombres consécutifs à partir d'un entier N donné ? Vous ne pouvez pas savoir combien il vous faudra écrire d'instructions d'affichage, puisque cela dépendra de la valeur de M !

I - Présentation de l'exemple

Voici la fenêtre du projet qui servira à illustrer nos explications (il s'agit du projet NombresConsecutifs que vous trouverez parmis les exemples de projets associés à ce cours):

Du-1er-Au-Dernier.jpg, 29kB

La zone de texte contenant la liste des nombres affichés s'appelle ZT_Nombres. Le champs de texte étiqueté Premier nombre s'appelle CT_Premier et l'autre s'appelle CT_Dernier.

Le bouton Du 1er au Dernier permet d'afficher tous les nombres consécutifs entre deux nombres donnés comme ci-dessus. Nous verrons comme faire cela avec une boucle while.

Par contre, les deux boutons jusqu'à un multiple de 7 ... arrêtent l'affichage dès qu'un multiple de 7 est rencontré. Le champ de texte étiqueté Dernier Nombre est donc dans ce cas ignoré et la seule donnée du programme est le nombre de départ. Voici par exemple le résultat affiché lorsque le nombre de départ est 31:

JusquaUnMultipleDe7.jpg, 28kB

Le premier bouton utilise une boucle while pour faire cela, alors que le second utilise une boucle do ... while. Nous verrons que la boucle do ... while est mieux adapté dans ce cas.

II - Les boucles while

Commencons par quelques généralités sur les boucles while.

Une boucle while permet de répéter du code tant qu'une certaine condition est vraie.

Elle s'écrit de la manière suivante :

while ( condition )
 Instructions à répéter
 tant que la condition est vraie

S'il y a plusieurs instructions à répéter, il faudra les mettre entre accolades. S'il n'y en a qu'une seule, elle devra être suivie d'un point-virgule. Ces instructions forment l'intérieur de la boucle.

Chaque exécution du code à l'intérieur d'une boucle s'appelle une itération

La condition est une expression logique quelconque. Dans une boucle while elle est évaluée avant chaque itération.

Passons à présent à notre exemple.

Voilà comment nous affichons les nombres consécutifs avec le bouton Du 1er au dernier :

private void BT_1er_Au_DernierActionPerformed(...) {                                                  
    int n, dernier;
    n = es.LireEntier(CT_Premier);
    dernier = es.LireEntier(CT_Dernier);
    es.Effacer(ZT_Nombres);
    while ( n <= dernier )
      {es.Afficher(n, ZT_Nombres); n++; }
    }    

La variable n est utisée pour mémoriser successivement les différentes valeurs des entiers. Commencons par l'intérieur de la boucle. On y trouve deux instructions. On affiche tout d'abord la valeur de n, puis, afin que la prochaine itération affiche l'entier suivant, on incrémente n.

Une variable de type entier que l'on incrémente (ou décrémente) à chaque itération porte le nom de compteur.

Ces deux opérations doivent être répétées tant que n est inférieur ou égal au dernier nombre souhaité. D'où la condition (n <= dernier).

Pour commencer l'affichage au premier nombre souhaité, on affecte à n la valeur de ce nombre (instruction n = es.LireEntier(CT_Premier); ) avant d'exécuter la boucle.

III - Les boucles do ... while

Dans une boucle do ... while, on demande également à l'ordinateur de répéter des instructions tant qu'une condition est vraie. La seule différence avec une boucle while est que la condition est évaluée après chaque itération. Dans une boucle do ... while il y a donc forcément au minimum une itération.

Les boucles do ... while s'écrivent de la manière suivante:

 do
   Instructions à répéter
   tant que la condition est vraie vraie
while ( condition ) ;

Notez la présence obligatoire d'un point virgule à la fin. L'écriture des instructions à répéter obéit aux mêmes règles que celles des boucles while. S'il y a plusieurs instructions, il faut les entourer d'accolades. S'il n'y en a qu'une seule, elle doit être suivie d'un point virgule.

Dans certains cas les boucles do ... while sont plus pratiques à utiliser que les boucles while. En particulier, lorsque l'on est sur qu'il y aura au moins une itération.

Reprenons notre exemple. Pour afficher une suite de nombres consécutifs se terminant par un multiple de 7, nous avons deux boutons. Le premier réalise ceci avec une boucle while. Voici le code de la procédure évènementielle associée:

private void BT_Jusqua_Multiple7_WhileActionPerformed(...) {                                                          
  int n, premier;
    
  es.Effacer(ZT_Nombres);
    
  premier = es.LireEntier(CT_Premier);
      
  if (premier % 7 == 0)
    es.Afficher(premier, ZT_Nombres);
  else {
    n = premier;
    while (n % 7 != 0){
      es.Afficher(n, ZT_Nombres);
      n++;}
     es.Afficher(n, ZT_Nombres);
  }    
}   

Nous en profitons ici pour présenter l'opérateur %, qui donne le reste de la division entière. d'un nombre entier par un autre nombre entier. Par exemple, 18 % 7 = 4 car 18 = 2 X 7 + 4. Cet opérateur permet en particulier de savoir si un nombre entier est un multiple d'un autre nombre entier, puisque dans ce cas le reste de la division du premier par le second est 0. Par exemple, n % 7 vaudra 0 si et seulement si n est un multiple de 7.

Revenons à notre problème. Nous voulons afficher une suite de nombres consécutifs se terminant par un multiple de 7. Autrement dit, nous voulons continuer à afficher le nombre n tant qu'il n'est pas un multiple de 7. Nous devons donc traiter à part le cas où le premier nombre est un multiple de 7, sans quoi il ne serait pas affiché. D'où le if (premier % 7 == 0). Si le premier nombre est un multiple de 7, on l'affiche immédiatement sans utiliser de boucle.

La boucle while est utilisée lorsque le premier nombre n'est pas un multiple de 7. Tant que n n'est pas un multiple de 7, on l'affiche puis on incrémente n. La boucle s'arrête dès que n est un multiple de 7. Ce dernier nombre ne sera donc pas affiché par la boucle ! Par conséquent, nous devons encore ajouter une instruction d'affichage à la sortie de la boucle.

Avouez que c'est un peu compliqué.

Avec une boucle do ... while, on peut faire la même chose sans distinguer le cas ou le premier nombre est un multiple de 7 et en écrivant une seule instruction d'affichage:

private void BT_Jusqua_Multiple7_DoWhileActionPerformed(...) {                                                            
 int n, premier;
    
 es.Effacer(ZT_Nombres);
    
 premier = es.LireEntier(CT_Premier);
  
 n = premier - 1;
 do {
     n++;
     es.Afficher(n, ZT_Nombres);}
 while (n % 7 != 0);
 
}         

Ici au lieu d'incrémenter n après l'avoir affiché, nous faisons l'inverse (afin que la condition soit testée sur le dernier nombre affiché). Mais dans ce cas, pour que le premier nombre soit affiché par la boucle, nous devons initialiser n à premier - 1 avant de démarrer la boucle.

IV - Erreurs fréquentes

De manière générale, les instructions répétées à l'intérieur d'une boucle vont modifier certaines variables, que j'appelerais variables de la boucle. Dans les cas les plus simple, il n'y en a qu'une seule (comme par exemple le compteur de la boucle).

Erreur d'initialisation

Afin que la boucle se déroule correctement, avant le démarrage de la boucle, les variables de la boucle doivent être initialisées de manière adéquate, puisque la suite des valeurs que prendrons ces variables dépend nécessairement de leur valeur de départ.

Une erreur fréquente est d'oublier d'initialiser les variables de la boucle ou de mal les initialiser.

Voilà par exemple une erreur d'initialisation avec le bouton Jusqu'à un multiple de 7, version do ... while:

private void BT_Jusqua_Multiple7_DoWhileActionPerformed(...) {                                                            
 int n, premier;
    
 es.Effacer(ZT_Nombres);
    
 premier = es.LireEntier(CT_Premier);
  
 n = premier; 
 do {
     n++;
     es.Afficher(n, ZT_Nombres);}
 while (n % 7 != 0);
 
}         

Nous avons intialisé n à Premier au lieu de Premier - 1. Dans ce cas le premier nombre de la suite ne sera pas affiché !

Remarquez que si vous oubliez simplement d'intialiser une variable X, cette erreur sera détectée par le compilateur. Vous obtiendrez alors un message d'erreur du type:

 variable X might not have been initialized

Ce qui signifie en francais: il est possible que la variable X n'a pas été initialisée.

Boucle infinie

La condition d'arrêt d'une boucle dépend en général de certaines variables. Si ces variables ne sont jamais modifiées dans la boucle, on obtient ce que l'on appelle une boucle infinie: la condition d'arrêt n'est jamais réalisée. Dans ce cas l'ordinateur va continuer indéfiniment à répéter les instructions de la boucle, ce qui se traduira par un temps de réponse extrèmement long. La seule solution dans ce cas est de forcer d'une manière ou d'une autre l'arrêt du programme (ne débranchez pas l'ordinateur !).

Voilà par exemple, une manière d'obtenir une boucle infinie dans notre exemple avec le bouton du 1er au dernier:

private void BT_1er_Au_DernierActionPerformed(...) {                                                  
    int n, dernier;
    n = es.LireEntier(CT_Premier);
    dernier = es.LireEntier(CT_Dernier);
    es.Effacer(ZT_Nombres);
    while ( n <= dernier )
      es.Afficher(n, ZT_Nombres);
    }    

Nous avons simplement omis l'incrémentation du compteur n. Par conséquent la condition de la boucle sera toujours vraie.

Notez qu'une boucle infinie peut également se produire bien que les variables intervenant dans la condition d'arrêt soient modifiées.

Dans notre exemple, nous pouvons par exemple réaliser ceci en décrémentant le compteur au lieu de l'incrémenter:

private void BT_1er_Au_DernierActionPerformed(...) {                                                  
    int n, dernier;
    n = es.LireEntier(CT_Premier);
    dernier = es.LireEntier(CT_Dernier);
    es.Effacer(ZT_Nombres);
    while ( n <= dernier ) {
      es.Afficher(n, ZT_Nombres); n--; }
    }