Twitter und Facebook-Anbindung
X
Tweet Follow @twitterapi
!!! Anbindung an twitter und facebook öffnen !!!

Wenn Ihnen mein Online-Buch gefällt,
dann bedanken Sie sich doch mit einer kleinen Spende...

7 Die Header-Dateien

7 Die Header-Dateien

Header-Dateien sind wie Inhaltsverzeichnisse der zugehörigen CPP-Datei anzusehen. Ich hatte mit Ihnen die Thematik der Funktionsprototypen bereits besprochen. Ich hatte Ihnen gezeigt, dass man diese Prototypen an den Anfang der CPP-Datei packt. Dies ist aber nur wirklich sinnvoll, wenn die "main.cpp" die einzige Quelltextdatei des Projektes ist. Immer dann, wenn Sie mehrere CPP-Dateien haben, müssen Sie eine s.g. Header-Datei anlegen, welche alle Funktionsprototypen der zugehörigen CPP enthält. Eine Ausnahme stellt wieder die "main.cpp" dar, da das System im Hintergrund selbst eine solche Datei anlegt, wobei dort nur die Funktion "main" eingetragen wird. Deshalb baut man ein Projekt so auf, dass in der "main.cpp" nur das aller nötigste enthalten ist (also nur Funktionsaufrufe etc.). Alles andere lagert man in andere Dateien aus.

Eine Header-Datei heißt üblicherweise genauso wie die zugehörige CPP-Datei, nur dass ihre Dateiendung ".h" lautet. Sie enthält wie erwähnt Funktionsprototypen und oftmals auch Konstanten und "defines". Ebenso ist es möglich, ganze Funktionen in die Header zu packen, aber davon rate ich dringlichst ab, zumal, um bei der Analogie zu bleiben, im Inhaltsverzeichnis eines Buches, keine Fließtexte etwas zu suchen haben. Ich erwähne dies nur, weil einem hin und wieder so etwas über den Weg läuft.

Ein weiterer Grund, warum man möglichst wenig Logik in Header-Dateien bringt ist, dass diese nicht mit kompiliert werden bzw. sie nicht in Bytecode umgesetzt werden. Wenn Sie Ihr Projekt weiter geben und der andere nicht sehen soll, wie Sie gewisse Sachen gelöst haben, reicht es ihm die vor kompilierten "obj" Dateien (das wird aus cpp Dateien) und die Header als Klartext zu geben. Somit darf in den Headern nichts stehen, was ein "Geheimnis" bleiben soll. Wenn man doch eine Funktion in die Header-Datei einbaut, sollte es nur etwas banales sein.

Zum Seitenanfang
Zum Inhaltsverzeichnis

7.3 Inlinefunktionen

7.3 Inlinefunktionen

Ich hatte kurz erwähnt, dass man hin und wieder auch in Header-Dateien Funktionen einbaut. Wenn dies getan wird, handelt es sich oftmals um s.g. Inlinefunktionen, welche in der Regel aus sehr wenig Quelltext bestehen (oft nur eine Zeile). Doch was hat es mit diesem "inline" auf sich? Um dies zu verdeutlichen, habe ich mir ein kleines Beispiel ausgedacht.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
					
inline int Summe(int iSummand1, int iSummand2) { return iSummand1 + iSummand2; };



// Hauptfunktion der Anwendung ////////////////////////////////////////////////
int main(int argc, char* argv) {
	printf("%i\n", Summe(5, 3));

	return 0;
} // main /////////////////////////////////////////////////////////////////////
					

In Zeile 1 habe ich eine Inlinefunktion deklariert. Auf den ersten Blick, sieht dies wie ein Funktionsprototyp aus, aber wenn Sie genauer hinschauen, sehen Sie, dass da noch mehr steht. Zu Beginn einer solchen Funktion schreibt man das Schlüsselwort "inline" und man muss am Ende ein Semikolon setzen. Des Weiteren wird an dieser Stelle gleich der Quelltext der Funktion mit hingeschrieben, welcher in geschweifte Klammern gesetzt wird. Aber wozu dieser ganze Aufwand? Inlinefunktionen sind etwas ganz besonderes, da der Präprozessor diese 1 zu 1 an die entsprechenden Stellen des Quelltextes kopiert. Der Funktionsaufruf wird also mit dem Inhalt der Inlinefunktion ersetzt und die Funktion selbst, taucht im resultierenden Programm nicht mehr auf. Der Quelltext, welcher dann tatsächlich im Hintergrund ausgeführt wird, sieht wie folgt aus.

 1
 2
 3
 4
 5
 6
					
// Hauptfunktion der Anwendung ////////////////////////////////////////////////
int main(int argc, char* argv) {
	printf("%i\n", 5 + 3);

	return 0;
} // main /////////////////////////////////////////////////////////////////////
					

Warum braucht man so etwas? Nun, Funktionen sind immer gut, um seinen Quelltext zu strukturieren bzw. häufig verwendete Sachen, auslagern zu können. Sie erleichtern somit das Leben des Programmierers. Allerdings hat die ganze Sache einen Haken. Jeder Funktionsaufruf kostet wie bereits besprochen Zeit und benötigt zusätzlichen Speicherplatz. Inlinefunktionen wirken dieser ganzen Sache entgegen, weil sie keine richtigen Funktionen sind. Nur in dem Fall, dass der Präprozessor der Meinung ist, dass der Inhalt der Funktion doch zu groß ist bzw. sich der Inhalt nicht einfach an die entsprechende Stelle einfügen lässt, macht er tatsächlich eine normale Funktion daraus. Sie als Programmierer haben also keinen richtigen Einfluss darauf. Manchmal werden auch Funktionen als inline behandelt, bei welchen Sie gar nicht dieses Schlüsselwort geschrieben haben.

Zum Seitenanfang
Zum Inhaltsverzeichnis

7.4 Übungsaufgaben V

7.4 Übungsaufgaben V

  1. Erstellen Sie ein Dateipaar "Square.h" und "Square.cpp" und ein Dateipaar "Circle.h" und "Circle.cpp". Implementieren Sie eine Funktion mit einem sprechenden Namen für die Berechnung des Umfanges und eine weitere für die Berechnung des Flächeninhaltes. Beiden soll eine Gleitkommazahl, die Kantenlänge bzw. den Radius, übergeben werden und eine Gleitkommazahl, mit dem Ergebnis, zurückgeben. In diesen Funktionen, sollen weder Ein -, noch Ausgaben getätigt werden. Vergessen Sie nicht, in den Header-Dateien die Prototypen und Includewächter zu deklarieren.
  2. Erstellen Sie zudem noch jeweils 2 weitere Funktionen, welche sich um die Eingabe und um die Ausgabe kümmern. Benutzen sie einen geeigneten Parameter, um zwischen Umfang oder Flächeninhalt unterscheiden zu können. Benutzen Sie "defines" für auszugebende Texte und bauen Sie deren Deklarationen eine separate Header-Datei mit dem Namen "Strings.h".
  3. Erstellen Sie in der "main.cpp" eine Funktion mit den Namen "MainMenu", welche das Hauptmenü auf dem Bildschirm ausgibt und den Benutzer fragt, was er ausgerechnet haben will (Flächeninhalt oder Umfang von Kreis oder Quadrat). Definieren Sie für die Ausgaben entsprechende "defines", welche ebenfalls in der entsprechenden Header-Datei stehen sollen.
  4. Rufen Sie in der Funktion "main" das Hauptmenü auf und speichern Sie das Ergebnis der Funktion in einer geeigneten Variable. Bauen Sie anschließend noch eine Fallunterscheidung ein, in welcher Sie die entsprechenden Funktionen für die Eingaben, Berechnungen und Ausgaben aufrufen. Zudem soll die Auswahl und die Berechnungen mehrfach ausgeführt werden können, ohne das Programm ständig neu starten zu müssen. Überlegen Sie sich dafür einen geeigneten Mechanismus.
  5. Implementieren Sie ein weiteres Dateipaar "Faculty.h" und "Faculty.cpp". Erstellen Sie dort die vier Funktionen "FacultyInput", "FacultyIterative", "FacultyRecursive" und "FacultyPutput". Erstere soll die Fakultät rein iterativ (mit einer Schleife) berechnen (zur Erinnerung - 5! = 5 * 4 * 3 * 2 * 1). Die zweite Funktion soll, wie der Name schon verrät, diese Berechnung rekursiv durchführen (5! = 5 * 4!). Damit soll gezeigt werden, dass sich Funktionen auch durchaus selbst aufrufen können.
  6. Erweitern Sie nun das Hauptmenü und die "main", um auch diese Berechnungen durchführen zu können.
  7. Bauen Sie nun das ganze Programm so um, dass man mit einem entsprechendem "define", zwischen deutsch und englisch umschalten kann. Natürlich müssen dazu alle "defines" doppelt vorkommen und übersetzt werden. Hierbei kommt es nicht auf ein perfektes englisch an.
Zum Seitenanfang
Zum Inhaltsverzeichnis

7.2 Beispiel

7.2 Beispiel

Ich benutze für dieses Beispiel weiterhin die Datei "beispiel.cpp" die ich eben erklärt habe. Allerdings möchte ich, die Header-Datei so absichern, dass sie niemals zweimal eingebunden wird.

beispiel.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
								
// Nur wenn noch nicht geschrieben wurde "Ich bin da"
#ifndef BEISPIEL_H
	// Schreie "Ich bin da"
	#define BEISPIEL_H
	
	// Erste Überschrift
	void Ausgabe(int iWert);

	// Zweite Überschrift
	int Addiere(int iSummand1, iSummand2);

	// Dritte Überschrift
	void Tausche(int &iZahl1, int &iZahl2);
#endif
								
main.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
								
#include <stdio.h>
#include "beispiel.h"



// Hauptfunktion der Anwendung ////////////////////////////////////////////////
int main(int argc char** argv) {
	int iWert1;
	int iWert2;

	printf("Bitte irgendeine Zahl eingeben:  ");
	scanf("%i", iWert1);

	printf("Bitte eine andere Zahl eingeben: ");
	scanf("%i", iWert1);
	
	Ausgabe(Addiere(iWert1, iWert2));
	
	Tausche(iWert1, iWert2);
	Ausgabe(iWert1);
	Ausgabe(iWert2);

	return 0;
} // main /////////////////////////////////////////////////////////////////////
								

Ausgabe:

Bitte irgendeine Zahl eingeben:  5
Bitte eine andere Zahl eingeben: 8
13
8
5
		

Wie Sie in der Header-Datei sehen können, habe ich "defines" und "ifndef" benutzt, damit es durch Verschachtelungen nicht vorkommen kann, dass ein Header mehrfach eingebunden wird, was zur Folge hätte, dass der Linker nicht mehr weiß, welcher Funktionsprototyp zu welcher Funktionsimplementierung gehört.

Um dies zu verdeutlichen, habe ich mir mal ein komplexeres Beispiel ausgedacht.

Struktur eines größeren Projektes

Die roten Pfeile stellen die tatsächlichen "includes" dar, während die gelben zeigen, was indirekt mit eingebunden wird.

Warum muss man jetzt also die Header vor einem doppelten Import schützen? Nun, die "main.cpp" braucht die "Volumen.h" und die wiederum die "MathematischenKonstanten.h" und jene die "Textbausteine.h". Nun braucht die "main.cpp" aber auch die "Oberflaechen.h", die ihrerseits auch die "MathematischenKonstanten.h" usw. braucht. Dadurch würde in der "main.cpp" zweimal die mathematischen Konstanten und zweimal die Textbausteine stehen. Sie können aber auch nicht einfach erst die "volumen.h" importieren und dann in der "Oberflaechen.h" einfach auf die "includes" verzichten. Theoretisch würde das zwar in diesem Spezialfall klappen, aber dann dürfte niemand die Reihenfolge der "includes" ändern und Sie könnten die Funktionalitäten der Oberflächenberechnungen in keinem anderen Projekt verwenden, ohne dass Sie Umbauten vornehmen müssten (die entsprechenden "includes" müssten wieder aufgenommen werden).

Sie sehen also, dass es klug ist, sich solche s.g. Includewächter mit "ifndef" zu bauen, da man sich dann um keine Konflikte bzw. Dopplungen mehr Sorgen machen muss. Zudem ist es ratsam, sich die Struktur eines Projektes so aufzuzeichnen, wie ich das oben gemacht habe. Dann sieht man, dass man ggf. das eine oder andere "include" gar nicht benötigt (überflüssige "includes" wurden in der oberen Grafik auskommentiert).

Zum Seitenanfang
Zum Inhaltsverzeichnis

7.1 Aufbau eines Headers

7.1 Aufbau eines Headers

beispiel.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
								
// Dies ist die einfachste Version einer Header-Datei.
// Bei dem unten stehenden Beispiel werde ich
// diesen #ifndef Kram mit rein nehmen

// Erste Überschrift
void Ausgabe(int iWert);

// Zweite Überschrift
int Addiere(int iSummand1, iSummand2);

// Dritte Überschrift
void Tausche(int &iZahl1, int &iZahl2);
							
beispiel.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
							
#include <stdio.h>
#include "beispiel.h"



// Erstens und Text dazu //////////////////////////////////////////////////////
void Ausgabe(int iWert) {
	printf("%i", iWert);
	printf("\n");
} // Ausgabe //////////////////////////////////////////////////////////////////



// Zweitens und Text dazu /////////////////////////////////////////////////////
int Addiere(int iSummand1, iSummand2) {
	return iSummand1 + iSummand2;
} // Addiere //////////////////////////////////////////////////////////////////



// Drittens und Text dazu /////////////////////////////////////////////////////
void Tausche(int &iZahl1, int &iZahl2) {
	int iTemp	= iZahl1;
	iZahl1		= iZahl2;
	iZahl2		= iTemp1;
} // Tausche //////////////////////////////////////////////////////////////////
								

Für die "main.cpp" wird in der Regel kein Header angelegt, da dies automatisch im Hintergrund geschieht, bzw. es nicht nötig sein sollte, da man Hilfsfunktionen in der Regel in anderen Dateien unterbringt und in der main nur das Nötigste rein gehört.

Zum Seitenanfang
Zum Inhaltsverzeichnis

© Copyright by Thomas Weiß, 2009 - 2012