Solars C++ Kurs
Programmieren leicht erlernt
Funktionen

Headerdateien

Wenn die Deklarationen in einer eigenen Datei stehen hat dies mehrere Vorteile. Erstens ist die Übersichtlichkeit größer und zweitens koennen die Funktionen so später auch in mehreren CPP-Dateien bekannt gemacht werden, indem einfach die Headerdatei eingebunden wird. Dazu gibt es auf der nächsten Seite mehr Informationen.

Hier wollen wir uns ersteinmal darum kümmern wie man eine Headerdatei anlegt. Dazu benutzen wird das gleiche Projekt, welches wir schon auf der Seite 'Funktionsdeklaration' benutzt haben. Als erstes veränder wir dort die Datei 'main.cpp' folgendermaßen:

main.cpp
#include <iostream.h>
#include <stdlib.h>
#include <limits.h>
#include <float.h>
#include "main.h"


// Die Funktionsdeklarationen wurden entfernt


void ScppkMinus( int nZahl1, int nZahl2 )
{
  double dErgebnis;
  double d1 = 0;

  dErgebnis = (double) nZahl1 - (double) nZahl2;
  if ( nScppkIntegertest( dErgebnis ) )
  {
    cout << nZahl1 - nZahl2;
  }
}

.
.
.


Mit #include "main.h" haben wir die Headerdatei eingebunden, welche wir gleich erstellen werden. Vom Compiler oder mit Bibliotheken mitgelieferte Header werden in die spitze Klammer <> gesetzt. Eigene Headerdateien werden in Anführungsstriche "" gesetzt.

Da die Funktionsdeklarationen nun in der CPP-Datei nicht mehr vorhanden sind, sollten man sie wo anders unterbringen. Der geeignete Ort dafür ist die Headerdatei. Die Headerdatei zu 'main.cpp' nennt man am besten 'main.h' damit man sofort erkennt, daß die beiden Dateien zusammengehören. Die entsprechende Headerdatei sieht dann so aus:

main.h
void ScppkMinus( int nZahl1, int nZahl2 );
int nScppkIntegertest( double dZahl );
void ScppkPlus( int nZahl1, int nZahl2 );
int main( int argc, char * argv[], char * envp[] );


Die Deklaration der Funktionen ist genau die Selbe wie sie es in der CPP-Datei war. Jede Funktion darf nur einmal deklariert werden. Es ist jedoch möglich eine Headerdatei in verschiedene CPP-Dateien einzubinden. Dann wären die Deklarationen auch mehrfach vorhanden. Dies läßt sich in einer Headerdatei aber ganz einfach verhindern.

Wenn man die Headerdatei folgendermaßen ändert werden die Deklarationen nur dann beachtet wenn die Headerdatei zum erstenmal eingebunden wird:

main.h
#ifndef __main_h__
#define __main_h__

void ScppkMinus( int nZahl1, int nZahl2 );
int nScppkIntegertest( double dZahl );
void ScppkPlus( int nZahl1, int nZahl2 );
int main( int argc, char * argv[], char * envp[] );

#endif


Mit # beginnen immer Präprozessor-Anweisungen. Dies bedeutet, diese Anweisungen werden vor dem Compilieren ausgeführt. Das hier schon öfters benutzte #include fügt z.B. die entsprechende Datei an dieser Stelle ein.
Bei #ifndef wird der folgende Code bis zum #endif nur beachtet, wenn der Wert __main_h__ nicht definiert ist. In dieser Headerdatei ist __main_h__ beim ersten Durchgang noch nicht definiert. Direkt nach der #ifndef-Abfrage wird __main_h__ mit #define definiert. Wenn die Headerdatei also ein zweitesmal aufgerufenwird, ist __main_h__ definiert und der Bereich zwischen #ifndef und #endif wird vom Compiler nicht mehr beachtet.

Die #define-Anweisung ist ziemlich vielseitig. Mit ihr lassen sich verschiedene Programmversionen erstellen, indem man mit ifndef verschiedene Bereiche einklammert, welche dann über ein gesetztes #define aktiviert oder deaktiviert werden können. So werden z.B. viele Shareware- und Demo-Versionen erstellt. Die Bereiche, welche in der Demo-Version nicht aktiv sein sollen werden mit #ifndef __DEMO__ und #endif eingerahmt. Um von dem Programm eine Demo-Version zu compilieren muß jetzt nur noch die Zeile #define __DEMO__ in die Headerdatei eingefügt werden. Um danach nochmal eine Vollversion zu kompilieren wird diese Zeile einfach wieder entfernt oder auskommentiert. (Mit auskommentieren ist das setzen von // vor die jeweilige Zeile gemeint.)

Ein anderer Vorteil von #define-Anweisungen ist die Möglichkeit, alle Textausgaben aus dem Quellcode herauszunehmen und in die Headerdatei einzufügen. So lassen sich Programme erstellen, welche sich einfach in eine andere Sprache übersetzen lassen, ohne das es nötig ist, den gesamten Quelltext nach Textausgaben zu durchsuchen. Diesen Einsatzbereich möchte ich hier etwas genauer vorstellen.

Es ist mit #define nicht nur möglich einen Wert zu definieren, man kann ihn auch mit einem ganz bestimmten Wert definieren. Wenn man hinter dem definiertem Begriff die Definition schreibt, gilt sie als diesem Begriff zugewiesen:
#define __ICH_BIN_EIN_DEFINE__ 123
Diese Zeile hat gleich zwei Auswirkungen, erstens ist der Begriff __ICH_BIN_EIN_DEFINE__ definiert, und zweitens entspricht dieser Begriff '123'. Wenn also irgendwo im Quelltext (äßer bei #ifndef und #ifdef) dieser Begriff steht, wird er vor dem Compilieren durch 123 ersetzt.
INT_MIN und INT_MAX sind z.B. auf diese Art und Weise definiert worden.

#define BEGRIFF WERT
BEGRIFF kann ein frei wählbarer Name sein. Normalerweise benutzt man Großbuchstaben und Unterstriche, damit man sofort erkennen kann ob es ein Define ist. Man muß jedoch sehr darauf achten, einen BEGRIFF zu wählen der sonst nicht im Quellcode vorkommt.
'#define 12 36'
Würde z.B. dazu führen, daß der Code 'nZahl += 12;' vor dem Compilieren nach 'nZahl += 36;' übersetzt würde, da 12 ja jetzt als 36 definiert ist. Verwirrend nicht war?
Der WERT ist die gesamte restliche Zeile. Der Compiler tauscht diese gesamt gegen den BEGRIFF aus. Deshalb kann das Semikolon in '#define __NICHTS__ 0;' zu Problemen führen. 'nZahl = __NICHTS__ + 2; sieht auf den ersten Blick OK aus. Aber der Compiler würde es mit 'nZahl = 0; + 2;' übersetzen. Solche Fehler sind nacher schwer zu finden. Deshalb sollte man immer darauf achten, keine Semikolons und Anmerkungen innerhalb vom WERT zu verwenden.

Hier sind alle Ausgabetexte von main.cpp durch Defines ersetzt:

main.cpp
#include <iostream.h>
#include <stdlib.h>
#include <limits.h>
#include <float.h>
#include "main.h"



void ScppkMinus( int nZahl1, int nZahl2 )
{
  double dErgebnis;
  double d1 = 0;

  dErgebnis = (double) nZahl1 - (double) nZahl2;
  if ( nScppkIntegertest( dErgebnis ) )
  {
    cout << nZahl1 - nZahl2;
  }
}



int nScppkIntegertest( double dZahl )
{
  int nReturn = 0;
  if ( _finite( dZahl ) )
  {
    if ( dZahl > INT_MAX )
    {
      cout << SCPPK_STR_ERROR_ZUGROSS;
    }
    else if ( dZahl < INT_MIN )
    {
      cout << SCPPK_STR_ERROR_ZUKLEIN;
    }
    else
    {
      nReturn = 1;
    }
  }
  else
  {
    cout << SCPPK_STR_ERROR_UNDEFINIERT;
  }
  return nReturn;
}



void ScppkPlus( int nZahl1, int nZahl2 )
{
  double dErgebnis;
  double d1 = 0;

  dErgebnis = (double) nZahl1 + (double) nZahl2;
  if ( nScppkIntegertest( dErgebnis ) )
  {
    cout << nZahl1 + nZahl2;
  }
}



int main( int argc, char * argv[], char * envp[] )
{
  int nZahl1;
  int nZahl2;

  if ( argc != 3 )
  {
    SCPPK_OUT_HILFE
  }
  else
  {
    nZahl1 = atoi( argv[ 1 ] );
    nZahl2 = atoi( argv[ 2 ] );
    cout << "\n" << nZahl1 << " + " << nZahl2 << " = ";
    ScppkPlus( nZahl1, nZahl2 );
    cout << "\n" << nZahl1 << " - " << nZahl2 << " = ";
    ScppkMinus( nZahl1, nZahl2 );
  }
  return 0;
}


So kann das natürlich noch nicht funktionieren. Die Begriffe sind ja noch nicht definiert!
Deshalb setzen wir die Defines in der Headerdatei:

main.h
#ifndef __main_h__
#define __main_h__

void ScppkMinus( int nZahl1, int nZahl2 );
int nScppkIntegertest( double dZahl );
void ScppkPlus( int nZahl1, int nZahl2 );
int main( int argc, char * argv[], char * envp[] );

#define SCPPK_STR_ERROR_ZUGROSS "Error (Das Ergebnis ist zu gross)\n"
#define SCPPK_STR_ERROR_ZUKLEIN "Error (Das Ergebnis ist zu klein)\n"
#define SCPPK_STR_ERROR_UNDEFINIERT "Error (Das Ergebnis ist \
  nicht definiert)\n"
#define SCPPK_OUT_HILFE \
  cout << "Bitte geben sie 2 Zahlen als Parameter an.\n";\
  cout << "z.B.  Rechne 12 8\n";

#endif


Normalerweise stört es in C++ nicht, wenn eine Zeile in zwei geschrieben wird. Schließlich werden die Zeilen erst mit dem Semikolon beendet.
Davon gibt es jedoch Ausnahmen. Innerhalb von Strings oder Defines darf die Zeile z.B. nicht gewechselt werden. Um eine Zeile im Editor in mehrere Zeilen zu schreiben kann man jedoch das Zeichen \ benutzen. Wenn dieses am Ende der Zeile steht, wird die nächste Zeile vor dem Compilieren an die vorherige drangehängt.

Für den Compiler ist zwischen
cout << "Hallo Du da";
und
cout << "Hallo \
Du da";

kein Unterschied.

Es ist in C++ erlaubt mehrere Code-Zeilen in eine einzige zu schreiben.
So ist auch
cout << "Hallo ";
cout << "Du da";

und
cout << "Hallo "; cout << "Du da";
für den Compiler das Selbe.

Deshalb ist es möglich mehrere Code-Zeilen in ein einziges Define zu schreiben.

Hoffentlich war das jetzt nicht zu verwirrend. Wenn jetzt noch einiges davon unklar ist, ist das nicht so schlimm. Durch den regelmäßigen Gebrauch wird sich in den nächsten Seiten vieles davon wie von selbst erklären.