2008-09-22 10 views
27

Los siguientes dos fragmentos de código C# producen resultados diferentes (suponiendo que el nivel variable se usa tanto antes como después de la llamada recursiva). ¿Por qué?¿Cuándo ++ no produce los mismos resultados que +1?

public DoStuff(int level) 
{ 
    // ... 
    DoStuff(level++); 
    // ... 
} 

,

public DoStuff(int level) 
{ 
    // ... 
    DoStuff(level+1); 
    // ... 
} 

Después de leer algunas de las respuestas a continuación pensé que valdría la pena publicar los seguimientos de pila para el nivel ++, ++ nivel y el nivel + 1 para resaltar cómo engañar a este problema es.

Los he simplificado para esta publicación. La secuencia de llamada recursiva comienza con DoStuff (1).

// nivel ++

DoStuff(int level = 1) 
DoStuff(int level = 2) 
DoStuff(int level = 2) 
DoStuff(int level = 2) 

// ++ nivel

DoStuff(int level = 4) 
DoStuff(int level = 4) 
DoStuff(int level = 3) 
DoStuff(int level = 2) 

// nivel + 1

DoStuff(int level = 4) 
DoStuff(int level = 3) 
DoStuff(int level = 2) 
DoStuff(int level = 1) 
+0

¡Gran pregunta y buena respuesta! He estado usando C++ durante años y C# más recientemente, ¡y no tenía idea! –

+0

Tus seguimientos de pila son incorrectos. nivel ++ debe ser 1, 1, 1, 1; ++ nivel debe ser 1, 2, 3, 4; y level + 1 debería ser 1, 2, 3, 4 –

+0

Orion - los rastros de pila se tomaron directamente de VS2008. Seguí las llamadas de función a cuatro niveles de recursión e hice un corte y pegado. –

Respuesta

27

nivel ++ pasará nivel en DoStuff y luego incremente nivel para usar en el resto de la función. Esto podría ser un error bastante desagradable, ya que la recursión nunca terminará (de lo que se muestra DoStuff siempre se está pasando el mismo valor). Tal vez ++ nivel se entiende en cambio, ya que este es el opuesto al nivel ++ (incrementa el nivel y pasa el valor incrementado a DoStuff)?

nivel + 1 pasará nivel + 1 en DoStuff y dejar nivel sin cambios para el resto de la función.

+0

++ level también produce un comportamiento diferente. Ver los cambios a mi pregunta. –

2

El primero es usar el valor en nivel y ENTONCES incrmentarlo.

Este último está utilizando nivel + 1 como variable pasada.

0

El primer fragmento de código utiliza el operador de incremento posterior a la operación, por lo que la llamada se realiza como DoStuff (nivel) ;. Si desea utilizar un operador de incremento aquí, use DoStuff (++ level) ;.

1

level++ devuelve el valor actual de level, luego aumenta level. level+1 no cambia level en absoluto, pero DoStuff se llama con el valor de (level + 1).

29

Debido a que el primer ejemplo es realmente equivalente a:

public DoStuff(int level) 
{ 
    // ... 
    int temp = level; 
    level = level + 1; 
    DoStuff(temp); 
    // ... 
} 

Tenga en cuenta que también se puede escribir ++ nivel; eso sería equivalente a:

public DoStuff(int level) 
{ 
    // ... 
    level = level + 1; 
    DoStuff(level); 
    // ... 
} 

Es mejor no abusar de los operadores ++ y - en mi opinión; rápidamente se vuelve confuso y/o indefinido lo que realmente está sucediendo, y los compiladores modernos de C++ no generan códigos más eficientes con estos operadores de todos modos.

+0

Estoy de acuerdo en no abusar de ellos. Lo que también es "muy divertido" es sobrecargar post y pre ++ con una clase, ya que todas las apuestas están desactivadas. – workmad3

+1

Tengo que estar en desacuerdo; '++' y '-' son extraordinariamente intuitivos y fáciles. El único momento en que surgen problemas es cuando las personas tratan de hacerse lindas o ni siquiera se molestan en buscar el comportamiento de los operadores que están usando. –

+0

Entonces, ¿qué es intuitivo y fácil al respecto? :-) DoMoreStuff (nivel ++, nivel ++); –

0

level + 1 envía el nivel + 1 a la función. nivel ++ envía nivel a la función y luego la incrementa.

Podría hacer ++ nivel y eso probablemente le daría los resultados que desea.

+0

++ level produce un resultado diferente. Ver los rastros de la pila en mi pregunta original. El ejemplo –

0

El primer ejemplo usa el valor de 'índice', incrementa el valor y actualizaciones 'índice'.

El segundo ejemplo usa el valor de 'índice' más 1, pero no cambia el contenido de 'índice'.

Así que, dependiendo de lo que quieras hacer aquí, ¡podría haber algunas sorpresas en la tienda!

+0

usa 'nivel' no índice. ¿Sugerir que edites esta respuesta para seguir? – workmad3

-2

Según mi experiencia, la expresión del parámetro se evalúa primero y obtiene un valor de nivel. La variable en sí misma se incrementa antes de llamar a la función, porque al compilador no le importa si está usando la expresión como parámetro o de lo contrario ... Todo lo que sabe es que debe incrementar el valor y obtener el valor anterior como resultado de la expresion.

Sin embargo, en mi opinión, código como este es muy descuidado, ya que al tratar de ser inteligente, hace que tenga que pensar dos veces acerca de lo que realmente está sucediendo.

12

el valor de retorno de level++ será level y therefore pase level en DoStuff. Esto podría ser un error bastante desagradable, ya que la recursión nunca terminará (de lo que se muestra DoStuff siempre se pasa con el mismo valor). Tal vez ++level o level + 1 se entiende en su lugar?

level + 1 pasará level + 1 en DoStuff y dejará level sin cambios para el resto de la función.


El operador de incremento posterior (variable ++) es precisamente equivalente a la función

int post_increment(ref int value) 
{ 
    int temp = value; 
    value = value + 1 
    return temp; 
} 

mientras el operador pre-incremento (++ variable) es precisamente equivalente a la función

int pre_increment(ref int value) 
{ 
    value = value + 1; 
    return value; 
} 

Por lo tanto, si expande el operador en línea en el código, los operadores son equivalentes a:

DoStuff(a + 1) 

int temp = a + 1; 
DoStuff(temp); 

DoStuff(++a) 

a = a + 1; 
DoStuff(a); 

DoStuff(a++); 

int temp = a; 
a = a + 1; 
DoStuff(temp); 

Es importante tener en cuenta que el post-incremento es no equivalente a:

DoStuff(a); 
a = a + 1; 

Además, como punto de estilo, no se debe incrementar un valor a menos que la intención sea usar el valor incrementado (una versión específica de la regla, "no asigne un valor a una variable a menos que planee usando ese valor "). Si el valor i + 1 nunca se usa nuevamente, entonces el uso preferido debe ser DoStuff(i + 1) y no DoStuff(++i).

+0

Lo que dices es 100% verdadero. Pero vale la pena mencionar que para la versión posterior al incremento, el compilador puede omitir el temporal y reubicar el inc hasta después del uso para situaciones simples (como el uso de tipos básicos). –

+0

Evan es un tipo de optimización que el compilador PODRÍA hacer, pero también es el tipo de optimización que podría causar problemas muy sutiles. –

+0

Tampoco es una optimización en la que puede confiar. Se trata de un detalle de implementación del compilador y, por lo tanto, no es algo que se diga definitivamente, a menos que también esté dispuesto a decir que sucede en estas versiones de estos compiladores. –

44

para aclarar todas las otras respuestas:

+++++++++++++++++++++

DoStuff(a++); 

es equivalente a:

DoStuff(a); 
a = a + 1; 

+++++++++++++++++++++

DoStuff(++a); 

es T equivalente o:

a = a + 1; 
DoStuff(a); 

+++++++++++++++++++++

DoStuff(a + 1); 

es equivalente a:

b = a + 1; 
DoStuff(b); 

++ +++++++++++++++++++

+1

Excepto su ejemplo para 'DoStuff (a ++)' es incorrecto. Debe ser: int temp = a; a = a + 1; DoStuff (temp); –

+3

@Orion Adrian: No, el ejemplo no está mal. – vitule

+0

Los parámetros nunca se evalúan después de la llamada a la función a la que pertenecen. Las optimizaciones del compilador pueden cambiar el orden de las llamadas, pero esto va más allá de este simple ejemplo. Se puede reorganizar cualquier cantidad de cosas. –

1
public DoStuff(int level) 
{ 

    // DoStuff(level); 
    DoStuff(level++); 
    // level = level + 1; 
    // here, level's value is 1 greater than when it came in 
} 

En realidad, aumenta el valor del nivel.

public DoStuff(int level) 
{ 
    // int iTmp = level + 1; 
    // DoStuff(iTmp); 
    DoStuff(level+1); 
    // here, level's value hasn't changed 
} 

en realidad no incrementa el valor del nivel.

No es un gran problema antes de la llamada a la función, pero después de la llamada a la función, los valores serán diferentes.

+0

Has recibido la primera al revés: primero llamará a DoStuff (nivel) y luego aumentará el nivel. – Sam

+0

Woops. Jaja, respuesta apresurada de mi parte: -p –

0

Si bien es tentador para volver a escribir como:

DoStuff(++level); 

Yo personalmente creo que esto es menos legible que incrementar la variable antes de la llamada al método. Como se ha señalado por un par de las respuestas anteriores, el siguiente sería más claro:

level++; 
DoStuff(level); 
+0

Los operadores de incremento previo y posterior al incremento están destinados a agregar un nivel de concisión al código, no necesariamente a la legibilidad. Si está buscando la legibilidad, no use este nivel de operador. Nivel de uso = nivel + 1; –

+0

No dije que fuera más conciso, solo ayuda a la lectura. No estoy de acuerdo con el uso de nivel = nivel + 1, ya que implica más tipeo :) - Creo que la mayoría de la gente sabe lo que hace ++, pero (según la pregunta original) a veces se confunden con el orden. –

0

Cuando se utiliza un lenguaje que permite la sobrecarga de operadores, y '+ < número entero >' ha sido definido a hacer algo que no sea posterior a la y prefijo '++'.

Por otra parte, solo he visto tales abominaciones en proyectos escolares *, si te encuentras en la naturaleza probablemente tengas una razón realmente buena, bien documentada.

[* una pila de enteros, si no me equivoco. '++' y '-' pulsados ​​y reventados, mientras que '+' y '-' realizaron aritmética normal]

1

En el nivel ++ está utilizando el operador postfix. Este operador funciona después de que se usa la variable. Es decir, después de que se coloca en la pila para la función llamada, se incrementa. Por otro lado, el nivel + 1 es una expresión matemática simple y se evalúa y el resultado se pasa a la función llamada. Si desea incrementar la variable primero y luego pasarlo a la función llamada, puede utilizar el operador de prefijo: ++ nivel

0

Para decirlo de la manera más sencilla, ++var es un operador prefijo y aumentará las variables antes de se evalúa el resto de la expresión. var++, un operador de posfijo, incrementa una variable después de se evalúa el resto de la expresión. Y como otros lo han mencionado, por supuesto, var+1 crea solo una variable temporal (separada en la memoria) que se inicia con var y se incrementa con la constante 1.

Cuestiones relacionadas