web-dev-qa-db-fra.com

Quelle est la syntaxe de boucle "for" complète en C?

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.

59
fmsf

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:

  • Si la condition est omise, elle est toujours true (résultant en une boucle infinie sauf si un break, goto, ou autre chose rompt la boucle).
  • Un 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).


Changements dans C99

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.

131
CesarB

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.

8
Konrad Rudolph

Le style C pour la boucle se compose de trois expressions:

for (initializer; condition; counter) statement_or_statement_block;
  • L'initialiseur s'exécute une fois, lorsque la boucle démarre.
  • La condition est vérifiée avant chaque itération. La boucle s'exécute tant qu'elle prend la valeur true.
  • Le compteur s'exécute une fois après chaque itération.

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 .

5
Tomalak

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(;;) 
2
karthik

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.

0
Bob