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 C++: la boucle while, la boucle for et la boucle do ... while.

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 des exemples

Pour expliquer le fonctionnement des boucles en C++, nous nous appuyerons sur quatre exemples de programme affichant des nombres entiers consécutifs. Vous les trouverez dans le répertoire Exemple-C-StrucControle.

Nombres consécutifs entre deux nombres

Deux programmes permettent d'afficher tous les nombres consécutifs entre deux nombres donnés:

Exemple d'exécution (le comportement des deux programmes est identique du point de vue de l'utilisateur) :

Du1er-Au-Dernier.jpg, 19kB
Nombres consécutifs jusqu'à un multiple de 7

Deux programmes permettent d'afficher tous les nombres consécutifs jusqu'au premier multiple de 7 rencontré:

Exemple d'exécution :

Jusqua-Mult7.jpg, 12kB

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 à un exemple de programme.

Voilà comment nous affichons les nombres consécutifs compris entre deux nombres avec le programme NC-Du1er-Au-Dernier-W :

int n , premier, dernier;

int main(int argc, char** argv) {
	cout << "\n\tPremier nombre : "; cin >> premier;
	cout << "\tDernier nombre : "; cin >> dernier;
	
	n = premier; cout << "\n\tListe: ";
	
	while (n <= dernier) {
		cout << n << " ";
		n++;
	}
  
	return 0;
}
  

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 = premier; ) avant d'exécuter la boucle.

III - Les boucles for

Pour commencer, voilà la syntaxe de la boucle for:

for ( initialisation ; condition; post-traitement)
   instructions à répéter

Les instructions à répéter doivent figurer entre accolades, s'il y en a plusieurs. S'il n'y en a qu'une seule, elle doit être suivie d'un point-virgule.

La partie initialisation sert à donner des valeurs initiales aux variables de la boucle. On y trouve donc en général des affections (très souvent, l'initialisation du compteur de la boucle ). S'il y en a plusieurs, elles doivent être séparées par des virgules (et non pas des point-virgules comme d'habitude). Les instructions figurant dans la partie initialisation seront exécutées avant le démarrage de la boucle.

Comme dans la boucle while:

Le post-traitement est un ensemble d'instructions qui seront exécutées à la fin de chaque itération. On y trouve très souvent l'incrémentation du compteur. S'il y a plusieurs instructions, il faut les séparer par des virgules.

Voilà comment nous affichons les nombres consécutifs compris entre deux nombres avec le programme NC-Du1er-Au-Dernier-F :

int n , premier, dernier;

int main(int argc, char** argv) {
	cout << "\n\tPremier nombre : "; cin >> premier;
	cout << "\tDernier nombre : "; cin >> dernier;
	
	cout << "\n\tListe: ";
	
	for (n= premier; n <= dernier; n++) {
		cout << n << " ";
	}
  
	return 0;
} 

IV - 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.

Par exemple, pour afficher une suite de nombres consécutifs se terminant par un multiple de 7, la boucle do ... while est plus pratique que la boucle while. Voici une manière de réaliser ceci avec une boucle while (programme BC_Jusqua-Mult7-W ) :

int n , premier;

int main(int argc, char** argv) {
	
	cout << "\n\tNombre de depart : "; cin >> premier;
	
	cout << "\n\tListe : ";
	
	n = premier; 
	
	if (n % 7 == 0)
		cout << n;
	else
	{	
		while (n % 7 !=0) {
			cout << n << " ";
			n++;
		}
		cout << n;
	}
  
	return 0;
}  

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 (n % 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 (programme exemple : NC-Jusqua-Mult7-D):

int n , premier;

int main(int argc, char** argv) {
	
cout << "\n\tNombre de depart : "; cin >> premier;
	
cout << "\n\tListe : ";
	
n = premier - 1; 
	
do {
   n++;
   cout << n << " ";
}
while (n % 7 !=0);
	
return 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.

V - Les boucles de saisie des données

Jusqu'à présent pour tester un programme sur différentes données vous étiez obliger de le relancer. Avec les boucles, il est possible d'éviter ceci en organisant les programmes différement. On écrit une boucle de saisie des données dans le programme principal. A chaque itération de cette boucle, l'utilisateur peut effectuer différents choix correspondant à différents traitements de données. Un de ces choix lui permet d'arrêter le programme. Tant que l'utilisateur n'effectue pas ce choix, le programme continue de tourner.

Pour fixer les idées, nous vous présentons ici un exemple de programme contenant une boucle de saisie des données. Il s'agit du projet NombresConsecutifs que vous trouverez dans le dossier Exemple-C-StructControle.

Lorsque vous lancez le programme, il vous affiche ceci:

NbrConsec1.jpg, 15kB

Le choix 1 permet d'obtenir tous les nombres entiers compris entre deux nombres:

NbrConsec2.jpg, 32kB

Le choix 2 permet d'obtenir tous les nombres consécutifs à partir d'un certain nombre jusqu'à rencontrer un multiple de 7:

NbrConsec3.jpg, 46kB

et le choix 3 permet de s'arrêter. Mais tant que l'utilisateur n'effectue pas ce choix, le programme ne s'arrête pas et continuera indéfiniment à lui proposer les trois choix.

Voici, le code du programme:

int choix;
int n , premier, dernier;

int main(int argc, char** argv) {

do {
    cout << "\n\n\t1: Du premier au dernier"; 
    cout << "\n\t2: Jusqu'a un multiple de 7"; 
    cout << "\n\t3: Arreter"; 
		
    cout << "\n\n\tVotre choix: "; cin >> choix;
		
    switch (choix){
      case 1 : 
      ....				
      break;
      case 2 : 
      ....		
      break;				
     }	
    }
while (choix !=3);

return 0;
}

Pour ne pas surcharger l'exemple, le code figurant après case 1 et case 2 a été ommis (il s'agit en gros du code du programme NC-Du1er-Au-Dernier-F dans le case 1 et de celui du programme NC-Jusqua-Mult7-D dans le case 2).

VI - 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

Initialiser une variable signifie lui donner une valeur de départ.

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 dans le programme NC_Jusqua-Mult-7-D:

int n , premier;

int main(int argc, char** argv) {
	
cout << "\n\tNombre de depart : "; cin >> premier;
	
cout << "\n\tListe : ";
	
n = premier; 
	
do {
   n++;
   cout << n << " ";
}
while (n % 7 !=0);
	
return 0;
}        

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

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 !). Vous pouvez par exemple fermer la fenêtre d'exécution ou taper CTRL C (maintenez la touche ctrl appuyée tout en appuyant sur la touche C) .

Voilà par exemple, une manière d'obtenir une boucle infinie dans le programme NC-Du1er-Au-Dernier-W :

int n , premier, dernier;

int main(int argc, char** argv) {

  cout << "\n\tPremier nombre : "; cin >> premier;
  cout << "\tDernier nombre : "; cin >> dernier;
	
  n = premier; cout << "\n\tListe: ";
	
  while (n <= dernier) {
      cout << n << " ";
  }
  
return 0;
}
  

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:

int n , premier, dernier;

int main(int argc, char** argv) {

  cout << "\n\tPremier nombre : "; cin >> premier;
  cout << "\tDernier nombre : "; cin >> dernier;
	
  n = premier; cout << "\n\tListe: ";
	
  while (n <= dernier) {
     cout << n << " ";
     n--;
   }
  
	return 0;
}