7.5. Schleifensteuerung mit break, continue und goto
Mit den Anweisungen break, continue und goto bietet C weitere Möglichkeiten an, Kontrolle auf den Programmfluß und speziell auf den Ablauf von Schleifen auszuüben. Allerdings finden die drei Befehle in verschiedenen Zusammenhängen Anwendung. Den Einsatz von break kennen wir bereits von der switch-Anweisung; zusätzlich kann break in allen drei Schleifentypen benutzt werden. Der Gebrauch von continue beschränkt sich auf die Steuerung von Schleifen. Demgegenüber ist goto kein Mittel zur Schleifensteuerung im engeren Sinne. Nach allgemeiner Auffassung bieten aber nur tief verschachtelte Schleifen einen Anlaß, der den Einsatz von goto rechtfertigt.
Die break-Anweisung
Der Befehl break bewirkt, wie bereits im Kapitel 6 ausgeführt, daß eine switch/case-Konstruktion verlassen und das Programm bei der nächsten darauffolgenden Anweisung fortgesetzt wird. Recht ähnlich stellt sich der Einsatz von break in Schleifen dar: Die Ausführung dieses Befehls führt zum sofortigen Verlassen der Schleife und ebenfalls zur Fortsetzung bei der darauffolgenden Anweisung. Bei verschachtelten Schleifen ist zu beachten, daß nur diejenige Schleife verlassen wird, die den break-Befehl enthält. Die Kontrolle geht in diesem Fall auf die nächst-äußere Schleife über.
Der Einsatz von break in Schleifen trägt dazu bei, überladene Bedingungen zu vermeiden. Durch break sind Sie nicht mehr darauf angewiesen, sämtliche Ausdrücke in der Schleifenbedingung unterzubringen, sondern Sie können damit Ereignisse, die einen Abbruch bewirken sollen, innerhalb des Schleifenkörpers überprüfen. Freilich ist eine allein stehende break-Anweisung kaum von Nutzen, weil die betreffende Schleife dadurch immer schon während des ersten Durchlaufs abgebrochen würde. Um dies zu vermeiden, hängt ein break-Statement generell von einer if-Bedingung ab.
Mit Hilfe von break kann eine do-while-Schleife durch eine while-Schleife ersetzt werden. Der Vorgang dabei ist recht einfach: Es muß nur dafür gesorgt werden, daß die Schleifenbedingung nicht am Anfang, sondern am Ende der while-Schleife abgefragt wird. Natürlich kann man nicht verhindern, daß while die Bedingung der Schleifenanweisung immer zuerst abfragt. Man kann aber dafür sorgen, daß diese Bedingung immer den Wert "wahr" liefert und damit zumindest die einmalige Ausführung des Schleifenkörpers garantiert. Die wirklich relevante Bedingung findet sich zusammen mit einer break-Anweisung erst am Ende des Schleifenkörpers. Wir wollen dies praktisch an unserem Beispiel zur do-while-Schleife überprüfen:
/* yesno1.c
* Version von yesno.c, die unter Verwendung von
* "break" eine do-while Schleife durch ein while-
* Schleife ersetzt
*/
#include <stdio.h>
#include <ctype.h> /* wg toupper */
int main(void){
char cAnswer;
while(1){ /* trifft immer zu */
printf(" (J/N) ");
cAnswer = toupper( getchar());
/* eingelesenes Zeichen in Großbuchstaben
* umwandeln
*/
getchar();
/* verbleibenden Zeilenvorschub aus dem
* Tastaturpuffer lesen
*/
if( cAnswer == 'J' || cAnswer == 'N' )
/* Überprüfung auf ungültige Eingaben
* erfolgt erst am Ende der Schleife.
* Falls gültige Eingabe vorliegt, wird
* die Schleife durch "break" verlassen.
*/
break;
}
if( cAnswer == 'J')
return(0);
else
return(1);
}
Wie Sie hier schnell erkennen können, ist break das Mittel, mit dem man scheinbare Endlosschleifen doch noch mit einer Abbruchbedingung versehen kann. In unserem Beispiel würde while(1) nie zu einem Ende führen, da diese Bedingung immer "wahr" ist. Die if-Bedingung wurde hier als cAnswer == 'J' || cAnswer == 'N' formuliert, weil ihr Zutreffen ausschlaggebend für den Abbruch der Schleife ist. Demgegenüber bewirkt der Wert "wahr
"
in der do-while-Schleife deren Fortsetzung und die relevante Bedingung muß daher als cAnswer != 'J' && cAnswer != 'N' formuliert werden.Schließlich sei noch darauf hingewiesen, daß ein break innerhalb einer switch-Anweisung nicht den Abbruch einer Schleife bewirkt, sofern sich switch im Körper einer Schleife befindet:
/* w_space.c */
#include <stdio.h>
int main(void){
int iBlank=0, iTab=0, iReturn=0, iNoWhiteSpace=0;
char cInChar;
while( (cInChar = getchar()) != '.')
{
switch(cInChar){
case ' ': iBlank++; break;
/* dieses break sorgt für das
* Verlassen von switch, nicht
* aber für das Beenden der
* while-Schleife
*/
case '\t': iTab++; break;
case '\n': iReturn++; break;
default: iNoWhiteSpace++; break;
}
}
printf("Leerzeichen: %d\nTabulatoren: %d\n"
"Zeilenvorschübe: %d\nRest: %d\n",
iBlank, iTab, iReturn, iNoWhiteSpace);
return 0;
}
Vorzeitige Rückkehr zur Schleifenanweisung: continue
Ergibt die Auswertung einer Schleifenbedingung den Wert "wahr", dann werden alle Anweisungen des Schleifenkörpers ausgeführt. Es besteht aber die Möglichkeit, die Abarbeitung des Schleifenkörpers an einem beliebigen Punkt zu unterbrechen. Wie inzwischen bekannt, sorgt break dafür, daß alle nachfolgenden Anweisungen des Schleifenkörpers unberücksichtigt bleiben, da die Schleife sofort beendet wird. Alternativ dazu können Sie mit continue die verbleibenden Anweisungen übergehen, ohne die Schleife selbst abzubrechen. In diesem Fall kehrt die Ausführung des Programms zur Schleifenanweisung zurück, wo nach Prüfung der Schleifenbedingung der nächste Durchlauf gestartet wird.
Wie schon bei break, ist es wenig sinnvoll, continue als eigene, allein stehende Anweisung zu gebrauchen. In diesem Fall könnte man sich genauso gut alle nachfolgenden Anweisungen schenken, da diese ohnehin nie zur Ausführung kämen. Für die C-Praxis bedeutet dies, daß auch continue-Anweisungen sinnvollerweise immer von einer if-Bedingung abhängen müssen. Folgendes Beispiel demonstriert die Verwendung von continue:
#include <stdio.h>
int main(void){
int iCount;
printf("Werte von iCount:\n");
for( iCount = 1; iCount <=10; iCount++ )
{
if( iCount == 5 )
continue;
/* Wenn iCount den Wert 5 hat, dann wird
* die folgende printf()-Anweisung nicht
* mehr ausgeführt. Die Kontrolle kehrt
* sofort zur for-Anweisung zurück
*/
printf("%d ", iCount);
}
return 0;
}
Erwartungsgemäß produziert dieses Programm die folgende Bildschirmausgabe, bei der der Wert 5 fehlt:
Werte von iCount:
1 2 3 4 6 7 8 9 10
Die geächtete Sprunganweisung: goto
Für die Iteration stehen in C drei verschiedene Schleifenbefehle zur Verfügung, für Auswahl u.a. if und switch. Neben diesen Mitteln der Ablaufsteuerung existiert ein weiteres, das eine willkürliche Verzweigung innerhalb des Programms zuläßt: goto.
Wegen seiner Eigenart, die Programmstruktur zu zerbrechen, ist goto in Verruf geraten. Häufiger Einsatz von goto führt zu sogenanntem "Spaghetti-Code", der für das Gegenteil gut strukturierter und verständlicher Programme steht. Prinzipiell gibt es kein Programmierproblem, das nicht unter Verzicht auf goto zu lösen wäre. Es existiert allerdings ein Anwendungsfall, bei dem der Einsatz von goto als angemessen und darüber hinaus als einfachste Lösung gilt: das Verlassen tief verschachtelter Schleifen.
Die Verwendung von goto ist denkbar einfach: Das Befehlswort goto muß vom Namen einer gültigen Sprungmarke gefolgt werden: Dorthin verzweigt anschließend das Programm. Die Sprungmarke muß innerhalb der gleichen Funktion stehen, und ihr Name muß ein in C zulässiger Bezeichner sein. Die Sprungmarke selbst wird durch einen abschließenden Doppelpunkt gekennzeichnet. Beispiel:
.
.
goto weiter
printf("Dieser Programmteil kommt nicht zur Ausführung"
"da er durch \'goto\' übersprungen wird\n");
weiter:
.
.
Einen sinnvollen Einsatz von goto skizziert das folgende Fragment:
.
.
for(iCount1=1; iCount1 <=100; iCount1++)
{
for(iCount2=1; iCount2 <=100; iCount2++)
{
for(iCount3=1; iCount3 <=100; iCount3++)
{
/* Anweisungen des Schleifenkörpers */
if( bDesaster == TRUE )
goto abort;
}
}
}
abort: printf("Schwerwiegender Fehler!\n");
.
.
Obwohl es technisch möglich ist, wird beim Gebrauch von goto dringend davon abgeraten, direkt in den von einer Kontrollstruktur abhängigen Anweisungsblock zu springen. Es ist jedenfalls ausgesprochen schlechter Stil, z.B. über die Schleifenanweisung hinweg in den Schleifenkörper oder direkt in einen if- bzw. else-Zweig zu springen.