do {…} while (false)

125

Patrzyłem na jakiś kod przez osobę i zauważyłem, że wydaje się mieć wzorzec w swoich funkcjach:

<return-type> function(<params>)
{
 <initialization>

 do
 {
   <main code for function>
 }
 while(false);

 <tidy-up & return>
}

Nie jest zły , bardziej osobliwy (rzeczywisty kod jest dość schludny i nie jest zaskakujący). To nie jest coś, co widziałem wcześniej i zastanawiałem się, czy ktoś może wymyślić jakąś logikę za tym - być może tło w innym języku?

Mr. Boy
źródło
2
Czy próbowałeś zmienić go na „normalną” wersję i sprawdzić, czy nadal się kompiluje? Jeśli kompilacja nie powiedzie się podczas normalnego zapisu, błąd kompilatora może dać wskazówkę, dlaczego tak się stało.
Mark Byers,
11
Dlaczego nie zapytać „osoby” zamiast prosić nas o odgadnięcie jej intencji?
1
Może jego profesor zażądał, aby użył a do...while()i od tego czasu używa tego kodu jako szablonu funkcji.
Hogan
6
@Neil ... Chcę nie wyglądać głupio, gdy się z nim konfrontuje, a on wyjaśnia super zaawansowane użycie C ++, o którym nigdy nie myślałem.
Mr. Boy
6
Wydaje się, że jest już kilka różnych odpowiedzi. Czy możesz zapytać autora oryginalnego o jego intencje i przesłać z powrotem?
nevets1219

Odpowiedzi:

175

Możesz breakwyjść do{...}while(false).

Thomas Eding
źródło
95
+1, ponieważ prawdopodobnie taki jest cel kodu, ale zrobienie czegoś takiego jest po prostu idiotycznie zamaskowanym goto. Jeśli uważasz, że goto jest odpowiednim narzędziem do tego zadania, powinieneś po prostu użyć # $ (* # @ goto.
dsimcha
55
To więcej niż zamaskowana goto. Jest to ograniczone (uporządkowane) goto.
Thomas Eding
19
W jaki sposób jest to „ograniczone”? Tylko skok do przodu nie jest „ograniczeniem”. Goto to goto, a ubranie go tak, aby wyglądało na to, że nie jest jednym z nich, jest gorsze niż zwykłe użycie goto w pierwszej kolejności.
Anon.
44
@Anon .: Skakanie do przodu jest ograniczeniem w goto, a wyskakiwanie jest zdecydowanie ograniczeniem. Prawdziwym problemem z gotos jest kod spaghetti, a skok do przodu i na zewnątrz znacznie ogranicza.
David Thornley
33
Rzeczywista pętla nie jest semantycznie goto. Warunek nie jest semantycznie goto. „Idź na koniec funkcji i wykonaj porządki w kodzie” jest semantycznie goto. Używaj gotos, gdy ma zastosowanie semantyka, nie ubierz czegoś innego semantycznie, ponieważ się ich boisz.
Anon.
126

Wiele osób zwraca uwagę, że często używa się go z przerwą jako niezręcznego sposobu pisania „goto”. To prawdopodobnie prawda, jeśli jest napisane bezpośrednio w funkcji.

W makrze OTOH do { something; } while (false)jest wygodnym sposobem na WYMUSZENIE średnika po wywołaniu makra, absolutnie żaden inny token nie jest dozwolony.

Inną możliwością jest to, że albo była tam kiedyś pętla, albo przewiduje się, że w przyszłości zostanie dodana iteracja (np. W programowaniu sterowanym testami iteracja nie była potrzebna, aby przejść testy, ale logicznie rzecz biorąc, pętla miałaby sens, gdyby funkcja musiała być nieco bardziej ogólna niż obecnie wymagana)

Ben Voigt
źródło
18
+1 za wzmiankę o użyteczności tego w makrach; Jestem zaskoczony, że nikt inny o tym nie wspomniał!
Nick Meyer
7
Tak, rzecz makro jest właściwie całkowicie poprawnym użyciem tego. Oczywiście poza makrami to po prostu głupie ...;)
jalf
12
To nie jest niezręczne, jest przydatne, ponieważ jest to goto Scoped - oznacza to, że wszystkie zmienne zadeklarowane w pętli do zostaną zniszczone, podczas gdy goto tego nie robi.
Ana Betts
9
@Paul: Nic nie stoi na przeszkodzie, aby dodać nawiasy klamrowe wokół kilku instrukcji, aby wymusić to zachowanie za pomocą goto.
erikkallen
5
@Paul: goto out of a block z całą pewnością powoduje zniszczenie zmiennych lokalnych w C ++, tak samo jak break. W C zmienne nie są tak naprawdę zniszczone, ich miejsce na stosie jest po prostu odzyskiwane.
Ben Voigt,
25

Przerwa jako goto jest prawdopodobnie odpowiedzią, ale przedstawię jeszcze jeden pomysł.

Może chciał mieć zmienne zdefiniowane lokalnie i użył tej konstrukcji, aby uzyskać nowy zakres.

Pamiętaj, że chociaż najnowsze C ++ pozwala na wszystko {...}, nie zawsze tak było.

Hogan
źródło
30
W takim przypadku mógł po prostu użyć aparatu ortodontycznego.
Nemanja Trifunovic
2
@Nemanja, zdziwiłbyś się, ilu programistów o tym nie wie i spróbuje czegoś związanego z tym, co sugeruje Hogan
Polaris878
8
@Polaris @Nemanja, dopiero wtedy, gdy programowałem w C / C ++ jakieś 4 lata, doszedłem do wniosku, że możesz stworzyć nowy zakres lokalny w {} dowolnym miejscu .. Jest to szczególnie przydatne w switch-casekodzie
Earlz
1
@Nemanja: Nie wiem dokładnie, kiedy, ale jestem pewien, że {...}gdziekolwiek jest nowocześniejszy program w C ++ (pamiętaj, że pierwszy C ++, którego użyłem, został zaimplementowany z preprocesorem i nie pozwalał na to nowoczesne użycie). autor był po prostu oldschoolowy.
Hogan
2
Kiedy nie pozwalało na stosowanie aparatu ortodontycznego wszędzie? Zacząłem programować jak 15 lat temu i wtedy było to dozwolone (zarówno w moim podręczniku, jak iw każdym kompilatorze, którego próbowałem).
erikkallen
20

Widziałem, że jest to użyteczny wzorzec, gdy istnieje wiele potencjalnych punktów wyjścia dla funkcji, ale ten sam kod czyszczenia jest zawsze wymagany, niezależnie od sposobu zakończenia funkcji.

Może to uczynić męczące drzewo jeśli / inaczej-jeśli o wiele łatwiejsze do odczytania, po prostu zmuszając się do łamania za każdym razem, gdy zostanie osiągnięty punkt wyjścia, a reszta logiki jest później wbudowana.

Ten wzorzec jest również przydatny w językach, które nie mają instrukcji goto. Być może właśnie tam pierwotny programista nauczył się wzoru.

Cameron
źródło
15
Następnie użyj uczciwego, prostego goto zamiast słabo zamaskowanego goto.
dsimcha
4
Tak mi się bardziej podoba. Jest łatwy do odczytania i nie nosi piętna goto.
Cameron,
8
Gotos są doskonale czytelne, jeśli są używane oszczędnie i lokalnie. Otrzymali piętno z powrotem, kiedy byli główną formą kontroli przepływu i skakali przez setki linii.
dsimcha
13
Nieużywanie goto z powodu „piętna” jest pewnym znakiem kultowego programowania cargo.
Anon.
6
Nie wspominając o tym, że masa kodu czyszczącego to nieprzyjemny zapach. Ponieważ jest to C ++, kod czyszczący powinien normalnie znajdować się w destruktorach wywoływanych podczas przetwarzania RAII.
David Thornley
12

Widziałem taki kod, więc możesz go używać breakjako swego gotorodzaju.

Brian Young
źródło
10

To jest po prostu wypaczenie, whileaby uzyskać sematykę programu goto tidy-upbez użycia słowa goto.

Jest to zła forma, ponieważ podczas korzystania z innych pętle wewnątrz zewnętrznej stają się dwuznaczne dla czytelnika. „Czy to ma doprowadzić do wyjścia? Czy ma to na celu jedynie wyrwanie się z wewnętrznej pętli?”whilebreaks

John Knoeller
źródło
10

Myślę, że wygodniej jest pisać breakzamiast pisać goto end. Nie musisz nawet wymyślać nazwy dla etykiety, która wyjaśnia zamiar: nie chcesz przeskakiwać do etykiety o określonej nazwie. Chcesz się stąd wydostać.

Istnieje również szansa, że ​​i tak będziesz potrzebować aparatu ortodontycznego. Więc to jest do{...}while(false);wersja:

do {
   // code
   if (condition) break; // or continue
   // more code
} while(false);

I tak musiałbyś to wyrazić, gdybyś chciał użyć goto:

{
   // code
   if (condition) goto end;
   // more code
}
end:

Myślę, że znaczenie pierwszej wersji jest znacznie łatwiejsze do zrozumienia. Jest też łatwiejsze do pisania, łatwiejsze do rozszerzenia, łatwiejsze do przetłumaczenia na język, który nie obsługuje gotoitp.


Najczęściej wspominanym problemem związanym z używaniem breakjest to, że jest źle zamaskowany goto. Ale w rzeczywistości breakjest bardziej podobny do return: Obie instrukcje wyskakują z bloku kodu, który jest prawie uporządkowany w porównaniu z goto. Niemniej jednak obie instrukcje dopuszczają wiele punktów wyjścia w bloku kodu, co czasami może być mylące. W końcu starałbym się znaleźć jak najbardziej jasne rozwiązanie, niezależnie od tego, co jest w konkretnej sytuacji.

Robert
źródło
Och, właśnie zauważyłem, że nie do końca zrozumiałem pytanie przed udzieleniem odpowiedzi. Myślałem, że chodzi o użycie do{ ... }while(false);w ogóle. Ale w rzeczywistości chodzi o użycie go do naśladowania jakiegoś try{ ... }finally{ ... }.
Robert
Niezła obserwacja podobieństwa między przerwaniem a powrotem zamiast goto. Zgadzam się, że najjaśniejsze rozwiązanie jest najlepsze. W wielu przypadkach prawdopodobnie jaśniejsze jest hermetyzowanie kodu w jego własnej oddzielnej funkcji, która używa return zamiast break, a następnie wykonuje czyszczenie w funkcji wywołującej.
persyflaż
7

Ta sztuczka jest używana przez programistów, którzy są zbyt nieśmiali, aby używać jawnego gotokodu w swoim kodzie. Autor powyższego kodu chciał mieć możliwość bezpośredniego przeskoczenia do punktu „czyszczenie i powrót” ze środka kodu. Ale nie chcieli używać etykiety i jednoznacznie goto. Zamiast tego mogą użyć breakwewnątrz ciała powyższego „fałszywego” cyklu, aby osiągnąć ten sam efekt.

Mrówka
źródło
7

To bardzo powszechna praktyka. W C . Staram się o tym myśleć tak, jakbyś chciał się okłamywać w sposób „nie używam goto”. Myśląc o tym, nie byłoby nic złego w gotoużywaniu podobnie. W rzeczywistości zmniejszyłoby to również poziom wcięć.

Mimo to zauważyłem, że bardzo często te do..whilepętle rosną. A potem dostają ifs i elses do środka, przez co kod w rzeczywistości nie jest zbyt czytelny, nie mówiąc już o testowaniu.

Ci do..whilesą zazwyczaj przeznaczone zrobić Clean-up . Jak najbardziej, wolałbym użyć RAII i wrócić wcześniej z krótkiej funkcji. Z drugiej strony, C nie zapewnia tak wielu udogodnień, jak C ++ , co stanowi do..whilejedno z najlepszych podejść do czyszczenia.

Dmitry
źródło
6

Wygląda jak programista w C. W C ++ zmienne automatyczne mają destruktory, których używasz do czyszczenia, więc nie powinno być potrzeby ich porządkowania przed powrotem. W C nie miałeś tego idiomu RAII , więc jeśli masz wspólny kod czyszczący, możesz gotogo albo użyć pętli jednorazowej, jak powyżej.

Jego główną wadą w porównaniu z idiomem C ++ jest to, że nie uporządkuje, jeśli w treści zostanie zgłoszony wyjątek. C nie ma wyjątków, więc nie był to problem, ale sprawia, że ​​jest to zły nawyk w C ++.

Pete Kirkham
źródło
4

Kilka wyjaśnień. Pierwsza jest ogólna, druga jest specyficzna dla makr preprocesora C z parametrami:

Kontrola przepływu

Widziałem to w zwykłym kodzie C. Zasadniczo jest to bezpieczniejsza wersja goto, ponieważ możesz się z niej wyrwać, a cała pamięć zostanie poprawnie wyczyszczona.

Dlaczego coś takiego jak gotobyłoby dobre? Cóż, jeśli masz kod, w którym prawie każda linia może zwrócić błąd, ale musisz zareagować na wszystkie z nich w ten sam sposób (np. Przekazując błąd dzwoniącemu po wyczyszczeniu), zwykle łatwiej jest uniknąć if( error ) { /* cleanup and error string generation and return here */ }as pozwala uniknąć powielania kodu oczyszczającego.

Jednak w C ++ masz wyjątki + RAII dokładnie w tym celu, więc uznałbym to za zły styl kodowania.

Sprawdzanie średników

Jeśli zapomnisz średnika po wywołaniu makra podobnego do funkcji, argumenty mogą skurczyć się w niepożądany sposób i skompilować się do prawidłowej składni. Wyobraź sobie makro

#define PRINT_IF_DEBUGMODE_ON(msg) if( gDebugModeOn ) printf("foo");

To jest przypadkowo nazwane jako

if( foo )
    PRINT_IF_DEBUGMODE_ON("Hullo\n")
else
    doSomethingElse();

„Inne” zostanie uznane za powiązane z elementem gDebugModeOn, więc kiedy foojest false, nastąpi dokładna odwrotność tego, co było zamierzone.

Zapewnienie zakresu dla zmiennych tymczasowych.

Ponieważ do / while ma nawiasy klamrowe, zmienne tymczasowe mają jasno określony zakres, przed którym nie mogą uciec.

Unikanie ostrzeżeń „prawdopodobnie niechcianych średników”

Niektóre makra są aktywowane tylko w kompilacjach do debugowania. Definiujesz je tak:

#if DEBUG
#define DBG_PRINT_NUM(n) printf("%d\n",n);
#else
#define DBG_PRINT_NUM(n) 
#endif

Teraz, jeśli użyjesz tego w kompilacji wydania wewnątrz warunku, kompiluje się do

if( foo )
    ;

Wielu kompilatorów uważa to za to samo, co

if( foo );

Które często jest napisane przypadkowo. Więc dostajesz ostrzeżenie. Do {} while (false) ukrywa to przed kompilatorem i jest przez niego akceptowane jako wskazanie, że naprawdę nie chcesz tutaj nic robić.

Unikanie przechwytywania linii przez warunkowe

Makro z poprzedniego przykładu:

if( foo )
    DBG_PRINT_NUM(42)
doSomething();

Teraz, w kompilacji do debugowania, ponieważ zwykle dodawaliśmy również średnik, kompiluje się to dobrze. Jednak w kompilacji wydania zmienia się to nagle w:

if( foo )

doSomething();

Lub wyraźniej sformatowany

if( foo )
    doSomething();

Co wcale nie jest tym, co było zamierzone. Dodanie do {...} while (false) wokół makra zamienia brakujący średnik w błąd kompilacji.

Co to oznacza dla PO?

Ogólnie rzecz biorąc, chcesz używać wyjątków w C ++ do obsługi błędów i szablonów zamiast makr. Jednak w bardzo rzadkich przypadkach, gdy nadal potrzebujesz makr (np. Podczas generowania nazw klas za pomocą wklejania tokenów) lub jesteś ograniczony do zwykłego C, jest to przydatny wzorzec.

bystrość
źródło
Jeśli chodzi o określanie zakresu, nie potrzebujesz aparatu pętlowego; Określenie „ozdób” blok jest legalne: x =1; y = 2; { int tmp = y; y = x; x = tmp; }.
chepner
Niezdobiony blok nie wymusza jednak obecności brakującego średnika, co może mieć niepożądane skutki uboczne. Do {} while (); WYMAGA średnika, a więc np. Nie powoduje, że następujące instrukcje są wciągane do „if”, jeśli makro definiuje nic w kompilacji wydania, jak w powyższym przykładzie.
uliwitness
Czy nie można tego uniknąć, podając po prostu lepszą definicję makra? #define DBG_PRINT_NUM(n) {}.
chepner
1
Nie, ponieważ {} jest pełną instrukcją, tj. Odpowiednikiem ";". Więc jeśli napiszesz, if(a) DBG_PRINT_NUM(n); else other();to się nie skompilujesz. Tylko if(a) {} else other();lub if(a) ; else other();jest poprawne, ale if(a) {}; else other();nie jest, ponieważ to sprawia, że ​​klauzula „if” składa się z dwóch instrukcji.
uliwitness
2

Może jest breakużywany w celu przerwania wykonywania dalszego kodu w dowolnym momencie:

do {
    if (!condition1) break;
    some_code;
    if (!condition2) break;
    some_further_code;
    // …
} while(false);
Gumbo
źródło
7
Wydaje się, że jest to próba uniknięcia używania gototylko dlatego, że ktoś słyszał, że jest to „złe”.
Anon.
1
Być może, ale C ++ ma wyjątki właśnie z tego powodu.
TED
2

Myślę, że ma to na celu użycie breaklub continueoświadczenia. Jakiś rodzaj logiki „goto”.

Ivan Nevostruev
źródło
2

To proste: najwyraźniej możesz wyskoczyć z fałszywej pętli w dowolnym momencie, używając breakinstrukcji. Ponadto doblok jest oddzielnym zakresem (który można również osiągnąć { ... }tylko za pomocą ).

W takiej sytuacji lepszym pomysłem może być użycie RAII (obiekty automatycznie niszczą się poprawnie po zakończeniu funkcji). Inną podobną konstrukcją jest użycie goto- tak, wiem, że to zło , ale można go użyć do posiadania wspólnego kodu czyszczącego, takiego jak:

<return-type> function(<params>)
{
 <initialization>

 <main code for function using "goto error;" if something goes wrong>

 <tidy-up in success case & return>

 error:

 <commmon tidy-up actions for error case & return error code or throw exception>
}

(Na marginesie: konstrukcja do-while-false jest używana w Lua, aby znaleźć brakującą continueinstrukcję).

AndiDog
źródło
2

Wielu respondentów podało powód do{(...)break;}while(false). Chciałbym uzupełnić ten obraz o kolejny przykład z życia wzięty.

W poniższym kodzie musiałem ustawić moduł wyliczający operationna podstawie adresu wskazywanego przez datawskaźnik. Ponieważ przypadek przełącznika może być używany tylko na typach skalarnych, najpierw zrobiłem to nieefektywnie w ten sposób

if (data == &array[o1])
    operation = O1;
else if (data == &array[o2])
    operation = O2;
else if (data == &array[on])
    operation = ON;

Log("operation:",operation);

Ale ponieważ Log () i reszta kodu powtarza się dla dowolnej wybranej wartości operacji, zastanawiałem się, jak pominąć resztę porównań, gdy adres został już odkryty. I tu się do{(...)break;}while(false)przydaje.

do {
    if (data == &array[o1]) {
        operation = O1;
        break;
    }
    if (data == &array[o2]) {
        operation = O2;
        break;
    }
    if (data == &array[on]) {
        operation = ON;
        break;
    }
} while (false);

Log("operation:",operation);

Można się zastanawiać, dlaczego nie mógł zrobić tego samego breakw ifoświadczeniu, takim jak:

if (data == &array[o1])
{
    operation = O1;
    break;
}
else if (...)

breakoddziałuje wyłącznie z najbliższą otaczającą pętlą lub przełącznikiem, czy to typu a for, whileczy do .. whiletypu, więc niestety to nie zadziała.

4pie0
źródło
1

Ile lat miał autor?

Pytam, ponieważ pod koniec lat 80-tych natknąłem się kiedyś na kod Fortran działający w czasie rzeczywistym, który to zrobił. Okazuje się, że to naprawdę dobry sposób na symulowanie wątków w systemie operacyjnym, który ich nie ma. Po prostu umieszczasz cały program (swój program planujący) w pętli i wywołujesz swoje „procedury wątku” jeden po drugim. Same procedury wątku są pętlami, które wykonują iterację aż do wystąpienia jednego z wielu warunków (często jeden jest określoną ilością Jest to „wielozadaniowość kooperacyjna”, w której poszczególne wątki od czasu do czasu rezygnują z procesora, aby inne nie zostały zagłodzone. Możesz zagnieżdżać wywołania podprogramu zapętlonego, aby symulować priorytet wątku Zespoły.

PRZETRZĄSAĆ
źródło
0

Zgadzam się z większością plakatów o użyciu jako słabo zamaskowanej goto. Wspomniano również o makrach jako potencjalnej motywacji do pisania kodu w stylu.

Widziałem również tę konstrukcję używaną w mieszanych środowiskach C / C ++ jako wyjątek dla biednych. Polecenie „do {} while (false)” z „break” może być użyte do przeskoczenia do końca bloku kodu, gdyby w pętli wystąpiło coś, co normalnie gwarantowałoby wyjątek.

Skorzystałem również z tej konstrukcji używanej w sklepach, w których obowiązuje ideologia „pojedynczego zwrotu na funkcję”. Ponownie, jest to zamiast wyraźnego „goto” - ale motywacją jest unikanie wielu punktów powrotu, a nie „pomijanie” kodu i kontynuowanie rzeczywistego wykonywania w ramach tej funkcji.

Stan Graves
źródło
0

Pracuję z Adobe InDesign SDK, a przykłady InDesign SDK mają prawie każdą funkcję napisaną w ten sposób. Wynika to z faktu, że funkcje są zwykle bardzo długie. Gdzie musisz zrobić QueryInterface (...), aby uzyskać cokolwiek z modelu obiektów aplikacji. Tak więc zwykle po każdym interfejsie QueryInterface występuje błąd, który się ifnie udał, przerwa.

Kugel
źródło
0

Wielu już stwierdziło podobieństwo między tą konstrukcją a a gotoi wyraziło preferencje dla goto. Być może przeszłość tej osoby obejmowała środowisko, w którym goto było surowo zabronione przez wytyczne dotyczące kodowania?

Mark Okup
źródło
0

Innym powodem, dla którego przychodzi mi do głowy, jest to, że ozdabia nawiasy klamrowe, podczas gdy uważam, że nowszy standard C ++ nie jest w porządku (ISO C ich nie lubi). W przeciwnym razie, aby wyciszyć analizator statyczny, taki jak kłaczki.

Nie jestem pewien, dlaczego chcesz je mieć, może zakres zmienny lub korzyść z debugera.

Zobacz Trivial Do While loop i Braces are Good from C2.

Aby wyjaśnić moją terminologię (która moim zdaniem jest zgodna ze standardowym użyciem):

Nagie szelki :

init();
...
{
c = NULL;
mkwidget(&c);
finishwidget(&c);
}
shutdown();

Puste szelki (NOP):

{}

na przykład

while (1)
   {}  /* Do nothing, endless loop */

Blok :

if (finished)
{
     closewindows(&windows);
     freememory(&cache);
}

który stanie się

if (finished)
     closewindows(&windows);
freememory(&cache);

jeśli nawiasy zostaną usunięte, zmieniając w ten sposób przepływ wykonywania, a nie tylko zakres zmiennych lokalnych. Zatem nie jest „wolnostojący” lub „nagi”.

Nagie nawiasy klamrowe lub blok mogą być używane do oznaczania dowolnej sekcji kodu, która może być potencjalną funkcją (wbudowaną), którą chcesz oznaczyć, ale nie w tym czasie refaktoryzacji.

mctylr
źródło
Naprawdę? Jaka szkoda. Jedną z niewielu rzeczy, które podobało mi się w C, było to, że pozwalało ci zadeklarować nowy zakres zagnieżdżony prawie wszędzie.
TED
1
Nagie szelki są czasami pomocne w naprawianiu dziwnych awarii. Zobacz blogs.msdn.com/oldnewthing/archive/2004/05/20/135841.aspx .
Brian
1
W C ++, blok (czyli "nagie nawiasy klamrowe") może być użyty wszędzie tam, gdzie dozwolone byłoby pojedyncze wyrażenie.
Ben Voigt
@BenVoigt Puste nawiasy klamrowe, tj. NOP różni się od „nagich nawiasów klamrowych”, czyli bloku dodawanego wokół liniowej sekwencji instrukcji. Np. `Printf (" Cześć "); {putchar (','); putchar (0x20); } printf ("świat! \ n"); `gdzie nawiasy klamrowe nie są częścią kontroli pętli lub gałęzi.
mctylr
@mctylr: Nie mówiłem o pustych szelkach.
Ben Voigt
0

To wymyślony sposób na emulację a, GOTOponieważ te dwa są praktycznie identyczne:

// NOTE: This is discouraged!
do {
    if (someCondition) break;
    // some code be here
} while (false);
// more code be here

i:

// NOTE: This is discouraged, too!
if (someCondition) goto marker;
// some code be here
marker:
// more code be here

Z drugiej strony, obie te rzeczy naprawdę powinny być zrobione za pomocą ifs:

if (!someCondition) {
    // some code be here
}
// more code be here

Chociaż zagnieżdżanie może stać się nieco brzydkie, jeśli po prostu zamienisz długi ciąg znaków forward GOTOw zagnieżdżone ifs. Prawdziwą odpowiedzią jest jednak właściwa refaktoryzacja, a nie naśladowanie archaicznych konstrukcji językowych.

Jeśli desperacko próbowałeś transliterować algorytm zawierający GOTOs, prawdopodobnie mógłbyś to zrobić za pomocą tego idiomu. Z pewnością jest to niestandardowe i dobre wskazanie, że nie trzymasz się ściśle oczekiwanych idiomów języka.

Nie znam żadnego języka podobnego do języka C, w którym funkcja do / while jest idiomatycznym rozwiązaniem na wszystko.

Prawdopodobnie mógłbyś przekształcić cały bałagan w coś bardziej sensownego, aby uczynić go bardziej idiomatycznym i znacznie bardziej czytelnym.

Alan Plum
źródło
1
Alan, niedo..while(false) służy do uruchamiania „undefined” razy, lecz do jednorazowego uruchamiania .
Dmitry
2
Działa niezdefiniowaną liczbę razy, jeśli continuena końcu znajduje się instrukcja warunkowa, tak jak pokazałem. By undefinedpo prostu znaczy, że nie wiem, czy to działa więcej niż jeden raz, chyba że można przewidzieć, czy te warunki zostaną spełnione w określonym iteracji. Bez żadnych continueznaków, do..while(false)uruchomi się raz, ale także bez żadnych break, while(true)będzie działać w nieskończoność, więc „zachowanie domyślne” nie jest tak naprawdę istotne dla zrozumienia, co można zrobić z pętlami.
Alan Plum
Jest to przydatne, gdy definiujesz makro, które ma wiele instrukcji. Jeśli zawiniesz je w do / while (false), możesz użyć makra w instrukcji if / else, tak jak w każdym innym wywołaniu funkcji. Zobacz noveltheory.com/TechPapers/while.htm, aby uzyskać wyjaśnienie
John Paquin
5
Ktoś nie rozumie semantyki continue. continueNIE powtarza pętli. „ continueInstrukcja powinna wystąpić tylko w instrukcji iteracji i powoduje przekazanie kontroli do części z kontynuacją pętli najmniejszej otaczającej instrukcji iteracji , to znaczy na koniec pętli”.
Ben Voigt
-1, dwa górne stwierdzenia nie są identyczne z powodów, na które wskazuje @BenVoigt.
cmh
0

Niektórzy programiści wolą mieć tylko jedno wyjście / powrót ze swoich funkcji. Użycie atrapy do {....} while (false); pozwala na „wyrwanie się” z atrapy pętli po zakończeniu i nadal mieć pojedynczy powrót.

Jestem programistą java, więc mój przykład byłby podobny

import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class p45
{
    static List<String> cakeNames = Arrays.asList("schwarzwald torte", "princess", "icecream");
    static Set<Integer> forbidden = Stream.of(0, 2).collect(Collectors.toSet());

    public static  void main(String[] argv)
    {
        for (int i = 0; i < 4; i++)
        {
            System.out.println(String.format("cake(%d)=\"%s\"", i, describeCake(i)));
        }
    }


    static String describeCake(int typeOfCake)
    {
        String result = "unknown";
        do {
            // ensure type of cake is valid
            if (typeOfCake < 0 || typeOfCake >= cakeNames.size()) break;

            if (forbidden.contains(typeOfCake)) {
                result = "not for you!!";
                break;
            }

            result = cakeNames.get(typeOfCake);
        } while (false);
        return result;
    }
}
David Young
źródło
0

W takich przypadkach używam

switch(true) {
   case condution1:
      ...
      break;
   case condution2:
      ...
      break;
}
Женя Остроушко
źródło
-1

To jest zabawne. Prawdopodobnie w pętli są przerwy, jak powiedzieli inni. Zrobiłbym to w ten sposób:

while(true)
{
   <main code for function>
   break; // at the end.
}
fastcodejava
źródło
2
Z potencjałem zapętlenia na zawsze? do..while(false)zawsze wychodzi, while(true)jest bardziej ryzykowna.
Dmitry
1
while(true)to poprawny idiom w większości języków. Często można go znaleźć w aplikacjach GUI jako główną pętlę programu. Ponieważ zasadniczo zakładasz, że program nie powinien umrzeć, dopóki nie zostanie mu to nakazane, do..while(false)spowodowałoby to wszelkiego rodzaju wymyśloną logikę. Takie podejście może być bardziej ryzykowne w przypadku perfekcjonistycznego POV, ale jest semantycznie łatwiejsze, a tym samym mniej podatne na błędy dla ludzkich programistów (przepraszam, Skynet).
Alan Plum
2
@dmitry do{...}while(false)jest dokładnie taki sam jakwhile(true){ .. break;}
N 1.1
4
@ N1.1: Nie w obecności continue, nie są takie same.
Ben Voigt