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?
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:
Puisque (1) et (4) sont découplés, vous pouvez utiliser un pré-incrément ou un post-incrément.
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.
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:
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é!
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
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);
}
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;
}
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.
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!
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.