J'ai vu des boucles for
très étranges lors de la lecture du code d'autres personnes. J'ai essayé de rechercher une explication de syntaxe complète pour la boucle for
dans C
mais c'est très difficile parce que le mot "for
" apparaît dans des phrases sans rapport faisant la recherche presque impossible à Google efficacement.
Cette question m'est venue à l'esprit après avoir lu ce fil ce qui m'a de nouveau rendu curieux.
Le for
ici:
for(p=0;p+=(a&1)*b,a!=1;a>>=1,b<<=1);
Dans la condition intermédiaire, il y a une virgule séparant les deux morceaux de code, que fait cette virgule? Je comprends la virgule sur le côté droit car elle fait à la fois a>>=1
et b<<=1
.
Mais dans une condition de sortie de boucle, que se passe-t-il? Quitte-t-il lorsque p==0
, quand a==1
ou quand les deux se produisent?
Ce serait formidable si quelqu'un pouvait m'aider à comprendre cela et peut-être me diriger vers une description complète de la syntaxe de boucle for
.
La virgule n'est pas exclusive des boucles for; c'est l'opérateur virgule.
x = (a, b);
fera d'abord a, puis b, puis définira x à la valeur de b.
La syntaxe for est:
for (init; condition; increment)
...
Ce qui équivaut quelque peu (en ignorant continue
et break
pour l'instant) à:
init;
while (condition) {
...
increment;
}
Ainsi, votre exemple de boucle for est (encore une fois en ignorant continue
et break
) équivalent à
p=0;
while (p+=(a&1)*b,a!=1) {
...
a>>=1,b<<=1;
}
Qui agit comme si c'était (en ignorant à nouveau continue
et break
):
p=0;
while (true) {
p+=(a&1)*b;
if (a == 1) break;
...
a>>=1;
b<<=1;
}
Deux détails supplémentaires de la boucle for qui n'étaient pas dans la conversion simplifiée en boucle while ci-dessus:
true
(résultant en une boucle infinie sauf si un break
, goto
, ou autre chose rompt la boucle).continue
agit comme s'il s'agissait d'un goto à une étiquette juste avant l'incrément, contrairement à un continue
dans la boucle while qui ignorerait l'incrément.Aussi, un détail important sur l'opérateur virgule: c'est un point de séquence, comme &&
et ||
(c'est pourquoi je peux le diviser en déclarations séparées et garder son sens intact).
La norme C99 introduit quelques nuances non mentionnées précédemment dans cette explication (ce qui est très bon pour C89/C90).
Tout d'abord, toutes les boucles sont des blocs à part entière. Efficacement,
for (...) { ... }
est lui-même enveloppé dans une paire d'accolades
{
for (...) { ... }
}
Le standard dit:
ISO/IEC 9899: 1999 §6.8.5 Déclarations d'itération
¶5 Une instruction d'itération est un bloc dont la portée est un sous-ensemble strict de la portée de son bloc englobant. Le corps de la boucle est également un bloc dont la portée est un sous-ensemble strict de la portée de l'instruction d'itération.
Ceci est également décrit dans la justification en termes de jeu supplémentaire de croisillons.
Deuxièmement, la partie init
en C99 peut être une (unique) déclaration, comme dans
for (int i = 0; i < sizeof(something); i++) { ... }
Maintenant, le "bloc enroulé autour de la boucle" prend tout son sens; il explique pourquoi la variable i
n'est pas accessible en dehors de la boucle. Vous pouvez déclarer plusieurs variables, mais elles doivent toutes être du même type:
for (int i = 0, j = sizeof(something); i < j; i++, j--) { ... }
Le standard dit:
ISO/IEC 9899: 1999 §6.8.5.3 La déclaration for
La déclaration
for ( clause-1 ; expression-2 ; expression-3 ) statement
se comporte comme suit: L'expression expression-2 est l'expression de contrôle qui est évaluée avant chaque exécution du corps de boucle. L'expression expression-3 est évaluée comme une expression vide après chaque exécution du corps de boucle. Si la clause-1 est une déclaration, la portée de toutes les variables qu'elle déclare est le reste de la déclaration et la boucle entière, y compris les deux autres expressions; elle est atteinte dans l'ordre d'exécution avant la première évaluation de l'expression de contrôle. Si la clause-1 est une expression, elle est évaluée en tant qu'expression vide avant la première évaluation de l'expression de contrôle.133)
La clause-1 et l'expression-3 peuvent être omises. Une expression-2 omise est remplacée par une constante non nulle.
133) Ainsi, la clause-1 spécifie l'initialisation de la boucle, déclarant éventuellement une ou plusieurs variables à utiliser dans la boucle; l'expression de contrôle, expression-2, spécifie une évaluation effectuée avant chaque itération, de telle sorte que l'exécution de la boucle continue jusqu'à ce que l'expression soit égale à 0; et expression-3 spécifie une opération (telle que l'incrémentation) qui est effectuée après chaque itération.
La virgule sépare simplement deux expressions et est valide partout dans C où une expression normale est autorisée. Celles-ci sont exécutées dans l'ordre de gauche à droite. La valeur de l'expression la plus à droite est la valeur de l'expression globale.
for
les boucles sont composées de trois parties, dont chacune peut également être vide; un (le premier) est exécuté au début et un (le troisième) à la fin de chaque itération. Ces parties initialisent et incrémentent généralement un compteur, respectivement; mais ils peuvent tout faire.
La deuxième partie est un test qui est exécuté au début de chaque exécution. Si le test donne false
, la boucle est abandonnée. C'est tout ce qu'on peut en dire.
Le style C pour la boucle se compose de trois expressions:
for (initializer; condition; counter) statement_or_statement_block;
Chacune de ces parties peut être une expression valide dans la langue dans laquelle vous écrivez la boucle. Cela signifie qu'elles peuvent être utilisées de manière plus créative. Tout ce que vous voulez faire au préalable peut aller dans l'initialiseur, tout ce que vous voulez faire entre les deux peut aller dans la condition ou le compteur, jusqu'au point où la boucle n'a plus de corps.
Pour y parvenir, l'opérateur virgule est très pratique. Il vous permet de chaîner des expressions pour former une seule nouvelle expression. La plupart du temps, il est utilisé de cette façon dans une boucle for, les autres implications de l'opérateur virgule (par exemple, les considérations d'attribution de valeur) jouent un rôle mineur.
Même si vous pouvez faire des choses intelligentes en utilisant la syntaxe de manière créative - je resterais à l'écart jusqu'à ce que je trouve une vraiment bonne raison de le faire. Jouer au golf avec des boucles for rend le code plus difficile à lire et à comprendre (et à maintenir).
Le wikipedia a aussi un Nice article sur la boucle for .
Tout est facultatif dans une boucle for
. Nous pouvons initialiser plus d'une variable, nous pouvons vérifier plus d'une condition, nous pouvons itérer plus d'une variable en utilisant l'opérateur virgule.
La boucle for
suivante vous emmènera dans une boucle infinie. Soyez prudent en vérifiant l'état.
for(;;)
Konrad a mentionné le point clé que je voudrais répéter: la valeur de l'expression la plus à droite est la valeur de l'expression globale.
Un compilateur GNU a déclaré cet avertissement lorsque j'ai mis deux tests dans la section "condition" de la boucle for
warning: left-hand operand of comma expression has no effect
Ce que je voulais vraiment pour la "condition", c'était deux tests avec un "&&" entre. Selon la déclaration de Konrad, seul le test à droite de la virgule affecterait la condition.