Kontrollstrukturen

Im folgenden soll kurz auf die Kontrollstrukturen in ANSI C eingegangen werden. Dabei wird auf Ihren soliden Pascal-Kenntnissen[18] aufgesetzt, die Beispiele aus Platzgründen werden also bewußt sehr kurz gehalten. Ebenso wird davon ausgegangen, daß Sie die Spielregeln der strukturierten Programmierung bereits kennengelernt haben.

Einzelanweisungen und Blöcke

Jeder Ausdruck in C wird zu einer Anweisung, wenn ihm ein Semikolon folgt! Die folgenden Zeilen beschreiben also in diesem Sinne einzelne C-Anweisungen.

x=y=z=0;
i++;
printf("\f\v\tLook here, are we \bnormal?\n");

Mit geschweiften Klammern { und } wird ein Block festgelegt. Ein solcher Block ist syntaktisch eine Anweisung und darf infolgedessen überall dort stehen, wo der Syntax gemäß eine Anweisung plaziert werden darf.

Innerhalb eines Blockes können (zu Beginn) (lokale) Variablen deklariert werden! (Vgl. hierzu das nachfolgende Kapitel zur Modularität.) Nach der schließenden geschweiften Klammer steht hierbei kein Semikolon.

Beispiel:

void main(void)
{ /* Hier beginnt der Block */
   int i; /* i ist gültig nur innerhalb dieses Blockes */
   /* ... */

} /* Hier endet der Block und damit die Gültigkeit von i */

Logische Ausdrücke und Verzweigung (if, if-else)

Mit der if-Anweisung bzw. if-else-Anweisung werden ein- oder zweifache Alternativen formuliert. Die Syntax unterscheidet sich etwas von der aus Pascal bekannten:

if ( <expression> ) <statement1> else <statement2>

Natürlich ist der else-Zweig optional. Die runden Klammern bei dem logischen Ausdruck sind erforderlich.

Beispiel:

if ( x != 0 )
   z /= x;
else
   z = -1;

Zu beachten ist hierbei, daß "logisch" bei C stets numerisch (ganzzahlig) bedeutet! Das heißt, der im if-Konstrukt auftretende Ausdruck kann generell jeden beliebigen numerischen Wert annehmen: wie bereits erwähnt wird 0 als FALSE, jeder andere Wert als TRUE interpretiert.

Obiges Beispiel läßt sich also umschreiben zu:

if (x)
   z /= x;
else
   z = -1;

Warnung: Ein häufiger Fehler (vor allem bei Menschen aus der Pascal-Welt) ist die Formulierung

if (x=0) ...;

in C wird hier der Variablen x der Wert 0 zugewiesen, das Ergebnis des Ausdruckes ist damit 0 (=FALSE), und ggf. wird der else-Zweig abgearbeitet! Korrekt muß es also

if (x==0) ...;

lauten!

Iterationen (while, for, do-while)

C bietet die drei gewohnten Schleifen an: die kopfgesteuerte while-Schleife, die fußgesteuerte do-while-Schleife und eine Schleife mit dem Schlüsselwort for, die weit mehr als die aus Pascal bekannte Zählschleife beinhaltet.

Die while-Schleife

Die kopfgesteuerte while-Schleife hat die Syntax

while ( <expression> ) <statement>

und wird solange abgearbeitet, wie <expression> einen Wert ungleich 0 besitzt.

Beispiel:

while ( i < MAX )
{
   sum += i++; /* i wird nach der Summation inkrementiert! */
}
/* Die geschweifte Block-Klammerung wäre hier nicht zwingend erforderlich. */

Die for-Schleife

Die for-Schleife in C ist ein sehr mächtiges Konstrukt, das durchaus nicht nur für die klassischen Zählschleifen verwendet wird.

Die Syntax hat die Form

for ( <expression1> ; <expression2> ; <expression3> ) <statement>

Mit Ausnahme der später zu besprechenden continue-Anweisung ist die for-Schleife äquivalent zu

<expression1>;
while (<expression2>)
{
   <statement>
   <expression3>;
}

Das bedeutet: <expression1> ist eine Anweisung, die vor dem eigentlichen Schleifenbeginn ausgeführt wird. <expression2> ist die logische Bedingung, die die weitere Schleifenverarbeitung regelt: solange <expression2> erfüllt (d.h. ungleich 0) ist, wird <statement> ausgeführt. Schließlich ist <expression3> eine Anweisung, die zum Abschluß eines jeden Schleifendurchgangs ausgeführt wird, die sogenannte Reinitialisierung.

Beispiel: Die nachstehende for-Anweisung summiert alle ganzen Zahlen von 0 bis 9 in der Variablen sum auf; dabei wird sum kompakterweise auch noch im Kopf der for-Schleife initialisiert.

for (sum=i=0; i<10; i++)
{

   sum += i;

}

Beispiel: Soll bei for nichts initialisiert werden, so kann ein for-Konstrukt auch so aussehen:

for ( ; i<10; i++ )
{

   sum += i;

}

Beispiel: Gelegentlich werden Endlosschleifen benötigt, z.B. wenn innerhalb einer Funktion mit return schon vorzeitig aus einer Schleife herausgesprungen wird. Das kann dann so aussehen:

for (;;)
{
   /* ... */
   if (x==0)
      return -1;
   /* ... */
} /* end for */

Die do-while-Schleife

In der Praxis etwas seltener wird die fußgesteuerte do-while-Schleife eingesetzt. Die Syntax ist

do
{

   <statements> 

} while ( <expression> );

dabei werden die <statements> solange abgearbeitet, wie <expression> einen Wert ungleich 0 besitzt, also TRUE ist. Beachten Sie bitte, daß dies die negierte Formulierung zum repeat-until-Konstrukt in Pascal ist!

Beispiel:

do
{
   sum += i;
   i++;
} while ( i < MAX );

Dieses Beispiel würde in Pascal so aussehen:

repeat 
   sum := sum + i;
   i := i + 1
until i> = MAX;

Mehrfachverzweigung (switch) und Marken

C kennt auch die Mehrfachverzweigung - das case von Pascal ist hier die switch-Anweisung. Die allgemeine Syntax hat die Form

switch ( <expression> )
{
   case <const-expr> : 
        <statements>
   case <const-expr> : 
        <statements>
   default : 
        <statements>
}

Hierbei wird <expression> mit den einzelnen konstanten (und paarweise verschiedenen) Ausdrücken hinter den Schlüsselwörtern case verglichen; ist irgendwo Gleichheit festzustellen, so wird dort eingesprungen, und die dahinter stehenden Anweisungen werden ausgeführt. Dabei werden dann sämtliche weiteren Anweisungen innerhalb des switch-Konstruktes abgearbeitet, also auch die, die hinter einer späteren switch-Marke stehen!

Um dies zu verhindern, kann mit break (vgl. den nächsten Abschnitt) der Ausstieg aus dem Konstrukt befohlen werden, wie im entsprechenden Beispiel dort gezeigt werden wird.

Trifft keine der case-Marken zu, so wird bei dem Schlüsselwort default eingesprungen; diese Marke darf jedoch auch fehlen, in diesem Fall findet bei switch dann keine Verarbeitung statt[19].

Abbruchmöglichkeiten (continue, break, return, exit)

Es ist manchmal sinnvoll, einen strukturierten Ablauf vorzeitig abzubrechen. Dies kann um den Preis umfangreicheren und schwerer zu lesenden Codes stets durch die Einführung logischer Flags geschehen, die den weiteren Ablauf z.B. eines Teiles der Anweisungen einer Schleife regeln. C stellt aber einige Möglichkeiten bereit, gezielt einen solchen vorzeitigen Ausstieg (und nur diesen) vorzunehmen.

Die Anweisung

continue;

bewirkt innerhalb einer Schleife, daß der restliche Schleifenkörper übersprungen und es mit ggf. dem nächsten Schleifendurchlauf weitergeht. Bei verschachtelten Schleifen bezieht sich continue naheliegenderweise auf die innerste Ebene.

Beispiel:

for (i=0; i<n; i++)
{ 
   /* ... */
   if (func(i)<0)
      continue; /* negative Werte werden übersprungen */
} /* end for */

Die Anweisung

break;

ist einfach: stößt das Programm innerhalb einer Schleife oder switch-Anweisung auf ein break, so wird die entsprechende Struktur (vorzeitig) verlassen.

Beispiel:

switch ( i )
{
   case 1 : printf("\nEins\n");
      break;
   case 2 : printf("\nZwei\n"); 
             /* ohne break; wird im Falle von i==2 */
             /* in der nächsten Zeile weitergemacht */
   case 3 : printf("\nDrei\n");
            break;
   default: printf("\nKeine der Zahlen...\n");
            break; /* nicht erforderlich, aber guter Stil */
} /* end switch /

Die return-Anweisung wurde bereits in Zusammenhang mit Funktionen vorgestellt: stößt die Abarbeitung auf ein return (mit oder ohne einen darauffolgenden Rückgabewert), so wird die betreffende Funktion beendet. Geschieht dies innerhalb von main(), so endet das gesamte Programm.

Mit

return 5;

oder

return(5);

wird der Wert 5 dabei zurückgeliefert.

C stellt einen weiteren Mechanismus (sozusagen als Notausstieg) bereit: mit der Standardfunktion exit() wird das (gesamte) Programm beendet, gleichgültig, aus welcher Funktion heraus exit() aufgerufen wird. Der Prototyp in stdlib.h ist

void exit(int status);

Als status kann dabei ein Wert an die aufrufende Instanz (Betriebssystem) zurückgeliefert werden. Vordefiniert sind die beiden symbolischen Konstanten EXIT_SUCCESS und EXIT_FAILURE, die für einen inhaltlich erfolgreichen bzw. fehlerbehafteten Programmausstieg stehen.

In einem korrekten Zweig sollte also mit

exit(EXIT_SUCCESS);

deutlich gemacht werden, daß das Programm ohne Fehler abgearbeitet worden ist; dementsprechend ist

exit(EXIT_FAILURE);

das Signal dafür, daß im Programm ein (nicht behebbarer) Fehler aufgetreten ist.

Sprünge (goto)

Neben den bisher vorgestellten Ablaufstrukturen gibt es auch in C ein goto, das aber seit der Erfindung der strukturierten Programmierung und dem Aussterben von COBOL nicht mehr verwendet wird.