Skip to main content Link Menu Expand (external link) Document Search Copy Copied

Kontrollstrukturen

Block

  • Wie in Java, werden Anweisungen, die zwischen zwei geschweiften Klammern { } stehen als Block bezeichnet. Ein Block kann überall dort eingesetzt werden, wo auch ein einzelnes Statement stehen kann (z. B. in Kontrollstrukturen)
int i = 7;

{
  /* Dies ist ein Block */
  i++;
}

Auswahl (if)

if-Anweisung bietet eine einseitige Auswahl

  • Syntax: if (BEDINGUNG) { TRUE-ZWEIG }
  • wenn die logische Bedingung erfüllt ist wird der True-Zweig ausgeführt
if (antwort == 42) {
    printf("Die Antwort auf alle Fragen");
}
Bedingung ist vom Typ int

Da sich Java bei der Syntax und den Kontrollstrukturen stark von C hat beeinflussen lassen, sollte es nicht verwunden, dass die C-Kontrollstrukturen denen von Java exakt gleichen. (Nur ist es natürlich in Wirklichkeit genau anders herum.)

Ein ganz großer Unterschied zu Java ist der Typ der Bedingung: Während in Java die Bedingung vom Datentyp boolean sein muss, ist sie in C vom Datentyp int. Der Grund ist, dass es in C keinen Datentyp für Wahrheitswerte gibt, sondern diese einfach als int dargestellt werden. Hierbei gilt, dass

  • 0 → false
  • != 0 → true

Dieses Manko wurde im Standard adressiert und ein neuer Datentyp _Bool wurde in C99 eingeführt. Er heißt _Bool, weil man sich nicht getraut hat, den Datentyp bool zu nennen: Existierende Programme, die sich selbst einen bool definiert haben, hätten sonst nicht mehr compiliert.

Der C11-Standard hat über die Header-Datei stdbool.h einen „echten“ bool eingeführt. In der Datei findet man dann aber auch nur:

#define bool    _Bool
#define true    1
#define false   0

Die Kontrollstrukturen fassen aber trotzdem weiterhin jeden Wert != 0 als true und 0 als false auf. Deswegen schreiben C-Programmierer völlig unverkrampft Code wie den folgenden – auch heute noch:

int i = 7;

while (i--) {
    printf("%d, ", i);
    /* Ausgabe: 6, 5, 4, 3, 2, 1, 0, */
}

if-else-Anweisung bietet eine zweiseitige Auswahl

  • Syntax: if (BEDINGUNG) { TRUE-ZWEIG } else { ELSE-ZWEIG }
  • wenn die logische Bedingung erfüllt ist, wird der True-Zweig ausgeführt, andernfalls der Else-Zweig
if (antwort == 42) {
    printf("Die Antwort auf alle Fragen");
} else {
    printf("Keine Antwort");
}

Konventionen für Formatierung von if

  • Leerzeichen zwischen if und (
  • Leerzeichen zwischen ) bzw. else und {
  • Kein Leerzeichen zwischen ( bzw. ) und Bedingung
  • else in selbe Zeile wie } oder direkt darunter
  • True- und False-Zweig werden vier Leerzeichen eingerückt

Mehrfachverzweigung mit if-else-if

if-else-if-Anweisung bietet eine Mehrfachverzweigung

  • Eigentlich nur eine Verschachtlung mehrerer if-else-Anweisungen
  • Wird speziell formatiert, um besser lesbar zu sein
  • Beliebig viele else if, nur ein else
Syntax
if (BEDINGUNG1) {
    TRUE-ZWEIG1
} else if (BEDINGUNG2) {
    TRUE-ZWEIG2
} else if (BEDINGUNG3) {
    TRUE-ZWEIG3
} else {
    ELSE-ZWEIG
}
if (temp < 10) {
    printf("Viel zu kalt");
}
else if (temp < 20) {
    printf("Kalt");
}
else if (temp > 50) {
    printf("Viel zu warm");
}
else if (temp > 30) {
    printf("Warm");
}
else {
    printf("Alles super");
}

Die Reihenfolge der Bedingungen ist natürlich entscheidend, da nach dem ersten Treffer die anderen Zweige nicht mehr betrachtet werden. Es wäre also falsch, die ersten beiden Bedingungen zu tauschen, weil dann sowohl bei 9 als auch bei 19 Grad immer die Ausgabe „Kalt“ gemacht würde.

Falsche Reihenfolge
if (temp < 20) {
    printf("Kalt");
}
else if (temp < 10) {
    /* Wird nie erreicht! */
    printf("Viel zu kalt");
}

Mehrfachverzweigung mit switch

switch-Anweisung bietet andere übersichtlichere Form der Mehrfachverzweigung

Syntax
    switch (VARIABLE) {
        case WERT1:
            ANWEISUNGEN;
            break;
        case WERT2:
            ANWEISUNGEN;
            break;
        ...
        default:
            ANWEISUNGEN;
    }

Besonderheiten

  • VARIABLE: muss eine Ganzzahl (int) sein
  • WERT: ein möglicher Wert der Variable als Literal oder Konstante
  • default: Zweig der ausgeführt wird, wenn kein Wert vorher gepasst hat
switch (monat) {
    case 2:  tage = 28; break;
    case 4:  tage = 30; break;
    case 6:  tage = 30; break;
    case 9:  tage = 30; break;
    case 11: tage = 30; break;
    default: tage = 31;
}

Die Variable, die im switch-Statement verwendet wird (hier monat) muss entweder zuweisungskompatibel mit dem Typ int sein (short, char, int) oder es muss sich um einen Aufzählungstyp handeln (enum). Es ist nicht möglich im switch-Statement Variablen von anderen Typen zu verwenden, d. h. double, float etc. sind nicht verwendbar.

Die Werte in den case-Ästen müssen Konstanten vom Typ int sein. Sie brauchen keinen expliziten Block zu schreiben, d. h. anders als beim if bezieht sich der case-Ast auf alle Statements bis zum nächsten case oder break. Gleichzeitig entsteht aber kein neuer Scope und das case muss ein Statement enthalten. Wollen Sie in einem case eine Variable definieren und dort verwenden, müssen Sie einen Block einsetzen.

#include <stdio.h>

int main() {
    int selection = 0;

    switch (selection) {
        case 0: {
            /* Block, damit Deklaration von k möglich wird */
            int k = 1;
            k--;
            printf("%d\n", k);
            break;
        }
        case 1:
            printf("%d\n", 1);
    }
}

Der default-Ast wird ausgeführt, wenn keine der Case-Konstanten gepasst hat.

Bei C fällt bei einem switch-Statement der Kontrollfluss durch alle case-Statements, die auf das case folgen, bei dem die Bedingung zutraf. Daher muss man immer explizit ein break; hinter die case-Statements schreiben, wenn man dieses Verhalten nicht möchte, was fast immer der Fall ist.

Konventionen für Formatierung von switch

  • Leerzeichen zwischen switch und (
  • Leerzeichen zwischen ) und {
  • Kein Leerzeichen zwischen ( bzw. ) bei Variable
  • case jeweils um vier Leerzeichen eingerückt
  • Anweisungen hinter : von case oder darunter, acht Leerzeichen eingerückt

Fragezeichen-Operator

  • Bedingungen mit if und case können nicht in Ausdrücken eingesetzt werden
  • Für die Verwendung in Ausdrücken gibt es einen speziellen Bedingungsoperator (Fragezeichen-Operator)
  • Einziger Operator in C mit drei Operanden (ternärer Operator)
  • Syntax: BEDINGUNG ? TRUE-WERT : FALSE-WERT

Beispiel: Absolutbetrag

Mit if
if (a < 0) {
    b = -a;
}
else {
    b = a;
}
Mit Fragezeichen-Operator
b = a < 0 ? -a : a;

while-Schleife

  • while-Schleife (while loop) prüft vor jedem Durchlauf eine Bedingung (Vergleichsausdruck)
  • kopfgesteuert und abweisend
  • Syntax: while (BEDINGUNG) { ANWEISUNGEN }
while (antwort < 42) {
    antwort++;
}

Der Rumpf der while-Schleife wird nur betreten, wenn die Bedingung des Ausdrucks wahr ist. Wenn man eine Schleife wünscht, die mindestens einmal durchlaufen wird, muss man die do/while-Schleife wählen. Die Variable auf die getestet wird sollte richtig initialisiert sein und man sollte nicht vergessen sie im Schleifenrumpf zu verändern, da man andernfalls in einer Endlosschleife landet.

Wenn die Anzahl der Schleifendurchläufe schon im Voraus feststeht, sollten Sie die for-Schleife verwenden. Das hier gewählte Beispiel ist also nicht optimal, da man es besser mit for implementiert hätte. while ist gut geeignet für Schleifen, bei denen währende des Durchlaufens erst festgestellt werden kann wann der Abbruch erfolgen soll, z. B. bei linearen Suchen etc.

/* Beispiel: Gib alle Zahlen von 1 bis 10 aus */
int i = 1;
while (i <= 10) {
    printf("%d\n", i);
    i++;
}
/* Beispiel: Berechne Summe der Zahlen von 1 bis 10 */
int i = 1;
int summe = 0;
while (i <= 10) {
    summe += i;
    i++;
}
printf("%d\n", summe);

do-while-Schleife

do-while-Schleife (do-loop) ist fußgesteuert (nicht abweisend)

  • Syntax: do { ANWEISUNGEN } while (BEDINGUNG);
do {
    n = readNatuerlicheZahl();
} while (n >= 0);

Der Rumpf der do/while-Schleife wird immer mindestens einmal betreten, da der Test erst am Ende erfolgt. Wenn man eine Schleife wünscht, die die Bedingung vorher testet, muss man die while-Schleife verwenden. Die Variable auf die getestet wird sollte richtig initialisiert sein und man sollte nicht vergessen sie im Schleifenrumpf zu verändern, da man andernfalls in einer Endlosschleife landet.

Wenn die Anzahl der Schleifendurchläufe schon im Voraus feststeht, sollten Sie die for-Schleife verwenden. Das hier gewählte Beispiel ist also nicht optimal, da man es besser mit for implementiert hätte.

/* Beispiel: Gib alle Zahlen von 1 bis 10 aus */
int i = 1;
do {
    printf("%d\n", i);
    i++;
} while (i <= 10);
/* Beispiel: Berechne Summe der Zahlen von 1 bis 10 */
int i = 1;
int summe = 0;
do {
    summe += i;
    i++;
} while (i <= 10);

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

for-Schleife

for-Schleife (for-loop) ist kopfgesteuerte (abweisende Schleife)

  • bietet mehr Möglichkeiten als die while-Schleife
  • Syntax: for (INIT; BEDINGUNG; UPDATE) { ANWEISUNGEN }
    • INIT: initialisiert Variablen (optional)
    • BEDINGUNG: Schleife läuft, solange die Bedingung wahr ist
    • UPDATE: Operationen, die nach jedem Durchlauf durchgeführt werden
int i;
for (i = 0; i < 10; i++) {
    /* tu was */
}

Wenn man Java gewöhnt ist, wird man sich wundern, warum die Variable i nicht in der for-Schleife deklariert wird. Der Grund liegt darin, dass bis zum C99-Standard Variablen nur am Anfang eines Blocks deklariert werden durften. Erst mit C99 kam die Möglichkeit, Variablen auch an anderer Stelle, z. B. in dem Initialisierungsteil einer for-Schleife erstmals einzuführen. Der vorhergehende Standard (ANSI-C bzw. C89) kannte diese Erweiterung noch nicht.

Wenn Sie nicht wissen, auf welcher Plattform, mit welchem C-Compiler Ihr Code später einmal compiliert werden wird, sollten Sie auf die Deklaration im for verzichten.

/* Beispiel: Gib alle Zahlen von 1 bis 10 aus */
int i = 0;
for (i = 1; i <= 10; i++) {
    printf("%d\n", i);
}
/* Beispiel: Berechne Summe aller Zahlen von 1 bis 10 */
int summe = 0;
int i;

for (i = 1; i <= 10; i++) {
    summe += i;
}

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

Schleifenabbruch

Aktueller Schleifendurchlauf kann abgebrochen werden

  • break; – Schleife wird ganz verlassen
    (Sprung an die Anweisung nach der Schleife)
  • continue; – Beginnt sofort einen neuen Schleifendurchlauf
    (Sprung in die Prüfung der Bedingung)

Wird häufig als schlechter Programmierstil angesehen. Alternativen

  • boolesche Kontrollvariable als Ersatz für break
  • zusätzliches if als Ersatz für continue
/* Suche kleinste Zahl, die durch 8 und 14  teilbar ist (mit break) */
int i;
for (i = 1; i <= 8*14; i++) {
    if ((i % 8 == 0) && (i % 14 == 0)) {
        printf("%d\n", i);
        break;
    }
}
/* Suche kleinste Zahl, die durch 8 und 14 teilbar ist (ohne break) */
int gefunden = 0;
int i;
for (i = 1; i <= 8*14 && !gefunden; i++) {
    if ((i % 8 == 0) && (i % 14 == 0)) {
        printf("%d\n", i);
        gefunden = 1;
    }
}

Das goto

  • Anders als Java besitzt C noch einen goto Befehl
  • goto LABEL springt zum Label LABEL
  • Kann nur innerhalb derselben Funktion angewandt werden
int i = 0;

jump_here:

/* Schleife mit goto. Pfui! */
if (i < 5) {
    printf("i=%d\n", i);
    i++;
    goto jump_here;
}

In Java ist goto zwar als Schlüsselwort reserviert aber nicht in der Sprache implementiert. In C hingegen hat es eine Funktion.

Über Funktionsgrenzen hinweg kann in C mit der Funktion _longjmp gesprungen werden.

Codekonventionen

  • Hinter do, for und while ein Leerzeichen
  • Vor und hinter den Operatoren ein Leerzeichen
  • falls Blockanweisung:
    • ) und { in dieselbe Zeile, durch Leerzeichen getrennt
    • } unter i von if bzw. w von while
  • innere Anweisungen um 4 Spalten einrücken
  • geschachtelte Schleifen vermeiden (→ Prozeduren)

Copyright © 2022 Thomas Smits