HOWTO · C++
Operadores pré-incremento VS pós-incremento em C++
Aprenda como o pré-incremento (++x) e o pós-incremento (x++) diferem nos valores das expressões, em loops e no uso de iteradores em C++, e como evitar acessos não sequenciados a uma variável.
Nesta página
Para uma variável aritmética integrada em C++, tanto ++x quanto x++ somam um a x. A diferença está no valor produzido pela expressão: o pré-incremento produz o valor atualizado, enquanto o pós-incremento produz o valor anterior à atualização. Escolha a forma de acordo com o valor de que a expressão ao redor precisa. Em tipos definidos pelo usuário, os operadores de incremento sobrecarregados seguem suas próprias definições; por convenção, costumam oferecer comportamentos análogos para os operadores de prefixo e de sufixo.
Diferença entre as operações de pré-incremento e pós-incremento em C++
Para tipos aritméticos integrados, ++x atualiza x antes de produzir o resultado da expressão. x++ produz o valor antigo e conclui o incremento como parte da operação. Nos dois casos, a variável muda uma vez. Operadores sobrecarregados podem ter outros detalhes, embora normalmente sigam a mesma distinção entre prefixo e sufixo.
Execute este exemplo:
#include <iostream>
int main() {
int value = 4;
int previous = value++;
int current = ++value;
std::cout << previous << ' ' << current << ' ' << value << '\n';
}
4 6 6
previous recebe o valor anterior ao primeiro incremento; current recebe o valor após o segundo incremento, que também é o valor final de value.
A posição do incremento no corpo do loop altera o valor impresso por ele. No primeiro loop, i++ é a expressão usada para imprimir: ela imprime o valor atual e, em seguida, incrementa i. No segundo, ++j é executado antes da expressão de impressão, então o corpo imprime o valor atualizado.
#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';
}
O primeiro loop imprime 0, 1, 2, 3 e 4, seguidos de final i = 5; o segundo imprime 1, 2, 3, 4 e 5, seguidos de final j = 5. Os rótulos final i e final j exibidos na saída permanecem em inglês. Nos dois casos, o contador começa em 0, o corpo é executado cinco vezes e o valor final do contador é 5. Nesses exemplos, o incremento ocorre no corpo do loop. Já na expressão de iteração de um loop for, ele é executado após o corpo. Se o resultado for descartado, trocar ++k por k++ mantém a mesma sequência: tanto for (int k = 0; k < 5; ++k) quanto a versão com k++ percorrem os valores de 0 a 4 e terminam com k igual a 5.
Evite modificar e ler a mesma variável em uma única expressão
Não combine o incremento de uma variável com outra leitura dessa mesma variável em uma expressão sem sequenciamento entre as operações. Por exemplo, k++ * k tem comportamento indefinido em C++: a modificação e a outra leitura não têm uma ordem definida entre si. Portanto, não é possível prever seu resultado nem apresentá-lo como uma saída válida do exemplo.
Se a intenção for multiplicar o valor antigo pelo valor seguinte, deixe as etapas explícitas e sequenciadas: inicialize k, salve seu valor em old, incremente k e então calcule old * k.
As instruções separadas salvam o valor antigo antes de incrementar k e, em seguida, multiplicam esse valor salvo pelo valor atualizado. Execute este exemplo:
#include <iostream>
int main() {
int k = 10;
int old = k;
++k;
int product = old * k;
std::cout << product << '\n';
}
110
Como a escolha muda com iteradores e tipos definidos pelo usuário
A distinção de valores importa sobretudo quando um incremento é combinado com outra operação na mesma expressão. O caso comum é a desreferenciação: *it++ significa *(it++), portanto lê o elemento para o qual o iterador aponta no momento e depois avança o iterador. Em contraste, *++it avança primeiro e depois lê o novo elemento. Pular ou repetir um elemento durante uma travessia costuma ser exatamente essa confusão entre prefixo e pós-fixo, e não um erro na condição do loop.
#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
A primeira linha imprime 10 e deixa o iterador apontando para 20; imprimir *it confirma a nova posição antes que *++it avance novamente e imprima 30.
Para um tipo definido pelo usuário, as duas formas são duas funções diferentes. Por convenção, o pré-incremento é declarado como operator++() e retorna uma referência para o objeto atualizado, enquanto o pós-incremento é declarado como operator++(int) — esse int é um marcador fictício, não um valor passado por quem chama —, salva uma cópia do estado antigo, incrementa pela forma prefixada e retorna essa cópia 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
Aqui, old mantém o valor inicial 5, o contador em si termina em 7 após os dois incrementos, e o 1 final confirma que o pré-incremento retornou o próprio contador, e não uma cópia. A mesma distinção explica por que o resultado de ++x ainda se refere ao próprio x, enquanto o resultado de x++ é uma cópia temporária do valor antigo.
É também aqui que a questão do desempenho se torna concreta. Para um inteiro integrado cujo resultado é descartado, os compiladores normalmente geram o mesmo código para as duas formas, portanto preferir o prefixo nesse caso é uma convenção de legibilidade, não um ganho de velocidade. A cópia do pós-incremento só custa algo quando copiar é trabalho real: um iterador ou contador definido pelo usuário que copia estado, ou um operador sobrecarregado que faz mais do que somar um. Se um tipo define apenas uma das duas formas, a outra simplesmente não está disponível; verifique as declarações do tipo em vez de presumir que ambas existem.
Quando usar pré-incremento ou pós-incremento
Use o pós-incremento quando a expressão precisar do valor antigo, como em int previous = value++;. Use o pré-incremento quando o valor atualizado for necessário ou quando o valor da expressão for descartado, como na expressão de iteração de um loop.
Para iteradores, o pré-incremento é uma convenção comum quando o valor anterior do iterador não é necessário. Os operadores sobrecarregados de um iterador definido pelo usuário seguem sua implementação, embora normalmente reproduzam a distinção entre prefixo e sufixo dos tipos integrados. Uma operação de pós-incremento de iterador pode preservar e retornar uma cópia do iterador anterior; para tipos de iterador em que essa cópia tem um custo, o pré-incremento pode evitar esse trabalho. Isso não demonstra que haverá uma diferença de velocidade mensurável em um programa específico: a otimização do compilador e a implementação do iterador influenciam o trabalho gerado.
Conclusão
As duas formas incrementam a variável uma vez. O pré-incremento produz o valor atualizado, e o pós-incremento produz o valor anterior. Use a forma que indique claramente de qual valor a expressão precisa; para iteradores, prefira o pré-incremento quando o valor anterior não for usado, sem presumir que isso garantirá maior velocidade.
Perguntas frequentes
Qual é a principal diferença entre pré-incremento e pós-incremento?
Para tipos aritméticos integrados, ++x atualiza primeiro e produz o valor atualizado. x++ produz o valor anterior e conclui o incremento. Operadores sobrecarregados seguem suas próprias definições, embora normalmente ofereçam comportamentos análogos para prefixo e sufixo.
Posso usar qualquer uma das formas em um loop?
Sim. Na expressão de iteração de um loop for, o resultado costuma ser descartado. Por isso, trocar ++i por i++ mantém inalterada a sequência de iterações desse loop.
Qual forma é mais rápida em C++?
Não existe uma classificação universal de velocidade para todas as expressões e todos os tipos. O pós-incremento pode envolver uma cópia temporária no caso de iteradores ou outros tipos definidos pelo usuário; a otimização pode eliminar trabalho desnecessário. Escolha com base no valor necessário e na clareza da intenção. Antes de fazer afirmações sobre desempenho, meça um caso de uso real.
Quando o pós-incremento é útil?
Use-o quando a expressão precisar do valor anterior ao incremento, por exemplo, para atribuí-lo a outra variável.
Pré-incremento e pós-incremento têm o mesmo comportamento em todas as linguagens?
Não. Operadores com grafia semelhante podem seguir regras específicas de cada linguagem. Consulte a semântica da linguagem que estiver usando; estas explicações se aplicam a C++.