Le Post-incrément et le pré-incrément à l'intérieur d'une boucle 'for' produisent la même sortie [dupliquer]
cette question a déjà une réponse ici:
- différence entre i++ et ++i dans une boucle? 21 réponses
ce qui suit pour les boucles produisent des résultats identiques même si l'une utilise l'incrément de poteau et l'autre pré-incrément.
voici le code:
for(i=0; i<5; i++) {
printf("%d", i);
}
for(i=0; i<5; ++i) {
printf("%d", i);
}
j'obtiens la même sortie pour les deux boucles 'for'. Ai-je raté quelque chose?
12 réponses
après évaluation de i++
ou ++i
, la nouvelle valeur de i
sera la même dans les deux cas. La différence entre pré-et post-incrément est dans le résultat de l'évaluation de l'expression elle-même.
++i
incréments i
et évalue à la nouvelle valeur de i
.
i++
évalue à l'ancienne valeur de i
, et incréments i
.
le 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:
- test de la condition
- si elle est fausse, terminez
- si c'est vrai, exécutez le corps
- exécuter l'étape d'incrémentation
parce que (1) et (4) sont découplés, soit pré - ou post-incrément peut être utilisé.
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 du point de vue de ce bloc de CODE. Ils ont le même effet sur la valeur de i
(incrémenter par un) et, par conséquent, ont le même effet sur le comportement de ces boucles.
noter que 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 du code j
voit la valeur de i
après l'accroissement ( i
est incrémenté en premier, ou pré-incrémenté, d'où le nom) et dans le deuxième bloc du code j
voit la valeur de i
avant l'accroissement.
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 juste jetée, ce qui signifie qu'il n'y a pas de différence perceptible dans la sortie.
cependant, sous le capot il y a une différence: la post-incrémentation i++
a besoin de 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 bon réglage d'optimisation devrait être capable de 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 compliquées comme les itérateurs. Puisque les deux méthodes surchargées pourraient avoir des opérations différentes (on pourrait vouloir sortir " Hé, je suis pré-incrémenté!"pour stdout par exemple) 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 d'arrêt ), il doit utiliser la version post-incrémentation plus chère si vous écrivez myiterator++
.
trois raisons pour lesquelles vous devriez pré-incrément:
- vous n'aurez pas à penser si la variable/l'objet pourrait avoir une méthode post-incrémentation surchargée (par exemple dans une fonction de modèle) et le traiter différemment (ou oublier de le traiter différemment).
- le code semble meilleur.
- Quand quelqu'un vous demande "Pourquoi avez-vous pré-incrément?"vous aurez l'occasion de leur enseigner le problème de l'arrêt et théorique limits of compilateur optimization . :)
C'est une de mes questions préférées. Je vais d'abord vous expliquer la réponse, et ensuite vous dire pourquoi j'aime la question.
Solution:
La réponse est que les deux extraits afficher les nombres de 0 à 4 inclusivement. Cela est dû au fait qu'une boucle for()
est généralement équivalente à une boucle while()
:
for (INITIALIZER; CONDITION; OPERATION) {
do_stuff();
}
peut s'écrire:
INITIALIZER;
while(CONDITION) {
do_stuff();
OPERATION;
}
vous pouvez voir que l'opération est toujours fait au bas de la boucle. Sous cette forme, il devrait être clair que i++
et ++i
auront le même effet: ils vont tous les deux augmenter i
et ignorer le résultat. La nouvelle valeur de i
n'est testée qu'au début de la prochaine itération, en haut de la boucle.
modifier : merci à Jason pour avoir souligné que cette équivalence for()
à while()
ne pas tient si la boucle contient des déclarations de contrôle (telles que continue
) qui empêcheraient OPERATION
d'être exécutée 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 la bonne réponse immédiatement, de sorte que nous pouvons passer directement à la question suivante.
mais étonnamment (pour moi), beaucoup de candidats me disent que la boucle avec le post-incrément affichera les nombres de 0 à 4, et la boucle pré-incrément affichera 0 à 5, ou 1 à 5. Ils expliquent généralement la différence entre pré - et post-incrémentation correctement, mais ils comprennent mal La mécanique de la for()
en boucle.
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 le doute sur la façon dont leur monde fonctionne.
à ce moment-là, la plupart des candidats se rendent compte de leur erreur et trouvent la bonne réponse. Mais j'en ai eu un qui a insisté pour son origine réponse était juste, puis a changé la façon dont il a traduit le for()
à la while()
. C'était une interview fascinante, mais on n'a pas fait d'offre!
Espère que ça aide!
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 post-increment (parce que normalement il a besoin de garder une copie de la valeur pre pour une utilisation ultérieure), mais je m'attendrais à ce que toutes les différences soient optimisées loin dans ce cas.
il pourrait être pratique de penser à la façon dont la boucle for est mise en œuvre, traduit essentiellement en un ensemble de tâches, de tests et d'instructions de branche. En pseudo-code le pré-incrément ressemblerait à:
set i = 0
test: if i >= 5 goto done
call printf,"%d",i
set i = i + 1
goto test
done: nop
Post-incrémentation aurait au moins une autre étape, mais il serait trivial pour optimiser loin
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 ceci, alors il serait important:
for(i=0; i<5; i=j++) {
printf("%d",i);
}
serait itérate une fois de plus que si écrit comme ceci:
for(i=0; i<5; i=++j) {
printf("%d",i);
}
vous pouvez le lire dans Google answer ici: http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Preincrement_and_Predecrement
ainsi, le point principal est, ce qu'aucune différence pour l'objet simple, mais pour les itérateurs et d'autres objets de modèle, vous devriez utiliser le pré-accroissement.
EDITED:
il n'y a pas de différence parce que vous utilisez le type simple, donc pas d'effets secondaires, et post-ou pré-augmentations exécuté après le corps de boucle, donc pas d'impact sur la valeur dans le corps de 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 fois i++ et ++i est exécuté après printf("%d", i) est exécuté à chaque fois, il n'y a donc aucune différence.
Oui, vous obtiendrez exactement les mêmes sorties pour les deux. pourquoi pensez-vous qu'ils devraient vous donner les différentes sorties?
Post-incrémentation ou de pré-incrémentation des questions dans des situations de ce genre:
int j = ++i;
int k = i++;
f(i++);
g(++i);
où vous fournissez une certaine valeur, soit en assignant ou en passant un argument. Vous ne le faites pas dans vos boucles for
. Il est incrémenté. Post - et pré-ça n'a pas de sens là-bas!
la troisième instruction de la construction for n'est exécutée que, mais sa valeur évaluée est écartée et n'est pas prise en compte.
Lorsque la valeur évaluée est écartée, les incréments avant et après sont égaux.
Ils ne diffèrent que 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:
à l'intérieur de post incement = 0
i = incrément de poteau dans la boucle 0
à l'intérieur de post incement = 1
i = incrément de poteau dans la boucle 1
la seconde pour boucle:
prix Intérieur = 0
i = pré incrément dans la boucle 1
à l'intérieur pré incement = 1
i = pré incrément dans la boucle 2
compilateurs traduire
for (a; b; c)
{
...
}
à
a;
while(b)
{
...
end:
c;
}
donc dans votre cas (post/pre - increment) cela n'a pas d'importance.
EDIT: continue sont tout simplement remplacé par goto end;