c++ - sentencia - El incremento posterior y el preincremento dentro de un bucle ''for'' producen el mismo resultado
sentencia for (12)
Esta pregunta ya tiene una respuesta aquí:
- Diferencia entre i ++ y ++ i en un bucle? 21 respuestas
Los siguientes bucles for producen resultados idénticos aunque uno usa el incremento de posteo y el otro preincremento.
Aquí está el código:
for(i=0; i<5; i++) {
printf("%d", i);
}
for(i=0; i<5; ++i) {
printf("%d", i);
}
Obtengo el mismo resultado para ambos bucles ''for''. ¿Me estoy perdiendo de algo?
Bueno, esto es simple. Los bucles for
arriba son semánticamente equivalentes a
int i = 0;
while(i < 5) {
printf("%d", i);
i++;
}
y
int i = 0;
while(i < 5) {
printf("%d", i);
++i;
}
Tenga en cuenta que las líneas i++;
y ++i;
tener la misma semántica DESDE LA PERSPECTIVA DE ESTE BLOQUE DE CÓDIGO. Ambos tienen el mismo efecto sobre el valor de i
(lo incrementan en uno) y, por lo tanto, tienen el mismo efecto en el comportamiento de estos bucles.
Tenga en cuenta que habría una diferencia si el bucle se reescribió como
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++;
}
Esto se debe a que en el primer bloque de código j
ve el valor de i
después del incremento ( i
se incrementa primero, o se incrementa previamente, de ahí el nombre) y en el segundo bloque de código j
ve el valor de i
antes del incremento.
Compiladores traducen
for (a; b; c)
{
...
}
a
a;
while(b)
{
...
end:
c;
}
Entonces en tu caso (post / incremento previo) no importa.
EDITAR: continúa simplemente reemplazado por goto end;
Después de evaluar i++
o ++i
, el nuevo valor de i
será el mismo en ambos casos. La diferencia entre el incremento previo y posterior es el resultado de evaluar la expresión en sí misma.
++i
incrementa i
y evalúa el nuevo valor de i
.
i++
evalúa el antiguo valor de i
, y aumenta i
.
La razón por la que esto no importa en un bucle for es que el flujo de control funciona más o menos así:
- prueba la condición
- si es falso, terminar
- si es verdad, ejecuta el cuerpo
- ejecutar el paso de incremento
Debido a que (1) y (4) están desacoplados, se puede usar el incremento previo o posterior.
El resultado de tu código será el mismo. La razón es que las dos operaciones de incremento se pueden ver como dos llamadas a funciones distintas. Ambas funciones causan un incremento de la variable, y solo sus valores de retorno son diferentes. En este caso, el valor de retorno simplemente se descarta, lo que significa que no hay diferencia distinguible en la salida.
Sin embargo, bajo el capó hay una diferencia: el i++
posterior al incremento necesita crear una variable temporal para almacenar el valor original de i
, luego realiza el incremento y devuelve la variable temporal. El pre-incremento ++i
no crea una variable temporal. Claro, cualquier ajuste de optimización decente debería ser capaz de optimizar esto cuando el objeto es algo simple como un int
, pero recuerde que los operadores ++ están sobrecargados en clases más complicadas, como los iteradores. Dado que los dos métodos sobrecargados pueden tener diferentes operaciones (uno podría querer dar como resultado "¡Hola, estoy preincorporado!" A stdout, por ejemplo), el compilador no puede decir si los métodos son equivalentes cuando el valor de retorno no se usa (básicamente porque tal compilador resolvería el problema de detención insoluble), necesita usar la versión de post-incremento más cara si escribe myiterator++
.
Tres razones por las que debes preincrementar:
- No tendrá que pensar si la variable / objeto puede tener un método de incremento incrementado posterior (por ejemplo, en una función de plantilla) y tratarlo de manera diferente (u olvidarse de tratarlo de manera diferente).
- El código consistente se ve mejor.
- Cuando alguien te pregunta "¿Por qué te pre-creces?" Tendrás la oportunidad de enseñarles sobre el problema de detención y los límites teóricos de la optimización del compilador . :)
Esta es una de mis preguntas de entrevista favoritas. Primero explicaré la respuesta y luego te diré por qué me gusta la pregunta.
Solución:
La respuesta es que ambos fragmentos imprimen los números del 0 al 4, inclusive. Esto se debe a que un bucle for()
generalmente es equivalente a un bucle while()
:
for (INITIALIZER; CONDITION; OPERATION) {
do_stuff();
}
Puede ser escrito:
INITIALIZER;
while(CONDITION) {
do_stuff();
OPERATION;
}
Puede ver que la OPERACIÓN siempre se realiza en la parte inferior del ciclo. De esta forma, debe quedar claro que i++
y ++i
tendré el mismo efecto: aumentarán i
e ignorarán el resultado. El nuevo valor de i
no se prueba hasta que comience la siguiente iteración, en la parte superior del ciclo.
Editar : Gracias a Jason por señalar que esto for()
equivalencia for()
a while()
no se mantiene si el ciclo contiene instrucciones de control (como continue
) que evitarían que OPERATION
se ejecute en un ciclo while()
. OPERATION
siempre se ejecuta justo antes de la próxima iteración de un bucle for()
.
Por qué es una Buena Pregunta de Entrevista
En primer lugar, solo lleva uno o dos minutos si un candidato le dice la respuesta correcta de inmediato, para que podamos pasar a la siguiente pregunta.
Pero sorprendentemente (para mí), muchos candidatos me dicen que el ciclo con el incremento posterior imprimirá los números del 0 al 4, y el ciclo de preincremento imprimirá de 0 a 5, o de 1 a 5. Generalmente explican la diferencia entre pre y post-incremento de forma correcta, pero malinterpretan la mecánica del bucle for()
.
En ese caso, les pido que reescriban el ciclo usando while()
, y esto realmente me da una buena idea de sus procesos de pensamiento. Y es por eso que hago la pregunta en primer lugar: quiero saber cómo abordan un problema y cómo proceden cuando pongo en duda la forma en que funciona su mundo.
En este punto, la mayoría de los candidatos se dan cuenta de su error y encuentran la respuesta correcta. Pero tuve uno que insistió en que su respuesta original era correcta, luego cambió la forma en que tradujo el for()
al while()
. Fue una entrevista fascinante, ¡pero no hicimos una oferta!
¡Espero que ayude!
Hay una diferencia 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;
}
El resultado:
dentro del incement del poste = 0
i = incremento de publicación en el ciclo 0
en el interior de la entrada = 1
i = incremento de publicación en el ciclo 1
El segundo bucle for:
dentro de la pre instalación = 0
i = pre incremento en el ciclo 1
dentro de la pre instalación = 1
i = pre incremento en el ciclo 2
La tercera instrucción en el constructo for solo se ejecuta, pero su valor evaluado se descarta y no se atiende.
Cuando se descarta el valor evaluado, el incremento pre y post es igual.
Solo difieren si se toma su valor.
Porque en cualquier caso el incremento se realiza después del cuerpo del bucle y, por lo tanto, no afecta a ninguno de los cálculos del bucle. Si el compilador es estúpido, podría ser un poco menos eficiente usar el post-incremento (porque normalmente necesita guardar una copia del valor pre para su uso posterior), pero esperaría que se optimizaran las diferencias en este caso.
Puede ser útil pensar cómo se implementa el bucle For, traducido esencialmente en un conjunto de asignaciones, pruebas e instrucciones de bifurcación. En pseudo-código, el pre-incremento se vería así:
set i = 0
test: if i >= 5 goto done
call printf,"%d",i
set i = i + 1
goto test
done: nop
El post-incremento tendría al menos otro paso, pero sería trivial optimizarlo
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
Puede leer la respuesta de Google aquí: http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Preincrement_and_Predecrement
Entonces, el punto principal es, qué diferencia hay para el objeto simple, pero para los iteradores y otros objetos de plantilla debe usar preincremento.
EDITADO:
No hay diferencia porque utiliza un tipo simple, por lo que no se producen efectos secundarios, ni se crean preincrementos o postincrementos después del cuerpo del ciclo, por lo que no afecta el valor en el cuerpo del ciclo.
Podrías verificarlo con un bucle así:
for (int i = 0; i < 5; cout << "we still not incremented here: " << i << endl, i++)
{
cout << "inside loop body: " << i << endl;
}
Sí, obtendrá exactamente los mismos resultados para ambos. ¿Por qué crees que deberían darte productos diferentes?
Asuntos posteriores al incremento o preincorporación en situaciones como esta:
int j = ++i;
int k = i++;
f(i++);
g(++i);
donde proporciona algún valor, ya sea asignando o pasando un argumento. No haces ninguno en tus bucles for
. Se incrementa solo ¡Publicar y no tener sentido allí!
Si lo escribió así, sería importante:
for(i=0; i<5; i=j++) {
printf("%d",i);
}
Se repetirá una vez más que si se escribe así:
for(i=0; i<5; i=++j) {
printf("%d",i);
}
Tanto i ++ como ++ i se ejecutan después de que printf ("% d", i) se ejecuta en cada momento, por lo que no hay diferencia.