In einem zweiten Schritt wollen wir das Problem nun objektorientiert angehen, losgelöst von herkömmlichen Typdefinitionen. Ein String ist ein eigenständiges Objekt: er kann erzeugt und zerstört werden, sein Wert kann verändert, ausgelesen und ausgegeben werden. Seine tatsächliche Repräsentierung bleibt vollkommen verborgen, das Objekt wird angesprochen über einen void * - Zeiger, also über seine Adresse im Speicher.
/* ( str_oo.h ) */ #ifndef STR_OO_H #define STR_OO_H /* String-Definition: abstrakte Objekte */ void * newString (const char * text); void deleteString (void * _string); void setvalue (void * _string, const char * text); char * getvalue (const void * _string); void cout (const void * _string); #endifnewString erzeugt einen neuen String; als Argument erhält die Funktion den Wert, der dem Objekt zugwiesen werden soll (Initialisierung).
Analog zerstört deleteString das Objekt, setvalue, getvalue und cout sind die Funktionen, die uns den Zugriff auf die Daten ermöglichen.
Das Hauptprogramm sieht dann so aus:
/* main_oo.c */ #include "str_oo.h" #include <stdio.h> /* Hauptprogramm: abstrakte Objekte */ int main() { void * s = newString("Hallo\n"); cout(s); setvalue(s,"nochmal Hallo\n"); printf("%s", getvalue(s)); return 0; }
Ein Testlauf:
cn@cicero:/home/cn/seminar/aktuell/seminar/kap1 > /bin/ls error.c main_ih main_oo.c str_ih.h str_oo.h error.h main_ih.c makefile str_ih.o test1 error.o main_ih.o str_ih.c str_oo.c typescript cn@cicero:/home/cn/seminar/aktuell/seminar/kap1 > make main_oo gcc -c main_oo.c gcc -c str_oo.c gcc -o main_oo main_oo.o str_oo.o error.o cn@cicero:/home/cn/seminar/aktuell/seminar/kap1 > main_oo Hallo nochmal Hallo
Betrachten wir nun die Implementierung:
/* str_oo.c */ #include <stdio.h> #include "str_oo.h" #include "error.h" /* String-Implementation: abstrakte Objekte */ static const char STR_Rep; static const void * STRING = & STR_Rep; /* die Repraesentation der Klasse STRING, einfach eine Adresse */ struct String { void * class; char * text; }; /* die Definition der Objektstruktur */
Die Definition sieht jetzt schon etwas komplizierter aus.
Wir fassen die Objekte vom Typ STRING zu einer eigenen
Klasse zusammen, da sie alle gleiche Eigenschaften besitzen.
Die Klasse STRING repräsentieren wir mit Hilfe einer
statischen Variablen STR_Rep, genauer gesagt ihrer
Adresse.
Das ist selbstverständlich nicht absolut eindeutig, aber
ohne speziellen Präprozessor haben wir nicht die
Möglichkeit, eine bessere Repräsentation zu realisieren.
( zur Eindeutigkeit vergleiche :
struct irgendwas { long zahl; ... };Eine Struktur dieses Typs , deren Komponente .zahl zufällig - wenn auch mit geringer Wahrscheinlichkeit - gleich der Adresse von STR_Rep wäre, könnte nicht als fehlerhafter Typ erkannt werden. )
Als nächstes definieren wir die Objektstruktur.
Ein Objekt zeigt zum einen auf seine Klasse und enthält zum
anderen seine Daten, nämlich den Text.
Betrachten wir nun die einzelnen Funktionen:
/* new: Erzeugen eines neuen Strings */ void * newString(const char * text) { void * p = (void*) calloc (1, sizeof(struct String)); if (p==0) fatalerror(ALLOCMEM,1); ((struct String *) p) -> text = (char*) calloc \ (strlen(text)+1,sizeof(char)); if ( ((struct String *) p ) -> text == 0) fatalerror(ALLOCMEM,1); strcpy( ((struct String *) p)->text,text); ((struct String *) p)->class = STRING; return p; }
newString sorgt dafür, daß Speicher bereitgestellt und der STRING korrekt initialisiert wird. Wir müssen hierzu selbstverständlich den void * - Zeiger, durch den wir das Objekt identifizieren, casten - ein STRING ist letzten Endes nichts anderes als ein Zeiger auf struct String.
/* delete: Zerstoeren eines Objekts */ void deleteString(void * _string) { struct String * string = _string; if (string==0) return; if ( string->class != STRING ) fatalerror(TYPE,1); free( string->text); free(_string); _string=0; }
deleteString zerstört ein Objekt; sämtlicher belegter Speicher wird freigegeben. Eine einfache Typprüfung führen wir durch, indem wir den Wert der Komponente .class überprüfen.
/* setvalue: Wert eines Strings veraendern */ void setvalue (void * _string, const char *text) { struct String * string = _string; if (string==0) fatalerror(NULLPTR,1); if (string->class != STRING) fatalerror(TYPE,1); free(string->text); string->text = ( char *) calloc (strlen(text)+1, sizeof(char)); if (string->text == 0) fatalerror(ALLOCMEM,1); strcpy(string->text,text); }
setvalue allokiert Speicher und weist der Komponente .text ihren neuen Wert zu.
/* getvalue: Wert eines Strings auslesen */ char * getvalue ( const void * _string) { char *text; const struct String * string = _string; if (string==0) fatalerror(NULLPTR,1); if (string->class != STRING) fatalerror(TYPE,1); text=(char*) calloc (strlen(string->text)+1,sizeof(char)); if (text==0) fatalerror(ALLOCMEM,1); strcpy(text, string->text); return text; }
getvalue liefert einen Zeiger auf den Text zurueck, und zwar einen Zeiger auf eine Kopie (wir wollen schließlich nicht, daß unsere Daten manipuliert werden) !
/* cout: einen String auf stdout schreiben */ void cout (const void * _string) { const struct String * string = _string; if (string==0) return; if (string->class != STRING) fatalerror(TYPE,1); fprintf(stdout, "%s", string->text); }
cout schreibt den STRING auf stdout.