Pourquoi une boucle for
if et switch choisissent une branche et l'exécutent une fois. Les vrais programmes ont besoin de faire des choses de façon répétée : afficher chaque score, additionner une liste de nombres, dessiner dix lignes d'une grille, traiter chaque caractère d'une chaîne. La boucle for est le cheval de bataille du C pour répéter du code un nombre de fois connu, avec un compteur que vous contrôlez.
Tout ce dont une boucle for a besoin tient dans un en-tête compact, de sorte que « combien de fois, et comment » est visible sur une seule ligne.
L'en-tête en trois parties
Un en-tête de for comporte trois parties séparées par des points-virgules : un initialiseur, une condition et une mise à jour.
for (initialiseur; condition; mise_a_jour) {
// corps - s'exécute tant que la condition est non nulle
}
Elles s'exécutent dans un ordre précis, et le connaître explique chaque boucle for que vous lirez un jour :
- L'initialiseur s'exécute une fois, avant tout le reste.
- La condition est testée. Si elle vaut zéro, la boucle se termine immédiatement.
- Le corps s'exécute.
- La mise à jour s'exécute.
- Retour à l'étape 2.
Déroulez la boucle ci-dessus avec cette liste : int i = 0 s'exécute une fois. 0 < 5 est vrai, donc le corps affiche i = 0, puis i++ fait passer i à 1. La condition est retestée, et ainsi de suite. Quand i atteint 5, le test échoue, le corps est sauté et termine s'affiche. Le corps a tourné exactement cinq fois, avec i prenant les valeurs 0 à 4.
Deux conséquences en découlent directement. La condition est vérifiée avant le premier passage, donc une boucle dont la condition est fausse dès le départ s'exécute zéro fois - for (int i = 10; i < 5; i++) n'entre jamais dans le corps. Et la mise à jour s'exécute après le corps, donc i conserve son ancienne valeur pendant tout le corps.
Déclarer le compteur dans l'en-tête (int i = 0) est légal depuis C99 et constitue le bon réflexe : i n'existe alors qu'à l'intérieur de la boucle, il ne peut donc entrer en collision avec rien après elle, et chaque boucle d'une fonction peut réutiliser le nom librement.
Compter vers le haut, vers le bas et par pas
La mise à jour ne se limite pas à i++. Comptez à rebours, avancez de n'importe quel pas, ou doublez à chaque passage :
Accordez la condition à la mise à jour. Compter vers le haut va avec < ou <= ; compter à rebours va avec > ou >=. Se tromper sur cet appariement, c'est écrire une boucle qui ne se termine jamais.
Remarquez que la deuxième boucle utilise <= 10 parce que 10 est une valeur que nous voulons inclure, alors que le premier exemple utilisait < 5 parce que 5 ne l'est pas. Ce choix est la source des bugs de décalage d'un, alors faites-le consciemment : i < n s'exécute n fois en partant de 0 ; i <= n s'exécute n + 1 fois.
Parcourir un tableau
L'usage le plus courant d'une boucle de comptage est le parcours d'un tableau par indice. Le compteur sert aussi de position que vous lisez.
Deux choses à retenir de cet exemple.
L'astuce sizeof. sizeof(scores) est la taille du tableau entier en octets, et sizeof(scores[0]) la taille d'un élément : leur quotient est donc le nombre d'éléments. Il s'adapte automatiquement quand vous ajoutez un élément, ce qu'un 5 écrit en dur ne fait pas. Le hic - et il est de taille - c'est que cela ne fonctionne que là où le tableau lui-même est visible. Passez le tableau à une fonction et il se dégrade en pointeur, donc sizeof y donne la taille d'un pointeur. À l'intérieur d'une fonction, prenez toujours la longueur comme paramètre distinct.
La condition est i < n, jamais i <= n. Un tableau de cinq éléments a des indices valides de 0 à 4. Lire scores[5] est un comportement indéfini : cela peut afficher n'importe quoi, planter, ou sembler fonctionner tout en corrompant silencieusement autre chose. Le C ne fait aucune vérification de bornes, c'est donc entièrement à vous de jouer.
Parcourir un tableau à l'envers, c'est la même idée en sens inverse :
Commencez à n - 1 (le dernier indice valide), arrêtez-vous à 0 inclus - d'où >=, et non >. Gardez ici i en int signé ; la section suivante explique pourquoi.
Boucles imbriquées
Placez un for à l'intérieur d'un autre pour travailler avec des grilles, des tables ou toutes les paires d'éléments. La boucle interne s'exécute entièrement à chaque pas unique de la boucle externe.
La boucle externe fixe une row ; la boucle interne balaie chaque col de cette ligne ; le printf("\n") après la boucle interne termine la ligne. Déplacez ce saut de ligne à l'intérieur de la boucle interne et toute la table devient une seule colonne - un essai utile, car il rend la structure d'imbrication concrète.
Donnez des noms distincts aux compteurs. row/col ou i/j conviennent ; réutiliser i pour les deux masque le compteur externe et produit des résultats déroutants. Et surveillez le coût : une boucle de n dans une boucle de n exécute le corps n * n fois, donc deux boucles de 1 000 itérations font un million de passages.
Voici une boucle imbriquée qui fait un vrai travail - un triangle d'étoiles, où la borne de la boucle interne dépend du compteur externe :
break et continue
Deux mots-clés modifient le flux en pleine boucle. break quitte la boucle immédiatement ; continue saute le reste du passage courant et saute à la mise à jour.
La première boucle s'arrête au moment où elle trouve 7 et ne vérifie jamais les 93 valeurs restantes. La seconde utilise continue pour sauter l'affichage des nombres pairs ; le i++ de l'en-tête s'exécute quand même, donc la boucle continue d'avancer. Il y a bien plus à dire sur les deux dans break et continue, notamment comment sortir d'une boucle imbriquée, ce qu'un simple break ne peut pas faire.
Boucles infinies et parties vides
Les trois parties de l'en-tête sont facultatives. Omettez la condition et elle est considérée comme toujours vraie :
for (;;) {
/* tourne indefiniment - sortez avec break ou return */
}
for (;;) est la boucle infinie idiomatique du C, et les points-virgules restent obligatoires. Ce n'est pas une erreur tant que quelque chose à l'intérieur peut faire break, return ou exit - les boucles d'événements et les boucles « redemander jusqu'à ce que la saisie soit valide » s'écrivent exactement ainsi.
Les autres parties peuvent aussi être omises. Si le compteur existe déjà, sautez l'initialiseur ; si le corps le fait avancer, sautez la mise à jour :
int i = 0;
for (; i < n; ) {
/* ... */
i += step;
}
C'est légal, mais cela se lit moins bien que la boucle while que c'est en réalité. Préférez un for quand le compteur, sa limite et son pas vont ensemble dans l'en-tête, et un while quand ce n'est pas le cas.
L'opérateur virgule dans un en-tête
L'initialiseur et la mise à jour peuvent chacun contenir plusieurs expressions séparées par des virgules, ce qui permet de faire tourner deux compteurs à la fois :
int i = 0, j = len - 1 déclare les deux compteurs, et i++, j-- les fait avancer tous les deux. C'est l'opérateur virgule, qui évalue ses opérandes dans l'ordre. Utilisez-le pour des compteurs réellement parallèles ; entasser du travail sans rapport dans un en-tête ne fait que le cacher.
Pièges courants
Une poignée de pièges expliquent la plupart des bugs de boucles for en C.
Décalage d'un. i <= n sur un tableau indexé à partir de zéro lit un élément au-delà de la fin. Utilisez i < n pour « n fois en partant de 0 ».
Un point-virgule après l'en-tête. Cela compile et n'est presque jamais ce que vous vouliez :
/* BUG: le corps de la boucle est l'instruction vide ; le printf s'execute une fois */
for (int i = 0; i < 5; i++);
{
printf("%d\n", i);
}
Le ; devient le corps, donc la boucle tourne cinq fois sans rien faire et les accolades en dessous s'exécutent une fois comme un simple bloc. gcc -Wall le signale.
Des compteurs non signés qui comptent à rebours. Ceci est une boucle infinie :
/* BUG: une valeur non signee n'est jamais < 0 */
for (size_t i = n - 1; i >= 0; i--) {
process(a[i]);
}
i >= 0 est toujours vrai pour un type non signé. Quand i atteint 0, i-- repasse à un nombre positif énorme et la boucle poursuit dans de la mémoire qui ne lui appartient pas. Utilisez un int signé pour les comptages descendants, ou écrivez la condition for (size_t i = n; i-- > 0; ), qui décrémente et teste en une étape et s'arrête correctement à zéro.
Modifier le compteur aux deux endroits. Changer i dans le corps en plus de l'en-tête rend le nombre d'itérations imprévisible. Choisissez un seul endroit.
Compteurs en virgule flottante. for (double x = 0.0; x != 1.0; x += 0.1) peut ne jamais se terminer, car 0.1 n'a pas de représentation binaire exacte et la somme dépasse 1.0 sans jamais l'atteindre. Bouclez avec un comptage entier et calculez la valeur à l'intérieur :
for (int i = 0; i <= 10; i++) {
double x = i / 10.0;
/* ... */
}
Changer la longueur d'un tableau pendant qu'on le parcourt. La condition i < n relit n à chaque passage, donc si le corps réduit la collection, ajustez l'indice délibérément plutôt que de laisser les deux diverger.
Questions fréquentes
Comment écrit-on une boucle for en C ?
Placez trois parties dans l'en-tête, séparées par des points-virgules - un initialiseur, une condition et une mise à jour : for (int i = 0; i < 5; i++) { printf("%d\n", i); }. Cela exécute le corps cinq fois, avec i prenant les valeurs 0 à 4, et s'arrête dès que la condition devient fausse.
Comment parcourir un tableau en C ?
Comptez un indice de 0 jusqu'à la longueur (exclue) : for (int i = 0; i < n; i++) { sum += a[i]; }. Pour un tableau déclaré dans la même fonction, vous pouvez calculer la longueur avec sizeof(a) / sizeof(a[0]) ; dès que le tableau a été passé à une fonction, cette astuce ne fonctionne plus, il faut donc passer la longueur en paramètre.
Pourquoi ma boucle for en C s'exécute-t-elle une fois de trop ?
C'est le bug de décalage d'un (off-by-one). Avec un tableau indexé à partir de zéro de taille n, les indices valides vont de 0 à n - 1 ; i <= n exécute donc une itération de trop et lit au-delà de la fin - un comportement indéfini. La valeur par défaut sûre est i < n.
Peut-on déclarer la variable de boucle à l'intérieur d'une boucle for en C ?
Oui, depuis C99 : for (int i = 0; i < n; i++). La variable n'existe alors qu'à l'intérieur de la boucle, ce qui est exactement ce que vous voulez. Compilez avec gcc -std=c99 ou plus récent (GCC et clang modernes utilisent C17 par défaut, donc cela fonctionne directement) ; l'ancien style C89 déclarait i avant la boucle.