HOWTO · C++
Operadores Pre-Incremento VS Post-Incremento en C++
Este artículo muestra cómo utilizar el preincremento VS el posincremento en C++
En esta página
Para una variable aritmética integrada en C++, tanto ++x como x++ suman uno a x. La diferencia está en el valor que produce la expresión: el preincremento produce el valor actualizado, mientras que el posincremento produce el valor anterior a la actualización. Elige según el valor que necesite la expresión que la contiene. En un tipo definido por el usuario, los operadores de incremento sobrecargados siguen sus propias definiciones; por convención, suelen ofrecer un comportamiento análogo para los operadores de prefijo y posfijo.
Diferencia entre las operaciones de preincremento y posincremento en C++
En los tipos aritméticos integrados, ++x actualiza x antes de producir el resultado de la expresión. x++ produce el valor anterior y completa el incremento como parte de la operación. En ambos casos, la variable cambia una vez. Los operadores sobrecargados pueden definir otros detalles, aunque por convención suelen seguir la misma distinción entre prefijo y posfijo.
Ejecuta este ejemplo:
#include <iostream>
int main() {
int value = 4;
int previous = value++;
int current = ++value;
std::cout << previous << ' ' << current << ' ' << value << '\n';
}
4 6 6
previous recibe el valor anterior al primer incremento; current recibe el valor posterior al segundo incremento, que también es el valor final de value.
La posición del incremento dentro del cuerpo de un bucle cambia el valor que imprime. En el primer bucle, i++ es la expresión que se imprime: muestra el valor actual y luego incrementa i. En el segundo, ++j se ejecuta antes de la expresión que se imprime, por lo que el cuerpo muestra el valor actualizado.
#include <iostream>
int main() {
int i = 0;
while (i < 5) {
std::cout << i++ << '\n';
}
std::cout << "final i = " << i << '\n';
}
#include <iostream>
int main() {
int j = 0;
while (j < 5) {
std::cout << ++j << '\n';
}
std::cout << "final j = " << j << '\n';
}
El primer bucle imprime 0, 1, 2, 3 y 4, seguidos de final i = 5; el segundo imprime 1, 2, 3, 4 y 5, seguidos de final j = 5. Las etiquetas visibles final i y final j están en inglés. En ambos bucles, el contador empieza en 0, el cuerpo se ejecuta cinco veces y el contador termina en 5. Estos ejemplos incrementan el contador dentro del cuerpo. En cambio, cuando el incremento aparece en la expresión de iteración de un bucle for, se ejecuta después del cuerpo. Si se descarta el resultado, sustituir ++k por k++ no cambia la secuencia: tanto for (int k = 0; k < 5; ++k) como su versión con k++ recorren los valores 0, 1, 2, 3 y 4, y terminan con k igual a 5.
Evita modificar y leer la misma variable en una expresión
No combines el incremento de una variable con otra lectura de esa misma variable en una expresión sin secuenciar. Por ejemplo, k++ * k tiene comportamiento indefinido en C++: la modificación y la otra lectura no están secuenciadas entre sí. No se puede predecir su resultado ni utilizarlo como una salida válida de ejemplo.
Si lo que quieres es multiplicar el valor anterior por el siguiente, expresa los pasos de forma explícita y secuenciada: inicializa k, guárdalo en old, incrementa k y luego calcula old * k.
Las instrucciones separadas guardan el valor anterior antes de incrementar k y luego multiplican el valor guardado por el valor actualizado. Ejecuta este ejemplo:
#include <iostream>
int main() {
int k = 10;
int old = k;
++k;
int product = old * k;
std::cout << product << '\n';
}
110
Cómo cambia la elección con iteradores y tipos definidos por el usuario
La distinción de valores importa sobre todo cuando un incremento se combina con otra operación en la misma expresión. El caso habitual es la desreferencia: *it++ significa *(it++), así que lee el elemento al que apunta el iterador en ese momento y después avanza el iterador. En cambio, *++it avanza primero y luego lee el nuevo elemento. Saltarse o repetir un elemento durante un recorrido suele ser exactamente esta confusión entre prefijo y posfijo, y no un error en la condición del bucle.
#include <iostream>
#include <vector>
int main() {
std::vector<int> values{10, 20, 30};
auto it = values.begin();
std::cout << *it++ << '\n';
std::cout << *it << '\n';
std::cout << *++it << '\n';
}
10
20
30
La primera línea imprime 10 y deja el iterador apuntando a 20; imprimir *it confirma la nueva posición antes de que *++it avance de nuevo e imprima 30.
En un tipo definido por el usuario, las dos formas son dos funciones distintas. Por convención, el preincremento se declara como operator++() y devuelve una referencia al objeto actualizado, mientras que el posincremento se declara como operator++(int) —ese int es un marcador ficticio, no un valor que pase quien llama—, guarda una copia del estado anterior, incrementa mediante la forma prefija y devuelve esa copia por valor.
#include <iostream>
class Counter {
public:
explicit Counter(int value) : value_(value) {}
Counter& operator++() {
++value_;
return *this;
}
Counter operator++(int) {
Counter old = *this;
++(*this);
return old;
}
int value() const { return value_; }
private:
int value_;
};
int main() {
Counter counter{5};
Counter old = counter++;
Counter& ref = ++counter;
std::cout << old.value() << ' ' << counter.value() << ' '
<< ( &ref == &counter ) << '\n';
}
5 7 1
Aquí old conserva el valor inicial 5, el contador en sí termina en 7 tras ambos incrementos, y el 1 final confirma que el preincremento devolvió el propio contador y no una copia. La misma distinción explica por qué el resultado de ++x sigue refiriéndose al propio x, mientras que el resultado de x++ es una copia temporal del valor anterior.
Aquí es también donde la pregunta por el rendimiento se vuelve concreta. Para un entero integrado cuyo resultado se descarta, los compiladores suelen generar el mismo código para ambas formas, así que preferir el prefijo en ese caso es una convención de legibilidad, no una ganancia de velocidad. La copia del posincremento solo cuesta algo cuando copiar es trabajo real: un iterador o contador definido por el usuario que copia estado, o un operador sobrecargado que hace más que sumar uno. Si un tipo define solo una de las dos formas, la otra simplemente no está disponible; compruebe las declaraciones del tipo en lugar de suponer que existen ambas.
Cuándo usar preincremento o posincremento
Usa el posincremento cuando la expresión necesite el valor anterior, como en int previous = value++;. Usa el preincremento cuando se necesite el valor actualizado o cuando se descarte el valor de la expresión, como ocurre en la expresión de iteración de un bucle.
Para los iteradores, el preincremento es una convención habitual cuando no se necesita el valor anterior del iterador. Los operadores sobrecargados de un iterador definido por el usuario siguen su implementación, aunque por convención suelen reflejar la distinción integrada entre prefijo y posfijo. Una operación posfija del iterador puede guardar y devolver una copia del iterador anterior; el preincremento puede evitar ese trabajo en los tipos de iterador cuya copia tiene un coste. Esto no demuestra que haya una diferencia de velocidad medible en un programa concreto: la optimización del compilador y la implementación del iterador influyen en el trabajo generado.
Conclusión
Ambas formas incrementan la variable una vez. El preincremento produce el valor actualizado y el posincremento, el valor anterior. Elige la forma que indique con claridad qué valor necesita la expresión; para los iteradores, prefiere el preincremento cuando no se use el valor anterior, sin dar por hecho que garantiza una mejora de velocidad.
Preguntas frecuentes
¿Cuál es la diferencia principal entre el preincremento y el posincremento?
En los tipos aritméticos integrados, ++x actualiza primero y produce el valor actualizado. x++ produce el valor anterior y completa el incremento. Los operadores sobrecargados siguen sus propias definiciones, aunque por convención suelen ofrecer un comportamiento análogo para el prefijo y el posfijo.
¿Puedo usar cualquiera de las dos formas en un bucle?
Sí. En la expresión de iteración de un bucle for, normalmente se descarta el resultado, así que sustituir ++i por i++ no cambia la secuencia de iteración de ese bucle.
¿Qué forma es más rápida en C++?
No hay una clasificación de velocidad universal que se aplique a todas las expresiones y tipos. El posincremento puede requerir una copia temporal en iteradores o tipos definidos por el usuario; la optimización puede eliminar trabajo innecesario. Elige según el valor que necesites y la claridad de la intención, y mide una carga de trabajo real antes de afirmar que hay una diferencia de rendimiento.
¿Cuándo resulta útil el posincremento?
Úsalo cuando la expresión necesite el valor anterior al incremento, por ejemplo, para asignar ese valor a otra variable.
¿Se comportan igual el preincremento y el posincremento en todos los lenguajes?
No. Los operadores con una escritura similar pueden tener reglas distintas según el lenguaje. Consulta la semántica del lenguaje que estés usando; estas explicaciones corresponden a C++.