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.

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.

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:

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;:

4.12 Initialisierungen

Zum Inhaltsverzeichnis Zum nächsten Abschnitt


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