Bisher wurden mit Arrays Datenstrukturen des selben Typs verwendet. In den folgenden Kapitel werden jetzt unterschiedliche Datentypen zu einer Struktur zusammengefasst. Damit kann anschließen auf diese Struktur zugegriffen werden, wie auf einfache Variablen. C C++ C/C++ Strukturen Arrays von Strukuren Strukturen in Strukturen Datenstrukturen enum typedef Bitfelder Unions Strukturen - Arrays von Strukuren Strukturen in Strukturen Datenstrukturen enum typedef Bitfelder Unions Kapitel 18: Strukturen

Bisher wurden mit den Arrays Datenstrukturen desselben Typs verwendet. In dem folgenden Kapitel werden jetzt unterschiedliche Datentypen zu einer Struktur zusammengefasst. Anschließend können Sie auf diese Struktur zugreifen wie auf einfache Variablen.

18.1. Struktur deklarieren            zurück  Ein Kapitel tiefer  zum Inhaltsverzeichnis

Als Beispiel dient hier ein Programm zur Verwaltung von Adressdaten mit folgenden Variablen:

char vname[20];
char nname[20];
long PLZ;
char ort[20];
int geburtsjahr;

Bei Ihrem jetzigen Kenntnisstand müsste jeder einzelne Parameter extra bearbeitet werden, sei es das Einlesen, Bearbeiten oder Ausgeben von Daten. Die Entwickler der Programmiersprache C haben zum Glück auch daran gedacht. Sie müssen einfach alle Variablen in eine Struktur verpacken. Bei den Adressdaten sieht dies dann so aus:

struct adres {
                char vname[20];
                char nname[20];
                long PLZ;
                char ort[20];
                int geburtsjahr;
             }adressen;

Gefolgt von dem Schlüsselwort struct wurden alle Daten in einer Struktur namens adres zusammengefasst. Die Sichtbarkeit und die Lebensdauer von Strukturen entsprechen exakt der von einfachen Variablen. Der Inhalt der Struktur adres wird in geschweiften Klammern zusammengefasst. Am Ende der geschweiften Klammern steht der Variablen-Bezeichner (adressen), mit dem auf die Struktur zugegriffen wird.

Zur Deklaration einer Struktur in C folgt hier die Syntax:

struct typNAME {
                Datentyp1;
                Datentyp2;
                .........
                /* Liste der Strukturelemente */
                Datentyp_n;
               }Variablen_Bezeichner;

Strukturelemente sind im Prinzip nichts anderes als normale Variablen, die als Teil einer Struktur definiert werden. Als Datentypen kommen alle bekannten Typen in Frage. Natürlich und vor allem auch Zeiger und Strukturen selbst. Folgende Struktur können Sie sich im Speicher so vorstellen:

struct index {
               int seite;
               char titel[30];
             };

Abbildung 18.1: Strukurelemente der Struktur index
Abbildung 18.1: Strukurelemente der Struktur index

In dem Beispiel wurde eine Struktur vom Typ index deklariert. Diese Struktur kann einen int-Wert und einen String von 30 Zeichen Länge aufnehmen. Folglich wäre die Gesamtgröße der Struktur 34 Bytes (auf 16-Bit-Systemen entsprechend 32 Bytes).

Hinweis
 

Vielleicht haben Sie schon mit dem sizeof-Operator die wirkliche Größe dieser Struktur getestet. Auf 32-Bit-Systemen dürften dies 36 Bytes sein. Dies liegt an der Fragmentierung des Betriebssystems. Meistens werden die Daten im Vier-Byte-Alignment gespeichert. Mehr dazu erfahren Sie ein paar Seiten später.

18.2. Initialisierung und Zugriff von Strukturen            zurück  Ein Kapitel tiefer  Ein Kapitel höher  zum Inhaltsverzeichnis

Zugreifen können Sie auf die einzelnen Variablen einer Struktur mithilfe des Punktoperators (.). Ansonsten erfolgen die Initialisierung und der Zugriff wie bei normalen Variablen. Beispielsweise:

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

struct index {
                int seite;
                char titel[30];
             };

int main()
{
   struct index lib;
   lib.seite = 23;
   strcpy(lib.titel, "C-Programmieren");

   printf("%d, %s\n",lib.seite, lib.titel);
   return 0;
}

Abbildung 18.2: Strukturelemente wurden mit Werten initialisiert
Abbildung 18.2: Strukturelemente wurden mit Werten initialisiert

Mit

struct index lib; 

wird eine Struktur mit der Bezeichnung lib vom Typ index deklariert. Diese Extra-Deklaration hätten Sie auch mit folgender Schreibweise erzielt:

struct index {
               int seite;
               char titel[30];
             }lib;

Wenn Sie den Typennamen dieser Struktur nicht benötigen, kann sie auch ohne deklariert werden:

struct {
         int seite;
         char titel[30];
       }lib;

Es spricht auch nichts dagegen, mehrere Typen auf einmal zu deklarieren:

struct index {
               int seite;
               char titel[30];
             }lib1, lib2, lib3;

Hiermit wurden drei Variablen vom Typ index deklariert. Strukturen können natürlich ebenso wie normale Datentypen direkt bei der Deklaration mit Werten initialisiert werden:

struct index {
               int seite;
               char titel[30];
             }lib = { 308, "Strukturen" };

Oder auch bei der Deklaration in der main()-Funktion:

struct index lib = { 55, "Einführung in C" };

Zur Demonstration folgt ein Listing, welches zeigt, wie Sie auf den Inhalt einer Struktur zugreifen können:

#include <stdio.h>
#define MAX 30

struct adres {
                  char vname[MAX];
                  char nname[MAX];
                  long PLZ;
                  char ort[MAX];
                  int geburtsjahr;
              }adressen;

/*Funktion zur Ausgabe des Satzes*/
void ausgabe(struct adres x)
{
   printf("\n\nSie gaben ein:\n\n");
   printf("Vorname.........:%s",   x.vname);
   printf("Nachname........:%s",   x.nname);
   printf("Postleitzahl....:%ld\n",x.PLZ);
   printf("Ort.............:%s",   x.ort);
   printf("Geburtsjahr.....:%d\n", x.geburtsjahr);
}

int main()
{
   printf("Vorname      : ");
   fgets(adressen.vname, MAX, stdin);
   printf("Nachname     : ");
   fgets(adressen.nname, MAX, stdin);
   printf("Postleitzahl : ");
   do {scanf("%5ld",&adressen.PLZ);
      } while(getchar()!= '\n');
   printf("Wohnort      : ");
   fgets(adressen.ort, MAX, stdin);
   printf("Geburtsjahr  : ");
   do {scanf("%4ld",&adressen.geburtsjahr);
      }while(getchar()!='\n' );

   ausgabe(adressen);
   return 0;
}

Abbildung 18.3: Einlesen und Ausgeben von Strukturen
Abbildung 18.3: Einlesen und Ausgeben von Strukturen

Die erste Eingabe in der main()-Funktion lautet:

fgets(adressen.vname, MAX, stdin);

Damit wird der Vorname eingelesen. Der Zugriff erfolgt über den Namen der Struktur, gefolgt vom Punkteoperator. Dahinter folgt das entsprechende Strukturelement, welches diese Daten erhalten soll. Dies funktioniert genauso, wenn Sie einen String direkt mit der Funktion strcpy() in ein Strukturelement einkopieren wollen:

strcpy(adressen.vname , "Tux"); 

Die direkte Initialisierung numerischer Werte an Strukturelementen lässt sich ebenfalls variablentypisch durchführen:

adressen.PLZ = 89000; 

Wenn Sie alle Strukturelemente eingegeben haben, wird die Funktion ausgabe() aufgerufen:

ausgabe(adressen); 

Als Argument erhält diese Funktion die Struktur adressen. Danach werden die einzelnen Elemente der Struktur auf dem Bildschirm ausgegeben.

Eine weitere Möglichkeit, die Struktur im Listing sofort mit einem Inhalt zu initialisieren, ist diese:

stuct adres {
                char vname[20];
                char nname[20];
                long PLZ;
                char ort[20];
                int geburtsjahr;
        }adressen = {"Ernest", "Hemming" ,3434, "Havanna" ,1913};

Folgende Wertzuweisung von Strukturen sollten Sie allerdings vermeiden:

struct {
           int a1;
           int a2;
           int a3;
       }werte1,werte2;

werte1.a1=8;
werte1.a2=16;
werte1.a3=32;

werte2=werte1;   /*Bitte vermeiden Sie solche Zuweisungen*/

Das ist zwar erlaubt in C, kann aber zu Fehlern führen, wenn ein Compiler dies nicht unterstützt. Sicherer wäre da die folgende Möglichkeit:

memcpy(&werte2,&wert1, sizeof(werte1)); 

Dies soll nur ein Hinweis sein und keine Regel!

Es folgt ein kleiner Tipp, wie Sie sich die Strukturen vielleicht noch besser vorstellen können. Als Vergleich dienen dazu die Variablen int x und char c.

Typ Name
int x
char c
struct adres adressen

Tabelle 18.1: Strukturen im Vergleich mit Standard-Datentypen

Wird die Variable x mit einem Wert initialisiert, gehen Sie bekannterweise so vor:

x=1999;

Bei einer Struktur kommt noch ein kleiner Zusatz hinzu:

adressen.geburtsjahr=1999;

18.3. Strukturen als Wertübergabe an eine Funktion            zurück  Ein Kapitel tiefer  Ein Kapitel höher  zum Inhaltsverzeichnis

Anhand des Funktionsaufrufs vom Beispiel zuvor konnten Sie sehen, dass Strukturen genauso wie jeder andere Datentyp an Funktionen per call-by-value übergeben werden können. Die Funktion bekommt dabei eine Kopie der vollständigen Struktur übergeben. Das Anlegen einer Kopie kann bei häufigen Funktionsaufrufen mit umfangreichen Strukturen die Laufzeit des Programms erheblich beeinträchtigen. Um diesen Mehraufwand zu sparen, empfehle ich Ihnen, Zeiger auf Strukturen als Parameter zu verwenden. Das folgende Listing soll dies demonstrieren:

#include <stdio.h>
#define MAX 30

struct adres { char vname[MAX];
               char nname[MAX];
               long PLZ;
               char ort[MAX];
               int geburtsjahr;
              }adressen;

/*Funktion zur Ausgabe des Satzes*/
void ausgabe(struct adres *struct_ptr)
{
   printf("\n\nSie gaben ein:\n\n");
   printf("Vorname.........:%s",(*struct_ptr).vname);
   printf("Nachname........:%s",(*struct_ptr).nname);
   printf("Postleitzahl....:%ld\n",(*struct_ptr).PLZ);
   printf("Ort.............:%s",(*struct_ptr).ort);
   printf("Geburtsjahr.....:%d\n",(*struct_ptr).geburtsjahr);
}

int main()
{
   printf("Vorname      : ");
   fgets(adressen.vname, MAX, stdin);
   printf("Nachname     : ");
   fgets(adressen.nname, MAX, stdin);
   printf("Postleitzahl : ");
   do {scanf("%5ld",&adressen.PLZ);
      } while(getchar()!= '\n');
   printf("Wohnort      : ");
   fgets(adressen.ort, MAX, stdin);
   printf("Geburtsjahr  : ");
   do {scanf("%4ld",&adressen.geburtsjahr);
      }while(getchar()!='\n' );

   ausgabe(&adressen);
   return 0;
}

Dies ist dasselbe Listing wie oben, nur wird dieses Mal das Argument der Funktion ausgabe() mit call-by-reference übergeben:

ausgabe(&adressen);

Die Funktion ausgabe() selbst musste dabei auch ein wenig verändert werden:

void ausgabe(struct adres *struct_ptr)
{
   printf("\n\nSie gaben ein:\n\n");
   printf("Vorname.........:%s",(*struct_ptr).vname);
   printf("Nachname........:%s",(*struct_ptr).nname);
   printf("Postleitzahl....:%ld\n",(*struct_ptr).PLZ);
   printf("Ort.............:%s",(*struct_ptr).ort);
   printf("Geburtsjahr.....:%d\n",(*struct_ptr).geburtsjahr);
}

Außer dem Zeiger struct_ptr als Parameter, der auf eine Struktur vom Typ adress zeigen kann, musste auch der Zugriff auf die Strukturelemente geändert werden. Dass Sie bei Call-by-reference-Variablen mit dem Dereferenzierungsoperator arbeiten müssen, ist Ihnen ja bekannt. Da aber hier der Punkteoperator verwendet wird, muss der Referenzzeiger struct_ptr zwischen zwei Klammern gestellt werden, da der Ausdruck zwischen den Klammern die höhere Bindungskraft hat und zuerst ausgewertet wird:

printf("Vorname.........:%s",(*struct_ptr).vname);

Die Hersteller von C haben aber auch gemerkt, dass eine solche Schreibweise - speziell wenn mehrere Referenzen folgen - schwer lesbar ist. Daher wurde der so genannte Elementkennzeichnungsoperator (->) eingeführt. Mit diesem würde die Ausgabe des Vornamens folgendermaßen vorgenommen werden:

printf("Vorname.........:%s",(struct_ptr->vname);

Dies lässt sich auch recht einfach lesen, da dieser Operator aussieht wie ein Pfeil oder auch ein Zeiger. Diesen Operator werden Sie noch häufiger in diesem Buch benötigen als Ihnen lieb sein wird. Speziell, wenn es um die dynamischen Datenstrukturen geht (Kapitel 24).

18.4. Strukturen als Rückgabewert einer Funktion            zurück  Ein Kapitel tiefer  Ein Kapitel höher  zum Inhaltsverzeichnis

Wie bei der Werteübergabe von Strukturen, sollten Sie auch bei der Werterückgabe von Strukturen Zeiger verwenden. Dafür soll wieder das eben geschriebene Listing verwendet werden. Es fehlt nämlich noch eine Funktion zur Eingabe der einzelnen Strukturelemente. Hier das Listing mit der Funktion eingabe(), welche die Anfangsadresse der Struktur zurückgibt:

#include <stdio.h>
#include <stdlib.h>
#define MAX 30

struct adres {
               char vname[MAX];
               char nname[MAX];
               long PLZ;
               char ort[MAX];
               int geburtsjahr;
              };

/*Funktion zur Ausgabe des Satzes*/
void ausgabe(struct adres *struct_ptr)
{
   printf("\n\nSie gaben ein:\n\n");
   printf("Vorname.........:%s",struct_ptr->vname);
   printf("Nachname........:%s",struct_ptr->nname);
   printf("Postleitzahl....:%ld\n",struct_ptr->PLZ);
   printf("Ort.............:%s",struct_ptr->ort);
   printf("Geburtsjahr.....:%d\n",struct_ptr->geburtsjahr);
}

struct adres *eingabe(void)
{
   struct adres *adressen;
   adressen=(struct adres *)malloc(sizeof(struct adres));
   printf("Vorname : ");
   fgets(adressen->vname, MAX, stdin);
   printf("Nachname : ");
   fgets(adressen->nname, MAX, stdin);
   printf("Postleitzahl : ");
   do {scanf("%ld",&adressen->PLZ);} while(getchar()!= '\n');
   printf("Wohnort : ");
   fgets(adressen->ort, MAX, stdin);
   printf("Geburtsjahr : ");
   do {
       scanf("%ld",&adressen->geburtsjahr);
      }while(getchar()!='\n' );
   return adressen;
}

int main()
{
   struct adres *adresse1, *adresse2;
   adresse1=eingabe();
   adresse2=eingabe();

   ausgabe(adresse1);
   ausgabe(adresse2);
   return 0;
}

Bei diesem Listing verwenden Sie bereits nur noch Zeiger und kommen zum ersten Mal mit den dynamischen Datenstrukturen in Berührung. Aufgerufen wird diese Funktion zur Eingabe von Strukturelementen mit:

adresse1=eingabe(); 

Einen Adressoperator benötigen Sie hier nicht, da Sie ja bereits einen Zeiger verwenden. Und da Sie hier einen Zeiger verwenden, benötigen Sie in der Funktion erst einmal Speicherplatz für eine Struktur. Dieser wird mit der Funktion malloc() angefordert:

struct adres *adressen;
adressen=(struct adres *)malloc(sizeof(struct adres));

Jetzt können Sie mithilfe des Elementkennzeichnungsoperators die Daten der einzelnen Strukturelemente einlesen. Am Ende der Funktion wird die Adresse dieser reservierten und mit neuem Inhalt versehenen Struktur mit return an den Aufrufer zurückgegeben. In der main()-Funktion zeigt jetzt der Zeiger adresse1 auf diesen Speicherbereich.

Hinweis für Anfänger
 

In den letzten beiden Kapiteln ist der Schwierigkeitsgrad enorm gestiegen. Falls Sie dabei nur Bahnhof verstanden haben, machen Sie sich keine Sorgen, im weiteren Verlauf dieses Buchs fügen sich auch für Sie die einzelnen Puzzle-Teile zu einem Ganzen. Die nächsten Abschnitte werden jetzt wieder um einiges verständlicher sein.

Natürlich lässt sich eine Funktion mit Strukturen als Rückgabewert auch einfacher deklarieren:

struct typNAME Funktionsname() 

18.5. Strukturen vergleichen            zurück  Ein Kapitel höher  zum Inhaltsverzeichnis

Es gibt leider keinen portablen oder sinnvollen Weg, zwei Strukturen direkt miteinander zu vergleichen. Das Problem liegt darin, dass ein Compiler aus Optimierungsgründen Lücken zwischen den einzelnen Elementen lassen kann. Mehr zu diesem so genannten Padding erfahren Sie in Abschnitt 18.12.

Es bleibt Ihnen also letztendlich nichts anderes übrig, als eine eigene Funktion zu schreiben. Hier ein Beispiel, wie Sie dabei vorgehen können:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MAX 30

struct adres { char vname[MAX];
               char nname[MAX];
               long PLZ;
               char ort[MAX];
               int geburtsjahr;
              };

int cmp_structs(struct adres *str1, struct adres *str2)
{
   /* Vorname gleich und */
   if(strcmp(str1->vname, str2->vname) == 0 &&
   /* Nachname gleich und */
      strcmp(str1->nname, str2->nname) == 0 &&
   /* Postleitzahl gleich und */
     (str1->PLZ-str2->PLZ) == 0 &&
   /* Wohnort gleich und */
     strcmp(str1->ort, str2->ort) == 0 &&
   /* geburtsjahr gleich */
     (str1->geburtsjahr-str2->geburtsjahr) == 0)
      return 0; /* Beide Strukturen gleich */
   else
      return 1; /* Strukturen nicht gleich */
}

int main()
{
   struct adres adresse1={"John","Leroy",1234,"New York",1980 };
   struct adres adresse2={"John","Leroy",1234,"New York",1980 };

   if(cmp_structs(&adresse1, &adresse2) == 0)
      printf("Beide Strukturen sind gleich?!?!\n");
   else
      printf("Die Strukturen weisen Unterschiede auf\n");
   return 0;
}

Weiter mit 18.6. Arrays von Strukturen            zum Inhaltsverzeichnis