Qual é a diferença entre i ++ e ++ i?

204

Eu vi os dois sendo usado em inúmeras peças de código C #, e eu gostaria de saber quando usar i++ou ++i( isendo uma variável número como int, float, double, etc). Alguém que sabe disso?

Dlaor
fonte
13
Exceto onde não faz diferença, você nunca deve usar nenhum deles, porque isso fará com que as pessoas façam a mesma pergunta que você está fazendo agora. "Não me faça pensar" se aplica tanto ao código quanto ao design.
Instance Hunter
2
@Dlaor: Você leu os links no meu comentário? O primeiro é sobre C # e o segundo é independente de idioma, com uma resposta aceita focada em C #.
Gnovice
7
@gnovice, o primeiro pergunta sobre a diferença de desempenho enquanto eu perguntava sobre a diferença real em termos de código, o segundo é sobre a diferença em um loop enquanto eu perguntava a diferença em geral, e o terceiro é sobre C ++.
Dlaor
1
Eu acredito que isso não é uma duplicata da diferença entre i ++ e ++ i em um loop? - como Dloar diz em seu comentário acima, a outra pergunta pergunta especificamente sobre o uso dentro de um loop.
chue x

Respostas:

201

Estranhamente, parece que as outras duas respostas não explicam bem, e definitivamente vale a pena dizer:


i++significa "diga-me o valor de ie depois aumente"

++isignifica "incremento i, então me diga o valor"


Eles são operadores de pré-incremento e pós-incremento. Nos dois casos, a variável é incrementada , mas se você pegar o valor de ambas as expressões exatamente nos mesmos casos, o resultado será diferente.

Kieren Johnstone
fonte
11
Isto parece estar em desacordo com o que Eric está dizendo
Evan Carroll
65
Nenhuma afirmação está correta. Considere sua primeira declaração. i ++ na verdade significa "salvar o valor, incrementá-lo, armazená-lo em i e depois me diga o valor salvo original". Ou seja, a contagem acontece após o incremento, não antes, como você afirmou. Considere a segunda declaração. i ++ na verdade significa "salvar o valor, incrementá-lo, armazená-lo em i e me informar o valor incrementado". Do jeito que você disse, não está claro se o valor é o valor de i ou o valor que foi atribuído a i; eles poderiam ser diferentes .
Eric Lippert
8
@ Eric, parece-me, mesmo com a sua resposta, a segunda afirmação é categoricamente correta. Embora ele exclua alguns passos.
Evan Carroll
20
Certamente, na maioria das vezes , as maneiras incorretas e desleixadas de descrever a semântica operacional fornecem os mesmos resultados que a descrição precisa e correta. Primeiro, não vejo nenhum valor atraente em obter as respostas corretas por meio de raciocínio incorreto e, segundo, sim, vi código de produção que causa exatamente esse tipo de coisa errada. Provavelmente recebo meia dúzia de perguntas por ano de programadores reais sobre por que alguma expressão em particular, repleta de incrementos, decrementos e desreferências de matriz, não funciona da maneira que eles supunham.
Eric Lippert
12
@supercat: A discussão é sobre C #, não C.
Thanatos
428

A resposta típica para esta pergunta, infelizmente já postada aqui, é que um faz o incremento "antes" das operações restantes e o outro faz o incremento "depois" das operações restantes. Embora intuitivamente transmita a ideia, essa afirmação está completamente errada . A sequência de eventos no tempo é extremamente bem definida em C # e enfaticamente não é o caso de as versões prefixo (++ var) e postfix (var ++) do ++ fazerem coisas em uma ordem diferente em relação a outras operações.

Não é de surpreender que você veja muitas respostas erradas para essa pergunta. Muitos livros "ensine a si mesmo C #" também entendem errado. Além disso, a maneira como o C # faz isso é diferente da maneira como o faz. Muitas pessoas raciocinam como se C # e C fossem a mesma linguagem; eles não são. O design dos operadores de incremento e decremento em C #, na minha opinião, evita as falhas de design desses operadores em C.

Há duas perguntas que devem ser respondidas para determinar qual é exatamente a operação do prefixo e do postfix ++ em C #. A primeira pergunta é qual é o resultado? e a segunda pergunta é quando ocorre o efeito colateral do incremento?

Não é óbvio qual é a resposta para qualquer uma das perguntas, mas na verdade é bastante simples quando você a vê. Deixe-me explicar exatamente o que x ++ e ++ x fazem por uma variável x.

Para o formato do prefixo (++ x):

  1. x é avaliado para produzir a variável
  2. O valor da variável é copiado para um local temporário
  3. O valor temporário é incrementado para produzir um novo valor (não substituindo o temporário!)
  4. O novo valor é armazenado na variável
  5. O resultado da operação é o novo valor (ou seja, o valor incrementado do temporário)

Para o formulário postfix (x ++):

  1. x é avaliado para produzir a variável
  2. O valor da variável é copiado para um local temporário
  3. O valor temporário é incrementado para produzir um novo valor (não substituindo o temporário!)
  4. O novo valor é armazenado na variável
  5. O resultado da operação é o valor do temporário

Algumas coisas a serem observadas:

Primeiro, a ordem dos eventos no tempo é exatamente a mesma nos dois casos . Mais uma vez, é absolutamente não o caso que a ordem dos eventos em tempo muda entre prefixo e postfix. É totalmente falso dizer que a avaliação ocorre antes de outras avaliações ou após outras avaliações. As avaliações ocorrem exatamente na mesma ordem nos dois casos, como você pode ver pelas etapas 1 a 4 sendo idênticas. A única diferença é o último passo - se o resultado é o valor do valor incrementado temporário ou novo.

Você pode demonstrar isso facilmente com um simples aplicativo de console em 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);
    }
}

Aqui estão os 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

No primeiro teste, você pode ver que ambos currentValuee o que foi passado para oTestMethod() extensão mostram o mesmo valor, conforme o esperado.

No entanto, no segundo caso, as pessoas tentarão dizer que o incremento de currentValueacontece após a chamada para TestMethod(), mas, como você pode ver nos resultados, ocorre antes da chamada, conforme indicado pelo resultado 'Atual: 2'.

Nesse caso, primeiro o valor de currentValueé armazenado temporariamente. Em seguida, uma versão incrementada desse valor é armazenada novamente, currentValuemas sem tocar no temporário, que ainda armazena o valor original. Finalmente, esse temporário é passado para TestMethod(). Se o incremento ocorreu após a chamada TestMethod(), ele gravaria o mesmo valor não incrementado duas vezes, mas não o faria.

É importante observar que o valor retornado das operações currentValue++e ++currentValueé baseado no valor temporário e não no valor real armazenado na variável no momento em que a operação termina.

Lembre-se da ordem das operações acima, as duas primeiras etapas copiam o valor atual da variável no temporário. É isso que é usado para calcular o valor de retorno; no caso da versão do prefixo, é esse valor temporário incrementado, enquanto no caso da versão do sufixo, é esse valor diretamente / não incrementado. A variável em si não é lida novamente após o armazenamento inicial no temporário.

Em termos mais simples, a versão do postfix retorna o valor que foi lido da variável (ou seja, o valor do temporário) enquanto a versão do prefixo retorna o valor que foi gravado de volta na variável (ou seja, o valor incrementado do temporário). Nem retorne o valor da variável.

Isso é importante para entender porque a variável em si pode ser volátil e mudou em outro encadeamento, o que significa que o valor de retorno dessas operações pode diferir do valor atual armazenado na variável.

É surpreendentemente comum as pessoas ficarem muito confusas sobre precedência, associatividade e a ordem em que os efeitos colaterais são executados, suspeito, principalmente porque é tão confuso em C. O C # foi cuidadosamente projetado para ser menos confuso em todos esses aspectos. Para algumas análises adicionais desses problemas, inclusive eu demonstrando ainda mais a falsidade da ideia de que operações de prefixo e postfix "movimentam as coisas no tempo", consulte:

https://ericlippert.com/2009/08/10/precedence-vs-order-redux/

o que levou a essa pergunta do SO:

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

Você também pode estar interessado nos meus artigos anteriores sobre o assunto:

https://ericlippert.com/2008/05/23/precedence-vs-associativity-vs-order/

e

https://ericlippert.com/2007/08/14/c-and-the-pit-of-despair/

e um caso interessante em que C dificulta a argumentação sobre a correção:

https://docs.microsoft.com/archive/blogs/ericlippert/bad-recursion-revisited

Além disso, encontramos problemas sutis semelhantes ao considerar outras operações com efeitos colaterais, como atribuições simples encadeadas:

https://docs.microsoft.com/archive/blogs/ericlippert/chaining-simple-assignments-is-not-so-simple

E aqui está um post interessante sobre por que os operadores de incremento resultam em valores em C # e não em variáveis :

Por que não consigo fazer o ++ i ++ em linguagens do tipo C?

Eric Lippert
fonte
4
+1: Para os realmente curiosos, você poderia dar uma referência ao que quer dizer com "falhas de design dessas operações em C"?
Justin Ardini 27/07/2010
21
@ Justin: Eu adicionei alguns links. Mas basicamente: em C, você não tem garantias de que ordem as coisas acontecem com o tempo. Um compilador em conformidade pode fazer qualquer coisa que quiser quando houver duas mutações no mesmo ponto de sequência e nunca precisar dizer que você está fazendo algo que é um comportamento definido pela implementação. Isso leva as pessoas a escrever código perigosamente não portável, que funciona em alguns compiladores e faz algo completamente diferente em outros.
Eric Lippert
14
Devo dizer que, para os realmente curiosos, esse é um bom conhecimento, mas, para o aplicativo C # médio, a diferença entre as palavras nas outras respostas e as coisas reais acontecendo está muito abaixo do nível de abstração da linguagem que realmente produz. sem diferença C # não é assembler e, em 99,9% das vezes i++ou ++ié usado em código, as coisas acontecendo em segundo plano são exatamente isso; em segundo plano . Eu escrevo C # para subir para níveis de abstração acima do que está acontecendo nesse nível; portanto, se isso realmente importa para o seu código C #, você já deve estar no idioma errado.
Tomas Aschan
24
Tomas @: Primeiro, estou preocupado com todos os aplicativos C #, não apenas com a massa de aplicativos médios. O número de aplicativos não médios é grande. Segundo, não faz diferença, exceto nos casos em que faz diferença . Eu recebo perguntas sobre esse material com base em erros reais em código real, provavelmente meia dúzia de vezes por ano. Terceiro, eu concordo com o seu ponto maior; toda a idéia do ++ é uma idéia de nível muito baixo que parece muito singular no código moderno. É realmente apenas lá porque é idiomático para linguagens do tipo C ter esse operador.
Eric Lippert
11
+1, mas tenho que dizer a verdade ... Faz anos e anos que o único uso de operadores postfix que faço que não está sozinho na fila (o que não i++;está) está em for (int i = 0; i < x; i++)... E eu sou muito, muito feliz por isso! (e eu nunca uso o operador prefixo). Se eu tiver que escrever algo que exija um programador sênior 2 minutos para decifrar ... Bem ... É melhor escrever mais uma linha de código ou introduzir uma variável temporária :-) Acho que o seu "artigo" (ganhei 't chamá-lo de 'resposta') reivindica a minha escolha :-)
Xanatos
23

Se você tem:

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

então xserá 11.

Mas se você tiver:

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

então xserá 10.

Observe como Eric aponta, o incremento ocorre ao mesmo tempo em ambos os casos, mas é o valor que é dado como resultado diferente (obrigado Eric!).

Geralmente, eu gosto de usar, a ++imenos que haja uma boa razão para não usar . Por exemplo, ao escrever um loop, eu gosto de usar:

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

Ou, se eu apenas precisar incrementar uma variável, gosto de usar:

++x;

Normalmente, de uma maneira ou de outra, não tem muito significado e se resume ao estilo de codificação, mas se você estiver usando os operadores em outras atribuições (como nos meus exemplos originais), é importante estar ciente dos possíveis efeitos colaterais.

dcp
fonte
2
Você provavelmente poderia esclarecer seus exemplos colocando as linhas de código em ordem - ou seja, digamos "var x = 10; var y = ++ x;" e "var x = 10; var y = x ++;" em vez de.
Tomas Aschan
21
Esta resposta está perigosamente errada. Quando o incremento ocorre, não muda em relação às operações restantes, portanto, dizer que em um caso é feito antes das operações restantes e no outro caso é feito após as operações restantes, é profundamente enganador. O incremento é feito ao mesmo tempo nos dois casos . O que é diferente é qual valor é dado como resultado , não quando o incremento é feito .
Eric Lippert
3
Eu editei isso para usar ino nome da variável, e não varcomo uma palavra-chave em C #.
Jeff Yates
2
Portanto, sem atribuir variáveis ​​e apenas declarar "i ++;" ou "++ i;" dará exatamente os mesmos resultados ou estou entendendo errado?
Dlaor
1
@ Eric: Você poderia esclarecer por que o texto original é "perigoso"? Isso leva ao uso correto, mesmo que os detalhes estejam incorretos.
Justin Ardini 27/07/2010
7
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.

Isso responde sua pergunta ?

o perdido
fonte
4
Pode-se imaginar que o incremento do postfix e o decréscimo do prefixo não afetam a variável: P
Andreas
5

A maneira como o operador trabalha é que ele é incrementado ao mesmo tempo, mas se for antes de uma variável, a expressão será avaliada com a variável incrementada / decrementada:

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

Se for após a variável, a instrução atual será executada com a variável original, como se ainda não tivesse sido incrementada / decrementada:

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

Concordo com o dcp no uso de pré-incremento / decremento (++ x), a menos que seja necessário. Realmente, a única vez em que uso o pós-incremento / decremento é enquanto loops ou loops desse tipo. Esses loops são os mesmos:

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

ou

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

Você também pode fazer isso ao indexar matrizes e coisas como:

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 etc...

Mike Webb
fonte
4

Apenas para o registro, em C ++, se você pode usar (ou seja), não se importa com a ordem das operações (você só deseja aumentar ou diminuir e usá-lo mais tarde), o operador de prefixo é mais eficiente, pois não precisa criar uma cópia temporária do objeto. Infelizmente, a maioria das pessoas usa o posfix (var ++) em vez do prefixo (++ var), apenas porque foi o que aprendemos inicialmente. (Fui perguntado sobre isso em uma entrevista). Não tenho certeza se isso é verdade em c #, mas presumo que seria.

DevinEllingson
fonte
2
É por isso que eu uso ++ i em c # para quando usado sozinho (ou seja, não em uma expressão maior). Sempre que vejo um loop de ac # com i ++, choro um pouco, mas agora que sei que em c # esse é realmente o mesmo código que me sinto melhor. Pessoalmente, ainda vou usar ++ i porque os hábitos morrem muito.
23611 Mikle