Strukturen in C [Strukturen in C] (Strukturen in C), Lektion, Seite 722305
https://www.purl.org/stefan_ram/pub/strukturen_in_c (Permalink) ist die kanonische URI dieser Seite.
Stefan Ram

Strukturen in C 

Reihungen sind homogene  Verbunde (Verbindungen von Daten gleichen  Typs und einer gewissen Gleichheit ihrer Bedeutung). Daneben gibt es auch heterogene  Verbunde (Verbindungen von Daten, deren Typen oder Bedeutungen unterschiedlich sein können).

Das klassische Beispiel für solche Verbunde sind die Datensätze einer Datenbank (beispielsweise Kontoname und Kontosaldo, also Text und Zahl). Eine Tabelle einer Datenbank wäre in C  als Reihung solcher Verbunde vorstellbar.

Aber auch in der Mathematik kennt man Verbindungen von Werten, etwa bei Matrizen oder komplexen Zahlen. Bei einer Matrix  gibt es eher gleichberechtigte Komponenten, so daß sie eher durch Reihungen dargestellt werden. Eine komplexe Zahl  kann man aber als Verbund zweier unterschiedlicher  Dinge (eines Real- und eines Imaginärteils) ansehen, so daß sie auch als Verbund aufgefaßt werden kann. Auch bei bestimmten Matrizen  ist dies denkbar, wenn ihre Dimension in einem Programmteil zur Laufzeit nicht veränderlich sein soll (etwa Paare aus einer X- und einer Y-Komponente).

Vereinfachte und unvollständige Syntax:

declaration 〉 ::=
struct-specifier 〉 ";".
struct-specifier 〉 ::=
"struct" [〈identifier 〉] "{" 〈struct-declaration-list 〉 "}".
Beispiel für eine Definition einer Variablen mit Strukturtyp
#include <stdio.h>  /* printf       */
#include <stdlib.h> /* EXIT_SUCCESS */
#include <string.h> /* strcpy */ int main( void )
{ struct{ char name[ 32 ]; double balance; } a;
strcpy( a.name, "Hans" );
a.balance = 3.14;
printf( "%.32s %f\n", a.name, a.balance );
return EXIT_SUCCESS; }
stdout
Hans 3.140000

Zum Zugriff auf eine Strukturkomponenten wird als ein ».« verwendet. Anders als bei Reihungen sind Strukturkomponenten nie numeriert, sondern stets benannt. Ausrichtungsanforderungen können dazu führen, daß eine Struktur größer ist als die Summe ihrer Komponentengrößen angibt.

Das Format »%.32s« gibt maximal 32 Zeichen aus.

Beispiel für Speichernutzung mit Ausrichtung
#include <stdio.h>  /* printf */
#include <stdlib.h> /* EXIT_SUCCESS */

int main( void )
{ struct{ char c; double x; }a;
printf( "%d = %d + %d\n", sizeof a, sizeof a.c, sizeof a.x );
return EXIT_SUCCESS; }
stdout
12 = 1 + 8

Es ist auch möglich, daß ein Strukturbezeichner ohne Variablendeklaration definiert wird, der dann später in einer Variablendeklaration verwendet werden kann.

struct-specifier 〉 ::=
"struct" 〈identifier 〉.
Beispiel der Deklaration eines Strukturbezeichners
#include <stdio.h>  /* printf       */
#include <stdlib.h> /* EXIT_SUCCESS */
#include <string.h> /* strcpy */ #define NAMESIZE 32 struct account
{ char name[ 32 ]; double balance; };

int main( void )
{ struct account a;
strncpy( a.name, "Hans", NAMESIZE );
a.balance = 3.14;
printf( "%.32s %f\n", a.name, a.balance );
return EXIT_SUCCESS; }
stdout
Hans 3.140000

Die Verwendung von »strncpy« verhindert ein versehentliches Übertreten der Grenzen der Reihung.

Eine Strukturdeklaration endet also – anders als eine Funktionsdefinition – immer mit einem Semikolon.

Die Strukturdeklaration könnte in dem obigen Programmbeispiel auch innerhalb der Funktion »main« direkt hinter der geschweiften Klammer auf erfolgen.

Beispiel für einen Strukturinitialisierer
#include <stdio.h>  /* printf       */
#include <stdlib.h> /* EXIT_SUCCESS */ struct account{ const char * name; double balance; };

int main( void )
{ struct account a ={ "Hans", 3.14 };
printf( "%.32s %f\n", a.name, a.balance );
return EXIT_SUCCESS; }
stdout
Hans 3.140000
/    Übung
Definieren Sie eine Struktur für eine komplexe Zahl mit zwei Komponenten vom Datentyp »double«.
Definieren Sie alsdann eine Funktion, die eine komplexe Zahl als Argument akzeptiert und deren Betrag ergibt.
Schreiben Sie ein Programm, das den Wert dieser Funktion für eine komplexe Zahl ausgibt.

Strukturzeiger

Da Strukturen Objekte sind, sind auch Zeiger auf Strukturen  möglich. Für sie gilt alles bisher über Zeiger Gesagte.

Beispiel für einen Zeiger auf eine Struktur
#include <stdio.h>  /* printf       */
#include <stdlib.h> /* EXIT_SUCCESS */ struct account{ const char * name; double balance; };

int main( void )
{ struct account a ={ "Hans", 3.14 };
struct account * const p = &a;
printf( "%.32s %f\n", ( *p ).name, ( *p ).balance );
(*p).balance = 0;
printf( "%.32s %f\n", ( *p ).name, ( *p ).balance );
return EXIT_SUCCESS; }
stdout
Hans 3.140000
Hans 0.000000

Zeiger auf Strukturen sind besonders deshalb beliebt, weil ihre Übergabe an eine Funktion der Funktion den Zugriff auf die Struktur erlaubt, ohne daß die Struktur dabei kopiert wird. Da Zeiger meist kleiner sind als Strukturen, ist dies oft schneller. Außerdem erlaubt die Übergabe eines Zeigers auch die Veränderung des Objektes, auf das gezeigt wird. Dies sollte mit »const« unterbunden werden, falls es nicht erwünscht ist.

Ein Zeiger ohne eine Struktur darf natürlich nicht dereferenziert werden.

Beispiel für einen Zeiger ohne Struktur
#include <stdio.h>  /* printf       */
#include <stdlib.h> /* EXIT_SUCCESS */ struct account{ const char * name; double balance; };

int main( void )
{ struct account * p;
printf( "%.32s %f\n", ( *p ).name, ( *p ).balance );
(*p).balance = 0;
printf( "%.32s %f\n", ( *p ).name, ( *p ).balance );
return EXIT_SUCCESS; }
Konsole
Segmentation fault

Die Schreibweise »〈expression ->« ist eine Abkürzung für »(*expression ).«.

Beispiel für »->«
#include <stdio.h>  /* printf       */
#include <stdlib.h> /* EXIT_SUCCESS */ struct account{ const char * name; double balance; };

int main( void )
{ struct account a ={ "Hans", 3.14 };
struct account * const p = &a;
printf( "%.32s %f\n", p->name, p->balance );
p->balance = 0;
printf( "%.32s %f\n", p->name, p->balance );
return EXIT_SUCCESS; }
stdout
Hans 3.140000
Hans 0.000000
/    Übung
Ändern Sie die Lösung der vorherigen Aufgabe so ab, daß die Betragsfunktion einen Zeiger  auf eine komplexe Zahl ergibt.

Bei Bedarf werden Strukturen auch alloziert, wofür Strukturzeiger unumgänglich sind. Wann immer möglich sollte jedoch automatischer Speicher (wie bisher verwendet) bevorzugt werden, da er weniger fehlerträchtig ist als allozierter Speicher.

Eine allozierte Struktur
#include <stdio.h>  /* printf       */
#include <stdlib.h> /* EXIT_SUCCESS */ struct account{ const char * name; double balance; };

int main( void )
{ int result = EXIT_FAILURE;
struct account * const p = malloc( sizeof *p );
if( p )
{ p->name = "Hans"; p->balance = 3.14;
printf( "%.32s %f\n", p->name, p->balance );
p->balance = 0;
printf( "%.32s %f\n", p->name, p->balance );
free( p );
result = EXIT_SUCCESS; }
return result; }
stdout
Hans 3.140000
Hans 0.000000

Typnamen

Typangaben können in C  beliebig kompliziert sein.

Sie können – ausgehend von der zu definierenden Variablen – von innen nach außen und dann von rechts nach links gelesen werden.

Typangaben in C 

char( *( *x() )[] )(); /* x is a function returning a pointer to an array of pointers to functions returning a char value */
char( *( *x[ 3 ])() )[ 5 ]; /* ? */
int( *( *x )[] )(); /*
? */
double( *x( double( * )[ 3 ]))[ 3 ]; /* ? */

Typangaben, wie die obenstehenden, werden in der Praxis auch tatsächlich immer einmal wieder benötigt.

Durch die Definition eines Typnamens  können kompliziertere Typangaben vereinfacht werden, und deren Wiederholung kann vermieden werden:

/    Funktionstyp ohne Typname
void apply( void( * const f )( int const x ))
{ f( 'a' ); f( 'b' ); f( 'c' ); }
/    Funktionstyp mit Typnamen
typedef void( * function )( int const x );

void apply( function const f ){ f( 'a' ); f( 'b' ); f( 'c' ); }

Der zu definierende Name steht dort, wo sonst der Name einer Variablen mit diesem Typ stehen würde.

Das »const« wurde oben nicht mit in die Typnamensdefinition aufgenommen, da es praktisch ist, dies noch bei jeder Verwendung individuell angeben zu können.

Mit »typedef« wird, obwohl es so klingt wie „Typdefinition“, kein neuer Typ  definiert, sondern nur ein neuer Name für einen schon vorhandenen Typ. Jedes Programm mit »typedef« kann also auch durch Einsetzen der Definition in ein äquivalentes Programm ohne »typedef« umgeschrieben werden.

Neu In manchen Stilregeln wird empfohlen, daß mit »typedef« definierte Namen mit großem Anfangsbuchstaben oder ganz in Großbuchstaben geschrieben werden (wie Makronamen). Von dieser Regelung wird hier gelegentlich Gebrauch gemacht werden, damit Leser diese Namen leichter von anderen unterscheiden können, ohne daß damit notwendigerweise empfohlen werden soll, dies auch sonst so zu tun.

Neu Einige Programmierer vermeiden es, Namen für Objektzeigertypen zu definieren und definieren nur Namen für Nichtzeigertypen (wie »FILE«) – abgesehen von den Fällen, in denen damit abstrakte Datentypen hinter einem „opaken Griff“ versteckt werden sollen. Andere Programmiere, verwenden »typedef« gar nicht.

Wäre »dbl« im folgenden Programm ein neuer Typ, so wäre der Aufruf mit einem double-Wert »3.0« nicht möglich. »dbl« ist aber nur ein neuer Name  für den schon vorhandenen Typ »double«.

Typnamensdefinition
#include <stdio.h>  /* printf       */
#include <stdlib.h> /* EXIT_SUCCESS */ typedef double dbl; dbl twice( dbl const x ){ return 2 * x; } int main( void )
{ printf( "%f\n", twice( 3.0 ));
return EXIT_SUCCESS; }
stdout
6.000000

Eine Geschmacksfrage ist die gelegentliche Praxis der Definition von Typnamen für Strukturen. Sie hat den Vorteil einer gewissen Verkürzung. Sie „versteckt“ die Verwendung einer Struktur, was als Vorteil angesehen werden kann, wenn Abstraktion gewünscht ist (abstrakte Datentypen), aber vor dem Leser eines Programms gleichzeitig eine von ihm vielleicht benötigte Information verstecken könnte.

Typnamensdefinition für eine Struktur
#include <stdio.h>  /* printf       */
#include <stdlib.h> /* EXIT_SUCCESS */ #define NAMESIZE 32 typedef struct account
{ const char * name; double balance; }account;
int main( void )
{ account const a ={ "Hans", 3.14 };
printf( "%.32s %f\n", a.name, a.balance );
return EXIT_SUCCESS; }
stdout
Hans 3.140000

Refaktor „Extraktion einer ‚inneren Funktion‘“ (“Extract Method ”)

Bei der strukturierten Ressourcenbehandlung sind die inhaltlich nahe zusammengehörigen Teile des Quellcodes zur Ressourcenverwaltung manchmal weit voneinander entfernt, wie in dem folgenden Programmbeispiel die Anforderung mit »malloc« und die Freigabe mit »free«. Dies ist besonders bei größeren Methoden und der Verschachtelung solcher Anweisungsstrukturen unübersichtlich und wird manchmal als Argument gegen die strukturierte Programmierung und zugunsten des Verlassens von Blöcken mit Sprunganweisungen vorgebracht.
Monolithische strukturierte Ressourcenbehandlung
#include <stdio.h>  /* printf       */
#include <stdlib.h> /* EXIT_SUCCESS */ struct account{ const char * name; double balance; };

int main( void )
{ /* Verwaltung */
int result = EXIT_FAILURE;

struct account * const p = malloc( sizeof *p );
if( p )
{ /* Verwendung */
p->name = "Hans"; p->balance = 3.14;
printf( "%.32s %f\n", p->name, p->balance );
p->balance = 0;
printf( "%.32s %f\n", p->name, p->balance ); /* Verwaltung (Fortsetzung) */
free( p );

result = EXIT_SUCCESS; }
return result; }
Doch tatsächlich ist das Problem bei dieser Vorgehensweise gar nicht die strukturierte Programmierung, sondern die monolithische Programmierung. Die monolithische Funktion kann durch Herauslösen einer „inneren Funktion“ in zwei Funktionen zerlegt werden, von denen die eine sich nur mit der Verwaltung einer Ressource beschäftigt und die andere diese Ressource nur verwendet, ohne sie zu verwalten.
Modulare strukturierte Ressourcenbehandlung
#include <stdio.h>  /* printf       */
#include <stdlib.h> /* EXIT_SUCCESS */ struct account{ const char * name; double balance; }; int use( struct account * const p )
{ p->name = "Hans"; p->balance = 3.14;
printf( "%.32s %f\n", p->name, p->balance );
p->balance = 0;
printf( "%.32s %f\n", p->name, p->balance );
return EXIT_SUCCESS; } int main( void )
{ int result = EXIT_FAILURE;
struct account * const p = malloc( sizeof *p );
if( p )
{ result = use( p );
free( p ); }
return result; }
Man sieht in dem voranstehenden Programm die Gleichheit der Deklaration des Parameters »p« mit dem Anfang der Deklaration der Variablen »p«. Um den Parameter richtig zu deklarieren, kann also einfach ein Teil der Variablendeklaration kopiert werden.
Der Erfolgsstatus  eines Programms kann vom Erfolgsstatus der „inneren Funktion“ abhängen, so daß dieser in der Methode »main« als Ergebnis festgelegt wird, obwohl er bei diesem Programm derzeit stets »EXIT_SUCCESS« ist.

Der extrahierte Teil wird hier „innere Funktion“ genannte, weil er hier aus dem Inneren  einer Anweisungsstruktur stammt, in der es sowohl vor ihm als auch nach ihm noch etwas steht.

Seiteninformationen und Impressum   |   Mitteilungsformular  |   "ram@zedat.fu-berlin.de" (ohne die Anführungszeichen) ist die Netzpostadresse von Stefan Ram.   |   Eine Verbindung zur Stefan-Ram-Startseite befindet sich oben auf dieser Seite hinter dem Text "Stefan Ram".)  |   Der Urheber dieses Textes ist Stefan Ram. Alle Rechte sind vorbehalten. Diese Seite ist eine Veröffentlichung von Stefan Ram. Schlüsselwörter zu dieser Seite/relevant keywords describing this page: Stefan Ram Berlin slrprd slrprd stefanramberlin spellched stefanram722305 stefan_ram:722305 Strukturen in C Stefan Ram, Berlin, and, or, near, uni, online, slrprd, slrprdqxx, slrprddoc, slrprd722305, slrprddef722305, PbclevtugFgrsnaEnz Erklärung, Beschreibung, Info, Information, Hinweis,

Der Urheber dieses Textes ist Stefan Ram. Alle Rechte sind vorbehalten.
https://www.purl.org/stefan_ram/pub/strukturen_in_c