C-Tutorial (C oder C++, Vorwort, Installation, Kapitel 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13)

if-Anweisung und Ausdrücke

6.1. Kontrollstrukturen: if-Anweisung und Ausdrücke

Sie haben bereits gelernt, den Benutzer Eingaben machen zu lassen, konnten diese auch wieder ausgeben, doch auswerten konnten Sie die Eingaben bisher noch nicht. Dem EVA-Modell fehlt also noch das "V": Die Verarbeitung. Es fehlen Kontrollstrukturen, mit denen sich der Programmablauf steuern lässt. Dazu gehören Bedingte Anweisungen (if-Anweisung in C), Verzweigungen (switch-Anweisung) und Schleifen (for, while, do-while).

Davon werden Sie die if-Anweisung in diesem Kapitel kennenlernen. Die switch-Anweisung folgt in Kapitel 7, Schleifen in Kapitel 8. Daneben gibt es noch den ?:-Operator, dem Sie in Kapitel 7 begegnen werden.

Die if-Anweisung führt eine Programmierzeile oder einen Anweisungsblock nur dann aus, WENN eine bestimmte Bedingung erfüllt ist. Die Bedingung könnte etwa sein, dass die Variable i den Wert 10 enthält. Wenn also diese Bedingung erfüllt, i also 10 ist, dann soll etwas geschehen. Was geschieht, ist die Ausführung einer oder mehrerer Anweisungen (Zeilen). Die if-Anweisung hat in ihrer einfachsten Form folgende Syntax:

if (Bedingung)
  Anweisung;

In dieser Form wird EINE Anweisung (nicht mehrere!) ausgeführt, WENN die Bedingung, die zwischen den Klammern steht, erfüllt ist. Wichtig: Die Anweisung endet mit dem Semikolon ; ! Auch wenn nur das Semikolon vorhanden wäre, wäre das eine gültige if-Anweisung. Ist die Bedingung nicht erfüllt, geht die Programmausführung (darunter) normal weiter.

Sollen mehrere Anweisungen bei erfüllter Bedingung ausgeführt werden, müssen diese zu einem Anweisungsblock zusammengefasst werden. Den Anweisungsblock kennen Sie bereits von der main()-Funktion. Er beginnt mit { und endet mit }.

if (Bedingung)
{
  Anweisung1;
  Anweisung2;
  ...
  ...
}

Ich habe die Anweisungen im Anweisungsblock aus Gründen der Übersichtlichkeit eingerückt. Ich würde Ihnen empfehlen, Ähnliches zu tun, zwingend sind die Einrückungen natürlich nicht. Bei verschachtelten if-Anweisungen (siehe weiter unten) bleibt Ihnen allerdings kaum etwas anderes übrig.

Die if-Anweisung kann noch um einen else-Block erweitert werden:

if (Bedingung)
  Anweisung;
else
  Ersatzanweisung;

Ist die Bedingung erfüllt, so wird die Anweisung nach if ausgeführt. Ist diese allerdings nicht erfüllt, wird die Anweisung nach else ausgeführt. Sollen mehrere Anweisungen ausgeführt werden, müssen diese zu einem Anweisungsblock zusammengefasst werden:

if (Bedingung)
{
  Anweisung1;
  Anweisung2;
  ...
  ...
}
else
{
  Ersatzanweisung1;
  Ersatzanweisung2;
  ...
  ...
}

Sehen Sie sich einmal folgendes Beispiel an:

if (i < 10)
  printf ("i kleiner als 10");
else
  printf ("i größer oder gleich 10");

Die Bedingung ist hier i < 10, i < 10 ist aber nicht nur eine Bedingung, sondern allgemein ein Ausdruck. Ein Ausdruck ist im Grunde eine Aneinanderreihung von Zeichen (Operatoren und Operanden) unter Beachtung einer Syntax (Grammatik). Also so ziemlich alles. Ein wesentliches Merkmal eines Ausdrucks ist, dass dieser einen Wert repräsentiert. Ist der Wert direkt erkennbar (konstant), wie etwa bei 'c', 23 oder "Hello World", bezeichnet man das als Elementarer Ausdruck (oder Atomarer Ausdruck). Diese Ausdrücke können nicht weiter zerlegt werden.

Die zweite Form sind Zusammengesetzte Ausdrücke. Diese entstehen, indem elementare Ausdrücke mit Operatoren verknüpft werden. Also etwa i < 10, 3 + 3 oder (a)*(a). Wichtig auch hier: Der Ausdruck besitzt einen Wert! Der Wert ist aber nicht unmittelbar erkennbar, sondern muss erst berechnet werden. Er ist aber auch hier eine wesentliche Eigenschaft des Ausdrucks.

Interessant ist an dieser Stelle eine Sonderform der Ausdrücke: Boole'sche Ausdrücke. Boole'sche Ausdrücke können nur zwei verschiedene Werte annehmen: WAHR oder FALSCH. Da es in C keinen boole'schen Datentyp gibt, behilft man sich eines Integers. 0 (Null) ist false (falsch) und alles davon Abweichende (meist 1) ist true (wahr).

Die Bedingung einer if-Anweisung ist natürlich auch ein Ausdruck. Diese kann wahr (true) oder falsch (false) sein. Im oberen Beispiel wird mit i < 10 überprüft, ob der Wert, den i gerade besitzt, kleiner als 10 ist. Ist er das, dann ist die Bedingung erfüllt (wahr) und die Anweisung nach if wird ausgeführt (bzw. der Anweisungsblock). Ist die Bedingung nicht erfüllt (falsch), wird die Anweisung (der Anweisungsblock) nach else ausgeführt.

In Kapitel 4 habe ich versprochen, in diesem Kapitel einige Operatoren zu behandeln. Das möchte ich hier in Form verschiedener Ausdrücke tun. Auf Ausdrücke als formelles, mathematisches Thema mit seinen feineren Unterscheidungen werde ich aber nicht näher eingehen. Interessierte finden hier weitere Informationen.

Das Schwierigste an der if-Anweisung dürfte für Anfänger die Bedingung (der Ausdruck; ich verwende beide Begriffe im Folgenden synonym) sein. Hierfür gibt es die Vergleichsoperatoren < > <= >= == != ! && || . Der GESAMTAUSDRUCK ist dann entweder wahr (erfüllt) oder falsch (nicht erfüllt).

Beginnen wir mit dem Gleichheitsoperator ==, wie im folgenden Beispiel gezeigt:

if (a == 10)
  printf ("a gleich 10!");

Hiermit überprüfen wir, ob der linke Wert (auch L-Wert genannt) gleich dem rechten (R-Wert) ist, also ob die Variable a den Wert 10 enthält. WICHTIG: Verwechseln Sie den Gleichheitsoperator == (doppeltes Ist-Gleich-Zeichen) nicht mit dem Zuweisungsoperator = (einfaches Ist-Gleich-Zeichen)! Unterschätzen Sie diesen Fehler nicht, denn er tritt sehr häufig auf.

Das genaue Gegenteil macht der Operator !=. Der Ausdruck ist dann wahr, wenn der linke Wert ungleich dem rechten, also von diesem verschieden, ist.

if (a != 10)
  printf ("a ist ungleich 10 (kleiner oder größer als 10)!");

Mit dem Operator < können Sie überprüfen, ob der linke Wert kleiner ist, mit >, ob der linke Wert größer ist, als der rechte.

if (a < 10)
  printf ("a ist kleiner als 10!");

if (a > 10)
  printf ("a ist groesser als 10!");

Weiters existieren die Operatoren <= für Kleiner-Gleich (linker Wert kleiner oder gleich groß wie der rechte) und >= (linker Wert größer oder gleich dem rechten).

if (a <= 10)
  printf ("a ist entweder kleiner als 10 oder gleich 10!");

if (a >= 10)
  printf ("a ist entweder groesser als 10 oder gleich 10!");

So weit, so einfach. Es lassen sich aber auch mehrere Teilbedingungen miteinander verknüpfen. Dazu gibt es die Logischen Operatoren ! && || . Diese repräsentieren ein logisches NOT (!), AND (&&) und OR (||). Fangen wir mit einem einfachen Beispiel an, bei dem zwei Teilausdrücke (zwei Ausdrücke) UND-verknüft sind:

if ( (a == 10) && (b < 20) )
  printf ("a ist 10 UND b ist kleiner als 20!");

Dieser Ausdruck lässt sich zweiteilen. Zum einen wird (a == 10) geprüft, zum anderen (b < 20). Beide Ausdrücke sind mit && (logisches UND) miteinander verknüft. Der GESAMTAUSDRUCK, der sich daraus ergibt, ist nur dann wahr, wenn BEIDE Ausdrücke (rechts und links vom &&) wahr sind! Wichtig ist hier, dass Sie die Klammern richtig setzen, damit auch wirklich das zusammengehört, was Sie beabsichtigen. Beachten Sie also die Priorität der Operatoren!

Ein Ausdruck, der immer wahr ist, ist der im nächsten Beispiel:

if (1)
  printf ("Immer wahr!");

Alles, was von 0 (Null) verschieden ist, wird in C als wahr angesehen. Also auch z.B. -7 und +34.

Im nächsten Beispiel werden zwei Ausdrücke mit einem logischen ODER (||) verknüft. Damit der Gesamtausdruck wahr ist, reicht es, wenn einer von beiden wahr ist. Es können aber auch beide wahr sein (obwohl der zweite dann gar nicht mehr geprüft wird!). Nur wenn gar keiner von beiden wahr ist, also beide falsch sind, ist der Gesamtausdruck auch falsch.

if ( (a == 10) || (b < 20) ) 
  printf ("a ist gleich 10 ODER b ist kleiner als 20 ODER beides!");

Der !-Operator negiert einen Ausdruck. Dazu einige Beispiele:

if (!a)
{
  printf ("a ist NICHT wahr!\n");
  printf ("Wahr ist alles ungleich 0. Da a falsch ist, muss a gleich Null (0) sein.");
}

Beachten Sie, dass der !-Operator zu den unären Operatoren gehört. Hier ist der zu ! gehörende Operand die Variable a. Der Ausdruck ist dann wahr, wenn a selbst falsch ist, das heißt, den Wert 0 besitzt. Ein weiteres Beispiel:

if (!(a == 5))
  printf ("a ist NICHT 5!");

Der Ausdruck !(a == 5) ist dann wahr, wenn (a == 5) nicht wahr, also falsch ist. Dies ließe sich einfacher mit

if (a != 5)
  printf ("a ist NICHT 5!");

realisieren. Nun einige komplexere Beispiele:

if ( ((a == 5) || (b <= 10)) && (a * b) )
{
  printf ("a ist 5 und/oder b ist kleiner oder gleich 10.\n");
  printf ("a * b ist außerdem ungleich 0.");
}

Damit es hier einfacher wird, habe ich die "äußeren" Klammern farblich markiert. Zuerst wird der geklammerte Ausdruck ((a == 5) || (b <= 10)) ausgewertet. Ist dieser wahr, dh. ist a gleich 5 und/oder b kleiner oder gleich 10, dann wird verglichen, ob auch (a * b) einen wahren, daher einen Wert ungleich Null enthält. Lässt man die Klammern weg ...

if ( (a == 5) || (b <= 10) && (a * b) )
  printf ("Was passiert hier ???");

... so geht nicht auf den ersten Blick hervor, was denn nun zusammengehört. Da allerdings && eine höhere Priorität als || hat, wird (b <= 10) && (a * b) zuerst überprüft, und erst dann wird dessen Ergebnis (wahr oder falsch) mit (a == 5) verglichen.

if ( !((a == 5) || (b <= 10)) && (0) )
  printf ("Weder a ist 5 noch b kleiner als 10 UND 0 ist ungleich 0! Aha!");

Lange Überlegungen dürfen hier entfallen, denn diese Bedingung wird nie erfüllt werden. Dazu müsste der Ausdruck (0) - irgendwann - wahr werden.

Bisher nur kurz erwähnt habe ich die Operatoren ++ und --. Um etwa den Wert einer Variable um 1 zu verringern (dekrementieren), gibt es zwei Möglichkeiten. 1. Post-Version mit dem Operator nach dem Operanden:

i--;

Oder 2.: Die Prä-Version mit dem Operator vor dem Operanden:

--i;

Beides hat in diesem Fall dieselbe Wirkung. Hätte i zuvor etwa den Wert 10 gehabt, wäre dieser nach einer der beiden Anweisungen nun 9. Gleiches funktioniert mit ++, wenn Sie einen Wert um 1 erhöhen (inkrementieren) wollen.

i++;

++i;

Die Anweisungen sind gleichwertig zu:

i = i + 1;

i += 1;

bzw. das Dekrementieren mit -- zu:

i = i - 1;

i -= 1;

Zwischen den Post- und den Prä-Versionen beider Operatoren bestehen allerdings Unterschiede. Wird einer der beiden Operatoren in der Prä-Version verwendet (z.B. ++i), so wird der Wert um 1 erhöht und sogleich der neue Wert verwendet. Wird allerdings die Post-Version verwendet (i++), wird der Wert zwar erhöht (danach enthält die Variable einen um 1 höheren Wert), allerdings wird noch der alte Wert verwendet. Ein einfaches Beispiel:

#include <stdio.h>
 
int main()
{
  int a = 5, b = 10, ergebnis = 0;
  printf ("Zuvor: a = %d, b = %d, ergebnis = %d\n", a, b, ergebnis);
 
  ergebnis = a + b++;    /* zu 5 SOLLTEN 11 addiert werden */
 
  printf ("Danach: a = %d, b = %d, ergebnis = %d\n", a, b, ergebnis);
  return 0;
}

Die Idee wäre, zu 5 den um 1 erhöhten Wert von b zu addieren, d.h. ergebnis sollte 5 + 11 speichern, also 16. Das ist aber nicht gelungen. Das ist deshalb so, weil wir die Post-Version von ++ verwendet haben. Mit der Prä-Version wäre DAS nicht passiert! ;-) Natürlich wäre auch möglich gewesen, b vorher in einer eigenen Zeile zu erhöhen. Noch einmal das gleiche Beispiel, diesmal allerdings mit der Prä-Version des ++-Operators:

#include <stdio.h>
 
int main()
{
  int a = 5, b = 10, ergebnis = 0;
  printf ("Zuvor: a = %d, b = %d, ergebnis = %d\n", a, b, ergebnis);
 
  ergebnis = a + ++b;    /* ++b statt b++ ! */
 
  printf ("Danach: a = %d, b = %d, ergebnis = %d\n", a, b, ergebnis);
  return 0;
}

Nun wird der inkrementierte Wert (11) verwendet, und alles funktioniert, wie es geplant war. Der Grund, weshalb ich an dieser Stelle die Funktionsweise beider Operatoren erkläre, liegt an den sog. Seiteneffekten (Begriff ist schlecht eingedeutscht, aber üblich!), die (auch) durch sie entstehen können und in C möglich sind. Ein Beispiel:

if (i++ == 10)
  printf ("i enthält den Wert 10.");

Damit diese Bedingung erfüllt ist, muss i ZUVOR den Wert 10 enthalten. Denn in der Post-Version wird, wie Sie wissen, der neue Wert NICHT verwendet. i enthält auf jeden Fall (ob die Bedingung erfüllt wurde oder nicht!) nach dieser Anweisung einen um 1 höheren Wert! Zwei weitere Beispiele:

if (--i == 5)
{
   /* tue irgendetwas wenn i zuvor 6 war .. */
}

/* i hat hier einen um 1 niedrigeren Wert!
   Egal ob i vorher 6 war oder nicht! */");

if ( (i += 10) == 20)
  printf ("i enthält den Wert 20.");

Im letzten Beispiel wird in jedem Fall i um 10 erhöht! Die Erhöhung ist keine "temporäre", die nur für die Bedingung gilt! Etwas anderes wäre hier:

if ( (i + 10) == 20)
  printf ("i enthält den Wert 20.");

Hier bleibt i unverändert durch die if-Anweisung! Der Ausdruck in der if-Anweisung beeinflusst keine "externen Elemente". Im letzten Beispiel gibt es also keine Seiteneffekte. Probieren Sie folgendes Beispiel aus. Entspricht das Ergebnis Ihren Erwartungen?

#include <stdio.h>
 
int main()
{
  int a = 5, b = 10;
  printf ("Zuvor: a = %d, b = %d\n",a,b);
 
  if ( (++a == 6) || (--b == 10) )
    ;   /* nichts wird getan */
 
  printf ("Danach: a = %d, b = %d\n",a,b);
  return 0;
}

Die Variable a wird um 1 inkrementiert, b allerdings nicht verändert. Doch warum ist das so? Vorhin habe ich bereits erwähnt, dass beim &&-Operator eine Kurzauswertung stattfindet. Genauso ist es bei ||. Das bedeutet, ist bei || der erste Wert bereits wahr, wird der zweite erst gar nicht überprüft. Dies ist hier der Fall. Mit ++a bekommt a den Wert 6. a enthält nun 6, der Ausdruck ist wahr und der zweite Ausdruck wird nicht überprüft. Darum wird b auch nicht um 1 dekrementiert! Bei && ist es ähnlich: Ist der erste Wert bereits falsch, wird der zweite nicht mehr überprüft. Wie im nächsten Beispiel:

#include <stdio.h>
 
int main()
{
  int a = 4, b = 10;
  printf ("Zuvor: a = %d, b = %d\n",a,b);
 
  if ( (++a == 6) && (--b == 10) )
    ;   /* nichts wird getan */
 
  printf ("Danach: a = %d, b = %d\n",a,b);
 
  return 0;
}
Vorheriges Kapitel Nächstes Kapitel