C: Jaka jest różnica między ++ i i ++?


887

W C jaka jest różnica między używaniem ++ii i++, a które należy stosować w bloku inkrementacji forpętli?


10
Nie jestem pewien, czy oryginalny plakat jest zainteresowany, ale w C ++ różnica w wydajności może być znaczna, ponieważ utworzenie obiektu tymczasowego może być kosztowne dla typu zdefiniowanego przez użytkownika.
W dniu Freund

Odpowiedzi:


1099
  • ++izwiększy wartość i, a następnie zwróci wartość zwiększoną.

     i = 1;
     j = ++i;
     (i is 2, j is 2)
    
  • i++zwiększy wartość i, ale zwróci pierwotną wartość, która była iprzechowywana przed zwiększeniem.

     i = 1;
     j = i++;
     (i is 2, j is 1)
    

W przypadku forpętli albo działa. ++iwydaje się bardziej powszechny, być może dlatego, że właśnie tego używa się w K&R .

W każdym razie postępuj zgodnie z wytyczną „preferuj ++iponad i++”, aby się nie pomylić.

Jest kilka komentarzy dotyczących wydajności ++ii i++. W żadnym kompilatorze nie będącym studentem nie będzie różnicy w wydajności. Możesz to sprawdzić, patrząc na wygenerowany kod, który będzie identyczny.

Pytanie o wydajność jest interesujące ... oto moja próba odpowiedzi: Czy istnieje różnica w wydajności między i ++ a ++ i w C?

Jak zauważa @OnFreund , dla obiektu C ++ jest inaczej, ponieważ operator++()jest to funkcja, a kompilator nie może wiedzieć, jak zoptymalizować tworzenie obiektu tymczasowego w celu przechowywania wartości pośredniej.


6
Czy ten efekt nie spowoduje, że pętla uruchomi się ponownie po osiągnięciu stanu końcowego? Na przykład, for(int i=0; i<10; i++){ print i; } czy to nie będzie inaczej niż w for(int i=0; i<10; ++i){ print i; } moim rozumieniu, że niektóre języki dają różne wyniki w zależności od tego, którego używasz.
aVeRTRAC

27
jonnyflash, oba będą działać identycznie, ponieważ przyrosty i i print są w różnych instrukcjach. Tak powinno być w przypadku każdego języka, który obsługuje styl C ++. Jedyną różnicą między ++ i i ++ będzie użycie wartości operacji w tej samej instrukcji.
Mark Harrison

16
Ponieważ w większości przypadków produkują identyczny kod, wolę, i++ponieważ ma on postać „operand-operator”, a la przypisanie „operand-operator-wartość”. Innymi słowy, operand docelowy znajduje się po lewej stronie wyrażenia, tak jak w instrukcji przypisania.
David R Tribble,

2
@MarkHarrison, będzie działać identycznie nie dlatego i++i print isą w różnych instrukcjach, ale dlatego i++;i i<10są. Uwaga @ jonnyflash nie jest poza bazą. Załóżmy, że masz for(int i=0; i++<10){ print i; }i for(int i=0; ++i<10){ print i; }. Będą one działać inaczej w sposób opisany przez @johnnyflash w pierwszym komentarzu.
Adam

3
@sam, ponieważ w typowej pętli nie występuje efekt uboczny (na przykład przypisanie) w części ++ i.
Mark Harrison

175

i ++ jest znany jako Post Increment, podczas gdy ++ i nazywa się Pre Increment.

i++

i++jest przyrostowy, ponieważ zwiększa iwartość o 1 po zakończeniu operacji.

Zobaczmy następujący przykład:

int i = 1, j;
j = i++;

Tutaj wartość j = 1ale i = 2. Tutaj wartość izostanie przypisana jako jpierwsza, a następnie ibędzie zwiększana.

++i

++ijest przyrostem wstępnym, ponieważ zwiększa iwartość o 1 przed operacją. Oznacza to, j = i;że wykona się po i++.

Zobaczmy następujący przykład:

int i = 1, j;
j = ++i;

Tutaj wartość j = 2ale i = 2. Tutaj wartość izostanie przypisana jpo i inkrementacji i. Podobnie ++ibędzie wcześniej wykonane j=i;.

Na pytanie, które należy zastosować w bloku inkrementacji pętli for? odpowiedź brzmi: możesz użyć dowolnego ... nie ma znaczenia. Wykona twoją pętlę dla tego samego nr. czasów.

for(i=0; i<5; i++)
   printf("%d ",i);

I

for(i=0; i<5; ++i)
   printf("%d ",i);

Obie pętle wytwarzają tę samą moc wyjściową. tj 0 1 2 3 4.

Ma to znaczenie tylko wtedy, gdy go używasz.

for(i = 0; i<5;)
    printf("%d ",++i);

W takim przypadku wyjście będzie 1 2 3 4 5.


1
Inicjowanie zmiennych po prefiksie i po poprawce pomaga zrozumieć. Dzięki.
Abdul Alim Shakir

42

Nie martw się o „wydajność” (szybkość, naprawdę), która z nich jest szybsza. Obecnie mamy kompilatory, które zajmują się tymi sprawami. Używaj tych, które mają sens, na podstawie których wyraźniej widać twoje zamiary.


1
co, mam nadzieję, oznacza „ użyj prefiksu (inc | dec), chyba że faktycznie potrzebujesz starej wartości przed (inc | dec), co robi bardzo niewiele osób, a jednak która jest oszałamiająca w stosunku do rzekomych materiałów dydaktycznych, tworząc kult cargo użytkowników Postfiksa, którzy nawet nie wiedzą, co to jest „..!
underscore_d

Nie jestem pewien, czy „kompilatory w dzisiejszych czasach ... dbają o te rzeczy” są ogólnie prawdą. W ramach niestandardowej operator++(int)(wersja postfiksowa) kod prawie musi utworzyć tymczasowy, który zostanie zwrócony. Czy jesteś pewien, że kompilatory zawsze mogą to zoptymalizować?
Peter - przywróć Monikę

35

++i zwiększa wartość, a następnie zwraca ją.

i++ zwraca wartość, a następnie ją zwiększa.

To subtelna różnica.

Użyj pętli for ++i, ponieważ jest ona nieco szybsza. i++utworzy dodatkową kopię, która zostanie po prostu wyrzucona.


23
Nie znam żadnego kompilatora, w którym ma to znaczenie dla liczb całkowitych.
blabla999

4
To nie jest szybsze . Wartości są ignorowane (działa tylko efekt uboczny), a kompilator może / wygeneruje dokładnie ten sam kod.
wildplasser

31

i++: W tym scenariuszu najpierw przypisywana jest wartość, a następnie następuje przyrost.

++i: W tym scenariuszu najpierw wykonywany jest przyrost, a następnie przypisywana jest wartość

Poniżej znajduje się wizualizacja obrazu, a także tutaj jest miły praktyczny film, który pokazuje to samo.

wprowadź opis zdjęcia tutaj


W jaki sposób można nieco zwiększyć przypisanie?
kouty

@kouty Możesz zwiększyć rejestr nieprzypisany do zmiennej.
Polluks

20

Przyczyna ++i może być nieco szybsza niż i++to, że i++może wymagać lokalnej kopii wartości i, zanim zostanie zwiększona, a ++inigdy tego nie robi. W niektórych przypadkach niektóre kompilatory zoptymalizują go, jeśli to możliwe ... ale nie zawsze jest to możliwe i nie wszystkie kompilatory to robią.

Staram się nie polegać zbytnio na optymalizacjach kompilatorów, więc postępuję zgodnie z radą Ryana Foxa: kiedy mogę korzystać z obu, używam ++i.


11
-1 dla odpowiedzi C ++ na pytanie C. Nie ma więcej „kopii lokalnej” o wartości iniż o wartości 1 podczas pisania instrukcji 1;.
R .. GitHub ZATRZYMAJ LÓD

14

Efektywny wynik użycia jednej z pętli jest identyczny. Innymi słowy, pętla zrobi dokładnie to samo w obu przypadkach.

Pod względem wydajności może wystąpić kara związana z wyborem i ++ zamiast ++ i. Jeśli chodzi o specyfikację języka, użycie operatora post-increment powinno stworzyć dodatkową kopię wartości, na którą działa operator. Może to być źródłem dodatkowych operacji.

Należy jednak wziąć pod uwagę dwa główne problemy z poprzednią logiką.

  1. Nowoczesne kompilatory są świetne. Wszystkie dobre kompilatory są wystarczająco inteligentne, aby zdać sobie sprawę z tego, że widzi przyrost liczby całkowitej w pętli for, i zoptymalizuje obie metody do tego samego wydajnego kodu. Jeśli użycie przyrostu powyżej przyrostu faktycznie powoduje, że Twój program działa wolniej, oznacza to, że używasz strasznego kompilatora.

  2. Pod względem złożoności operacyjnej dwie metody (nawet jeśli kopia jest faktycznie wykonywana) są równoważne. Liczba instrukcji wykonywanych wewnątrz pętli powinna znacznie zdominować liczbę operacji w operacji przyrostowej. Dlatego w każdej pętli o znacznych rozmiarach metoda inkrementacji zostanie znacznie przyćmiona przez wykonanie korpusu pętli. Innymi słowy, lepiej jest martwić się o optymalizację kodu w pętli niż o przyrost.

Moim zdaniem cała kwestia sprowadza się po prostu do preferencji stylu. Jeśli uważasz, że wzrost wstępny jest bardziej czytelny, skorzystaj z niego. Osobiście wolę post-increment, ale prawdopodobnie dlatego, że tego się nauczyłem, zanim dowiedziałem się czegoś o optymalizacji.

Jest to kwintesencyjny przykład przedwczesnej optymalizacji, a takie problemy mogą odciągnąć nas od poważnych problemów projektowych. Jest to jednak dobre pytanie, ponieważ nie ma jednorodności w stosowaniu ani konsensusu w kwestii „najlepszych praktyk”.


13

Obaj zwiększają liczbę. ++ijest równoważne z i = i + 1.

i++i ++isą bardzo podobne, ale nie dokładnie takie same. Oba zwiększają liczbę, ale ++izwiększają liczbę przed obliczeniem bieżącego wyrażenia, podczas gdy i++zwiększają liczbę po obliczeniu wyrażenia.

Przykład:

int i = 1;
int x = i++; //x is 1, i is 2
int y = ++i; //y is 3, i is 3

8

++i(Praca prefiks) Przyrosty a następnie przydziela wartość
(na przykład) int i = 5, int b = ++i w tym przypadku, 6 przyporządkowany jest w pozycji b, a następnie stopniowo, aby 7 i tak dalej.

i++(Praca Postfix) wyznacza i następnie zwiększa wartość
(na przykład) int i = 5, int b = i++ w tym przypadku 5 przyporządkowany jest w pozycji b, a następnie stopniowo, aby 6 i tak dalej.

Przyrost pętli for: i++jest najczęściej używany, ponieważ zwykle używamy wartości początkowej iprzed inkrementacją pętli for. Ale w zależności od logiki programu może się różnić.


7

++i: jest inkrementem, drugi jest inkrementem.

i++: pobiera element, a następnie zwiększa go.
++i: inkrementuje i, a następnie zwraca element.

Przykład:

int i = 0;
printf("i: %d\n", i);
printf("i++: %d\n", i++);
printf("++i: %d\n", ++i);

Wynik:

i: 0
i++: 0
++i: 2

5

Zakładam, że rozumiesz teraz różnicę w semantyce (choć szczerze mówiąc, zastanawiam się, dlaczego ludzie pytają „co oznacza operator X” na temat przepełnienia stosu zamiast czytać, no wiesz, książkę lub tutorial internetowy lub coś w tym rodzaju.

Ale w każdym razie, o ile użyć, ignoruj ​​pytania dotyczące wydajności, które są mało prawdopodobne nawet w C ++. Jest to zasada, którą powinieneś zastosować, decydując, którego użyć:

Powiedz, co masz na myśli w kodzie.

Jeśli nie potrzebujesz przyrostu wartości przed wyciągiem, nie używaj tej formy operatora. Jest to drobny problem, ale jeśli nie pracujesz z przewodnikiem po stylu, który zbanuje jedną wersję na korzyść drugiej (aka przewodnik po stylu z kośćmi), powinieneś użyć formularza, który najlepiej wyraża to, co próbujesz zrobić.

QED, użyj wersji wstępnej:

for (int i = 0; i != X; ++i) ...

5

Różnicę można zrozumieć za pomocą tego prostego kodu C ++ poniżej:

int i, j, k, l;
i = 1; //initialize int i with 1
j = i+1; //add 1 with i and set that as the value of j. i is still 1
k = i++; //k gets the current value of i, after that i is incremented. So here i is 2, but k is 1
l = ++i; // i is incremented first and then returned. So the value of i is 3 and so does l.
cout << i << ' ' << j << ' ' << k << ' '<< l << endl;
return 0;

5

Główną różnicą jest

  • i ++ Post ( po zwiększeniu ) i
  • ++ i Pre ( przed przyrostem )

    • post, jeśli i =1 przyrosty pętli jak1,2,3,4,n
    • pre, jeśli i =1 przyrosty pętli jak2,3,4,5,n

5

i ++ i ++ i

Ten mały kod może pomóc w wizualizacji różnicy pod innym kątem niż już opublikowane odpowiedzi:

int i = 10, j = 10;

printf ("i is %i \n", i);
printf ("i++ is %i \n", i++);
printf ("i is %i \n\n", i);

printf ("j is %i \n", j);
printf ("++j is %i \n", ++j);
printf ("j is %i \n", j);

Wynik jest następujący:

//Remember that the values are i = 10, and j = 10

i is 10 
i++ is 10     //Assigns (print out), then increments
i is 11 

j is 10 
++j is 11    //Increments, then assigns (print out)
j is 11 

Zwróć uwagę na sytuacje przed i po.

dla pętli

Jeśli chodzi o to, który z nich powinien zostać użyty w bloku inkrementacyjnym pętli for, myślę, że najlepszym sposobem na podjęcie decyzji jest dobry przykład:

int i, j;

for (i = 0; i <= 3; i++)
    printf (" > iteration #%i", i);

printf ("\n");

for (j = 0; j <= 3; ++j)
    printf (" > iteration #%i", j);

Wynik jest następujący:

> iteration #0 > iteration #1 > iteration #2 > iteration #3
> iteration #0 > iteration #1 > iteration #2 > iteration #3 

Nie wiem o tobie, ale nie widzę żadnej różnicy w jego użyciu, przynajmniej w pętli for.


5

Poniższy fragment kodu C ilustruje różnicę między operatorami inkrementacji i dekrementacji przed i po:

int  i;
int  j;

Operatorzy przyrostowi:

i = 1;
j = ++i;    // i is now 2, j is also 2
j = i++;    // i is now 3, j is 2

4

Wstępne tworzenie oznacza przyrost na tej samej linii. Post-increment oznacza przyrost po wykonaniu linii.

int j=0;
System.out.println(j); //0
System.out.println(j++); //0. post-increment. It means after this line executes j increments.

int k=0;
System.out.println(k); //0
System.out.println(++k); //1. pre increment. It means it increments first and then the line executes

Jeśli chodzi o operatory OR, AND, staje się bardziej interesujące.

int m=0;
if((m == 0 || m++ == 0) && (m++ == 1)) { //false
/* in OR condition if first line is already true then compiler doesn't check the rest. It is technique of compiler optimization */
System.out.println("post-increment "+m);
}

int n=0;
if((n == 0 || n++ == 0) && (++n == 1)) { //true
System.out.println("pre-increment "+n); //1
}

W szyku

System.out.println("In Array");
int[] a = { 55, 11, 15, 20, 25 } ;
int ii, jj, kk = 1, mm;
ii = ++a[1]; // ii = 12. a[1] = a[1] + 1
System.out.println(a[1]); //12

jj = a[1]++; //12
System.out.println(a[1]); //a[1] = 13

mm = a[1];//13
System.out.printf ( "\n%d %d %d\n", ii, jj, mm ) ; //12, 12, 13

for (int val: a) {
     System.out.print(" " +val); //55, 13, 15, 20, 25
}

W C ++ post / wstępna inkrementacja zmiennej wskaźnika

#include <iostream>
using namespace std;

int main() {

    int x=10;
    int* p = &x;

    std::cout<<"address = "<<p<<"\n"; //prints address of x
    std::cout<<"address = "<<p<<"\n"; //prints (address of x) + sizeof(int)
    std::cout<<"address = "<<&x<<"\n"; //prints address of x

    std::cout<<"address = "<<++&x<<"\n"; //error. reference can't re-assign because it is fixed (immutable)
}

4

Wkrótce:

++ii i++działa tak samo, jeśli nie piszesz ich w funkcji. Jeśli użyjesz czegoś podobnego function(i++)lub function(++i)zauważysz różnicę.

function(++i)mówi pierwszy przyrost i o 1, a następnie wstaw to ido funkcji z nową wartością.

function(i++)mówi najpierw wstaw ido funkcji po tym zwiększeniu io 1.

int i=4;
printf("%d\n",pow(++i,2));//it prints 25 and i is 5 now
i=4;
printf("%d",pow(i++,2));//it prints 16 i is 5 now

2
Różnica nie jest tak naprawdę związana z wywołaniami funkcji (można ją dostrzec bez wykonywania wywołań funkcji). Istnieje różnica między, int j = ++i;a int k = i++;nawet wtedy, gdy nie ma w tym wywołania funkcji.
Jonathan Leffler,

3

Jedyną różnicą jest kolejność operacji między przyrostem zmiennej a wartością zwracaną przez operatora.

Ten kod i jego dane wyjściowe wyjaśniają różnicę:

#include<stdio.h>

int main(int argc, char* argv[])
{
  unsigned int i=0, a;
  a = i++;
  printf("i before: %d; value returned by i++: %d, i after: %d\n", i, a, i);
  i=0;
  a = ++i;
  printf("i before: %d; value returned by ++i: %d, i after: %d\n", i, a, i);
}

Dane wyjściowe to:

i before: 1; value returned by i++: 0, i after: 1
i before: 1; value returned by ++i: 1, i after: 1

Zasadniczo ++izwraca więc wartość po jej zwiększeniu, a ++izwraca wartość przed jej zwiększeniem. Na koniec w obu przypadkach wartość izostanie zwiększona.

Inny przykład:

#include<stdio.h>

int main ()
  int i=0;
  int a = i++*2;
  printf("i=0, i++*2=%d\n", a);
  i=0;
  a = ++i * 2;
  printf("i=0, ++i*2=%d\n", a);
  i=0;
  a = (++i) * 2;
  printf("i=0, (++i)*2=%d\n", a);
  i=0;
  a = (++i) * 2;
  printf("i=0, (++i)*2=%d\n", a);
  return 0;
}

Wynik:

i=0, i++*2=0
i=0, ++i*2=2
i=0, (++i)*2=2
i=0, (++i)*2=2

Wiele razy nie ma różnicy

Różnice są jasne, gdy wartość zwracana jest przypisany do innej zmiennej lub gdy przyrost przeprowadza się łączenie z innych operacji, w których pierwszeństwo jest zastosowanie operacji ( i++*2różni się od ++i*2, ale (i++)*2i (++i)*2powraca do tej samej wartości), w wielu przypadkach stosować zamiennie. Klasycznym przykładem jest składnia pętli for:

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

ma taki sam efekt jak

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

Reguła do zapamiętania

Aby nie wprowadzać zamieszania między dwoma operatorami, przyjąłem tę zasadę:

Skojarz pozycję operatora ++w odniesieniu do zmiennej iz kolejnością ++operacji w odniesieniu do przypisania

Innymi słowy:

  • ++ przed i środkami należy dokonać przyrostu przed przypisaniem;
  • ++ po i środkach należy wykonać inkrementację po przypisaniu:

3

Możesz myśleć o wewnętrznej konwersji tego jako o wielu instrukcjach ;

  • przypadek 1
i++;

możesz myśleć tak,

i;
i = i+1;
  • przypadek 2
++i;

możesz myśleć tak,

i = i+i;
i;

-3

a = i ++ oznacza a zawiera bieżącą wartość i a = ++ i oznacza a zawiera przyrostową wartość i


10
Ta odpowiedź nie jest dokładna. a = i++;oznacza, że ​​przechowywana wartość abędzie wartością iprzed inkrementem, ale „bez inkrementacji” oznacza, że inie jest inkrementowana, co jest całkowicie błędne - ijest inkrementowana, ale wartość wyrażenia jest wartością przed inkrementacją.
Jonathan Leffler,

-6

Oto przykład, aby zrozumieć różnicę

int i=10;
printf("%d %d",i++,++i);

wyjście: 10 12/11 11(w zależności od kolejności oceny argumentów printffunkcji, która różni się w zależności od kompilatora i architektury)

Objaśnienie: i++-> ijest drukowane, a następnie przyrosty. (Drukuje 10, ale istanie się 11) ++i-> izwiększa wartość i drukuje wartość. (Drukuje 12, a także wartość i12)


11
Powoduje to niezdefiniowane zachowanie, ponieważ nie ma punktu sekwencji między i++i++i
MM

@Lundin ma rację, że LHS, RHS przecinka mają między sobą punkt sekwencyjny, ale 2 wyrażenia nadal nie są ze sobą konsekwentne
Antti Haapala
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.