web-dev-qa-db-fra.com

La post-incrémentation et la pré-incrémentation dans une boucle 'pour' produisent le même résultat

Les boucles for suivantes produisent des résultats identiques, même si l’une utilise post-incrémentation et l’autre pré-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 "pour". Est-ce que je manque quelque chose?

182
theReverseFlick

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 donne 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 flux de contrôle fonctionne à peu près comme ceci:

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

Puisque (1) et (4) sont découplés, vous pouvez utiliser un pré-incrément ou un post-incrément.

310
danben

Eh bien, c'est simple. Les boucles for 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; ont la même sémantique DEPUIS LA PERSPECTIVE DE CE BLOC DE CODE. Ils ont tous deux le même effet sur la valeur de i (l'incrémentent d'une unité) 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 en tant que

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++;
}

En effet, 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 second bloc de code j voit la valeur de i avant l'incrément.

113
jason

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 entraînent 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: la post-incrémentation i++ doit créer une variable temporaire pour stocker la valeur originale de i, puis effectue l'incrémentation et renvoie 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 pouvoir l’optimiser lorsque l’objet est quelque chose de simple, comme un int, mais rappelez-vous que les opérateurs ++ - sont surchargés dans des classes plus complexes comme les itérateurs. Étant donné que les deux méthodes surchargées peuvent avoir des opérations différentes (par exemple, vous voulez générer "hé, je suis pré-incrémenté!" Sur stdout), le compilateur ne peut pas dire si les méthodes sont équivalentes lorsque la valeur de retour n'est pas utilisée (essentiellement parce qu'un tel compilateur résoudrait le problème insoluble problème bloquant ), il doit utiliser la version de 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'objet 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 a meilleure apparence.
  3. Quand quelqu'un vous demande "Pourquoi pré-incrémentez-vous?" vous aurez la chance de leur apprendre le problème d'arrêt et limites théoriques de l'optimisation du compilateur . :)
89
Anders Sjöqvist

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

Solution:

La réponse est que les deux extraits impriment 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'OPERATION est toujours en bas de la boucle. Sous cette forme, il devrait être clair que i++ et ++i auront le même effet: ils incrémenteront i et ignoreront le résultat. La nouvelle valeur de i n'est pas testée avant le début de la prochaine itération, en haut de la boucle.


Edit : Merci à Jason pour avoir signalé que cette for() à while() équivalence fait not hold si la boucle contient des instructions de contrôle (telles que continue) empêchant 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, cela ne prend qu’une minute ou deux si un candidat donne immédiatement la bonne réponse, nous pouvons donc passer directement à la question suivante.

Mais étonnamment (pour moi), de nombreux candidats me disent que la boucle avec post-incrémentation imprimera les nombres de 0 à 4 et que la boucle de pré-incrémentation affichera 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 les mécanismes de la boucle for().

Dans ce cas, je leur demande de réécrire la boucle en utilisant while(), et cela me donne vraiment 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 jette un doute sur le fonctionnement de leur monde.

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

J'espère que ça t'as aidé!

24
Adam Liss

Parce que dans les deux cas, l'incrément est effectué 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 post-incrémentation (car normalement, il doit conserver une copie de la valeur pre pour une utilisation ultérieure), mais je m'attendrais à ce que toute différence soit optimisé loin dans ce cas.

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

      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 trivial d’optimiser ailleurs

      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
6
tvanfosson

Si vous l’écriviez comme cela, cela aurait de l’importance:

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

Itérerais-je encore une fois que si c'était écrit comme ceci:

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

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

Donc, l’essentiel est qu’il n’y ait aucune différence pour un objet simple, mais pour les itérateurs et autres objets de modèle, vous devez utiliser la préincrémentation.

ÉDITÉ:

Il n'y a pas de différence, car vous utilisez un type simple, donc aucun effet secondaire, ni pré-incrémentation ou post-exécution exécutée après le corps de la boucle, donc aucun 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;
}
2
Yola

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 pré-incréments et les post-incréments sont égaux.
Ils ne diffèrent que si leur valeur est prise.

1
Petruza

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 compte dans des situations comme celle-ci:

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

où vous fournissez une valeur, en assignant ou en passant un argument. Vous ne faites ni dans vos boucles for. Il est incrémenté seulement. Post- et pre- n'ont pas de sens là-bas!

1
Nawaz

I ++ et ++ i sont tous deux exécutés après que printf ("% d", i) a été exécuté à chaque fois, il n'y a donc aucune différence.

1
Hoàng Long