Comment mettre deux instructions d'incrémentation dans une boucle C ++ 'for'?

93

Je voudrais incrémenter deux variables dans une forcondition -loop au lieu d'une.

Donc quelque chose comme:

for (int i = 0; i != 5; ++i and ++j) 
    do_something(i, j);

Quelle est la syntaxe pour cela?

Peter Smit
la source

Réponses:

154

Un idiome courant consiste à utiliser l' opérateur virgule qui évalue les deux opérandes et renvoie le deuxième opérande. Donc:

for(int i = 0; i != 5; ++i,++j) 
    do_something(i,j);

Mais est-ce vraiment un opérateur virgule?

Après avoir écrit cela, un commentateur a suggéré qu'il s'agissait en fait d'un sucre syntaxique spécial dans l'instruction for, et non d'un opérateur virgule du tout. J'ai vérifié cela dans GCC comme suit:

int i=0;
int a=5;
int x=0;

for(i; i<5; x=i++,a++){
    printf("i=%d a=%d x=%d\n",i,a,x);
}

Je m'attendais à ce que x prenne la valeur d'origine de a, donc il aurait dû afficher 5,6,7 .. pour x. Ce que j'ai eu c'est ça

i=0 a=5 x=0
i=1 a=6 x=0
i=2 a=7 x=1
i=3 a=8 x=2
i=4 a=9 x=3

Cependant, si je place l'expression entre crochets pour forcer l'analyseur à vraiment voir un opérateur virgule, j'obtiens ceci

int main(){
    int i=0;
    int a=5;
    int x=0;

    for(i=0; i<5; x=(i++,a++)){
        printf("i=%d a=%d x=%d\n",i,a,x);
    }
}

i=0 a=5 x=0
i=1 a=6 x=5
i=2 a=7 x=6
i=3 a=8 x=7
i=4 a=9 x=8

Au départ, je pensais que cela montrait qu'il ne se comportait pas du tout comme un opérateur virgule, mais il s'avère qu'il s'agit simplement d'un problème de priorité - l'opérateur virgule a la priorité la plus basse possible , donc l'expression x = i ++, a ++ est effectivement analysé comme (x = i ++), a ++

Merci pour tous les commentaires, ce fut une expérience d'apprentissage intéressante, et j'utilise C depuis de nombreuses années!

Paul Dixon
la source
1
J'ai lu plusieurs fois que la virgule dans la première ou la troisième partie d'une boucle for n'est pas l'opérateur virgule, mais juste un séparateur de virgule. (Cependant, je ne trouve pas de source officielle pour cela, car je suis particulièrement mauvais pour analyser le standard du langage C ++.)
Daniel Daranas
J'ai d'abord pensé que vous étiez incorrect, mais j'ai écrit un code de test et vous avez raison - il ne se comporte pas comme un opérateur virgule. Je modifierai ma réponse!
Paul Dixon
19
Il est un opérateur virgule dans ce contexte. La raison pour laquelle vous n'obtenez pas ce que vous attendez est que l'opérateur de commande a une priorité inférieure à l'opérateur d'affectation, donc sans les paranthèses, il analyse comme (x = i ++), j ++.
caf
6
C'est un opérateur virgule. L'affectation est plus liée que l'opérateur virgule, donc x = i ++, a ++ est analysé (x = i ++), a ++ et non x = (i ++, a ++). Cette caractéristique est mal utilisée par certaines bibliothèques de sorte que v = 1,2,3; fait les choses intuitives, mais uniquement parce que v = 1 renvoie un objet proxy pour lequel l'opérateur virgule surchargé fait un ajout.
AProgrammer
3
D'accord. De open-std.org/jtc1/sc22/wg21/docs/papers/2009/n2857.pdf section 6.5.3, la dernière partie est une "expression". (Bien que 1.6 # 2 définisse une "liste d'expressions" comme une "liste d'expressions séparées par des virgules", cette construction n'apparaît pas dans 6.5.3.). Cela signifie que lorsque nous écrivons "++ i, ++ j", il doit s'agir d'une expression dans un par lui-même, et donc "," doit être l'opérateur virgule (5.18). (Ce n'est pas une "liste d'initialiseurs" ou une "liste d'arguments aux fonctions", qui sont des exemples où "la virgule a une signification spéciale", comme le dit 5.18 # 2.). Je trouve cela un peu déroutant cependant.
Daniel Daranas
55

Essaye ça

for(int i = 0; i != 5; ++i, ++j)
    do_something(i,j);
yeyeyerman
la source
17
+1 Vous pouvez également déclarer j dans la première partie. for (int i = 0, j = 0; i! = 5; ++ i, ++ j) {...}
Daniel Daranas
1
+1 En remarque, cette même syntaxe fonctionne en C # (je suis arrivé ici à partir d'une recherche Google pour "C # pour les compteurs de boucle incrament 2", alors j'ai pensé que je le mentionnerais).
CodingWithSpike
@CodingWithSpike: Eh bien, en C #, la virgule est spéciale, ce n'est pas vraiment légal pour une expression d'opérateur de virgule d'y apparaître. Exemple d'utilisation légale de l'opérateur virgule en C ++, mais rejeté par C #:for( ; ; ((++i), (++j)) )
Ben Voigt
@BenVoigt n'a rien à voir avec la virgule. Ce n'est pas non plus légal en C #: for(int i = 0; i != 5; (++i)) {les parenthèses supplémentaires font croire au compilateur que ce n'est plus une opération "d'incrémentation".
CodingWithSpike
@CodingWithSpike: C'est vrai, mais les parenthèses changent également la façon dont C # voit la virgule et empêche la signification spéciale à l'intérieur de l'action for.
Ben Voigt
6

Essayez de ne pas le faire!

De http://www.research.att.com/~bs/JSF-AV-rules.pdf :

Règle AV 199
L'expression d'incrémentation dans une boucle for n'effectuera aucune action autre que de changer un paramètre de boucle unique à la valeur suivante pour la boucle.

Justification: lisibilité.

squelart
la source
4
C'est vrai, mais pour être honnête, je suis à peu près sûr que la norme des règles a été écrite pour les logiciels embarqués dans un avion de combat, pas pour le programme C (++) de variété de jardin. Cela étant dit, c'est probablement une bonne habitude de lisibilité à prendre, et qui sait, peut-être que vous concevrez un logiciel F-35, et ce sera une habitude de moins à rompre.
galois
3
for (int i = 0; i != 5; ++i, ++j) 
    do_something(i, j);
malais
la source
2

Je suis venu ici pour me rappeler comment coder un deuxième index dans la clause d'incrémentation d'une boucle FOR, ce qui, je le savais, pouvait être fait principalement en l'observant dans un échantillon que j'ai incorporé dans un autre projet, celui écrit en C ++.

Aujourd'hui, je travaille en C #, mais je suis convaincu qu'il obéirait aux mêmes règles à cet égard, puisque l'instruction FOR est l'une des plus anciennes structures de contrôle de toute la programmation. Heureusement, j'avais récemment passé plusieurs jours à documenter précisément le comportement d'une boucle FOR dans l'un de mes anciens programmes C, et j'ai rapidement réalisé que ces études contenaient des leçons qui s'appliquaient au problème C # d'aujourd'hui, en particulier au comportement de la deuxième variable d'index .

Pour les imprudents, voici un résumé de mes observations. Tout ce que j'ai vu se produire aujourd'hui, en observant attentivement les variables dans la fenêtre Locals, a confirmé mon attente qu'une instruction C # FOR se comporte exactement comme une instruction C ou C ++ FOR.

  1. La première fois qu'une boucle FOR s'exécute, la clause d'incrémentation (la troisième de ses trois) est ignorée. Dans Visual C et C ++, l'incrément est généré sous la forme de trois instructions machine au milieu du bloc qui implémente la boucle, de sorte que la passe initiale exécute le code d'initialisation une seule fois, puis saute par-dessus le bloc d'incrémentation pour exécuter le test de terminaison. Cela implémente la fonctionnalité selon laquelle une boucle FOR s'exécute zéro ou plusieurs fois, en fonction de l'état de ses variables d'index et de limite.
  2. Si le corps de la boucle s'exécute, sa dernière instruction est un saut vers la première des trois instructions d'incrémentation qui ont été ignorées par la première itération. Après leur exécution, le contrôle tombe naturellement dans le code de test limite qui implémente la clause du milieu. Le résultat de ce test détermine si le corps de la boucle FOR s'exécute ou si le contrôle passe à l'instruction suivante après le saut au bas de sa portée.
  3. Puisque le contrôle passe du bas du bloc de boucle FOR au bloc d'incrémentation, la variable d'index est incrémentée avant l'exécution du test. Non seulement ce comportement explique pourquoi vous devez coder vos clauses de limite comme vous l'avez appris, mais il affecte tout incrément secondaire que vous ajoutez, via l'opérateur virgule, car il fait partie de la troisième clause. Par conséquent, il n'est pas modifié à la première itération, mais à la dernière itération, qui n'exécute jamais le corps.

Si l'une de vos variables d'index reste dans la portée à la fin de la boucle, sa valeur sera supérieure d'une unité au seuil qui arrête la boucle, dans le cas de la vraie variable d'index. De même, si, par exemple, la deuxième variable est initialisée à zéro avant l'entrée de la boucle, sa valeur à la fin sera le nombre d'itérations, en supposant qu'il s'agit d'un incrément (++), pas d'un décrément, et que rien dans le corps de la boucle change de valeur.

David A. Gray
la source
1

Je suis d'accord avec squelart. L'incrémentation de deux variables est sujette à des bogues, surtout si vous ne testez qu'une seule d'entre elles.

Voici la manière lisible de faire ceci:

int j = 0;
for(int i = 0; i < 5; ++i) {
    do_something(i, j);
    ++j;
}

Forles boucles sont destinées aux cas où votre boucle s'exécute sur une variable croissante / décroissante. Pour toute autre variable, modifiez-la dans la boucle.

Si vous devez jêtre lié à i, pourquoi ne pas laisser la variable d'origine telle quelle et ajouter i?

for(int i = 0; i < 5; ++i) {
    do_something(i,a+i);
}

Si votre logique est plus complexe (par exemple, vous devez en fait surveiller plus d'une variable), j'utiliserais une whileboucle.

Ran Halprin
la source
2
Dans le premier exemple, j est incrémenté une fois de plus que i! Qu'en est-il d'un itérateur sur lequel une action doit être effectuée pour les x premières étapes? (Et la collection est toujours assez longue) Vous pouvez que monter l'itérateur à chaque itération, mais c'est beaucoup plus propre à mon humble avis.
Peter Smit
0
int main(){
    int i=0;
    int a=0;
    for(i;i<5;i++,a++){
        printf("%d %d\n",a,i);
    } 
}
Arkaitz Jimenez
la source
1
Quel est l'intérêt de ne pas faire iet alocal à la boucle?
sbi
2
Aucun, montrant simplement comment faire les deux incréments dans le for, c'est juste un exemple du sytnax
Arkaitz Jimenez
0

Utilisez les mathématiques. Si les deux opérations dépendent mathématiquement de l'itération de la boucle, pourquoi ne pas faire le calcul?

int i, j;//That have some meaningful values in them?
for( int counter = 0; counter < count_max; ++counter )
    do_something (counter+i, counter+j);

Ou, plus précisément en se référant à l'exemple du PO:

for(int i = 0; i != 5; ++i)
    do_something(i, j+i);

Surtout si vous passez à une fonction par valeur, vous devriez obtenir quelque chose qui fait exactement ce que vous voulez.

xaviersjs
la source