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

Programmaufbau, Headerdateien

1.1. Aufbau eines C-Programms

Wie Sie bereits wissen und im vorherigen Beispiel gesehen haben, erfolgt die Programmierung in Form von Anweisungen, die die einzelnen Programmierzeilen beinhalten. Im Hello-World-Programm wurde schlussendlich der Text hello, world! auf dem Bildschirm ausgegeben. Dafür gesorgt hat prinzipiell nur eine einzige Programmierzeile, und zwar:

printf ("hello, world!");

Damit allein wäre es aber nicht getan. Testen Sie es einmal selbst: Schreiben Sie nur diese Zeile und lassen Sie das "Programm" compilieren und ausführen. Der Compiler meldet Ihnen daraufhin einen Fehler. Der Compiler beschwert sich, dass er keine main()-Funktion finden kann.

Ein Funktion beinhaltet eine oder mehrere Programmierzeilen, und soll einem bestimmten Zweck dienen. Der Zweck unserer main()-Funktion im Hello-World-Programm war, einen Text auszugeben.

Jedes C-Programm muss eine main()-Funktion besitzen, allerdings nur eine. Existieren mehrere main()-Funktionen, meldet der Compiler einen Fehler, da der Bezeichner (= Name) main mehrmals verwendet wurde. Das Wort main heißt auf Deutsch soviel wie Haupt-, bei C ist hiermit die Hauptfunktion gemeint, denn diese wird als erstes aufgerufen. Der Compiler sucht also immer zuerst nach der main()-Funktion. Die main()-Funktion stellt den Programmeinsprungspunkt dar. Nachdem das Programm gestartet wurde, wird immer zunächst die main()-Funktion aufgerufen. Wie es dann weitergeht, steht in main(), bzw. ist Ihre Entscheidung.

Es ist unter C-Programmierern übrigens gebräuchlich, wenn von Funktionen gesprochen wird, hinter Funktionsnamen Klammern () zu schreiben. Diese Schreibweise deutet noch einmal darauf hin: abc() ist eine Funktion. abc ohne Klammern ist etwas anderes (meist eine Variable; mehr dazu später). Falls Sie sich unter Funktionen zurzeit noch nicht viel vorstellen können, ist das nicht weiter schlimm. Zum jetzigen Zeitpunkt reicht es zu wissen, dass eine Funktion einem bestimmten abgegrenzten Zweck dient (z.B. Text ausgeben, printf() ist nämlich auch eine) und eine oder mehrere Programmierzeilen in sich "zusammenfasst".

Sehen wir uns die main()-Funktion einmal an. Die kürzeste Form wäre folgende:

int main()
{
}

Dies ist zugleich auch das kürzeste Programm! Es macht allerdings nichts. Tippen Sie folgenden Quellcode ab und testen Sie das Programm:

int main()
{
}

Das Programm macht nichts, der Compiler meldet allerdings auch keinen Fehler - sofern Sie sich nicht vertippt haben. Das deutet darauf hin, dass das Programm lauffähig ist. In C ist es übrigens nicht egal, ob Sie etwas groß oder klein schreiben! C ist eine case-sensitive Sprache (schön eingedeutscht), demnach wird zwischen der Groß- und Kleinschreibung unterschieden. Würden Sie etwa statt main Main schreiben, ergäbe dies eine Fehlermeldung des Compilers, da dieser die main()-Funktion (klein geschrieben) vermisst. Die Main()-Funktion könnte irgendeine andere Funktion sein (obwohl hier ein anderer Name angebracht wäre; je "sprechender" und besser der Funktionsname die Teilaufgabe der Funktion beschreibt, umso besser).

Testen Sie einmal folgendes Programm:

#include <stdio.h>
 
int Main()
{
  printf ("Diese Zeile werden Sie nie angezeigt bekommen!");
}
 
int main()
{
  printf ("Die Hauptfunktion wurde ausgeführt!");
}

Achten Sie auf die genaue Schreibweise! Die beiden Funktionsnamen unterscheiden sich lediglich durch den ersten Buchstaben, und hier auch nur durch die Groß- und Kleinschreibung! Die erste Funktion heißt Main() und die zweite main(). Nach dem Start wird main() ausgeführt und der Text Die Hauptfunktion wurde ausgeführt! auf dem Bildschirm angezeigt. Da nach der Zeile mit printf ("Die Hauptfunktion wurde ausgeführt!"); nichts mehr folgt, wird das Programm beendet.

Kehren wir noch einmal zur einfachsten Variante zurück, und sehen wir uns die "Bestandteile" näher an:

int main()
{

main ist, wie mehrfach erwähnt, der Funktionsname. Vor dem Namen steht in diesem Fall das Wort int. int (Kurzform für Integer) ist einer von mehreren möglichen Rückgabetypen. Funktionen dürfen Werte zurückliefern. Bzw. eine Funktion muss einen Wert zurückliefern, wenn das so angegeben wurde. Dass int vor einem Funktionsnamen steht, bedeutet: Diese Funktion muss einen Wert vom Typ int zurückliefern.

Gehen wir noch einmal einen Schritt zurück. Stellen Sie sich folgendes Szenario vor: Sie möchten eine mathematische Berechnung durchführen. Der Einfachheit halber soll das die Quadratwurzel einer Zahl sein. Die von Ihnen geschriebene Funktion erledigt diese Teilaufgabe. Dazu nimmt die Funktion Daten entgegen (Eingabe; z.B. eine Eingabe vom Benutzer), führt die Berechnung durch (Verarbeitung) und liefert das Ergebnis zurück (Ausgabe). Diesen Ablauf kennt man auch als EVA-Prinzip (= Eingabe-Verarbeitung-Ausgabe).

Die Daten nimmt die Funktion in Form von Parametern entgegen. Hier wären wir wieder bei den Klammern (). Was zwischen den Klammern steht, wird an die Funktion übergeben. In der Zeile

printf ("hello, world!");

wird der Text (genauer: ein String; so nennt man Zeichenketten in C; mehr dazu später) hello, world! an die Funktion printf() als ein Parameter übergeben. Was unter Anführungszeichen "..." steht, gehört zusammen und wird als ein String (Zeichenkette) an printf() übergeben. Es sind theoretisch beliebig viele Parameter möglich, je nachdem, wie Sie das als Programmierer festgelegt haben.

Um an dieser Stelle wieder - gezwungenermaßen - etwas vorwegzunehmen: int ist ein Ganzzahlentyp. main() muss also eine Ganzzahl (positive oder negative Ganzzahl, -10, 70, 0, 30000, was auch immer) zurückliefern. Um den Rückgabewert, das ist quasi das Ergebnis der Verarbeitung durch die Funktion, zurückzuliefern, gibt es return. return kennen Sie auch schon:

return 0;

Mit return geben Sie den Wert an, den eine Funktion zurückliefert. Hier die Zahl 0. return beendet auch zugleich die Ausführung der Funktion. In main() beendet return das ganze Programm. Wenn also main() den Rückgabetyp int hat und eine Ganzzahl (int) zurückliefern muss, warum funktioniert das kürzest-mögliche Programm (siehe oben) dann trotzdem? Ganz einfach ein Ausnahmefall, den der C-Standard auch erlaubt. Schön ist es aber nicht. Darum: Lassen Sie main() immer einen Wert zurückliefern. 0 (Null) gibt üblicherweise an, dass das Programm korrekt ausgeführt wurde.

Wie bereits erwähnt, gibt es auch Funktionen ohne Rückgabewert. Sie sehen vielleicht einmal folgende Variante von main():

void main()
{

void bedeutet soviel wie "nichts", hier: KEIN Rückgabewert. void können Sie auch zwischen die Klammern schreiben, um noch einmal darauf hinzuweisen, dass keine Parameter benötigt werden, und auch nicht erlaubt sind. (Es gibt einen Ausnahmefall: An main() können Parameter von der Kommandozeile übergeben werden. Dann muss main() aber anders aussehen, mehr dazu in Kapitel 12.)

void main(void)
{
}

Testen Sie es selbst. Das Programm lässt sich fehlerfrei kompilieren und läuft auch. GCC gibt aber eine Warnung aus: Warnung: Rückgabetyp von »main« ist nicht »int«. main() muss lt. C-Standard nämlich vom Typ int sein. void funktioniert zwar (mit Warnung), sollte aber nicht verwendet werden.

Eines fehlt noch: { } (geschwungene Klammern) umschließen einen Anweisungsblock. Ein Anweisungsblock umfasst mehrere Programmierzeilen (oder eine).

Zusammenfassend kann man für

int main()
{

also sagen:

int = Rückgabewert, hier int für eine Ganzzahl
main = Bezeichner (Name) der Funktion, main hat eine Sonderstellung und steht für die Hauptfunktion
() = zwischen den Klammern können Parameter stehen; diese Daten werden an die Funktion übergeben
{ = öffnende geschwungene Klammer, Anfang des Anweisungsblocks, dieser Klammer folgen die Anweisungen
} = schließende geschwungene Klammer, beendet den Anweisungsblock, steht nach der letzten Anweisung

Im Übrigen wäre auch folgende Version des Beispiels gültig:

int
   main
(   
  )     {
}

Diese stilistisch "unmögliche" Variante lässt sich fehlerfrei und ohne Warnungen compilieren. Das liegt daran, dass der Compiler den Quellcode in einzelne Teile, Bausteine (sog. Token), zerlegt und für sich auswertet. Leerzeichen, Zeilenumbrüche, Tabulatorzeichen, etc. sind egal. Woran weiß der Compiler, wann eine Programmierzeile aus ist? Dazu dient das Semikolon ; am Ende einer "normalen" Zeile, wie wir es etwa nach printf() hatten:

printf ("hello, world!");

In Sprachen, in denen Semikola (Strichpunkte) am Ende der Zeilen nicht Pflicht sind (z.B. BASIC, aber auch in JavaScript ist es nicht Pflicht!), dürfen Sie Anweisungen nicht über mehrere Zeilen verteilen (weil der Compiler ja nicht weiß, wo die Zeile aus ist). Wichtig ist hier nur: Achten Sie auf die ; am Ende der Zeilen!

Ein Wort noch zu Einrückungen: Obwohl Leerzeichen dem Compiler egal sind, habe ich im Hello-World-Beispiel die Zeilen innerhalb des Anweisungsblocks um 2 Leerzeichen eingerückt. Das hilft dabei, die Übersicht zu bewahren, wenn Sie mehrere verschachtelte Anweisungsblöcke haben (werden Sie später sehen). Um wieviele Leerzeichen Sie einrücken oder ob um 1-2 Tabulator-Positionen, bleibt Ihnen überlassen und ist eine Frage des Stils bzw. der festgelegten Konventionen (wenn Sie Vorgaben dazu erhalten).

#include <stdio.h>
 
int main()
{
  printf ("Diese Zeile ist um 2 Leerzeichen von links eingerückt.");
  return 0;
}

1.2. Headerdateien mit #include einbinden

Eines fehlt noch, um das Hello-World-Programm ganz zu verstehen. Es gab die Zeile:

#include <stdio.h>

Das Doppelkreuz # hat eine besondere Bedeutung: Es zeichnet eine Anweisung an den Präprozessor aus. Der Präprozessor ist ein Teil bzw. eigenes Programm, das zum Compiler gehört. Der Präprozessor arbeitet den Quellcode noch vor dem eigentlichen Übersetzungsvorgang ab. Hier macht dieser Folgendes: Er lädt die Datei stdio.h und kopiert den gesamten Text (Quellcode) aus der angegebenen Datei an die Stelle, an der die #include-Anweisung steht.

stdio.h ist eine sog. Headerdatei. In einer Headerdatei (auch als Include-Datei bezeichnet) speichert man üblicherweise Deklarationen ab. Eine Deklaration ist eine Bekanntgabe an den Compiler. Zum Beispiel kann man mithilfe einer Deklaration dem Compiler bekannt geben, welche Funktionen existieren. Was die Funktion tatsächlich macht, steht an anderer Stelle (= Definition).

Der Compiler sieht dann die Anweisungen aus der Headerdatei so an, als hätten Sie sie direkt in die Quelldatei geschrieben. Üblicherweise befinden sich #include-Anweisungen am Anfang eines Programmes. Es wäre auch möglich gewesen, jeglichen Quellcode in einer Datei auszulagern, und dann per #include einzufügen. Davon kann ich aber abraten und ist nicht Sinn der Sache.

stdio.h enthält Deklarationen der Standardfunktionen, die für die Ein- und Ausgabe benötigt werden. Wenn Sie printf() benötigen, müssen Sie stdio.h einbinden. stdio.h (std wie standard) gehört zur C Standard Library (C-Standard-Bibliothek). Die bringt jeder C-Compiler mit. Wer es genauer wissen möchte: Standard C Library bei Wikipedia.

Je nachdem, WO (!) die Includedatei liegt, muss der Dateiname entweder zwischen spitzen Klammern < > oder Anführungszeichen " " stehen. Das ist sehr wichtig und ein häufiger Anfängerfehler! Liegt die Datei im Standardverzeichnis für Includedateien (jeder Compiler bietet die Möglichkeit, ein solches Verzeichnis anzugeben), verwendet man spitze Klammern < > (oder bei Dateien der Standard-Library). Liegt die Datei im aktuellen (!!) Verzeichnis (also jenes, in dem sich auch die Quelldatei des Programmes befindet), verwendet man Anführungszeichen " ". Vergleichen Sie:

#include <stdio.h>

#include "LokaleHeaderdatei.h"

Ich möchte an dieser Stelle noch einmal darauf hinweisen, wie wichtig es ist, das Gelesene auszuprobieren. Testen Sie jedes Beispiel und alles Neue! Schreiben Sie, soweit möglich, kleinere Programme zur Übung. Programmieren lernt man nur durch viel Übung!

Vorheriges Kapitel Nächstes Kapitel