wake-up-neo.com

'do ... while' vs. 'while'

Mögliche Duplikate: 
While vs. Do While
Wann sollte ich do-while anstelle von while-Schleifen verwenden?

Ich programmiere nun schon eine Weile (2 Jahre Arbeit + 4,5 Jahre Studium + 1 Jahr Vor-Hochschulabschluss) und habe nie eine Do-While-Schleife benutzt, die im Kurs Einführung in die Programmierung zu kurz kam. Ich habe ein wachsendes Gefühl, dass ich falsch programmiere, wenn ich nie auf so etwas grundlegendes stoße.

Könnte es sein, dass ich einfach nicht in die richtigen Umstände geraten bin?

Was sind einige Beispiele, bei denen ein Do-while-Vorgang anstelle einer Weile erforderlich wäre?

(Meine Schulung war fast alles in C/C++ und meine Arbeit ist in C #. Wenn es also eine andere Sprache gibt, in der es absolut Sinn macht, weil Do-Whiles anders funktionieren, dann gelten diese Fragen nicht wirklich.)

Zur Klarstellung ... Ich kenne den Unterschied zwischen einer while und einem do-while. Während prüft die Beendigungsbedingung und führt dann Aufgaben aus. do-while führt Aufgaben aus und prüft dann die Beendigungsbedingung.

69
mphair

Wenn Sie möchten, dass die Schleife mindestens einmal ausgeführt wird. Es ist nicht üblich, aber ich benutze es ab und zu. Ein Fall, in dem Sie sie verwenden möchten, ist der Versuch, auf eine Ressource zuzugreifen, für die ein erneuter Versuch erforderlich ist, z.

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

} while (user says retry);
94
Bob Moore

do-while ist besser, wenn der Compiler bei der Optimierung nicht kompetent ist. do-while hat nur einen einzigen bedingten Sprung, im Gegensatz zu for und while, die einen bedingten Sprung und einen unbedingten Sprung haben. Bei CPUs, die über Pipelines verfügen und keine Verzweigungsvoraussage ausführen, kann dies die Leistung einer engen Schleife erheblich beeinflussen.

Da die meisten Compiler intelligent genug sind, um diese Optimierung durchzuführen, werden normalerweise alle im dekompilierten Code gefundenen Schleifen do-while sein (wenn der Dekompilierer sogar stört, Schleifen aus lokalen Rückwärts-Gotos zu rekonstruieren).

56
Ben Voigt

Do while ist nützlich, wenn Sie etwas mindestens einmal ausführen möchten. Ein gutes Beispiel für die Verwendung von do while vs. while: Nehmen wir an, Sie möchten Folgendes machen: Einen Taschenrechner.

Sie können dies mithilfe einer Schleife erreichen und nach jeder Berechnung überprüfen, ob die Person das Programm beenden möchte. Nun können Sie vermutlich davon ausgehen, dass die Person dies nach dem Öffnen des Programms mindestens einmal tun möchte, damit Sie Folgendes tun können:

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

Ich habe dies in einer TryDeleteDirectory-Funktion verwendet. Es war so etwas 

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

Dies ist eine Art indirekte Antwort, aber diese Frage brachte mich dazu, über die Logik dahinter nachzudenken, und ich dachte, dass dies eine Überlegung wert wäre.

Wie alle anderen schon gesagt haben, verwenden Sie eine do ... while-Schleife, wenn Sie den Body mindestens einmal ausführen möchten. Aber unter welchen Umständen möchten Sie das tun?

Nun, die offensichtlichste Klasse von Situationen, die ich mir vorstellen kann, wäre, wenn der anfängliche ("nicht grundierte") Wert der Prüfbedingung derselbe ist, als wenn Sie beenden möchten. Dies bedeutet, dass Sie den Schleifenkörper einmal ausführen müssen, um die Bedingung auf einen nicht vorhandenen Wert zu setzen, und dann die tatsächliche Wiederholung basierend auf dieser Bedingung durchführen. Bei so faulen Programmierern hat jemand beschlossen, dies in eine Kontrollstruktur zu packen.

Das Lesen von Zeichen von einem seriellen Port mit einem Timeout kann beispielsweise die Form (in Python) annehmen:

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)

Beachten Sie die Duplizierung des Codes: char_read = port.read(1). Wenn Python eine do ... while-Schleife hätte, hätte ich vielleicht Folgendes verwendet:

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

Der zusätzliche Vorteil für Sprachen, die einen neuen Geltungsbereich für Schleifen schaffen: char_read verschmutzt den Funktionsnamespace nicht. Beachten Sie aber auch, dass es einen besseren Weg gibt, dies zu tun, und zwar mit dem None-Wert von Python:

response_buffer = []
char_read = None

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

response = ''.join(response_buffer)

Hier ist der Kern meines Punktes: In Sprachen mit nullfähigen Typen tritt die Situation initial_value == exit_value weitaus seltener auf, und das kann der Grund dafür sein, dass Sie nicht darauf stoßen. Ich sage nicht, dass es nie passiert, denn es gibt noch Zeiten, zu denen eine Funktion None zurückgibt, um eine gültige Bedingung anzuzeigen. Nach meiner eiligen und kurz überlegten Meinung würde dies jedoch viel mehr passieren, wenn die von Ihnen verwendeten Sprachen keinen Wert zulassen würden, der besagt, dass diese Variable noch nicht initialisiert wurde.

Dies ist keine perfekte Argumentation: In der Realität bilden Null-Werte einfach ein weiteres Element der Menge gültiger Werte, die eine Variable annehmen kann. In der Praxis haben Programmierer jedoch eine Möglichkeit, zwischen einer im sensiblen Zustand befindlichen Variablen, die den Schleifenaustrittszustand enthalten kann, und der Uninitialisierung zu unterscheiden.

10
detly

Ich habe sie ziemlich oft in der Schule benutzt, aber seitdem nicht mehr so ​​oft. 

Theoretisch sind sie nützlich, wenn der Schleifenkörper vor der Exit-Bedingung einmal ausgeführt werden soll. Das Problem ist, dass in den wenigen Fällen, in denen ich die Überprüfung nicht zuerst durchführen möchte, in der Regel die Exit-Überprüfung in der Mitte des Schleifenkörpers statt am Ende. In diesem Fall ziehe ich es vor, die bekannte for (;;) mit einer if (condition) exit; irgendwo im Körper zu verwenden.

Wenn die Exit-Bedingung der Schleife etwas verwackelt ist, finde ich es manchmal nützlich, die Schleife als for (;;) {} mit einer Exit-Anweisung zu schreiben "Aufgeräumt" durch Verschieben von Initialisierungen, Beendigungsbedingungen und/oder Inkrementierungscode innerhalb der Klammern von for.

7
T.E.D.

do while ist, wenn Sie den Codeblock mindestens einmal ausführen möchten. while wird jedoch nicht immer in Abhängigkeit von den angegebenen Kriterien ausgeführt.

6
spinon

Eine Situation, in der Sie einen Code immer einmal ausführen müssen, je nach Ergebnis möglicherweise mehrmals. Dasselbe kann auch mit einer regulären while-Schleife erzeugt werden.

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

do
{
    rc = get_something();
}
while (rc == wrong_stuff);
6
Edward Leno

So einfach ist das:

vorbedingung vs. Nachbedingung

  • während (cond) {...} - Vorbedingung führt sie den Code erst nach der Überprüfung aus. 
  • do {...} while (cond) - Nachbedingungen wird der Code mindestens einmal ausgeführt.

Jetzt, wo du das Geheimnis kennst .. benutze sie weise :)

5
Ioan Paul Pirau

Ich sehe, dass diese Frage ausreichend beantwortet wurde, möchte aber dieses sehr spezifische Anwendungsszenario hinzufügen. Sie könnten do ... häufiger verwenden.

do
{
   ...
} while (0)

wird oft für mehrzeilige #defines verwendet. Zum Beispiel:

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

Das funktioniert gut für:

r = 4;
h = 3;
compute_values;

-aber- es gibt eine gotcha für:

if (shape == circle)  compute_values;

wie dies erweitert auf:

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

Wenn Sie es in eine do ... umwickeln, während (0) eine Schleife bildet, wird es ordnungsgemäß zu einem einzelnen Block erweitert:

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

Ich habe es für einen Leser verwendet, der dieselbe Struktur mehrmals liest.

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

Die bisherigen Antworten fassen die allgemeine Verwendung für das Do-While zusammen. Aber das OP hat nach einem Beispiel gefragt, also hier eines: Benutzereingabe abrufen. Die Eingaben des Benutzers sind jedoch möglicherweise ungültig. Sie müssen also nach Eingaben fragen, sie überprüfen und fortfahren, wenn sie gültig sind.

Mit do-while erhalten Sie die Eingabe, während die Eingabe nicht gültig ist. Mit einer normalen while-Schleife erhalten Sie die Eingabe einmal, aber wenn sie ungültig ist, erhalten Sie sie immer wieder, bis sie gültig ist. Es ist nicht schwer zu erkennen, dass die erstere kürzer, eleganter und einfacher zu pflegen ist, wenn der Körper der Schleife komplexer wird.

2
user395760

Ich programmiere vor etwa 12 Jahren und erst vor drei Monaten habe ich eine Situation getroffen, in der es wirklich praktisch war, do-while zu verwenden, da vor der Überprüfung einer Bedingung immer eine Iteration erforderlich war. Also denke mal, dass deine große Zeit voraus ist :).

1
Narek

Hier ist meine Theorie, warum die meisten Leute (einschließlich mir) while () {} -Schleifen bevorzugen, um {} while () zu tun: Eine while () {} -Schleife kann leicht angepasst werden, um wie eine do..while () -Schleife auszuführen, während das Gegenteil der Fall ist ist nicht wahr. Eine while-Schleife ist gewissermaßen "allgemeiner". Auch Programmierer mögen es einfach, Muster zu erfassen. Eine while-Schleife sagt gleich zu Beginn, was ihre Invariante ist und das ist eine nette Sache.

Hier meine ich das "allgemeinere" Ding. Nehmen Sie diese do..while-Schleife vor: 

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

Das Umwandeln in eine while-Schleife ist einfach: 

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

Nun nehmen wir ein Modell während einer Schleife:

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

Und verwandeln Sie dies in eine do..while-Schleife, die diese Monstrosität ergibt:

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

 } while(INV)
}

Jetzt haben wir zwei Überprüfungen an entgegengesetzten Enden. Wenn sich die Invariante ändert, müssen Sie sie an zwei Stellen aktualisieren. In gewisser Weise ist do..while wie die spezialisierte Schraubendreher im Werkzeugkasten, die Sie niemals verwenden, da der Standardschraubendreher alles tut, was Sie brauchen.

1

Ich kann mir nicht vorstellen, wie Sie ohne do...while-Schleife so lange gegangen sind.

Es gibt gerade einen auf einem anderen Monitor und es gibt mehrere solcher Loops in diesem Programm. Sie haben alle die Form:

do
{
    GetProspectiveResult();
}
while (!ProspectIsGood());
1
Loren Pechtel

Das häufigste Szenario, in dem ich eine do/while-Schleife verwende, ist ein kleines Konsolenprogramm, das auf der Grundlage einiger Eingaben ausgeführt wird und sich so oft wiederholt, wie es dem Benutzer gefällt. Natürlich macht es keinen Sinn, dass ein Konsolenprogramm keine Zeiten ausführt. Aber nach dem ersten Mal liegt es am Benutzer - daher do/while statt nur while.

Auf diese Weise kann der Benutzer bei Bedarf eine Reihe verschiedener Eingaben ausprobieren.

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

Ich vermute, dass Softwareentwickler heutzutage do/while immer weniger verwenden, da praktisch jedes Programm unter der Sun eine GUI hat. Bei Konsolen-Apps ist dies sinnvoller, da die Ausgabe ständig aktualisiert werden muss, um Anweisungen bereitzustellen oder den Benutzer mit neuen Informationen aufzufordern. Im Gegensatz dazu kann bei einer GUI der Text, der dem Benutzer diese Informationen liefert, einfach auf einem Formular platziert werden und muss niemals programmgesteuert wiederholt werden.

0
Dan Tao

Ich habe es in einer Funktion verwendet, die die nächste Zeichenposition in einem utf-8-String zurückgegeben hat:

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;
}

Beachten Sie, dass diese Funktion von Grund auf geschrieben und nicht getestet wurde. Der Punkt ist, dass Sie den ersten Schritt trotzdem machen müssen und Sie müssen es tun, bevor Sie den Zustand beurteilen können.

0
quinmars

while-Schleifen prüfen die Bedingung vor der Schleife, do...while-Schleifen prüfen die Bedingung nach der Schleife. Dies ist nützlich, wenn Sie die Bedingung auf Nebeneffekte der laufenden Schleife legen oder, wie in anderen Postern gesagt, die Schleife mindestens einmal ausführen soll.

Ich verstehe, woher du kommst, aber der do-while ist etwas, was die meisten selten verwenden, und ich habe mich selbst nie benutzt. Du machst es nicht falsch.

Du machst es nicht falsch. Das ist so, als würde man sagen, jemand macht es falsch, weil er niemals das byte-Primitiv verwendet hat. Es ist einfach nicht so üblich.

0
Rafe Kettler

Dies ist meine persönliche Meinung, aber diese Frage bittet um eine erfahrungsbasierte Antwort:

  • Ich programmiere seit 36 ​​Jahren in C und verwende niemals dowhile-Schleifen im regulären Code.

  • Die einzige zwingende Verwendung für dieses Konstrukt besteht in Makros, in denen es mehrere Anweisungen über eine do { multiple statements } while (0) in eine einzige Anweisung zusammenfassen kann.

  • Ich habe unzählige Beispiele für do/while-Schleifen mit gefälschter Fehlererkennung oder redundanten Funktionsaufrufen gesehen.

  • Meine Erklärung für diese Beobachtung ist, dass Programmierer dazu neigen, Probleme falsch zu modellieren, wenn sie in do/while-Schleifen denken. Sie verfehlen entweder eine wichtige Endbedingung oder sie versäumen den möglichen Ausfall der Anfangsbedingung, die sie zum Ende bringen.

Aus diesen Gründen bin ich zu der Überzeugung gelangt, dass _/wo es eine do/while-Schleife gibt, einen Fehler, und ich fordere Neulinge regelmäßig heraus, mir eine do/while-Schleife zu zeigen, bei der ich keinen Fehler in der Nähe erkennen kann.

Diese Art von Schleife kann leicht vermieden werden: Verwenden Sie eine for (;;) { ... } und fügen Sie die erforderlichen Abbruchtests hinzu, wenn sie geeignet sind. Es ist durchaus üblich, dass es mehr als einen solchen Test gibt.

Hier ist ein klassisches Beispiel:

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

Dies schlägt fehl, wenn die Datei nicht mit einem Zeilenumbruch endet. Ein triviales Beispiel für eine solche Datei ist die leere Datei.

Eine bessere Version ist diese:

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

Alternativ verbirgt diese Version auch die Variable c:

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

Suchen Sie in einer Suchmaschine nach while (c != '\n'); und Sie werden solche Fehler finden (abgerufen am 24. Juni 2017):

In ftp://ftp.dante.de/tex-archive/biblio/tib/src/streams.c , Funktion getword(stream,p,ignore), hat eine do/while und sicher genug mindestens 2 Fehler:

  • c ist definiert als char und
  • es gibt eine mögliche Endlosschleife while (c!='\n') c=getc(stream);

Fazit: Vermeiden Sie do/while-Schleifen und suchen Sie nach Fehlern, wenn Sie einen sehen.

0
chqrlie

Beim Einlesen von Dateien verwende ich ständig Do-while-Loops. Ich arbeite mit vielen Textdateien, die Kommentare in der Kopfzeile enthalten:

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

ich verwende eine do-while-Schleife, um bis zur Zeile "column1 column2" zu lesen, damit ich nach der interessierenden Spalte suchen kann. Hier ist der Pseudocode:

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

Dann mache ich eine while-Schleife, um den Rest der Datei durchzulesen.

0
flies

Ich habe einen do while verwendet, wenn ich einen Sentinel-Wert am Anfang einer Datei lese, aber abgesehen davon halte ich es nicht für ungewöhnlich, dass diese Struktur nicht allzu häufig verwendet wird --do-whiles sind wirklich situativ.

-- 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)
0
danyim

Jede Art von Konsoleneingabe funktioniert gut mit do-while, weil Sie beim ersten Mal Prompt eingeben und immer dann erneut auffordern, wenn die Eingabeüberprüfung fehlschlägt.

0
Lotus Notes

Als Geezer-Programmierer benutzten viele meiner Schulprogrammierungsprojekte Textmenü-gesteuerte Interaktionen. Praktisch alle verwendeten so etwas wie die folgende Logik für die Hauptprozedur:

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

Seit der Schulzeit habe ich festgestellt, dass ich die while-Schleife häufiger benutze.

0
GreenMatt

Eine der Anwendungen, die ich gesehen habe, ist in Oracle, wenn wir Ergebnismengen betrachten.

Wenn Sie eine Ergebnismenge festgelegt haben, holen Sie zuerst von ihr (do) ab und prüfen von dort aus, ob der Abruf ein Element zurückgibt oder nicht (solange das Element gefunden wurde ..). Abruf-ähnliche "Implementierungen.

0

Es ist eine ziemlich übliche Struktur in einem Server/Consumer:

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

die REPEAT UNTIL(cond) ist eine do {...} while(!cond)

Manchmal ist das Warten auf Arbeit (0) billiger im Hinblick auf die CPU (sogar das Weglassen der Zeitüberschreitungsberechnung kann eine Verbesserung bei sehr hohen Ankunftsraten sein). Darüber hinaus gibt es viele Warteschlangentheorie-Ergebnisse, die die Anzahl der in einer geschäftigen Periode gedienten Nummern zu einer wichtigen Statistik machen. (Siehe zum Beispiel Kleinrock - Band 1.)

Ähnlich:

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

wobei check for and do other work möglicherweise extrem teuer in die Hauptschleife oder in einen Kernel eingefügt werden kann, der keine effiziente Operation vom Typ waitany(waitcontrol*,n) unterstützt, oder möglicherweise eine Situation, in der eine priorisierte Warteschlange die andere Arbeit hungert und Drosseln als Hungersteuerung verwendet wird.

Diese Art des Ausgleichs kann wie ein Hack aussehen, kann aber notwendig sein. Die blinde Verwendung von Thread-Pools würde die Leistungsvorteile der Verwendung eines Caretaker-Threads mit einer privaten Warteschlange für eine mit einer hohen Aktualisierungsrate komplizierte Datenstruktur völlig zunichte machen, da die Verwendung eines Thread-Pools anstelle eines Caretaker-Threads eine Thread-sichere Implementierung erfordern würde.

Ich möchte wirklich nicht in eine Debatte über den Pseudo-Code (z. B. ob das angeforderte Herunterfahren in UNTIL getestet werden sollte) oder über Hausmeister-Threads im Vergleich zu Thread-Pools debattieren. Dies ist nur dazu gedacht, einen bestimmten Anwendungsfall zu beschreiben die Kontrollflussstruktur.

0
pgast