Viele Programmieraufgaben lassen sich mit den so genannten Strukturen, einem speziellen Datenkonstrukt, leichter lösen. Eine Struktur ist ein von Ihnen definierter Datentyp, der direkt auf Ihre Programmierbedürfnisse zugeschnitten ist. Heute lernen Sie:
Eine Struktur ist eine Sammlung von einer oder mehreren Variablen, die zum Zwecke der leichteren Manipulation unter einem Namen zusammengefasst werden. Die Variablen in einer Struktur können im Gegensatz zu denen in einem Array unterschiedlichen Datentypen angehören. Eine Struktur kann jeden beliebigen C-Datentyp enthalten, einschließlich Arrays und andere Strukturen. Die Variablen einer Struktur werden auch als Elemente dieser Struktur bezeichnet. Im nächsten Abschnitt sehen Sie hierzu ein einfaches Beispiel.
Sie sollten mit einfachen Strukturen beginnen. Beachten Sie, dass C keinen Unterschied zwischen einfachen und komplexen Strukturen macht. Aber zum besseren Verständnis sollte man zuerst die einfachen Strukturen behandeln.
Wenn Sie ein Grafikprogramm schreiben, wird Ihr Code mit den Koordinaten von
Bildschirmpunkten arbeiten müssen. Bildschirmkoordinaten setzen sich aus einem
x
-Wert für die horizontale Position und einem y
-Wert für die vertikale Position
zusammen. Zum Abspeichern der Koordinaten lohnt es sich, eine Struktur - im
nachfolgenden Code heißt sie koord
- zu definieren, die sowohl die x
- als auch die y
-
Werte einer Bildschirmposition einschließt:
struct koord {
int x;
int y;
};
Auf das Schlüsselwort struct
, das eine Strukturdefinition einleitet, folgt direkt der
Name des Strukturtyps. Die sich an den Strukturnamen anschließenden geschweiften
Klammern umfassen die Liste der Elementvariablen der Struktur. Für jede
Elementvariable müssen ein Datentyp und ein Name angegeben werden.
Es ist zwar zulässig, Strukturen ohne Namen zu erzeugen,
aber solche Strukturen sind nicht besonders nützlich.
Die obigen Anweisungen definieren einen Strukturtyp namens koord
, der zwei
Integervariablen, x
und y
, enthält. Die Anweisungen erzeugen allerdings noch keine
Instanzen der Struktur koord
. Mit anderen Worten, sie deklarieren keine
Strukturenvariablen (es wird kein Speicher reserviert). Es gibt zwei Möglichkeiten,
Strukturvariablen zu deklarieren. Eine Möglichkeit besteht darin, die Strukturdefinition
um eine Liste von einem oder mehreren Variablennamen zu ergänzen:
struct koord {
int x;
int y;
} erste, zweite;
Diese Anweisungen definieren den Strukturtyp koord
und deklarieren die zwei
Strukturenvariablen erste
und zweite
. erste
und zweite
sind beides Instanzen vom
Typ koord
und enthalten zwei Integer-Elemente namens x
und y
.
Diese Art, eine Struktur zu deklarieren, kombiniert die Deklaration mit der Definition.
Die zweite Möglichkeit besteht darin, die Deklaration der Strukturvariablen
unabhängig von der Definition vorzunehmen. Die folgenden zwei Anweisungen
deklarieren ebenfalls zwei Instanzen vom Typ koord
:
struct koord {
int x;
int y;
};
/* Hier kann zusätzlicher Code stehen */
struct koord erste, zweite;
Die einzelnen Strukturelemente können wie normale Variablen des gleichen Typs
verwendet werden. Der Zugriff auf die Strukturelemente erfolgt über den
Strukturelement-Operator (.), auch Punktoperator genannt, der zwischen den
Strukturnamen und den Elementnamen gesetzt wird. Um die Bildschirmposition mit
den Koordinaten x=50
und y=100
in einer Strukturvariablen namens erste
abzuspeichern, könnten Sie schreiben:
erste.x = 50;
erste.y = 100;
Um die Bildschirmpositionen, die in der Strukturvariablen zweite
gespeichert ist,
anzuzeigen, könnten Sie schreiben:
printf("%d,%d", zweite.x, zweite.y);
Vielleicht fragen Sie sich inzwischen, worin der Vorteil der Strukturen gegenüber den einzelnen Variablen liegt. Ein großer Vorteil ist, dass man Daten, die in Strukturen des gleichen Typs abgelegt sind, mit einer einfachen Zuweisung kopieren kann1. So ist die Anweisung
erste = zweite;
erste.x = zweite.x;
erste.y = zweite.y;
Wenn Ihr Programm komplexe Strukturen mit vielen Elementen enthält, kann Ihnen diese Notation viel Zeit sparen. Es gibt noch weitere Vorteile, die sich Ihnen im Laufe der Zeit und beim Erlernen weiter fortgeschrittener Programmiertechniken erschließen werden. Allgemein lässt sich sagen, dass Strukturen immer dann sinnvoll sind, wenn Informationen unterschiedlicher Variablentypen als Gruppe bearbeitet werden sollen. So könnten Sie zum Beispiel die Einträge in einer Adressdatenbank als Variablen einer Struktur betrachten, in der für jede einzelne Information (Name, Adresse, Stadt und so weiter) ein eigenes Strukturelement deklariert ist.
struct name {
struktur_element(e);
/* Hier kann zusätzlicher Code stehen */
} instanz;
Das Schlüsselwort struct
dient dazu, Strukturen zu deklarieren. Eine Struktur ist eine
Sammlung von einer oder mehreren Variablen (struktur_elemente
), die zur leichteren
Bearbeitung unter einem Namen zusammengefasst wurden. Die Variablen müssen
nicht vom gleichen Datentyp und auch keine einfachen Variablen sein. So können
Strukturen auch Arrays, Zeiger und andere Strukturen enthalten.
Das Schlüsselwort struct
kennzeichnet den Anfang einer Strukturdefinition. Es wird
gefolgt von dem Namen der Struktur. An den Strukturnamen schließen sich in
geschweiften Klammern die Strukturelemente an. Eine Instanz
, die Deklaration einer
Strukturvariablen, kann ebenfalls definiert werden. Wenn Sie die Struktur ohne die
Instanz definieren, erhalten Sie lediglich eine Schablone, die dann später in dem
Programm zur Deklaration von Strukturvariablen verwendet werden kann. Das
Format einer Schablone sieht wie folgt aus:
struct name {
struktur_element(e);
/* Hier können zusätzliche Anweisungen stehen */
};
Um auf der Grundlage der Schablone Instanzen zu deklarieren, verwenden Sie folgende Syntax:
struct name instanz;
Voraussetzung ist, dass Sie zuvor eine Struktur mit dem angegebenen Namen deklariert haben.
/* Deklariert eine Strukturschablone namens kunden_nr */
struct kunden_nr {
int zahl1;
char bindestrich1;
int zahl2;
char bindestrich2;
int zahl3;
};
/* Verwendet die Strukturschablone */
struct kunden_nr aktueller_kunde;
/* Deklariert gleichzeitig eine Struktur und eine Instanz */
struct datum {
char monat[2];
char tag[2];
char jahr[4];
} aktuelles_datum;
/* Deklariert und initialisiert eine Strukturvariable */
struct zeit {
int stunden;
int minuten;
int sekunden;
} zeitpunkt_der_geburt = { 8, 45, 0 };
Nachdem Sie inzwischen die einfachen Strukturen kennen gelernt haben, kommen wir jetzt zu den interessanteren, weil komplexeren Strukturtypen. Dazu gehören Strukturen, die andere Strukturen oder auch Arrays als Elemente enthalten.
Wie bereits erwähnt, kann eine Struktur jeden beliebigen C-Datentypen enthalten. So kann eine Struktur unter anderem auch andere Strukturen aufnehmen. Lassen Sie uns das veranschaulichen, indem wir das obige Beispiel etwas ausbauen.
Angenommen Ihr Grafikprogramm soll Rechtecke unterstützen. Ein Rechteck wird
definiert durch die Koordinaten zweier gegenüberliegenden Ecken. Wie man eine
Struktur zum Abspeichern von Koordinaten (x, y) definiert, haben Sie bereits gesehen.
Zur Definition eines Rechtecks benötigen Sie zwei Instanzen dieser Struktur.
Vorausgesetzt, Sie haben bereits eine Struktur vom Typ koord
definiert, könnte die
Definition einer Struktur für die Rechtecke dann wie folgt aussehen:
struct rechteck {
struct koord obenlinks;
struct koord untenrechts;
};
Diese Anweisung definiert eine Struktur vom Typ rechteck
, die zwei Strukturen vom
Typ koord
enthält. Die beiden koord
-Strukturen heißen obenlinks
und untenrechts
.
Die obige Anweisung definiert lediglich den Strukturtyp rechteck
. Um eine Instanz der
Struktur zu deklarieren, müssen Sie eine Anweisung wie die folgende hinzufügen:
struct rechteck meinfeld;
Sie hätten - wie bereits oben im Falle von koord
- Definition und Deklaration auch
kombinieren können:
struct rechteck {
struct koord obenlinks;
struct koord untenrechts;
} meinfeld;
Um auf die Speicherstellen der eigentlichen Daten (die Elemente vom Typ int
)
zuzugreifen, müssen Sie den Element-Operator (.
) zweimal verwenden. So bezieht
sich der Ausdruck
meinfeld.obenlinks.x
auf das Element x
des Elements obenlinks
der Strukturvariablen meinfeld
vom Typ
rechteck
. Um ein Rechteck mit den Koordinaten (0,10) und (100,200) zu definieren,
würden Sie schreiben:
meinfeld.obenlinks.x = 0;
meinfeld.obenlinks.y = 10;
meinfeld.untenrechts.x = 100;
meinfeld.untenrechts.y = 200;
Wenn Sie hierdurch etwas verwirrt sind, sollten Sie am besten einen Blick auf
Abbildung 10.1 werfen, in der die Beziehung zwischen der Struktur vom Typ
rechteck
, den darin enthaltenen zwei Strukturen vom Typ koord
sowie den darin
jeweils enthaltenen zwei Variablen vom Typ int
deutlich gemacht werden. Die
Namen der Strukturen entsprechen dem obigen Beispiel.
Abbildung 10.1: Die Beziehung zwischen einer Struktur, den Strukturen in dieser Struktur und den Strukturelementen.
Listing 10.1 demonstriert die Verwendung von Strukturen, die andere Strukturen enthalten. Das Programm nimmt vom Anwender die Koordinaten eines Rechtecks entgegen, berechnet daraus die Fläche des Rechtecks und gibt diesen Wert aus. Beachten Sie die Voraussetzungen für dieses Programm, die innerhalb eines Kommentars am Anfang des Programms stehen (Zeilen 3 bis 8).
Listing 10.1: Ein Beispiel für eine Struktur, die andere Strukturen enthält.
1: /* Beispiel für Strukturen, die andere Strukturen enthalten. */
2:
3: /* Übernimmt die Eingabe der Eck-Koordinaten eines Rechtecks
4: und berechnet die Fläche. Geht davon aus, dass die y-Koordinate
5: der unteren rechten Ecke größer ist als die y-Koordinate der
6: oberen linken Ecke, dass die x-Koordinate der unteren rechten
7: Ecke größer ist als die x-Koordinate der unteren linken Ecke
8: und dass alle Koordinaten positiv sind. */
9:
10: #include <stdio.h>
11:
12: int laenge, hoehe;
13: long flaeche;
14:
15: struct koord{
16: int x;
17: int y;
18: };
19:
20: struct rechteck{
21: struct koord obenlinks;
22: struct koord untenrechts;
23: } meinfeld;
24:
25: int main(void)
26: {
27: /* Eingabe der Koordinaten */
28:
29: printf("\nGeben Sie die x-Koordinate von oben links ein: ");
30: scanf("%d", &meinfeld.obenlinks.x);
31:
32: printf("\nGeben Sie die y-Koordinate von oben links ein: ");
33: scanf("%d", &meinfeld.obenlinks.y);
34:
35: printf("\nGeben Sie die x-Koordinate von unten rechts ein: ");
36: scanf("%d", &meinfeld.untenrechts.x);
37:
38: printf("\nGeben Sie die y-Koordinate von unten rechts ein: ");
39: scanf("%d", &meinfeld.untenrechts.y);
40:
41: /* Länge und Höhe berechnen */
42:
43: hoehe = meinfeld.untenrechts.x - meinfeld.obenlinks.x;
44: laenge = meinfeld.untenrechts.y - meinfeld.obenlinks.y;
45:
46: /* Fläche berechnen und ausgeben */
47:
48: flaeche = hoehe * laenge;
49: printf("\nDie Fläche misst %ld Einheiten.\n", flaeche);
50:
51: return 0;
52: }
Geben Sie die x-Koordinate von oben links ein: 1
Geben Sie die y-Koordinate von oben links ein: 1
Geben Sie die x-Koordinate von unten rechts ein: 10
Geben Sie die y-Koordinate von unten rechts ein: 10
Die Fläche misst 81 Einheiten.
Die Struktur koord
mit ihren zwei Elementen x
und y
ist in den Zeilen 15 bis 18
definiert. Die Zeilen 20 bis 23 deklarieren und definieren eine Instanz (meinfeld
) der
Struktur rechteck
. Die zwei Elemente dieser Rechteckstruktur lauten obenlinks
und
untenrechts
und sind beides Strukturen vom Typ koord
.
Die Zeilen 29 bis 39 fordern zur Eingabe der Werte für die Struktur meinfeld
auf. Da
meinfeld
nur zwei Elemente enthält, könnten Sie fälschlicherweise annehmen, dass
auch nur zwei Werte benötigt werden. Jedes Element von meinfeld
verfügt jedoch
wieder über eigene Elemente (die Elemente x
und y
der koord
-Struktur). Dadurch
ergeben sich insgesamt vier Elemente, für die Werte eingegeben werden müssen.
Nachdem diese Werte eingelesen wurden, wird mit Hilfe der Struktur und ihrer
Elemente die Fläche berechnet. Beachten Sie, dass Sie für den Zugriff auf die x
- und y
-
Werte über den Instanznamen der Struktur gehen müssen. Da x
und y
in einer Struktur
innerhalb einer Struktur verborgen sind, müssen Sie für die Berechnung der Fläche
sogar über die Instanznamen beider Strukturen auf die x- und y-Werte zugreifen -
meinfeld.untenrechts.x
, meinfeld.untenrechts.y
, meinfeld.obenlinks.x
und
meinfeld.obenlinks.y
.
Die Verschachtelungstiefe für Strukturen ist in C praktisch unbegrenzt. Solange es Ihr Speicher erlaubt, können Sie Strukturen definieren, die Strukturen enthalten, die Strukturen enthalten, die wiederum Strukturen enthalten und so weiter - ich glaube, Sie verstehen, was ich meine! Selbstverständlich gibt es einen Punkt, ab dem eine weitere Verschachtelung keine Vorteile mehr bringt. In der Praxis werden in C- Programmen selten mehr als drei Verschachtelungsebenen verwendet.
In C ist es möglich, Strukturen zu definieren, die ein oder mehrere Arrays als
Elemente enthält. Die Arrays können Elemente eines beliebigen C-Datentyps
enthalten (int
, char
und so weiter). So definieren die Anweisungen
struct daten{
int x[4];
char y[10];
};
eine Struktur vom Typ daten
, die zwei Arrays enthält - eines mit vier Integer-
Elementen namens x
und eines mit zehn Zeichen namens y
. Nach der Definition der
Struktur daten
können Sie eine Strukturvariable (datensatz
) vom Typ der Struktur
deklarieren:
struct daten datensatz;
Den Aufbau dieser Struktur sehen Sie in Abbildung 10.2. Beachten Sie, dass in dieser
Abbildung die Elemente des Arrays x
wesentlich mehr Platz beanspruchen als die
Elemente des Arrays y
. Dies liegt daran, dass der Datentyp int
in der Regel 4 Byte
Speicher belegt, während char
nur 1 Byte beansprucht (dies dürfte Ihnen bereits von
Tag 2, »Die Komponenten eine C-Programms: Quellcode und Daten«, her bekannt
sein).
Abbildung 10.2: Der Aufbau einer Struktur, die Arrays als Elemente enthält.
Der Zugriff auf die einzelnen Elemente eines Arrays, das Element einer Struktur ist, erfolgt über Element-Operatoren und Array-Index:
datensatz.x[2] = 100;
datensatz.y[1] = 'x';
Sie erinnern sich wahrscheinlich, dass Zeichenarrays meistens dazu dienen, Strings zu speichern. Sie sollten auch daran denken (siehe Tag 8, »Zeiger«), dass der Name eines Arrays ohne eckige Klammern ein Zeiger auf das Array ist. Da dies auch für Arrays gilt, die Element einer Struktur sind, ist der Ausdruck
datensatz.y
ein Zeiger auf das erste Element im Array y[]
der Struktur datensatz
. Deshalb
können Sie den Inhalt von y[]
mit folgender Anweisung auf dem Bildschirm
ausgeben:
puts(datensatz.y);
Betrachten wir ein weiteres Beispiel. Listing 10.2 verwendet eine Struktur, die eine
Variable vom Typ float
und zwei Arrays vom Typ char
enthält.
Listing 10.2: Eine Struktur, die Arrays als Elemente enthält.
1: /* Eine Struktur, die Arrays als Elemente enthält. */
2:
3: #include <stdio.h>
4:
5: /* Definiert und deklariert eine Struktur für die Aufnahme der Daten. */
6: /* Die Struktur enthält eine float-Variable und zwei char-Arrays. */
7:
8: struct daten{
9: float betrag;
10: char vname[30];
11: char nname[30];
12: } rec;
13:
14: int main(void)
15: {
16: /* Eingabe der Daten über die Tastatur. */
17:
18: printf("Geben Sie den Vor- und Nachnamen des Spenders,\n");
19: printf("getrennt durch ein Leerzeichen, ein: ");
20: scanf("%s %s", rec.vname, rec.nname);
21:
22: printf("\nGeben Sie die Höhe der Spende ein: ");
23: scanf("%f", &rec.betrag);
24:
25: /* Zeigt die Informationen an. */
26: /* Achtung: %.2f gibt einen Fließkommawert aus, */
27: /* der mit zwei Stellen hinter dem Dezimalpunkt */
28: /* angegeben wird. */
29:
30: /* Gibt die Daten auf dem Bildschirm aus. */
31:
32: printf("\nDer Spender %s %s gab %.2f DM.\n", rec.vname,
33: rec.nname, rec.betrag);
34:
35: return 0;
36: }
Geben Sie den Vor- und Nachnamen des Spenders,
getrennt durch ein Leerzeichen, ein: Bradley Jones
Geben Sie die Höhe der Spende ein: 1000.00
Der Spender Bradley Jones gab 1000.00 DM.
Dieses Programm weist eine Struktur auf, die zwei Arrays enthält - vname[30]
und
nname[30]
. Beides sind Zeichenarrays und nehmen den Vor- beziehungsweise den
Nachnamen einer Person auf. Die in den Zeilen 8 bis 12 deklarierte Struktur erhält
den Namen daten
. Sie enthält neben den Zeichenarrays vname
und nname
die float
-
Variable betrag
. Eine Struktur wie diese eignet sich hervorragend, um (in zwei Arrays)
den Vor- und Nachnamen einer Person sowie einen Wert aufzunehmen wie zum
Beispiel den Betrag, den eine Person für einen sozialen Zweck gespendet hat.
In Zeile 12 wird eine Instanz der Struktur, rec2
, deklariert. Der Rest des Programms
verwendet rec
, um Werte vom Anwender einzulesen (Zeilen 18 bis 23) und diese
dann auszugeben (Zeilen 32 und 33).
Wenn es Strukturen gibt, die Arrays enthalten, gibt es dann auch Arrays von Strukturen? Aber natürlich! Arrays von Strukturen stellen sogar ein besonders mächtiges und wichtiges Programmkonstrukt dar. Wie man dieses Konstrukt nutzt, wird im Folgenden erläutert.
Sie haben bereits gesehen, wie die Definition einer Struktur auf die Daten, mit denen ein Programm arbeitet, zugeschnitten werden kann. In der Regel muss ein Programm allerdings mit mehr als einer Instanz dieser Daten arbeiten. So könnten Sie zum Beispiel in einem Programm zur Verwaltung einer Telefonnummerliste eine Struktur definieren, die für jeden Eintrag in der Telefonliste den Namen der zugehörigen Person und die Telefonnummer enthält:
struct eintrag{
char vname[10];
char nname[12];
char telefon[8];
};
Eine Telefonliste besteht aus vielen Einträgen. Deshalb wäre eine einzige Instanz der
Struktur nicht besonders nützlich. Was Sie hier benötigen, ist ein Array von
Strukturvariablen des Typs eintrag
. Nachdem die Struktur definiert ist, können Sie
das Array wie folgt deklarieren.
struct eintrag liste[1000];
Diese Anweisung deklariert ein Array namens liste
mit 1.000 Elementen. Jedes
Element ist eine Struktur vom Typ eintrag
und wird, wie bei anderen Array-
Elementtypen, durch einen Index identifiziert. Jede der Strukturvariablen im Array
besteht aus drei Elementen, bei denen es sich um Arrays vom Typ char
handelt.
Dieses ziemlich komplexe Gebilde ist in Abbildung 10.3 grafisch veranschaulicht.
Abbildung 10.3: Der Aufbau des im Text definierten Arrays von Strukturen.
Wenn Sie das Array für die Strukturvariablen deklariert haben, können Sie die Daten in vielfältiger Weise manipulieren. Um zum Beispiel die Daten in einem Array- Element einem anderen Array-Element zuzuweisen, können Sie schreiben:
liste[1] = liste[5];
Diese Anweisung weist jedem Element der Struktur liste[1]
die Werte der
entsprechenden Elemente von liste[5]
zu. Sie können aber auch Daten zwischen
den einzelnen Strukturelementen hin- und herschieben. Die Anweisung
strcpy(liste[1].telefon, liste[5].telefon);
kopiert den String in list[5].telefon
in liste[1].telefon
. (Die Bibliotheksfunktion
strcpy()
kopiert einen String in einen anderen String. Näheres dazu erfahren Sie am
Tag 16, »Stringmanipulation«.) Wenn Sie wollen, können Sie sogar Daten zwischen
den einzelnen Element-Arrays der Strukturelemente verschieben:
liste[5].telefon[1] = liste[2].telefon[3];
Diese Anweisung verschiebt das zweite Zeichen der Telefonnummer in liste[5]
an
die vierte Position der Telefonnummer in liste[2]
. (Zur Erinnerung, die Indexzählung
beginnt mit der 0
.)
Listing 10.3 demonstriert die Verwendung von Arrays von Strukturen (sogar von Arrays von Strukturen, die selbst wieder Arrays als Elemente enthalten).
Listing 10.3: Arrays von Strukturen.
1: /* Beispiel für Arrays von Strukturen. */
2:
3: #include <stdio.h>
4:
5: /* Definiert eine Struktur zur Aufnahme von Einträgen. */
6:
7: struct eintrag {
8: char vname[20];
9: char nname[20];
10: char telefon[10];
11: };
12:
13: /* Deklariert ein Array von Strukturen. */
14:
15: struct eintrag liste[4];
16:
17: int i;
18:
19: int main(void)
20: {
21:
22: /* Durchläuft eine Schleife für die Eingabe der Daten von 4
23: Personen. */
24: for (i = 0; i < 4; i++)
25: {
26: printf("\nBitte Vornamen eingeben: ");
27: scanf("%s", liste[i].vname);
28: printf("Bitte Nachnamen eingeben: ");
29: scanf("%s", liste[i].nname);
30: printf("Bitte Telefonnummer im Format 123-4567 eingeben: ");
31: scanf("%s", liste[i].telefon);
32: }
33:
34: /* Zwei leere Zeilen ausgeben. */
35:
36: printf("\n\n");
37:
38: /* Durchläuft eine Schleife zur Anzeige der Daten. */
39:
40: for (i = 0; i < 4; i++)
41: {
42: printf("Name: %s %s", liste[i].vname, liste[i].nname);
43: printf("\t\tTelefon: %s\n", liste[i].telefon);
44: }
45:
46: return 0;
47: }
Bitte Vornamen eingeben: Bradley
Bitte Nachnamen eingeben: Jones
Bitte Telefonnummer im Format 123-4567 eingeben: 555-1212
Bitte Vornamen eingeben: Peter
Bitte Nachnamen eingeben: Aitken
Bitte Telefonnummer im Format 123-4567 eingeben: 555-3434
Bitte Vornamen eingeben: Melissa
Bitte Nachnamen eingeben: Jones
Bitte Telefonnummer im Format 123-4567 eingeben: 555-1212
Bitte Vornamen eingeben: Deanna
Bitte Nachnamen eingeben: Townsend
Bitte Telefonnummer im Format 123-4567 eingeben: 555-1234
Name: Bradley Jones Telefon: 555-1212
Name: Peter Aitken Telefon: 555-3434
Name: Melissa Jones Telefon: 555-1212
Name: Deanna Townsend Telefon: 555-1234
Dieses Listing hat den gleichen allgemeinen Aufbau wie die vorangehenden Listings.
Es beginnt mit dem Kommentar in Zeile 1, gefolgt von der Einbindung der Header-
Datei stdio.h
für die Ein-/Ausgabefunktionen (#include
in Zeile 3). In den Zeilen 7 bis
11 wird eine Strukturschablone namens eintrag
definiert, die drei Zeichenarrays
enthält: vname
, nname
und telefon
. Zeile 15 verwendet diese Schablone, um ein Array
liste
für vier Strukturvariablen vom Typ eintrag
zu definieren. Zeile 17 definiert eine
Variable vom Typ int
, die im weiteren Verlauf des Programms als Zähler dient. In
Zeile 19 beginnt main()
. Die erste Aufgabe von main()
besteht darin, eine for
-Schleife
viermal zu durchlaufen, um das Array der Strukturelemente mit Informationen zu
»füttern«. Diese Schleife befindet sich in den Zeilen 24 bis 32. Beachten Sie, dass sich
die Indizierung von liste
nicht von der Indizierung der Array-Variablen von Tag 7,
»Numerische Arrays«, unterscheidet.
Zeile 36 schafft einen Abstand zwischen den Eingabeaufforderungen und der
Ausgabe. Die Zeile gibt zwei Zeilenumbrüche aus, was Ihnen sicherlich nicht neu sein
dürfte. Die Zeilen 40 bis 44 geben die Daten aus, die der Anwender in dem
vorherigen Schritt eingegeben hat. Die Werte in dem Array der Strukturelemente
werden über den indizierten Array-Namen gefolgt von dem Elementoperator (.
) und
dem Namen des Strukturelements ausgegeben.
Machen Sie sich mit den Techniken aus Listing 10.3 vertraut. Viele reale Programmieraufgaben lassen sich mit Arrays von Strukturen (deren Elemente wiederum Arrays sein können) am besten lösen.
Strukturen können wie andere Variablentypen auch bei ihrer Deklaration initialisiert werden. Die Verfahrensweise ähnelt der zum Initialisieren von Arrays. Die Strukturdeklaration wird gefolgt von einem Gleichheitszeichen und einer Liste von initialisierten Werten, die - durch Kommata getrennt - in geschweiften Klammern stehen. Betrachten Sie dazu folgende Anweisungen:
1: struct verkauf {
2: char kunde[20];
3: char artikel[20];
4: float betrag;
5: } meinverkauf = { "Acme Industries",
6: "Einspritzpumpe",
7: 1000.00
8: };
Bei Ausführung dieser Anweisungen werden folgende Aktionen ausgelöst:
verkauf
definiert (Zeilen 1 bis 5).
verkauf
namens meinverkauf
deklariert (Zeile
5).
meinverkauf.kunde
mit dem String »Acme
Industries«
initialisiert (Zeile 5).
meinverkauf.artikel
mit dem String
»Einspritzpumpe«
initialisiert (Zeile 6).
meinverkauf.betrag
mit dem Wert 1000.00
initialisiert
(Zeile 7).
Für eine Struktur, die Strukturen als Elemente enthält, werden die Strukturelemente in der Reihenfolge initialisiert, in der sie in der Strukturdefinition aufgelistet wurden. Sehen Sie dazu eine Erweiterung des oberen Beispiels:
1: struct kunde {
2: char firma[20];
3: char kontakt[25];
4: }
5:
6: struct verkauf {
7: struct kunde kaeufer;
8: char artikel[20];
9: float betrag;
10: } meinverkauf = { { "Acme Industries", "George Adams"},
11: "Einspritzpumpe",
12: 1000.00
13: };
Mit diesen Anweisungen werden folgende Initialisierungen vorgenommen:
meinverkauf.kaeufer.firma
wird mit dem String »Acme
Industries«
initialisiert (Zeile 10).
meinverkauf.kaeufer.kontakt
wird mit dem String »George
Adams«
initialisiert (Zeile 10).
meinverkauf.artikel
wird mit dem String »Einspritzpumpe«
initialisiert (Zeile 11).
meinverkauf.betrag
wird mit dem Betrag 1000.00
initialisiert
(Zeile 12).
Sie können auch Arrays von Strukturen initialisieren. Die von Ihnen angegebenen
Initialisierungsdaten werden dabei der Reihenfolge nach auf die Strukturen im Array
angewendet. Um zum Beispiel ein Array von Strukturen vom Typ verkauf
zu
deklarieren und die ersten zwei Array-Elemente (das heißt die ersten zwei Strukturen)
zu initialisieren, würden Sie schreiben:
1: struct kunde {
2: char firma[20];
3: char kontakt[25];
4: };
5:
6: struct verkauf {
7: struct kunde kaeufer;
8: char artikel[20];
9: float betrag;
10: };
11:
12:
13: struct verkauf j1990[100] = {
14: { { "Acme Industries", "George Adams"},
15: "Einspritzpumpe",
16: 1000.00
17: },
18: { { "Wilson & Co.", "Ed Wilson"},
19: "Typ 12",
20: 290.00
21: }
22: };
Folgendes passiert in diesem Code:
j1990[0].kaeufer.firma
wird mit dem String »Acme
Industries«
initialisiert (Zeile 14).
j1990[0].kaeufer.kontakt
wird mit dem String »George
Adams«
initialisiert (Zeile 14).
j1990[0].artikel
wird mit dem String »Einspritzpumpe«
initialisiert (Zeile 15).
j1990[0].betrag
wird mit dem Betrag 1000.00
initialisiert
(Zeile 16).
j1990[1].kaeufer.firma
wird mit dem String »Wilson & Co.«
initialisiert (Zeile 18).
j1990[1].kaeufer.kontakt
wird mit dem String »Ed Wilson«
initialisiert (Zeile 18).
j1990[1].artikel
wird mit dem String »Typ 12«
initialisiert
(Zeile 19).
j1990[1].betrag
wird mit dem Betrag 290.00
initialisiert
(Zeile 20).
Angesichts der Tatsache, dass Zeiger in C eine wichtige Stellung einnehmen, sollte es Sie nicht überraschen, dass man sie auch zusammen mit Strukturen verwenden kann. Sie können Zeiger sowohl als Strukturelemente verwenden als auch als Zeiger auf Strukturen deklarieren. Diese beiden Anwendungsbereiche sind Thema der folgenden Abschnitte.
Was die Verwendung von Zeigern als Strukturelemente angeht, haben Sie sämtliche
Freiheiten. Zeigerelemente werden genauso deklariert wie normale Zeiger, die nicht
Elemente von Strukturen sind - und zwar mit Hilfe des Indirektionsoperators (*
).
Sehen Sie dazu ein Beispiel:
struct daten {
int *wert;
int *rate;
} erste;
Diese Anweisungen definieren und deklarieren eine Struktur, deren beide Elemente
Zeiger auf den Datentyp int
sind. Wie bei anderen Zeigern auch reicht die
Deklaration allein nicht aus. Sie müssen diese Zeiger auch initialisieren, damit sie auf
etwas zeigen. Denken Sie daran, dass Sie ihnen dazu auch die Adresse einer
Variablen zuweisen können. Wenn Sie bereits kosten
und zinsen
als Variablen vom
Typ int
deklariert haben, könnten Sie schreiben:
erste.wert = &kosten;
erste.rate = &zinsen;
Nachdem die Zeiger initialisiert sind, können Sie den Indirektionsoperator (*
), wie am
Tag 8 erläutert, verwenden, um auf die referenzierten Werte zuzugreifen. Der
Ausdruck *erste.wert
greift auf den Wert von kosten
zu und der Ausdruck
*erste.rate
auf den Wert von zinsen
.
Der wahrscheinlich am häufigsten für Strukturelemente verwendete Zeigertyp ist ein
Zeiger auf den Typ char
. Zur Erinnerung: Am Tag 9, »Zeichen und Strings«, haben
Sie gelernt, dass ein String eine Folge von Zeichen ist, die durch einen Zeiger auf das
erste Zeichen des Strings und einem Nullzeichen für das Stringende genau umrissen
ist. Frischen wir Ihr Gedächtnis noch ein wenig weiter auf. Um einen Zeiger auf char
zu deklarieren und auf einen String zu richten, schreiben Sie:
char *z_nachricht;
z_nachricht = "C-Programmierung in 21 Tagen";
Und genauso können Sie auch mit Zeigern auf den Typ char
verfahren; die
Strukturelemente sind:
struct nrt {
char *z1;
char *z2;
} meinezgr;
meinezgr.z1 = "C-Programmierung in 21 Tagen";
meinezgr.z2 = "Markt & Technik-Verlag";
Abbildung 10.4 illustriert das Ergebnis dieser Anweisungen. Jedes Zeigerelement der
Struktur zeigt auf das erste Byte eines Strings, der irgendwo im Speicher abgelegt ist.
Vergleichen Sie dies mit Abbildung 10.2, wo gezeigt wurde, wie Daten in einer
Struktur gespeichert werden, die Arrays vom Typ char
enthält.
Abbildung 10.4: Eine Struktur mit Zeigern auf char.
Sie können Strukturelement-Zeiger überall dort verwenden, wo Sie auch einen normalen Zeiger verwenden würden. Um zum Beispiel den String, auf den gezeigt wird, auszugeben, würden Sie schreiben:
printf("%s %s", meinezgr.z1, meinezgr.z2);
Was ist der Unterschied zwischen einem Strukturelement, das als Array vom Typ char
beziehungsweise als Zeiger auf den Typ char
deklariert ist? Beide Deklarationen
stellen eine Möglichkeit dar, wie man Strings in Strukturen »speichern« kann. Die
nachfolgend definierte Struktur nrt
demonstriert beide Methoden:
struct nrt {
char z1[30];
char *z2; /* Achtung: nicht initialisiert */
} meinezgr;
Denken Sie daran, dass ein Array-Name ohne eckige Klammern ein Zeiger auf das
erste Array-Element darstellt. Deshalb können Sie diese zwei Strukturelemente in
gleicher Weise verwenden (beachten Sie, dass z2
initialisiert sein sollte, bevor Sie
einen Wert in z2
kopieren):
strcpy(meinezgr.z1, "C-Programmierung in 21 Tagen");
strcpy(meinezgr.z2, "Markt & Technik-Verlag");
/* hier steht sonstiger Code */
puts(meinezgr.z1);
puts(meinezgr.z2);
Worin aber unterscheiden sich diese beiden Methoden? Der Unterschied ist folgender:
Wenn Sie eine Struktur definieren, die ein Array vom Typ char
enthält, belegt jede
Instanz dieses Strukturtyps einen Speicherbereich für ein Array der angegebenen
Größe. Außerdem sind Sie auf die angegebene Größe beschränkt. Sie können keinen
größeren String in der Struktur speichern. Sehen Sie dazu folgendes Beispiel:
struct nrt {
char z1[10];
char z2[10];
} meinezgr;
...
strcpy(z1, "Minneapolis"); /* Falsch! String länger als Array. */
strcpy(z2, "MN"); /* OK, aber verschwendet Speicherplatz, */
/* da der String kürzer als das Array ist. */
Wenn Sie dagegen eine Struktur definieren, die Zeiger auf den Typ char
enthält,
gelten diese Beschränkungen nicht mehr. Die einzelnen Instanzen der Struktur
belegen lediglich Speicherbereich für die Zeiger. Die eigentlichen Strings sind
irgendwo sonst im Speicher abgelegt (wo genau, braucht Sie nicht zu kümmern). Es
gibt weder Längenbeschränkungen noch verschwendeten Speicherplatz. Die
eigentlichen Strings werden nicht als Teil der Struktur gespeichert. Jeder Zeiger in der
Struktur kann auf einen String einer beliebigen Länge zeigen. Damit wird der String
Teil der Struktur, ohne in der Struktur selbst gespeichert zu sein.
Wenn Sie die Zeiger nicht initialisieren, laufen Sie Gefahr, unabsichtlich Speicher zu überschreiben, der bereits anderweitig verwendet wird. Wenn Sie einen Zeiger statt eines Arrays verwenden, müssen Sie vor allem daran denken, den Zeiger zu initialisieren. Dazu können Sie dem Zeiger die Adresse einer anderen Variablen zuweisen oder dynamisch Speicher für den Zeiger reservieren.
Zeiger auf Strukturen werden genauso deklariert und verwendet wie Zeiger auf jeden anderen Datentyp. Weiter hinten in dieser Lektion werden Sie lernen, dass Zeiger auf Strukturen vornehmlich dann verwendet werden, wenn Strukturen als Argumente an Funktionen übergeben werden sollen. Zeiger auf Strukturen werden darüber hinaus auch in einem sehr mächtigen Konstrukt zur Datenspeicherung, den so genannten verketteten Listen, eingesetzt. Verkettete Listen werden am Tag 14, »Zeiger für Fortgeschrittene«, genauer beschrieben.
An dieser Stelle wollen wir uns anschauen, wie Sie in einem Programm Zeiger auf Strukturen erzeugen und verwenden. Dazu müssen Sie zuerst eine Struktur definieren:
struct teil {
int zahl;
char name[10];
};
Deklarieren Sie jetzt einen Zeiger auf den Typ teil
:
struct teil *z_teil;
Denken Sie daran, dass der Indirektionsoperator (*
) in der Deklaration besagt, dass
z_teil
ein Zeiger auf den Typ teil
und keine Instanz des Typs teil
ist.
Kann der Zeiger jetzt initialisiert werden? Nein, denn auch wenn die Struktur teil
bereits definiert ist, wurden noch keine Instanzen der Struktur deklariert. Denken Sie
daran, dass Speicherplatz für Datenobjekte nur durch eine Deklaration und nicht durch
eine Definition reserviert werden kann. Da ein Zeiger eine Speicheradresse benötigt,
auf die er zeigen kann, müssen Sie zuerst eine Instanz vom Typ teil
deklarieren, auf die
Sie verweisen können. Die Deklaration der Strukturvariablen sieht folgendermaßen aus:
struct teil gizmo;
Jetzt können Sie den Zeiger initialisieren:
z_teil = &gizmo;
Diese Anweisung weist dem Zeiger z_teil
die Adresse von gizmo
zu. (Vergessen Sie
dabei nicht den Adressoperator &
.) Abbildung 10.5 veranschaulicht die Beziehung
zwischen einer Struktur und einem Zeiger auf eine Struktur.
Abbildung 10.5: Ein Zeiger auf eine Struktur zeigt auf das erste Byte der Struktur.
Was machen Sie jetzt mit diesem Zeiger auf die Struktur gizmo
? Eine Möglichkeit
bestünde darin, den Indirektionsoperator (*
) zu verwenden. Wenn zgr
ein Zeiger auf
ein Datenobjekt ist, dann bezieht sich der Ausdruck *zgr
auf das Objekt, auf das
gezeigt wird.
Übertragen wir dies auf unser Beispiel. Wir wissen, dass z_teil
ein Zeiger auf die
Strukturvariable gizmo
ist. Demzufolge verweist *z_teil
auf gizmo
. Mit dem
Punktoperator (.
) können Sie auf die einzelnen Elemente von gizmo
zugreifen. Um
gizmo.zahl
den Wert 100
zuzuweisen, könnten Sie schreiben:
(*z_teil).zahl = 100;
*z_teil
muss in Klammern stehen, da der Punktoperator (.
) eine höhere Priorität hat
als der Indirektionsoperator (*
).
Der zweite Weg, um über einen Zeiger auf eine Struktur auf die Elemente der Struktur
zuzugreifen, verwendet den Elementverweis-Operator, der aus den Zeichen ->
(einem Bindestrich gefolgt von einem Größer-Zeichen) besteht. Beachten Sie, dass
diese Zeichen in der Kombination als ein einziger Operator und nicht als zwei
Operatoren betrachtet werden.) Dieses Symbol wird zwischen den Zeigernamen und
den Elementnamen gesetzt. Um zum Beispiel mit Hilfe des Zeigers z_teil
auf das
gizmo
-Element zahl
zuzugreifen, schreiben Sie:
z_teil->zahl
Betrachten wir ein weiteres Beispiel: Wenn str
eine Struktur, z_str
ein Zeiger auf str
und elem
ein Element von str
ist, können Sie auf str.elem
auch auf folgendem Wege
zugreifen:
z_str->elem
Demzufolge gibt es drei Wege, um auf die Elemente einer Struktur zuzugreifen:
*
)
->
)
Wenn z_str
ein Zeiger auf die Struktur str
ist, das sind die folgenden drei Ausdrücke
alle äquivalent:
str.elem
(*z_str).elem
z_str->elem
Sie haben gesehen, dass Arrays von Strukturen genauso wie Zeiger auf Strukturen sehr mächtige Programmkonstrukte darstellen. Sie können beide Konstrukte auch kombinieren und mit Zeigern auf Strukturen zugreifen, die Elemente eines Arrays sind.
Zur Veranschaulichung erst einmal die Definition einer Struktur aus einem früheren Beispiel:
struct teil {
int zahl;
char name[10];
};
Nachdem die Struktur teil
definiert ist, können Sie ein Array mit Elementen vom Typ
teil
deklarieren:
struct teil daten[100];
Als Nächstes können Sie einen Zeiger auf eine Variable vom Typ teil
deklarieren und
so initialisieren, dass er auf die erste Struktur im Array daten
zeigt:
struct teil *z_teil;
z_teil = &daten[0];
Denken Sie daran, dass der Name eines Arrays ohne eckige Klammern ein Zeiger auf das erste Array-Element ist, so dass die zweite Zeile auch folgendermaßen hätte geschrieben werden können:
z_teil = daten;
Damit haben Sie jetzt ein Array mit Strukturelementen des Typs teil
und einen
Zeiger auf das erste Array-Element (das heißt, die erste Struktur in dem Array). Sie
könnten jetzt zum Beispiel den Inhalt des ersten Elements mit der folgenden
Anweisung ausgeben:
printf("%d %s", z_teil->zahl, z_teil->name);
Und wie gehen Sie vor, wenn Sie alle Array-Elemente ausgeben wollen?
Wahrscheinlich würden Sie eine for
-Schleife verwenden und bei jedem Durchgang
der Schleife ein Array-Element ausgeben. Um mit Hilfe der Zeigernotation auf die
Elemente zuzugreifen, müssen Sie den Zeiger z_teil
so inkrementieren, dass er bei
jedem Durchlauf der Schleife auf das nächste Array-Element zeigt (das heißt, die
nächste Struktur in dem Array). Die Frage ist nur, wie?
Abhilfe schafft in diesem Falle die Zeigerarithmetik von C. Der unäre
Inkrementoperator (++
) hat in Kombination mit einem Zeiger - dank der Regeln der
Zeigerarithmetik - eine besondere Bedeutung. Diese besagt: »Inkrementiere den
Zeiger um die Größe des Objekts, auf das der Zeiger zeigt.« Anders ausgedrückt:
Wenn Sie einen Zeiger zgr
haben, der auf ein Datenobjekt vom Typ obj
zeigt, hat die
Anweisung
zgr++;
zgr += sizeof(obj);
Dieser Aspekt der Zeigerarithmetik ist besonders für Arrays wichtig, da Array-
Elemente sequentiell im Speicher abgelegt werden. Wenn ein Zeiger auf das Array-
Element n
zeigt, dann zeigt der Zeiger nach der Inkrementierung mittels des (++
)-
Operators auf das Element n+1
. Schauen Sie sich dazu Abbildung 10.6 an, in der ein
Array namens x[]
zu sehen ist, das aus Elementen besteht, die 4 Byte lang sind
(beispielsweise Strukturen, die zwei Elemente vom Typ short
, der üblicherweise 2
Byte umfasst, enthalten). Nach der Initialisierung zeigt der Zeiger zgr
auf x[0]
und
nach jeder Inkrementierung auf das jeweils nächste Array-Element.
Abbildung 10.6: Mit jeder Inkrementierung rückt der Zeiger um ein Array-Element weiter.
Dies bedeutet, dass Ihr Programm ein Array von Strukturen (oder ein Array eines beliebigen anderen Datentyps) durchlaufen kann, indem es einen Zeiger inkrementiert. Diese Art der Notation ist normalerweise leichter und kürzer als die Verwendung von Array-Indizes für die gleiche Aufgabe. Listing 10.4 demonstriert, wie das geht.
Listing 10.4: Zugriff auf sequentielle Array-Elemente durch Inkrementierung eines Zeigers.
1 : /* Beispiel für den Durchlauf eines Arrays von Strukturen */
2 : /* mit Hilfe der Zeiger-Notation. */
3 :
4 : #include <stdio.h>
5 :
6 : #define MAX 4
7 :
8 : /* Definiert eine Struktur und deklariert und initialisiert */
9 : /* dann ein Array mit 4 Elementen der Strukturen. */
10:
11: struct teil {
12: int zahl;
13: char name[10];
14: } daten[MAX] = { {1, "Schmidt"},
15: {2, "Meier"},
16: {3, "Adams"},
17: {4, "Walter"}
18: };
19:
20: /* Deklariert einen Zeiger auf den Typ teil und eine Zähler-Variable. */
21:
22: struct teil *z_teil;
23: int count;
24:
25: int main()
26: {
27: /* Initialisiert den Zeiger mit dem ersten Array-Element. */
28:
29: z_teil = daten;
30:
31: /* Durchläuft das Array und inkrementiert den Zeiger */
32: /* mit jeder Iteration */
33:
34: for (count = 0; count < MAX; count++)
35: {
36: printf("An Adresse %lu: %d %s\n",
37: (unsigned long)z_teil, z_teil->zahl,
38: z_teil->name);
39: z_teil++;
40: }
41:
42: return 0;
43: }
An Adresse 134517984: 1 Brand
An Adresse 134518000: 2 Meier
An Adresse 134518016: 3 Adams
An Adresse 134518032: 4 Walter
Zuerst deklariert und initialisiert dieses Programm ein Array von teil
-Strukturen, das
den Namen daten
erhält (Zeilen 11 bis 18). Achten Sie darauf, dass jede Struktur in
der Array-Initialisierung von einem Paar geschweifter Klammern umschlossen sein
muss. Danach wird in Zeile 22 ein Zeiger namens z_teil
definiert, der auf die
Struktur daten
zeigen soll. Die erste Aufgabe der Funktion main()
besteht darin, in
Zeile 29 den Zeiger z_teil
auf das erste teil
-Strukturelement in daten
zu setzen.
Anschließend werden mit Hilfe der for
-Schleife in den Zeilen 34 bis 39 alle Elemente
ausgegeben, indem der Zeiger auf das Array bei jedem Durchlauf inkrementiert wird.
Gleichzeitig gibt das Programm zu jedem Element die Adresse aus.
Schauen Sie sich die Adressen einmal genauer an. Die tatsächlichen Werte werden
auf Ihrem System anders aussehen, aber die Inkrementierung sollte in der gleichen
Schrittweite erfolgen. In diesem Fall besteht die Struktur aus einem int
(4 Byte) und
einem Array von zehn Zeichen namens char
, was zusammen 14 Byte macht. Wo
kommen die zusätzlichen 2 Byte her? Der C-Compiler versucht den Speicher so
aufzuteilen, dass die CPU schnell und möglichst effizient auf die Daten zugreifen kann.
Die CPU bevorzugt für die Speicherung von int
-Variablen von 4 Byte Länge
Speicheradressen, die durch vier teilbar sind. Deshalb werden zwischen Strukturen
von 14 Byte zwei Füllbyte untergebracht, so dass die nachfolgenden Strukturelemente
wieder an 4-Byte-Grenzen ausgerichtet sind.
Wie Variablen anderer Datentypen kann man auch Strukturen als
Funktionsargumente übergeben. Listing 10.5 zeigt Ihnen, wie das geht. Das
Programm ist eine Neufassung des Programms aus Listing 10.2. In Listing 10.5
werden die Daten mit Hilfe einer eigenen Funktion auf den Bildschirm ausgegeben,
während in Listing 10.2 dazu Anweisungen verwendet werden, die Teil von main()
sind.
Listing 10.5: Eine Struktur als Funktionsargument übergeben.
1: /* Beispiel für die Übergabe einer Struktur als Argument. */
2:
3: #include <stdio.h>
4:
5: /* Deklariert und definiert eine Struktur zur Aufnahme der Daten. */
6:
7: struct daten {
8: float betrag;
9: char vname[30];
10: char nname[30];
11: } rec;
12:
13: /* Der Funktionsprototyp. Die Funktion hat keinen Rückgabewert */
14: /* und übernimmt eine Struktur vom Typ daten als einziges Argument. */
15:
16: void ausgabe(struct daten x);
17:
18: int main(void)
19: {
20: /* Eingabe der Daten über die Tastatur. */
21:
22: printf("Geben Sie den Vor- und Nachnamen des Spenders,\n");
23: printf("getrennt durch ein Leerzeichen, ein: ");
24: scanf("%s %s", rec.vname, rec.nname);
25:
26: printf("\nGeben Sie die Höhe der Spende ein: ");
27: scanf("%f", &rec.betrag);
28:
29: /* Aufruf der Funktion zur Ausgabe. */
30: ausgabe( rec );
31:
32: return 0;
33: }
34: void ausgabe(struct daten x)
35: {
36: printf("\nSpender %s %s gab %.2f DM.\n", x.vname, x.nname,
37: x.betrag);
38: }
Geben Sie den Vor- und Nachnamen des Spenders,
getrennt durch ein Leerzeichen, ein: Bradley Jones
Geben Sie die Höhe der Spende ein: 1000.00
Spender Bradley Jones gab 1000.00 DM.
In Zeile 16 finden Sie den Funktionsprototyp für die Funktion, die die Struktur als
Argument übernehmen soll. Wie bei allen anderen Datentypen auch müssen Sie die
korrekten Parameter deklarieren. In diesem Fall ist es eine Struktur des Typs daten
.
Zeile 34 wiederholt dies im Funktionsheader. Wenn Sie die Funktion aufrufen,
müssen Sie nur den Namen der Strukturinstanz - hier rec
(Zeile 30) - übergeben. Das
ist schon alles. Die Übergabe einer Struktur an eine Funktion unterscheidet sich nicht
sehr von der einer einfachen Variablen.
Sie können eine Struktur einer Funktion auch übergeben, indem Sie die Adresse der
Struktur übergeben (das heißt einen Zeiger auf die Struktur). Tatsächlich war dies in
älteren Versionen von C die einzige Möglichkeit, um eine Struktur als Argument zu
übergeben. Heutzutage ist das nicht mehr nötig, aber Sie können immer noch auf
ältere Programme stoßen, die diese Methode verwenden. Wenn Sie einen Zeiger auf
eine Struktur als Argument übergeben, müssen Sie allerdings daran denken, den
Elementverweis-Operator (->
) zu verwenden, um auf die Elemente der Struktur in der
Funktion zuzugreifen.
Unions sind den Strukturen sehr ähnlich. Eine Union wird genauso deklariert und verwendet wie eine Struktur. Der einzige Unterschied liegt darin, dass in einer Union immer nur eines der deklarierten Elemente zur Zeit verwendet werden kann. Der Grund dafür ist einfach. Alle Elemente einer Union belegen den gleichen Speicherbereich - sie liegen quasi übereinander.
Unions werden auf die gleiche Art und Weise definiert und deklariert wie Strukturen.
Der einzige Unterschied in den Deklarationen besteht darin, dass anstelle des
Schlüsselwortes struct
das Schlüsselwort union
verwendet wird. Um eine einfache
Union zu definieren, die eine char
-Variable und eine Integer-Variable enthält, würden
Sie schreiben:
union shared {
char c;
int i;
};
Diese Union namens shared
kann dazu verwendet werden, Instanzen einer Union zu
erzeugen, die entweder einen Zeichenwert c
oder einen Integerwert i
aufnehmen.
Dabei handelt es sich um eine ODER-Bedingung. Im Gegensatz zu einer Struktur, die
beide Werte aufnehmen könnte, kann die Union nur einen Wert zur Zeit enthalten.
Abbildung 10.7 demonstriert, wie die Union shared
im Speicher angelegt würde.
Abbildung 10.7: Eine Union kann nur einen Wert zur Zeit enthalten.
Unions können bei ihrer Deklaration initialisiert werden. Da nur immer eines ihrer
Elemente zur Zeit verwendet werden kann, kann auch nur eines initialisiert werden.
Um Verwirrung zu vermeiden, kann nur das erste Element der Union initialisiert
werden. Der folgende Code zeigt die Deklaration und Initialisierung einer Instanz der
Union shared
:
union shared generische_variable = {'@'};
Beachten Sie, dass die Union generische_variable
genauso initialisiert wurde wie das
erste Element einer Struktur.
Einzelne Unionelemente können genauso verwendet werden wie die Elemente einer
Struktur - mit Hilfe des Punktoperators (.
). Beim Zugriff auf die Unionelemente gibt
es jedoch einen wichtigen Unterschied: Da eine Union ihre Elemente übereinander
speichert, ist es wichtig, immer nur auf ein Element zur Zeit zuzugreifen.
Listing 10.6: Ein Beispiel für die falsche Anwendung von Unions.
1: /* Beispiel für den Zugriff auf mehr als ein Unionelement zur Zeit */
2: #include <stdio.h>
3:
4: int main(void)
5: {
6: union u_shared {
7: char c;
8: int i;
9: long l;
10: float f;
11: double d;
12: } shared;
13:
14: shared.c = '$';
15:
16: printf("\nchar c = %c", shared.c);
17: printf("\nint i = %d", shared.i);
18: printf("\nlong l = %ld", shared.l);
19: printf("\nfloat f = %f", shared.f);
20: printf("\ndouble d = %f", shared.d);
21:
22: shared.d = 123456789.8765;
23:
24: printf("\n\nchar c = %c", shared.c);
25: printf("\nint i = %d", shared.i);
26: printf("\nlong l = %ld", shared.l);
27: printf("\nfloat f = %f", shared.f);
28: printf("\ndouble d = %f\n", shared.d);
29:
30: return 0;
31: }
char c = $
int i = 36
long l = 36
float f = 0.000000
double d = -1.998047
char c = 7
int i = 1468107063
long l = 1468107063
float f = 284852666499072.000000
double d = 123456789.876500
In diesem Listing sehen Sie wie in den Zeilen 6 bis 12 eine Union definiert und eine
Unionvariable namens shared
deklariert wird. shared
enthält fünf Elemente von
jeweils unterschiedlichem Datentyp. Die Zeilen 14 und 22 weisen verschiedenen
Elementen von shared
Werte zu. Die Zeilen 16 bis 20 und 24 bis 28 geben dann die
Werte der einzelnen Elemente mit printf()
-Anweisungen aus.
Beachten Sie, dass mit Ausnahme von char c = $
und double d = 123456789.876500
die Ausgabe auf Ihrem Computer nicht gleichlautend sein muss. Da die
Zeichenvariable c
in Zeile 14 initialisiert wurde, ist dies der einzige Wert, der
verwendet werden sollte, bis ein anderes Element initialisiert wird. Die Ausgabe der
anderen Unionelement-Variablen (i
, l
, f
und d
) kann zu unvorhersehbaren
Ergebnissen führen (Zeile 16 bis 20). Zeile 22 legt einen Wert in der double
-Variablen
d
ab. Beachten Sie, dass auch hier wieder die Ausgabe der anderen Unionelemente zu
unvorhersehbaren Ergebnissen führen kann. Der Wert, mit dem c
in Zeile 14
initialisiert wurde, ist jetzt verloren, da er mit der Zuweisung an d
überschrieben
wurde (Zeile 22). Dies beweist, dass die Elemente alle den gleichen Speicherplatz
belegen.
union name {
union_element(e);
/* hier stehen weitere Anweisungen */
}instanz;
Unions werden mit dem Schlüsselwort union
deklariert. Eine Union ist eine Sammlung
von einer oder mehreren Variablen (union_elemente
), die unter einem Namen
zusammengefasst wurden. Darüber hinaus belegen alle Unionelemente den gleichen
Speicherplatz.
Das Schlüsselwort union
markiert den Anfang einer Uniondefinition. Es wird gefolgt
von dem Namen der Union. Auf den Namen folgen in geschweiften Klammern die
Unionelemente. Gleichzeitig kann eine Instanz, eine Unionvariable, deklariert
werden. Wenn Sie eine Union ohne eine Instanz definieren, erhalten Sie nur eine
Schablone, die Sie später in einem Programm verwenden können, um
Unionvariablen zu deklarieren. Das Format einer Schablone sieht folgendermaßen
aus:
union name {
union_element(e);
/* stehen weitere Anweisungen. */
};
Um auf der Grundlage der Schablone Instanzen zu deklarieren, verwenden Sie folgende Syntax:
union name instanz;
Dies Format setzt allerdings voraus, dass Sie bereits vorher eine Union mit dem angegebenen Namen definiert haben.
/* Deklariert eine Unionschablone namens meine_union. */
union meine_union {
int nbr;
char zeichen;
};
/* Nutzen der Unionschablone */
union meine_union beliebig_variable;
/* Deklariert eine Union zusammen mit einer Instanz. */
union generischer_typ {
char c;
int i;
float f;
double d;
} generisch;
/* Initialisiert eine Union. */
union u_datum {
char volles_datum[9];
struct s_datum {
char monat[2];
char trennwert1;
char tag[2];
char trennwert2;
char jahr[2];
} teil_datum;
}datum = {"01.01.00"};
Listing 10.7 gibt Ihnen ein etwas praxisnäheres Beispiel für den Einsatz einer Union. Das Beispiel ist zwar noch sehr einfach, beschreibt aber einen typischen Einsatzbereich einer Union.
Listing 10.7: Praxisnaher Einsatz einer Union.
1: /* Typische Verwendung einer Union. */
2:
3: #include <stdio.h>
4:
5: #define CHARACTER 'C'
6: #define INTEGER 'I'
7: #define FLOAT 'F'
8:
9: struct s_generisch{
10: char typ;
11: union u_shared {
12: char c;
13: int i;
14: float f;
15: } shared;
16: };
17:
18: void ausgabe( struct s_generisch generisch );
19:
20: int main(void)
21: {
22: struct s_generisch var;
23:
24: var.typ = CHARACTER;
25: var.shared.c = '$';
26: ausgabe( var );
27:
28: var.typ = FLOAT;
29: var.shared.f = (float) 12345.67890;
30: ausgabe( var );
31:
32: var.typ = 'x';
33: var.shared.i = 111;
34: ausgabe( var );
35: return 0;
36: }
37: void ausgabe( struct s_generisch generisch )
38: {
39: printf("\nDer generische Wert ist...");
40: switch( generisch.typ )
41: {
42: case CHARACTER: printf("%c\n", generisch.shared.c);
43: break;
44: case INTEGER: printf("%d\n", generisch.shared.i);
45: break;
46: case FLOAT: printf("%f\n", generisch.shared.f);
47: break;
48: default: printf("Typ unbekannt: %c\n",
49: generisch.typ);
50: break;
51: }
52: }
Der generische Wert ist...$
Der generische Wert ist...12345.678711
Der generische Wert ist...Typ unbekannt: x
Dieses Programm gibt ein einfaches Beispiel für den Einsatz einer Union. Das
Programm ist in der Lage, Variablen unterschiedlicher Datentypen an einer
Speicherstelle abzulegen. Die Struktur s_generisch
ermöglicht es, ein Zeichen, einen
Integer oder eine Fließkommazahl in dem gleichen Bereich zu speichern. Dieser
Bereich ist eine Union, die den Namen u_shared
trägt und sich genauso verhält wie die
Union in Listing 10.6. Beachten Sie, dass die Struktur s_generisch
ein zusätzliches
Element namens typ
deklariert. Dieses Element wird verwendet, um Informationen zu
dem Typ der in u_shared
enthaltenen Variablen aufzunehmen. Mit typ
wird verhindert,
dass shared
falsch verwendet wird und fehlerhafte Daten wie in Listing 10.6
ausgegeben werden.
Ein kurzer Blick auf das Programm zeigt, dass in den Zeilen 5, 6 und 7 die Konstanten
CHARACTER
, INTEGER
und FLOAT
definiert werden. Diese sollen später das Programm
lesbarer machen. Die Zeilen 9 bis 16 definieren die Struktur s_generisch
, die später
verwendet wird. Zeile 18 enthält den Prototyp der Funktion ausgabe()
. In Zeile 22
wird die Strukturvariable var
deklariert und in den Zeilen 24 und 25 mit einem
Zeichenwert initialisiert. Ein Aufruf von ausgabe()
in Zeile 26 gibt den Wert aus. Die
Zeilen 28 bis 30 und 32 bis 34 wiederholen diesen Vorgang für andere Werte.
Die Funktion ausgabe()
ist das Kernstück dieses Listings. Im Programm wird diese
Funktion dazu verwendet, den Wert einer s_generisch
-Variablen auszugeben. Eine
ähnliche konstruierte Funktion hätte auch zur Initialisierung verwendet werden
können. Die Funktion ausgabe()
prüft den Inhalt des Elements typ
und gibt dann den
Inhalt der Union als Wert des gefundenen Variablentyps aus. Damit werden
fehlerhafte Ausgaben wie in Listing 10.6 vermieden.
Sie können mit dem Schlüsselwort typedef
ein Synonym für eine Struktur oder einen
Uniontyp erzeugen. Zum Beispiel definiert folgende Anweisung koord
als ein
Synonym für die angegebene Struktur:
typedef struct {
int x;
int y;
} koord;
Instanzen dieser Struktur können dann mit dem koord
-Bezeichner deklariert werden:
koord obenlinks, untenrechts;
Beachten Sie, dass sich typedef
von einem Strukturnamen, wie er weiter vorn in
dieser Lektion beschrieben wurde, unterscheidet. Wenn Sie schreiben:
struct koord {
int x;
int y;
};
ist der Bezeichner koord
der Name der Struktur. Sie können den Namen zur
Deklaration von Instanzen der Struktur verwenden, müssen aber im Gegensatz zu
typedef
das Schlüsselwort struct
voranstellen:
struct koord obenlinks, untenrechts;
Es macht eigentlich keinen großen Unterschied, ob Sie typedef
oder den
Strukturnamen zur Deklaration von Strukturenvariablen verwenden. Mit typedef
erhalten Sie etwas kürzeren Code, da Sie auf das Schlüsselwort struct
verzichten
können. Andererseits ist es vielleicht gerade das Schlüsselwort struct
im
Zusammenspiel mit dem Strukturnamen, das deutlich macht, dass hier eine
Strukturvariable deklariert wird.
Die heutige Lektion hat Ihnen gezeigt, wie sich Strukturen einsetzen lassen - ein
Datentyp, den Sie individuell den Bedürfnissen Ihres Programms anpassen können.
Strukturen können Elemente jeden beliebigen C-Datentyps enthalten, einschließlich
anderer Strukturen, Zeiger und Arrays. Der Zugriff auf die einzelnen Datenelemente
einer Struktur erfolgt über den Punktoperator (.
), der zwischen den Strukturnamen
und den Elementnamen gesetzt wird. Strukturen können einzeln, aber auch in Arrays
verwendet werden.
Unions sind den Strukturen sehr ähnlich. Der Hauptunterschied besteht darin, dass eine Union alle ihre Elemente im gleichen Speicherbereich ablegt. Dies hat zur Folge, dass immer nur ein Element einer Union zur Zeit genutzt werden kann.
Frage:
Gibt es Gründe, eine Struktur ohne eine Instanz zu deklarieren?
Antwort:
Heute haben Sie drei Wege kennen gelernt, eine Struktur zu deklarieren.
Zum einen können Sie Strukturrumpf, Namen und Instanz alle gleichzeitig
deklarieren, oder Sie deklarieren einen Strukturrumpf und eine Instanz, aber
keinen Strukturnamen. Die dritte Möglichkeit besteht darin, die Struktur und
den Namen ohne eine Instanz zu deklarieren. Die Instanz kann dann später
mit dem Schlüsselwort struct
, dem Strukturnamen und einem Namen für die
Instanz »nachdeklariert« werden. Unter Programmierern ist es allgemein
üblich, entweder die zweite oder dritte Variante anzuwenden. Viele
Programmierer deklarieren den Strukturrumpf und den Namen ohne
irgendwelche Instanzen. Diese werden dann bei Bedarf später im Programm
nachgeholt. Am Tag 11 widmen wir uns dem Gültigkeitsbereich von
Variablen. Gültigkeitsbereiche gelten auch für Instanzen, hingegen nicht für
den Strukturnamen oder den Strukturrumpf.
Frage:
Was wird in der Praxis häufiger verwendet: typedef
oder der Strukturname?
Antwort:
Viele Programmierer verwenden typedef
, um ihren Code leichter lesbar zu
machen. In der Praxis ist der Unterschied allerdings gering. Viele der mit
Linux ausgelieferten Bibliotheken verwenden hauptsächlich typedef
, um dem
Bibliotheksverwalter die Arbeit zu erleichtern.
Frage:
Kann ich eine Strukturvariable mit Hilfe des Zuweisungsoperators einer anderen
Strukturvariablen zuweisen?
Antwort:
Ja und nein. Neuere C-Compiler, einschließlich des GNU-C-Compilers,
erlauben die Zuweisung einer Strukturvariablen (oder einer Unionvariablen)
an eine des gleichen Typs. In älteren C-Versionen kann es passieren, dass Sie
jedes Element der Struktur einzeln zuweisen müssen! Wenn Sie wollen, dass
Ihr Code auch auf ältere Compiler portierbar ist, sollten Sie lieber zweimal
überlegen, bevor Sie von dieser Möglichkeit Gebrauch machen.
Frage:
Wie groß ist eine Union?
Antwort:
Da jedes Element einer Union an ein und derselben Speicherstelle steht, ist
der Speicherplatz, der von der Union belegt wird, gleich dem Platz, der von
ihrem größten Element eingenommen wird.
Der Workshop enthält Quizfragen, die Ihnen helfen sollen, Ihr Wissen zu festigen, sowie Übungen, die Sie anregen sollen, das Gelernte umzusetzen und eigene Erfahrungen zu sammeln. Die Lösungen zu den Fragen und den Übungen finden Sie in Anhang C.
struct adresse {
char name[31];
char adr1[31];
char adr2[31];
char stadt[11];
char staat[3];
char plz[11];
} meineadresse = { "Bradley Jones",
"RTSoftware",
"P.O. Box 1213",
"Carmel", "IN", "46082-1213"};
zgr
auf das erste Array-Element (das heißt das erste Strukturelement in dem Array)
gesetzt. Wie richten Sie zgr
auf das zweite Array-Element?
zeit
definiert, die drei int
-
Elemente enthält.
daten
definieren, die ein Element vom Typ int
und zwei Elemente vom Typ float
enthält, und er soll eine Instanz namens info
vom Typ daten
deklarieren.
info
den Wert 100
zu.
info
.
float
-Element von info
mit Hilfe der Zeigernotation den Wert 5.5
zuweisen kann.
daten
, der einen einzigen String von bis
zu 20 Zeichen enthalten kann.
adresse1
, adresse2
, stadt
,
staat
und plz
. Erzeugen Sie mit typedef
ein Synonym namens DATENSATZ
, das zur
Erzeugung von Instanzen dieser Struktur verwendet werden kann.
typedef
-Synonym aus Übung 7 und initialisieren Sie ein
Element namens meineadresse
.
struct {
char tierkreiszeichen[21];
int monat;
} zeichen = "Löwe", 8;
/* eine Union einrichten */
union daten{
char ein_wort[4];
long eine_zahl;
}generische_variable = { "WOW", 1000 };
Beachten Sie, dass das Kopieren von Strukturen mit Zeigerelementen u.U. zu unerwarteten Ergebnissen führt, da die Adressen der Zeiger und nicht die Objekte, auf die die Zeiger verweisen, kopiert werden.