W tym wątku przyjrzymy się przykładom dobrych zastosowań goto
w C i C ++. Inspiruje się odpowiedzią, na którą ludzie głosowali, ponieważ myśleli, że żartuję.
Podsumowanie (zmieniono etykietę z oryginalnej, aby intencja była jeszcze jaśniejsza):
infinite_loop:
// code goes here
goto infinite_loop;
Dlaczego jest lepszy niż alternatywy:
- To jest specyficzne.
goto
jest konstrukcją języka, która powoduje bezwarunkową gałąź. Alternatywy zależą od użycia struktur obsługujących gałęzie warunkowe, ze zdegenerowanym, zawsze prawdziwym warunkiem. - Etykieta dokumentuje zamiar bez dodatkowych komentarzy.
- Czytelnik nie musi skanować kodu interweniującego w poszukiwaniu wczesnych
break
(chociaż haker bez zasad nadal może przeprowadzić symulacjęcontinue
z wczesnymgoto
).
Zasady:
- Udawaj, że gotofobowie nie wygrali. Rozumie się, że powyższego nie można używać w prawdziwym kodzie, ponieważ jest to sprzeczne z ustalonym idiomem.
- Załóżmy, że wszyscy słyszeliśmy o „Goto uważane za szkodliwe” i wiemy, że goto można wykorzystać do napisania kodu spaghetti.
- Jeśli nie zgadzasz się z przykładem, skrytykuj go wyłącznie pod względem technicznym („Ponieważ ludzie nie lubią goto” nie jest technicznym powodem).
Zobaczmy, czy możemy mówić o tym jak dorośli.
Edytować
To pytanie wydaje się zakończone. To wygenerowało kilka wysokiej jakości odpowiedzi. Dziękuję wszystkim, zwłaszcza tym, którzy poważnie potraktowali mój przykład małej pętli. Większość sceptyków była zaniepokojona brakiem zakresu blokowego. Jak @quinmars wskazał w komentarzu, zawsze możesz założyć klamry wokół ciała pętli. Zwracam uwagę na to mimochodem for(;;)
i while(true)
nie dostarczam też szelek za darmo (a ich pominięcie może spowodować irytujące błędy). W każdym razie nie będę marnować więcej mocy twojego mózgu na ten drobiazg - mogę żyć z nieszkodliwym i idiomatycznym for(;;)
i while(true)
(równie dobrze, jeśli chcę zachować swoją pracę).
Biorąc pod uwagę inne odpowiedzi, widzę, że wielu ludzi postrzega to goto
jako coś, co zawsze trzeba przepisać w inny sposób. Oczywiście możesz uniknąć a goto
, wprowadzając pętlę, dodatkową flagę, stos zagnieżdżonych if
s lub cokolwiek innego, ale dlaczego nie zastanowić się, czy goto
jest to być może najlepsze narzędzie do tego zadania? Innymi słowy, ile brzydoty są gotowi znieść ludzie, aby uniknąć używania wbudowanej funkcji języka zgodnie z jej przeznaczeniem? Uważam, że nawet dodanie flagi to zbyt wysoka cena do zapłacenia. Lubię, gdy moje zmienne reprezentują rzeczy w domenach problemów lub rozwiązań. „Tylko po to, by uniknąć goto
” nie wystarcza.
Przyjmę pierwszą odpowiedź, która dała wzorzec C do rozgałęzienia do bloku porządkującego. IMO, to jest najsilniejszym argumentem za jedną goto
ze wszystkich opublikowanych odpowiedzi, z pewnością jeśli mierzysz to przez wykrzywienia, przez które musi przejść hejter, aby tego uniknąć.
for (;;)
) nie ma zaskakujących punktów wejścia.Odpowiedzi:
Oto jedna sztuczka, której używają ludzie. Jednak nigdy nie widziałem tego na wolności. Dotyczy to tylko C, ponieważ C ++ ma RAII, aby robić to bardziej idiomatycznie.
void foo() { if (!doA()) goto exit; if (!doB()) goto cleanupA; if (!doC()) goto cleanupB; /* everything has succeeded */ return; cleanupB: undoB(); cleanupA: undoA(); exit: return; }
źródło
Klasyczna potrzeba GOTO w C jest następująca
for ... for ... if(breakout_condition) goto final; final:
Nie ma prostego sposobu na wyrwanie się z zagnieżdżonych pętli bez goto.
źródło
Oto mój niemądry przykład (ze Stevens APITUE) dla wywołań systemowych Uniksa, które mogą być przerywane przez sygnał.
restart: if (system_call() == -1) { if (errno == EINTR) goto restart; // handle real errors }
Alternatywą jest zdegenerowana pętla. Ta wersja brzmi jak w języku angielskim „jeśli wywołanie systemowe zostało przerwane przez sygnał, uruchom je ponownie”.
źródło
continue
jest tylkogoto
w masce.else break
(po jednym na każde ifs), aby było równoważne .. co mogę powiedzieć ...goto
lub nie, twój kod jest tak dobry jak ty :(Jeśli urządzenie Duffa nie potrzebuje goto, ty też nie powinieneś! ;)
void dsend(int count) { int n; if (!count) return; n = (count + 7) / 8; switch (count % 8) { case 0: do { puts("case 0"); case 7: puts("case 7"); case 6: puts("case 6"); case 5: puts("case 5"); case 4: puts("case 4"); case 3: puts("case 3"); case 2: puts("case 2"); case 1: puts("case 1"); } while (--n > 0); } }
powyższy kod z wpisu w Wikipedii .
źródło
Knuth napisał artykuł "Programowanie strukturalne z instrukcjami GOTO", możesz go pobrać np . Stąd . Znajdziesz tam wiele przykładów.
źródło
Bardzo częste.
do_stuff(thingy) { lock(thingy); foo; if (foo failed) { status = -EFOO; goto OUT; } bar; if (bar failed) { status = -EBAR; goto OUT; } do_stuff_to(thingy); OUT: unlock(thingy); return status; }
Jedynym przypadkiem, którego kiedykolwiek używam,
goto
jest skakanie do przodu, zwykle z bloków i nigdy do bloków. Pozwala to uniknąć nadużyćdo{}while(0)
i innych konstrukcji, które zwiększają zagnieżdżanie, zachowując jednocześnie czytelny, ustrukturyzowany kod.źródło
do_stuff(thingy) { RAIILockerObject(thingy); ..... /* -->*/ } /* <---*/
:)try { lock();..code.. } finally { unlock(); }
ale tak, C nie ma odpowiednika.Ogólnie nie mam nic przeciwko gotosom, ale przychodzi mi do głowy kilka powodów, dla których nie chciałbyś ich używać w pętli, o której wspomniałeś:
źródło
Dobrym miejscem na użycie goto jest procedura, która może zostać przerwana w kilku punktach, z których każdy wymaga różnych poziomów czyszczenia. Gotofobowie zawsze mogą zastąpić gotos kodem strukturalnym i serią testów, ale myślę, że jest to prostsze, ponieważ eliminuje nadmierne wcięcia:
źródło
freeResourcesCloseFileQuit
,closeFileQuit
, iquit
.@ fizzer.myopenid.com: Twój opublikowany fragment kodu jest równoważny z następującym:
while (system_call() == -1) { if (errno != EINTR) { // handle real errors break; } }
Zdecydowanie wolę tę formę.
źródło
Mimo że z czasem nienawidzę tego wzorca, jest on zakorzeniony w programowaniu COM.
#define IfFailGo(x) {hr = (x); if (FAILED(hr)) goto Error} ... HRESULT SomeMethod(IFoo* pFoo) { HRESULT hr = S_OK; IfFailGo( pFoo->PerformAction() ); IfFailGo( pFoo->SomeOtherAction() ); Error: return hr; }
źródło
Oto przykład dobrego goto:
// No Code
źródło
Widziałem, że goto jest używany poprawnie, ale sytuacje są normalne brzydkie. Dzieje się tak tylko wtedy, gdy użycie
goto
samego siebie jest o wiele gorsze niż oryginał. @Johnathon Holland, lęk, że twoja wersja jest mniej wyraźna. ludzie wydają się bać zmiennych lokalnych:void foo() { bool doAsuccess = doA(); bool doBsuccess = doAsuccess && doB(); bool doCsuccess = doBsuccess && doC(); if (!doCsuccess) { if (doBsuccess) undoB(); if (doAsuccess) undoA(); } }
Wolę takie pętle, ale niektórzy wolą
while(true)
.for (;;) { //code goes here }
źródło
== true
usunięty punkt . I tak jest bliżej mojego prawdziwego stylu. :)Moim problemem jest to, że tracisz zakres blokowy; wszelkie zmienne lokalne zadeklarowane między gotos pozostają w mocy, jeśli pętla zostanie kiedykolwiek przerwana. (Może zakładasz, że pętla będzie trwać wiecznie; nie sądzę jednak, że to właśnie zadawał autor pytania).
Problem z określaniem zakresu jest bardziej problemem w C ++, ponieważ niektóre obiekty mogą zależeć od wywołania ich dtora w odpowiednim czasie.
Dla mnie najlepszym powodem używania goto jest wieloetapowy proces inicjalizacji, w którym ważne jest, aby wszystkie inity zostały wycofane, jeśli jeden z nich zawiedzie, a la:
if(!foo_init()) goto bye; if(!bar_init()) goto foo_bye; if(!xyzzy_init()) goto bar_bye; return TRUE; bar_bye: bar_terminate(); foo_bye: foo_terminate(); bye: return FALSE;
źródło
Sam nie używam goto, ale pracowałem kiedyś z osobą, która używała ich w określonych przypadkach. Jeśli dobrze pamiętam, jego uzasadnienie dotyczyło problemów z wydajnością - miał też określone zasady, jak . Zawsze w tej samej funkcji, a etykieta zawsze znajdowała się PONIŻEJ instrukcji goto.
źródło
#include <stdio.h> #include <string.h> int main() { char name[64]; char url[80]; /*The final url name with http://www..com*/ char *pName; int x; pName = name; INPUT: printf("\nWrite the name of a web page (Without www, http, .com) "); gets(name); for(x=0;x<=(strlen(name));x++) if(*(pName+0) == '\0' || *(pName+x) == ' ') { printf("Name blank or with spaces!"); getch(); system("cls"); goto INPUT; } strcpy(url,"http://www."); strcat(url,name); strcat(url,".com"); printf("%s",url); return(0); }
źródło
@Greg:
Dlaczego nie zrobić takiego przykładu:
void foo() { if (doA()) { if (doB()) { if (!doC()) { UndoA(); UndoB(); } } else { UndoA(); } } return; }
źródło