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 src
i dst
będą miały różne wartości końcowe w tych alternatywnych rozwiązaniach, ale ponieważ strcpy
natychmiast 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?
strcpy
metodę w ten sposób (z powodów, o których już wspomniałeś).int c = 0; c++;
Odpowiedzi:
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.
źródło
if(x = y)
, przysięgam!) Należy dostosować ostrzeżenia opcje kompilatora, tak przypadkiem nie przegap ostrzeżenia, które lubisz.-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 .(void)x
wyciszanie nieużywanych ostrzeżeń są dość dobrze znanymi idiomami służącymi do wyciszania innych użytecznych ostrzeżeń. Adnotacja wygląda trochę jakpragmas
, w najlepszym razie nieprzenośna: /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
i
... 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
*--x
lub*x++
oznaczałoby, że jesteś nie „dostałem” PDP-11, a ludzie mogliby podejść do ciebie i powiedzieć „czy wiesz, że ...” :-) :-)źródło
−−i
Ii++
konstruktów w C. Jeślii
ij
obie 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 ] ”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 :
źródło
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: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-1
lub(x+=1)-1
. Nadalx++
jest bardziej czytelny.źródło
(++x,x-1)
(może być wywołanie funkcji?)x++
w większości przypadków (jeden wyjątek:x
jest typem bez znaku z rangą niższą niż,int
a wartość początkowax
to maksymalna wartość tego typu).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:
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.
źródło
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
Lubię aperator postfiksowy podczas pracy ze wskaźnikami (niezależnie od tego, czy je usuwam), ponieważ
czyta bardziej naturalnie jako „przejdź do następnego miejsca” niż ekwiwalent
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
parsuje jako
i nie
jak niektórzy mogą się spodziewać?
(Myślisz, że to źle? Zobacz Deklaracje czytania C. )
źródło
Do subtelnych elementów dobrego programowania należą lokalizacja i minimalizm :
W tym samym duchu,
x++
może być postrzegane jako sposób na zlokalizowanie odniesienie do bieżącej wartościx
chwilę natychmiast wskazuje, że masz - przynajmniej na razie - wykończony tej wartości i chcą, aby przejść do następnego (ważne, czyx
jestint
lub wskaźnik lub iterator). Przy odrobinie wyobraźni można to porównać do pozostawienia starej wartości / pozycjix
„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”,x
moż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.
źródło
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 zmiennax
jest następnie zwiększana.Prefiks
++x
nie 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:
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ść.źródło
for
instrukcji, do cholery, nawetwhile
(przecież mamygoto
)?Spójrzmy na oryginalne uzasadnienie Kernighana i Ritchiego (oryginalne K&R str. 42 i 43):
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()
istrcat()
) 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.
*--p
I*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.
źródło
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++
/++p
jest z natury zaciemniony.źródło
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ć:
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.
źródło
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 :
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
if
i jakie sąwhile
warunki, i odwrotnie, w jaki sposób obiefor
pę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).
źródło
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.)Być może powinieneś pomyśleć również o kosmetykach.
prawdopodobnie „wygląda lepiej” niż
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:
„patrzy wstecz”, prawda? Nigdy nie widzisz znaku plus PIERWSZE w matematyce, z wyjątkiem sytuacji, gdy ktoś jest głupi, podając coś pozytywnego (
+0
lub 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!źródło
i++
Zwraca pierwotną wartośći
, ai+=1
i++i
przywrócić pierwotną wartośći
plus 1. Ponieważ używasz wartości zwracanych do kontroli przepływu, to sprawy.Licząc wstecz od 9 do 0 włącznie:
Lub odpowiednik pętli while.
źródło
for (unsigned i = 9; i <= 9; --i)
?