ERCC (http://eriedel.info)
Teil 3 (Array, String, Zeiger)

Die Sprache C

Teil 4 (Konstrukte und Schluss)


Konstrukte

Im letzten Teil dieser Sprachübersicht geht es um Konstrukte. C verfügt über alle Strukturformen, die wir auch von BASIC her kennen. Neben vielen Übereinstimmungen und Ähnlichkeiten bestehen allerdings auch einige Unterschiede.

Wie ich eingangs gezeigt habe, lassen sich in C mehrere Anweisungen durch geschweifte Klammern zu einem Block zusammenfassen, der syntaktisch einer einzelnen Anweisung entspricht. Da in den C-Konstruktteilen (mit einer Ausnahme) jeweils nur einzelne Anweisungen erlaubt sind, ist die Blockdefinition auch sehr oft notwendig.

Das BASIC-Konstrukt

If n > 0 Then
    m = n
    n = 0
End If

wird in C zu

if (n > 0) {
    m = n;
    n = 0;
}

wohingegen der (eher unschöne) BASIC-Code

If n > 0 Then m = n : n = 0

nicht mit dem (noch unschöneren) C-Code

if (n > 0) m = n; n = 0;

übereinstimmt, weil dem if-Zweig in C hier tatsächlich nur eine Anweisung zugeordnet ist!

Zu sagen, dass BASIC in dieser Hinsicht eindeutiger oder verständlicher ist, würde die Dinge verdrehen. C folgt eben etwas anderen Regeln. Allerdings gibt es besondere Fälle verschachtelter if-Konstrukte, in denen man diese Regeln leicht übersehen und zu einer falschen Ablauflogik gelangen kann:

Grundsätzlich wird der (optionale) else-Zweig immer auf das vorhergehende else-lose if bezogen. Fehlt in einer verschachtelten if-else-Struktur ein else-Teil, so kann diese Auswertung in recht tückischer, weil unauffälliger Weise der Programmiererabsicht zuwiderlaufen.

Das Konstrukt

if (n > 0)
    if (n % 2 == 0)
        printf("positive gerade Zahl\n");
else
    printf("negative Zahl oder Null\n");

ist in Wahrheit mit

if (n > 0)
    if (n % 2 == 0)
        printf("positive gerade Zahl\n");
    else
        printf("negative Zahl oder Null\n");

identisch! Erst durch eine Klammerung

if (n > 0) {
    if (n % 2 == 0)
        printf("positive gerade Zahl\n");
    }
else
    printf("negative Zahl oder Null\n");

wird der else-Teil auf das äußere if bezogen (und das Konstrukt auch semantisch korrekt).

Denken Sie stets daran, dass Einrückungen nur der Optik dienen. Sie sollen zwar die Programmstruktur veranschaulichen, aber sie definieren oder beeinflussen die Struktur nicht.

Man kann daher (nicht nur C-Anfängern) empfehlen, mit Blockdefinitionen ruhig etwas großzügig zu sein. C-Profis mögen lästern, aber geschweifte Klammern machen den Code nicht schlechter und helfen in Zweifelsfällen, die Programmstruktur für uns (und den Compiler!) deutlich zu machen.


Mehrfachauswahlen

C kennt streng genommen kein ElseIf, es ist aber möglich und auch üblich, else und if zusammenzufassen:

if (n > 0)
    printf("positive Zahl\n");
else if (n == 0)
    printf("Null\n");
else
    printf("negative Zahl\n");

In BASIC könnten wir ein gleichwertiges Konstrukt auch mittels Select Case formulieren. Für die Fallunterscheidung in C (switch) trifft dies nicht zu, sie weist demgegenüber einige Unterschiede und Einschränkungen auf.

Zunächst einmal kann das switch-Konstrukt nur verwendet werden, um einen ganzzahligen Ausdruck auf die Gleichheit mit Konstanten hin zu prüfen. Ein »CASE > 0« ist also nicht erlaubt. Die Anwendung auf einzelne Zeichen ist allerdings möglich, da in C ein Zeichen (char) einen numerischen Wert darstellt.

Auch an eine andere Besonderheit muss sich ein BASIC-Programmierer erst gewöhnen:
Das Konstrukt wird mit dem Ende eines zutreffenden case-Zweigs nicht automatisch verlassen! Der Programmierer muss dies vielmehr durch die Anweisung break anordnen. Obwohl diese Eigenart auch nützlich sein kann, führt sie natürlich leicht zu unerwünschten Fallauswertungen und ggfs. zu einer falschen Konstruktlogik.

Das folgende Beispiel zeigt alle wichtigen Eigenschaften und demonstriert daneben, wie sich durch #define »magische Zahlen« vermeiden lassen.

#define ERR_FILE_NOT_FOUND      53
#define ERR_BAD_FILE_NAME       64
#define ERR_PERMISSION_DENIED   70
#define ERR_PATH_FILE_ACCESS    75

// ...

switch(error) {
    case ERR_FILE_NOT_FOUND:
        printf("Datei nicht gefunden.\n");
        break;
    case ERR_PERMISSION_DENIED:
    case ERR_PATH_FILE_ACCESS:
        printf("Dateizugriffsfehler.\n");
        break;
    case ERR_BAD_FILE_NAME:
        printf("Dateiname fehlerhaft.\n");
        break;
    default:
        printf("Sonstiger Fehler.\n");
        break;
}

Man sieht, dass ein case-Zweig ausnahmsweise auch ohne Blockdefinition mehrere Anweisungen enthalten kann. Da bei der Auswertung tatsächlich ein Sprung in den zutreffenden Zweig erfolgt, werden alle folgenden Anweisungen ausgeführt, falls man nicht das Konstrukt durch die break-Anweisung verlässt.

Im Beispiel werden zwei Fälle zusammengefasst. Viele Programmierer verdeutlichen durch einen Kommentar, dass das »Hineinfallen« in den nächsten case-Zweig beabsichtigt ist:

// ...
    case ERR_PERMISSION_DENIED:
        // Zugriffsfehler zusammen behandeln
    case ERR_PATH_FILE_ACCESS:
// ...

Die optionale default-Marke erfüllt denselben Zweck wie Case Else in BASIC. Sie muss jedoch nicht unbedingt an letzter Stelle stehen. Wenn sie das wie im Beispiel (und wie es auch üblich ist) tut, so ist dort ein break eigentlich nicht nötig.


FOR (ohne NEXT, aber mit Extras)

Die for-Schleife in C ist so flexibel, dass sie die Möglichkeiten der For-Next-Schleife in BASIC weit hinter sich lässt und kaum mehr als Zählschleife bezeichnet werden kann, obwohl das natürlich der primäre Verwendungszweck ist.

Die for-Schleife kann durch drei Ausdrücke bestimmt werden, die ihrerseits sehr komplex sein können! Allgemein betrachtet, stellt sich dies so dar:

for (INITIALIZE; CONDITION; STATEMENTS)

dabei bedeutet

INITIALIZE: Anweisungen, die vor Schleifenbeginn ausgeführt werden
CONDITION: Bedingung(en) für das Verweilen in der Schleife
STATEMENTS: Anweisungen, die nach jedem Durchlauf auszuführen sind

Zur Verdeutlichung nehmen wir einmal eine einfache BASIC-Schleife:

For n% = 1 to 20
    MakeWhat(n%)
Next

In C müssen wir vorher die Zählvariable deklarieren:

int n;         // in C Pflicht!

Die Schleife könnte dann so aussehen (1):

for (n = 1; n <= 20; n++)
    MakeWhat(n);

oder so (2):

for (n = 1; n <= 20;) {
    MakeWhat(n);
    n++;
}

oder so (3):

for (n = 1; n <= 20; MakeWhat(n++))
;

Diese Versionen unterscheiden sich im Wesentlichen nur durch ihr Layout. (Die erste Version stellt sicher die gebräuchlichste Form dar.)

Zuerst möchte ich auf zwei besondere Unterschiede zwischen BASIC und C hinweisen:

  1. Die Schleifenbedingung muss in C vollständig formuliert sein, während in BASIC nur ein Grenzwert angegeben wird. Man hat also darauf zu achten, als Bedingung nicht versehentlich n = 20 (bzw. n == 20) anzugeben, wodurch die Schleife überhaupt nicht ausgeführt würde.
  2. In C ist die Änderung der Zählvariable (hier die Inkrementierung von n) nicht implizit und muss angewiesen werden!

Ich habe hierbei übrigens die Suffix-Notation für den Inkrement-Operator ++ verwendet, was nur im dritten Beispiel eine – allerdings wichtige – Rolle spielt. Der Wert von n wird dadurch erst erhöht, nachdem der Ausdruck ausgewertet ist. Somit erhält die Prozedur MakeWhat dort richtigerweise noch den Wert, den n vor der Inkrementierung hat.

Wie bei den anderen Konstruktformen, kann die Schleife syntaktisch nur eine Anweisung (Beispiel 1) oder einen Block (Beispiel 2) enthalten.
Fehlt (wie im dritten Beispiel) jegliche Anweisung, so muss das Konstruktende wenigstens durch ein Semikolon ersichtlich sein. Dieses Semikolon (man spricht auch von einer leeren Anweisung) ist also erforderlich.

Auch die beiden Semikolons zwischen den for-Komponenten sind vorgeschrieben, während die Komponenten selbst fehlen können (siehe zweites Beispiel). Deshalb ist es möglich, durch

for (;;) {
    // ...
}

eine Endlos-Schleife zu formulieren. Die könnte – wie jede Schleife – durch die Anweisung break verlassen werden.

Mit der Anweisung continue kann man unmittelbar den nächsten Schleifendurchlauf veranlassen (entspricht Iterate in PowerBASIC), wobei zuerst die Inkrement-Anweisungen (STATEMENTS-Komponente) ausgeführt werden. Bei der for-Schleife des dritten Beispiels würde also auch noch der Funktionsaufruf von MakeWhat() erfolgen.


Weitere Wiederholungen

Die gewöhnliche bedingte Schleife ist ihrem BASIC-Pendant so ähnlich, dass dazu im Grunde wenig zu sagen ist.

Die allgemeine Syntax ist:

while (Bedingung)
    Anweisung;

Daneben existiert wie in BASIC eine »offene« Form

do
    Anweisung;
while (Bedingung);

bei der die Bedingung erst nach einem Schleifendurchlauf bewertet wird. (Man beachte das abschließende Semikolon!)

Wiederum kann man durch continue den nächsten Durchlauf der (umgebenden) Schleife unmittelbar starten, wobei sofort wieder die Bedingung ausgewertet wird.

Auch diese Schleifen können natürlich durch break vorzeitig verlassen werden. In verschachtelten Schleifen (oder auch switch-Anweisungen) bezieht sich break – erwartungsgemäß – auf das innerste umgebende Konstrukt.

Damit habe ich alle Konstrukte vorgestellt. Der Vollständigkeit halber ist noch zu erwähnen, dass auch C ein goto kennt. Die Syntax ist dieselbe wie in BASIC:

goto alabel
// ...
alabel:
// ...

Über Wohl und Wehe dieser Sprunganweisung ist in der Literatur schon viel gesagt worden. Zweifellos ist goto (auch) in C immer vermeidbar – und in Ausnahmefällen nützlich.


Schlussbemerkungen

Damit ist diese kurze Betrachtung der Sprache C zu Ende. Ich hoffe, diese Übersicht unterstützt interessierte Leser bei den ersten Schritten. Der Vergleich mit BASIC sollte helfen, das Konzept und die Unterschiede von C aus der Sicht einer Hochsprache besser zu verstehen. Vielleicht fühlen sich einige BASIC-Programmierer dadurch auch ermutigt, ein Projekt in C zu beginnen.

Natürlich bedarf es weiterer Literatur, um C besser kennenzulernen und für praktische Aufgaben einzusetzen. Daran besteht auch gewiss kein Mangel. Zudem finden sich im Internet viele (auch kostenlose) Werke über C, von der Einführung bis zu speziellen und anspruchsvollen Darstellungen.
Die folgende Liste soll einige Beispiele geben: 1 

Schließlich noch ein paar Hinweise zu kostenlos erhältlichen C-Übersetzern und Programmierwerkzeugen: 1  

Die unter Linux obligatorischen (oder über Paketquellen leicht verfügbaren) Compiler und Tools sind vielfach auch für Windows erhältlich. An erster Stelle ist hier natürlich die GNU Compiler Collection GCC zu nennen (http://gcc.gnu.org/install/specific.html#windows).

Microsofts Produkt Visual C++ kann auch C-Code übersetzen und ist als Bestandteil von Visual Studio erhältlich, das in den »Express«-Varianten sowie einer »Community«-Edition auch kostenlos angeboten wird. Visual Studio stellt eine recht komplexe Entwicklungsumgebung dar, die hinsichtlich der Systemvoraussetzungen nicht ganz anspruchslos ist (https://www.visualstudio.com/downloads/).

Das früher kommerzielle Produkt Watcom C/C++ ist in eine Open-Source-Entwicklung übergegangen. Open Watcom ist (auch) für Windows verfügbar und kostenlos (http://www.openwatcom.org).  2  

Embarcadero bietet – wie früher schon einmal Borland selbst – kostenlos den Borland C++ 5.5 Compiler an. Dabei handelt es sich allerdings um reine Kommandozeilen-Tools (Compiler, Linker etc.). Der Turbo Debugger ist nicht enthalten, befand sich jedoch im selben Verzeichnis (»TurboDebugger.exe«). Die Download-Adresse wird per E-Mail mitgeteilt (http://edn.embarcadero.com/article/20633).

Auf thefreecountry.com (englisch) finden sich Link-Zusammenstellungen zu vielen kostenlosen Programmierer-Tools und Dokumentationen, darunter auch zu C/C++ Compilern und Interpretern sowie C/C++ Online-Beschreibungen.



Anmerkungen:

1 Stand: Juli 2017. Ergänzungs- oder Korrekturhinweise sind natürlich willkommen!

2 Software und Doku sind dort via FTP erreichbar, Watcom C/C++ für Windows unter der Adresse ftp://ftp.openwatcom.org/install/open-watcom-c-win32-1.9.exe.



http://eriedel.info/info/c-prog/cpl4.html


Textanfang
ERCC (http://eriedel.info)  11/2011 + 2015   © Erhard Riedel Computer Consulting (ERCC)


Link zur ERCC-Hauptseite   Link zur Info-Übersicht