La post-incrémentation et la pré-incrémentation dans une boucle ‘for’ produisent la même sortie

Les boucles suivantes produisent des résultats identiques, même si l’une utilise l’incrémentation et l’autre l’incrémentation.

Voici le code:

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

Je reçois le même résultat pour les deux boucles «for». Est-ce que je manque quelque chose?

Après avoir évalué i++ ou ++i , la nouvelle valeur de i sera la même dans les deux cas. La différence entre pré et post-incrémentation réside dans le résultat de l’évaluation de l’expression elle-même.

++i incrémente i et évalue à la nouvelle valeur de i .

i++ évalue l’ancienne valeur de i et incrémente i .

La raison pour laquelle cela n’a pas d’importance dans une boucle for est que le stream de contrôle fonctionne à peu près comme ceci:

  1. tester la condition
  2. si c’est faux, terminez
  3. si c’est vrai, exécuter le corps
  4. exécuter l’étape d’incrémentation

Parce que (1) et (4) sont découplés, un pré-incrément ou un post-incrémentation peut être utilisé.

Eh bien, c’est simple. Les boucles ci-dessus sont sémantiquement équivalentes à

 int i = 0; while(i < 5) { printf("%d", i); i++; } 

et

 int i = 0; while(i < 5) { printf("%d", i); ++i; } 

Notez que les lignes i++; et ++i; avoir la même sémantique DE LA PERSPECTIVE DE CE BLOC DE CODE. Ils ont tous deux le même effet sur la valeur de i (l'incrémente de un) et ont donc le même effet sur le comportement de ces boucles.

Notez qu'il y aurait une différence si la boucle était réécrite comme

 int i = 0; int j = i; while(j < 5) { printf("%d", i); j = ++i; } int i = 0; int j = i; while(j < 5) { printf("%d", i); j = i++; } 

C'est parce que dans le premier bloc de code j voit la valeur de i après l'incrément ( i est incrémenté en premier, ou pré-incrémenté, d'où le nom) et dans le deuxième bloc de code j voit la valeur de i avant l'incrément.

Le résultat de votre code sera le même. La raison en est que les deux opérations d’incrémentation peuvent être considérées comme deux appels de fonction distincts. Les deux fonctions provoquent une incrémentation de la variable et seules leurs valeurs de retour sont différentes. Dans ce cas, la valeur de retour est simplement supprimée, ce qui signifie qu’il n’y a pas de différence notable dans la sortie.

Cependant, sous le capot, il y a une différence: i++ après l’incrémentation doit créer une variable temporaire pour stocker la valeur d’origine de i , puis effectue l’incrémentation et retourne la variable temporaire. La pré-incrémentation ++i ne crée pas de variable temporaire. Bien sûr, tout paramètre d’optimisation décent devrait être capable de l’optimiser lorsque l’object est quelque chose de simple comme un int , mais rappelez-vous que les opérateurs ++ sont surchargés dans des classes plus compliquées telles que les iterators. Comme les deux méthodes surchargées peuvent avoir des opérations différentes (on peut vouloir afficher “Hey, je suis pré-incrémenté!” Pour stdout par exemple), le compilateur ne peut pas dire si les méthodes sont équivalentes quand la valeur de retour n’est pas utilisée (essentiellement parce qu’un tel compilateur résoudrait le problème d’arrêt insoluble), il doit utiliser la version post-incrémentation plus coûteuse si vous écrivez myiterator++ .

Trois raisons pour lesquelles vous devriez pré-incrémenter:

  1. Vous n’aurez pas à vous demander si la variable / l’object peut avoir une méthode de post-incrémentation surchargée (par exemple dans une fonction de modèle) et la traiter différemment (ou oublier de la traiter différemment).
  2. Un code cohérent est meilleur.
  3. Quand quelqu’un vous demande “Pourquoi pré-augmentez-vous?” vous aurez la chance de leur apprendre le problème en suspens et les limites théoriques de l’optimisation du compilateur . 🙂

C’est l’une de mes questions d’entrevue préférées. Je vais d’abord expliquer la réponse, puis vous dire pourquoi j’aime la question.

Solution:

La réponse est que les deux extraits affichent les nombres de 0 à 4 inclus. En effet, une boucle for() est généralement équivalente à une boucle while() :

 for (INITIALIZER; CONDITION; OPERATION) { do_stuff(); } 

Peut être écrit:

 INITIALIZER; while(CONDITION) { do_stuff(); OPERATION; } 

Vous pouvez voir que l’opération se fait toujours au bas de la boucle. Sous cette forme, il devrait être clair que i++ et ++i auront le même effet: ils vont tous deux incrémenter i et ignorer le résultat. La nouvelle valeur de i n’est testée que lorsque la prochaine itération commence en haut de la boucle.


Edit : Merci à Jason d’avoir souligné que cette équivalence for() to while() n’est pas valide si la boucle contient des instructions de contrôle (telles que continue ) qui empêcheraient l’exécution de OPERATION dans une boucle while() . OPERATION est toujours exécuté juste avant la prochaine itération d’une boucle for() .


Pourquoi c’est une bonne question d’entrevue

Tout d’abord, il suffit d’une minute ou deux si un candidat dit immédiatement la bonne réponse, pour que nous puissions passer à la question suivante.

Mais de manière surprenante (pour moi), beaucoup de candidats me disent que la boucle avec le post-incrément imprimera les nombres de 0 à 4, et la boucle de pré-incrémentation imprimera 0 à 5, ou 1 à 5. Ils expliquent généralement la différence entre pré-et post-incrémentation correctement, mais ils ne comprennent pas la mécanique de la boucle for() .

Dans ce cas, je leur demande de réécrire la boucle en utilisant while() , ce qui me donne une bonne idée de leurs processus de pensée. Et c’est pourquoi je pose la question en premier lieu: je veux savoir comment ils abordent un problème et comment ils procèdent lorsque je doute de la façon dont leur monde fonctionne.

À ce stade, la plupart des candidats réalisent leur erreur et trouvent la bonne réponse. Mais j’avais quelqu’un qui insistait sur le fait que sa réponse initiale était correcte, puis a changé la façon dont il a traduit le for() dans le while() . C’était une interview passionnante, mais nous n’avons pas fait d’offre!

J’espère que cela pourra aider!

Parce que dans les deux cas l’incrément est fait après le corps de la boucle et n’affecte donc aucun des calculs de la boucle. Si le compilateur est stupide, il pourrait être légèrement moins efficace d’utiliser le post-incrémentation (car il doit normalement conserver une copie de la pré- valeur pour une utilisation ultérieure), mais je m’attendrais à ce que les différences soient optimisées.

Il peut être utile de réfléchir à la manière dont la boucle for est implémentée, essentiellement traduite en un ensemble d’affectations, de tests et d’instructions de twig. En pseudo-code, la pré-incrémentation ressemblerait à:

  set i = 0 test: if i >= 5 goto done call printf,"%d",i set i = i + 1 goto test done: nop 

La post-incrémentation aurait au moins une autre étape, mais il serait sortingvial d’optimiser

  set i = 0 test: if i >= 5 goto done call printf,"%d",i set j = i // store value of i for later increment set i = j + 1 // oops, we're incrementing right-away goto test done: nop 

Si vous l’avez écrit comme ça, alors ce serait important:

 for(i=0; i<5; i=j++) { printf("%d",i); } 

Itère une fois de plus que s'il était écrit comme ceci:

 for(i=0; i<5; i=++j) { printf("%d",i); } 

Vous pouvez lire la réponse de Google pour cela ici: http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Preincrement_and_Predecrement

Donc, le point principal est, quelle aucune différence pour un object simple, mais pour les iterators et les autres objects de modèle, vous devez utiliser preincrement.

ÉDITÉ:

Il n’y a pas de différence car vous utilisez un type simple, donc pas d’effets secondaires, et des post-ou pré-incréments exécutés après le corps de la boucle, donc pas d’impact sur la valeur du corps de la boucle.

Vous pouvez le vérifier avec une telle boucle:

 for (int i = 0; i < 5; cout << "we still not incremented here: " << i << endl, i++) { cout << "inside loop body: " << i << endl; } 

I ++ et ++ i sont tous deux exécutés après que printf (“% d”, i) soit exécuté à chaque fois, donc il n’y a pas de différence.

Oui, vous obtiendrez exactement les mêmes résultats pour les deux. pourquoi pensez-vous qu’ils devraient vous donner différents résultats?

Post-incrémentation ou pré-incrémentation dans des situations comme celles-ci:

 int j = ++i; int k = i++; f(i++); g(++i); 

où vous fournissez une valeur, soit en atsortingbuant ou en passant un argument. Vous ne faites ni dans vos boucles for . Il est incrémenté seulement. Post-et pré-ne pas faire sens là-bas!

La troisième instruction de la construction for est uniquement exécutée, mais sa valeur évaluée est ignorée et non prise en charge.
Lorsque la valeur évaluée est ignorée, les incréments pré et post sont égaux.
Ils diffèrent seulement si leur valeur est prise.

Il y a une différence si:

 int main() { for(int i(0); i<2; printf("i = post increment in loop %d\n", i++)) { cout << "inside post incement = " << i << endl; } for(int i(0); i<2; printf("i = pre increment in loop %d\n",++i)) { cout << "inside pre incement = " << i << endl; } return 0; } 

Le résultat:

intérieur post incement = 0

i = post incrémenter dans la boucle 0

intérieur post incement = 1

i = poste incrément dans la boucle 1

La seconde pour la boucle:

pré incement intérieur = 0

i = pré-incrément dans la boucle 1

pré incement intérieur = 1

i = pré-incrément dans la boucle 2

Les compilateurs traduisent

 for (a; b; c) { ... } 

à

 a; while(b) { ... end: c; } 

Donc, dans votre cas (post / pré-incrémentation), cela n’a pas d’importance.

EDIT: continue est simplement remplacé par goto end;