Programmieren in C

| ### Programmieren in C von Prof. Jürgen Plate

# 4 Datentypen unter C (Teil II)

## 4.8 Felder (Arrays)

### Eindimensionale Felder

Beispiel: Deklaration eines Integer-Feldes mit 5 Elementen

`int n[5];`

Diese 5 Variablen liegen dann im Speicher hintereinander:

`n[0]` | `n[1]` | `n[2]` | `n[3]` | `n[4]`

Wichtig: Die Angabe der Feldgröße muß in diesem Fall durch eine Konstante erfolgen, deshalb die Bezeichnung "statisch"! Die so deklarierten Felder beginnen immer mit Index `[0]` und enden mit Index `[`Feldgröße-1`]`.

Es erfolgt keine Überprüfung auf gültigen Speicherbereich von Seiten des Compilers!

Ansonsten gilt für Feldelemente das gleiche, wie für sonstige Variable dieses Typs.

- Bei der Deklaration kann eine elegante Initialisierung erfolgen;
  Beispiel: `int n[] = {8,7,9,-13};`
  Die (Mindest-)Feldgröße wird dabei automatisch festgelegt.
- Strings stellen hierbei einen Sonderfall dar:
  `"..."` sind bereits eindimensionale Felder, die mit `'\0'` terminiert sind. Beispiel:
  `char ch[] = "String";` ist völlig äquivalent mit
  `char ch[] = {'S','t','r','i','n','g','\0'};`
  mögliche Verwendung bei `printf()` bzw. `scanf()`:

      char  msg[] = "Hello world!", format[] = "%s\n";
      printf(format, msg);

  oder:

      char  msg[100];    /* sicherheitshalber ueberdimensioniert */
      scanf("%s", msg);  /* hier vor msg KEIN '&', weil
                            'msg' eine Adresse ist */

### Mehrdimensionale Felder

Bei mehrdimensionalen Arrays werden die Variablen durch ein Tupel von Indizes unterschieden. Beispiel (Deklaration):
`float x[8][30];`
Hier gilt dann analog: erstes Element ist `x[0][0]`, bzw. letztes `x[7][29]`. Dementsprechend werden zweidimensionale Array zeilenweise gespeichert:

Analog lassen sich auch Felder mit mehr als zwei Dimensionen definieren. Angesprochen werden die Komponenten über den Index:
`x[8][30] =12.5;`

Anstelle der Zahlen kann für den Index auch ein beliebiger Integer-Ausdruck angegeben werden, z. B.:
`x[i-1][j*k+2] =32.5;`

Der Compiler kann aber selbstständig nur die Zahl der Zeilen feststellen; die notwendige (Mindest-)Zahl der Spalten muß ihm mitgeteilt werden.

Bemerkung: intern werden mehrdimensionale Felder (wenn statisch allociert!) eindimensional angelegt; wenn also z. B. ein Array deklariert ist mit `int a[4][3];`, dann sind die Aufrufe `a[i][j]`, `a[0][i*3+j]` und `a[k][(i-k)*3+j]` völlig äquivalent:

`a[0][0]` | `a[0][1]` | `a[0][2]` | `a[1][0]` | `a[1][1]` | `a[1][2]` | `a[2][0]` | ...

`a[0][0]` | `a[0][1]` | `a[0][2]` | `a[0][3]` | `a[0][4]` | `a[0][5]` | `a[0][6]` | ...

Auch hier gibt es bei der Deklaration wieder die bequeme Möglichkeit der Initialisierung:

    static int md[5][10] =
      {
       {0,1,2,3,4,5,6,7,8,9},
       {1,2,3,4,5,6,7,8,9,10},
       {2,3,4,5,6,7,8,9,10,11},
       {3,4,5,6,7,8,9,10,11,12},
       {4,5,6,7,8,9,10,11,12,13}
      };

Die Angaben für die zweite (und weitere) Dimension werden also jeweils in eigene geschweifte Klammern geschrieben. Innerhalb der geschweiften Klammern gilt, daß eventuell nicht initialisierte Elemente auf null gesetzt werden. Die Kennzeichnung der Array-Struktur bei den Initialisierungswerten ist notwendig, wenn Elemente "innerhalb" des Arrays durch fehlende Angabe mit 0 initialisiert werden sollen. Beispiele:

    int mat[3][4] = {  {  1,  4,  3, -4 },
                       {  2,  0, -3,  1 } };

Die letzte Zeile (`mat[2][0] ... mat[2][3]`) wird mit 0 initialisiert.

    int mat[3][4] = { {  1,  4,  3 },
                      {  2,  0, -3 },
                      {  0, -5,  6 } };

Die letzte Spalte (`mat[0][3], mat[1][3], mat[2][3]`) wird mit 0 initialisiert. Die Kennzeichnung der Arraystruktur bei den Initialisierungswerten ist notwendig.

Man kann die inneren geschweiften Klammern auch weglassen, dann wird der ganze Vektor Element für Element initialisiert.

    int mat[3][4] = { 1,  4,  3, -4,  2,  0, -3,  1,  0, -5 };

oder:

    int mat[ ][4] = { 1,  4,  3, -4,  2,  0, -3,  1,  0, -5 };

Die beiden letzten Elemente der letzten Zeile (`mat[2][2], mat[2][3]`) werden mit 0 initialisiert. Ein weiteres Beispiel:

    /* Tag im Jahr aus Monat und Tag bestimmen */
    day_of_year(int year, int month, int day)
      {
      static int day_tab[2][13] = {
          {0,31,28,31,30,31,30,31,31,30,31,30,31},
          {0,31,29,31,30,31,30,31,31,30,31,30,31}
        };
      int i, leap;
      leap = year%4 == 0 && year%100 != 0 || year%400 == 0;
      for (i=1; i < month; i++)
        day += day_tab[leap][i];
      return (day);
      }

Die folgende Funktion liefert den Namen eines Monats, falls der Parameter im Bereich 1..12 liegt und andernfalls einen Fehlertext

    char *month_name(int n)
      {
      static char *name[] = {     /* Vektor von Zeigern */
        "*** gibt's nicht ***",   /* String ist ein char- */
        "Januar",                 /* Vektor und daher durch */
        "Februar",                /* seine Anfangsadresse */
        "Maerz",                  /* charakterisiert */
        "April",
        "Mai",
        "Juni",
        "Juli",
        "August",
        "September",
        "Oktober",
        "November",
        "Dezember"
        };
      return ((n < 1 || n > 12) ? name[0] : name[n]);
      }

Beispiele für die Verwendung von zweidimensionalen Feldern, die als Felder von Feldern aufgefaßt werden können.

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

    void main(void)
      {
      /* Deklaration eines Schachbretts von Zeichen */
      char brett[8][8];

      /* Belegen des Schachbretts */
      for(i=0;i<8;i++)
        {
        for(j=0;j<8;j++)
          {
          brett[i][j]=' ';
          }
        }
      for(i=0;i<8;i++) /* Bauern */
        {
        brett[1][i]='b';
        brett[6][i]='B';
        }
      /* weitere Figuren */
      brett[0][0]=brett[0][7]='t';
      brett[0][1]=brett[0][6]='s';
      brett[0][2]=brett[0][5]='l';
      brett[0][3]='d';
      brett[0][4]='k';
      brett[7][0]=brett[7][7]='T';
      brett[7][1]=brett[7][6]='S';
      brett[7][2]=brett[7][5]='L';
      brett[7][3]='D';
      brett[7][4]='K';

      /* Ausgabe */
      printf("  A B C D E F G H\n");
      for(i=7;i>=0;i=i-1)
        {
        printf(" +-+-+-+-+-+-+-+-+\n%d|",i+1);
        for(j=0;j<=7;j=j+1)
          {
          printf("%c|",brett[i][j]);
          }
        printf("%d\n",i+1);
        }
      printf(" +-+-+-+-+-+-+-+-+\n  A B C D E F G H\n");
      }

Demonstrationsprogramm zu 2-dimensionalen Arrays: Matrixaddition

    #include <stdio.h>
    #define ZEILEN  3
    #define SPALTEN 4

    void mat_add (float mat1[ZEILEN][SPALTEN],
                  float mat2[ZEILEN][SPALTEN],
                  float erg[ZEILEN][SPALTEN])
      {
      int i, j;
      for (i=0; i<ZEILEN; i++)
        for (j=0; j<SPALTEN; j++)
          erg[i][j]=mat1[i][j] + mat2[i][j];
      }

    void mat_aus(float fmat[ZEILEN][SPALTEN])
      {
      int i,j;
      for(i=0; i<ZEILEN; i++)
        {
        printf("\n{");
        for (j=0; j<SPALTEN; j++)
          printf("%6.2f", fmat[i][j]);
        printf("}");
        }
      printf("\n");
      }

    int main(void)
      {
      float fmat1[ZEILEN][SPALTEN] = {
        {  1.5,  4.1,  3.4, -4.0 },
        {  2.2,    0, -3.7,  1.1 },
        {    0, -5.1,  6.6,  0.2 }
        };
      float fmat2[ZEILEN][SPALTEN] =
        {-1.5, 2.3, 3.7, 2.1, 0.7,5.5, 3.3,0,0,0,0,0};

      float fmat3[ZEILEN][SPALTEN];

      mat_add(fmat1, fmat2, fmat3);
      mat_aus(fmat3);

      return 0;
      }

Ein `matadd`-Probelauf ergibt:

    {  0.00,   6.40,   7.10,  -1.90, }
    {  2.90,   5.50,  -0.40,   1.10, }
    {  0.00,  -5.10,   6.60,   0.20, }

## 4.9 Zeichen- und Stringverarbeitung

Zeichenketten sind eindimenionale Vektoren mit mehreren Besonderheiten. Sie können auch in der Speicherklasse `auto` initialisiert werden. Als Zeichenkette müssen sie mit `'\0'` abgeschlossen sein. Dies läßt sich aber auch automatisch mit Hilfe der vereinfachten Initialisierung erreichen:

    char s[] = {'s','t','r','i','n','g','\0'};
    char s[] = "string";       /* \0 intern angehaengt */
    char s[10] = "string";     /* restl. Elemente mit 0 init. */

Im Übrigen werden Zeichenketten wie normale Vektoren behandelt. Insbesondere ist der Zugriff auf Vektorelemente gleich:

    s[0] hat den Wert 's'
    s[3] hat den Wert 'i'

Wichtig:Die Zeichenkettenkonstante "x" unterscheidet sich signifikant von der Zeichenkonstante 'x'. Letztere ist ein einzelnes Zeichen, das man z. B. durch `char ch = 'x';` definieren könnte. Bei "x" handelt es sich um ein Zeichenkettenarray, das aus zwei Zeichen besteht ('x' und '\0').

char-Arrays können auch mit einer String-Konstanten initialisiert werden. Statt

`char s[] = {'s','t','r','i','n','g','\0'};`

kann man auch schreiben

`char s[] = "string";`

In C gibt es keine speziellen Sprachelemente für die Manipulation von Zeichenketten. Es existieren jedoch in der C-Standardbibliothek eine Reihe von Funktionen zur Stringbearbeitung.

- Funktionen zum Kopieren von Strings und Zeichenarrays
- Funktionen zum Aneinanderhängen von Strings
- Funktionen zum Vergleich von Strings und Zeichenarrays
- Funktionen zum Suchen nach Zeichen und Zeichenfolgen in Strings und Zeichenarrays
- Funktion zur Ermittlung der Länge eines Strings
- Funktion zum Füllen eines Arrays mit einem Zeichen
- Funktion zur Ermittlung der zu einer Fehlernummer gehörenden Fehlermeldung

Die entsprechenden Funktions-Deklarationen befinden sich in der Standard-Header-Datei <string.h>. Diese ist daher bei Verwendung der Funktionen mittels `#include <string.h>` einzubinden. In `<string.h>` ist auch der von einigen Funktionen verwendete Datentyp `size_t` definiert. Dies ist der "natürliche" unsigned-Typ, d. h. der Typ, den der sizeof-Operator liefert.

Da in C Array-Grenzen-Überschreitungen nicht überprüft werden, liegt es am Programmierer einen Array-Überlauf zu verhindern. Im ANSI-Standard ist lediglich festgelegt, daß das Überschreiten von Array-Grenzen zu einem undefinierten Verhalten führt. Alle Kopierfunktionen (siehe unten) setzen voraus, daß sich der Quell- und der Zielbereich nicht überlappen. Im Falle der Überlappung ist das Verhalten undefiniert.

### Bibliothek zum Test von Zeichen: <ctype.h>

Das Argument der folgenden Funktionen ist jeweils ein `int` dessen Wert entweder `EOF` oder als `unsigned char` darstellbar sein muß. Der Rückgabewert ist ein `int`, wobei dieser gleich Null ist, wenn das Argument `c` die Bedingung nicht erfüllt, ansonsten ist er ungleich Null.

`islower(c)` | Kleinbuchstabe
`isupper(c)` | Großbuchstabe
`isalpha(c)` | Klein- oder Großbuchstabe
`isdigit(c)` | Dezimalzahl
`isalnum(c)` | Klein- oder Großbuchstabe oder Dezimalzahl
`iscntrl(c)` | Control-Zeichen
`isgraph(c)` | druckbares Zeichen außer space
`isprint(c)` | druckbares Zeichen mit space
`ispunct(c)` | druckbares Zeichen außer space, Buchstabe und Ziffern
`isspace(c)` | space, formfeed, newline, carriage return, tab, vertical tab
`isxdigit(c)` | Hexadezimalzahl

Achtung: Die deutschen Umlaute und das "ß" sind keine Buchstaben im obigen Sinne!!

In diesem Headerfile sind außerdem noch zwei Funktionen zur Konvertierung von Buchstaben enthalten:

`int tolower(int c)` | konvertiert `c` zu Kleinbuchstaben
`int toupper(int c)` | konvertiert `c` zu Großbuchstaben

Das folgende Programm gibt für eine eingegebene ganze Zahl zwischen 0 und 999 das entsprechende Zahlwort aus (z. B. für den automatischen Druck von Schecks). Es demonstriert die Initialisierung und Anwendung von Character-Arrays (Zeichenketten). Die Denkarbeit bei der Konzeption besteht darin, daß man bei der Einerstelle nicht bei "9" aufhören darf, sondern man die Werte von 0 bis 19 verwenden muß.

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

    int main(void)
     {

      /******************  Zahlwörtertabellen  ***************************/

      char low [20][10] = {  "null", "eins", "zwei", "drei", "vier", "fünf",
                          "sechs", "sieben", "acht", "neun", "zehn",
                          "elf", "zwölf", "dreizehn", "vierzehn",
                          "fünfzehn", "sechzehn", "siebzehn",
                          "achtzehn", "neunzehn" };

      char ten [10][10] = {  "", "", "zwanzig", "dreißig", "vierzig",
                          "fünfzig", "sechzig", "siebzig",
                          "achtzig", "neunzig" };

      char hun [10][15] = {  "", "einhundert", "zweihundert", "dreihundert",
                          "vierhundert", "fünfhundert", "sechshundert",
                          "siebenhundert", "achthundert", "neunhundert" };

      /*****************************************************************/

      int num;

      printf("\nZahl: ");
      scanf("%d",&num);

      if (num < 20)                                        /*  0 <= num <  20 */
       printf("%s\n", low[num]);
      else if (num < 100)                                  /* 20 <= num < 100 */
        {
        if ((num % 10) == 0)                                   /* Keine Einer */
          printf("%s\n", ten[num/10]);
        else if ((num % 10) == 1)                            /* Einerziffer 1 */
          printf("einund%s\n", ten[num/10]);
        else                                                  /* andere Einer */
          printf("%sund%s\n", low[num%10], ten[num/10]);
        }
       else                                              /* 100 <= num < 1000 */
        {
        if ((num%100) == 0)                          /* keine 1er, keine 10er */
          printf("%s\n", hun[num/100]);
        else if ((num%100) < 20)                               /* H01 bis H19 */
          printf("%s%s\n", hun[num/100], low[num%100]);
        else if ((num%10) == 0)                            /* letzte Ziffer 0 */
          printf("%s%s\n", hun[num/100], ten[(num%100)/10]);
        else if ((num%10) == 1)                            /* letzte Ziffer 1 */
          printf("%seinund%s\n", hun[num/100], ten[(num%100)/10]);
        else                                                 /* sonst. Zahlen */
          printf("%s%sund%s\n", hun[num/100], low[num%10], ten[(num%100)/10]);
        }
      return(0);
      }

Für die Bearbeitung von Zeichenketten gibt es eine umfangreiche Funktionsbibliothek:

### Bibliothek mit String-Funktionen: <string.h>

Wie schon der Name sagt, werden in diesem Header Funktionen zur Manipulation von Strings deklariert. Dabei werden folgende Typkonventionen benutzt:

    char *s, *t;
    const char *cs, *ct;
    int c;
    size_t n;

`char *strcpy(s,ct)` | kopiert `ct` zu `s` inklusive dem abschließenden `'\0'`. Zurückgegeben wird `s`.
`char *strncpy(s,ct,n)` | kopiert höchstens `n` Zeichen des Strings `ct` zu `s` und liefert `s` zurück, wobei wie üblich mit `'\0'` terminiert wird.
`char *strcat(s,ct)` | hängt `ct` an `s` an und liefert `s` zurück.
`char *strncat(s,ct,n)` | hängt höchstens `n` Zeichen von `ct` an `s` an, terminiert mit `'\0'` und liefert `s` zurück.
`int strcmp(cs,ct)` | vergleicht `cs` und `ct`. Der Rückgabewert ist 0, wenn die beiden Strings identisch sind, negativ, wenn `cs<ct` ist und positiv, wenn `cs>ct` ist, wobei das `<` und `>` im lexikalischen Sinne verstanden wird, d.h. es wird Buchstabe für Buchstabe verglichen und geprüft, welcher als erster im Alphabet steht, wobei wiederum Großbuchstaben vor Kleinbuchstaben kommen (ASCII-Code).
`int strncmp(cs,ct,n)` | Im wesentlichen dasselbe wie zuvor, nur daß diesmal höchstens `n` Zeichen verglichen werden.
`char *strchr(cs,c)` | liefert Pointer zum ersten Auftreten von `c` in `cs` zurück, oder `NULL` wenn `c` nicht auftritt.
`char *strrchr(cs,c)` | liefert Pointer zum letzten Auftreten von `c` in `cs` zurück, oder `NULL` wenn `c` nicht auftritt.
`char *strstr(cs,ct)` | liefert Pointer zum ersten Auftreten des Strings `ct` in `cs` zurück oder `NULL` wenn `ct` nicht auftritt.
`size_t strlen(cs)` | liefert die Länge des Strings `cs` zurück.

Beispiel für Strungbearbeitung: Stringposition ermitteln
Alle Operationen werden ohne Bibliotheksfunktionen und ohne Pointer ausgeführt.

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

    main()
      {
      int i, c;
      char object[120], target[120],       /* Suchstring, Quellstring */
           hilf[120];                      /* Hilfs-String fuer Tausch */
      int opos, tpos, ergpos,              /* Positionen */
          olen, tlen, hlen;                /* Laengen */

      do
        {
        printf("Erster String : ");
        i = 0;                             /* String zeichenweise lesen */
        while ((c = getchar()) != EOF && c != '\n')
          object[i++] = c;
        object[i] = '\0'; olen = i;        /* Laenge gleich speichern */
        if (c != EOF)                     /* Ctrl-D eingegeben ? */
          {
          printf("Zweiter String: ");
          i = 0;
          while ((c = getchar()) != EOF && c != '\n')
            target[i++] = c;
          target[i] = '\0'; tlen = i;

          /* Suche immer den kuerzeren im laengeren String */
          if (strlen(object) > strlen(target))
            /* dann beide Strings vertauschen */
            {
            i = 0;                         /* object --> hilf */
            while ((hilf[i]=object[i]) != '\0') i++;
            i = 0;                         /* target --> object */
            while ((object[i]=target[i]) != '\0') i++;
            i = 0;                         /* hilf --> target */
            while ((target[i]=hilf[i]) != '\0') i++;
            hlen = olen; olen = tlen;      /* und die Laengen auch */
            tlen = hlen;
            }
          /* Beginn der Suche */
          tpos = 0;
          while (tlen != 0 && tpos < (tlen-olen+1))
            {                      /* Solange objet in target Platz hat */
            opos = 0;
            while ((target[tpos+opos] == object[opos]) && (opos < olen))
              opos++;                      /* vergleichen ab tpos */
            if (opos >= olen)              /* object in target - fertig */
              {
              ergpos = tpos;               /* Position merken */
              tpos = tlen;                 /* sorgt fuer Abbruch */
              }
            else
              ergpos = -1;                 /* nicht gefunden */
            tpos++;                        /* sonst naechste Pos. */
            }/*endwhile (tlen != 0...) */

          /* Ausgabe */
          if (ergpos < 0)
            printf("Kein String ist Teilstring des anderen!");
          else
            printf("'%s' in '%s' ab Pos. %d enthalten",
                    object,target,ergpos);
          }/* endif (c != EOF) */
        printf("\n");
        }
      while (c != EOF);                    /* bis Ctrl-D eingegeben */
      }

Dieses Programm ist sehr ""ausführlich" programmiert. Das folgende Programm ist nicht nur etwas kürzer, sondern es werden vor allem zwei sinnvolle Funktionen definiert. Das Programm sucht alle Zeilen aus der Standardeingabe, die ein bestimmtes Suchmuster enthalten.

    # include <stdio.h>

    # define MAXLINE 1000   /* Maximale Zeilenlaenge */

    int main(void)
      {
      char line[MAXLINE];

      while (getline(line,MAXLINE) > 0)
        if (index(line,"the") > 0)
          printf("%s",line);
      return 0;
      }

    int getline(char s[], int lim)
      /* Eingabezeile mit max. lom Zeichen in s ablegen, Laenge liefern */
      {
      int c,i;
      i = 0;
      while (--lim>0 && (c=getchar()) != EOF && c!='\n')
        s[i++] = c;
      if (c == '\n') s[i++] = c;
      s[i] = '\0';
      return(i);
      }

    index (char s[],char t[])
      /* Position von t in s liefern, -1 falls nicht da */
      {
      int i, j, k;
      for (i = 0; s[i] != '\0'; i++)
        {
        for (j = i, k = 0; t[k] != '\0' && s[j] == t[k]; j++,k++)
          ;
        if (t[k] == '\0') return(i);
        }
      return(-1);
      }

Zum Abschluß noch ein völlig unnützes Programm: Es gibt auf die Eingabe eines Geburtstages das entsprechende Tierkreiszeichen aus.

    #include <stdio.h>

    int main(void)
     {
     /*  Sternzeichentabelle:  */
      char zodiac [12] [11] = { "STEINBOCK" , "WASSERMANN", "FISCHE",
                                "WIDDER", "STIER", "ZWILLINGE",
                                "KREBS", "LÖWE", "JUNGFRAU",
                                "WAAGE", "SKORPION", "SCHÜTZE"};

      /*  Tabelle der Tage, die bis zu einem bestimmten Monat vergangen sind:  */
      int monsum [13] = { 0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };

      /*  Nummer des Anfangstages (im Intervall 1 - 365) eines Sternzeichens:  */
      int start [12] = { 1, 31, 62, 90, 121, 151, 183, 214, 246, 277, 307, 337 };

      /*  Nummer des Endtages (im Intervall 1 - 365) eines Sternzeichens
          (hier nicht noetig): */
      int end [12] = { 30, 61, 89, 120, 150, 182, 213, 245, 276, 306, 336, 365 };

      int i;
      int day;              /*  Geburtstag */
      int mon;              /*  Geburtsmonat*/
      int tnum;             /*  Geburtstag und Geburtsmonat umgerechnet in den
                                entsprechenden Tag des Tierkreisjahres (1-365) */

      printf("\n\nTag und Monat des Geburtsdatums eingeben (tt mm): ");
      scanf("%d %d", &day, &mon);

      /*  Datum umrechnen in Anzahl der vergangenen Tage seit dem 22.12.,
          dem ersten Zeichen im Turnus (STEINBOCK) */
      if (monsum[mon] + day + 10 > 365)
        tnum = monsum[mon] + day + 10 - 365;
      else
        tnum = monsum[mon] + day + 10;

      i = 0;
      while (tnum > start[i]) i++;
      printf("%d %d Ihr Sternzeichen ist %s\n", tnum,i-1,zodiac[i-1]);
      return(0);
     }

## 4.10 Record und varianter Record

### Records

Während Vektoren eine Zusammenfassung von Objekten gleichen Typs sind, handelt es sich bei einer Struktur um eine Zusammenfassung von Objekten möglicherweise verschiedenen Typs zu einer Einheit. Die Verwendung von Strukturen bietet Vorteile bei der Organisation komplexer Daten. Beispiele sind Personaldaten (Name, Adresse, Gehalt, Steuerklasse, usw.) oder Studentendaten (Name, Adresse, Studienfach, Note).

Strukturen werden mit Hilfe des Schlüsselwortes `struct` vereinbart

    struct Strukturname  { Komponente(n) } Strukturvariable(n) Init. ;

Strukturname ist optional und kann nach seiner Definition für die Form (den Datentyp) dieser speziellen Struktur verwendet werden, d. h. als Abkürzung für die Angaben in den geschweiften Klammern. Strukturkomponenten werden wie normale Variable vereinbart. Struktur- und Komponentennamen können mit anderen Variablennamen identisch sein ohne daß Konflikte auftreten, da sie vom Compiler in einer separaten Tabelle geführt werden.

Der Aufruf der einzelnen Elemente erfolgt dann nicht über Indizes, sondern über deren Namen. Beispiel (für Definition/Deklaration):

    struct datum
    {   int tag;
        int monat;
        int jahr;
        char mon_name[4];
      };
| Legt nur die Form der Struktur `datum` fest
    struct datum {
        int tag;
        int monat;
        int jahr;
        char mon_name[4];
    } geb_dat, heute;
| Erzeugt zusätzlich die Strukturvariablen `geb_dat` und `heute`
    struct point
    {   double  spx, spy;
        int     farbe;
        char    label;
    }    spot1;
| `point` ist der Strukturname, `spx, spy`, etc. sind Elementnamen und `spot1` ist die deklarierte Variable
    struct point  punkt1, punkt2;
| ebenfalls so deklarierte Variablen

Durch die Angabe einer (oder mehrerer) Strukturvariablen wird diese Struktur erzeugt (d. h. Speicherplatz dafür bereitgestellt). Strukturvereinbarungen ohne Angabe einer Strukturvariablen legen nur die Form (den Prototyp) der Struktur fest.

Die geschlossene Initialisierung erfolgt (analog zu den Arrays) bei der Deklaration. Zum Beispiel:

    struct datum heute = {26,9,1987,"jun"};
    struct point  spot2 = {2.8, -33.7, 15, 'A'};

Für den Elementzugriff gibt es zwei eigene Operatoren. Der direkte Zugriff wird dabei mit dem Punktoperator . nach folgendem Schema durchgeführt (Der Operator `->` wird bei den Pointern besprochen):

    Strukturvariable . Komponente

Beispiel:

    punkt1.farbe = 11;
    punkt2.spy = spot2.spy;
    heute.tag = 22;
    heute.monat = 1;
    heute.jahr = 2000;

Strukturvariable können an Funktionen übergeben werden, und Funktionen können Strukturen als Rückgabetyp haben. Beispiel (mit obiger Definition):

    /* createpoint  :  bepackt Struktur 'point' */
    struct point createpoint(double x, double y, int farbe, char label)
      {
      struct point dummy;
      dummy.spx = x;
      dummy.spy = y;
      dummy.farbe = farbe;   /* gleiche Bezeichnungen */
      dummy.label = label;   /* interferieren NICHT   */
      return dummy;
      }

Strukturen können als Elemente ebenfalls wieder Strukturen enthalten (allerdings nicht sich selbst) und Strukturen können zu Vektoren zusammengefaßt werden:

    struct kunde
      {
      char name[NAMLAE];
      char adresse[ADRLAE];
      int kund_nr;
      struct datum liefer_dat;
      struct datum rech_dat;
      struct datum bez_dat;
      };
    struct kunde kunde1, kunde2, ... ;
    struct kunde kunden[KUNANZ];

Programmbeispiel: Komplexe Arithmetik (`typedef` siehe unten)

    #include <stdio.h>

    struct complex
      {
      double r;
      double i;
      };

    typedef struct complex cpx;

    cpx makecpx(double, double);
    cpx sum(cpx, cpx);
    cpx product(cpx, cpx);
    cpx power(cpx, int);
    void compprint(cpx);

    int main(void)
      /* Berechnet Potenzen komplexer Zahlen */
      {
      int k;
      cpx basis, result;
      basis = makecpx(1,1);
      for (k=0; k < 10; ++k)
        {
        result = power(basis, k);
        printf("%2d   ", k);
        compprint(result);
        }
      return(0);
      }

    cpx makecpx(double r, double i)
      /* Komplexe Zahl erzeugen */
      {
      cpx tmp;
      tmp.r = r; tmp.i = i;
      return (tmp);
      }

    cpx sum(cpx a, cpx b)
      /* Summe zweier komplexer Zahlen */
      {
      a.r += b.r;
      a.i += b.i;
      return (a);
      }

    cpx product(cpx x, cpx y)
      /* Produkt zweier komplexer Zahlen */
      {
      cpx u;
      u.r = x.r * y.r - x.i * y.i;
      u.i = x.r * y.i + x.i * y.r;
      return (u);
      }

    cpx power( cpx basis, int expo)
      /* Potenz einer komplexen Zahl */
      {
      cpx u = {1, 0};
      while (expo > 0)
        {
        if (expo % 2)
          {
          expo--;
          u = product(basis, u);
          }
        else
          {
          expo = expo/2;
          basis = product(basis, basis);
          }
        }
      return(u);
      }

    void compprint(cpx z)
      /* Druckt eine komplexe Zahl */
      {
      if ((z.r != 0) && (z.i != 0))
        printf("%5.2f + %5.2f * i\n", z.r, z.i);
      else if ((z.r == 0) && (z.i != 0))
        printf("%13.2f * i\n", z.i);
      else if ((z.r != 0) && (z.i == 0))
        printf("%5.2f\n", z.r);
      else printf("0\n");
      }

### Varianter Record: union

Während eine Struktur mehrere Variablen (verschiedenen Typs) enthält, ist eine Variante eine Variable, die (aber natürlich nicht gleichzeitig) Objekte verschiedenen Typs speichern kann. Verschiedene Arten von Datenobjekten können so in einem einzigen Speicherbereich maschinenunabhängig manipuliert werden. Syntaktisch sind `union` und `struct` analog (bis auf Initialisierung). Der wesentliche Unterschied ist, daß eine Variable vom Typ `union` zu einer Zeit immer nur eines deren angegebener Elemente enthalten kann. Beispiel:

    union utype
    {   int n;
        double d;  }   irgendwas;

    irgendwas.n = 3;
    irgendwas.d = 11.7;
    zahl = irgendwas.n;     /* in diesem Fall: Fehler! */

Allerdings ist Buchführung notwendig, um zu wissen, welcher Datentyp in welcher `union`-Variablen zuletzt abgespeichert wurde (deshalb der obige Fehler). Beispiel:

    if (utype == INT)
        printf("%d\n",uval.ival);
    else if (utype == FLOAT)
        printf("%f\n",uval.fval);
    else if (utype == STRING)
        printf("%s\n",uval.pval);
    else
        printf("bad type %d in utype\n",utype);

Ein Beispiel für Struktur- und Variantenvereinbarung ist die Definition der Strukturen WORDREGS und BYTEREGS sowie der Varianten REGS für MS-DOS Funktionsaufrufe:

    struct WORDREGS {
        unsigned int ax;
        unsigned int bx;
        unsigned int cx;
        unsigned int dx;
        unsigned int si;
        unsigned int di;
        unsigned int cflag;
    };
    struct BYTEREGS {
        unsigned char al,ah;
        unsigned char bl,bh;
        unsigned char cl,ch;
        unsigned char dl,dh;
    };
    union REGS {
        struct WORDREGS x;
        struct BYTEREGS h;
    };
    union REGS inregs,outregs;
    inregs.x.bx = 0x12;    /* BX Register auf Hex 12 stellen */
    inregs.h.ah = 0x10     /* AH Register auf Hex 10 stellen */
    c = outregs.x.cx       /* CX Register nach c kopieren */

### typedef

Mit `typedef` kann man neue Datentypnamen definieren (Nicht aber neue Datentypen!). `typedef` ist `#define` ähnlich, aber weitergehender, da erst vom Compiler verarbeitet und nicht nur einfacher Textersatz. Die Anwendungen von `typedef` liegen darin, ein Programm gegen Protabilitätsprobleme abzuschirmen und für eine bessere interne Dokumentation zu sorgen. Die Syntax lautet "`typedef` <bekannter Typ> <neuer Typ>", z.B.:

    typedef int t_count;
    typedef unsigned long int bigint;

Auch wenn der neue Typ `t_count` dem `int` entspricht, kann er Programme "fehlerbewusster" machen. Definiert man eine Funktion mit einem Parameter von Typ `t_count` wird - bei entsprechend strikter Compilereinstellung bereits bei der Übersetzung ein Fehler gemeldet, wenn ein `int`-Parameter übergeben wird.

Durch `typedef` wird aber auch der Aufbau von Structures verschleiert, so daß viele Programmierer keinen Gebrauch davon machen. Zur Syntax: Der neue Typname steht an genau der Stelle, an der ohne `typedef` der Variablenname stünde. Beispiele:

    typedef struct Verbund
      {
      int i;
      float f;
      double df;
      } collect;

`collect` ist hier also keine Strukturvariable, sondern der so neu definierte Typname!

## 4.11 Bitfelder

Strukturen können auch Elemente haben, die vom Typ (`signed` oder `unsigned`) `int`, aber nur einige wenige bit lang sind; diese Elemente bezeichnet man als Bitfelder. Auf sie kann zugegriffen, und mit ihnen gerechnet werden, wie dies auch für "normale" Strukturelemente der Fall ist. Bei der Definition der Struktur wird die Zahl der bits solcher Variablen explizit angegeben, gemäß der Syntax:

    <type> <variable_name> : <bit_zahl> ;

Wird kein Variablenname angegeben, sind diese bits unbenutzt. (Dies kann sinnvoll sein beim Nachbilden von bestimmten Registern.) Ist die angegebene Bitzahl 0, so werden evtl. weitere Bitfelder an den Anfang der Adresse des nächsten Maschinenwortes geschrieben. Der Platzbedarf solcher Strukturen ist das nächste ganzzahlige Vielfache eines solchen Maschinenwortes, dessen Größe man demzufolge auch bestimmen kann mit:

`sizeof(struct { int : 0; })`

Bitfelder sind von Bedeutung, wenn Speicherplatz gespart werden soll. Allerdings gelten gegenüber "normalen" Strukturelementen die folgenden Einschränkungen:

- Die Größe eines Bitfeldes darf die eines Maschinenwortes nicht übersteigen.
- Der Adressoperator `&` darf nicht auf Bitfelder angewandt werden.
- Arrays aus Bitfeldern sind nicht erlaubt.

Beispiel:

    typedef struct
       {   unsigned b1 : 1;
           unsigned b2 : 1;
           unsigned b3 : 1;
           unsigned b4 : 1;
             int       : 6;
             int farbe : 4;
           igned flags : 2;
        }   bitpack;

Die bit-Belegung im Speicher sieht dann etwa folgendermaßen aus:

Wesentliche Eigenschaften von Bitfeldern sind implementationsabhängig;:

- die Länge eines Maschinenwortes,
- die Richtung der Belegung der Bitfelder in einer Speicherstelle,
- die maximal erlaubte Gesamtgröße der Bitfelder in einer Struktur.
- Programme, die von diesen Punkten abhängen, sind nicht portabel!

## 4.12 Initialisierungen

- `int x=1;`
- `int x[3] = {1, 2};`
- `int x[] = {1, 2, 0};`
- `char x[] = "abc";`
- `struct {int i; char c} s = {1, 'c'};`
- `struct {int i; char c} s[3] = { {1, 'c'}, {2}};`
- Initialisierung von Unions: Erstes Element.
- `auto` und `register` sind bei jedem Eintritt in den Block undefiniert, eine angegebene Initialisierung wird jedesmal durchgeführt.
- `static` (global und lokal) und `extern` werden nur beim Programmbeginn mit 0 oder ggf. dem angegebenen Wert initialisiert.

Zum Inhaltsverzeichnis | Zum nächsten Abschnitt

###### Copyright © FH München, FB 04, Prof. Jürgen Plate