¿Cuál es la diferencia entre i ++ y ++ i?

Los he visto a ambos siendo usados ​​en numerosos pedazos de código C #, y me gustaría saber cuándo usar i++ o ++i (siendo i una variable numérica como int , float , double , etc.). ¿Alguien que sabe esto?

Curiosamente, parece que las otras dos respuestas no lo explican, y definitivamente vale la pena decir:


i++ significa ‘dime el valor de i , luego incremente’

++i significa ‘incrementar i , luego dime el valor’


Son operadores de incremento previo y de incremento posterior. En ambos casos, la variable se incrementa , pero si tomas el valor de ambas expresiones en exactamente los mismos casos, el resultado será diferente.

La respuesta típica a esta pregunta, desafortunadamente publicada aquí, es que uno realiza el incremento “antes” de las operaciones restantes y el otro hace el incremento “después” de las operaciones restantes. Aunque intuitivamente atraviesa la idea, esa afirmación está completamente equivocada . La secuencia de eventos en el tiempo está extremadamente bien definida en C #, y enfáticamente no es el caso que las versiones prefijo y posfijo de ++ hagan las cosas en un orden diferente con respecto a otras operaciones.

No es sorprendente que veas muchas respuestas incorrectas a esta pregunta. Muchos libros de “enséñate C #” también se equivocan. Además, la forma en que C # lo hace es diferente de cómo lo hace C. Muchas personas razonan como si C # y C fueran el mismo idioma; ellos no son. El diseño de los operadores de incremento y decremento en C # en mi opinión evita los defectos de diseño de estos operadores en C.

Hay dos preguntas que deben ser respondidas para determinar qué exactamente la operación de prefijo y postfix ++ están en C #. La primera pregunta es ¿cuál es el resultado? y la segunda pregunta es ¿ cuándo tiene lugar el efecto secundario del incremento?

No es obvio cuál es la respuesta a ninguna de las preguntas, pero en realidad es bastante simple una vez que la ves. Permíteme explicarte exactamente qué hacen x ++ y ++ x para una variable x.

Para la forma de prefijo:

  1. x se evalúa para producir la variable
  2. El valor de la variable se copia a una ubicación temporal
  3. El valor temporal se incrementa para producir un nuevo valor (¡no se sobrescribe el temporal!)
  4. El nuevo valor se almacena en la variable
  5. El resultado de la operación es el nuevo valor (es decir, el valor incrementado del temporal)

Para el formulario de postfix:

  1. x se evalúa para producir la variable
  2. El valor de la variable se copia a una ubicación temporal
  3. El valor temporal se incrementa para producir un nuevo valor (¡no se sobrescribe el temporal!)
  4. El nuevo valor se almacena en la variable
  5. El resultado de la operación es el valor del temporal

Algunas cosas para notar:

Primero, el orden de los eventos en el tiempo es exactamente el mismo en ambos casos . Nuevamente, no es el caso que el orden de los eventos en el tiempo cambie entre el prefijo y el postfijo. Es completamente falso decir que la evaluación ocurre antes de otras evaluaciones o después de otras evaluaciones. Las evaluaciones ocurren exactamente en el mismo orden en ambos casos, ya que puede ver que los pasos del 1 al 4 son idénticos. La única diferencia es el último paso : si el resultado es el valor del valor incrementado temporal o nuevo.

Puedes demostrarlo fácilmente con una simple aplicación de consola C #:

 public class Application { public static int currentValue = 0; public static void Main() { Console.WriteLine("Test 1: ++x"); (++currentValue).TestMethod(); Console.WriteLine("\nTest 2: x++"); (currentValue++).TestMethod(); Console.WriteLine("\nTest 3: ++x"); (++currentValue).TestMethod(); Console.ReadKey(); } } public static class ExtensionMethods { public static void TestMethod(this int passedInValue) { Console.WriteLine("Current:{0} Passed-in:{1}", Application.currentValue, passedInValue); } } 

Aquí están los resultados…

 Test 1: ++x Current:1 Passed-in:1 Test 2: x++ Current:2 Passed-in:1 Test 3: ++x Current:3 Passed-in:3 

En la primera prueba, puede ver que tanto currentValue como lo que se pasó a la extensión TestMethod() muestran el mismo valor, como se esperaba.

Sin embargo, en el segundo caso, la gente tratará de decirle que el incremento de currentValue ocurre después de la llamada a TestMethod() , pero como puede ver en los resultados, ocurre antes de la llamada como lo indica el ‘Current: 2’ resultado.

En este caso, primero el valor de currentValue se almacena de forma temporal. A continuación, una versión incrementada de ese valor se almacena nuevamente en currentValue pero sin tocar el temporal que todavía almacena el valor original. Finalmente ese temporal se pasa a TestMethod() . Si el incremento sucedió después de la llamada a TestMethod() , escribiría el mismo valor no incrementado dos veces, pero no es así.

Es importante tener en cuenta que el valor devuelto por las currentValue++ y ++currentValue se basa en el valor temporal y no en el valor real almacenado en la variable en el momento en que finaliza la operación.

Recuerde en el orden de las operaciones anteriores, los primeros dos pasos copie el valor actual de la variable en el temporal. Eso es lo que se usa para calcular el valor de retorno; en el caso de la versión de prefijo, es ese valor temporal incrementado mientras que en el caso de la versión de sufijo, es ese valor directamente / no incrementado. La variable en sí no se lee nuevamente después del almacenamiento inicial en el temporal.

Dicho de manera más simple, la versión de postfix devuelve el valor que se leyó de la variable (es decir, el valor del temporal) mientras que la versión del prefijo devuelve el valor que se escribió a la variable (es decir, el valor incrementado del temporal). Tampoco devuelve el valor de la variable.

Esto es importante de entender porque la variable en sí podría ser volátil y haber cambiado en otro hilo, lo que significa que el valor de retorno de esas operaciones podría diferir del valor actual almacenado en la variable.

Es sorprendentemente común que las personas se confundan mucho sobre la precedencia, la asociatividad y el orden en que se ejecutan los efectos secundarios, sospecho principalmente porque es muy confuso en C. C # ha sido cuidadosamente diseñado para ser menos confuso en todos estos aspectos. Para un análisis adicional de estos problemas, incluyéndome a mí demostrando más la falsedad de la idea de que las operaciones de prefijo y postfijo “mueven cosas a tiempo”, ver:

http://blogs.msdn.com/b/ericlippert/archive/2009/08/10/precedence-vs-order-redux.aspx

que condujo a esta pregunta SO:

int [] arr = {0}; int value = arr [arr [0] ++]; Valor = 1?

También podría estar interesado en mis artículos anteriores sobre el tema:

http://blogs.msdn.com/b/ericlippert/archive/2008/05/23/precedence-vs-associativity-vs-order.aspx

y

http://blogs.msdn.com/b/ericlippert/archive/2007/08/14/c-and-the-pit-of-despair.aspx

y un caso interesante donde C hace que sea difícil razonar sobre la corrección:

http://blogs.msdn.com/b/ericlippert/archive/2005/04/28/bad-recursion-revisited.aspx

Además, nos encontramos con problemas sutiles similares al considerar otras operaciones que tienen efectos secundarios, como asignaciones simples encadenadas:

http://blogs.msdn.com/b/ericlippert/archive/2010/02/11/chaining-simple-assignments-is-not-so-simple.aspx

Y aquí hay una publicación interesante sobre por qué los operadores de incremento resultan en valores en C # en lugar de en variables :

¿Por qué no puedo hacer ++ i ++ en lenguajes tipo C?

Si usted tiene:

 int i = 10; int x = ++i; 

entonces x será 11 .

Pero si tienes:

 int i = 10; int x = i++; 

entonces x será 10 .

Tenga en cuenta que, como Eric señala, el incremento ocurre al mismo tiempo en ambos casos, pero es el valor que se da como el resultado que difiere (¡gracias Eric!).

En general, me gusta usar ++i menos que haya una buena razón para no hacerlo. Por ejemplo, cuando escribo un bucle, me gusta usar:

 for (int i = 0; i < 10; ++i) { } 

O, si solo necesito incrementar una variable, me gusta usar:

 ++x; 

Normalmente, de una manera u otra no tiene mucha importancia y se reduce al estilo de encoding, pero si está utilizando los operadores dentro de otras asignaciones (como en mis ejemplos originales), es importante tener en cuenta los posibles efectos secundarios.

La forma en que funciona el operador es que se incrementa al mismo tiempo, pero si está antes de una variable, la expresión se evaluará con la variable incrementada / decrementada:

 int x = 0; //x is 0 int y = ++x; //x is 1 and y is 1 

Si es después de la variable, la instrucción actual se ejecutará con la variable original, como si todavía no se hubiera incrementado / decrementado:

 int x = 0; //x is 0 int y = x++; //'y = x' is evaluated with x=0, but x is still incremented. So, x is 1, but y is 0 

Estoy de acuerdo con dcp en el uso de preincremento / decremento (++ x) a menos que sea necesario. Realmente, la única vez que uso el incremento / decremento posterior es en bucles while o bucles de ese tipo. Estos bucles son lo mismo:

 while (x < 5) //evaluates conditional statement { //some code ++x; //increments x } 

o

 while (x++ < 5) //evaluates conditional statement with x value before increment, and x is incremented { //some code } 

También puede hacerlo al indexar matrices y cosas por el estilo:

 int i = 0; int[] MyArray = new int[2]; MyArray[i++] = 1234; //sets array at index 0 to '1234' and i is incremented MyArray[i] = 5678; //sets array at index 1 to '5678' int temp = MyArray[--i]; //temp is 1234 (becasue of pre-decrement); 

Etcétera etcétera...

Solo para el registro, en C ++, si puede usar cualquiera (es decir) no le importa el orden de las operaciones (solo desea boost o disminuir y usarlo más adelante) el operador de prefijo es más eficiente ya que no lo hace tiene que crear una copia temporal del objeto. Desafortunadamente, la mayoría de la gente usa posfix (var ++) en lugar del prefijo (var ++), simplemente porque eso es lo que aprendimos inicialmente. (Me preguntaron sobre esto en una entrevista). No estoy seguro de si esto es cierto en C #, pero supongo que lo sería.

 int i = 0; Console.WriteLine(i++); // Prints 0. Then value of "i" becomes 1. Console.WriteLine(--i); // Value of "i" becomes 0. Then prints 0. 

¿Responde esto a tu pregunta?