|
Objektorientiertes Programmieren mit C++ und JAVA
|
|  
|
9.3 C++-Version von intstack
|  
|
Wir setzen die Bibliothek zunächst 1:1 in eine Klasse um. Was die
einzelnen Dateien angeht, sind wir nun in der Situation, die im
Abschnitt 2.2.2
grafisch dargestellt wurde.
Die Header-Datei zu unserer Klasse ist folgende:
- // intstack.h -- Header-Datei zur Klassenbibliothek "intstack"
class intstack
{
private:
struct element *first;
public:
intstack();
bool isempty();
void push(int);
void pop();
int top();
~intstack();
};
Es folgt wieder ein kurzes Testprogramm:
- #include <iostream.h>
#include "intstack.h"
void main(void)
{
intstack s;
cout << "Stack ist" << (s.isempty()?" ":" nicht") << " leer\n";
s.push(1);
cout << "Stack ist" << (s.isempty()?" ":" nicht") << " leer\n";
s.push(2);
s.push(3);
cout << "--> " << s.top() << endl; s.pop();
cout << "--> " << s.top() << endl; s.pop();
cout << "--> " << s.top() << endl; s.pop();
}
Es folgt die Ausgabe des Testprogramms von oben:
- (intstack konstruiert)
Stack ist leer
Stack ist nicht leer
--> 3
--> 2
--> 1
(intstack destruiert)
Der Konstruktor wird automatisch bei der
Definition intstack s; aufgerufen, der Destruktor am Ende des
Hauptprogramms, weil der Gültigkeitsbereich von s dort
zu Ende ist.
Außer den erwarteten Ausgaben erhalten wir die beiden Zeilen
(intstack konstruiert) und (intstack destruiert). Sie sind zum
Testen in den Konstruktor, bzw. Destruktor eingebaut, wie wir unten
sehen werden.
Die Implementationsdatei intstack.cpp sieht wie folgt aus:
- /* intstack.cpp -- Implementations-Datei der Klassenbibliothek "intstack" */
#include "intstack.h" // eigene Header-Datei zur Sicherheit
#include <iostream.h> // wegen cerr
#include <stdlib.h> // wegen exit
struct element // erst hier wird die interne
{ // Darstellung festgelegt
int value; // Integer-Wert (eigentliche Daten)
struct element *next; // Zeiger auf Folgeknoten
};
static char *err_msg[]=
{
"pop bei leerem Stack", "top bei leerem Stack", "Speichermangel"
};
enum err_codes
{
ERR_POP_EMPTY, ERR_TOP_EMPTY, ERR_MEMORY
};
static void error(enum err_codes msg_nr) // interne Fehler-Routine
{
cerr << "FEHLER(intstack): " << err_msg[msg_nr] << endl;
exit(10);
}
intstack::intstack() // Konstruktor -- anstelle stk_new
{
cout << "(intstack konstruiert)\n"; // unsere Test-Ausgabe
first=0; // nur first auf 0 setzen
}
bool intstack::isempty() // Test auf 0
{
return first==0;
}
void intstack::push(int n) // Element auf den Stack
{
element *new_elem;
new_elem=new element; // neuen Knoten erzeugen
if (new_elem==0) error(ERR_MEMORY); // Speichermangel...
new_elem->value=n; // int eintragen
new_elem->next=first; // Verkettung
first=new_elem; // neue Stack-Spitze setzen
}
void intstack::pop() // Element herunterholen
{
element *old_top; // Hilfszeiger
if (isempty()) error(ERR_POP_EMPTY); // pop bei leerem Stack
old_top=first; // alte Spitze merkem
first=first->next; // neue Stack-Spitze setzen
delete old_top; // alte Spitze freigeben
}
int intstack::top() // oberstes Element abfragen
{
if (isempty()) error(ERR_TOP_EMPTY); // top bei leerem Stack
return first->value;
}
intstack::~intstack() // Destruktor - anstelle stk_free
{
element *run1,*run2; // 2 Hilfszeiger
run1=first; // Start bei Stack-Spitze
while (run1!=0) // solange es noch Elemente gibt:
{
run2=run1->next; // nächstes Element merken
delete run1; // aktuelles Element freigeben
run1=run2; // weitergehen
}
first=0; // Stack auf leer setzen
cout << "(intstack destruiert)\n"; // unsere Test-Ausgabe
}
Wie schon bei den structs angedeutet, gibt es die
Möglichkeit,
Member-Funktionen direkt in der Klassendefinition mitzudefinieren -- sie sind
dann automatisch inline. Das macht man meistens mit kurzen und
einfachen Funktionen, um die Effizienz zu steigern. Der Nachteil ist, daß
ihre Implementation dann in der Header-Datei offenbart wird.
isempty und der Konstruktor sind in unserem Fall gute Kandidaten.
Die C++-Version ist zwar schon sicherer und leichter lesbar. Daß
wir die wirklichen Vorteile noch nicht überschauen können, liegt
daran, daß wir bisher im wesentlichen nur die C
-Möglichkeiten auf C++ übertragen haben.
Wir müssen in den nächsten Kapiteln die nötigen Hilfsmittel wie
überladene Konstruktoren und Operatoren sowie Vererbung erst noch
kennenlernen.
Nun gehen wir systematisch genauer auf die einzelnen Erscheinungen
beim Klassenkonzept ein.