Dla i = 0, dlaczego (i + = i ++) jest równe 0?


253

Weź następujący kod (używany jako aplikacja konsolowa):

static void Main(string[] args)
{
    int i = 0;
    i += i++;
    Console.WriteLine(i);
    Console.ReadLine();
}

Wynik ito 0. Spodziewałem się 2 (podobnie jak niektórzy z moich kolegów). Prawdopodobnie kompilator tworzy jakąś strukturę, której wynikiem ijest zero.

Powodem, dla którego spodziewałem się 2, jest to, że według mojej oceny pierwsze zdanie zostanie ocenione najpierw, zwiększając i o 1. Następnie dodaje się do i. Ponieważ i ma już 1, dodaje 1 do 1. Więc 1 + 1 = 2. Oczywiście nie dzieje się tak.

Czy możesz wyjaśnić, co robi kompilator lub co dzieje się w czasie wykonywania? Dlaczego wynik wynosi zero?

Coś w rodzaju zrzeczenia się odpowiedzialności: Jestem absolutnie świadomy, że nie będziesz (i prawdopodobnie nie powinien) używać tego kodu. Wiem, że nigdy nie będę. Niemniej jednak uważam za interesujące wiedzieć, dlaczego działa w taki sposób i co dokładnie się dzieje.


57
czy oczekiwany wynik nie powinien wynosić 1? i (0) + = i ++ (1), dlatego 0 + = 1 = 1
aleation

11
Działa zgodnie z oczekiwaniami
Steve jest D

177
Ile wariantów tego pytania zostanie zadanych?
mowwwalker

20
Wstępna inkrementacja zwiększy wartość przed wykonaniem akcji i + = ++ dam ci 1
Pierluc SS

21
Dlaczego wszyscy koncentrują się na wstępnym zwiększeniu wartości? W „dziwne” jest to, że wartość stanowi ipo stronie lewej +=jest „buforowane” przed po prawej stronie jest oceniana. Jest to sprzeczne z intuicją, ponieważ wymagałoby na przykład operacji kopiowania, gdyby ibył obiektem. (Proszę, nie zrozumcie mnie źle: absolutnie zgadzam się na stwierdzenie, że 0jest to poprawna i zgodna ze standardami odpowiedź.)
JohnB

Odpowiedzi:


425

To:

int i = 0;
i += i++

Można to zaobserwować podczas pracy (poniższe uproszczenie jest ogromne):

int i = 0;
i = i + i; // i=0 because the ++ is a postfix operator and hasn't been executed
i + 1; // Note that you are discarding the calculation result

To, co faktycznie się dzieje, jest bardziej zaangażowane - spójrz na MSDN, operatory inkrementacji i dekrementacji Postfiksa 7.5.9 :

Przetwarzanie w czasie wykonywania operacji zwiększania lub zmniejszania Postfiksa w postaci x ++ lub x-- składa się z następujących kroków:

  • Jeśli x jest klasyfikowany jako zmienna:

    • x jest obliczane w celu utworzenia zmiennej.
    • Wartość x zostaje zapisana.
    • Wybrany operator jest wywoływany z zapisaną wartością x jako argumentu.
    • Wartość zwrócona przez operatora jest przechowywana w miejscu podanym na podstawie oceny x.
    • Zapisana wartość x staje się wynikiem operacji.

Zauważ, że ze względu na kolejność pierwszeństwa , postfiks ++występuje wcześniej += , ale wynik ostatecznie nie jest używany (ponieważ iużywana jest poprzednia wartość parametru ).


Dokładniejszy rozkład i += i++części, z których jest wykonany, wymaga, aby wiedzieć, że oba +=i ++nie są atomowe (to znaczy żadna z nich nie jest pojedynczą operacją), nawet jeśli wyglądają tak, jak są. Sposób ich realizacji obejmuje zmienne tymczasowe, kopie iprzed operacjami - po jednej dla każdej operacji. (Użyję nazw iAddi iAssignzmiennych tymczasowych używanych odpowiednio dla ++i +=).

A zatem bliższe przybliżenie tego, co się dzieje, to:

int i = 0;
int iAdd = i; // Copy of the current value of i, for ++
int iAssign = i; // Copy of the current value of i, for +=

i = i + 1; // i++ - Happens before += due to order of precedence
i = iAdd + iAssign;

3
@Oded ++Operacja jest wykonywana przed zakończeniem oceny instrukcji. Więc +=nadpisuje wartość. Czy tak się stało?
Anirudh Ramanathan

6
@Oded właściwie jej: int i = 0; i = i + 1; (postfix) i = 0; (assignment). Jeśli używałeś i gdzie indziej w tym stwierdzeniu, to w tym momencie będzie to 1.
drch

@Cthulhu - Zasadniczo. Odpowiedź przez DTB przechodzi do szczegółów.
Oded

6
Nie kupuję tego. Odpowiedź @yoriy jest znacznie dokładniejsza. Po pierwsze, w swojej odpowiedzi mówisz, że ostatnia linia byłaby, i+1a powinna być i=i+1. Czy to nie i++to?
recluze

3
Pierwsza część odpowiedzi jest zbędna. Twoja ostatnia próbka kodu mogła to zrobić IMHO. +1.
corazza

194

Demontaż działającego kodu:

int i = 0;
  xor         edx, edx
  mov         dword ptr i, edx         // set i = 0
i += i++;
  mov         eax, dword ptr i         // set eax = i (=0)
  mov         dword ptr tempVar1, eax  // set tempVar1 = eax (=0)
  mov         eax, dword ptr i         // set eax = 0 ( again... why??? =\ )
  mov         dword ptr tempVar2, eax  // set tempVar2 = eax (=0)
  inc         dword ptr i              // set i = i+1 (=1)
  mov         eax, dword ptr tempVar1  // set eax = tempVar1 (=0)
  add         eax, dword ptr tempVar2  // set eax = eax+tempVar2 (=0)
  mov         dword ptr i, eax         // set i = eax (=0)

Kod ekwiwalentny

Kompiluje do tego samego kodu, co następujący kod:

int i, tempVar1, tempVar2;
i = 0;
tempVar1 = i; // created due to postfix ++ operator
tempVar2 = i; // created due to += operator
++i;
i = tempVar1 + tempVar2;

Demontaż drugiego kodu (aby udowodnić, że są takie same)

int i, tempVar1, tempVar2;
i = 0;
    xor         edx, edx
    mov         dword ptr i, edx
tempVar1 = i; // created due to postfix ++ operator
    mov         eax, dword ptr i
    mov         dword ptr tempVar1, eax
tempVar2 = i; // created due to += operator
    mov         eax, dword ptr i
    mov         dword ptr tempVar2, eax
++i;
    inc         dword ptr i
i = tempVar1 + tempVar2;
    mov         eax, dword ptr tempVar1
    add         eax, dword ptr tempVar2
    mov         dword ptr i, eax

Otwieranie okna demontażu

Większość ludzi nie wie, a nawet nie pamięta, że ​​może zobaczyć końcowy kod asemblera w pamięci za pomocą okna Visual Studio Disassembly . Pokazuje wykonywany kod maszynowy, nie jest to CIL.

Użyj tego podczas debugowania:

Debug (menu) -> Windows (submenu) -> Disassembly

Co się dzieje z postfix ++?

Postfiks ++ mówi, że chcielibyśmy zwiększyć wartość argumentu po ocenie ... że wszyscy wiedzą ... co nieco myli znaczenie słowa "po ocenie" .

Co więc oznacza „po ocenie” :

  • inne zastosowania operandu w tym samym wierszu kodu muszą zostać zmienione:
    • a = i++ + i drugi i ma wpływ na przyrost
    • Func(i++, i) drugi ma wpływ
  • inne zastosowania na tej samej linii dotyczą operatora zwarciowego, takiego jak ||i &&:
    • (false && i++ != i) || i == 0 na trzecie i nie ma wpływu i ++, ponieważ nie jest ono oceniane

Jakie jest zatem znaczenie i += i++;:?

To jest to samo co i = i + i++;

Kolejność oceny jest następująca:

  1. Przechowuj i + i (czyli 0 + 0)
  2. Przyrost i (i staje się 1)
  3. Przypisz wartość kroku 1 do i (i staje się 0)

Nie chodzi o to, że przyrost jest odrzucany.

Jakie jest znaczenie i = i++ + i;:?

To nie to samo co w poprzednim przykładzie. Trzeci ima wpływ na przyrost.

Kolejność oceny jest następująca:

  1. Przechowuj i (czyli 0)
  2. Przyrost i (i staje się 1)
  3. Przechowuj wartość kroku 1 + i (czyli 0 + 1)
  4. Przypisz wartość kroku 3 do i (i staje się 1)

22
+ 1 ++ - dla ostrego rozbioru. Chuck Norris byłby dumny :)
Wydaje mi się,

19
C # ma dobrze zdefiniowaną kolejność oceny dla wyrażenia, a kod obiektowy po prostu implementuje tę kolejność. Dane wyjściowe kodu maszynowego nie są przyczyną ani wyjaśnieniem kolejności oceny.
Kaz

8
Kod maszynowy ułatwia zrozumienie, w jaki sposób kolejność oceny jest realizowana IMO.
Kevin,

5
@StuartLC Widzę, co tam zrobiłeś. Wstyd jednak z powodu odrzuconej opinii.
Steffan Donal

2
a++ + ato nie to samo, a + a++ponieważ nie jest to już czysta matematyka. Prawo przemienności w algebrze nie bierze pod uwagę możliwości, że zmienne zmieniają wartość w połowie wyrażenia. Matematyka porządnie odwzorowuje programowanie tylko wtedy, gdy programowanie jest programowaniem funkcjonalnym. I nawet wtedy, z powodu ograniczeń reprezentacyjnych. Na przykład liczby zmiennoprzecinkowe czasami zachowują się jak liczby rzeczywiste, a czasem nie. Nawet bez skutków ubocznych, przepisy dotyczące przemian i asocjatywności, które mają zastosowanie do liczb rzeczywistych w matematyce, przekraczają liczby zmiennoprzecinkowe.
Kaz

61
int i = 0;
i += i++;

jest oceniany w następujący sposób:

Stack<int> stack = new Stack<int>();
int i;

// int i = 0;
stack.Push(0);                   // push 0
i = stack.Pop();                 // pop 0 --> i == 0

// i += i++;
stack.Push(i);                   // push 0
stack.Push(i);                   // push 0
stack.Push(i);                   // push 0
stack.Push(1);                   // push 1
i = stack.Pop() + stack.Pop();   // pop 0 and 1 --> i == 1
i = stack.Pop() + stack.Pop();   // pop 0 and 0 --> i == 0

tzn. izmienia się dwukrotnie: raz przez i++wyrażenie i raz przez +=wyrażenie.

Ale operandy tego +=zdania są

  • wartość iprzed oszacowaniem i++(po lewej stronie +=) i
  • wartość iprzed obliczeniem i++(prawa strona +=).

Ach, to fantastyczne wytłumaczenie. Przypomina mi, kiedy pracowałem na kalkulatorze opartym na stosie, używając odwrotnej notacji polskiej.
Nathan

36

Najpierw i++zwraca 0. Następnie izwiększa się o 1. Na koniec iustawia się wartość początkową, iktóra wynosi 0 plus i++zwrócona wartość , która również wynosi zero. 0 + 0 = 0.


2
Ale tak i += i++;nie jest i = i++;, więc wartość i++(0) jest dodawana i, a nie „ ijest ustawiona na i++zwracaną wartość ”. Teraz pytanie brzmi: czy po dodaniu i++zwróconej wartości ibędzie ito wartość przyrostowa, czy nie przyrostowa? Odpowiedź, mój przyjacielu, jest zapisana w specyfikacji.
Daniel Fischer

To prawda, naprawię to. Ale zresztą od i = 0początku i += somethingjest równoważne z tym, i = 0 + somethingco jest i = something.
Jong

32

Jest to po prostu od lewej do prawej, oddolna ocena abstrakcyjnego drzewa składni. Pod względem koncepcyjnym drzewo wyrażenia jest przemieszczane od góry do dołu, ale ocena jest rozwijana, gdy rekurencja pojawia się z powrotem na drzewie od dołu.

// source code
i += i++;

// abstract syntax tree

     +=
    /  \
   i    ++ (post)
         \
         i

Ocena rozpoczyna się od rozważenia węzła głównego +=. To jest główny składnik tego wyrażenia. Lewy argument operatora +=musi zostać oceniony, aby określić miejsce, w którym przechowujemy zmienną, i uzyskać wcześniejszą wartość, która wynosi zero. Następnie należy ocenić prawą stronę.

Prawa strona to ++operator inkrementacyjny . Ma jeden operand, iktóry jest oceniany zarówno jako źródło wartości, jak i miejsce, w którym wartość ma być przechowywana. Operator ocenia i, znajduje 0i konsekwentnie zapisuje 1w tej lokalizacji. Zwraca poprzednią wartość, 0zgodnie z semantyką zwracania poprzedniej wartości.

Teraz kontrola wraca do +=operatora. Ma teraz wszystkie informacje potrzebne do zakończenia operacji. Zna miejsce, w którym należy przechowywać wynik (miejsce przechowywania i), a także poprzednią wartość, i ma wartość, którą należy dodać do poprzedniej wartości, a mianowicie 0. Tak więc ikończy się na zero.

Podobnie jak Java, C # zdezynfekował bardzo asinine aspekt języka C, ustalając kolejność oceny. Od lewej do prawej, od dołu do góry: najbardziej oczywista kolejność, której koder może się spodziewać.


+1: Zgadzam się z tobą, z tym wyjątkiem, że każdy programista spodziewa się, że ... spodziewałem się, że będzie tak samo jak z tym: SetSum(ref i, Inc(ref i))z int SetSum(ref int a, int b) { return a += b; }i int Inc(ref int a) { return a++; }... oczywiście nie więcej tego oczekuję.
Miguel Angelo,

To, czego się spodziewałem, jest niespójne! To nie byłoby równe Set(ref i, Sum(i, Inc(ref i)))z int Set(ref int a, int b) { return a = b; }i int Sum(int a, int b) { return a + b; }.
Miguel Angelo

Dzięki; w mojej odpowiedzi sugerujesz wadę / niekompletność, którą muszę naprawić.
Kaz

Problem SetSumpolega na tym, że nie ocenia lewego operandu i, a jedynie pobiera jego adres, więc nie jest równoważny z całkowitą oceną operandu od lewej do prawej. Potrzebujesz czegoś takiego SetSum(ref i, i, PostInc(ref i)). Drugim argumentem SetSumjest wartość, która ma zostać dodana, gdzie po prostu używamy ido określenia poprzedniej wartości i. SetSumjest po prostu int SetSum(ref int dest, int a, int b) { return dest = a + b; }.
Kaz

Pomyłka zdarza się (przynajmniej dla mnie) z operatorem + =, ponieważ operator przypisania ma ocenę od prawej do lewej (np. A = b = c = d) ... więc można sobie wyobrazić, że + = przestrzega tej samej reguły, jako operacja atomowa (jak to zrobiłem z moją metodą SetSum) ... ale tak naprawdę dzieje się tak, że C # przekłada się a += bna a = a + b... pokazując, że operator + = nie jest atomowy ... to tylko cukier syntaktyczny.
Miguel Angelo,

30

Ponieważ i++najpierw zwraca wartość, a następnie ją zwiększa. Ale po ustawieniu i na 1, ustawisz ponownie na 0.


17

Metoda przyrostowa wygląda mniej więcej tak

int ++(ref int i)
{
    int c = i;
    i = i + 1;
    return c;
}

Zasadniczo, kiedy dzwonisz i++, ijest to przyrost, ale w twoim przypadku zwracana jest pierwotna wartość, a 0 jest zwracane.


12

Prosta odpowiedź

int i = 0;
i += i++;
// Translates to:
i = i + 0; // because post increment returns the current value 0 of i
// Before the above operation is set, i will be incremented to 1
// Now i gets set after the increment,
// so the original returned value of i will be taken.
i = 0;

12

i ++ oznacza: zwróć wartość i TO następnie ją zwiększ.

i + = i ++ oznacza: Weź aktualną wartość i. Dodaj wynik i ++.

Dodajmy teraz i = 0 jako warunek początkowy. i + = i ++ jest teraz oceniany w następujący sposób:

  1. Jaka jest aktualna wartość i? Jest 0. Zapisz go, abyśmy mogli dodać do niego wynik działania i ++.
  2. Oceń i ++ (ocenia na 0, ponieważ jest to bieżąca wartość i)
  3. Załaduj zapamiętaną wartość i dodaj do niej wynik kroku 2. (dodaj 0 do 0)

Uwaga: pod koniec kroku 2 wartość i wynosi w rzeczywistości 1. Jednak w kroku 3 odrzucasz ją, ładując wartość i przed jej zwiększeniem.

W przeciwieństwie do i ++, ++ i zwraca wartość przyrostową.

Dlatego i + = ++ dałbym ci 1.


To jest pomoc Full
sonsha

11

Operator przyrostowy post fix ++, nadaje zmiennej wartość w wyrażeniu, a następnie wykonaj przypisanyi przyrost zwrócił zero (0) do wartości, która zastępuje przyrostową (1) , więc otrzymujesz zero. Możesz przeczytać więcej o operatorze przyrostowym w ++ Operator (MSDN).


8

i += i++;będzie równa zero, ponieważ robi to ++później.

i += ++i; zrobi to wcześniej


4
Jeśli tak się ++stanie, spodziewam się, że wynik będzie 1.
komiks


8

Co robi C # i „dlaczego” zamieszania

Spodziewałem się również, że wartość będzie wynosić 1 ... ale niektóre eksploracje tej kwestii wyjaśniły pewne punkty.

Rozważ następujące metody:

    static int SetSum(ref int a, int b) { return a += b; }

    static int Inc(ref int a) { return a++; }

Spodziewałem się, że i += i++będzie tak samo jak SetSum(ref i, Inc(ref i)). Wartość i po tej instrukcji wynosi 1 :

int i = 0;
SetSum(ref i, Inc(ref i));
Console.WriteLine(i); // i is 1

Ale potem doszedłem do innego wniosku ... w i += i++rzeczywistości jest taki sam jak i = i + i++... więc stworzyłem inny podobny przykład, używając tych funkcji:

    static int Sum(int a, int b) { return a + b; }

    static int Set(ref int a, int b) { return a = b; }

Po wywołaniu tego Set(ref i, Sum(i, Inc(ref i)))wartość i wynosi 0 :

int i = 0;
Set(ref i, Sum(i, Inc(ref i)));
Console.WriteLine(i); // i is 0

To nie tylko wyjaśnia, co robi C # ... ale także dlaczego wielu ludzi się z tym myliło ... w tym ja.


2
Dodaj to do swojej oryginalnej odpowiedzi, nie ma żadnej korzyści, aby mieć ją jako osobną odpowiedź.
casperOne

2
Zrobiłem to, aby nie zepsuć drugiej odpowiedzi, ponieważ chodzi o zdekompilowany kod ... podczas gdy w tej jednej próbowałem obrać inne podejście do wyjaśnienia. Co myślisz? Czy powinienem edytować drugą odpowiedź i dołączyć tę? Może, przygotuj ten ... nie wiem! Dzięki za sugestie!
Miguel Angelo

7

Dobry mnemonik, o którym zawsze pamiętam, jest następujący:

Jeśli ++stoi po wyrażeniu, zwraca wartość, którą był wcześniej . Więc następujący kod

int a = 1;
int b = a++;

wynosi 1, ponieważ abyło 1, zanim wzrosło o ++pozycję po a . Ludzie nazywają to notowanie poprawek postów . Istnieje również notacja przed poprawką, w której sytuacja jest dokładnie odwrotna: jeśli ++stoi przed , wyrażenie zwraca wartość taką, jaka jest po operacji:

int a = 1;
int b = ++a;

b jest tu dwa.

To oznacza dla twojego kodu

int i = 0;
i += (i++);

i++zwraca 0 (jak opisano powyżej), więc 0 + 0 = 0.

i += (++i); // Here 'i' would become two

Scott Meyers opisuje różnicę między tymi dwoma notacjami w „Efektywnym programowaniu w C ++”. Wewnętrznie i++(Postfix) pamięta wartość ibyła i wywołuje prefix-notację ( ++i) i zwraca starą wartość i. Dlatego zawsze powinieneś używać ++iw forpętlach (chociaż myślę, że wszystkie współczesne kompilatory tłumaczą i++się ++iw forpętlach).


1
Testowałem int i = 0; i += (++i)i ijest ustawiony na jeden zamiast dwóch. To ma sens dla mnie zbyt, ponieważ przy użyciu prefiksu zamiast postfix nie zmienia faktu, że jeśli piszesz i += (++i)się i = i + (++i)The ioceniano przed ++i, w wyniku i = 0 + (++i)i ostatecznie i = 0 + 1.
Wutz

6

Jedyną prawidłową odpowiedzią na twoje pytanie jest: Ponieważ jest niezdefiniowana.

Ok, zanim wszyscy mnie spalicie ...

Wszyscy odpowiedzieliście, dlaczego wynik i+=i++jest w porządku i logiczny i=0.

Kusiło mnie, by oddać głos za każdą z twoich odpowiedzi, ale obliczona przeze mnie reputacja byłaby zbyt wysoka.

Dlaczego jestem taki zły na was, ludzie? nie z powodu tego, co wyjaśniają twoje odpowiedzi.
Chodzi mi o to, że każda odpowiedź, którą przeczytałem, podjęła niezwykły wysiłek, aby wyjaśnić niemożliwe, ja brawa!

Ale jaki jest wynik? czy to wynik intuicyjny - czy jest to wynik akceptowalny?

Każdy z was widział „nagiego króla” i jakoś zaakceptował go jako racjonalnego króla.

Wszyscy jesteście ŹLE!

i+=i++;wynik w 0jest niezdefiniowany.

błąd w mechanizmie oceny języka, jeśli chcesz ... lub nawet gorzej! błąd w projekcie.

chcesz dowód? oczywiście, że chcesz!

int t=0; int i=0; t+=i++; //t=0; i=1

To jest ... intuicyjny wynik! ponieważ najpierw oceniliśmy tprzypisano mu wartość i dopiero po dokonaniu oceny i przypisaniu mieliśmy do czynienia z operacją po - racjonalne, prawda?

czy to racjonalne, że: i=i++i i=idać taki sam wynik i?

podczas t=i++i t=imają różne wyniki dla i.

Operacja po jest czymś, co powinno się wydarzyć po ocenie instrukcji.
W związku z tym:

int i=0;
i+=i++;

Powinno być tak samo, gdybyśmy napisali:

int i=0;
i = i + i ++;

i dlatego taki sam jak:

int i=0;
i= i + i;
i ++;

i dlatego taki sam jak:

int i=0;
i = i + i;
i = i + 1;

Jakikolwiek wynik, który nie 1wskazuje na błąd w kompilatorze lub błąd w projekcie języka, jeśli idziemy z racjonalnym myśleniem - jednak MSDN i wiele innych źródeł mówi nam „hej - to jest niezdefiniowane!”

Teraz, zanim przejdę dalej, nawet ten zestaw przykładów, które podałem, nie jest przez nikogo wspierany ani uznawany. Jednak to, co według intuicyjnego i racjonalnego sposobu powinno być wynikiem.

Koder nie powinien mieć wiedzy na temat pisania lub tłumaczenia zestawu.

Jeśli jest napisany w sposób niezgodny z definicjami języka - to błąd!

Na koniec skopiowałem to z Wikipedii, Operatory inkrementacji i dekrementacji :
Ponieważ operator inkrementacji / dekrementacji modyfikuje operand, użycie takiego operandu więcej niż jeden raz w ramach tego samego wyrażenia może dawać nieokreślone wyniki . Na przykład w wyrażeniach takich jak x - ++ x nie jest jasne, w jakiej kolejności należy wykonać operatory odejmowania i inkrementacji. Takie sytuacje są jeszcze gorsze, gdy kompilator stosuje optymalizacje, co może spowodować, że kolejność wykonywania operacji będzie inna niż zamierzona przez programistę.

I dlatego.

Prawidłowa odpowiedź brzmi: NIE NALEŻY UŻYWAĆ! (ponieważ jest NIEDEFINIOWANY!)

Tak .. - Ma nieprzewidywalne wyniki, nawet jeśli kompilator C # próbuje jakoś go znormalizować.

Nie znalazłem żadnej dokumentacji C # opisującej zachowanie, które wszyscy udokumentowaliście jako normalne lub dobrze zdefiniowane zachowanie języka. To, co znalazłem, jest dokładnie odwrotne!

[ skopiowane z dokumentacji MSDN dla operatorów przyrostowych i zmniejszających Postfix: ++ i - ]

Gdy operator postfiksu jest stosowany do argumentu funkcji, nie można zagwarantować , że wartość argumentu będzie zwiększana lub zmniejszana przed przekazaniem jej do funkcji. Więcej informacji znajduje się w sekcji 1.9.17 standardu C ++.

Zwróć uwagę, że te słowa nie są gwarantowane ...

Wybacz mi, jeśli ta odpowiedź wydaje się arogancka - nie jestem arogancką osobą. Po prostu uważam, że tysiące ludzi przyjeżdżają tutaj, aby się uczyć, a odpowiedzi, które czytam, wprowadzą ich w błąd i zaszkodzą ich logice i zrozumieniu tematu.


Nie jestem pewien czy śledzę w 100%, ale odwołujesz się do dokumentacji C ++, ale moje pytanie dotyczyło C #. Dokumentacja na ten temat jest tutaj .
Peter

Odniosłem się do C # w mojej odpowiedzi. Z podanego linku: Wynik x ++ lub x-- jest wartością x przed operacją, podczas gdy wynik ++ x lub --x jest wartością x po operacji. W obu przypadkach sam x ma tę samą wartość po operacji. wyraźnie pokazuje, że tak nie jest podczas testowania .. ponieważ i=++izapewni inny wynik niż i=i++. Dlatego moja odpowiedź jest ważna.
GY

Aha, ok, ale jest to mylące, gdy odwołujesz się do dokumentacji C ++. Mówisz więc, że specyfikacja nie została poprawnie zaimplementowana?
Peter

Nie. Mówię, że jest niezdefiniowany zgodnie ze specyfikacją, a użycie niezdefiniowanego spowoduje nieokreślone wyniki.
GY

Niezdefiniowane w C ++, ale C # mówi, że po operacji powinna mieć tę samą wartość , nie? To nie to samo, co niezdefiniowane (ale zgadzam się, że nie powinieneś go używać, patrz moje zrzeczenie się, próbowałem po prostu zrozumieć, co się dzieje).
Peter

4

Operator ++ po zmiennej powoduje, że jest to przyrost postfiksowy. Inkrementacja następuje po wszystkim innym w instrukcji, dodawaniu i przypisywaniu. Jeśli zamiast tego wstawisz ++ przed zmienną, stanie się to przed oszacowaniem wartości i i otrzymasz oczekiwaną odpowiedź.


2
++Nie dzieje po+= oświadczenie, zdarza się podczas realizacji +=oświadczenie. Właśnie dlatego efekty ++override zostały zastąpione przez +=.
dtb

Używanie ++ i faktycznie daje 1, a nie 2 (moja pierwotnie „oczekiwana odpowiedź”).
Peter

Wygląda na to, że przypisanie += zastępuje modyfikację z powodu przyrostu wyrażenia przed lub po nim.
Steven Lu

4

Kroki w obliczeniach to:

  1. int i=0 // Zainicjowano na 0
  2. i+=i++ //Równanie
  3. i=i+i++ // po uproszczeniu równania przez kompilator
  4. i=0+i++ // i podstawienie wartości
  5. i=0+0 // i ++ ma wartość 0, jak wyjaśniono poniżej
  6. i=0 // Wynik końcowy i = 0

Tutaj początkowo wartość iwynosi 0. WKT, i++jest niczym innym jak: najpierw użyj iwartości, a następnie zwiększ iwartość o 1. Więc używa iwartości 0, podczas obliczania, i++a następnie zwiększa ją o 1. Więc daje wartość z 0.


3

Istnieją dwie opcje:

Pierwsza opcja: jeśli kompilator przeczyta instrukcję w następujący sposób,

i++;
i+=i;

wynik to 2.

Dla

else if
i+=0;
i++;

wynik to 1.


5
Żaden z nich nie jest faktycznym wynikiem.
Steven Lu,

3

Bądź bardzo ostrożny: przeczytaj C FAQ : to, co próbujesz zrobić (mieszanie przypisania i ++tej samej zmiennej) jest nie tylko nieokreślone, ale także niezdefiniowane (co oznacza, że ​​kompilator może zrobić cokolwiek podczas oceny !, nie tylko dając „uzasadnione” wyniki).

Przeczytaj rozdział 3 . Cały rozdział jest wart przeczytania! Zwłaszcza 3,9, co wyjaśnia implikację nieokreślonego. Sekcja 3.3 zawiera krótkie podsumowanie tego, co możesz, a czego nie możesz zrobić, używając „i ++” i tym podobnych.

W zależności od wewnętrznych kompilatorów, możesz dostać 0, 2, 1, a nawet cokolwiek innego! A ponieważ jest nieokreślony, jest w stanie to zrobić.


oops, c # ... Zaskoczyło mnie "gcc", przez które przeszli niektórzy, aby zdemontować kod.
Olivier Dulac

1
Tęskniłem też za C #, ale i tak lubiłem odpowiedź.
Iain Collins

1
@Iain: dziękuję, również uważam, że warto było zachować odpowiedź, wiele osób nie wie o tym (ani o tym świetnym fakcie, z najlepszego czasu Usenetu, kiedy większość osób posiadających wiedzę na temat subjcetu wybierała to samo miejsce na aktualizację)
Olivier Dulac,

3

W powyższych odpowiedziach jest wiele doskonałych argumentów, właśnie wykonałem mały test i chcę się z tobą podzielić

int i = 0;
i+ = i++;

Tutaj wynik i pokazuje 0 wynik. Teraz rozważ poniższe przypadki:

Przypadek 1:

i = i++ + i; //Answer 1

wcześniej myślałem, że powyższy kod jest podobny do tego, więc na pierwszy rzut oka odpowiedź wynosi 1, a tak naprawdę odpowiedź i dla tego jednego to 1.

Przypadek 2:

i = i + i++; //Answer 0 this resembles the question code.

tutaj operator przyrostu nie pojawia się na ścieżce wykonania, w przeciwieństwie do poprzedniego przypadku, w którym i ++ ma szansę wykonać przed dodaniem.

Mam nadzieję, że to trochę pomoże. Dzięki


2

Mam nadzieję odpowiedzieć na to z perspektywy programowania typu 101.

Wygląda mi na to, że dzieje się w tej kolejności:

  1. ijest oceniany jako 0, co powoduje, i = 0 + 0że operacja przyrostowa jest i++„w kolejce”, ale przypisanie 0 do ijeszcze się nie zdarzyło.
  2. Przyrost i++zachodzi
  3. Przypisanie i = 0z wyżej dzieje, skutecznie nadpisywania niczego, nr 2 (post-inkrementacja) zrobiłby.

Teraz # 2 może się nigdy nie wydarzyć (prawdopodobnie nie?), Ponieważ kompilator prawdopodobnie zdaje sobie sprawę, że nie będzie miał żadnego celu, ale może to zależeć od kompilatora. Tak czy inaczej, inne, bardziej kompetentne odpowiedzi wykazały, że wynik jest poprawny i zgodny ze standardem C #, ale nie jest określone, co się tutaj dzieje dla C / C ++.

Jak i dlaczego wykraczam poza moje doświadczenie, ale fakt, że poprzednio ocenione zadanie po prawej stronie dzieje się po zwiększeniu, jest prawdopodobnie mylące.

Co więcej, nie spodziewałbyś się, że wynik będzie równy 2, chyba że zrobisz to ++izamiast w i++to wierzę.


1
Wstępna wersja generuje wynik 2z C ++: ideone.com/8dH8tf
Steven Lu

To ma sens. Ale wzrost wstępny jest sytuacją nieco mniej skomplikowaną niż wzrost przyrostowy.
gkimsey

2

Po prostu,

i ++, doda 1 do „i” po zakończeniu operatora „+ =”.

To czego potrzebujesz to ++ i, aby dodać 1 do „i” przed wykonaniem operatora „+ =”.


0
i=0

i+=i

i=i+1

i=0;

Następnie dodaje się 1 do i.

i + = i ++

Tak aby przed dodaniem 1 i, iwziął wartość 0. Tylko jeśli dodamy 1 przed, iuzyskać wartość 0.

i+=++i

i=2

-4

Odpowiedź ibędzie 1.

Zobaczmy, jak:

Początkowo i=0;.

Następnie podczas obliczania i +=i++;według wartości będziemy mieli coś podobnego 0 +=0++;, więc według operatora pierwszeństwo 0+=0zostanie wykonane jako pierwsze, a wynik będzie 0.

Następnie operator przyrostu zostanie zastosowany jako 0++, as, 0+1a wartość ibędzie 1.


3
Ta odpowiedź jest zła. Nie dostaniesz 1, ponieważ kiedy wykonujesz 0 += 0++;zadanie, następuje przyrost, ++ale z wartością i interpretowaną przed ++(ponieważ jest operatorem pocztowym.
PhoneixS,

2
Przepraszam, ale to nieprawda. Przeczytaj moje pytanie, a zobaczysz, że wynik to 0. Jeśli uruchomisz kod, zobaczysz, że jest to efektywnie 0.
Peter
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.