vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


Woche 2

Tag 12

Fortgeschrittene Programmsteueru ng

Am Tag 5, »Grundlagen der Programmsteuerung,« haben Sie bereits einige Kontrollanweisungen von C kennen gelernt, mit deren Hilfe die Ausführung anderer Anweisungen im Programm gesteuert werden kann. Heute werden wir uns mit weiter fortgeschrittenen Aspekten der Programmsteuerung, einschließlich der goto- Anweisung und einiger interessanterer Anwendungen von Schleifen, beschäftigen. Heute lernen Sie:

Schleifen vorzeitig beenden

Am Tag 5 haben Sie gelernt, wie Sie mit der for-, der while- und der do...while- Schleife die Programmausführung kontrollieren können. Diese Schleifenkonstruktionen führen einen Block von C-Anweisungen nie, einmal oder mehr als einmal aus, je nachdem ob bestimmte Bedingungen in dem Programm erfüllt werden. In allen drei Fällen wird die Schleife nur beendet oder verlassen, wenn eine bestimmte Bedingung zutrifft.

Es kann jedoch vorkommen, dass Sie mehr Einfluss auf die Ausführung der Schleife nehmen wollen. Die Anweisungen break und continue bieten diese Möglichkeit der Einflussnahme.

Die break-Anweisung

Die break-Anweisung kann nur in dem Rumpf einer for-, while- oder do...while- Schleife verwendet werden. (Sie kann darüber hinaus auch in switch-Anweisungen verwendet werden, doch dazu komme ich erst weiter hinten in diesem Kapitel.) Wenn das Programm auf eine break-Anweisung trifft, wird die Ausführung der Schleife sofort abgebrochen. Sehen Sie dazu folgendes Beispiel:

for ( count = 0; count < 10; count++ )
{
if ( count == 5 )
break;
}

Die for-Schleife ohne die break-Anweisung würde 10-Mal ausgeführt. Beim sechsten Durchlauf ist jedoch count gleich 5, und die break-Anweisung wird ausgeführt. Sie bewirkt, dass die for-Schleife abgebrochen wird. Die Ausführung geht damit an die Anweisung über, die direkt auf die schließende geschweifte Klammer der for-Schleife folgt. Wenn eine break-Anweisung innerhalb einer verschachtelten Schleife steht, tritt das Programm lediglich aus der innersten Schleife aus.

Listing 12.1 veranschaulicht die Verwendung einer break-Anweisung.

Listing 12.1: Beispiel für eine break-Anweisung.

1:  /* Beispiel für eine break-Anweisung. */
2:
3: #include <stdio.h>
4:
5: char s[] = "Dies ist ein Test-String. Er enthält zwei Sätze.";
6:
7: int main(void)
8: {
9: int count;
10:
11: printf("\nOriginal-String: %s", s);
12:
13: for (count = 0; s[count]!='\0'; count++)
14: {
15: if (s[count] == '.')
16: {
17: s[count+1] = '\0';
18: break;
19: }
20: }
21: printf("\nGeänderter String: %s\n", s);
22:
23: return 0;
24: }

Original-String: Dies ist ein Test-String. Er enthält zwei Sätze.
Geänderter String: Dies ist ein Test-String.

Dieses Programm extrahiert den ersten Satz aus einem String. Es durchsucht den String zeichenweise nach dem ersten Punkt (der das Satzende markieren sollte). Dies geschieht in der for-Schleife in den Zeilen 13 bis 20. Zeile 13 startet die for-Schleife und inkrementiert count, so dass count als Index auf die Zeichen in dem String s fungieren kann. Zeile 15 überprüft, ob das aktuelle Zeichen in dem String ein Punkt ist. Wenn ja, wird direkt nach dem Punkt ein Nullzeichen eingefügt (Zeile 17). Damit wird der String abgeschnitten. Nachdem Sie den String abgeschnitten haben, benötigen Sie keinen weiteren Schleifendurchlauf mehr, wird die Schleife mit einer break-Anweisung (Zeile 18) schnell beendet und die Programmausführung mit der ersten Zeile nach der Schleife fortgesetzt (Zeile 21). Wird kein Punkt gefunden, bleibt der String unverändert.

Eine Schleife kann mehrere break-Anweisungen enthalten, von denen aber immer nur eine break-Anweisung ausgeführt wird (falls überhaupt). Wenn kein break ausgeführt wird, endet die Schleife normal (entsprechend ihrer Testbedingung). Abbildung 12.1 zeigt den Ablauf der break-Anweisung.

Abbildung 12.1:  Der Ablauf der break- und continue-Anweisung.

Die break-Anweisung

break;

break wird innerhalb einer Schleife oder einer switch-Anweisung verwendet. Die break-Anweisung bewirkt, dass die aktuelle Schleife (for, while oder do...while) oder switch-Anweisung unmittelbar verlassen wird, d.h. es gibt keine weiteren Schleifendurchläufe, und das Programm wird mit der ersten Anweisung nach der Schleife oder der switch-Anweisung forgesetzt.

Beispiel

int x;
printf ( "Zählt von 1 bis 10\n" );

/* ohne Abbruchbedingung in der Schleife würde diese endlos durchlaufen */
for( x = 1; ; x++ )
{
if( x == 10 ) /* Prüft, ob der Wert gleich 10 ist */
break; /* Beendet die Schleife */
printf( "\n%d", x );
}

Die continue-Anweisung

Wie die break-Anweisung kann auch die continue-Anweisung nur in dem Rumpf einer for-, einer while- oder einer do...while-Schleife verwendet werden. Die Ausführung einer continue-Anweisung bewirkt, dass der aktuelle Schleifendurchlauf abgebrochen und direkt der nächste Durchlauf der umschließenden Schleife begonnen wird. Die Anweisungen zwischen der continue-Anweisung und dem Ende der Schleife werden dabei übersprungen. Wie continue den Programmablauf verändert, ist in Listing 12.2 dargestellt. Achten Sie dabei auf die Unterschiede zu der break-Anweisung.

Das Programmbeispiel in Listing 12.2 liest eine über die Tastatur eingegebene Zeile ein und zeigt sie an, nachdem alle kleingeschriebenen Vokale entfernt wurden.

Listing 12.2: Beispiel für eine continue-Anweisung.

1:  /* Beispiel für eine continue-Anweisung. */
2:
3: #include <stdio.h>
4:
5: int main(void)
6: {
7: /* Deklariert den Puffer für die Eingabe und eine Zählervariable. */
8:
9: char puffer[81];
10: int ctr;
11:
12: /* Einlesen einer Textzeile. */
13:
14: puts("Geben Sie eine Textzeile ein:");
15: fgets(puffer,81,stdin);
16:
17: /* Durchläuft den String und zeigt nur die Zeichen an, */
18: /* die keine kleingeschriebenen Vokale sind. */
19:
20: for (ctr = 0; puffer[ctr] !='\0'; ctr++)
21: {
22:
23: /* Ist das Zeichen ein kleingeschriebener Vokal, gehe zurück */
24: /* an den Anfang der Schleife, ohne ihn anzuzeigen. */
25:
26: if (puffer[ctr] == 'a' || puffer[ctr] == 'e'
27: || puffer[ctr] == 'i' || puffer[ctr] == 'o'
28: || puffer[ctr] == 'u')
29: continue;
30:
31: /* Nur Nichtvokale anzeigen. */
32:
33: putchar(puffer[ctr]);
34: }
35: return 0;
36: }

Geben Sie eine Textzeile ein:
Dies ist eine Textzeile
Ds st n Txtzl

Auch wenn dieses Programm keinen besonderen praktischen Nutzen hat, zeigt es doch sehr wirkungsvoll den Einsatz der continue-Anweisung. Die Zeilen 9 und 10 deklarieren die Variablen des Programms. puffer[] nimmt den String auf, den der Anwender in Zeile 15 eingibt. Die andere Variable, ctr, inkrementiert durch die Elemente des Arrays puffer[], während die for-Schleife in den Zeilen 20 bis 34 nach Vokalen sucht. Eine if-Anweisung in den Zeilen 26 bis 28 prüft für jeden Buchstaben, ob es sich um einen kleingeschriebenen Vokal handelt. Ist dies der Fall, kommt es zur Ausführung der continue-Anweisung, die die Programmausführung zurück zu Zeile 20, der for-Schleife, schickt. Ist der Buchstabe kein Vokal, wird mit Zeile 33 fortgefahren. Zeile 33 enthält eine neue Bibliotheksfunktion, putchar(), die ein einzelnes Zeichen auf dem Bildschirm ausgibt.

Die continue-Anweisung

continue;

continue wird innerhalb von Schleifen verwendet. Diese Anweisung veranlasst, dass der Rest des aktuellen Schleifendurchlaufs übersprungen und das Programm mit dem nächsten Durchlauf fortgesetzt wird.

Beispiel:

int x;
printf("Gibt nur die geraden Zahlen von 1 bis 10 aus\n");
for( x = 1; x <= 10; x++ )
{
if( x % 2 != 0 ) /* Prüft, ob die Zahl nicht gerade ist */
continue; /* Springt zum nächsten Wert für x */
printf( "\n%d", x );
}

Die goto-Anweisung

Die goto-Anweisung ist eine der Sprung- oder Verzweigungsanweisungen in C, die nicht mit einer Bedingung verknüpft sind. Wenn ein Programm auf eine goto- Anweisung trifft, springt beziehungsweise verzweigt die Programmausführung direkt zu der Stelle, die in der goto-Anweisung spezifiziert wurde. Diese Anweisung bedarf keiner Bedingung, da die Ausführung immer verzweigt, wenn sie auf eine goto- Anweisung trifft. Die Verzweigung hängt nicht von der Erfüllung irgendwelcher Programmbedingungen ab (wie das zum Beispiel bei if-Anweisungen der Fall ist).

Das Ziel einer goto-Anweisung wird mit einer Sprungmarke (Label) gefolgt von einem Doppelpunkt angegeben. Eine Sprungmarke kann allein in einer Zeile stehen oder am Anfang einer Zeile, die eine C-Anweisung enthält. Die Sprungmarken eines Programms müssen eindeutig sein.

Das Ziel einer goto-Anweisung muss in der gleichen Funktion wie die goto-Anweisung stehen, aber nicht notwendigerweise im gleichen Block. Listing 12.3 zeigt ein einfaches Programm, in dem eine goto-Anweisung verwendet wird.

Listing 12.3: Beispiel für eine goto-Anweisung.

1: /* Beispiel für eine goto-Anweisung */
2:
3: #include <stdio.h>
4:
5: int main(void)
6: {
7: int n;
8:
9: start:
10:
11: puts("Geben Sie eine Zahl zwischen 0 und 10 ein: ");
12: scanf("%d", &n);
13:
14: if (n < 0 ||n > 10 )
15: goto start;
16: else if (n == 0)
17: goto location0;
18: else if (n == 1)
19: goto location1;
20: else
21: goto location2;
22:
23: location0:
24: puts("Ihre Eingabe lautete 0.\n");
25: goto ende;
26:
27: location1:
28: puts("Ihre Eingabe lautete 1.\n");
29: goto ende;
30:
31: location2:
32: puts("Sie haben einen Wert zwischen 2 und 10 eingegeben.\n");
33:
34: ende:
35: return 0;
36: }

Geben Sie eine Zahl zwischen 0 und 10 ein:
1
Ihre Eingabe lautete 1.
Geben Sie eine Zahl zwischen 0 und 10 ein:
9
Sie haben einen Wert zwischen 2 und 10 eingegeben.

Dies ist ein einfaches Programm, das eine Zahl zwischen 0 und 10 einliest. Wenn die eingegebene Zahl nicht zwischen 0 und 10 liegt, springt das Programm mit einer goto-Anweisung zurück zu start in Zeile 9. Andernfalls prüft das Programm in Zeile 16, ob die Zahl gleich 0 ist. Wenn ja, lässt eine goto-Anweisung in Zeile 17 die Ausführung mit location0 (Zeile 23) fortfahren, woraufhin in Zeile 24 ein Text ausgegeben und anschließend eine weitere goto-Anweisung ausgeführt wird. Die goto- Anweisung in Zeile 25 bewirkt, dass die Programmausführung zu dem Label ende, dem Ende des Programms, springt. Das Programm verfährt für die Eingabe des Wertes 1 und für alle Werte zwischen 2 und 10 nach den gleichen Regeln.

Die Sprungmarke einer goto-Anweisung kann entweder vor oder nach der Anweisung stehen. Die einzige, bereits erwähnte Einschränkung ist, dass goto und die Sprungmarke in der gleichen Funktion stehen müssen. Sie können jedoch unterschiedlichen Blöcken angehören. Mit goto-Anweisungen können Sie in Schleifen (beispielsweise for-Anweisungen) einsteigen oder aus ihnen aussteigen, aber Sie sollten das möglichst vermeiden. Um ehrlich zu sein, möchte ich Ihnen sogar davon abraten, je eine goto-Anweisung irgendwo in Ihren Programmen zu verwenden. Dafür gibt es zwei Gründe:

Es gibt Programmierer, die goto verwenden und dennoch perfekte Programme schreiben. Es kann auch immer mal Situationen geben, in denen der wohlüberlegte Einsatz von goto die einfachste Lösung eines Programmierproblems darstellt. Es ist jedoch nie die einzige Lösung. Wenn Sie diese Warnung schon ignorieren, sollten Sie zumindest Vorsicht walten lassen!

Was Sie tun sollten

Was nicht

Vermeiden Sie goto, wenn möglich (und es ist immer möglich!).

Verwechseln Sie nicht break und continue. break beendet eine Schleife, wohingegen continue den nächsten Durchlauf der Schleife startet.

Die goto-Anweisung

goto Ziel;

Ziel ist eine Label-Anweisung, die die Stelle im Programm identifiziert, zu der die Ausführung verzweigt. Eine Label-Anweisung besteht aus einem Bezeichner gefolgt von einem Doppelpunkt und, optional, einer C-Anweisung:

Ziel: eine C Anweisung;

Sie können das Label (die Sprungmarke) auch allein in eine Zeile setzen. Manche Programmierer lassen in einem solchen Fall eine Leeranweisung folgen (ein Semikolon). Dies ist aber nicht notwendig.

Ziel: ;

Endlosschleifen

Was ist eine Endlosschleife und wann bietet es sich an, eine solche Schleife in einem Programm zu verwenden? Unter Endlosschleifen versteht man Schleifen, die - für sich genommen - ohne Ende durchlaufen werden. Es kann sich dabei um eine for-, eine while- oder eine do...while-Schleife handeln. Wenn Sie zum Beispiel

while (1)
{
/* hier steht weiterer Code */
}

schreiben, erzeugen Sie eine Endlosschleife. Die Bedingung, die in der while-Schleife getestet wird, ist die Konstante 1, die immer wahr ist und nicht von dem Programm geändert werden kann. Da die Bedingung immer erfüllt ist, wird die Schleife nie beendet.

Im vorigen Abschnitt haben Sie gelernt, dass Sie mit der break-Anweisung eine Schleife verlassen können. Ohne break-Anweisung wäre eine Endlosschleife nutzlos. Mit break jedoch können Sie Endlosschleifen zu Ihrem Vorteil nutzen.

Endlosschleifen mit for oder do...while werden wie folgt erzeugt:

for (;;)
{
/* hier steht weiterer Code */
}
do
{
/* hier steht weiterer Code */
} while (1);

Das Prinzip ist für alle drei Schleifentypen das Gleiche. Für die Beispiele in diesem Abschnitt verwenden wir eine while-Schleife.

Endlosschleifen können beispielsweise verwendet werden, wenn man viele Abbruchbedingungen zu testen hat. Dabei kann es sich als kompliziert erweisen, alle Testbedingungen in Klammern nach der while-Anweisung anzugeben. Unter Umständen ist es einfacher, die Bedingungen einzeln im Rumpf der Schleife zu testen und dann bei Bedarf die Schleife mit einem break zu verlassen.

Eine Endlosschleife kann auch ein Menüsystem erzeugen, das den Ablauf Ihres Programms steuert. Erinnern wir uns an Tag 4, »Funktionen«, wo wir darüber gesprochen haben, dass die main()-Funktion oft als eine Art »Verkehrspolizist« fungiert, der die Ausführung der verschiedenen Funktionen, die die eigentliche Arbeit des Programms erledigen, dirigiert. Häufig wird dies durch Menüs unterstützt: Das Programm präsentiert dem Anwender eine Liste von Optionen, aus denen er eine auswählen muss (eine der verfügbaren Optionen sollte das Programm beenden). Nachdem eine Wahl getroffen wurde, sorgt eine geeignete Entscheidungsanweisung im Programm dafür, dass das Programm entsprechend der Benutzerauswahl fortgesetzt wird.

Listing 12.4 demonstriert die Verwendung eines Menüsystems.

Listing 12.4: Ein Menüsystem mit einer Endlosschleife implementieren.

1:  /* Beispiel für ein Menüsystem, das mit einer */
2: /* Endlosschleife implementiert wird. */
3: #include <stdio.h>
4: #define DELAY 1500000 /* Für Warteschleife. */
5:
6: int menue(void);
7: void warten(void);
8:
9: int main(void)
10: {
11: int option;
12:
13: while (1)
14: {
15:
16: /* Übernimmt die Auswahl des Anwenders. */
17:
18: option = menue();
19:
20: /* Verzweigt auf Basis der Eingabe. */
21:
22: if (option == 1)
23: {
24: puts("\nAufgabe A wird ausgeführt.");
25: warten();
26: }
27: else if (option == 2)
28: {
29: puts("\nAufgabe B wird ausgeführt.");
30: warten();
31: }
32: else if (option == 3)
33: {
34: puts("\nAufgabe C wird ausgeführt.");
35: warten();
36: }
37: else if (option == 4)
38: {
39: puts("\nAufgabe D wird ausgeführt.");
40: warten();
41: }
42: else if (option == 5) /* Ende des Programms. */
43: {
44: puts("\nSie verlassen das Programm...\n");
45: warten();
46: break;
47: }
48: else
49: {
50: puts("\nUngültige Option, versuchen Sie es noch einmal.");
51: warten();
52: }
53: }
54: return 0;
55: }
56:
57: /* Gibt ein Menü aus und liest die Auswahl des Anwenders ein. */
58: int menue(void)
59: {
60: int antwort;
61:
62: puts("\nGeben Sie 1 für Aufgabe A ein.");
63: puts("Geben Sie 2 für Aufgabe B ein.");
64: puts("Geben Sie 3 für Aufgabe C ein.");
65: puts("Geben Sie 4 für Aufgabe D ein.");
66: puts("Geben Sie 5 zum Verlassen des Programms ein.");
67:
68: scanf("%d", &antwort);
69:
70: return antwort;
71: }
72:
73: void warten( void )
74: {
75: long x;
76: for ( x = 0; x < DELAY; x++ )
77: ;
78: }

Geben Sie 1 für Aufgabe A ein.
Geben Sie 2 für Aufgabe B ein.
Geben Sie 3 für Aufgabe C ein.
Geben Sie 4 für Aufgabe D ein.
Geben Sie 5 zum Verlassen des Programms ein.
1

Aufgabe A wird ausgeführt.

Geben Sie 1 für Aufgabe A ein.
Geben Sie 2 für Aufgabe B ein.
Geben Sie 3 für Aufgabe C ein.
Geben Sie 4 für Aufgabe D ein.
Geben Sie 5 zum Verlassen des Programms ein.
6

Ungültige Option, versuchen Sie es noch einmal.

Geben Sie 1 für Aufgabe A ein.
Geben Sie 2 für Aufgabe B ein.
Geben Sie 3 für Aufgabe C ein.
Geben Sie 4 für Aufgabe D ein.
Geben Sie 5 zum Verlassen des Programms ein.
5

Sie verlassen das Programm...

In Zeile 18 wird eine Funktion namens menue() aufgerufen, die in den Zeilen 58 bis 71 definiert ist. menue() gibt ein Menü auf den Bildschirm aus, liest die Eingabe des Anwenders ein und übergibt sie an das Hauptprogramm. In main() wird der Rückgabewert von einer Reihe von verschachtelten if-Anweisungen getestet und die Programmausführung entsprechend verzweigt. In unserem Beispielprogramm werden nur Meldungen auf dem Bildschirm ausgegeben. In einem echten, praxisorientierten Programm würde der Code zur Lösung der jeweiligen Aufgabe wahrscheinlich eine passende Funktion aufrufen.

Das Programm enthält noch eine zweite Funktion mit Namen warten(). warten() wird in den Zeilen 73 bis 78 definiert und macht eigentlich nicht allzu viel, außer dass die for-Anweisung aus Zeile 76 ausgeführt wird. Die for-Schleife selbst tut auch nichts. Da sie aber DELAY-mal ausgeführt wird, sorgt sie für eine gewisse Verzögerung im Programmablauf.

Diese Art der Verzögerung wird auch als »busy-wait« bezeichnet, da die CPU des Computers für die Dauer der Verzögerung beschäftigt und besetzt ist. Auf einem Multiuser-Betriebssystem mit Multitasking wie Linux wäre es dumm die CPU damit zu beschäftigen, nichts zu tun, wenn sie eigentlich sinnvollere Arbeit erledigen könnte.

Besser ist es daher, unter Linux und anderen Unix-ähnlichen Betriebssystemen die sleep()-Funktion zu verwenden. Diese Funktion übergibt die Kontrolle über die CPU für eine bestimmte Zeit (die in Sekunden gemessen und als Argument an die Funktion übergeben wird) zurück an das Betriebssystem. Um sleep() verwenden zu können, muss Ihr Programm die Header-Datei unistd.h einbinden.

Die switch-Anweisung

Die wohl flexibelste Anweisung zur Steuerung des Programmflusses ist die switch- Anweisung. Mit ihr können Sie die weitere Programmausführung von Ausdrücken abhängig machen, die mehr als zwei Werte annehmen können. Frühere Programmsteueranweisungen, wie zum Beispiel if, waren auf boolesche Ausdrücke beschränkt, die nur einen von zwei Werten annehmen konnten: wahr oder falsch. Um den Programmfluss auf der Basis von mehr als zwei Werten zu steuern, mussten Sie, wie in Listing 12.4, mehrere verschachtelte if-Anweisungen verwenden. Mit der switch-Anweisung werden solche Verschachtelungen überflüssig.

Die allgemeine Form der switch-Anweisung lautet:

switch (Ausdruck)
{
case Konstante_1: Anweisung(en);
case Konstante_2: Anweisung(en);
...
case Konstante_n: Anweisung(en);
default: Anweisung(en);
}

In dieser Anweisung ist Ausdruck ein beliebiger Ausdruck, der zu einem Integer-Wert des Typs long, int oder char ausgewertet wird. Die switch-Anweisung wertet Ausdruck aus und vergleicht den Wert mit den Konstanten, die auf die case-Marken folgen. Diese drei Möglichkeiten ergeben sich:

In Listing 12.5 finden Sie ein Beispiel für eine switch-Anweisung, die eine Meldung auf der Basis der Benutzereingabe ausgibt.

Listing 12.5: Beispiel für eine switch-Anweisung.

1:  /* Beispiel für eine switch-Anweisung. */
2:
3: #include <stdio.h>
4:
5: int main(void)
6: {
7: int antwort;
8:
9: puts("Geben Sie eine Zahl zwischen 1 und 5 ein:");
10: scanf("%d", &antwort);
11:
12: switch (antwort)
13: {
14: case 1:
15: puts("Ihre Eingabe lautete 1.");
16: case 2:
17: puts("Ihre Eingabe lautete 2.");
18: case 3:
19: puts("Ihre Eingabe lautete 3.");
20: case 4:
21: puts("Ihre Eingabe lautete 4.");
22: case 5:
23: puts("Ihre Eingabe lautete 5.");
24: default:
25: puts("Nicht gültig, versuchen Sie es noch einmal.");
26: }
27:
28: return 0;
29: }

Geben Sie eine Zahl zwischen 1 und 5 ein:
2
Ihre Eingabe lautete 2.
Ihre Eingabe lautete 3.
Ihre Eingabe lautete 4.
Ihre Eingabe lautete 5.
Nicht gültig, versuchen Sie es noch einmal.

Hier liegt doch sicherlich ein Fehler vor? Es scheint, als ob die switch-Anweisung die erste übereinstimmende case-Marke findet und dann alle folgenden Anweisungen ausführt (und nicht nur die Anweisungen, die mit der case-Marke verbunden sind). Und genau das ist hier passiert, denn genau so soll switch arbeiten (als ob man mit goto-Anweisungen zu den übereinstimmenden case-Marken springen würde). Um sicherzustellen, dass nur die mit der übereinstimmenden case-Marke verbundenen Anweisungen ausgeführt werden, müssen Sie überall wo nötig eine break-Anweisung hinzufügen. Listing 12.6 enthält eine Neufassung des Programms mit den hinzugefügten break-Anweisungen. Damit funktioniert das Programm ordnungsgemäß.

Listing 12.6: Korrekte Verwendung von switch einschließlich aller benötigten break-Anweisungen.

1: /* Korrektes Beispiel für eine switch-Anweisung. */
2:
3: #include <stdio.h>
4:
5: int main(void)
6: {
7: int antwort;
8:
9: puts("\nGeben Sie eine Zahl zwischen 1 und 5 ein:");
10: scanf("%d", &antwort);
11:
12: switch (antwort)
13: {
14: case 0:
15: break;
16 case 1:
17: {
18: puts("Ihre Eingabe lautete 1.\n");
19: break;
20: }
21: case 2:
22: {
23: puts("Ihre Eingabe lautete 2.\n");
24: break;
25: }
26: case 3:
27: {
28: puts("Ihre Eingabe lautete 3.\n");
29: break;
30: }
31: case 4:
32: {
33: puts("Ihre Eingabe lautete 4.\n");
34: break;
35: }
36: case 5:
37: {
38: puts("Ihre Eingabe lautete 5.\n");
39: break;
40: }
41: default:
42: {
43: puts("Nicht gültig, versuchen Sie es noch einmal.\n");
44: }
45: } /* Ende der switch-Anweisung */
46: return 0;
47: }

Geben Sie eine Zahl zwischen 1 und 5 ein:
1
Ihre Eingabe lautete 1.
Geben Sie eine Zahl zwischen 1 und 5 ein:
6
Nicht gültig, versuchen Sie es noch einmal.

Kompilieren Sie diese Version und führen Sie sie aus. Jetzt hat alles seine Ordnung.

Häufig wird die switch-Anweisung dazu verwendet, Menüs wie in Listing 12.4 zu implementieren. Listing 12.7 verwendet switch anstelle von if zur Unterstützung des Menüs. switch-Anweisungen eignen sich dazu viel besser als verschachtelte if- Anweisungen, wie wir sie in der früheren Version des Menü-Programms (Listing 12.4) verwendet haben.

Listing 12.7: Realisierung eines Menüs mit Hilfe einer switch-Anweisung.

1 : /* Beispiel für die Implementierung eines Menüsystems mit */
2 : /* einer Endlosschleife und einer switch-Anweisung. */
3 : #include <stdio.h>
4 : #include <stdlib.h>
5 : #include <unistd.h>
6 :
7 : int menue(void);
8 :
9 : int main(void)
10: {
11:
12: while (1)
13: {
14: /*Liest die Anwendereingabe ein und verzweigt auf Basis der Eingabe.*/
15:
16: switch(menue())
17: {
18: case 1:
19: {
20: puts("\nAufgabe A wird ausgeführt.");
21: sleep(1);
22: break;
23: }
24: case 2:
25: {
26: puts("\nAufgabe B wird ausgeführt.");
27: sleep(1);
28: break;
29: }
30: case 3:
31: {
32: puts("\nAufgabe C wird ausgeführt.");
33: sleep(1);
34: break;
35: }
36: case 4:
37: {
38: puts("\nAufgabe D wird ausgeführt.");
39: sleep(1);
40: break;
41: }
42: case 5: /* Ende des Programms. */
43: {
44: puts("\nSie verlassen das Programm...\n");
45: sleep(1);
46: exit(0);
47: }
48: default:
49: {
50: puts("\nUngültige Option, versuchen Sie es noch einmal.");
51: sleep(1);
52: }
53: } /* Ende der switch-Anweisung */
54: } /* Ende der while-Schleife */
55: return 0;
56: }
57:
58: /* Gibt ein Menü aus und liest die Auswahl des Anwenders ein. */
59: int menue(void)
60: {
61: int antwort;
62:
63: puts("\nGeben Sie 1 für Aufgabe A ein.");
64: puts("Geben Sie 2 für Aufgabe B ein.");
65: puts("Geben Sie 3 für Aufgabe C ein.");
66: puts("Geben Sie 4 für Aufgabe D ein.");
67: puts("Geben Sie 5 zum Verlassen des Programms ein.");
68:
69: scanf("%d", &antwort);
70:
71: return antwort;
72: }

Geben Sie 1 für Aufgabe A ein.
Geben Sie 2 für Aufgabe B ein.
Geben Sie 3 für Aufgabe C ein.
Geben Sie 4 für Aufgabe D ein.
Geben Sie 5 zum Verlassen des Programms ein.
1

Aufgabe A wird ausgeführt.

Geben Sie 1 für Aufgabe A ein.
Geben Sie 2 für Aufgabe B ein.
Geben Sie 3 für Aufgabe C ein.
Geben Sie 4 für Aufgabe D ein.
Geben Sie 5 zum Verlassen des Programms ein.
6

Ungültige Option, versuchen Sie es noch einmal.

Geben Sie 1 für Aufgabe A ein.
Geben Sie 2 für Aufgabe B ein.
Geben Sie 3 für Aufgabe C ein.
Geben Sie 4 für Aufgabe D ein.
Geben Sie 5 zum Verlassen des Programms ein.
5

Sie verlassen das Programm...

In diesem Programm taucht eine neue Anweisung auf: der Aufruf der Bibliotheksfunktion exit() (Zeile 46), die in der case 5-Klausel zum Beenden des Programms verwendet wird. Sie können hier nicht wie in Listing 12.4 break verwenden. Mit break würden Sie lediglich die switch-Anweisung verlassen, aber nicht die umgebende while-Schleife. Im nächsten Abschnitt gehen wir ausführlicher auf die exit()-Funktion ein.

Manchmal kann es nützlich sein, die Ausführung durch mehrere case-Klauseln einer switch-Konstruktion »hindurchrutschen« zu lassen. Angenommen Sie wollten für mehrere mögliche Werte des Kontrollausdrucks den gleichen Anweisungsblock ausführen. Lassen Sie einfach die break-Anweisungen fort und listen Sie die case- Marken vor den Anweisungen auf. Wenn der Kontrollausdruck mit einer der case- Konstanten übereinstimmt, »rutscht« die Ausführung durch die folgenden case- Anweisungen, bis sie auf den Code-Block trifft, der ausgeführt werden soll. Ein Beispiel dafür finden Sie in Listing 12.8.

Listing 12.8: Eine weitere Möglichkeit für den Einsatz einer switch-Anweisung.

1:  /* Eine weitere Möglichkeit für den Einsatz einer switch-Anweisung. */
2:
3: #include <stdio.h>
4: #include <stdlib.h>
5:
6: int main(void)
7: {
8: int antwort;
9:
10: while (1)
11: {
12: puts("\nGeben Sie einen Wert zwischen 1 und 10 ein, 0 für Ende:");
13: scanf("%d", &antwort);
14:
15: switch (antwort)
16: {
17: case 0:
18: exit(0);
19: case 1:
20: case 2:
21: case 3:
22: case 4:
23: case 5:
24: {
25: puts("Ihre Eingabe lautete 5 oder kleiner.\n");
26: break;
27: }
28: case 6:
29: case 7:
30: case 8:
31: case 9:
32: case 10:
33: {
34: puts("Ihre Eingabe lautete 6 oder größer.\n");
35: break;
36: }
37: default:
38: puts("Zwischen 1 und 10 bitte!\n");
39: } /* Ende der switch-Anweisung */
40: } /* Ende der while-Schleife */
41: return 0;
42: }

Geben Sie einen Wert zwischen 1 und 10 ein, 0 für Ende:
11
Zwischen 1 und 10 bitte!

Geben Sie einen Wert zwischen 1 und 10 ein, 0 für Ende:
1
Ihre Eingabe lautete 5 oder kleiner.

Geben Sie einen Wert zwischen 1 und 10 ein, 0 für Ende:
6
Ihre Eingabe lautete 6 oder größer.

Geben Sie einen Wert zwischen 1 und 10 ein, 0 für Ende:
0

Dieses Programm liest einen Wert von der Tastatur ein und teilt Ihnen dann mit, ob der Wert bei 5 oder kleiner, bei 6 oder größer oder überhaupt nicht zwischen 1 und 10 liegt. Wenn der Wert 0 beträgt, wird in Zeile 18 ein Aufruf an die exit()-Funktion ausgeführt und das Programm damit beendet.

Die switch-Anweisung

switch (Ausdruck)
{
case Konstante_1: Anweisung(en);
case Konstante_2: Anweisung(en);
...
case Konstante_n: Anweisung(en);
default: Anweisung(en);
}

Mit der switch-Anweisung können Sie in Abhängigkeit vom Wert eines Ausdrucks in mehrere Anweisungsblöcke verzweigen. Diese Vorgehensweise ist effizienter und leichter nachzuvollziehen als eine tiefverschachtelte if-Anweisung. Eine switch- Anweisung wertet einen Ausdruck aus und verzweigt dann zu der case-Anweisung, deren Konstante mit dem Ergebnis des Ausdrucks übereinstimmt. Gibt es keine übereinstimmende case-Marke, springt die Programmsteuerung zu der Auffanganweisung default. Wenn keine default-Anweisung vorgesehen ist, springt die Programmsteuerung an das Ende der switch-Anweisung.

Der Programmfluss bewegt sich von der case-Anweisung kontinuierlich nach unten, es sei denn, eine break-Anweisung zwingt das Programm, an das Ende der switch- Anweisung zu springen.

Beispiel 1

switch( buchstabe )
{
case 'A':
case 'a':
printf( "Ihre Eingabe lautete A" );
break;
case 'B':
case 'b':
printf( "Ihre Eingabe lautete B");
break;
...
...
default:
printf( "Ich habe keine case-Anweisung für %c", buchstabe );
}

Beispiel 2

switch( zahl )
{
case 0: puts( "Ihre Zahl ist 0 oder kleiner.");
case 1: puts( "Ihre Zahl ist 1 oder kleiner.");
case 2: puts( "Ihre Zahl ist 2 oder kleiner.");
case 3: puts( "Ihre Zahl ist 3 oder kleiner.");
...
...
case 99: puts( "Ihre Zahl ist 99 oder kleiner.");
break;
default: puts( "Ihre Zahl ist größer als 99.");
}

Da es in diesem Beispiel für die ersten case-Anweisungen keine break-Anweisungen gibt, werden hier nach dem Sprung zu der übereinstimmenden case-Marke alle nachfolgenden Ausgaben ausgeführt, bis die Programmausführung auf das break der case 99-Klausel stößt. Wenn die Zahl beispielsweise 3 wäre, würde das Programm Ihnen mitteilen, dass Ihre Zahl gleich 3 oder kleiner, 4 oder kleiner, 5 oder kleiner bis hin zu 99 oder kleiner ist. Das Programm fährt mit der Ausgabe fort, bis es auf die break-Anweisung in case 99 trifft.

Was Sie tun sollten

Was nicht

Versehen Sie Ihre switch-Anweisungen mit default-Klauseln - auch wenn Sie davon überzeugt sind, dass Sie alle möglichen Fälle abgedeckt haben.

Verwenden Sie eine switch-Anweisung anstelle einer if-Anweisung, wenn mehr als zwei Bedingungen für die gleiche Variable ausgewertet werden.

Schreiben Sie die case-Anweisungen untereinander, so dass sie leicht zu lesen sind.

Verwenden Sie die sleep()-Funktion anstelle selbst implementierter Warteschleifen, wenn Sie vorübergehend die Ausführung Ihres Programms anhalten möchten.

Vergessen Sie nicht, wo nötig break- Anweisungen in Ihre switch-Anweisungen einzubauen.

Das Programm verlassen

Ein C-Programm ist normalerweise zu Ende, wenn die Ausführung auf die schließende geschweifte Klammer der main()-Funktion trifft. Sie können ein Programm jedoch jederzeit durch Aufruf der Bibliotheksfunktion exit() beenden. Sie können auch eine oder mehrere Funktionen angeben, die bei Programmende automatisch ausgeführt werden sollen.

Die Funktion exit()

Die Funktion exit() beendet die Programmausführung und gibt die Steuerung zurück an das Betriebssystem. Diese Funktion übernimmt ein einziges Argument vom Typ int, das sie an das Betriebssystem weiterreicht. Über dieses Argument kann man anzeigen, ob das Programm erfolgreich war oder nicht. Die Syntax der exit()- Funktion lautet:

exit(status);

Wenn status den Wert 0 hat, ist das ein Zeichen dafür, dass das Programm normal beendet wurde. Jeder andere Wert ungleich 0 bedeutet, dass das Programm mit irgendeinem Fehler beendet wurde. Dies kann sinnvoll sein, wenn Ihr C-Programm von einem anderen Programm ausgeführt wird, beispielsweise mit Hilfe der system()- Funktion, die wir im nächsten Abschnitt behandeln werden.

Um die Funktion exit() verwenden zu können, muss die Header-Datei stdlib.h eingebunden werden. Diese Header-Datei definiert zusätzlich zwei symbolische Konstanten, die als Argumente für die exit()-Funktion verwendet werden können:

#define EXIT_SUCCESS   0
#define EXIT_FAILURE 1

Um das Programm mit einem Rückgabewert von 0 zu verlassen, müssen Sie exit(EXIT_SUCCESS) aufrufen, für einen Rückgabewert von 1 exit(EXIT_FAILURE).

Was Sie tun sollten

Verwenden Sie den Befehl exit(), um das Programm zu verlassen, wenn ein Problem auftaucht.

Übergeben Sie der exit()-Funktion sinnvolle Werte.

Befehle aus einem Programm heraus ausführen

Die C-Standardbibliothek enthält eine Funktion namens system(), mit der Sie Befehle des Betriebssystems in einem laufenden C-Programm ausführen können. Dies kann nützlich sein, wenn Sie das Verzeichnis einer Platte lesen oder eine Diskette formatieren wollen. Um die Funktion system() zu verwenden, müssen Sie die Header- Datei stdlib.h in das Programm mit einbinden. Das Format von system() lautet:

system(befehl);

Das Argument befehl kann entweder eine String-Konstante oder ein Zeiger auf einen String sein. Um beispielsweise unter Linux den Inhalt eines Verzeichnisses auflisten zu lassen, können Sie schreiben:

system("ls");

oder

char *befehl = "ls";
system(befehl);

Nachdem der Betriebssystembefehl ausgeführt wurde, wird das Programm mit der Anweisung fortgesetzt, die auf den Aufruf von system() folgt. Wenn der Befehl, den Sie system() übergeben, kein gültiger Betriebssystembefehl ist, erhalten Sie die Fehlermeldung command not found (»Befehl nicht gefunden«). Ein Beispiel für die system()-Funktion finden Sie in Listing 12.9.

Listing 12.9: Mit der Funktion system() Systembefehle ausführen.

1:  /* Beispiel für den Einsatz der system()-Funktion. */
2: #include <stdio.h>
3: #include <stdlib.h>
4:
5: int main(void)
6: {
7: /* Deklariert einen Puffer für die Aufnahme der Eingabe. */
8:
9: char eingabe[40];
10:
11: while (1)
12: {
13: /* Liest den Befehl des Anwenders ein. */
14:
15: puts("\nSystembefehl eingeben (Eingabetaste für Ende)");
16: fgets(eingabe, 40, stdin);
17:
18: /* Ende, wenn eine Leerzeile eingegeben wurde. */
19:
20: if (eingabe[0] == '\n')
21: exit(0);
22:
23: /* Führt den Befehl aus. */
24:
25: system(eingabe);
26: }
27: return 0;
28: }

Systembefehl eingeben (Eingabetaste für Ende):
ls *.c

list1201.c list1203.c list1205.c list1207.c list1209.c
list1202.c list1204.c list1206.c list1208.c

Systembefehl eingeben (Eingabetaste für Ende):

Listing 12.9 veranschaulicht, wie man system() verwenden kann. Die while-Schleife in den Zeilen 11 bis 26 ermöglicht dem Programm die Ausführung von Betriebssystembefehlen. Die Zeilen 15 und 16 fordern den Anwender auf, einen Betriebssystembefehl einzugeben. Wenn der Anwender die Eingabetaste betätigt, ohne einen Befehl einzugeben, wird die Funktion exit() aufgerufen, die das Programm beendet (Zeilen 20 und 21). Zeile 25 ruft system() mit dem vom Anwender eingegebenen Befehl auf. Wenn Sie das Programm auf Ihrem System laufen lassen, wird Ihre Ausgabe selbstverständlich etwas anders aussehen.

Die Befehle, die Sie system() übergeben können, sind nicht nur auf einfache Betriebssystembefehle, wie Verzeichnisse ausgeben oder Platten formatieren, beschränkt. Sie können genauso gut den Namen einer x-beliebigen ausführbaren Datei oder Batch-Datei übergeben - und das Programm wird ganz normal ausgeführt. Wenn Sie zum Beispiel das Argument list1208 übergeben, würden Sie damit das Programm list1208 ausführen. Nach Beendigung dieses Programms kehrt die Ausführung dorthin zurück, von wo der system()-Aufruf erfolgt ist.

Die einzigen Beschränkungen, die es bei der Verwendung von system() gibt, betreffen den Speicher. Wenn system() ausgeführt wird, bleibt das ursprüngliche Programm im RAM und das Betriebssystem versucht, eine neue Kopie der Befehls-Shell (auf Linux- Systemen normalerweise bash) oder das Programm, das Sie ausführen, in den Arbeitsspeicher zu laden. Verständlicherweise ist dies nur möglich, wenn Ihr Computer über genug Speicherkapazität verfügt. Wenn nicht, erhalten Sie eine Fehlermeldung.

Zusammenfassung

Unser heutiges Kapitel behandelte eine Reihe von Themen, die mit der Programmsteuerung zu tun hatten. Sie haben die goto-Anweisung kennen gelernt und wissen jetzt, warum Sie sie in Ihren Programmen besser vermeiden sollten. Es wurden Ihnen die break- und die continue-Anweisung vorgestellt, mit denen Sie größeren Einfluss auf die Ausführung von Schleifen nehmen können. In Verbindung mit Endlosschleifen können diese Anweisungen schwierige Programmierprobleme lösen. Außerdem haben Sie gesehen, wie Sie mit der exit()-Funktion ein Programm beenden können. Abschließend wurde Ihnen gezeigt, wie Sie mit der system()- Funktion Systembefehle aus Ihrem Programm heraus ausführen können.

Fragen und Antworten

Frage:
Was ist besser, eine switch-Anweisung oder eine verschachtelte if-Anweisung?

Antwort:
Wenn Sie eine Variable prüfen, die mehr als zwei Werte annehmen kann, ist die switch-Anweisung fast immer die bessere Alternative. Der resultierende Code ist einfacher zu lesen. Wenn Sie eine wahr/falsch-Bedingung testen, ist die if-Anweisung vorzuziehen.

Frage:
Warum sollte man goto-Anweisungen vermeiden?

Antwort:
Auf den ersten Blick scheint es, als ob die goto-Anweisung eine recht nützliche Anweisung sei. goto kann jedoch mehr Probleme verursachen, als es behebt. Eine goto-Anweisung ist ein unstrukturierter Befehl, der Sie zu einem anderen Punkt in einem Programm springen lässt. Viele Debugger (Software, die Ihnen hilft, Probleme aufzuspüren) können goto-Anweisungen nicht ordentlich zurückverfolgen. Außerdem führen goto-Anweisungen zu Spaghetti-Code - Code ohne strukturieren Programmfluss.

Frage:
Ist es ratsam, mit der system()-Funktion Systemfunktionen auszuführen?

Antwort:
Die Funktion system() scheint auf den ersten Blick gut dafür geeignet, so etwas wie Dateien in einem Verzeichnis auszugeben. Aber Sie sollten vorsichtig damit sein. Die meisten Befehle eines Betriebssystems sind betriebssystemspezifisch. Wenn Sie diese Befehle zusammen mit einem Aufruf von system() verwenden, ist Ihr Code unter Umständen nicht mehr portierbar. Wenn Sie ein anderes Programm (statt eines Betriebssystembefehls) ausführen wollen, dürften Sie keine Schwierigkeiten mit der Portierung haben.

Workshop

Der Workshop enthält Quizfragen, die Ihnen helfen sollen, Ihr Wissen zu festigen, sowie Übungen, die Sie anregen sollen, das Gelernte umzusetzen und eigene Erfahrungen zu sammeln. Die Lösungen zu den Fragen und den Übungen finden Sie in Anhang C.

Quiz

  1. Wann ist es empfehlenswert, die goto-Anweisung zu verwenden?
  2. Worin besteht der Unterschied zwischen einer break- und einer continue- Anweisung?
  3. Was ist eine Endlosschleife und wie erzeugt man sie?
  4. Welche zwei Ereignisse beenden die Programmausführung?
  5. Zu welchen Variablentypen kann eine switch-Anweisung ausgewertet werden?
  6. Was bewirkt die default-Anweisung?
  7. Was bewirkt die exit()-Funktion?
  8. Was bewirkt die system()-Funktion?

Übungen

  1. Schreiben Sie eine Anweisung, die die Programmausführung veranlasst, zum nächsten Schleifendurchlauf zu springen.
  2. Schreiben Sie eine Anweisung, die die Programmausführung veranlasst, an das Ende der Schleife zu springen.
  3. Schreiben Sie eine Codezeile, die alle Dateien im aktuellen Verzeichnis auflistet.
  4. FEHLERSUCHE: Enthält der folgende Code einen Fehler?
    switch( antwort )
    {
    case 'J': printf("Ihre Antwort lautete Ja");
    break;
    case 'N': printf( "Ihre Antwort lautete Nein");
    }
  5. FEHLERSUCHE: Enthält der folgende Code einen Fehler?
    switch( option )
    {
    default:
    printf("Sie haben weder 1 noch 2 gewählt");
    case 1:
    printf("Ihre Antwort lautete 1");
    break;
    case 2:
    printf( "Ihre Antwort lautete 2");
    break;
    }
  6. Bilden Sie die switch-Anweisung aus Übung 5 mit if-Anweisungen nach.
  7. Implementieren Sie eine Endlosschleife mit do...while.
  8. Aufgrund der vielen möglichen Antworten gibt es zu den folgenden Übungen im Anhang keine Lösungen. Bearbeiten Sie diese Übungen für sich alleine.
  9. OHNE LÖSUNG: Schreiben Sie ein Programm, das wie ein Rechner arbeitet. Das Programm sollte Addition, Subtraktion, Multiplikation und Division beherrschen.
  10. OHNE LÖSUNG: Schreiben Sie ein Programm, das ein Menü mit fünf verschiedenen Optionen bereitstellt. Die fünfte Option soll zum Verlassen des Programms dienen. Die anderen vier Optionen sollen mit Hilfe der system()- Funktion verschiedene Systembefehle ausführen.


vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbackKapitelanfangnächstes Kapitel


© Markt&Technik Verlag, ein Imprint der Pearson Education Deutschland GmbH