1 Theorie | |
→ | 1.1 Die Programmiersprache C |
typedef |
enum (Aufzählung) |
struct (kombinierte Daten) |
union (Auswahl) |
Initialisierung, Zugriff auf struct/union-Elemente |
Coding Standards |
Empfehlungen |
Aufgaben |
Mit
typedef vorhanden neu;
kann aus einem vorhandenen Datentyp ein neuer Datentyp definiert
werden.
Z.B. kann nach
typedef long KOORDINATE;
der neue Datentyp KOORDINATE für die Deklaration von Variablen genutzt werden:
KOORDINATE a; a = 5L;
Der typedef-Operator wird häufig in Zusammenhang mit den nachfolgenden Datentypen enum, struct und union benutzt.
Mit
enum Typ { Name1, Name2, ... };
wird ein Aufzählungstyp erzeugt. Dieser kann die Werte
Name1, Name2... annehmen.
Im Hintergrund handelt es sich dabei um einen int-Typ,
Name1, Name2 werden als Konstanten definiert.
In der enum-Deklaration können auch Werte für die Namen vorgegeben
werden.
Beispiel für ein Wasserreservoir:
enum WASSER_RESERVOIR_ZUSTAND { FUELLSTAND_LEER = 1, FUELLSTAND_OK , FUELLSTAND_VOLL , }; enum WASSER_RESERVOIR_ZUSTAND wasser_reservoir_zustand = FUELLSTAND_LEER;
In Verbindung mit dem typedef-Operator kann bei Variablendeklarationen das enum weggelassen werden:
typedef enum { FUELLSTAND_LEER = 1, FUELLSTAND_OK, FUELLSTAND_VOLL, } WASSER_RESERVOIR_ZUSTAND; WASSER_RESERVOIR_ZUSTAND wasser_reservoir_zustand = FUELLSTAND_LEER;
Wird für den ersten Namen kein Wert angegeben, wird 0
verwendet.
Für andere Namen ohne Wertangabe wird jeweils der Wert des
unmittelbar vorangegangenen Namens um 1 erhöht.
Quelltext wie z.B.
WASSER_RESERVOIR_ZUSTAND wrz; ... wrz = ... /* Zustand ermitteln */ switch (wrz) { case FUELLSTAND_LEER: { zufluss_oeffnen(); abfluss_schliessen(); } break; case FUELLSTAND_OK: { zufluss_schliessen(); abfluss_schliessen(); } break; case FUELLSTAND_VOLL: { zufluss_schliessen(); abfluss_oeffnen(); } break; }
ist besser zu verstehen und zu bearbeiten als:
int wrz; ... wrz = ... /* Zustand ermitteln */ switch (wrz) { case 1: { zufluss_oeffnen(); abfluss_schliessen(); } break; case 2: { zufluss_schliessen(); abfluss_schliessen(); } break; case 3: { zufluss_schliessen(); abfluss_oeffnen(); } break; }
In einem Struct werden zusammengehörige Daten gespeichert, die alle gleichzeitig verfügbar sein sollen.
Beispiel:
struct flug_reise_zeit { int stunden; int minuten; };
Auch hier kann typedef benutzt werden, um spätere Variablendeklarationen zu vereinfachen:
typdef struct { int stunden; int minuten; } flug_reise_zeit;
In einem union können Daten zusammengefasst werden, dabei ist immer nur eine Komponente verfügbar. Schreiben einer Komponente führt dazu, dass zuvor gespeicherte andere Komponenten zerstört werden.
/** Koordinaten fuer einen Punkt. */ typedef struct { double x; /**< x-Koordinate. */ double y; /**< y-Koordinate. */ } PUNKT; /** Achsenparalleles Rechteck. */ typedef struct { PUNKT lu; /**< Eckpunkt links unten. */ PUNKT ro; /**< Eckpunkt rechts oben. */ } RECHTECK; /** Kreis. */ typedef struct { PUNKT mp; /**< Mittelpunkt. */ double ra; /**< Radius. */ } KREIS; /** Dreieck. */ typedef struct { PUNKT a; /**< Eckpunkt. */ PUNKT b; /**< Eckpunkt. */ PUNKT c; /**< Eckpunkt. */ } DREIECK; /** Geometrisches Objekt, kann verschiedene Typen speichern. */ typedef struct { /** Daten des Geometrieobjektes. */ union { RECHTECK re; /**< Rechteck-Daten. */ KREIS kr; /**< Kreis-Daten. */ DREIECK dr; /**< Dreiecks-Daten. */ } daten; int was_ist_drin; /**< 0=Rechteck, 1=Kreis, 2=Dreieck. */ } GEOMETRIEOBJEKT;
Im Beispiel wird ein struct GEOMETRIEOBJEKT definiert. Dieses
besteht aus einem int-Feld was_ist_drin, das den Typ der
gespeicherten Daten angibt.
Die eigentlichen Daten sind im union-Feld daten abgelegt,
das die drei Komponenten re, kr und dr hat, um
ein Rechteck, einen Kreis oder ein Dreieck abzuspeichern.
Zu jedem Zeitpunkt kann nur eine der drei Komponenten belegt sein,
was_ist_drin gibt an, welche.
Um einen struct zu initialisieren, werden die Werte in
geschweife Klammern geschrieben. Die Reihenfolge der Wertangaben
muss der Reihenfolge der Komponenten in der Definition des
Datentypes entsprechen. Sind einzelne Komponenten eines struct
wiederum ein struct, werden die Daten für den Struct ebenfalls mit
geschweiften Klammern zusammengefasst.
Ein Beispiel hierfür ist die Initialisierung des konstanten
Rechteckes re in den Beispielen ex016.c und ex017.c
Bei Initialisierung eines union wird der Wert für die erste Komponente angegeben.
Zum Zugriff auf Elemente eines struct oder union dienen die Operatoren "." für direkten Zugriff bzw. "->" für Zugriff über Zeiger.
Die Beispiele ex016.c und ex017.c demonstrieren dies.
/* Flaechenberechnung fuer achsparalleles Rechteck.
Dieses Beispiel demonstriert die Verwendung von struct.
*/
/* Copyright (C) 2014-2017 - HS Schmalkalden. All rights reserved. */
#include <stdio.h>
#include <math.h>
/** Koordinaten fuer einen Punkt.
*/
typedef struct {
double x; /**< x-Koordinate. */
double y; /**< y-Koordinate. */
} PUNKT;
/** Achsenparalleles Rechteck.
*/
typedef struct {
PUNKT lu; /**< Eckpunkt links unten. */
PUNKT ro; /**< Eckpunkt rechts oben. */
} RECHTECK;
/** Konstantes Rechteck, gegeben durch zwei Punkte.
*/
const RECHTECK re = { { 1.0, 2.0 }, { 3.0, 4.0} };
/** Hauptprogramm.
@return 0 bei Erfolg, alle anderen Werte bedeuten Fehler.
*/
int
main(void)
{
double breite;
double hoehe;
double flaeche;
/* Direkter Zugriff.
re.ro liefert die Komponente ro des RECHTECK re, das Resultat
ist ein PUNKT.
Das angehangene .x liefert hiervon die Komponente x, das
Resultat ist der double-Wert mit der x-Koordinate.
*/
breite = re.ro.x - re.lu.x;
hoehe = re.ro.y - re.lu.y;
/* fabs() bildet Betrag. */
flaeche = fabs(breite * hoehe);
printf("Flaeche: %lg\n", flaeche);
return 0;
}
/* vim: set ai sw=4 ts=4 expandtab : */
/* Flaeche eines achsparallelen Rechteckes berechnen.
Dieses Beispiel demonstriert den Zugriff auf struct-Komponenten
ueber Zeiger.
*/
/* Copyright (C) 2014-2018 - HS Schmalkalden. All rights reserved. */
#include <stdio.h>
#include <math.h>
/** Koordinaten fuer einen Punkt.
*/
typedef struct {
double x; /**< x-Koordinate. */
double y; /**< y-Koordinate. */
} PUNKT;
/** Achsenparalleles Rechteck.
*/
typedef struct {
PUNKT lu; /**< Eckpunkt links unten. */
PUNKT ro; /**< Eckpunkt rechts oben. */
} RECHTECK;
/** Rechteck-Daten.
*/
const RECHTECK re = { { 1.0, 2.0 }, { 3.0, 4.0} };
/** Hauptprogramm.
@return 0 bei Erfolg, alle anderen Werte bedeuten Fehler.
*/
int
main(void)
{
const RECHTECK *reptr;
double breite;
double hoehe;
double flaeche;
/* Zugriff ueber Zeiger.
Der Zeiger reptr wird auf die Anfangsadresse des Rechteckes
re gesetzt.
reptr->ro liefert die Komponente ro des Rechteckes, auf das
der Zeiger reptr zeigt. Das Resultat is ein PUNKT.
Das angehangene .x selektiert hiervon die Komponente x. Das
Resultat ist ein double-Wert (die x-Koordinate des Punktes).
*/
reptr = &re;
breite = reptr->ro.x - reptr->lu.x;
hoehe = reptr->ro.y - reptr->lu.y;
/*
Alternativ kann auch mit dem Operator * der Zeiger
dereferenziert werden, dann kann mit dem Operator .
auf die Komponenten zugegriffen werden.
*/
hoehe = (*reptr).ro.y - (*reptr).lu.y;
/* fabs() bildet Betrag. */
flaeche = fabs(breite * hoehe);
printf("Flaeche: %lg\n", flaeche);
return 0;
}
/* vim: set ai sw=4 ts=4 : */
Quelle: CERT C Coding Standard 〈1〉
#define VOLLJAEHRIGKEIT 18Stattdessen empfohlen:
enum { VOLLJAEHRIGKEIT = 18 };
/** Datumsangabe */ typedef struct { int tag; /**< Tag im Monat, mit 1 beginnend. */ char monat[4]; /**< Begrenzt auf 3 Zeichen. */ int jahr; /**< Jahreszahl komplett, beispielsweise 2014. */ } DATUM;Besser:
/** Datumsangabe */ typedef struct { int tag; /**< Tag im Monat, mit 1 beginnend. */ int monat; /**< Monat, beginnend mit 1 fuer Januar. */ int jahr; /**< Jahreszahl komplett, beispielsweise 2014. */ } DATUM;
typedef struct { char strasse[20]; int hausnummer; long postzleitzahl; char ort[20]; } ADRESSE;Hier kann weder eine "Müller-Lüdenscheidt-Straße" noch der Ort "Garmisch-Partenkirchen" gespeichert werden.
typedef struct { char strasse[200]; int hausnummer; long postzleitzahl; char ort[200]; } ADRESSE;Hier ist zwar hoffentlich eine ausreichende Puffergröße gewählt, bei kurzen Orts- oder Straßennamen wird Speicherplatz verschwendet.
typedef struct { char *strasse; int hausnummer; long postzleitzahl; char *ort; } ADRESSE;Hier ist ein großer Textpuffer nur einmalig in der Erfassungsroutine notwendig, nicht jedoch in jedem gespeicherten Datensatz.
typedef struct { char hokus; /* 1 Byte gross */ /* Hier werden 7 Padding-Bytes eingefuegt, weil die naechste Komponente 8 Bytes gross ist und somit an einer durch 8 teilbaren Adresse beginnt. */ long long pokus; /* 8 Byte gross */ char fidi; /* 1 Byte gross */ /* Hier werden 7 Padding-Bytes eingefuegt, weil die naechste Komponente 8 Bytes gross ist und somit an einer durch 8 teilbaren Adresse beginnt. */ long long bus; /* 8 Byte gross */ } irgendwas;Richtig:
typedef struct { long long pokus; /* 8 Byte gross */ long long bus; /* 8 Byte gross */ char hokus; /* 1 Byte gross */ char fidi; /* 1 Byte gross */ } irgendwas;
/** Zustand eines Wasserreservoirs. */ enum WASSER_RESERVOIR_ZUSTAND { /** Wasserstand unterhalb des Mindest-Levels. Abflusshahn schliessen, Zuflusshahn oeffnen. */ FUELLSTAND_LEER = 1, /** Wasserstand im akzeptablen Bereich. Abflusshahn schliessen, Zuflusshahn schliessen. */ FUELLSTAND_OK, /** Wasserstand am Hoechstlevel oder oberhalb. Abflusshahn oeffnen, Zuflusshahn schliessen. */ FUELLSTAND_VOLL };
/** Zustand eines Wasserreservoirs. */ enum WASSER_RESERVOIR_ZUSTAND { FUELLSTAND_LEER = 1, /**< Wasserstand unterhalb des Mindest-Levels. */ FUELLSTAND_OK = 2, /**< Wasserstand im akzeptablen Bereich. */ FUELLSTAND_VOLL = 3 /**< Wasserstand am Hoechstlevel oder oberhalb. */ } ;Richtig:
/** Zustand eines Wasserreservoirs. */ enum WASSER_RESERVOIR_ZUSTAND { FUELLSTAND_LEER = 1, /**< Wasserstand unterhalb des Mindest-Levels. */ FUELLSTAND_OK = 2, /**< Wasserstand im akzeptablen Bereich. */ FUELLSTAND_VOLL = 3 /**< Wasserstand am Hoechstlevel oder oberhalb. */ };
1 | http://www.securecoding.cert.org |