[Inhalt][0][1][2][3][4][5][6][7][8][9][10][11][12][13][14][15][16][17]

Willkommen beim vierten Teil des C-Kurses!
Module

Nachdem wir nun schon etwas hineingeschnüffelt haben, wie C-Programme aufgebaut sind, machen wir 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 fuer Modulprogrammierung */ 
#include <stdio.h>

/* Prototypen der Module */

void hallo (void); 
int frage (int);
void antwort (int, float);

/* Hauptprogramm */
void main (void)
{

    int b=2, c;
    float zahl=1.34;

    hallo ();
    c = frage (b);
    antwort (c, zahl);

}

/* Modul : Ausgabe eines Textes */
void hallo (void
{

    printf ("\n\nDas Modul sagt HALLO :-)\n\n");
}

/* 

Modul : Anzeige eines Übergabeparameters, 
Rückgabe einer eingelesenen Zahl
*/
int frage (int eingabe)
{
    int rueckgabe;

    printf ("\n\nEs wurde der Wert %d uebergeben",eingabe);
    printf ("\nBitte einen Integerwert, z.B. 2 : ");
    scanf ("%d",&rueckgabe);

    return rueckgabe; 

}

/* Modul : Anzeige der Übergabeparameter */
void antwort (int zahl1, float zahl2) 
{

    printf ("\n\nModul 'antwort' meldet sich");
    printf ("\nEs wurden %d und %f uebergeben\n\n",zahl1,zahl2);
}

Nach dem Start erscheint eine Abfrage, die wir z.B. mit 5 beantworten.

Screenshot des ausgeführten Programms

Danach arbeitet das Programm wie folgt den Rest ab

Screenshot des ausgeführten Programms

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) )
    /* Modulkörper */
    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 */
int test ( int a ) 
{

    /* Modulkörper */
    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:
 

/* Beispiel fuer Modulprogrammierung 2 */ 
#include <stdio.h>

/* Prototypen */
char* zeichen (void);

 /* Hauptprogramm */
void main (void
{

printf ("\n\nSie gaben ein : %s\n\n",zeichen()); 
 }

/* Modul : Rückgabe eines eingegebenen Wortes */
char* zeichen (void)
{

char string[200];
char *rueck;

printf ("\n\nBitte ein Wort eingeben : ");
scanf ("%s",&string);

/*
rueck zeigt auf das erste Zeichen
von string
*/
 rueck = string;
return rueck;

}

Nach dem Start wird man zur Eingabe eines Wortes aufgefordert, z.B. Teststring ,danach wird das eingegebene Wort nochmals ausgegeben.

Screenshot des ausgeführten Programms


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.
 
 

incl.h
/*
Wenn _meine_datei nicht definiert wurde
werden alle Befehle bis zu #endif abgearbeitet

auch bei mehrmaligen #include dieser Datei,
da nach dem ersten Durchlauf die Variable
_meine_datei gesetzt ist

Statt #ifndef kann man auch schreiben
#if !_meine_datei
#ifdef !_meine_datei

Das ! ist hier die Negation

*/

#ifndef _meine_datei
#define _meine_datei

#define Ausgabe printf("\nHallo Welt\n");

#define zeigeText(A) printf(A);

#endif

test.c
#include <stdio.h>
#include "incl.h"
 

void main (void)
{

Ausgabe
zeigeText("\nIch bin wieder da\n")

/*
Definition Ausgabe wird ungültig
und eine neue erzeugt
*/
#undef Ausgabe
#define Ausgabe printf("\ciao!\n");

Ausgabe

}

Screenshot des ausgeführten Programms

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..
 
 

modul.c
void Modul (void)
{
Ausgabe
}
incl.h
#ifndef _meine_datei
#define _meine_datei

#define Ausgabe printf("\nHier ist das Modul\n");

void Modul(void);
#include "modul.c"

#undef Ausgabe
#define Ausgabe printf("\nHier ist die Ausgabe\n");

#endif

 test.c
#include <stdio.h>
#include "incl.h"

void main(void)
{

Modul();
Ausgabe
}

Screenshot des ausgeführten Programms

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.
 
 

incl.h
#ifndef _meine_datei
#define _meine_datei

#define ZAHL 3

#if ZAHL == 3
  #define Ausgabe printf("\nZahl = 3\n");

/* entspricht else if */
#elif ZAHL == 4
  #define Ausgabe printf("\nZahl = 4\n");

#elif ZAHL == 5
  #define Ausgabe printf("\nZahl = 5\n");

/* entspricht dem else */
#else
  #define Ausgabe printf("\nZahl ist nicht 3,4 oder 5\n");

#endif
 

#endif

test.c
#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.

Screenshot des ausgeführten Programms



[Inhalt][0][1][2][3][4][5][6][7][8][9][10][11][12][13][14][15][16][17]