Dlaczego dysponujemy przyrostem Postfiksa?

55

Oświadczenie : Doskonale znam semantykę przyrostu przedrostka i przyrostka. Więc proszę, nie wyjaśniaj mi, jak działają.

Czytając pytania na temat przepełnienia stosu, nie mogę nie zauważyć, że programiści wpadają w zamieszanie przez operatora przyrostu Postfiksa w kółko. Z tego wynika następujące pytanie: czy jest jakikolwiek przypadek użycia, w którym przyrostek Postfiksa zapewnia rzeczywistą korzyść pod względem jakości kodu?

Pozwól mi wyjaśnić moje pytanie za pomocą przykładu. Oto super-krótka implementacja strcpy:

while (*dst++ = *src++);

Ale to nie jest najbardziej samok dokumentujący się kod w mojej książce (i generuje dwa irytujące ostrzeżenia w rozsądnych kompilatorach). Więc co jest nie tak z następującą alternatywą?

while (*dst = *src)
{
    ++src;
    ++dst;
}

Możemy wtedy pozbyć się mylącego przypisania w stanie i uzyskać całkowicie bez ostrzeżenia kod:

while (*src != '\0')
{
    *dst = *src;
    ++src;
    ++dst;
}
*dst = '\0';

(Tak, wiem srci dstbędą miały różne wartości końcowe w tych alternatywnych rozwiązaniach, ale ponieważ strcpynatychmiast powraca po pętli, w tym przypadku nie ma to znaczenia).

Wydaje się, że celem przyrostu postfiksowego jest uczynienie kodu możliwie najkrótszym. Po prostu nie rozumiem, o co powinniśmy dążyć. Jeśli pierwotnie chodziło o wydajność, czy nadal jest aktualne?

fredoverflow
źródło
7
Wariant z postfiksem jest najczęściej używany, więc jeśli masz zamiar zrobić przeciwko niemu, lepiej być dość mocnym przypadkiem. A twoim pierwszym przykładem jest trochę słaby człowiek, ponieważ podejrzewam, że niewiele osób faktycznie koduje tę strcpymetodę w ten sposób (z powodów, o których już wspomniałeś).
Robert Harvey
4
Dlaczego w ogóle mają?
James McNellis
31
Gdybyśmy usunęli wszystko z języka, który mógłby wprowadzić w błąd programistów, nie mielibyśmy zbyt wielu funkcji. Fakt, że coś nie jest dla ciebie przydatne lub tylko bardzo rzadko przydatne, nie oznacza, że ​​należy go wyciąć. Jeśli to nie jest już istotne, nie używaj go; koniec.
Cody Gray
4
Tak, ale nigdy nie da się tego zrobićint c = 0; c++;
Biff MaGriff
4
@Cody: Ponieważ niektóre rzeczy są zarówno mylące, jak i przydatne. Nie jest zdezorientowany samą inkrementacją, myli się co do jego przydatności . Nie powinniśmy mieć rzeczy, które są bezużyteczne, zagmatwane lub nie.
GManNickG

Odpowiedzi:

23

Chociaż kiedyś miało to wpływ na wydajność, myślę, że prawdziwym powodem jest czyste wyrażanie intencji. Prawdziwe pytanie brzmi, czy coś while (*d++=*s++);jasno wyraża intencję, czy nie. IMO to robi i uważam, że alternatywy, które oferujesz, są mniej jasne - ale może to (łatwo) wynikać z faktu, że spędziłeś dekady przyzwyczajając się do tego, jak się to robi. Nauka C od K&R (ponieważ w tamtym czasie prawie nie było innych książek na temat C) prawdopodobnie również pomaga.

Do pewnego stopnia prawdą jest, że zwięzłość była ceniona w znacznie większym stopniu w starszym kodzie. Osobiście uważam, że była to w dużej mierze dobra rzecz - zrozumienie kilku linii kodu jest zwykle dość trywialne; trudne jest zrozumienie dużych fragmentów kodu. Testy i badania wielokrotnie wykazały, że dopasowanie całego kodu na ekranie jest ważnym czynnikiem w zrozumieniu kodu. W miarę rozszerzania się ekranów wydaje się, że tak jest, więc zachowanie kodu (rozsądnie) zwięzłe pozostaje cenne.

Oczywiście można przesadzić, ale nie sądzę, że tak jest. W szczególności myślę, że przesadza, gdy zrozumienie jednego wiersza kodu staje się niezwykle trudne lub czasochłonne - szczególnie gdy zrozumienie mniejszej liczby wierszy kodu wymaga więcej wysiłku niż zrozumienie większej liczby wierszy. Jest to częste w Lisp i APL, ale nie wydaje się (przynajmniej dla mnie) tak w tym przypadku.

Mniej martwię się ostrzeżeniami kompilatora - z mojego doświadczenia wynika, że ​​wiele kompilatorów emituje całkowicie absurdalne ostrzeżenia dość regularnie. Chociaż z pewnością uważam, że ludzie powinni rozumieć swój kod (i wszelkie ostrzeżenia, które może on wygenerować), przyzwoity kod, który powoduje ostrzeżenie w niektórych kompilatorach, niekoniecznie jest zły. Trzeba przyznać, że początkujący nie zawsze wiedzą, co mogą bezpiecznie zignorować, ale nie pozostajemy początkującymi na zawsze i nie musimy kodować tak jak my.

Jerry Coffin
źródło
12
+1 za wskazanie, że niektórym z nas łatwiej jest odczytać zwięzłą wersję . :-)
1
Jeśli nie lubisz niektórych swoich ostrzeżeń kompilatora (a jest ich więcej niż kilka mogłem zrobić bez - poważnie, mam na myśli rodzaju if(x = y), przysięgam!) Należy dostosować ostrzeżenia opcje kompilatora, tak przypadkiem nie przegap ostrzeżenia, które lubisz.
4
@Brooks Moses: Jestem o wiele mniej entuzjastycznie nastawiony. Nie lubię zanieczyszczać mojego kodu, aby uzupełnić braki kompilatora.
Jerry Coffin
1
@Jerry, @Chris: Ta adnotacja jest przeznaczona dla przypadku, w którym uważasz, że ostrzeżenie jest zwykle dobrą rzeczą, ale nie chcesz, aby dotyczyło konkretnej linii, która robi coś niezwykłego. Jeśli nigdy nie chcesz, aby to ostrzeżenie było uważane za wadę, użyj -Wno-X, ale jeśli chcesz zrobić tylko jeden wyjątek od niego i chcesz, aby ostrzeżenie obowiązywało wszędzie indziej, jest to o wiele bardziej zrozumiałe niż stosowanie tajemnych skoków do obręczy, o których Chris mówi .
1
@ Brooks: podwójne nawiasy i (void)xwyciszanie nieużywanych ostrzeżeń są dość dobrze znanymi idiomami służącymi do wyciszania innych użytecznych ostrzeżeń. Adnotacja wygląda trochę jak pragmas, w najlepszym razie nieprzenośna: /
Matthieu M.
32

To była sprawa sprzętowa


Ciekawe, że zauważysz. Przyrost Postfiksa jest zapewne z wielu zupełnie dobrych powodów, ale podobnie jak wiele rzeczy w C, jego popularność można przypisać pochodzeniu języka.

Mimo że C został opracowany na wielu wczesnych i słabo wyposażonych komputerach, C i Unix po raz pierwszy uderzyły względnie długo dzięki modelom PDP-11 zarządzanym przez pamięć. Były to stosunkowo ważne komputery w ich czasach, a Unix był zdecydowanie lepszy - wykładniczo lepszy - niż pozostałe 7 kiepskich systemów operacyjnych dostępnych dla -11.

Tak się składa, że ​​na PDP-11

*--p

i

*p++

... zostały zaimplementowane sprzętowo jako tryby adresowania. (Poza tym *p, ale żadna inna kombinacja.) Na tych wczesnych maszynach, wszystkie poniżej 0,001 GHz, zapisanie instrukcji lub dwóch w pętli musiało prawie czekać na sekundę, na minutę lub na wyjście -lunch różnica. Nie dotyczy to konkretnie przyrostu postincrement, ale pętla ze wskaźnikiem zwiększania postu mogła być znacznie lepsza niż indeksowanie.

W konsekwencji wzorce projektowe, ponieważ idiomy C, które stały się makrami C ment.

To coś jak deklarowanie zmiennych zaraz po {... nie od czasu, gdy C89 był aktualny, było to wymaganie, ale teraz jest to wzorzec kodu.

Aktualizacja: Oczywiście głównym powodem *p++tego języka jest to, że tak właśnie chce się robić. Popularność wzorca kodu została wzmocniona przez popularny sprzęt, który pojawił się i pasował do już istniejącego wzorca języka zaprojektowanego nieco przed pojawieniem się PDP-11.

W dzisiejszych czasach nie ma znaczenia, jakiego wzorca używasz lub czy korzystasz z indeksowania, a my i tak zwykle programujemy na wyższym poziomie, ale to musiało mieć duże znaczenie na tych maszynach 0,001 GHz i używanie niczego innego *--xlub *x++oznaczałoby, że jesteś nie „dostałem” PDP-11, a ludzie mogliby podejść do ciebie i powiedzieć „czy wiesz, że ...” :-) :-)

DigitalRoss
źródło
25
Wikipedia mówi: „A (fałsz) mit ludowej jest to, że zestaw instrukcji architektury PDP-11 wpływa na idiomatyczne użycie języka programowania C inkrementacji i dekrementacji tryby adresowania PDP-11 za odpowiadają. −−iI i++konstruktów w C. Jeśli ii jobie były zmiennymi rejestrowymi, wyrażenie, które *(−−i) = *(j++)można by skompilować do pojedynczej instrukcji maszynowej. [...] Dennis Ritchie jednoznacznie zaprzecza ludowemu mitowi. [Rozwój języka C ] ”
fredoverflow
3
Huh (z linku podanego w komentarzu FredOverflow): „Ludzie często zgadują, że zostali stworzeni do korzystania z trybów adresów auto-inkrementacji i auto-dekrementacji zapewnianych przez DEC PDP-11, na których C i Unix po raz pierwszy stały się popularne. Jest to historycznie niemożliwe, ponieważ nie było PDP-11, gdy opracowano B. PDP-7 miał jednak kilka komórek pamięci z „automatyczną inkrementacją”, z tą właściwością, że pośrednie odwołanie do pamięci przez nie zwiększyło komórkę. zasugerował Thompsonowi takich operatorów; uogólnienie polegające na uczynieniu ich jednocześnie przedrostkami i postfiksami było jego własnym ”.
3
DMR powiedział tylko, że PDP-11 nie był powodem, dla którego został dodany do C. Ja też to powiedziałem. Powiedziałem, że wykorzystano go do przewagi nad PDP-11 i właśnie z tego powodu stał się popularnym wzorcem projektowym. Jeśli Wikipedia zaprzecza temu , to po prostu się mylą, ale nie czytam tego w ten sposób. Jest źle sformułowany na tej stronie W.
DigitalRoss
3
@FredOverflow. Myślę, że źle zrozumiałeś, czym jest „mit ludowy”. Mitem jest to, że C został zaprojektowany do wykorzystania tych 11 operacji, nie dlatego, że one nie istnieją i nie, że wzorzec kodu nie stał się popularny, ponieważ wszyscy wiedzieli, że mapowali do studni 11. W przypadku, gdy nie jest to jasne: PDP-11 naprawdę naprawdę implementuje te dwa tryby sprzętowo dla prawie każdej instrukcji jako tryb adresowania, i tak naprawdę była to pierwsza ważna maszyna C.
DigitalRoss
2
„na tych maszynach 0,001 GHz musiało to mieć duże znaczenie”. Hmm, nie lubię tego mówić, ponieważ to się ze mną spotyka, ale miałem instrukcje Motoroli i Intela dla moich procesorów, aby sprawdzić rozmiar instrukcji i liczbę cykli, które wykonały. Znalezienie najmniejszych kodów łącznie z możliwością dodania jeszcze jednej funkcji do bufora wydruku i zapisanie kilku cykli oznaczało, że port szeregowy mógł nadążyć za jakimś protokołem takim jak nowo opracowany MIDI. C odciążył trochę wybieranie nitów, zwłaszcza gdy kompilatory uległy poprawie, ale nadal ważne było, aby wiedzieć, jaki był najbardziej wydajny sposób pisania kodu.
Tin Man
14

Prefiks, postfiks --i ++operatory zostały wprowadzone w języku B (poprzednik C) przez Kena Thompsona - i nie, nie były inspirowane PDP-11, który wtedy nie istniał.

Cytując z „The Development of the C Language” Dennisa Ritchiego :

Thompson poszedł o krok dalej, wymyślając ++i--operatorzy, którzy zwiększają lub zmniejszają; ich pozycja przedrostka lub postfiksu określa, czy zmiana występuje przed czy po zanotowaniu wartości argumentu. Nie były w najwcześniejszych wersjach B, ale pojawiły się po drodze. Ludzie często zgadują, że zostali stworzeni do korzystania z trybów adresowych auto-inkrementacji i auto-dekrementacji zapewnianych przez DEC PDP-11, w którym C i Unix stały się popularne. Jest to historycznie niemożliwe, ponieważ nie było PDP-11, gdy B został opracowany. PDP-7 miał jednak kilka komórek pamięci z „auto-przyrostem”, z tą właściwością, że pośrednie odwołanie do pamięci poprzez nie zwiększało komórkę. Ta funkcja prawdopodobnie sugerowała Thompsonowi takich operatorów; uogólnienie polegające na tym, aby były zarówno przedrostkiem, jak i postfiksem, było jego własnym. W rzeczy samej,++xbył mniejszy niż x=x+1.

Keith Thompson
źródło
8
Nie, nie jestem spokrewniony z Kenem Thompsonem.
Keith Thompson
Dzięki za to bardzo interesujące odniesienie! Potwierdza to mój cytat z oryginalnego K&R, który sugerował raczej cel kompaktowości niż technicznej optymalizacji. Nie wiedziałem jednak, że operatorzy ci już istnieli w B.
Christophe
@BenPen O ile mi wiadomo, nie ma zasady zamykania pytań, jeśli istnieje duplikat na innej stronie SE, o ile oba pytania pasują do ich odpowiednich stron.
Ordous,
Ciekawe ... Programista z pewnością nie jest tak blisko pytania.
BenPen
@BenPen: Akceptowana odpowiedź na pytanie o przepełnienie stosu cytuje już to samo źródło, co ja (choć nie tak bardzo).
Keith Thompson,
6

Oczywistym powodem istnienia operatora postinkrementu jest to, że nie musisz pisać wyrażeń takich jak (++x,x-1)lub (x+=1,x-1)wszędzie w tym miejscu ani bezużytecznie oddzielnych trywialnych instrukcji z łatwymi do zrozumienia skutkami ubocznymi w wielu instrukcjach. Oto kilka przykładów:

while (*s) x+=*s++-'0';

if (x) *s++='.';

Ilekroć czytanie lub pisanie łańcucha w kierunku do przodu, zwiększenie wstępne, a nie wstępne, jest prawie zawsze naturalną operacją. Prawie nigdy nie spotkałem się z rzeczywistymi zastosowaniami do wstępnego zwiększania, a jedynym głównym zastosowaniem, jakie znalazłem dla wstępnego zwiększania, jest przekształcanie liczb na ciągi znaków (ponieważ ludzkie systemy zapisu zapisują liczby wstecz).

Edycja: W rzeczywistości nie byłoby to takie brzydkie; zamiast (++x,x-1)ciebie możesz oczywiście użyć ++x-1lub (x+=1)-1. Nadal x++jest bardziej czytelny.

R ..
źródło
Musisz być ostrożny z tymi wyrażeniami, ponieważ modyfikujesz i odczytujesz zmienną między dwoma punktami sekwencji. Kolejność, w jakiej wyrażenia te są oceniane, nie jest gwarantowana.
@Snowman: Który? W mojej odpowiedzi nie widzę takich problemów.
R ..
jeśli masz coś takiego (++x,x-1)(może być wywołanie funkcji?)
@Snowman: To nie jest wywołanie funkcji. Jest to operator przecinka C, który jest sekwencją sekwencyjną, nawiasowaną w celu kontrolowania pierwszeństwa. Wynik jest taki sam jak x++w większości przypadków (jeden wyjątek: xjest typem bez znaku z rangą niższą niż, inta wartość początkowa xto maksymalna wartość tego typu).
R ..
Ach, podstępny operator przecinków ... kiedy przecinek nie jest przecinkiem. Nawiasy i rzadkość operatora przecinka mnie wyrzuciły. Nieważne!
4

PDP-11 oferował operacje inkrementacji i dekrementacji w zestawie instrukcji. To nie były instrukcje. Były to modyfikatory instrukcji, które pozwoliły ci określić, że chcesz wartość rejestru przed jego zwiększeniem lub po jego zmniejszeniu.

Oto krok kopiowania słowa w języku maszynowym:

movw (r1)++,(r2)++

które przenosiło słowo z jednego miejsca do drugiego, wskazywane przez rejestry, a rejestry byłyby gotowe, aby to zrobić ponownie.

Ponieważ C został zbudowany i dla PDP-11, wiele przydatnych koncepcji znalazło drogę do C. C miało być użytecznym zamiennikiem języka asemblera. W celu symetrii dodano wstępny wzrost i późniejszy spadek.

BobDalgleish
źródło
To genialne modyfikatory ... To sprawia, że ​​chcę nauczyć się składania PDP-11. BTW, czy masz odniesienie do „symetrii”? To ma sens, ale to historia, czasem sens nie był kluczem. LOL
BenPen
2
Znane również jako tryby adresowania . PDP-11 zapewniał przyrostową i wstępną dekrementację, która ładnie sparowała się razem dla operacji stosu.
Erik Eidt,
1
PDP-8 zapewniał pośrednią pamięć po inkrementacji, ale nie odpowiadał żadnej operacji wstępnej dekrecji. W przypadku pamięci z rdzeniem magnetycznym odczytanie słowa pamięci wymazało go (!), Więc procesor użył funkcji zapisu wstecznego, aby przywrócić tylko przeczytane słowo. Zastosowanie przyrostu przed odpisem nastąpiło raczej naturalnie i stało się możliwe bardziej wydajne przesuwanie bloku.
Erik Eidt,
3
Niestety, PDP-11 nie zainspirował tych operatorów. Nie istniał jeszcze, kiedy Ken Thompson je wynalazł. Zobacz moją odpowiedź .
Keith Thompson
3

Wątpię, czy kiedykolwiek było to naprawdę konieczne. O ile mi wiadomo, nie można go skompilować do niczego bardziej kompaktowego na większości platform niż użycie wstępnej inkrementacji, tak jak to robiłeś, w pętli. Po prostu w momencie jego tworzenia zwięzłość kodu była ważniejsza niż przejrzystość kodu.

Nie oznacza to, że przejrzystość kodu nie jest (lub nie była) ważna, ale podczas pisania na modemie o niskiej prędkości transmisji (wszystko, co mierzysz w prędkości transmisji, jest powolne), gdzie każde naciśnięcie klawisza musiało mainframe, a następnie odbijane z powrotem o jeden bajt za jednym razem z pojedynczym bitem używanym do sprawdzania parzystości, nie trzeba było dużo pisać.

To trochę jak & i operator mający niższy priorytet niż ==

Został zaprojektowany z najlepszymi intencjami (wersja bez zwarć && i ||), ale teraz codziennie myli programistów i prawdopodobnie nigdy się nie zmieni.

W każdym razie, jest to tylko odpowiedź, ponieważ myślę, że nie ma dobrej odpowiedzi na twoje pytanie, ale prawdopodobnie okaże się, że błędny koder jest bardziej guru niż ja.

--EDYTOWAĆ--

Powinienem zauważyć, że posiadanie obu jest niezwykle przydatne, po prostu zaznaczam, że nie zmieni to, czy komuś się to podoba, czy nie.


źródło
To nawet nie jest prawdą. W żadnym momencie „zwięzłość kodu” nie była ważniejsza od przejrzystości.
Cody Gray
Cóż, twierdzę, że był to punkt skupienia. Masz rację choć to z pewnością nigdy nie było bardziej istotne niż czytelności kodu ... dla większości ludzi.
6
Cóż, definicja „zwięzłego” jest „gładko elegancka: wypolerowana” lub „za pomocą kilku słów: pozbawiona zbędności”, z których oba są na ogół dobre podczas pisania kodu. „Terse” nie oznacza „niejasne, trudne do odczytania i zaciemnione”, jak wielu ludzi myśli.
James McNellis
@James: Chociaż masz rację, myślę, że większość ludzi ma na myśli drugą definicję „zwięzłego”, gdy używa się go w kontekście programowania: „używając kilku słów; pozbawiony nadmiaru”. Zgodnie z tą definicją z pewnością łatwiej jest wyobrazić sobie sytuacje, w których występuje kompromis między zwięzłością i ekspresją określonego fragmentu kodu. Oczywiście właściwą rzeczą do zrobienia podczas pisania kodu jest to samo, co przy mówieniu: zrób wszystko tak krótko, jak trzeba, ale nie krócej. Docenianie zwięzłości nad ekspresją może prowadzić do zaciemnienia kodu, ale z pewnością nie powinno.
Cody Grey
1
przewiń o 40 lat i wyobraź sobie, że musiałeś zmieścić swój program na tym bębnie, który pomieści tylko około 1000 znaków, lub napisać, że kompilator może pracować tylko z 4 tysiącami bajtów pamięci RAM. Czasami zwięzłość i jasność nie stanowiły nawet problemu. Musiałeś być zwięzły, na tej planecie nie istniał komputer, który mógłby przechowywać, uruchamiać lub kompilować twój nie-zwięzły program.
nos
3

Lubię aperator postfiksowy podczas pracy ze wskaźnikami (niezależnie od tego, czy je usuwam), ponieważ

p++

czyta bardziej naturalnie jako „przejdź do następnego miejsca” niż ekwiwalent

p += 1

co z pewnością musi pomylić początkujących za pierwszym razem, gdy widzą, że jest używany ze wskaźnikiem, gdzie sizeof (* p)! = 1.

Czy jesteś pewien, że poprawnie podajesz problem zamieszania? Nie chodzi o to, że początkujący myli fakt, że operator postfix ++ ma wyższy priorytet niż operator dereferencji *, dzięki czemu

*p++

parsuje jako

*(p++)

i nie

(*p)++

jak niektórzy mogą się spodziewać?

(Myślisz, że to źle? Zobacz Deklaracje czytania C. )

Eric Giguere
źródło
2

Do subtelnych elementów dobrego programowania należą lokalizacja i minimalizm :

  • umieszczenie zmiennych w minimalnym zakresie użycia
  • używanie const, gdy dostęp do zapisu nie jest wymagany
  • itp.

W tym samym duchu, x++może być postrzegane jako sposób na zlokalizowanie odniesienie do bieżącej wartości xchwilę natychmiast wskazuje, że masz - przynajmniej na razie - wykończony tej wartości i chcą, aby przejść do następnego (ważne, czy xjest intlub wskaźnik lub iterator). Przy odrobinie wyobraźni można to porównać do pozostawienia starej wartości / pozycji x„poza zakresem” natychmiast po tym, gdy nie jest już potrzebna, i przejścia do nowej wartości. Dokładny punkt, w którym przejście jest możliwe, jest zaznaczony postfiksem++ . Implikacja, że ​​jest to prawdopodobnie ostateczne użycie „starego”, xmoże być cennym wglądem w algorytm, pomagając programiście w skanowaniu otaczającego kodu. Oczywiście postfiks++ może zmusić programistę do poszukiwania zastosowań nowej wartości, która może, ale nie musi być dobra, w zależności od tego, kiedy ta nowa wartość jest rzeczywiście potrzebna, więc jest to aspekt „sztuki” lub „rzemiosła” programowania w celu ustalenia, co więcej pomocny w danych okolicznościach.

Chociaż wielu początkujących może być zdezorientowanych jakąś funkcją, warto to zrównoważyć z długofalowymi korzyściami: początkujący nie pozostają długo dla początkujących.

Tony
źródło
2

Wow, wiele odpowiedzi niezupełnie trafnych (jeśli mogę być tak odważny), i proszę wybacz mi, jeśli wskazuję na oczywiste - szczególnie w świetle twojego komentarza, aby nie zwracać uwagi na semantykę, ale oczywiste (z punktu widzenia Stroustrupa, jak sądzę) nie wydaje się jeszcze opublikowany! :)

Postfiks x++tworzy wartość tymczasową, która jest przekazywana w wyrażeniu „w górę”, podczas gdy zmienna xjest następnie zwiększana.

Prefiks ++xnie tworzy obiektu tymczasowego, ale zwiększa „x” i przekazuje wynik do wyrażenia.

Wartością jest wygoda dla tych, którzy znają operatora.

Na przykład:

int x = 1;
foo(x++); // == foo(1)
// x == 2: true

x = 1;
foo(++x); // == foo(2)
// x == 2: true

Oczywiście wyniki tego przykładu można uzyskać z innego równoważnego (i być może mniej skośnego) kodu.

Dlaczego więc mamy operatora Postfiksa? Chyba dlatego, że jest to idiom, który przetrwał, pomimo zamieszania, które oczywiście powoduje. To starsza konstrukcja, która kiedyś była postrzegana jako posiadająca wartość, chociaż nie jestem pewien, czy postrzegana wartość była zarówno pod względem wydajności, jak i wygody. Wygoda nie została utracona, ale myślę, że wzrosła uznanie dla czytelności, co skutkuje zakwestionowaniem celu operatora.

Czy potrzebujemy już operatora Postfiksa? Prawdopodobnie nie. Jego wynik jest mylący i stwarza barierę dla zrozumienia. Niektórzy dobrzy koderzy z pewnością od razu dowiedzą się, gdzie może być przydatny, często w miejscach, w których ma on szczególnie „perłowy” wygląd. Kosztem tego piękna jest czytelność.

Zgadzam się, że wyraźny kod ma zalety nad zwięzłością, w czytelności, zrozumiałości i łatwości konserwacji. Tego chcą dobrzy projektanci i menedżerowie.

Jest jednak pewne piękno, które niektórzy programiści rozpoznają, co zachęca ich do tworzenia kodu przy użyciu rzeczy wyłącznie dla pięknej prostoty - takich jak operator postfiksów - o kodzie, o którym inaczej by nigdy by nie pomyśleli - lub które uważałyby za stymulujące intelektualnie. Jest w tym coś konkretnego, co czyni ją piękniejszą, jeśli nie bardziej pożądaną, pomimo litości.

Innymi słowy, niektórzy ludzie uważają, że jest while (*dst++ = *src++);to po prostu piękniejsze rozwiązanie, coś, co zapiera dech w piersiach dzięki swojej prostocie, tak jakby to był pędzel do płótna. To, że musisz zrozumieć język, aby docenić piękno, tylko zwiększa jego wspaniałość.

Brian M. Hunt
źródło
3
+1 „niektórzy uważają, że while (* dst ++ = * src ++); po prostu być piękniejszym rozwiązaniem”. Zdecydowanie. Ludzie, którzy nie rozumieją języka asemblera, tak naprawdę nie rozumieją, jak elegancki może być C, ale ta konkretna instrukcja kodu jest świecącą gwiazdą.
Tin Man
„Czy potrzebujemy już operatora Postfiksa? Prawdopodobnie nie”. - Nie mogę przestać myśleć o maszynach Turinga tutaj. Czy kiedykolwiek potrzebowaliśmy zmiennych automatycznych, forinstrukcji, do cholery, nawet while(przecież mamy goto)?
@Bernd: Ha! Wyobraź sobie zamknięcia! Domknięcia! Skandaliczny! lol
Brian M. Hunt
Domknięcia! Niedorzeczne. Po prostu daj mi taśmę! Poważnie, miałem na myśli to, że nie uważam / potrzebuję / cechy powinno być głównym kryterium decyzyjnym (chyba że chcesz zaprojektować, powiedzmy, seplenienie). IME Używam (nie, potrzebuję ) operatorów postfiksów prawie wyłącznie i tylko rzadko potrzebuję prefiksu jeden.
2

Spójrzmy na oryginalne uzasadnienie Kernighana i Ritchiego (oryginalne K&R str. 42 i 43):

Niezwykłe jest to, że ++ i - mogą być używane jako przedrostek lub postfiks. (...) W kontekście, w którym nie jest pożądana żadna wartość (..) wybierz prefiks lub postfiks zgodnie z gustem. Ale są sytuacje, w których jeden lub drugi jest specjalnie wezwany.

Tekst kontynuuje kilka przykładów, które wykorzystują przyrosty w indeksie, z wyraźnym celem napisania „ bardziej zwartego ” kodu. Dlatego powodem tych operatorów jest wygoda bardziej zwartego kodu.

Trzy przykłady podane ( squeeze(), getline()i strcat()) używać tylko postfix w wyrażeniach korzystających indeksowanie. Autorzy porównują kod z dłuższą wersją, która nie używa osadzonych przyrostów. To potwierdza, że ​​nacisk kładziony jest na zwartość.

K&R podkreśla na stronie 102 wykorzystanie tych operatorów w połączeniu z dereferencją wskaźnika (np. *--pI *p--). Nie podano żadnego kolejnego przykładu, ale ponownie wyjaśniają, że korzyścią jest zwartość.

Prefiks jest na przykład bardzo często używany podczas zmniejszania indeksu lub wskaźnika, patrząc od końca.

 int i=0; 
 while (i<10) 
     doit (i++);  // calls function for 0 to 9  
                  // i is 10 at this stage
 while (--i >=0) 
     doit (i);    // calls function from 9 to 0 
Christophe
źródło
1

Mam zamiar zgadzam się z założenia, że ++p, p++jest w jakiś sposób nieczytelny lub niejasne. Jeden oznacza „przyrost p, a następnie odczyt p”, drugi oznacza „odczyt p, a następnie przyrost p”. W obu przypadkach operator kodu jest dokładnie tam, gdzie jest w objaśnieniu kodu, więc jeśli wiesz, co to ++znaczy, wiesz, co oznacza wynikowy kod.

Możesz utworzyć zaciemniony kod przy użyciu dowolnej składni, ale nie widzę tu przypadku, który p++/ ++pjest z natury zaciemniony.

filozofodad
źródło
1

pochodzi z POV inżyniera elektryka:

istnieje wiele procesorów, które mają wbudowane operatory post-inkrementacji i pre-dekrementacji w celu utrzymania stosu Last-First-First-Out (LIFO).

może to być:

 float stack[4096];
 int stack_pointer = 0;

 ... 

 #define push_stack(arg) stack[stack_pointer++] = arg;
 #define pop_stack(arg) arg = stack[--stack_pointer];

 ...

nie wiem, ale to jest powód, dla którego spodziewałbym się zobaczyć zarówno operatorów przyrostowych przedrostków, jak i postfiksów.

Robert Bristol-Johnson
źródło
1

Aby podkreślić punkt Christophe'a na temat zwartości, chcę pokazać wam trochę kodu z V6. Jest to część oryginalnego kompilatora C z http://minnie.tuhs.org/cgi-bin/utree.pl?file=V6/usr/source/c/c00.c :

/*
 * Look up the identifier in symbuf in the symbol table.
 * If it hashes to the same spot as a keyword, try the keyword table
 * first.  An initial "." is ignored in the hash.
 * Return is a ptr to the symbol table entry.
 */
lookup()
{
    int ihash;
    register struct hshtab *rp;
    register char *sp, *np;

    ihash = 0;
    sp = symbuf;
    if (*sp=='.')
        sp++;
    while (sp<symbuf+ncps)
        ihash =+ *sp++;
    rp = &hshtab[ihash%hshsiz];
    if (rp->hflag&FKEYW)
        if (findkw())
            return(KEYW);
    while (*(np = rp->name)) {
        for (sp=symbuf; sp<symbuf+ncps;)
            if (*np++ != *sp++)
                goto no;
        csym = rp;
        return(NAME);
    no:
        if (++rp >= &hshtab[hshsiz])
            rp = hshtab;
    }
    if(++hshused >= hshsiz) {
        error("Symbol table overflow");
        exit(1);
    }
    rp->hclass = 0;
    rp->htype = 0;
    rp->hoffset = 0;
    rp->dimp = 0;
    rp->hflag =| xdflg;
    sp = symbuf;
    for (np=rp->name; sp<symbuf+ncps;)
        *np++ = *sp++;
    csym = rp;
    return(NAME);
}

Jest to kod, który został przekazany w samizdat jako edukacja w dobrym stylu, a jednak nigdy nie napisalibyśmy go dzisiaj. Zobacz, ile efektów ubocznych zostało zapakowanych ifi jakie są whilewarunki, i odwrotnie, w jaki sposób obie forpętle nie mają wyrażenia przyrostowego, ponieważ odbywa się to w ciele pętli. Zobacz, jak bloki są owinięte w nawiasy klamrowe tylko wtedy, gdy jest to absolutnie konieczne.

Częścią tego była z pewnością ręczna mikrooptymalizacja, której oczekujemy od kompilatora dzisiaj, ale wysunę też hipotezę, że gdy cały interfejs do komputera to pojedynczy szklany serwer 80x25, potrzebujesz kodu być tak gęsty, jak to możliwe, abyś mógł zobaczyć więcej z nich jednocześnie.

(Korzystanie z globalnych buforów do wszystkiego jest prawdopodobnie kacem po wycięciu zębów w języku asemblera).

zwol
źródło
Postępuj ostrożnie: „ihash = + * sp ++;” W pewnym momencie C miał nie tylko + =, ale także = + operator. Dziś = = jest operatorem przypisania, po którym następuje jednoargumentowy plus, który nic nie robi, więc jest równoważny z „ihash = * sp; sp + = 1;”
gnasher729,
@ gnasher729 Tak, a później ma rp->hflag =| xdflg, co moim zdaniem będzie błędem składniowym nowoczesnego kompilatora. „W pewnym momencie” jest właśnie V6, jak to się dzieje; przejście do +=formy miało miejsce w wersji 7. (Kompilator V6 akceptuje tylko=+ , jeśli poprawnie czytam ten kod - patrz symbol () w tym samym pliku.)
zwolnić
-1

Być może powinieneś pomyśleć również o kosmetykach.

while( i++ )

prawdopodobnie „wygląda lepiej” niż

while( i+=1 )

Z jakiegoś powodu operator post-increment wygląda bardzo atrakcyjnie. Jest krótki, słodki i zwiększa wszystko o 1. Porównaj go z przedrostkiem:

while( ++i )

„patrzy wstecz”, prawda? Nigdy nie widzisz znaku plus PIERWSZE w matematyce, z wyjątkiem sytuacji, gdy ktoś jest głupi, podając coś pozytywnego ( +0lub coś takiego). Jesteśmy przyzwyczajeni do oglądania OBIEKT + COŚ

Czy to ma coś wspólnego z ocenianiem? A, A +, A ++? Mogę powiedzieć, że na początku byłem dość serowy, że ludzie Python usunęli operator++ich język!

Bobobobo
źródło
1
Twoje przykłady nie robią tego samego. i++Zwraca pierwotną wartość i, a i+=1i ++iprzywrócić pierwotną wartość iplus 1. Ponieważ używasz wartości zwracanych do kontroli przepływu, to sprawy.
David Thornley,
Tak, masz rację. Ale patrzę tylko na czytelność tutaj.
bobobobo,
-2

Licząc wstecz od 9 do 0 włącznie:

for (unsigned int i=10; i-- > 0;)  {
    cout << i << " ";
}

Lub odpowiednik pętli while.


źródło
1
Jak o for (unsigned i = 9; i <= 9; --i)?
fredoverflow