Strukturen in C
Motivation hinter Strukturen
Ein struct ermöglicht es, einen logischen Zusammenhalt mehrerer Werte explizit auszudrücken und den Dingen einen Namen zu geben.
Beispielsweise besteht ein Datum nun mal aus Tag/Monat/Jahr
struct date_
{
uint8_t Tag;
uint8_t Monat;
uint16_t Jahr;
};
Der komplette Name einer Person besteht aus Vorname und Familienname
struct name_
{
char Vorname[20];
char Familienname[30];
}
Eine Ortsangabe besteht aus Postleitzahl, Ortsname, Strasse und Hausnummer
struct ort_
{
char PLZ[6];
char Name[30];
char Strasse[30];
char Nummer[5];
};
und eine komplette Personenbeschreibung besteht aus dem kompletten Namen, einem Wohnort, seinem Geburtstag, seinem Hochzeitstag und der Anzahl der Kinder
struct person
{
struct name_ Name;
struct date_ Geburtstag;
struct date_ Hochzeitstag;
struct ort_ Wohnort;
uint8_t NrKinder;
};
Man sieht sehr schön, wie sich die Beschreibung einer Person mit den vorhergehenden Strukturen ganz automatisch strukturiert. Eine Person Franz
struct person_ Franz;
hat damit automatisch einen Monat in dem er geboren wurde. Er "erbt" dies, weil er einen Geburtstag hat und dieser als struct date_ klarerweise über ein Monat verfügt. Der Monat in dem Franz geboren wurde ist daher
Franz.Geburtstag.Monat
Liest man diesen C-Ausdruck von links nach rechts ist das Ganze weitgehend selbsterklärend. Vom Allgemeineren zum immer Spezifischeren. 'Franz' ist die komplette Person. 'Franz.Geburtstag' ist der Geburtstag der Person Franz. 'Franz.Geburtstag.Monat' ist der Monat des Geburtstages der Person Franz.
Würde man zum struct ort_ noch das Land mit dazunehmen,
struct ort_
{
char PLZ[6];
char Name[30];
char Strasse[30];
char Nummer[5];
char Land[30];
};
dann 'erben' alle weiteren Strukturen oder Funktionen, die mit einem struct ort_ arbeiten ganz automatisch, dass ein struct ort_ jetzt auch noch das Land oder den Staat mit beinhaltet.
Strukturen sind Datentypen wie alle anderen eingebauten Datentypen auch (int, long, char, double, ...). Will man also beispielsweise die Belegschaft einer Firma speichern, die aus maximal 300 Personen besteht, dann kann man selbstverständlich ein derartiges Array von Personen erzeugen
struct person_ Belegschaft[300];
int AnzahlPersonen;
Damit hat man entsprechenden Speicher bereitgestellt um 300 Personen speichern zu können. Mit allem drum und dran. Inklusive Wohnort, inklusive Geburtstag, inklusive eben allem was eine Person ausmacht. Jede einzelne Person der Belegschaft hat einen Geburtsmonat. Denn im Array sind ja lauter Personen gespeichert und jede ist vom Typ struct person_ und hat als solches einen Geburtstag. Schreibt man sich daher eine Funktion, die einen beliebiges struct date_ ausgeben kann
void printDate( struct date_ * Datum )
{
printf( "%d/%d/%04d", Datum->Tag, Datum->Monat, Datum->Jahr );
}
dann kann man, weil ja ein Geburtstag einer Person so ein Datum ist, die Geburtstage der kompletten Belegschaft ausgeben
printf( "Geburtstage\n" );
for( i = 0; i < AnzahlPersonen; i++ ) { printf( "%s %s: ", Belegschaft[i].Name, Belegschaft[i].Vorname ); printfDate( & Belegschaft[i].Geburtstag ); printf( "\n" ); }
Das ist aber nicht die einzige mögliche Verwendung so einer Funktion. Würde man für eine Person in der Belegschaft zb auch noch das Datum mitspeichern, an dem sie in die Firma gekommen ist.
struct person
{
struct name_ Name;
struct date_ Geburtstag;
struct date_ Hochzeitstag;
struct date_ Firmeneintritt;
struct ort_ Wohnort;
};
dann kann man selbstverständlich die Funktion printDate auch dazu benutzen, dieses Datum auszugeben. Denn auch ein 'Firmeneintritt' ist ja nur ein struct date_ und damit ist die Funktion dafür benutzbar
printf( "Firmeneintritte\n" );
for( i = 0; i < 300; i++ ) {
printf( "%s %s: ", Belegschaft[i].Name, Belegschaft[i].Vorname );
printfDate( & Belegschaft[i].Firmeneintritt);
printf( "\n" );
}
Ein anderes Beispiel wären Basisfunktionen, die zb mit einem struct date_ diverse Vergleiche oder Berechnungen anstellen, dann kann man dann zb. wiederrum Funktionen schreiben, die alle Mitarbeiter rausfiltern, die heuer 30 Jahre in der Firma sind, oder die gerade ihren 25. Geburtstag feiern etc, etc.
Aus einer 'logischen Gruppierung', in der man Dinge die zusammengehören auch in Form einer Datenstruktur zusammenfasst und strukturiert, folgt also wesentlich mehr als man jemals mit mehrspaltigen Arrays ausdrücken könnte. Man verpasst damit den beteiligten Daten eine Struktur die diesem logischen Zusammenhalt entspricht und versetzt sich damit in die Lage, die reale Welt besser und übersichtlicher und vor allen Dingen in 'der dem Programmierer gewohnten Sprache' in einem Programm abbilden zu können. Wieviel aussagekräftiger ist
Belegschaft[i].Geburtstag.Monat
als im Vergleich dazu
Belegschaft[i][12]
nur weil der Programmierer zufällig weißt, dass in der Spalte 12 eines 2-dimensionalen Arrays das Geburtsmonat gespeichert ist?
Zudem ermöglichst es Strukturen, dass man sich bei der Programmierung auf Teilbereiche konzentrieren kann. Erstellt sich der Programmierer Funktionen, die mit einem struct date_ arbeiten (egal ob Ausgeben, Vergleichen, Tage zwischen Daten berechnen, etc...), zu diesem Zeitpunkt konzentriert man sich nur und ausschliesslich auf das, was mit einem struct date_ möglich sein soll und wie diese Funktionalität zu implementieren ist.
Das dieses struct date_ dann in Form von Geburtstagen, Firmeneintritten, Jubiläen etc. benutzt wird bzw. werden kann, ist beim Schreiben der Datumsfunktionen herzlich egal - und das soll es auch sein. Natürlich wird die komplette Entwicklung dieser Funktionen durch das eigentliche Projekt getrieben werden, weil man ja weiß, was an Funktionalität in etwa gebraucht werden wird. Der springende Punkt ist aber, dass die entstehenden Funktionen erst mal damit nichts zu tun haben. Sie wären auch abseits dieses Projektes nützlich.