6.5. Alternativen zu if

Unter bestimmten Umständen lassen sich if/else-Anweisungen durch bedingte Ausdrücke ersetzen. Solche Ausdrücke nutzen die Auswertungsreihenfolge der binären logischen Operatoren, um Bedingungen ohne das Schlüsselwort if zu formulieren. Eine häufiger genutzte, elegante Variante sind ternäre (also dreigliedrige) Ausdrücke unter Verwendung des Fragezeichen-Operators (Bedingungsoperators).

Geschachtelten if-Anweisungen nach dem Muster der else-if-Konstruktion kann außerdem die switch/case-Anweisung vorgezogen werden. Dies ist allerdings nur dann möglich, wenn aussschließlich die Gleichheit von Ausdrücken überprüft werden soll. Darüber hinaus muß es sich bei den case-Marken um Integer- oder char-Konstanten handeln. Beide, bedingte Ausdrücke und switch/case-Anweisungen sind also nicht als vollständiger Ersatz für if gedacht; sie bieten aber für bestimmte Anwendungsfälle die Möglichkeit, Formulierungen zur Steuerung des Programmablaufs eleganter und effizienter vorzunehmen.

Bedingte Ausdrücke

Einfache Formen bedingter Ausdrücke machen sich die Tatsache zunutze, daß die Auswertung von logischen Ausdrücken abgebrochen wird, sobald die Berücksichtigung weiterer Operanden am Resultat nichts mehr ändern wird. Beispielsweise ist es beim Ausdruck Operand1 && Operand2 nicht mehr notwendig, den Wahrheitswert des zweiten Operanden zu ermitteln, sobald Operand1 "falsch" ist: Der logische Gesamtwert dieses Ausdrucks kann ja nur dann "wahr" sein, wenn beide Operanden "wahr" sind. Umgekehrt erübrigt sich beim Ausdruck Operand1 || Operand2 die Auswertung von Operand2, sobald feststeht, daß Operand1 "wahr" ist: Dadurch ist der gesamte Ausdruck zwangsläufig "wahr". Wie kann diese Tatsache genutzt werden, um if zu ersetzen? Einfach indem die Bedingung als Operand1 auftritt und die Anweisung, die bei zutreffender Bedingung ausgeführt werden soll, als zweiter Operand dargestellt wird.


.
iCount == iMaxCount && printf("Fertig...");
.

Die printf()-Funktion kommt in diesem Beispiel nur dann zur Ausführung, wenn der Wert von iCount dem von iMaxCount entspricht; nur dann besteht ja überhaupt die Möglichkeit, daß der Gesamtausdruck "wahr" ist. Für den logischen Wert des ganzen Ausdrucks besteht hier offensichtlich keine Verwendung, und er wird deshalb verworfen.

Natürlich kann eine solche Konstruktion nur bei einfacheren Problemen Anwendung finden. Schon der Versuch, eine Entsprechung zu einer else-Verzweigung zu finden, macht die ganze Anweisung relativ schwer lesbar:


.
(iCount == iMaxCount && printf("Fertig!")) || printf("Bitte warten...");
.

Das zweite printf() kommt nur zur Ausführung, wenn der logische Wert des ersten Teilausdrucks (vor dem || ) "falsch" ist, d.h., wenn iCount ungleich iMaxCount ist. Ist der Wert beider Variablen gleich, dann wird das erste printf() ausgeführt. Dessen Rückgabewert entspricht der Anzahl der gedruckten Zeichen und ist daher wahr. Damit ist das Ergebnis der &&-Verknüpfung wahr und der zweite Operand der || — Verknüpfung, also das zweite printf(), muß nicht mehr ausgeführt werden.

Bedingte Ausdrücke dieser Form sind relativ selten zu finden. Gängig sind hingegen solche, in denen der Fragezeichen-Operator eingesetzt wird. Hierbei handelt es sich um den einzigen Operator in C, der drei Operanden verlangt. Das Fragezeichen steht nach dem ersten Ausdruck, der zweite wird vom dritten durch einen Doppelpunkt getrennt. In allgemeiner Form stellt sich dies so dar:


Bedingung ? Ausdruck_wenn_wahr : Ausdruck_wenn_falsch;

Der erste Ausdruck, also die Bedingung wird herangezogen, um zu entscheiden, welcher der beiden verbleibenden ausgeführt werden soll. Ist dieser wahr, kommt Ausdruck_wenn_wahr zum Zug, andernfalls Ausdruck_wenn_falsch. Zum Beispiel wird in


(fGehalt <=2500.0) ? (fZulage = 120.6) : (fZulage = 160.2);

der Variablen fZulage nur dann der Wert 120.6 zugewiesen, wenn fGehalt kleiner oder gleich 2500.0 ist; andernfalls erhält sie den Wert 160.2. Die Klammern um die einzelnen Ausdrücke sind nicht notwendig; sie dienen auch hier dazu, die Lesbarkeit und Verständlichkeit des Programms zu erhöhen. Offensichtlich kann dieser Ausdruck ohne weiteres mit Hilfe von if/else formuliert werden. Der Fragezeichen-Operator bewirkt im vorliegenden Beispiel das gleiche wie die folgende if/else-Anweisung:


.
.
if( fGehalt <=2500.0 )
	fZulage = 120.6;
else
	fZulage = 160.2;
.
.

Umgekehrt ist es nicht so ohne weiteres möglich, jede beliebige if/else-Anweisung durch Verwendung des Fragezeichen-Operators zu ersetzen. Es besteht nämlich die Einschränkung, daß als Operanden dieses Operators nur einzelne Ausdrücke und nicht ganze Anweisungsblöcke auftreten dürfen. Daher ist der Bedingungsoperator dort die richtige Wahl, wo Verzweigungen innerhalb des Programmablaufs kompakt formuliert werden sollen und die Verwendung von if weitaus umständlicher wäre. Ein typischer Anwendungsfall wäre der copy-Befehl von Windows 95 und NT: Egal ob eine oder mehrere Dateien kopiert werden, immer meldet er "x Datei(en) kopiert". Damit er abhängig von deren Anzahl den Singular oder Plural verwendet, könnte unter Einsatz des Bedingungsoperators folgende printf()-Anweisung herangezogen werden:


.
.
printf("\t%d Datei%s kopiert",
	uFileCount,
	uFileCount != 1 ? "en":"");
.
.

Nur wenn der Wert von uFileCount ungleich 1 ist, wird die Zeichenkette "en", also die Pluralmarkierung an "Datei" angehängt; andernfalls wird die leere Zeichenkette angefügt, d.h., es bleibt bei "Datei".

Der Fragezeichen-Operator erlaubt es, das Resultat des ausgeführten Zweigs gleich einer Variablen zuzuweisen. Für diesen Zweck sind sowohl der einfache Zuweisungsoperator als auch zusammengesetzte Zuweisungsoperatoren zulässig. Unser erstes Beispiel für den Bedingungsoperators ließe sich so kompakter formulieren:


.
.
fGehalt = 2460.23;
fZulage = (fGehalt <=2500.0) ? 120.6 : 160.2;
.
.

Bei dieser Schreibweise wird der Variablen fZulage der Wert 120.6 zugewiesen, da fGehalt kleiner oder gleich 2500 ist und so der erste Ausdruck zum Zuge kommt. Etwas komplizierter sieht ein bedingter Ausdruck bei Verwendung zusammengesetzter Zuweisungsoperatoren aus:


fPreis *= (fPreis <= 10000.0) ? 1.15 : 1.1;

Ist der Anfangswert von fPreis kleiner oder gleich 10000.0, dann beläuft sich der neue Wert von fPreis auf fPreis * 1.15; andernfalls wird fPreis mit 1.1 multipliziert.

Viele Bedingungen testen: Die switch Anweisung

Wie bereits erwähnt, findet die switch-Anweisung Verwendung, sobald eine Auswahl zwischen mehreren Alternativen zu treffen ist. Sie erfüllt damit den gleichen Zweck wie eine else-if-Kaskade; allerdings muß das Resultat des Ausdrucks, das mit verschiedenen Werten verglichen wird, ganzzahlig oder vom Typ char sein. Die einzelnen Vergleichswerte müssen ihrerseits auch ganzzahlig oder Zeichenkonstanten sein. In allgemeiner Form sieht die switch-Anweisung folgendermaßen aus:


switch( Ausdruck){
	case konstanter_Ausdruck_1:
		Anweisung1a;
	 	Anweisung1b;
		...
		...
	case konstanter_Ausdruck_2:
		Anweisung2a;
	 	Anweisung2b;
		...
		...
	case konstanter_Ausdruck_N:
		AnweisungNa;
	 	AnweisungNb;
		...
		...
	 default:
		AnweisungDa;
		 AnweisungDb;
		...
		...
}

Bei case handelt es sich wie bei switch und default u m reservierte Bezeichner in der Sprache C. Jede switch-Anweisung verfügt im allgemeinen über mehrere case-Marken, die default-Marke ist optional. Jede case-Marke muß sich durch ihren Wert von den anderen unterscheiden, d.h., es darf keine zwei case-Marken geben, die über den gleichen Wert verfügen. Die default-Marke steht üblicherweise am Ende einer switch-Anweisung, diese Anordnung ist jedoch nicht zwingend.

Bei der Abarbeitung einer switch-Konstruktion wird zuerst der Wert des Ausdrucks nach switch berechnet und dieser dann der Reihe nach mit den Werten der einzelnen case-Marken verglichen. Während die Bedingungen einer if-Anweisung alle logischen Operatoren sowie Vergleichs- und Äquivalenzoperatoren enthalten können, überprüft switch/case das Ergebnis des Kontrollausdrucks nur auf Gleichheit mit den einzelnen Werten hinter den case-Marken.

Ist das Ergebnis des switch-Ausdrucks mit dem Wert einer case-Marke identisch, dann gelangen alle Anweisungen, die mit diesem Label verbunden sind, zur Ausführung. Anschließend werden aber sämtliche Anweisungen, die mit den darauf folgenden case-Marken verbunden sind, ebenfalls ausgeführt. Nach einer Übereinstimmung werden also alle weiteren case-Marken so behandelt, als ob auch sie mit dem Vergleichsausdruck übereinstimmen würden!

Die Anweisungen nach default kommen immer dann zur Ausführung, wenn keiner der case-Werte zutrifft; fehlt die default-Marke und trifft kein case-Wert zu, dann wird die switch-Anweisung beendet, ohne daß eine Aktion stattfindet.


.
.
iCount = 2;

switch(iCount + 1){
	case 1: printf("eins\n");
	case 2: printf("zwei\n");
	case 3: printf("drei\n");
	case 4: printf("vier\n");
	case 5: printf("fünf\n");
	default: printf("default\n");
}
.
.

Der Kontrollausdruck ist der Wert von iCount plus 1. Das bedeutet, daß die Werte der einzelnen Labels, also die Konstanten 1 bis 5, mit 3 verglichen werden. Würde keine Übereinstimmung gefunden (wenn beispielsweise iCount den Wert 10 hätte), käme die Anweisung unter default zur Ausführung. Das obige Beispiel produziert folgende Ausgabe:


drei
vier
fünf
default

Die Erklärung für dieses Ergebnis ist einfach. Die switch/case-Anweisung funktioniert als Einsprungliste, die ab der ersten Übereinstimmung alle nachfolgenden Anweisungen ausführt. In diesem Fall ist die Bedingung der Gleichheit bei case 3 erfüllt. In der Folge kommen alle verbleibenden printf-Anweisungen zur Ausführung.

Anweisungen, die auf eine case-Marke folgen, können auch leer sein. Mit anderen Worten: Auf case-Marken müssen keine Anweisungen folgen. Erwartungsgemäß führt dann das Zutreffen eines solchen Werts zur Abarbeitung aller Anweisungen des nächsten case-Labels, da dessen Wert ja nicht mehr zum Vergleich herangezogen wird. Folgendes Fragment


.
.
iCount = 2;

switch(iCount){
	case 1: printf("eins\n");
	case 2: 
	case 3: printf("zwei oder drei\n");
	case 4: printf("vier\n");
	case 5: printf("fünf\n");
	 default: printf("default\n");
}
.
.

führt zu dieser Ausgabe:


zwei oder drei
vier
fünf
default

Der typische Anwendungsfall einer switch-Anweisung besteht in der Auswahl einer Bedingung unter vielen anderen. Deshalb ist die Eigenschaft von switch, nach dem Zutreffen einer Bedingung alle Anweisungen der darauffolgenden case-Marken auch auszuführen, meistens unerwünscht. Stattdessen sollen im allgemeinen nur die Anweisungen der zutreffenden case-Marke ausgeführt werden; anschließend kann die switch-Anweisung verlassen werden. Um dies zu erreichen, wird als letzte Anweisung einer case-Marke der Befehl break eingesetzt. Der Einsatz von break ist allerdings nicht auf switch/case-Konstruktionen beschränkt; break wird im Abschnitt 7.5 genauer besprochen.

Um nur den Wert der Variablen iCount auszugeben und danach abzubrechen, müßte das letzte Programmfragment folgendermaßen modifiziert werden:


.
.
iCount = 2;

switch(iCount){
	case 1:
		 printf("eins\n");
		break;
	case 2: /* wird von case 3 behandelt */
	case 3:
		 printf("zwei oder drei\n");
		break;
	case 4:
		printf("vier\n");
		 break;
	case 5:
		printf("fünf\n");
	 	break;
	default:
		 printf("default\n");
}
.
.

Beachten Sie, daß die Übereinstimmung des Werts von iCount mit case 2 nach wie vor dazu führt, daß die printf-Anweisung unter case 3 zur Ausführung gelangt, weil die Marke von case 2 über kein break verfügt. Dies ist hier beabsichtigt; in einem solchen Fall sollten Sie aber mit Hilfe eines Kommentars klarmachen, daß das Fehlen von break nicht auf ein Versehen zurückzuführen ist.

Folgendes Beispiel demonstriert den Einsatz von switch zur Umwandlung des Datumformats:


/********************************************************
 * switch.c                                             *
 * Übersetzt mit Hilfe von switch/case das Datumsformat *
 * tt/mm/jjjj in eine ausführlichere Schreibweise       *
 ********************************************************/

#include <stdio.h>
int main(void){

int iTag, iMonat, iJahr;

 printf("Bitte Datum (tt/mm/jjjj) eingeben:\n");
 scanf("%d%*c%d%*c%d", &iTag, &iMonat, &iJahr);
 /* Details dieses scanf()-Aufrufs unter 5.2.2 */

 printf("%d.", iTag);

 switch(iMonat){
	case 1: printf("Januar");
		break;
	case 2: printf("Februar");
		break;
	case 3: printf("März");
		break;
	case 4: printf("April");
		break;
	case 5: printf("Mai");
		break;
	case 6: printf("Juni");
		break;
	case 7: printf("Juli");
		break;
	case 8: printf("August");
		break;
	case 9: printf("September");
		break;
	case 10: printf("Oktober");
		break;
	case 11: printf("November");
		break;
	case 12: printf("Dezember");
		break;
	default: printf("Ungültige Monatsangabe!");
	}

 printf(" %d\n", iJahr);

return 0;
}

Besonderheiten von switch