Weitere zusammengesetzte Datentypen

| ### Programmieren in C von Prof. Dr. Rainer Thomas

# 9. Weitere zusammengesetzte Datentypen

### 9.1 Aufbau und Vereinbarungen von Strukturen ("structures")
9.2 Operationen mit Structuren
9.3 Dynamische Datenstrukturen
9.4 Typdefinition mit "typedef"
9.5 Variante Strukturen ("unions")
9.6 Bitfelder
9.7 Vorwärtsdeklaration von Struktur-Typen

## 9.1 Aufbau und Vereinbarungen von Strukturen ("structures")

### Strukturen (structures) in C

- Strukturen (structures) in C sind selbstdefinierte zusammengesetzte Datentypen.
  Sie bestehen aus einer festen Anzahl von Komponenten, die von unterschiedlichen Typen (auch wieder zusammengesetzten Typen) sein können. (entsprechend den RECORDs in PASCAL)

  --> mehrere Daten der unterschiedlichsten Art lassen sich somit zu einer einzigen Variablen zusammenfassen und damit unter einem einzigen Namen ansprechen.

  --> | Aufbau von sehr effektiv handhabbaren problemangepaßten Datenstrukturen.
  Beispiel : | Zusammmenfassung relevanter Personaldaten, wie
  - Name | `char name[20];`
  - Vorname | `char vorname[20];`
  - Geburtsdatum | `int jahr; char monat[4]; short tag;`
  - Familienstand | `char verheiratet;`

  Die einzelnen Komponenten (Felder) einer structure werden nicht - wie bei Arrays - durch einen Index sondern durch einen Namen gekennzeichnet.

- Vereinbarung von Strukturen

  - Auflistung der einzelnen Komponenten (Felder) der structure unter Angabe von Typ und Namen.

  - Eine Strukturvereinbarung kann sein :
    - eine reine Strukturtypdefinition.
      Der dabei eingeführte Typname (structure tag) kann im weiteren für die Vereinbarungvon Strukturvariablen verwendet werden.
    - eine reine Strukturvariablenvereinbarung.
      Ein Typname wird hierbei nicht eingeführt - wohl aber ein namenloser Strukturtyp definiert.
    - eine gleichzeitige Strukturtypdefinition und Strukturvariableneinbarung.

  - Aufbau einer Strukturvereinbarung siehe Syntaxdiagramm

  - Der Name einer Strukturkomponente darf mit dem Typnamen der gleichen oder einer anderen Struktur übereinstimmen.
    Strukturkomponentennamen und Strukturtypnamen dürfen mit Variablennamen übereinstimmen.
    In ANSI-C (nicht jedoch in KuR-C) dürfen auch in unterschiedlichen Strukturen gleiche Komponentennamen verwendet werden.
    Derartige Namensübereinstimmungen sollten aber nach Möglichkeit vermieden werden.

- Initialisierung von Strukturvariablen
  mit einer Liste konstanter Ausdrücke - entsprechend den Komponenten
  - bei KuR-C : nur für Speicherklasse `extern` und `static`
  - bei ANSI-C: auch für Speicherklasse `auto` zulässig

- Ablage der Strukturkomponenten im Arbeitsspeicher entsprechend der Reihenfolge ihrer Auflistung in der Strukturtypdefinition bei niedrigerer Adresse beginnend.
  Infolge eventueller Speicherausrichtung (alignment) können sich zwischen den Komponenten Lücken befinden.

### Vereinbarung von Strukturen in C

- Strukturvereinbarung

  - reine Strukturtypdefinition
    (ohne Angabe einer Variablenbezeichnung)

  - reine Strukturvariablenvereinbarung
    (ohne Angabe eines Strukturnamens)

  - Strukturtypdefinition und Strukturvariablenvereinbarung

  Komponentenangabe

- Variablenvereinbarung mit vorher definiertem Strukturnamen

  `struct Strukturname` dient als Datentypangabe.

### Beispiele zur Vereinbarung von Strukturen in C

    /* -------------------------------------------------------------------- */

    /* Definition eines Structure-Typ-Namens */

       struct datum
       { int tag;
         char monat[4];
         int jahr;
       };

    /* -------------------------------------------------------------------- */

    /* Variablenvereinbarung unter Verwendung eines vorher definierten
       Structure-Typ-Namens */

       struct datum geb_datum;

    /* -------------------------------------------------------------------- */

    /* Vereinbarung einer Variablen eines "namenlosen" Structure-Typs */

       struct
       { int tag;
         char monat[4];
         int jahr;
       } geb_datum;

    /* -------------------------------------------------------------------- */

    /* Definition eines Structure-Typ-Namens und Vereinbarung einer
       Variablen dieses Typs */

       struct datum
       { int tag;
         char monat[4];
         int jahr;
       } geb_datum;

    /* -------------------------------------------------------------------- */

    /* Struktur mit einer Struktur als Komponente */

       struct person
       { char name[20],
              vorname[20];
         struct
         { int tag;
           char monat[4];
           int jahr;
         } geb_datum;
         char verheiratet;
       } student, diplomand;

    /* -------------------------------------------------------------------- */

    /* Struktur mit einer Struktur als Komponente
       Verwendung eines vorher definierten Typ-Namens fuer diese Struktur */

       struct datum
       { int tag;
         char monat[4];
         int jahr;
       };

       struct person
       { char name[20],
              vorname[20];
         struct datum geb_datum;
         char verheiratet;
       } student, diplomand;

    /* -------------------------------------------------------------------- */

### Initialisierung von Strukturen in C

    /* ------------------------------------------------------------------- */
    /*                                                                     */
    /*   KuR-C  : Nur für Speicherklassen "extern" und "static" zulässig   */
    /*                         (wie bei arrays)                            */
    /*   ANSI-C : Auch für Speicherklasse "auto" zulässig                  */
    /*            (Initialisierung durch konst. Ausdrücke)                 */
    /*                                                                     */
    /* ------------------------------------------------------------------- */
    /*                                                                     */
    /*   Achtung : Eine Initialisierung ist nicht zulässig in einer reinen */
    /*             Strukturtypdefinition                                   */
    /*                                                                     */
    /* ------------------------------------------------------------------- */

    /* Beispiel 1  */

       struct
       { int tag;
         char monat[4];
         int jahr;
       } aktuell_datum = {1, "jan", 1987};

    /* -------------------------------------------------------------------- */

    /* Beispiel 2  */

       struct datum
       { int tag;
         char monat[4];
         int jahr;
       };

       struct datum aktuell_datum = {1, "jan", 1987};

    /* -------------------------------------------------------------------- */

## 9.2 Operationen mit Strukturen

- Anwendung des Adreßoperators &

  Auf Strukturvariable läßt sich - wie auf einfache Variable - der Adreßoperator "&" anwenden. Die dadurch gebildete Adresse der Variablen läßt sich geeignet definierten Struktur-Pointer-Variablen zuweisen.
  Der Objekttyp dieser Pointer-Variablen muß mit dem Typ der Strukturvariablen übereinstimmen

  Beispiel :

      struct person student, *persptr;
      persptr=&student;

- Zugriff zu Strukturkomponenten

  - Direkter Zugriff zur Komponente einer Strukturvariablen mittels Element-Operator "."

    z.B.

        scanf("%s", student.name);
        student.vorname[0]='F';
        student.geb_datum.jahr=1991;

  - Zugriff zur Komponente eines Struktur-Pointer-Objekts mittels Objektelement-Operator "->"

    `sptr->komp` ist äquivalent mit `(*sptr).komp`

    z.B.

        scanf("%s", persptr->name);
        persptr->vorname[0]='F';
        persptr->geb_datum.jahr=1991;

  - Die Operatoren "." und "->" haben die höchste Priorität (die gleiche wie "()" und "[]"). Die Assoziavität ist von links nach rechts.

  - Strukturkomponenten können genauso wie andere Variable ihres jeweiligen Typs gehandhabt und verwendet werden.

- Wertzuweisung an Strukturvariable

  - In KuR-C ist nur eine komponentenweise Zuweisung des Werts einer Struktur an eine andere Strukturvariable möglich

  - In ANSI-C ist auch eine geschlossene Zuweisung des Werts einer Struktur an eine andere Struktuvariable möglich

    z.B.

        diplomand=student;   /* alle Komponenten von student wer-
                                       den nach diplomand übertragen  */

- Strukturen als Funktionswerte und Funktionsparameter

  - In KuR-C können nur Struktur-Pointer als Funktionsparameter und Funktionswerte verwendet werden, nicht jedoch Strukturen direkt

  - In ANSI-C können Strukturen auch direkt als Funktionsparameter übergeben und als Funktionswert erhalten werden.
    Anmerkung :
      Die direkte Parameterübergabe größerer Strukturen ist i.a. ineffektiv, da die Übergabe über den Stack erfolgt und dies Zeit und Speicherplatz erfordert.

### Programmbeispiele zu Strukturen in C

    /* Programm CPLMUL1 */

    /* Multiplikation zweier komplexer Zahlen */
    /* Pointer auf Strukturen als Parameter   */

    #include <stdio.h>

    struct complex
    { double re;
      double im;
    };

    void liescomplex(struct complex *px)
                     /* px ist Pointer auf Variable vom Typ complex */
    { struct complex xh;
      scanf("%lf%lf",&xh.re,&xh.im);
      *px=xh;
    }

    void multcomplex(struct complex *px1, struct complex *px2,
                     struct complex *pz)
                   /* px1, px2, pz sind Pointer auf Variable vom Typ complex */
    { (*pz).re = (*px1).re * (*px2).re - (*px1).im * (*px2).im;
      (*pz).im = (*px1).re * (*px2).im + (*px1).im * (*px2).re;
    }

    void auscomplex(struct complex *px)
                    /* px ist Pointer auf Variable vom Typ complex */
    { printf("( %.4f , %.4f )",px->re,px->im);
    }

    void main(void)
    { struct complex czahl1,czahl2,czahl3;
      int zeich;
      printf("\n? ");
      while ((zeich=getchar())!=EOF)
      { ungetc(zeich,stdin);
        liescomplex(&czahl1);
        liescomplex(&czahl2);
        multcomplex(&czahl1,&czahl2,&czahl3);
        auscomplex(&czahl1);
        printf(" * ");
        auscomplex(&czahl2);
        printf(" = ");
        auscomplex(&czahl3);
        printf("\n\n? ");
        getchar();
      }
    }

Bildschirmausgabe :

    E:\RT\C\VORL>cplmul1

    ? 1 1 1 1
    ( 1.0000 , 1.0000 ) * ( 1.0000 , 1.0000 ) = ( 0.0000 , 2.0000 )

    ? 2 1 -2 3
    ( 2.0000 , 1.0000 ) * ( -2.0000 , 3.0000 ) = ( -7.0000 , 4.0000 )

    ? ^Z

    /* Programm CPLMUL2 */

    /* Multiplikation zweier komplexer Zahlen      */
    /* Strukturen als Parameter und Funktionswert  */

    #include <stdio.h>

    struct complex
    { double re;
      double im;
    };

    struct complex liescomplex(void)
    { struct complex x;
      scanf("%lf%lf",&x.re,&x.im);
      return(x);
    }

    struct complex multcomplex(struct complex x1, struct complex x2)
                       /* Parameter x1 und x2 sind Variable vom Typ complex */
    { struct complex z;
      z.re = x1.re * x2.re - x1.im * x2.im;
      z.im = x1.re * x2.im + x1.im * x2.re;
      return(z);
    }

    void auscomplex(struct complex x)
                               /* Parameter x ist Variable vom Typ complex */
    { printf("( %.4f , %.4f )",x.re,x.im);
    }

    void main(void)
    { struct complex czahl1,czahl2,czahl3;
      int zeich;
      printf("\n? ");
      while ((zeich=getchar())!=EOF)
      { ungetc(zeich,stdin);
        czahl1=liescomplex();
        czahl2=liescomplex();
        czahl3=multcomplex(czahl1,czahl2);
        auscomplex(czahl1);
        printf(" * ");
        auscomplex(czahl2);
        printf(" = ");
        auscomplex(czahl3);
        printf("\n\n? ");
        getchar();
      }
    }

Bildschirmausgabe :

    E:\RT\C\VORL>cplmul2

    ? 2 1 -2 3
    ( 2.0000 , 1.0000 ) * ( -2.0000 , 3.0000 ) = ( -7.0000 , 4.0000 )

    ? ^Z

### Der sizeof-Operator in C

- unärer Operator : `sizeof`

  Auswertung zur Compilezeit.

  Operator dient zur Ermittlung der Größe des Speicherplatzes in Bytes, den ein Objekt des Typs seines Operanden belegt.

  - Operand kann sein :

    - Ausdruck
      z.B.
        `sizeof i
        sizeof 3
        sizeof (3)
        sizeof (a+b)`

      Der Ausdruck wird nicht ausgewertet, es wird nur sein Typ bestimmt

    - in Klammern gesetzte Typangabe

      z.B.
        `sizeof (int)
        sizeof (double)
        sizeof (char *)`

  - Ergebnis der Operation ist vom Typ `size_t`
    (`size_t` ist der implementierungsabhängige &qzot;natürliche" unsigned Ganzzahl-Typ, u.a. in Header-Datei `stddef.h` und `stdlib.h` definiert)

- Anwendung :
  - zur Ermittlung des Platzbedarfs zusammengesetzter Datentypen, wie structures und arrays

  - zur portablen Angabe des Platzbedarfs der Standard- Datentypen

  Die genaue Größe des von Objekten belegten Speicherplatzes wird z.B. für eine dynamische Speicherallokation benötigt.

- Beispiel :

      /* Programm TYPESIZE */
      /* Demonstrationsprogramm zum sizeof-Operator */
      /* Ausgabe der Speichergrößen der einfachen Datentypen */

      #include <stdio.h>

      void main(void)
      {
        int i=6;
        printf("\nchar       %2u Bytes\n", sizeof(char));        /* -->  1 */
        printf("short        %2u Bytes\n", sizeof(short));       /* -->  2 */
        printf("int          %2u Bytes\n", sizeof i);            /* -->  2 */
        printf("unsigned     %2u Bytes\n", sizeof(unsigned));    /* -->  2 */
        printf("long         %2u Bytes\n", sizeof(long));        /* -->  4 */
        printf("float        %2u Bytes\n", sizeof(float));       /* -->  4 */
        printf("double       %2u Bytes\n", sizeof(double));      /* -->  8 */
        printf("long double  %2u Bytes\n", sizeof(long double)); /* --> 10 */
      }

### Arrays von Strukturen in C

- Strukturen können auch Komponenten von Arrays sein (arrays of structures)

- Beispiel :

      /* --------------- Variablendefinition (1.Moeglichkeit) ------------ */

         #define MAX 1000

         struct
         { char *name;
           char *vorname;
           char *anschrift;
           long nummer;
         } telefonbuch[MAX];

      /* --------------- Variablendefinition (2. Moeglichkeit) ----------- */

         #define MAX 1000

         struct eintrag
         { char *name;
           char *vorname;
           char *anschrift;
           long nummer;
         };

         struct eintrag telefonbuch[MAX];

      /* ----------------------------------------------------------------- */
      /*                                                                   */
      /* Komponentenzugriff ueber Indizierung                              */
      /*                                                                   */
      /* z.B. : Suchen der Telefonnummer einer bestimmten Person           */

         /*  . . .  */
         char suchname[20],
         suchvorn[20];
         int i,gefunden;
         scanf("%s%s",suchname,suchvorn);
         i=gefunden=0;
         while (i<MAX && !gefunden)
         { gefunden = !strcmp(suchname,telefonbuch[i].name)
                      && !strcmp(suchvorn,telefonbuch[i].vorname);
           i++;
         }
         if (gefunden)
           printf("\n%ld\n",telefonbuch[i-1].nummer);
         /*  . . .  */

      /* ----------------------------------------------------------------- */
      /*                                                                   */
      /* Komponentenzugriff ueber Pointer-Ausdruck                         */

         printf("\n%ld\n",(*(telefonbuch+i-1)).nummer);
         printf("\n%ld\n",(telefonbuch+i-1)->nummer);

      /* ----------------------------------------------------------------- */

## Dynamische Datenstrukturen in C

- Dynamische Datenstrukturen ändern ihre Struktur und den von ihnen belegten Speicherplatz während der Programmausführung.
  Sie sind aus einzelnen Elementen (Knoten) aufgebaut, zwischen denen üblicherweise eine bestimmte ("Nachbarschafts"-) Beziehung besteht.
  Die Dynamik liegt im Einfügen neuer Knoten, Entfernen vorhandener Knoten und Änderung der Nachbarschaftsbeziehung.

  Der Speicherplatz für einen Knoten wird erst bei Bedarf zur Programmlaufzeit allokiert --> dynamische Speicherallokation mittels spezieller Speicherallokationsfunktionen.

  Da der Speicherort der einzelnen Knoten im voraus nicht bekannt ist und auch für "benachbarte" Knoten weit auseinander liegen kann, läßt sich eine ("Nachbarschafts"-) Beziehung nicht durch den Speicherort abbilden.
  Vielmehr muß die Beziehung der einzelnen Knoten untereinander über Zeiger - die jeweils auf den Speicherort des "Nachbarn" zeigen - hergestellt werden.

  --> | Jeder Knoten wird daher neben den jeweils zu speichernden Nutzdaten Zeiger auf die jeweiligen "Nachbar"-Knoten - also auf Elemente des gleichen Typs wie der des Knotens sebst enthalten
  --> | Verkettete Datenstrukturen

- Die wichtigsten dieser Datenstrukturen sind :

  - Lineare Listen
    - einfach verkette Listen
    - doppelt verkette Listen
    - Spezialformen (bezüglich der Anwendung)
      - Queue (Pufferspeicher, FIFO)
      - Stack (Kellerspeicher, LIFO)

  - Bäume
    - Binärbäume (zwei Nachfolger)
    - Vielweg-Bäume (mehr als zwei Nachfolger)

  - Allgemeine Graphen

- Die wichtigsten Operationen mit dynamischen Datenstrukturen sind :

  - Erzeugen eines neuen Elements
  - Einfügen eines Elements
  - Entfernen eines Elements
  - Suchen eines Elements

- In C lassen sich die einzelnen Elemente (Knoten) durch structures dar- stellen.
  Zur Realisierung verketteter Datenstrukturen müssen diese structures Pointer auf structures ihres eigenen Typs enthalten.
  Derartige Strukturen nennt man rekursiv.

### Aufbau verketteter Datenstrukturen mittels rekursiver Strukturen

- Einfach verkettete Liste

      struct listelement
      { int inhalt;
        struct listelement *next;
      };

- Doppelt verkettete Liste

      struct listelement
      { int inhalt;
        struct listelement *next;
        struct listelement *back;
      };

- Binärer Baum

      struct baumelement
      { int inhalt;
        struct baumelement *rechts;
        struct baumelement *links;
      };

### Beispiel zu rekursiven Strukturen :
Ý Sortierte Speicherung von double-Werten in einem Binär-Baum

- Aufgabe :
  Einlesen einer beliebigen Anzahl von double-Werten von der Tastatur (Ende der Eingabe mit `CTRL-Z`).
  Ausgabe der Werte in aufsteigender Wertigkeit am Bildschirm.
  Mehrmalige gleiche Werte sind nur einmal - zusammen mit der Anzahl ihrer Auftritte - auszugeben.

- Lösung :
  Sortierte Speicherung der eingelesenen Werte in einem Binär-Baum. Jeder Knoten des Baums enthält
  den jeweiligen Wert,
  die Anzahl der Auftritte dieses Wertes,
  sowie je einen Pointer auf einen rechten
  und einen linken Folgeknoten

  ---> | Typdefinition des Knotens :

      struct dknoten
      { double wert;
        int anzahl;
        struct dknoten *rechts;
        struct dknoten *links;
      };

  Speicherung erfolgt so, daß sich jeder Wert der kleiner als ein Knotenwert ist, im linken Unterbaum und jeder Wert der größer ist, im rechten Unterbaum dieses Knotens befindet.

  Die Ausgabe erfolgt mittels einer rekursiven Funktion:
  Beginnend mit dem Wurzelknoten wird zuerst der linke Unterbaum, dann der Wurzelknoten und dann der rechte Unterbaum ausgegeben.

- Beispiel für Eingabe : `1.2 4.5 3.1 -4.5 6.3 3.1 -5.8`

### Programm zum Beispiel zu rekursiven Strukturen

    /* Programm SORTD */

    /* Sortierte Ausgabe (aufsteigende Wertefolge) einer beliebigen
       Anzahl in beliebiger Reihenfolge ueber stdin eingegebenen Folge
       von double-Werten nach stdout.
       Speicherung der Werte in einem Binär-Baum */

    #include <stdio.h>
    #include <stdlib.h>

    struct dknoten                     /* Typ-Definition des Knotens */
    { double wert;
      int anzahl;
      struct dknoten *rechts;
      struct dknoten *links;
    };

    void main(void)
    { struct dknoten *wurzel;
      double d;
      struct dknoten *einf_baum(struct dknoten *, double);
      void baumaus(struct dknoten *);
      wurzel=NULL;
      printf("\nBitte Zahlen eingeben !\n");
      while (scanf("%lf",&d)!=EOF)
        wurzel=einf_baum(wurzel,d);
      printf("\nSortierte Folge der eingegeben Zahlen :\n\n");
      baumaus(wurzel);
    }

    struct dknoten *einf_baum(struct dknoten *pk, double a)
      /* Einfügen Wert in Baum */   /* pk  Zeiger auf akt. Knoten des Baums */
    {                               /* a   neuer einzufuegender Wert        */
      if (pk==NULL)                    /* neuer Wert ist einzufuegen        */
      { pk=malloc(sizeof(struct dknoten));
        pk->wert = a;
        pk->anzahl = 1;
        pk->links = pk->rechts = NULL;
      }
      else if (a==pk->wert)            /* Wert bereits vorhanden */
        pk->anzahl++;
      else if (a<pk->wert)             /* Wert ist kleiner als Knotenwert */
        pk->links = einf_baum(pk->links,a);
      else                             /* Wert ist groesser als Knotenwert */
        pk->rechts = einf_baum(pk->rechts,a);
      return pk;
    }

    void baumaus(struct dknoten *pk)   /* Rekursive Ausgabe der Baumwerte */
    {                             /* pk  Zeiger auf akt. Knoten des Baums */
      if (pk!=NULL)
      { baumaus(pk->links);
        printf("%13.6lf",pk->wert);
        if (pk->anzahl > 1)
          printf("  (%2d)",pk->anzahl);
        putchar('\n');
        baumaus(pk->rechts);
      }
    }                                     /*  Ergebnis Probelauf s. umseitig */

Bildschirmausgabe :

    C>sortd

    Bitte Zahlen eingeben !
    1.2  4.5  3.1  -4.5  6.3  1.2  -2.3  -2.4  -2.3
    10.4 1.2  -8   -4.5  0    2.0   9.5  10.4   0
    ^Z

    Sortierte Folge der eingegeben Zahlen :

         -8.000000
         -4.500000  ( 2)
         -2.400000
         -2.300000  ( 2)
          0.000000  ( 2)
          1.200000  ( 3)
          2.000000
          3.100000
          4.500000
          6.300000
          9.500000
         10.400000  ( 2)

## 9.4 Typedefinition mit "typedef"

- Es ist möglich, in C neue Typnamen mittels `typedef` einzuführen, die dann in weiteren Vereinbarungen allein (ohne Zusätze wie `struct`, `union` oder `enum`) wie die Standardtypnamen verwendet werden können
  --> Typdefinition mittels `typedef`

  Anmerkung :
    Es wird nur ein neuer Name für einen existierenden Typ ein- geführt, aber kein tatsächlich neuer Typ geschaffen

- Syntax der Typdefinition

  - Eine Typdefinition mittels typedef darf überall stehen, wo eine Vereinbarung stehen darf - also sowohl außerhalb jeder Funktion, als auch im Vereinbarungsteil einer Funktion, als auch im Vereinbarungsteil eines Blocks.

  - Die Typdefinition weist eine formale Ähnlichkeit mit der Variablenvereinbarung auf.
    Die neue Typbezeichnung steht dort, wo in der Variablenvereinbarung die Variablenbezeichnung steht.
    Das Wortsymbol typedef steht dort, wo in der Variablenvereinbarung die Speicherklassenangabe steht.
    Aus diesem Grund wird `typedef` syntaktisch auch als storage class specifier betrachtet (obwohl es eine ganz andere Bedeutung und Wirkung hat)

- Mittels typedef ist die Definition neuer Namen für beliebige Typen, auch für Pointertypen (einschließlich Pointer auf Funktionen) möglich

- Anwendung der Typdefinition mittels `typedef`

  - Parameterisierung eines Programms gegen Portabiltätsprobleme.
    Definition von Typnamen für maschinenabhängige Datentypen,
    häufig in entsprechenden Header-Dateien,
    Austausch dieser Definitionen für andere Maschinen bzw. Implementierungen.
    z.B. Definition von speziellen Typnamen in der ANSI-C-Standardbibliothek, wie z.B.
    `typedef unsigned size_t;`
    (in `<stdlib.h> , <stdio.h> , <stddef.h>` u. `<string.h>`)

  - Erhöhung der Übersichtlichkeit und Lesbarkeit eines Quellprogramms (Wahl von problemangepaßten treffenden Typnamen
    --> Selbstdokumentation)

  - Verwendung von "allgemeinen" Typnamen zur Formulierung bestimmter Funktionen.
    Konkretisierung für spezielle Anwendungen durch entsprechende Typdefinitionen ohne Änderung der Funktionen (auf der Quelltextebene)

### Beispiele zur Typdefinition mittels "typedef" in C

    /* --------------------------------------------------------------------* /

    /* statt :   */

               struct datum                   /* Typdefinition */
               { int tag;
                 char monat[4];
                 int jahr;
               };

               struct datum geb_datum;        /* Variablenvereinbarung */

    /* kann man auch formulieren :   */

               typedef struct                 /* Typdefinition */
               { int tag;
                 char monat[4];
                 int jahr;
               } datum;               /* Typname ! */

               datum geb_datum;               /* Variablenvereinbarung */

    /* -------------------------------------------------------------------- */

    /* Die Anwendung von "typedef" ist nicht auf struct-/union-Typen
       beschraenkt,
       sondern ist fuer alle Datentypen zulaessig */

    /* zum Beispiel :   */

               typedef char wort[80];         /* Typdefinition */

               typedef float real;            /* Typdefinition */

               wort name;                     /* Variablenvereinbarung */

               real a;                        /* Variablenvereinbarung */

    /* -------------------------------------------------------------------- */

    /* Auch die Definition von Pointertypen (auch Pointer auf Funktionen)
       ist mit "typedef" moeglich */

    /* zum Beispiel :    */              /* oder :                          */

       typedef struct                    /*  typedef struct                 */
               { double re;              /*          { double re;           */
                 double im;              /*            double im;           */
               } complex;                /*          } complex, *complptr;  */

       typedef complex *complptr;        /* Definition des Typs "complptr"  */
                                         /* als Pointer auf complex         */

       typedef double (*pfd)(char *,     /* Definition des Typs "pfd" als   */
                             char *);    /* Pointer auf eine Funktion, die  */
                                         /* einen double-Wert liefert und   */
                                         /* zwei char-Pointer-Parameter hat */
    /*--------------------------------------------------------------------- */

## 9.5 Variante Strukturen ("unions")

### Unions (Variante Strukturen) in C

- Variante Strukturen - in C Unions genannt - sind Strukturen, deren Komponenten alle an der gleichen Anfangsadresse im Arbeitspeicher beginnen.

  --> | Die Komponenten einer Union liegen nicht wie bei einer structure hintereinander - an verschiedenen Adressen - im Arbeisspeicher, sondern sie liegen "übereinander" im gleichen Speicherbereich, der am Unionanfang beginnt
  --> | d.h. der Inhalt des gleichen Speicherbereichs wird je nach union- Komponente unterschiedlich interpretiert

  Beispiel :

        struct
        { int i;
          float f;
          char *pc;
        } svar;
        union
        { int i;
          float f;
          char *pc;
        } uvar;

- Die einzelnen Komponenten einer Union können durchaus unterschiedlich lang sein.
  Der von der Union belegte Speicherplatz richtet sich nach der längsten Komponente.

- Die Vereinbarung von Unions (Typdefinition und Variablenvereinbarung) erfolgt analog zur Vereinbarung von Strukturen.
  Lediglich Ersatz des Schlüselwortes `struct` durch das Schlüsselwort `union`.

- Initialisierung von Unions

  - in KuR-C : nicht zulässig
  - in ANSI-C: zulässig mit konstantem Ausdruck für die 1. Komponente

- Operationen mit Unions

  die gleichen Operationen wie bei Strukturen :

  - Anwendung des Adreßoperators
  - Zugriff zu Union-Komponenten mittels des Element-Operators "." und mittels des Objektelement-Operators "->"
  - Wertzuweisung :
    - in KuR-C : komponentenweise
    - in ANSI-C: auch ganze Unions geschlossen
  - Verwendung als Funktionsparameter und Funktionswerte :
    - in KuR-C : nur Union-Pointer
    - in ANSI-C: auch Unions direkt

- Eine Union-Variable kann als eine Variable aufgefaßt werden, die Werte unterschiedlichen Typs annehmen kann. Die jeweils richtige Interpretation durch entsprechende Wahl der Komponente obliegt dem Programmierer.

### Vereinbarung von Unions in C

- Die Vereinbarung von Unions erfolgt analog zur Vereinbarung von Strukturen (Ersatz von `struct` durch `union`)

- Unionvereinbarung

  - reine Uniontypdefinition
    (ohne Angabe einer Variablenbezeichnung)

  - reine Unionvariablenvereinbarung
    (ohne Angabe eines Uniontypnamens)

  - Uniontypdefinition und Unionvariablenvereinbarung

  Komponentenangabe

- Variablenvereinbarung mit vorher definiertem Unionnamen

  `union unionname` dient als Datentypangabe.

### Programmbeispiel zu Unions in C

    /*---------------------------------------------------------------------*/
    /* Programm FLOATBYTE */
    /*---------------------------------------------------------------------*/
    /* Ausgabe der internen Darstellung von float-Werten als sedezimale    */
    /* Bytefolge (MS-Byte zuerst)                                          */
    /*---------------------------------------------------------------------*/

    #include <stdio.h>

    union floathex
    { float f;
      char b[sizeof(float)];      /* unabhaengig von interner Darstellung */
    };

    void main(void)

    { union floathex flvar;
      float r;
      int i;

      printf("\nInterne Darstellung von FLOAT-Werten als Bytefolge");
      printf(" (MS-Byte zuerst).\n");
      printf("Bitte geben Sie Zahlenwerte ein :\n\n");
      while (scanf("%f",&r)!=EOF)
      { flvar.f=r;
        for (i=sizeof(float)-1; i>=0; i--)
          printf("%02x  ",flvar.b[i]&0xff);
        printf("\n\n");
      }
    }

    /*---------------------------------------------------------------------*/

Bildschirmausgabe :

    E:\RT\C\VORL>floatbyte

    Interne Darstellung von FLOAT-Werten als Bytefolge (MS-Byte zuerst).
    Bitte geben Sie Zahlenwerte ein :

    0
    00  00  00  00

    1.0
    3f  80  00  00

    -1.0
    bf  80  00  00

    2.6
    40  26  66  66

    2.6e-6
    36  2e  7b  a9

    ^Z

## 9.6 Bitfelder

- Die Manipulation (Setzen, Rücksetzen, Invertieren, Abfragen) einzelner Bits bzw. Bitgruppen innerhalb eines Maschinenworts ist in C mittels der Bitmanipulations-Operatoren möglich

- Daneben besteht aber noch eine weitere Möglichkeit zur Lösung derartiger Aufgaben : die Verwendung von Bitfeldern.

  In C lassen sich innerhalb von - implementierungsabhängigen - Speicherobjekten (als "Wort" bezeichnet, meist int) einzelne Bits bzw. Bitgruppen als kleinste über einen Namen ansprechbare Objekte definieren.
  Diese Objekte werden Bitfelder genannt.

- - In einem "Wort" können mehrere Bitfelder untergebracht werden.
  - Ob ein Bitfeld sich über eine "Wort"-Grenze erstrecken darf, ist implementierungsabhängig.
    Gegebenenfalls erfolgt vom Compiler eine automatische Angleichung an "Wort"-Grenzen
  - Ein Bitfeld darf nicht länger als die für die Speicherung verwendete "Wort"-Länge sein

- Definition von Bitfeldern

  - als Struktur-Komponenten in einer Strukturvereinbarung

  - Beispiel :
    Aufteilung eines Wortes (16 Bit) im Bildschirmspeicher des PC-D-1

        struct zeichen
        { unsigned wert       : 9;
          unsigned dummy      : 2;
          unsigned helligkeit : 2;
          unsigned unterstr   : 1;
          unsigned blink      : 1;
          unsigned sichtbar   : 1;
        };

        struct zeichen bildschirm[25][80];

- Zugriff zu Bitfeldern

  - Analog zum Zugriff zu sonstigen Struktur-Komponenten über Namen unter Verwendung des Element-Operators "." bzw. des Objektelement-Operators "->".
  - Wertzuweisung und Verwendung in Ausdrücken wie int-Variable
    --> sehr einfache Manipulation einzelner Bits bzw. Bitgruppen

    Beispiel :

        bildschirm[0][0].wert  = 'A';
        bildschirm[0][0].blink = 1;

- Es ist zulässig namenlose Bitfelder zu definieren, d.h. nur die Länge eines Bitfeldes anzugeben.
  Zu derartigen Bitfeldern kann nicht zugegriffen werden.
  Sinnvoll i.a. nur, wenn bestimmte Bits nicht benötigt oder benutzt werden und das folgende Bitfeld an einer ganz bestimmten Grenze beginnen soll.

  Speziell : | Namenloses Bitfeld der Länge 0 --> das folgende Bitfeld beginnt an einer neuen "Wort"-Grenze

- Bitfelder können in einer Struktur mit Komponenten "normaler" Datentypen gemischt werden.
  Jedes Bitfeld nach einer "normalen" Datentyp-Komponente beginnt an einer neuen "Wort"-Grenze.

- Einschränkungen in der Anwendung von Bitfeldern

  - Von Bitfeldern lassen sich keine Adressen ermitteln
    --> keine Anwendung des &-Operators
  - Es lassen sich keine Arrays von Bitfeldern definieren
  - Bitfelder können keine Komponenten von Unions sein
    (wohl aber Komponenten einer Struktur, die selbst wiederum Komponente einer Union ist)
  - Bitfelder können keine Funktionswerte sein

- Portabilitätsproblem :

  Die einzelnen Bitfelder werden zwar innerhalb eines "Worts" in der Reihenfolge ihrer Auflistung angeordnet.
  Es ist aber implementierungsabhängig, ob diese Anordnung beim niederwertigsten Bit des "Worts" beginnt, im Wort also aufsteigend ist, oder beim höchstwertigsten Bit des "Worts" beginnt, im Wort also absteigend ist. (TURBO-C : bei niederwertigsten Bit beginnend)

- Anwendung von Bitfeldern

  - Platzsparende Speicherung von Informationen, die weniger als ein Byte zur Darstellung benötigen

    z.B. :

        struct datum
        { unsigned tag   : 5;
          unsigned monat : 4;
          unsigned jahr  : 7;    /* Darstellung nur der letzten */
        };                       /* beiden Ziffern              */

  - Vereinfachter Zugriff zu Registern (z.B. Statusregister von E/A-Bausteinen) und vorgegebenen Organisationsstrukturen (z.B. Status- und Attribut-Worten in Betriebssystemen)

    z.B. :

        struct line_ctrl_reg       /* Line Control Register der */
        { unsigned w_laenge : 2;   /* ACE NS16450               */
          unsigned stop_bits: 1;
          unsigned parity   : 3;
          unsigned set_break: 1;
          unsigned dlab     : 1;
        };

## 9.7 Vorwärtsdeklaration von Struktur-Typen

- In ANSI-C (nicht jedoch in KuR-C) ist es möglich, einen `struct-` oder `union-`Typ vorwärts zu deklarieren.

  Diese Typ-Vorwärtsdeklaration besteht lediglich aus dem Wortsymbol `struct` bzw. `union` und dem Struktur- bzw. Union-Name.
  Die Auflistung der Komponenten fehlt.

  Syntax :

- Anwendung :

  Die Typ-Vorwärtsdeklaration ermöglicht die Definition von structure- bzw. union-Typen, die gegenseitig aufeinander bezugnehmen wobei wenigstens einer der Typnamen bereits für einen anderen structure- bzw. union-Typ in einem übergeordneten Block vereinbart ist.

  Beispiel :

      struct st2
      { int ba;
        int lu;
      };

      void func(void)
      {
        struct st2;           /* Typ-Vorwärtsdeklaration : setzt globale  */
                              /* Vereinbarung von struct st2  außer Kraft */

        struct st1
        { struct st2 *s2p;    /* Pointer auf lokal definierte struct st2  */
          int a;
        } v1;

        struct st2
        { struct st1 *s1p;
          int b;
        }v2;

        /* ... */

        (v1.s2p)->b=0;        /* ohne Typ-Vorwärtsdeklaration falsch */

        /* ... */

      }

Zum Inhaltsverzeichnis | Zum nächsten Abschnitt

###### Copyright © FH München, FB 04, Prof. Dr. Rainer Thomas
V - PC - 911 - 00 - TH - 02
V - PC - 912 - 00 - TH - 02
V - PC - 913 - 01 - TH - 02
V - PC - 913 - 02 - TH - 02
V - PC - 914 - 00 - TH - 04
V - PC - 921 - 00 - TH - 02
V - PC - 922 - 00 - TH - 06
V - PC - 923 - 00 - TH - 06
V - PC - 924 - 00 - TH - 05
V - PC - 925 - 00 - TH - 03
V - PC - 931 - 00 - TH - 02
V - PC - 932 - 00 - TH - 02
V - PC - 934 - 00 - TH - 03
V - PC - 935 - 01 - TH - 03
V - PC - 935 - 02 - TH - 03
V - PC - 941 - 00 - TH - 05
V - PC - 943 - 00 - TH - 04
V - PC - 951 - 00 - TH - 04
V - PC - 952 - 00 - TH - 03
V - PC - 953 - 00 - TH - 03
V - PC - 961 - 00 - TH - 02
V - PC - 962 - 00 - TH - 02
V - PC - 971 - 00 - TH - 02