„do… while” vs. „while”


86

Możliwe duplikaty:
While vs. Do While
Kiedy należy używać pętli do-while zamiast pętli while?

Od jakiegoś czasu zajmuję się programowaniem (2 lata pracy + 4,5 roku studiów + 1 rok przed college'u) i nigdy nie korzystałem z pętli „do-while” na kursie Wprowadzenie do programowania. Mam coraz większe poczucie, że programuję źle, jeśli nigdy nie napotkam czegoś tak fundamentalnego.

Czy to możliwe, że po prostu nie natknąłem się na właściwe okoliczności?

Jakie są przykłady, w których należałoby użyć czasu do zrobienia zamiast chwili?

(Moja nauka była prawie w całości w C / C ++, a moja praca jest w C #, więc jeśli jest inny język, w którym ma to absolutnie sens, ponieważ do-whiles działa inaczej, te pytania tak naprawdę nie mają zastosowania).

Aby wyjaśnić ... znam różnicę między a whilei a do-while. Podczas sprawdza warunek wyjścia, a następnie wykonuje zadania. do-whilewykonuje zadania, a następnie sprawdza warunek zakończenia.


42
To naprawdę warte, po dziesięciu latach programowania jako mojej kariery, użyłem pętli do-while, jak raz lub dwa.
Matt Greer,

@Matt - To sprawia, że ​​czuję się lepiej. Przynajmniej oznacza to, że nie jestem sam.
mphair

2
Poproszę Matta i dodam do tego kolejną dekadę. Rzadko też korzystam z pętli do-while.
quentin-starin

WIELE duplikatów. Oto jeden, który prowadzi do kilku z nich: stackoverflow.com/questions/3094972/…
gnovice

3
Wydaje się, że poprawne „główne pytanie” brzmi: stackoverflow.com/questions/224059/…
Donal Fellows,

Odpowiedzi:


115

Jeśli zawsze chcesz, aby pętla została wykonana przynajmniej raz. To nie jest powszechne, ale używam go od czasu do czasu. Jednym z przypadków, w którym możesz chcieć go użyć, jest próba uzyskania dostępu do zasobu, który może wymagać ponownej próby, np

do
{
   try to access resource...
   put up message box with retry option

} while (user says retry);

3
Więc myślę, że następowałoby to pod hasłem „Nie napotkałem takich okoliczności”.
mphair

7
szczerze mówiąc, bardzo rzadko spotykam się z sytuacją, w której po prostu muszę użyć do ... w międzyczasie ...
Stan R.

1
Może trochę za krótkie, ale Bob ma rację. Do / while powinno być używane, gdy chcesz uruchomić blok kodu przynajmniej raz . Powinieneś użyć pętli while, gdy nie wiesz, czy chcesz ją uruchomić jednorazowo. To powiedziawszy, mogę powiedzieć, że mając około 15 lat doświadczenia w programowaniu, używałem wielokrotnie więcej pętli while niż pętli do / while.
ConsultUtah,

1
Używałem ich też od czasu do czasu, choć rzadko. Po pewnym czasie zwykle są one również refaktoryzowane w normalną pętlę while - tylko dlatego, że obszary wokół niej zmieniły się i zrobiły coś ... choć bardziej niezgrabne niż na początku.
Joshua

2
To jest dość powszechne. Ale nie rozumiem, dlaczego w twoim przykładzie nie używałbyś pętli while. I nie rozumiem, dlaczego ta odpowiedź jest pozytywnie oceniana.

65

do-while jest lepsze, jeśli kompilator nie jest kompetentny w optymalizacji. Do-while ma tylko jeden skok warunkowy, w przeciwieństwie do for i while, które mają skok warunkowy i skok bezwarunkowy. W przypadku procesorów, które działają w trybie potokowym i nie przewidują rozgałęzień, może to mieć duży wpływ na wydajność ścisłej pętli.

Ponadto, ponieważ większość kompilatorów jest wystarczająco sprytnych, aby wykonać tę optymalizację, wszystkie pętle znalezione w zdekompilowanym kodzie będą zwykle działały do-while (jeśli dekompilator w ogóle zadaje sobie trud rekonstrukcji pętli z wcześniejszych lokalnych elementów gotowych).


5
+1 za dodanie odpowiednich informacji technicznych do wyboru między formami pętli.
quentin-starin

2
Nie tylko przedwczesne, ale bezużyteczne. Jeśli naprawdę chciałeś pętli while, tj. Możliwości iteracji 0 razy, musisz
zawrzeć

3
@ Jeremy: Skok, o którym mówisz, jest poza pętlą, nie jest wykonywany N razy.
Ben Voigt

1
Rozważ również klasyczne użycie do-while w urządzeniu Duffa (gdzie pętla while jest przekształcana w rozwiniętą pętlę do-while z większym krokiem i tabelą skoków do obsługi pozostałej części), co radykalnie zmniejsza narzut pętli - w zastosowaniach takich jak memset, memcmp, memcpy może zwiększyć przepustowość dziesięciokrotnie
Ben Voigt

@BenVoigt Wiem, że Twój komentarz ma już dziesięć lat, ale o Urządzeniu Duffa nie powinno się wspominać bez bardzo ważnego zastrzeżenia - stało się mniej wydajne w przypadku nowoczesnych kompilatorów, ponieważ nowoczesne kompilatory mogą zwykle inteligentnie rozwijać same proste pętle jako optymalne dla maszyny docelowej , podczas gdy generalnie nie mają logiki do odgadywania intencji urządzenia Duffa, a zatem nie mogą go tak dobrze zoptymalizować. Co jest świetną lekcją na temat optymalizacji kodu dla jakiejś maszyny docelowej zamiast abstrakcyjnego „dostatecznie zaawansowanego optymalizatora”.
mtraceur

12

Użyłem tego w funkcji TryDeleteDirectory. To było coś takiego

do
{
    try
    {
        DisableReadOnly(directory);
        directory.Delete(true);
    }
    catch (Exception)
    {
        retryDeleteDirectoryCount++;
    }
} while (Directory.Exists(fullPath) && retryDeleteDirectoryCount < 4);

Ładny. To jest pierwszy przykład, który ma sens, by być opakowanym w do ... chwilę.
Joshua

4
Dlaczego / w jaki sposób jest to lepsze niż standard while?
Josh Stodola

Pętla będzie zawsze wykonywana raz, więc możesz chcieć coś zrobić, a następnie spróbować ponownie, jeśli to się nie powiedzie. Jeśli używasz normalnej pętli while, będziesz sprawdzać, czy się nie powiedzie, zanim jeszcze to zrobisz w pierwszej iteracji.
NibblyPig

1
@SLC Ale czy nie chciałbyś i tak sprawdzić, czy katalog istnieje wcześniej?
Josh Stodola

1
@Josh W tym przykładzie tak. Ale jeśli próbujesz otworzyć połączenie z zajętym serwerem, najpierw łączysz się i sprawdzasz, czy odpowiedź była „zajęta”. Gdyby tak było, spróbuj kilka razy, zanim się poddasz.
NibblyPig

11

Funkcja Do while jest przydatna, gdy chcesz wykonać coś przynajmniej raz. Jeśli chodzi o dobry przykład użycia funkcji do while vs. while, powiedzmy, że chcesz wykonać następujące czynności: Kalkulator.

Możesz podejść do tego, używając pętli i sprawdzając po każdym obliczeniu, czy dana osoba chce wyjść z programu. Teraz możesz prawdopodobnie założyć, że po otwarciu programu osoba chce to zrobić przynajmniej raz, więc możesz wykonać następujące czynności:

do
{
    //do calculator logic here
    //prompt user for continue here
} while(cont==true);//cont is short for continue

2
Właściwie, skoro już o tym wspomniałeś, każdy interfejs oparty na konsoli, oparty na menu, poradziłby sobie również w pętli do ... while. Na przykład monitowanie o opcje (po jednym klawiszu na raz) i wychodzenie tylko wtedy, gdy wybiorą kontynuuj lub zakończ.
Joshua

7
continueto słowo kluczowe; D
Thomas Eding

Atrinithis: Lol ... poślizgnąłem się. Dzięki za poprawienie mnie.

== truenie jest potrzebne. cont == truejeśli cont jest prawdziwe, zwraca prawdę. To całkowicie bezużyteczne.
Mr.DeleteMyMessages

11

To w pewnym sensie odpowiedź pośrednia, ale to pytanie skłoniło mnie do zastanowienia się nad logiką, która za nim stoi, i pomyślałem, że warto się tym podzielić.

Jak wszyscy powiedzieli, do ... whilepętli używasz, gdy chcesz przynajmniej raz wykonać body. Ale w jakich okolicznościach chciałbyś to zrobić?

Cóż, najbardziej oczywistą klasą sytuacji, o których przychodzi mi do głowy, byłaby sytuacja, w której początkowa („niewzbudzona”) wartość warunku sprawdzenia jest taka sama, jak przy wyjściu . Oznacza to, że musisz raz wykonać treść pętli, aby zaliczyć warunek do wartości nieistniejącej, a następnie wykonać rzeczywiste powtórzenie w oparciu o ten warunek. Ponieważ programiści są tak leniwi, ktoś postanowił zawrzeć to w strukturze kontrolnej.

Na przykład odczyt znaków z portu szeregowego z limitem czasu może mieć postać (w Pythonie):

response_buffer = []
char_read = port.read(1)

while char_read:
    response_buffer.append(char_read)
    char_read = port.read(1)

# When there's nothing to read after 1s, there is no more data

response = ''.join(response_buffer)

Uwaga powielania kodu: char_read = port.read(1) . Gdyby Python miał do ... whilepętlę, mógłbym użyć:

do:
    char_read = port.read(1)
    response_buffer.append(char_read)
while char_read

Dodatkowa korzyść dla języków, które tworzą nowy zakres dla pętli: char_readnie zanieczyszcza przestrzeni nazw funkcji. Ale pamiętaj również, że istnieje lepszy sposób na zrobienie tego, a jest to użycie Nonewartości Pythona :

response_buffer = []
char_read = None

while char_read != '':
    char_read = port.read(1)
    response_buffer.append(char_read)

response = ''.join(response_buffer)

Więc oto sedno mojego punktu: w językach z pustych rodzaju sytuacja initial_value == exit_valuepowstaje znacznie rzadziej, a to może być, dlaczego nie spotykamy go. Nie mówię, że to się nigdy nie zdarza, ponieważ wciąż są chwile, kiedy funkcja wróci, Noneaby oznaczyć ważny warunek. Ale w mojej pospiesznej i krótko przemyślanej opinii zdarzyłoby się to znacznie częściej, gdyby używane języki nie pozwalały na wartość, która oznacza: ta zmienna nie została jeszcze zainicjowana.

Nie jest to doskonałe rozumowanie: w rzeczywistości, teraz, gdy wartości zerowe są powszechne, po prostu tworzą jeszcze jeden element zbioru prawidłowych wartości, które zmienna może przyjąć. Praktycznie jednak programiści mają sposób na rozróżnienie między zmienną będącą w stanie rozsądnym, który może obejmować stan wyjścia pętli, a stanem niezainicjalizowanym.


To jedna z ciekawszych odpowiedzi, które tutaj przeczytałem. Niezły wkład.
Bob Moore

Te dwie wersje nie są równoważne. Jeśli pierwszy odczyt powróci None, whilewersja poprawnie nie dodaje nic do bufora odpowiedzi, ale twoja dowersja zawsze coś dodaje.
David Stone

@DavidStone read()nigdy nie powinien wrócić None. Jeśli korzystasz z biblioteki, w której tak jest, przesłanka nie ma zastosowania (tj. Wartość wartownika nie znajduje się w zestawie możliwych wartości kontrolnych).
beztrosko

W twojej najnowszej edycji widzę, że ''zamiast tego powinienem był powiedzieć None. Jakaś wartość, której wynikiem jest fałsz. Podkreśl to, że nie znam readfunkcji Pythona .
David Stone

@DavidStone Nie rozumiem. Jeśli read()zwraca '', nic nie zostanie dodane do ciągu, jest puste.
detly

7

Używałem ich dość często, kiedy byłem w szkole, ale od tamtej pory nie tak bardzo.

Teoretycznie są przydatne, gdy chcesz, aby treść pętli została wykonana raz przed sprawdzeniem warunków zakończenia. Problem polega na tym, że w kilku przypadkach, w których nie chcę najpierw sprawdzić, zazwyczaj chcę, aby kontrola wyjścia znajdowała się na środku ciała pętli, a nie na samym końcu. W takim przypadku wolę używać dobrze znanego for (;;)z rozszerzeniemif (condition) exit; gdzieś w organizmie.

W rzeczywistości, jeśli jestem trochę niepewny warunku zakończenia pętli, czasami uważam, że przydatne jest rozpoczęcie pisania pętli jako for (;;) {}z instrukcją zakończenia w razie potrzeby, a kiedy skończę, mogę zobaczyć, czy to możliwe. wyczyszczone ”przez przeniesienie inicjalizacji, warunków wyjścia i / lub kodu inkrementacji wewnątrz fornawiasów.


5
Z ciekawości… dlaczego „dla (;;)” zamiast „podczas (prawda)”?
mphair

4
Prawdopodobnie tylko smak. Urosłem do utożsamiania się for(;;)z „nieskończoną pętlą”, podczas gdy while(true)muszę się zatrzymać i pomyśleć. Być może dlatego, że kodowałem C, zanim pojawił się plik true. Kiedyś mieliśmy tylko TRUEmakro, a jeśli coś było wadliwe, musiałbym sam przekonać się, że makro nie zostało w 0jakiś sposób przedefiniowane (tak się stało!). Ze for(;;)nie ma szans na to. Jeśli nalegasz na formę while, prawie wolałbym zobaczyć while (1). Oczywiście niektórzy mogą się zastanawiać, czy to nie jest litera l ...
TED

1
@mphair: Tak, jeśli weźmiesz pod uwagę tekst, który redaktor wstawia podczas pisania kursywą lub codeifytekstem, możesz po prostu wpisać go bezpośrednio w polu komentarza. Widziałem, jak niektórzy ludzie umieszczali hiperłącza, ale to dla mnie zbyt trudne.
TED,

2
@TED: Moją preferowaną notacją dla pętli do jawnego wyjścia jest "do {} while (1);". Nawiasem mówiąc, w systemach wbudowanych moim normalnym paradygmatem pętli jest „i = 10; do {} while (- i);”. W przypadku ciasnych pętli jest to znacznie szybsze niż typowe for (); Wydaje mi się, że bardziej czytelne jest używanie tego konsekwentnie jako wzorca pętli, gdy licznik w górę nie jest potrzebny, niż arbitralne użycie for () w przypadkach, które nie są krytyczne dla wydajności.
supercat

2
Dodając do while (1) i dla (;;) disusion, widziałem kod używający for (EVER) z EVER zdefiniowanym jako ;;
asr

6

Sytuacja, w której zawsze trzeba uruchomić fragment kodu raz i, w zależności od wyniku, prawdopodobnie więcej razy. To samo można również uzyskać za pomocą zwykłej whilepętli.

rc = get_something();
while (rc == wrong_stuff)
{
    rc = get_something();
}

do
{
    rc = get_something();
}
while (rc == wrong_stuff);

6

do whilejest jeśli chcesz uruchomić blok kodu przynajmniej raz. whilez drugiej strony nie zawsze będzie działać w zależności od określonych kryteriów.


5

To takie proste:

warunek wstępny a warunek końcowy

  • while (cond) {...} - warunek wstępny, wykonuje kod tylko po sprawdzeniu.
  • do {...} while (cond) - warunek końcowy, kod jest wykonywany co najmniej raz.

Teraz, gdy znasz sekret ... używaj ich mądrze :)


1
Jak powiedziałem, wiem, jak działają, po prostu nie byłem pewien, w jakim przypadku jeden ma sens nad drugim.
mphair

Logiczne jest, że używasz do {}, gdy wiesz na pewno, że musisz przynajmniej raz przejść przez pętlę. Tak jak na przykład, jeśli masz funkcję, która znajduje element w tablicy i wcześniej wstawiłeś if array.IsEmpty () then return; . Jeśli przeszedł pomyślnie ten test, możesz bezpiecznie użyć do {} while. Do-while jest również łatwe do wyobrażenia jako powtarzająca się pętla: „Jem, dopóki nie będę usatysfakcjonowany” oznacza do {zjad ();} podczas (! spełniony ();). Testowanie stanu przed pętlą jest uważane za bezpieczniejsze ... i dlatego jest częściej używane.
Ioan Paul Pirau

4

Widzę, że odpowiedź na to pytanie jest wystarczająca, ale chciałbym dodać ten bardzo konkretny scenariusz użycia. Możesz zacząć używać do ... while częściej.

do
{
   ...
} while (0)

jest często używany dla #defines wieloliniowych. Na przykład:

#define compute_values     \
   area = pi * r * r;      \
   volume = area * h

Działa to dobrze w przypadku:

r = 4;
h = 3;
compute_values;

-ale- jest gotcha na:

if (shape == circle)  compute_values;

ponieważ rozszerza się to do:

if (shape == circle) area = pi *r * r;
volume = area * h;

Jeśli zawiniesz go w pętlę do ... while (0), to poprawnie rozwinie się do pojedynczego bloku:

if (shape == circle)
  do
  {
    area = pi * r * r;
    volume = area * h;
  } while (0);

2

Dotychczasowe odpowiedzi podsumowują ogólne użycie określenia „do czasu”. Ale OP poprosił o przykład, więc oto jeden: Uzyskaj dane wejściowe użytkownika. Ale dane wejściowe użytkownika mogą być nieprawidłowe - więc pytasz o dane wejściowe, sprawdzaj je, kontynuuj, jeśli są prawidłowe, w przeciwnym razie powtórz.

Z do-while, otrzymujesz dane wejściowe, gdy dane wejściowe są nieprawidłowe. W przypadku zwykłej pętli while otrzymujesz dane wejściowe raz, ale jeśli jest nieprawidłowe, otrzymujesz je wielokrotnie, aż będzie prawidłowe. Nietrudno zauważyć, że ta pierwsza jest krótsza, bardziej elegancka i prostsza w utrzymaniu, jeśli korpus pętli staje się bardziej złożony.


Myślę, że powodem, dla którego nigdy ich do tego nie używałem, jest to, że zazwyczaj w końcu mówię użytkownikowi, co było nie tak z ich danymi wejściowymi. Co oznacza, że ​​warunek byłby w środku pętli i zrobiłbym to tylko breakwtedy, gdyby dane wejściowe były poprawne.
mphair

@mphair: Właściwie w tego rodzaju sytuacjach mogę czasami być bardziej skłonny do użycia „do {} while (0);” pętla, używając "kontynuuj"; jeśli dane wejściowe użytkownika są niedopuszczalne. Jeśli jest tylko jeden problem i komunikat, użyj "do {} while (1);" i "złamać"; działa dobrze, ale jeśli istnieje wiele powodów, aby domagać się ponownego wejścia, przycisk „kontynuuj”; konstrukcja działa ładniej.
supercat

2

Użyłem go do czytnika, który czyta tę samą strukturę wiele razy.

using(IDataReader reader = connection.ExecuteReader())
{
    do
    {
        while(reader.Read())
        {
            //Read record
        }
    } while(reader.NextResult());
}

1

Użyłem do whilekiedy czytam wartości wskaźnikowych na początku pliku, ale poza tym, nie sądzę, że to nienormalne, że struktura ta nie jest zbyt powszechnie used-- do-whiles są bardzo sytuacyjne.

-- file --
5
Joe
Bob
Jake
Sarah
Sue

-- code --
int MAX;
int count = 0;
do {
MAX = a.readLine();
k[count] = a.readLine();
count++;
} while(count <= MAX)

1

Oto moja teoria, dlaczego większość ludzi (w tym ja) preferuje pętle while () {} do wykonania {} while (): pętlę while () {} można łatwo dostosować do wykonywania pętli do..while (), podczas gdy jest odwrotnie to nie jest prawda. Pętla while jest w pewnym sensie „bardziej ogólna”. Również programiści lubią łatwe do uchwycenia wzorce. Pętla while mówi na samym początku, jaki jest jej niezmiennik i to jest fajna rzecz.

Oto, co mam na myśli, mówiąc o „bardziej ogólnej” rzeczy. Zrób to ... w trakcie pętli:

do {
 A;
 if (condition) INV=false; 
 B;
} while(INV);

Przekształcenie tego w pętlę while jest proste:

INV=true;
while(INV) {
 A;
 if (condition) INV=false; 
 B;
}

Teraz bierzemy model pętli while:

while(INV) {
     A;
     if (condition) INV=false; 
     B;
}

I przekształcić to w pętlę do..while, daje to potworność:

if (INV) {
 do
 {
         A;
         if (condition) INV=false; 
         B;

 } while(INV)
}

Teraz mamy dwa sprawdzenia na przeciwnych końcach i jeśli niezmienne zmiany, musisz zaktualizować je w dwóch miejscach. W pewnym sensie to ... a jednocześnie jest jak specjalistyczne śrubokręty w skrzynce narzędziowej, których nigdy nie używasz, ponieważ standardowy śrubokręt robi wszystko, czego potrzebujesz.


1
Zastępowanie "do {} while (x);" pętla do "while (x);" pętla wymaga, aby X był albo warunkiem, który można łatwo „przygotować”, albo aby był „naturalnie” prawdziwy w pierwszym przebiegu. Dla mnie instrukcje torujące przed pętlą oznaczają, że coś w pętli wymaga torowania, a pętla while (x) {}; "oznacza, że ​​pętla może wykonać zero razy. Jeśli chcę, aby pętla była wykonywana co najmniej raz, ja użyj pętli „do {} while (x);”.
supercat

1

Programuję około 12 lat i dopiero 3 miesiące temu spotkałem się z sytuacją, w której użycie do-while było naprawdę wygodne, ponieważ zawsze potrzebna była jedna iteracja przed sprawdzeniem warunku. Więc zgadnij, że twój wielki czas jest przed nami :).


1
Prawie za każdym razem, gdy widziałem problem ... podczas gdy było to spowodowane brakiem zrozumienia problemu i wiązało się z okropnym włamaniem. Są wyjątki, ale ogólnie rzecz biorąc, jeśli masz ochotę ... podczas gdy powinieneś przemyśleć to, co robisz ... :-)
Brian Knoblauch

2
Jeśli masz rację, to do-while zostałoby wykluczone ze wszystkich języków programowania, aby nie zmylić programistów :).
Narek

@Narek: Minęło już prawie siedem lat, odkąd to napisałeś, czy miałeś więcej okoliczności, w których do whilebyło lepsze rozwiązanie bez żalu?
chqrlie

Tak, chyba jeszcze raz: D
Narek

1

Nie mogę sobie wyobrazić, jak długo nie korzystałeś z domeny do...while pętli.

W tej chwili jest jeden na innym monitorze, aw tym programie jest wiele takich pętli. Wszystkie mają postać:

do
{
    GetProspectiveResult();
}
while (!ProspectIsGood());

1

Jest to dość powszechna struktura w serwerze / konsumencie:

DOWHILE (no shutdown requested)
   determine timeout
   wait for work(timeout)
   IF (there is work)
      REPEAT
          process
      UNTIL(wait for work(0 timeout) indicates no work)
      do what is supposed to be done at end of busy period.
   ENDIF
ENDDO

REPEAT UNTIL(cond)byciado {...} while(!cond)

Czasami oczekiwanie na pracę (0) może być tańsze z punktu widzenia procesora (nawet wyeliminowanie obliczania limitu czasu może być poprawą przy bardzo wysokich wskaźnikach dotarcia). Ponadto istnieje wiele wyników teorii kolejkowania, które sprawiają, że liczba obsługiwana w okresie o dużym natężeniu ruchu jest ważną statystyką. (Zobacz na przykład Kleinrock - tom 1.)

Podobnie:

DOWHILE (no shutdown requested)
   determine timeout
   wait for work(timeout)
   IF (there is work)
      set throttle
      REPEAT
          process
      UNTIL(--throttle<0 **OR** wait for work(0 timeout) indicates no work)
   ENDIF
   check for and do other (perhaps polled) work.
ENDDO

gdzie check for and do other workumieszczenie w głównej pętli może być niebotycznie drogie lub może jądro, które nie obsługuje wydajnej waitany(waitcontrol*,n)operacji typu, lub może sytuacja, w której kolejka z priorytetami może zagłodzić inną pracę, a przepustnica jest używana jako kontrola głodu.

Ten rodzaj balansowania może wydawać się hackem, ale może być konieczny. Ślepe użycie pul wątków całkowicie zniweczyłoby korzyści wydajnościowe wynikające z użycia wątku dozorcy z prywatną kolejką dla skomplikowanej struktury danych o wysokiej szybkości aktualizowania, ponieważ użycie puli wątków zamiast wątku opiekuna wymagałoby implementacji bezpiecznej dla wątków.

Naprawdę nie chcę wdawać się w debatę na temat pseudokodu (na przykład, czy żądane zamknięcie powinno być testowane w UNTIL) lub wątków opiekuna kontra pule wątków - ma to po prostu nadać posmak szczególnemu przypadkowi użycia strukturę przepływu sterowania.


1

To moja osobista opinia, ale to pytanie prosi o odpowiedź zakorzenioną w doświadczeniu:

  • Programuję w C od 38 lat i nigdy nie używam pętli do/ whilew zwykłym kodzie.

  • Jedynym przekonującym zastosowaniem tej konstrukcji są makra, w których można zawijać wiele instrukcji w jedną instrukcję za pośrednictwem pliku do { multiple statements } while (0)

  • Widziałem niezliczone przykłady pętli do/ whilez fałszywym wykrywaniem błędów lub nadmiarowymi wywołaniami funkcji.

  • Moje wyjaśnienie dla tej obserwacji jest takie, że programiści mają tendencję do nieprawidłowego modelowania problemów, gdy myślą w kategoriach do/ whilepętli. Albo przegapią ważny warunek końcowy, albo przegapią możliwą porażkę warunku początkowego, który przenoszą do końca.

Z tych powodów doszedłem do wniosku, że tamdowhile , gdzie jest pętla / , jest błąd i regularnie wzywam początkujących programistów do pokazania mi do/while pętli , w której nie mogę znaleźć błędu w pobliżu.

Tego typu pętli można łatwo uniknąć: użyj a for (;;) { ... }i dodaj niezbędne testy zakończenia, jeśli są one odpowiednie. Dość często potrzeba więcej niż jednego takiego testu.

Oto klasyczny przykład:

/* skip the line */
do {
    c = getc(fp);
} while (c != '\n');

To się nie powiedzie, jeśli plik nie kończy się znakiem nowej linii. Trywialnym przykładem takiego pliku jest plik pusty.

Lepsza wersja to:

int c;  // another classic bug is to define c as char.
while ((c = getc(fp)) != EOF && c != '\n')
    continue;

Alternatywnie ta wersja ukrywa również czmienną:

for (;;) {
    int c = getc(fp);
    if (c == EOF || c == '\n')
        break;
}

Spróbuj wyszukać while (c != '\n'); w dowolnej wyszukiwarce, a znajdziesz błędy takie jak ta (pobrano 24 czerwca 2017 r.):

Na ftp://ftp.dante.de/tex-archive/biblio/tib/src/streams.c , function getword(stream,p,ignore), ma do/ whilei na pewno wystarczające co najmniej 2 błędy:

  • cjest zdefiniowany jako a chari
  • istnieje potencjalna nieskończona pętla while (c!='\n') c=getc(stream);

Wniosek: unikaj do/ whilezapętlonych i szukaj błędów, gdy je zobaczysz.


1
Wolę unikać while() {}pętli, ponieważ są one bezpieczniejsze z punktu widzenia wstępnych kontroli, więc zazwyczaj wprawiają Cię w „odstresowanie”, leniwe myślenie o lepszym algorytmie, nakładanie mnóstwa nieoczekiwanego sprawdzania błędów itd. „Brak do{}while()użycia” to oznaką złego (bezmózgiego) lub początkującego programisty, produkującego niższą jakość i wolniejszy kod. Więc najpierw zadaję sobie pytanie „Czy jest to przypadek ściśle while () {}?” Jeśli nie - po prostu spróbuję napisać do pętli while, mimo że może to wymagać więcej przemyśleń i dokładności danych wejściowych
xakepp35

Rozumiem twoją odpowiedź, ale mógłbym wymyślić jakiś możliwy scenariusz, w którym tak naprawdę byłoby lepiej. Proszę spojrzeć na while: prnt.sc/v2zy04 lub at do while: prnt.sc/v2zyka . Nie byłem programistą tak długo, jak ty, ale nie mogłem znaleźć tam błędu. Jest całkiem jasne, że funkcja do while jest czystsza, ponieważ nie wymaga dodatkowego sprawdzania czasu, aby wejść do pętli. Jeśli mówimy o optymalizacji kodu, to powiedziałbym, że jest całkiem czysty.
Roy Berris

@RoyBerris: Twój przykład jest interesujący. W programie w C funkcje używane wewnątrz pętli wymagałyby dodatkowych testów, aby upewnić się, że nie zawiodły. Dodałoby to dodatkowy bałagan i / breaklub returninstrukcje. Przekształcenie pętli do/ whilew for(;;)pętlę będzie bardziej spójne IMHO. Mój ostracyzm wobec tego stwierdzenia jest podobny do mojej odmowy użycia strncpy() nawet w rzadkich przypadkach, w których byłoby to właściwe narzędzie: nie pozwól przypadkowym czytelnikom wyobrazić sobie, że te konstrukcje są nieszkodliwe, ponieważ w większości przypadków są źródłem trudnych do znalezienia błędów .
chqrlie

@chqrlie widząc, że oryginalna odpowiedź dotyczy dowolnego języka C, myślę, że obie odpowiedzi wystarczą. C # jest dużo bardziej "oszczędny" niż ich poprzednicy, więc technicznie byłoby szybsze z czasem.
Roy Berris

0

whilepętle sprawdzają stan przed pętlą, do...whilepętle sprawdzają stan po pętli. Jest to przydatne, jeśli chcesz oprzeć warunek na efektach ubocznych uruchomionej pętli lub, jak powiedzieli inni plakaty, jeśli chcesz, aby pętla uruchomiła się co najmniej raz.

Rozumiem, skąd pochodzisz, ale do-whilejest to coś, czego najczęściej używam rzadko, a ja nigdy nie używałem siebie. Nie robisz tego źle.

Nie robisz tego źle. To tak, jakby powiedzieć, że ktoś robi to źle, ponieważ nigdy nie używał byteprymitywu. Po prostu nie jest tak powszechnie używany.


0

Najczęstszym scenariuszem, na który napotykam, w którym używam pętli do/ while, jest mały program konsolowy, który działa w oparciu o pewne dane wejściowe i będzie powtarzany tyle razy, ile chce użytkownik. Oczywiście nie ma sensu, aby program konsoli działał bez czasu; ale poza pierwszym razem to zależy od użytkownika - stąd do/ whilezamiast tylko while.

Dzięki temu użytkownik może w razie potrzeby wypróbować kilka różnych wejść.

do
{
   int input = GetInt("Enter any integer");
   // Do something with input.
}
while (GetBool("Go again?"));

Podejrzewam, że programiści używają do/ whilecoraz mniej w dzisiejszych czasach, teraz, gdy praktycznie każdy program pod słońcem ma jakiś rodzaj GUI. Ma to większy sens w przypadku aplikacji konsolowych, ponieważ istnieje potrzeba ciągłego odświeżania danych wyjściowych w celu dostarczania instrukcji lub monitowania użytkownika o nowe informacje. Natomiast w przypadku GUI tekst dostarczający użytkownikowi tych informacji może po prostu znajdować się na formularzu i nigdy nie musi być powtarzany programowo.


0

Podczas wczytywania plików cały czas używam pętli do-while. Pracuję z wieloma plikami tekstowymi, które zawierają komentarze w nagłówku:

# some comments
# some more comments
column1 column2
  1.234   5.678
  9.012   3.456
    ...     ...

użyję pętli do-while, aby przeczytać wiersz „kolumna1 kolumna2”, aby móc wyszukać interesującą nas kolumnę. Oto pseudokod:

do {
    line = read_line();
} while ( line[0] == '#');
/* parse line */

Następnie zrobię pętlę while, aby przeczytać resztę pliku.


Obsługa komentarzy w dowolnym miejscu pliku byłaby trywialna (i ogólnie uprościłaby kod), gdybyś umieścił if (line[0]=='#') continue;zaraz po read_linewywołaniu w głównej pętli while ...
R .. GitHub STOP HELPING ICE

Nie widzę, jak mógłbym zrealizować twoją sugestię znalezienia nagłówka. (dang, nie mogę dowiedzieć się, jak sformatować kod w komentarzach, proszę o pomoc!) header = false; while (! nagłówek) {line = read_line (); if (wiersz [0] == '#') kontynuuj; nagłówek = prawda; } to jest dla ciebie jaśniejsze / prostsze?
leci

0

Będąc prymusem programistą, wiele moich szkolnych projektów programistycznych wykorzystywało interakcje oparte na menu tekstowym. Praktycznie wszyscy używali czegoś podobnego do następującej logiki dla głównej procedury:

do
    display options
    get choice
    perform action appropriate to choice
while choice is something other than exit

Od czasów szkolnych stwierdziłem, że częściej używam pętli while.


0

Jedną z aplikacji, które widziałem, jest Oracle, kiedy patrzymy na zestawy wyników.

Gdy już masz zestaw wyników, najpierw pobierasz z niego (wykonaj) i od tego momentu .. sprawdź, czy pobieranie zwróci element, czy nie (gdy element został znaleziony ..). To samo może mieć zastosowanie do każdego innego " fetch-like ”.


0

Użyłem go w funkcji, która zwróciła następną pozycję znaku w ciągu utf-8:

char *next_utf8_character(const char *txt)
{
    if (!txt || *txt == '\0')
        return txt;

    do {
        txt++;
    } while (((signed char) *txt) < 0 && (((unsigned char) *txt) & 0xc0) == 0xc0)

    return (char *)txt;
}

Zauważ, że ta funkcja jest napisana z pamięci i nie jest testowana. Chodzi o to, że i tak musisz zrobić pierwszy krok i musisz to zrobić, zanim będziesz mógł ocenić stan.


To paskudny sposób pisania *(unsigned char *)txt-0x80U<0x40.
R .. GitHub PRZESTAŃ POMÓC NA LODZIE

0

Dowolny rodzaj danych wejściowych konsoli działa dobrze z do-while, ponieważ pytasz za pierwszym razem i ponownie pytasz, gdy weryfikacja danych wejściowych nie powiedzie się.


0

Mimo że odpowiedzi jest tutaj wiele, to moja opinia. Wszystko sprowadza się do optymalizacji. Pokażę dwa przykłady, w których jeden jest szybszy niż drugi.

Przypadek 1: while

string fileName = string.Empty, fullPath = string.Empty;

while (string.IsNullOrEmpty(fileName) || File.Exists(fullPath))
{
    fileName = Guid.NewGuid().ToString() + fileExtension;
    fullPath = Path.Combine(uploadDirectory, fileName);
}

Przypadek 2: do while

string fileName = string.Empty, fullPath = string.Empty;

do
{
    fileName = Guid.NewGuid().ToString() + fileExtension;
    fullPath = Path.Combine(uploadDirectory, fileName);
}
while (File.Exists(fullPath));

Więc tam dwóch zrobi dokładnie to samo. Ale jest jedna fundamentalna różnica, a mianowicie, że chwila wymaga dodatkowego oświadczenia, aby wprowadzić chwilę. Co jest brzydkie, ponieważ powiedzmy, że każdy możliwy scenariusz tej Guidklasy został już przyjęty z wyjątkiem jednego wariantu. Oznacza to, że będę musiał 5,316,911,983,139,663,491,615,228,241,121,400,000powtarzać czas. Za każdym razem, gdy dotrę do końca mojego oświadczenia while, będę musiał string.IsNullOrEmpty(fileName)sprawdzić. Zajmie to trochę, mały ułamek pracy procesora. Ale zrób to bardzo małe zadanie razy możliwe kombinacjeGuid klasy i mówimy o godzinach, dniach, miesiącach lub dodatkowym czasie?

Oczywiście jest to skrajny przykład, ponieważ prawdopodobnie nie zobaczyłbyś tego w produkcji. Ale jeśli pomyślimy o algorytmie YouTube, jest bardzo prawdopodobne, że napotkaliby generację identyfikatora, w którym niektóre identyfikatory zostały już pobrane. Sprowadza się więc do dużych projektów i optymalizacji.


0

Lubię je rozumieć jako:
while-> „powtarzaj do”,
do ... while-> „powtarzaj, jeśli”.


-1

Natknąłem się na to, szukając właściwej pętli do wykorzystania w sytuacji, w której się znalazłem. Wierzę, że to w pełni zaspokoi typową sytuację, w której pętla do .. while jest lepszą implementacją niż pętla while (język C #, ponieważ stwierdziłeś, że jest to Twój podstawowy element do pracy).

Generuję listę ciągów na podstawie wyników zapytania SQL. Obiekt zwracany przez moje zapytanie to SQLDataReader. Ten obiekt ma funkcję o nazwie Read (), która przesuwa obiekt do następnego wiersza danych i zwraca wartość true, jeśli był inny wiersz. Zwróci false, jeśli nie ma innego wiersza.

Korzystając z tych informacji, chcę zwrócić każdy wiersz do listy, a następnie zatrzymać się, gdy nie ma więcej danych do zwrócenia. Pętla Do ... While działa najlepiej w tej sytuacji, ponieważ zapewnia, że ​​dodanie pozycji do listy nastąpi PRZED sprawdzeniem, czy jest inny wiersz. Powodem, dla którego należy to zrobić PRZED sprawdzeniem while (warunek) jest to, że gdy sprawdza, to również się rozwija. Użycie pętli while w tej sytuacji spowodowałoby pominięcie pierwszego wiersza ze względu na charakter tej konkretnej funkcji.

W skrócie:

To nie zadziała w mojej sytuacji.

    //This will skip the first row because Read() returns true after advancing.
    while (_read.NextResult())
           {
               list.Add(_read.GetValue(0).ToString());
           }

   return list;

To będzie.

    //This will make sure the currently read row is added before advancing.
    do
        {
            list.Add(_read.GetValue(0).ToString());
        } 
        while (_read.NextResult());

    return list;

Użycie SQLDataReader byłoby zagnieżdżonymi pętlami. Wewnętrzny wywołuje Read()i powinien być pętlą while, ponieważ pierwsze Read()wywołanie uzyskuje dostęp do pierwszego wiersza. Zewnętrzny wywołuje NextResult()i powinien być do czasu, ponieważ pierwszy wynikowy zestaw danych jest aktywny przed pierwszym NextResult()wywołaniem. Fragmenty kodu nie mają większego sensu, zwłaszcza, że ​​komentarze nie pasują do kodu i są błędne.
Ben Voigt

-1
Console.WriteLine("hoeveel keer moet je de zin schrijven?");
        int aantal = Convert.ToInt32(Console.ReadLine());
        int counter = 0;

        while ( counter <= aantal)
        {
            Console.WriteLine("Ik mag geen stiften gooien");
            counter = counter + 1;

Powinieneś odpowiedzieć na swoje pytanie tak, jakbyś miał odpowiedzieć na pytanie kogoś innego. Zawiera wyjaśnienie, a także kod
Simon
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.