Aufteilung auf mehrere Quelltexte

Da ein Programm in aller Regel nicht in einem Stück geschrieben wird, bietet es sich an, die Definition einer Klasse in eine eigene Datei zu schreiben und in allen Programmteilen, welche die Klasse verwenden, eine passende Headerdatei mit #include ... einzufügen.

Solche einzelnen Module sind übersichtlicher, besser wartbar, und leichter wieder zu verwenden.

Prinzipiell gilt dieses Kapitel nicht nur für C++, sondern ebenso für C. Dort wird aber natürlich nicht eine Klasse in einem solchen Modul abgelegt, sondern thematisch zusammengehörige Funktionen und Datentypen; eben genau das, was man in C++ zu einer Klasse zusammenfaßt.

Das eben genannte Beispiel mit der Klasse ErsteKlasse könnte also so aufgeteilt werden:

Jede Klasse (im Beispiel nur ErsteKlasse) sollte für sich eine funktionale Einheit bilden, und genau einem Quelltext zur Definition (ErsteKlasse.cpp) und einem zur Deklaration (ErsteKlasse.h) entsprechen.

Der Dateiname sollte (von der Endung abgesehen) möglichst eng aus dem Klassennamen abgeleitet sein. Die unter MSDOS und ähnlichen Systemen bekannten Beschränkungen des Dateinamens auf 8 Zeichen (zuzüglich Endung) sind inzwischen Geschichte; sogar die aktuellen Windowsversionen vertragen hier mehr. Leider gibt es aber nach wie beschränkte Systeme, die nicht zwischen Groß- und Kleinschreibung unterscheiden. Trotzdem ist man gut geraten, wenn man die Groß-/Kleinschreibung der Klassennamen auch in den include-Anweisungen und den Dateinamen konsequent einhält, weil immer wieder zumindest Teile eines Projekts auf fortschrittlichere Systeme portiert werden müssen, wo dann die Schreibweise beachtet wird.

ErsteKlasse.h enthält alle Deklarationen, die für die Definition und Verwendung der Klasse benötigt werden:

// Time-stamp: "(13.11.01 13:18) ErsteKlasse.h [Klaus Wachtler (aw38)]"
//
// Vereinbarung (Deklaration) der Klasse ErsteKlasse:

#ifndef _ERSTEKLASSE_H_
#define _ERSTEKLASSE_H_

class ErsteKlasse
{
private:

    int     zahl1;
    int     zahl2;

public:

    void setzezahlen( int z1, int z2 );
    int summe( void );

};

#endif // defined _ERSTEKLASSE_H_
Die Elementfunktionen werden innerhalb der class-Anweisung jetzt nicht mehr definiert, sondern nur deklariert.

Damit ein mehrfaches #includen nicht zu Problemen führt, wird am Anfang jeder Headerdatei ein Makro definiert, das aus dem Dateinamen abgeleitet wird (hier: _ERSTEKLASSE_H_), und alles mit einem #ifndef _ERSTEKLASSE_H_ eingerahmt. Dadurch wird der gesamte Inhalt der Datei nur beim ersten #includen wirksam.

Eine weitere Datei (ErsteKlasse.cpp) nimmt die Definition der Elementfunktionen auf (Implementation der Klasse):

// Time-stamp: "(13.11.01 13:19) ErsteKlasse.cpp [Klaus Wachtler (aw38)]"
//
// kleine Demonstration einer Klasse
//
// In dieser Datei werden die Elementfunktionen der Klasse
// definiert.

#include "ErsteKlasse.h"   // Hiermit ist die Klasse mit dem 
                           // Namen ErsteKlasse deklariert.


void ErsteKlasse::setzezahlen( int z1, int z2 )
{
    zahl1 = z1;
    zahl2 = z2;
}

int ErsteKlasse::summe( void )
{
    return zahl1 + zahl2;
}
Nach den üblichen Anfangskommentaren werden in den Quelltext ziemlich zu Anfang die zugehörigen Deklarationen mit #include "ErsteKlasse.h" eingefügt. Weil die Elementfunktionen innerhalb der class-Anweisung nur noch deklariert werden, weiß der Compiler nicht automatisch, daß die Funktionen setzezahlen() und summe() zu der Klasse ErsteKlasse gehören. Daher muß man ihm dies mit dem scope resolution-Operator :: mitteilen.

Da das ,,Anwendungsprogramm`` (hier: klasse1t.cpp) eher klein ist, bildet es nur ein Modul ohne eigene Headerdatei:

// Time-stamp: "(15.11.01 16:10) klasse1t.cpp [Klaus Wachtler (aw38)]"

// Kleines Testprogramm zu der Klasse ErsteKlasse

#include <iostream>

using namespace std;

#include "ErsteKlasse.h"   // Hiermit ist die Klasse mit dem 
// Namen ErsteKlasse deklariert.

int main( int nargs, char *args[] )
{
  // von der Klasse ErsteKlasse werden zwei Instanzen a und b erzeugt:
  ErsteKlasse   
    a,
    b;

  // a wird auf { 2, 3 } und b auf { 10, 12 } gesetzt:
  a.setzezahlen( 2, 3 );
  b.setzezahlen( 10, 12 );

    // Die Summe a.zahl1+a.zahl2 ausgeben:
  cout << "a hat den Wert " << a.summe() << "\n";
  // dto. mit b:
  cout << "b hat den Wert " << b.summe() << "\n";

  return 0;
}
In echten Programmen hat man als Anwendung meist mehrere Module. Jedes dieser Module holt sich gegebenenfalls über #include "ErsteKlasse.h" die nötigen Vereinbarungen und kann ErsteKlasse wie einen eingebauten Datentyp verwenden, indem es Objekte des Typs (Instanzen der Klasse) vereinbart. Die kompilierten Module werden dann mit dem Kompilat von ErsteKlasse.cpp zusammengelinkt.

Hat man weitere Programmteile, die für sich genommen zu einem Modul zusammenpassen (ohne eine Klasse zu bilden), dann schreibt man auch hier zu jedem Modul einen Quelltext mit den Definitionen und eine Headerdatei mit den Deklarationen.

Damit wird eine sehr günstige Aufteilung des Quelltextes erreicht: die anwendenden Programmteile sehen nur Deklarationen, ohne die Repräsentation der Elementfunktionen zu kennen oder die geschützten Elemente direkt anfassen zu können. Andererseits kann in der Definition der Elementfunktionen die Implementation der Methoden geändert werden, ohne die Schnittstelle zu ändern. Dadurch bleiben die anwendenden Programmteile von solchen Änderungen unberührt; sie müssen gegebenenfalls nicht neu kompiliert oder gar geändert werden. So sind Funktion und Verwendung der Klassen vollkommen trennbar.

Zu beachten ist aber, daß inline-Funktionen dem Compiler dann bekannt sein müssen, wenn er ihren ,,Aufruf`` übersetzt. Solche Elementfunktionen gehören deshalb nicht in das Implementationsmodul (hier ErsteKlasse.cpp), sondern in die Deklarationsdatei (ErsteKlasse.h). Elementfunktionen werden (wenn sie klein und damit für inline geeignet sind) dann oft gleich in den Block der zugehörigen class geschrieben.

AnyWare@Wachtler.de