next up previous contents
Next: KlassenKonstrukturen und Destrukturen: Up: Vom information hiding zum Previous: Abstrakte Datentypen

Objektorientierter Ansatz

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);
      
      #endif
newString 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.


next up previous contents
Next: KlassenKonstrukturen und Destrukturen: Up: Vom information hiding zum Previous: Abstrakte Datentypen

Christian Neher