Incremento en C ++ - ¿Cuándo usar x ++ o ++ x?

91

Actualmente estoy aprendiendo C ++ y aprendí sobre el incremento hace un tiempo. Sé que puedes usar "++ x" para hacer el incremento antes y "x ++" para hacerlo después.

Aún así, realmente no sé cuándo usar cualquiera de los dos ... Nunca he usado realmente "++ x" y las cosas siempre funcionaron bien hasta ahora, entonces, ¿cuándo debería usarlo?

Ejemplo: En un bucle for, ¿cuándo es preferible utilizar "++ x"?

Además, ¿alguien podría explicar exactamente cómo funcionan las diferentes incrementos (o decrementaciones)? Yo realmente lo apreciaría.

Jesse Emond
fuente

Respuestas:

114

No es una cuestión de preferencia, sino de lógica.

x++incrementa el valor de la variable x después de procesar la declaración actual.

++xincrementa el valor de la variable x antes de procesar la declaración actual.

Así que solo decide la lógica que escribes.

x += ++iincrementará iy sumará i + 1 ax. x += i++agregará i a x, luego incrementará i.

Oliver Friedrich
fuente
27
y tenga en cuenta que en un bucle for, en primativas, no hay absolutamente ninguna diferencia. Muchos estilos de codificación recomendarán no utilizar nunca un operador de incremento en el que podría malinterpretarse; es decir, x ++ o ++ x solo deberían existir en su propia línea, nunca como y = x ++. Personalmente, no me gusta esto, pero es poco común
Mikeage
2
Y si se usa en su propia línea, es casi seguro que el código generado sea el mismo.
Nosredna
14
Esto puede parecer pedantería (principalmente porque lo es :)) pero en C ++, x++es un rvalue con el valor de xantes del incremento, x++es un lvalue con el valor de xdespués de un incremento. Ninguna expresión garantiza que el valor real incrementado se vuelva a almacenar ax, solo se garantiza que suceda antes del siguiente punto de secuencia. 'después de procesar la declaración actual' no es estrictamente precisa ya que algunas expresiones tienen puntos de secuencia y algunas declaraciones son declaraciones compuestas.
CB Bailey
10
De hecho, la respuesta es engañosa. El momento en el que se modifica la variable x probablemente no difiera en la práctica. La diferencia es que x ++ está definido para devolver un rvalor del valor anterior de x, mientras que ++ x todavía se refiere a la variable x.
sellibitze
5
@BeowulfOF: La respuesta implica un orden que no existe. No hay nada en el estándar que decir cuando se realizan los incrementos. El compilador tiene derecho a implementar "x + = i ++" como: int j = i; i = i + 1; x + = j; "(es decir, 'i' incrementado antes de" procesar la declaración actual "). Es por eso que" i = i ++ "tiene un comportamiento indefinido y es por eso que creo que la respuesta necesita" ajustes ". La descripción de" x + = ++ i "es correcto ya que no hay sugerencia de orden:" incrementará i y agregará i + 1 ax ".
Richard Corden
53

Scott Meyers te dice que prefieras el prefijo excepto en aquellas ocasiones en las que la lógica dicte que el prefijo es apropiado.

Elemento n. ° 6 de "C ++ más eficaz" : para mí es suficiente autoridad.

Para aquellos que no son dueños del libro, aquí están las citas pertinentes. Desde la página 32:

De sus días como programador en C, puede recordar que la forma de prefijo del operador de incremento a veces se llama "incrementar y recuperar", mientras que la forma de sufijo se conoce a menudo como "recuperar e incrementar". Es importante recordar las dos frases, porque casi actúan como especificaciones formales ...

Y en la página 34:

Si es del tipo que se preocupa por la eficiencia, probablemente se puso a sudar cuando vio por primera vez la función de incremento de sufijo. Esa función tiene que crear un objeto temporal por su valor de retorno y la implementación anterior también crea un objeto temporal explícito que debe construirse y destruirse. La función de incremento de prefijo no tiene tales temporales ...

duffymo
fuente
4
Si el compilador no se da cuenta de que el valor antes del incremento no es aterrador, podría implementar el incremento de sufijo en varias instrucciones: copie el valor anterior y luego incremente. El incremento de prefijo siempre debe ser solo una instrucción.
gnud
8
Probé esto ayer con gcc: en un bucle for en el que el valor se desecha después de ejecutar i++o ++i, el código generado es el mismo.
Giorgio
Pruébelo fuera del bucle for. El comportamiento en una tarea debe ser diferente.
duffymo
No estoy de acuerdo explícitamente con Scott Meyers en su segundo punto; por lo general, es irrelevante, ya que el 90% o más de los casos de "x ++" o "++ x" suelen estar aislados de cualquier asignación, y los optimizadores son lo suficientemente inteligentes como para reconocer que no se necesitan variables temporales ser creado en tales casos. En ese caso, las dos formas son completamente intercambiables. La implicación de esto es que las bases de código antiguas plagadas de "x ++" deben dejarse en paz; es más probable que introduzca errores sutiles al cambiarlas a "++ x" que para mejorar el rendimiento en cualquier lugar. Podría decirse que es mejor usar "x ++" y hacer pensar a la gente.
omatai
2
Puede confiar en Scott Meyers todo lo que quiera, pero si su código depende tanto del rendimiento que cualquier diferencia de rendimiento entre ++xy x++realmente importa, es mucho más importante que realmente use un compilador que pueda optimizar completa y adecuadamente cualquiera de las versiones sin importar la contexto. "Ya que estoy usando este viejo martillo de mierda, sólo puedo clavar clavos en un ángulo de 43,7 grados" es un mal argumento para construir una casa clavando clavos a sólo 43,7 grados. Utilice una mejor herramienta.
Andrew Henle
28

Desde cppreference al incrementar iteradores:

Debería preferir el operador de incremento previo (++ iter) al operador de incremento posterior (iter ++) si no va a utilizar el valor anterior. El post-incremento generalmente se implementa de la siguiente manera:

   Iter operator++(int)   {
     Iter tmp(*this); // store the old value in a temporary object
     ++*this;         // call pre-increment
     return tmp;      // return the old value   }

Obviamente, es menos eficiente que el preincremento.

El incremento previo no genera el objeto temporal. Esto puede marcar una diferencia significativa si su objeto es costoso de crear.

Phillip Ngan
fuente
8

Solo quiero notar que el código generado a menudo es el mismo si usa el incremento pre / post donde la semántica (de pre / post) no importa.

ejemplo:

pre.cpp:

#include <iostream>

int main()
{
  int i = 13;
  i++;
  for (; i < 42; i++)
    {
      std::cout << i << std::endl;
    }
}

post.cpp:

#include <iostream>

int main()
{

  int i = 13;
  ++i;
  for (; i < 42; ++i)
    {
      std::cout << i << std::endl;
    }
}

_

$> g++ -S pre.cpp
$> g++ -S post.cpp
$> diff pre.s post.s   
1c1
<   .file   "pre.cpp"
---
>   .file   "post.cpp"
cacho
fuente
5
Para un tipo primitivo como un número entero, sí. ¿Ha comprobado cuál resulta ser la diferencia para algo como un std::map::iterator? Por supuesto, los dos operadores son diferentes, pero tengo curiosidad por saber si el compilador optimizará postfijo a prefijo si no se usa el resultado. No creo que esté permitido, dado que la versión de postfix podría contener efectos secundarios.
Seh
Además, ' el compilador probablemente se dará cuenta de que no necesita el efecto secundario y lo optimizará ' no debería ser una excusa para escribir código descuidado que usa los operadores de postfijo más complejos sin ningún motivo, aparte de presumiblemente el hecho de que tantos los supuestos materiales de enseñanza usan postfix sin razón aparente y se copian al por mayor.
underscore_d
6

Lo más importante a tener en cuenta, en mi opinión, es que x ++ debe devolver el valor antes de que se produjera el incremento; por lo tanto, tiene que hacer una copia temporal del objeto (incremento previo). Esto es menos eficiente que ++ x, que se incrementa en el lugar y se devuelve.

Sin embargo, otra cosa que vale la pena mencionar es que la mayoría de los compiladores podrán optimizar esas cosas innecesarias cuando sea posible, por ejemplo, ambas opciones conducirán al mismo código aquí:

for (int i(0);i<10;++i)
for (int i(0);i<10;i++)
rmn
fuente
5

Estoy de acuerdo con @BeowulfOF, aunque para mayor claridad siempre abogaría por dividir las declaraciones para que la lógica sea absolutamente clara, es decir:

i++;
x += i;

o

x += i;
i++;

Entonces, mi respuesta es que si escribe un código claro, esto rara vez debería importar (y si importa, entonces su código probablemente no sea lo suficientemente claro).

Pujas
fuente
2

Solo quería volver a enfatizar que se espera que ++ x sea más rápido que x ++, (especialmente si x es un objeto de algún tipo arbitrario), por lo que, a menos que sea necesario por razones lógicas, se debe usar ++ x.

Shailesh Kumar
fuente
2
Solo quiero enfatizar que este énfasis es muy probablemente engañoso. Si estás mirando un bucle que termina con una "x ++" aislada y piensas "¡Ajá! ¡Ésa es la razón por la que funciona tan lento!" y lo cambia a "++ x", entonces no espere exactamente ninguna diferencia. Los optimizadores son lo suficientemente inteligentes como para reconocer que no es necesario crear variables temporales cuando nadie va a utilizar sus resultados. La implicación es que las bases de código antiguas plagadas de "x ++" deben dejarse en paz; es más probable que introduzca errores al cambiarlas que mejorar el rendimiento en cualquier lugar.
omatai
1

Explicaste la diferencia correctamente. Solo depende de si desea que x se incremente antes de cada ejecución a través de un bucle, o después de eso. Depende de la lógica de su programa, lo que sea apropiado.

Una diferencia importante cuando se trata de iteradores STL (que también implementan estos operadores) es que ++ crea una copia del objeto al que apunta el iterador, luego incrementa y luego devuelve la copia. ++, por otro lado, hace el incremento primero y luego devuelve una referencia al objeto al que apunta ahora el iterador. Esto es principalmente relevante cuando cada bit de rendimiento cuenta o cuando implementa su propio iterador STL.

Editar: se corrigió la confusión de notación de prefijo y sufijo

Björn Pollex
fuente
Hablar de "antes / después" de la iteración de un bucle solo tiene sentido si el aumento / disminución pre / post ocurre en la condición. Más a menudo, estará en la cláusula de continuación, donde no puede cambiar ninguna lógica, aunque podría ser más lento para los tipos de clase usar postfix y la gente no debería usar eso sin razón.
underscore_d
1

Forma de sufijo de ++, - el operador sigue la regla use-then-change ,

La forma de prefijo (++ x, - x) sigue la regla cambiar-luego-usar .

Ejemplo 1:

Cuando varios valores se conectan en cascada con << usando cout , los cálculos (si los hay) se realizan de derecha a izquierda, pero la impresión se realiza de izquierda a derecha, por ejemplo, (si val si inicialmente 10)

 cout<< ++val<<" "<< val++<<" "<< val;

resultará en

12    10    10 

Ejemplo 2:

En Turbo C ++, si se encuentran múltiples apariciones de ++ o (en cualquier forma) en una expresión, primero se calculan todas las formas de prefijo, luego se evalúa la expresión y finalmente se calculan las formas de sufijo, por ejemplo,

int a=10,b;
b=a++ + ++a + ++a + a;
cout<<b<<a<<endl;

Su salida en Turbo C ++ será

48 13

Mientras que su salida en el compilador moderno será (porque siguen estrictamente las reglas)

45 13
  • Nota: No se recomienda el uso múltiple de operadores de incremento / decremento en la misma variable en una expresión. El manejo / resultados de tales
    expresiones varían de un compilador a otro.
Sunil Dhillon
fuente
No es que las expresiones que contienen múltiples operaciones de incremento / disminución "varíen de un compilador a otro", sino algo peor: tales modificaciones múltiples entre puntos de secuencia tienen un comportamiento indefinido y envenenan el programa.
underscore_d
0

Comprender la sintaxis del lenguaje es importante cuando se considera la claridad del código. Considere copiar una cadena de caracteres, por ejemplo, con un incremento posterior:

char a[256] = "Hello world!";
char b[256];
int i = 0;
do {
  b[i] = a[i];
} while (a[i++]);

Queremos que el ciclo se ejecute encontrando el carácter cero (que prueba falso) al final de la cadena. Eso requiere probar el valor previo al incremento y también incrementar el índice. Pero no necesariamente en ese orden, una forma de codificar esto con el preincremento sería:

int i = -1;
do {
  ++i;
  b[i] = a[i];
} while (a[i]);

Es una cuestión de gusto que es más claro y si la máquina tiene un puñado de registros, ambos deberían tener el mismo tiempo de ejecución, incluso si una [i] es una función que es cara o tiene efectos secundarios. Una diferencia significativa podría ser el valor de salida del índice.

shkeyser
fuente