switch - case - default - Struktur

Die switch-Struktur erlaubt es, abhängig von einem Dezimalwert eine Fallunterscheidung zu machen. Der zu überprüfende Wert steht in runden Klammern () nach dem switch-Keyword und der Unterscheidungsblock gleich danach in geschweiften Klammern {}. Innerhalb des Unterscheidungsblocks stehen die verschiedenen Fälle aufgelistet mit Labels, welche durch das Keyword case mit einem angehängten Kolon : eingeleitet werden. Unbehandelte Fälle werden optional mit dem spezial-Label default eingeleitet. Um am Schluss der Bearbeitung eines Falls zum Ende des Unterscheidungsblockes zu springen, wird die break-Anweisung verwendet.







Value is two.
#include <stdio.h>

int main(){
  int x = 2;
  switch(x){
    case 1:  printf("Value is one.\n");   break;
    case 2:  printf("Value is two.\n");   break;
    case 3:  printf("Value is three.\n"); break;
    default: printf("Value is x.\n");
  }
  return 0;
}

Details

Der zu überprüfende Wert einer switch-Struktur wird im Gegensatz zur if-Struktur nicht als boolscher Wert aufgefasst, sondern als Ganzzahl. Es kann sich bei dem zu überprüfenden Wert um einen konstanten Wert, eine Variable oder das Ergebnis einer Berechnung handeln, der schlussendliche Wert muss jedoch einen Integer-Wert ergeben. Nicht erlaubt sind Fliesskommazahlen oder Pointer.

int getCode();
int x;

switch(5){}
switch(x){}
switch(getCode()){}
switch(5 * x - getCode()){}

case - Label

Die Werteangabe des zu behandelnden Falles mittels des case-Keywords muss stets ein konstanter Wert sein. Erlaubt sind somit: Feste Integer-Werte (Dezimal, Hexadezimal, Oktal), Zeichen-Literale in einfachen Anführungszeichen '' und jegliche konstante Ausdrücke (keine Funktionen, keine Variablen, keine Pointer), die zu einem Integer-Wert evaluieren:

char c = 'A';
switch(c){
  case 0x40      : printf("%c is an @.\n", c); break;
  case 65        : printf("%c is an A.\n", c); break;
  case 'B'       : printf("%c is a  B.\n", c); break;
  case 0103      : printf("%c is a  C.\n", c); break;
  case (30 + 38) : printf("%c is a  D.\n", c); break;
  case (int)69.0 : printf("%c is an E.\n", c); break;
}

Die verschiedenen Fälle müssen innerhalb derselben switch-Struktur eindeutig sein. Die durch das Keyword definierten Adressen haben nur innerhalb der betreffenden switch-Struktur Gültigkeit. In jeder anderen switch-Struktur können wiederum sämtliche case-Labels stehen. Ausserhalb einer switch-Struktur ist ein case-Label nicht erlaubt. Das Verzweigen an ein case-Label mittels der goto-Anweisung ist NICHT möglich, sowohl innerhalb als auch ausserhalb einer switch-Struktur.

Es ist anzumerken, dass nicht sämtliche Fälle durch case-Keywords beschrieben werden müssen und dass auch nicht sämtliche beschriebenen Fälle im laufenden Programm tatsächlich angesprungen werden. Dies ist im Grunde genommen logisch, sei hier aber einfach bemerkt.

Am Ende eines Falles, beziehungsweise vor Beginn des nächsten Falles muss sehr häufig (aber nicht zwingend) die break-Anweisung stehen, welche nach der Behandlung des Falles zum Ende der switch-Struktur springt. Diese Anweisung kann leicht vergessen gehen und führt zu schwer auffindbaren Fehlern, da ohne diesen Sprung die Anweisungen des nächsten Falles ebenfalls ausgeführt werden.

default - Label

Das default-Label ist optional in einer switch-Struktur. Dieses wird immer dann angesprungen, wenn der zu prüfende Wert nicht durch die anderen case-Labels behandelt wird. Falls der default-Fall nicht ausprogrammiert ist, so wird bei Auftreten eines unbehandelten Falles automatisch ans Ende des Unterscheidungsblockes gesprungen.

Wenn in der switch-Struktur alle Fälle einer enum-Variablen durchgeprüft werden, geben moderne Code-Analyse-Tools Warnungen aus für alle Fälle, welche nicht explizit abgefangen werden. Entweder wird dann ein default-Label hinzugefügt, um alle nicht-behandelten Fälle implizit abzufangen und somit die Warnung zu unterdrücken, oder aber die Warnung wird beibehalten, um stets daran erinnert zu werden, dass gewisse Fälle noch nicht bearbeitet werden.

Innerhalb einer switch-Struktur darf höchstens ein default-Label stehen. Die durch das Keyword definierte Adresse hat nur innerhalb der betreffenden switch-Struktur Gültigkeit. Ausserhalb einer switch-Struktur ist das default-Label nicht erlaubt. Das Verzweigen an ein default-Label mittels der goto-Anweisung ist nicht möglich, sowohl innerhalb als auch ausserhalb einer switch-Struktur.

Es ist zu beachten, dass wie auch bei dem case-Keyword am Ende des default-Falles die break-Anweisung stehen muss, wenn nach der Behandlung des Falles zum Ende der switch-Struktur gesprungen werden soll. Diese Anweisung kann leicht vergessen gehen und führt zu schwer auffindbaren Fehlern, da ohne diesen Sprung die Anweisungen des nächsten Falles ebenfalls ausgeführt werden. Genau aus diesem Grund jedoch wird das default-Label üblicherweise ans Ende der switch-Struktur gesetzt, wo keine break-Anweisung nötig ist, da danach die switch-Struktur zu Ende ist. Diese Positionierung des default-Labels am Ende der switch-Struktur ist jedoch nicht zwingend.

Mehrere gleiche Fälle

In anderen Programmiersprachen ist es teilweise möglich, beispielsweise eine Angabe wie case 1 to 5 zu schreiben, welche die Fälle 1 bis 5 gleichzeitig behandelt. In C ist dies nicht möglich, auch eine Auflistung wie case 1, 2, 3, 4, 5 ergibt Syntax-Fehler. In C kann dies nur mit folgenden Zeilen erreicht werden:

case 1:
case 2:
case 3:
case 4:
case 5: printf("All 5 cases.\n"); break;

Hierzu die Erklärung: Da das case-Keyword grundsätzlich nur ein Label, also eine Adresse definiert, werden durch die fünf aufeinanderfolgenden case-Labels fünf Adressen für die fünf Fälle definiert, welche alle auf dieselbe Codezeile zeigen. Wichtig ist dabei, dass zwischen den case-Labels kein Code, insbesondere keine break-Anweisung steht.

Diese Methode scheint umständlich, es gibt jedoch Fälle, wo genau dieses Verfahren angewendet wird. Beispielsweise, wenn bei einem Eingabewert überprüft werden soll, um welchen Buchstaben es sich handelt, und wenn es keine Rolle spielt, ob dieser gross oder klein geschrieben ist:

case 'a':
case 'A':
  printf("You pressed the A-Key.\n"); break;

Wenn grössere Intervalle geprüft werden müssen, können nicht sämtliche darin enthaltenen Fälle aufgeschrieben werden. Hier empfielt es sich, anstelle der switch-Struktur eine if-Struktur für diesen speziellen Fall zu programmieren.

Vergessene break-Anweisung

Ein typischer Fehler bei der switch-Struktur ist das Vergessen der break-Anweisung. Ohne diese Anweisung wird der Programmcode nicht zum Ende des Unterscheidungsblockes springen. In folgendem Beispiel wird dies sofort ersichtlich insofern, dass sämtliche Zeilen nach dem gewünschten Fall zu einer Ausgabe führen:







Value is two.
Value is three.
Value is x.
#include <stdio.h>

int main(){
  int x = 2;
  switch(x){
    case 1:  printf("Value is one.\n");
    case 2:  printf("Value is two.\n");
    case 3:  printf("Value is three.\n");
    default: printf("Value is x.\n");
  }
  return 0;
}

Dieser Fehler sieht harmlos aus. In der Hitze des Gefechtes kann jedoch die break-Anweisung schnell mal vergessen gehen und seltsame Effekte hervorrufen, was bei ernsten Anwendungen zu mehrstündigem Fehlersuchen führen kann:










Value: 100
#include <stdio.h>

int main(){
  int value = 100;
  char letter = '-';
  switch(letter){
    case '-': --value;
    case '+': ++value;
  }
  printf("Value: %d\n", value);
  return 0;
}

Variablendeklaration im Unterscheidungsblock

Die switch-Struktur definiert mittels der geschweiften Klammern {} einen Anweisungsblock. Somit ist es möglich, lokale Variablen zu deklarieren, welche ausserhalb nicht sichtbar sind. Dies wird nach allgemeinem Bewusstsein nicht gemacht, die Sprache erlaubt es jedoch. Folgendes Konstrukt ist also möglich:





x half: 1
int x = 2;
switch(x){
  int y;
  case 1: y = x / 3; printf("x third: %d\n", y); break;
  case 2: y = x / 2; printf("x half : %d\n", y); break;
}

Wieso dieses Konstrukt im allgemeinen verpönt ist, zeigt sich, wenn die Variable zusätzlich mit einer Initialisierung versehen wird. Der Compiler generiert für eine solche Initialisierung ausführbaren Code, welcher durch den Sprung zum korrekten Fall übersprungen wird, was in sinnlosen Werten resultiert:





y is -1073743116
int x = 2;
switch(x){
  int y = 4;
  case 1: printf("y is %d\n", y); break;
  case 2: printf("y is %d\n", y); break;
}

Um diese gefährliche Situation zu unterbinden, wird nach allgemeinem Bewusstsein eine Variable niemals innerhalb des Unterscheidungsblockes deklariert, sondern stets ausserhalb.

Interessanter hingegen ist der Fall, dass eine Variable nur für einen bestimmten Fall benötigt wird. Dann können um die entsprechenden Anweisungen einfach nochmals geschweifte Klammern {} gesetzt werden. Leider wird dadurch häufig der Code schwieriger zu lesen:






y is 4
int x = 2;
switch(x){
  case 1: printf("x is %d\n", x); break;
  case 2: {
    int y = 4;
    printf("y is %d\n", y);
    break;
    }
}