Willkommen beim vierten Teil des C-Kurses! |
Module |
Nachdem wir nun schon etwas hineingeschnüffelt haben, wie C-Programme
aufgebaut sind, machen wr uns nun daran eigene Unterprogramme zu programmieren.
Wie schon früher erwähnt wurde ist C modular aufgebaut, d.h.
das die meisten Befehle Unterprogramme sind, die sich aus einfacheren aufbauen.
Der printf-Befehl ist z.B. ein solches Modul. Das bringt uns natürlich
auf die Idee eigene Befehle, also Module zu schreiben. Sehen wir uns einmal
ein solches Beispiel für die Modulprogrammierung an.
/* Beispiel fue Modulprogrammierung */
#include <stdio.h> /* Prototypen der Module */ void hallo (void);
/* Hauptprogramm */
float zahl=1.34; hallo ();
/* Modul : Ausgabe eines Textes */
/* Modul : Anzeige eines Übergabeparameters,*/ int frage (int eingabe) {
printf ("\n\nEs wurde der Wert %d uebergeben",eingabe);
return rueckgabe; /* Modul : Anzeige der Übergabeparameter */
printf ("\nEs wurden %d und %f uebergeben\n\n",zahl1,zahl2); |
Nach dem Start erscheint eine Abfrage, die wir z.B. mit 5 beantworten.
Danach arbeitet das Programm wie folgt den Rest ab
Den Vorgängen im Einzelnen werden werden wir uns nun zuwenden.
Prototypen |
Bevor der C-Compiler unsere neuen Module benutzen kann, muß er
wissen, welche Art von Datentypen von Modul wir benutzen wollen. Dazu schreiben
wir sogenannte Prototypen. Das sind Modulköpfe, die nur den Rückgabetyp
und den Parametertyp eines Moduls enthalten.
Rückgabetyp Modulname ( Parametertyp(en) ); |
Der Rückgabetyp bezeichnet den Variablentyp der Variablen, die
das Programm zurückgibt. Der Modulname ist der Name unseres Unterprogrammes.
Der Parametertyp gibt an, welche Variablentypen man an das Programm übergibt.
Es können mehrere Parametertypen übergeben werden, die werden
dann mittels eines Kommas voneinander getrennt.
void hallo (void);
int frage (int); |
In unserem Beispiel wird ein Integerwert an das Modul frage übergeben
und ein Integerwert zurückgegeben. Aufgerufen wird das Programm mit:
int b,c;
c = frage ( b ); |
Der Rückgabewert wird in 'c' gespeichert. Zu beachten ist,
das die Variablentypen vom Rückgabewert und der Variable gleich sein
müssen, in der der Rückgabewert gespeichert wird.
Was, wenn wir nun keine Werte übergeben wollen und keine zurückkriegen
? Dafür wurde 'void' eingeführt. Damit wird dem Compiler
gesagt: Hier wird kein Variablentyp übergeben oder zurückgegeben.
Der Aufruf gestaltet sich in unserem Beispiel daher recht einfach:
hallo ( ); |
Da das Modul nichts zurückgibt, benötigt man nur den Modulnamen
ohne Parameter. Das einzige, was noch stehen bleiben muß, sind die
leeren runden Klammern, da ja 'nichts' übergeben wird.
Aufbau von Modulen und Prototypen |
Was sind Prototypen und wozu sind sie überhaupt gut ? Angenommen
wir schreiben ein Programm, was eine Menge Module besitzt. Wenn das main-Modul
andere Module aufruft und diese hinter ihm stehen, so gibt es mehrere Probleme.
Woher soll der Compiler wissen, ob die Aufrufparameter und die Rückgabewerte
der aufgerufenen Programme in Ordnung sind ? Damit der Compiler also von
vornherein weiß welche Daten erwartet werden, sollte man am Anfang
stets die sogennanten Prototypen schreiben, das sind sogenannte
Rümpfe
der Programme, die den Rückgabewert, den
Modulnamen
und die Übergabeparameter enthalten.Abgeschlossen werden die
Prototypen wie gewohnt mit einem Semikolon. Wie wir lernten sind Module
wie folgt aufgebaut:
Rückgabetyp Modulname ( Parametertyp(en) )
{
return Rückgabe ; |
Die Rückgabewerte sind Daten vom Typ Rückgabetyp. Ausnahme
ist einzig der Rückgabetyp void, wo wir keinen Wert zurückgeben
müssen. Mit den Parametern wird hier gleich eine Variablendeklaration
durchgeführt.
/* Prototyp */
int test ( int ); :
/* Modul */
return Rueckgabe; |
Den erzeugten Variablen wird der Wert des Übergabeparameters zugewiesen.
Wird das Modul mit
c = test ( 3 ); |
aufgerufen, so wird im Modul der Variable 'a' der Wert 3 zugewiesen.
Innerhalb eines Moduls wird ein Rückgabewert mittels
return Rückgabewert im Format des Rückgabetypen; |
Steht nun ein void als Rückgabetype braucht natürlich
kein 'return' stehen. Das ganze funktioniert natürlich auch
mit Zeigern. Zu beachten bei den Prototypen ist: Wird ein Zeiger als Rückgabewert
benutzt, so wird im Prototyp und im Modulkopf der Stern hinter dem Rückgabewert
gesetzt.
char* Modulname (void) ... |
Mit dem Wissen aus dem letzten Kursteil, der die Zeiger betraf, müßten
sie es schaffen das folgende Programm zu verstehen:
#include <stdio.h> /* Prototypen */
/* Hauptprogramm */
/* Modul : Rückgabe eines eingegebenen Wortes */
char *rueck; printf ("\n\nBitte ein Wort eingeben : ");
/*
|
Nach dem Start wird man zur Eingabe eines Wortes aufgefordert, z.B. Teststring ,danach wird das eingegebene Wort nochmals ausgegeben.
Der Precompiler und wie man Module einbindet |
Wie man sich sicherlich denken kann, so kann so ein Programm sehr groß und unübersichtlich werden, wenn man alle Module in einem Code stehen hat. Zusätzlich wird es schwerer, einmal geschriebene Module wiederzuverwenden, da man ja alle Moduleteile erst einmal finden und dann in den anderen Code kopieren müßte. Um diese wiederverwendbarkeit unter C zu gewährleisten, gibt es die Möglichkeiten, die Module in einer externen Datei zu erstellen und dann mittels #include einzubinden. Dieses verfahren haben wir schon an den vorangegangenen Beispielen sehen können, wo sog. Headerdateien, also Dateien mit Definitionen und Prototypen, in das Programm eingebunden werden.
Es gibt hier zwei Konventionen, wie Dateibezeichnungen lauten und was diese Dateien enthalten sollten. Dateien mit der Endung .c enthalten Module oder andere Sourcecodes , Dateien mit der Endung .h enthalten Typendefinitionen, Konstanten und Prototypen. Dateien mit der Endung .h werden oft auch Header-Dateien genannt. Doch was macht nun der Compiler, wenn er eine #include-Anweisung sieht ? An der Stelle wid dann die Eingebundene Datei in den Sourcecode durch den sog. Precompiler oder Preprozessor eingebunden. Im Groben haben wir das schon am Anfang beim prinzipiellen Vorgang des compilierens kurz angeschnitten.
Es gibt hier zwei verschiedene Arten der #include Anweisung,
welche Angeben, in welchen Verzeichnissen nach den einzubindenen Files
gesucht werden soll.
#include <filename> | such in den Defaultverzeichnissen der Pfadangabe |
#include "filename" | sucht im aktuellen Verziechnis, danach wie #include <filename> |
Damit man nicht nicht zweimal identische Module einbindet und somit
auch Fehler generiert, gibt es eine zweite sehr nützliche Precompileranweisung,
#define.
Sie kommt zusammen mit #ifndef , #if , #ifdef, #undef
und
#endif.
Für die Anweisung #define gibt es folgende Möglichkeiten
ihn anzuwenden.
#define Name | Die Variable Name wird definiert |
#define Text Ersatztext | Im Programm werden alle Texteile Text durch den Ersatztext ersetzt |
Um es nochmals klar zu sagen: Vor dem eigentlichen Compilerlauf werden
alle Textstelle, die durch #define zu ersetzen sind, im Quellcode
selber ersetzt. Wird mittels #define Name eine Variable definiert,
so kann man dies mit #ifndef , bzw. #ifdef abfragen Dieses
wird in der Regel zur sicheren Moduleinbindung benutzt. Weiterhin, wie
wir in unserem Beispiel sehen werden, kann man auch Übergabeparameter
angeben, die dann in die Ersetzung eingebunden werden. Im folgenden Beispiel
wird also der Parameter A durch den übergebenen String ersetzt.
Doch da alle Theorie grau ist, probieren wir uns an folgendem Beispielprogramm.
Die Dateinamen der beiden Dateien stehen über den Kästen und
sie sollten im selben Verzeichnis liegen.
|
/*
Wenn _meine_datei nicht definiert wurde*/ #ifndef _meine_datei
#define Ausgabe printf("\nHallo Welt\n"); #define zeigeText(A) printf(A); #endif |
|
#include <stdio.h>
#include "incl.h" void main (void)
Ausgabe} |
Man sollte ein wenig herum herumspielen, um sicher mit den #defines
umgehen zu können, da sie einem die Arbeit erleichtern können.
Wie man sehen kann, kann auch eine #define -Anweisung wieder
aufgehoben werden. Dies geschieht mittels #undef . Am Beispiel kann
man gut erkennen, wie die Definition von Ausgabe aufgehoben und
dann neu gesetzt wird. Das kann man auch am nächsten Beispiel erkennen,
wo einmal ein bereits erstelltes unterprogramm eingebunden wird und auch
eine neudefinition vorgenommen wird..
|
void Modul (void)
{ Ausgabe} |
|
#ifndef _meine_datei
#define _meine_datei #define Ausgabe printf("\nHier ist das Modul\n"); void Modul(void);
#undef Ausgabe
#endif |
|
#include <stdio.h>
#include "incl.h" void main(void)
Modul();} |
Greifen wir doch schon etwas auf das nächste Kapitel vor. Wie wir
sehen werden, hat das #if des Precompilers eine große Ähnlichkeit
mit der if-Abfrage in C selber. Dort
gibt es aber ja noch die Möglichkeit der mittels else
Alternativprüfungen durchzutesten. Das geht analog dazu auch mit dem
Precompiler. Diese Alternativen können mittels #elif und #else
abgefragt,
bzw. vorgegeben werden. Wie vorher auch wird jedes #if - Konstrukt
mit einem #endif beendet.Einfach folgendes Programm ausprobieren
und verschiedene Werte für die ZAHL eingeben.
|
#ifndef _meine_datei
#define _meine_datei #define ZAHL 3 #if ZAHL == 3
/* entspricht else if */
#elif ZAHL == 5
/* entspricht dem else */
#endif
#endif |
|
#include <stdio.h>
#include "incl.h" void main(void)
Ausgabe} |
Wenn wir die Analogie zwischen dem if-else des Preprozessors und der Sprache C betrachten, können wir sehen, das else if dem #elif entspricht. Das Beispielprogramm schon ausprobiert ? Beim start müßte nun folgendes auf dem Bildschirm stehen.
...das Obligatorische |
Autor: Sebastian Cyris \ PCD Bascht
Dieser C-Kurs dient nur zu Lehrzwecken! Eine Vervielfältigung ist ohne vorherige Absprache mit dem Autor verboten! Die verwendete Software unterliegt der GPL und unterliegt der Software beiliegenden Bestimmungen zu deren Nutzung! Jede weitere Lizenzbestimmung die der benutzten Software beiliegt, ist zu beachten!