Suche…


Einführung

Strukturen bieten die Möglichkeit, eine Gruppe verwandter Variablen verschiedener Typen in einer einzigen Speichereinheit zu gruppieren. Die Struktur als Ganzes kann durch einen einzelnen Namen oder Zeiger referenziert werden. Die Strukturelemente können auch einzeln aufgerufen werden. Strukturen können an Funktionen übergeben und von Funktionen zurückgegeben werden. Sie werden mit dem Schlüsselwort struct .

Einfache Datenstrukturen

Strukturdatentypen sind eine nützliche Methode, um verwandte Daten zu packen und sich wie eine einzelne Variable zu verhalten.

Eine einfache struct , die zwei int Mitglieder enthält:

struct point 
{
    int x;
    int y; 
};

x und y werden die Mitglieder (oder Felder ) der point .

Definieren und Verwenden von Strukturen:

struct point p;    // declare p as a point struct
p.x = 5;           // assign p member variables
p.y = 3;

Strukturen können bei der Definition initialisiert werden. Das obige ist äquivalent zu:

struct point p = {5, 3};

Strukturen können auch mit bestimmten Initialisierern initialisiert werden .

Der Zugriff auf Felder erfolgt ebenfalls mit . Operator

printf("point is (x = %d, y = %d)", p.x, p.y);

Typedef-Strukturen

Die Kombination von typedef mit struct kann den Code klarer machen. Zum Beispiel:

typedef struct 
{
    int x, y;
} Point;

im Gegensatz zu:

struct Point 
{
    int x, y;
};

könnte erklärt werden als:

Point point;

anstatt:

struct Point point;

Noch besser ist es, folgendes zu verwenden

typedef struct Point Point;

struct Point 
{
    int x, y;
};

beide mögliche Definitionen von point . Eine solche Deklaration ist am bequemsten, wenn Sie zuerst C ++ gelernt haben, wobei Sie das Schlüsselwort struct auslassen können, wenn der Name nicht eindeutig ist.

typedef Namen für Strukturen können mit anderen Bezeichnern anderer Programmteile in Konflikt stehen. Einige halten dies für einen Nachteil, aber für die meisten Menschen, die über eine struct und eine andere Kennung struct ist dies ziemlich störend. Notorious ist zB POSIX‘ stat

int stat(const char *pathname, struct stat *buf);

wo sehen Sie eine Funktion stat , das ein Argument hat , das ist struct stat .

typedef -Strukturen ohne Tag-Namen ist immer die gesamte struct Deklaration für den Code sichtbar, der sie verwendet. Die gesamte struct Deklaration muss dann in einer Headerdatei abgelegt werden.

Erwägen:

#include "bar.h"

struct foo 
{
    bar *aBar;
};

Bei einer typedef d- struct , die keinen Tag-Namen hat, muss die Datei bar.h immer die gesamte Definition der bar . Wenn wir verwenden

typedef struct bar bar;

In bar.h können die Details der bar ausgeblendet werden.

Siehe Typedef

Verweise auf structs

Wenn Sie über eine Variable verfügen, die eine struct , können Sie mit dem Punktoperator ( . ) Auf ihre Felder zugreifen. Wenn Sie jedoch einen Zeiger auf eine struct , funktioniert dies nicht. Sie müssen den Pfeiloperator ( -> ) verwenden, um auf die Felder zuzugreifen. Hier ist ein Beispiel einer schrecklich einfachen (manche sagen "schrecklich und einfach") Implementierung eines Stacks, der Zeiger für struct und den Pfeiloperator veranschaulicht.

#include <stdlib.h>
#include <stdio.h>

/* structs */
struct stack
{
    struct node *top;
    int size;
};

struct node
{
    int data;
    struct node *next;
};

/* function declarations */
int push(int, struct stack*);
int pop(struct stack*);
void destroy(struct stack*);

int main(void)
{
    int result = EXIT_SUCCESS;

    size_t i;

    /* allocate memory for a struct stack and record its pointer */
    struct stack *stack = malloc(sizeof *stack);
    if (NULL == stack)
    {
        perror("malloc() failed");
        return EXIT_FAILURE;
    }

    /* initialize stack */
    stack->top = NULL;
    stack->size = 0;

    /* push 10 ints */
    {
        int data = 0;
        for(i = 0; i < 10; i++)
        {
            printf("Pushing: %d\n", data);
            if (-1 == push(data, stack))
            {
                perror("push() failed");
                result = EXIT_FAILURE;
                break;
            }

            ++data;
        }
    }

    if (EXIT_SUCCESS == result)
    {
        /* pop 5 ints */
        for(i = 0; i < 5; i++)
        {
            printf("Popped: %i\n", pop(stack));
        }
    }

    /* destroy stack */
    destroy(stack);

    return result;
}

/* Push a value onto the stack. */
/* Returns 0 on success and -1 on failure. */
int push(int data, struct stack *stack)
{
    int result = 0;

    /* allocate memory for new node */
    struct node *new_node = malloc(sizeof *new_node);
    if (NULL == new_node)
    {
        result = -1;
    }
    else
    {
        new_node->data = data;
        new_node->next = stack->top;
        stack->top = new_node;
        stack->size++;
    }

    return result;
}

/* Pop a value off of the stack. */
/* Returns the value popped off the stack */
int pop(struct stack *stack)
{
    struct node *top = stack->top;
    int data = top->data;
    stack->top = top->next;
    stack->size--;
    free(top);
    return data;
}

/* destroy the stack */
void destroy(struct stack *stack)
{
    /* free all pointers */
    while(stack->top != NULL)
    {
        pop(stack);
    }
}

Flexible Array-Mitglieder

C99

Typenerklärung

Eine Struktur mit mindestens einem Member kann zusätzlich ein einzelnes Array-Member mit nicht näher bezeichneter Länge am Ende der Struktur enthalten. Dies wird als flexibles Arraymitglied bezeichnet:

struct ex1 
{
    size_t foo;
    int flex[];
};

struct ex2_header 
{
    int foo;
    char bar;
};

struct ex2 
{
    struct ex2_header hdr;
    int flex[];
};

/* Merged ex2_header and ex2 structures. */
struct ex3 
{
    int foo;
    char bar;
    int flex[];
};

Auswirkungen auf Größe und Polsterung

Ein flexibles Array-Member wird bei der Berechnung der Größe einer Struktur als ohne Größe behandelt. Es kann jedoch immer noch eine Auffüllung zwischen diesem Member und dem vorherigen Member der Struktur vorhanden sein:

/* Prints "8,8" on my machine, so there is no padding. */
printf("%zu,%zu\n", sizeof(size_t), sizeof(struct ex1));

/* Also prints "8,8" on my machine, so there is no padding in the ex2 structure itself. */
printf("%zu,%zu\n", sizeof(struct ex2_header), sizeof(struct ex2));

/* Prints "5,8" on my machine, so there are 3 bytes of padding. */
printf("%zu,%zu\n", sizeof(int) + sizeof(char), sizeof(struct ex3));

Das flexible Array- sizeof hat einen unvollständigen Array-Typ, daher kann seine Größe nicht mit sizeof berechnet werden.

Verwendungszweck

Sie können ein Objekt mit einem Strukturtyp deklarieren und initialisieren, der ein flexibles Arraymitglied enthält. Sie dürfen jedoch nicht versuchen, das flexible Arraymitglied zu initialisieren, da es so behandelt wird, als wäre es nicht vorhanden. Es ist verboten, dies zu versuchen, und es werden Kompilierungsfehler ausgegeben.

Ebenso sollten Sie nicht versuchen, einem Element eines flexiblen Arraymitglieds einen Wert zuzuweisen, wenn Sie eine Struktur auf diese Weise deklarieren, da am Ende der Struktur möglicherweise nicht genügend Auffüllung vorhanden ist, um alle vom flexiblen Arraymitglied benötigten Objekte zuzulassen. Der Compiler hindert Sie jedoch nicht unbedingt daran, dies kann zu undefiniertem Verhalten führen.

/* invalid: cannot initialize flexible array member */
struct ex1 e1 = {1, {2, 3}};
/* invalid: hdr={foo=1, bar=2} OK, but cannot initialize flexible array member */
struct ex2 e2 = {{1, 2}, {3}};
/* valid: initialize foo=1, bar=2 members */
struct ex3 e3 = {1, 2};

e1.flex[0] = 3; /* undefined behavior, in my case */
e3.flex[0] = 2; /* undefined behavior again */
e2.flex[0] = e3.flex[0]; /* undefined behavior */

Sie können stattdessen malloc , calloc oder realloc , um die Struktur mit zusätzlichem Speicher zuzuordnen und später calloc , calloc Sie das flexible Array- calloc nach Belieben verwenden können:

/* valid: allocate an object of structure type `ex1` along with an array of 2 ints */
struct ex1 *pe1 = malloc(sizeof(*pe1) + 2 * sizeof(pe1->flex[0]));

/* valid: allocate an object of structure type ex2 along with an array of 4 ints */
struct ex2 *pe2 = malloc(sizeof(struct ex2) + sizeof(int[4]));

/* valid: allocate 5 structure type ex3 objects along with an array of 3 ints per object */
struct ex3 *pe3 = malloc(5 * (sizeof(*pe3) + sizeof(int[3])));

pe1->flex[0] = 3; /* valid */
pe3[0]->flex[0] = pe1->flex[0]; /* valid */
C99

Der "struct hack"

Flexible Array-Mitglieder waren vor C99 nicht vorhanden und werden als Fehler behandelt. Eine gängige Problemumgehung besteht darin, ein Array der Länge 1 zu deklarieren, eine Technik, die als "struct hack" bezeichnet wird:

struct ex1 
{
    size_t foo;
    int flex[1];
};

Dies hat jedoch Auswirkungen auf die Größe der Struktur, im Gegensatz zu einem echten flexiblen Arraymitglied:

/* Prints "8,4,16" on my machine, signifying that there are 4 bytes of padding. */
printf("%d,%d,%d\n", (int)sizeof(size_t), (int)sizeof(int[1]), (int)sizeof(struct ex1));

Um die Verwendung flex als ein flexibles Element - Array, würde man es mit zuteilen malloc , wie oben gezeigt, mit der Ausnahme , dass sizeof(*pe1) (oder das Äquivalent sizeof(struct ex1) ) würde ersetzt werden durch offsetof(struct ex1, flex) oder die längere sizeof(*pe1)-sizeof(pe1->flex) Expressionsgröße von sizeof(*pe1)-sizeof(pe1->flex) . Alternativ können Sie 1 von der gewünschten Länge des "flexiblen" Arrays subtrahieren, da es bereits in der Strukturgröße enthalten ist, sofern die gewünschte Länge größer als 0 ist. Die gleiche Logik kann auf die anderen Verwendungsbeispiele angewendet werden.

Kompatibilität

Wenn Kompatibilität mit Compilern gewünscht wird, die keine flexiblen Array-Mitglieder unterstützen, können Sie ein Makro verwenden, das wie FLEXMEMB_SIZE definiert ist: FLEXMEMB_SIZE :

#if __STDC_VERSION__ < 199901L
#define FLEXMEMB_SIZE 1
#else
#define FLEXMEMB_SIZE /* nothing */
#endif

struct ex1 
{
    size_t foo;
    int flex[FLEXMEMB_SIZE];
};

Beim offsetof(struct ex1, flex) Objekten sollten Sie das offsetof(struct ex1, flex) verwenden, um auf die Strukturgröße zu verweisen (mit Ausnahme des flexiblen Array-Mitglieds), da dies der einzige Ausdruck ist, der zwischen Compilern, die flexible Array-Mitglieder unterstützen, und den Compilern, die dies tun, konsistent bleibt nicht:

struct ex1 *pe10 = malloc(offsetof(struct ex1, flex) + n * sizeof(pe10->flex[0]));

Die Alternative ist, den Präprozessor zu verwenden, um 1 von der angegebenen Länge bedingt abzuziehen. Aufgrund des erhöhten Potenzials für Inkonsistenz und allgemeines menschliches Versagen in dieser Form habe ich die Logik in eine separate Funktion verschoben:

struct ex1 *ex1_alloc(size_t n)
{
    struct ex1 tmp;
#if __STDC_VERSION__ < 199901L
    if (n != 0)
        n--;
#endif
    return malloc(sizeof(tmp) + n * sizeof(tmp.flex[0]));
}
...

/* allocate an ex1 object with "flex" array of length 3 */
struct ex1 *pe1 = ex1_alloc(3);

Übergabe von Strukturen an Funktionen

In C werden alle Argumente nach Wert an Funktionen übergeben, einschließlich Strukturen. Für kleine Strukturen ist dies eine gute Sache, da dies bedeutet, dass kein Zugriff auf die Daten durch einen Zeiger entsteht. Es macht es jedoch auch sehr leicht, versehentlich eine große Struktur zu übergeben, was zu einer schlechten Leistung führt, insbesondere wenn der Programmierer an andere Sprachen gewöhnt ist, in denen Argumente als Referenz übergeben werden.

struct coordinates
{
    int x;
    int y;
    int z;
};

// Passing and returning a small struct by value, very fast
struct coordinates move(struct coordinates position, struct coordinates movement)
{
    position.x += movement.x;
    position.y += movement.y;
    position.z += movement.z;
    return position;
}

// A very big struct
struct lotsOfData
{
    int param1;
    char param2[80000];
};

// Passing and returning a large struct by value, very slow!
// Given the large size of the struct this could even cause stack overflow
struct lotsOfData doubleParam1(struct lotsOfData value)
{
    value.param1 *= 2;
    return value;
}

// Passing the large struct by pointer instead, fairly fast
void doubleParam1ByPtr(struct lotsOfData *value)
{
    value->param1 *= 2;
}

Objektbasierte Programmierung mit Strukturen

Strukturen können verwendet werden, um Code objektorientiert zu implementieren. Eine Struktur ähnelt einer Klasse, es fehlen jedoch die Funktionen, die normalerweise auch Teil einer Klasse sind. Wir können diese als Funktionszeiger-Member-Variablen hinzufügen. Um bei unserem Koordinatenbeispiel zu bleiben:

/* coordinates.h */

typedef struct coordinate_s 
{
    /* Pointers to method functions */
    void (*setx)(coordinate *this, int x);
    void (*sety)(coordinate *this, int y);
    void (*print)(coordinate *this);
    /* Data */
    int x;
    int y;
} coordinate;

/* Constructor */
coordinate *coordinate_create(void);
/* Destructor */
void coordinate_destroy(coordinate *this);

Und nun die implementierende C-Datei:

/* coordinates.c */

#include "coordinates.h"
#include <stdio.h>
#include <stdlib.h>

/* Constructor */
coordinate *coordinate_create(void)
{
    coordinate *c = malloc(sizeof(*c));
    if (c != 0)
    {
        c->setx = &coordinate_setx;
        c->sety = &coordinate_sety;
        c->print = &coordinate_print;
        c->x = 0;
        c->y = 0;
    }
    return c;
}

/* Destructor */
void coordinate_destroy(coordinate *this)
{
    if (this != NULL)
    {
        free(this);  
    }  
}

/* Methods */
static void coordinate_setx(coordinate *this, int x)
{
    if (this != NULL)
    {    
        this->x = x;
    }
}

static void coordinate_sety(coordinate *this, int y)
{
    if (this != NULL)
    {
        this->y = y;
    }
}

static void coordinate_print(coordinate *this)
{
    if (this != NULL)
    {
        printf("Coordinate: (%i, %i)\n", this->x, this->y);
    }
    else
    {
        printf("NULL pointer exception!\n");
    }
}

Ein Beispiel für die Verwendung unserer Koordinatenklasse wäre:

/* main.c */

#include "coordinates.h"
#include <stddef.h>

int main(void) 
{
    /* Create and initialize pointers to coordinate objects */
    coordinate *c1 = coordinate_create();
    coordinate *c2 = coordinate_create();
    
    /* Now we can use our objects using our methods and passing the object as parameter */
    c1->setx(c1, 1);
    c1->sety(c1, 2);

    c2->setx(c2, 3);
    c2->sety(c2, 4);

    c1->print(c1);
    c2->print(c2);

    /* After using our objects we destroy them using our "destructor" function */
    coordinate_destroy(c1);
    c1 = NULL;
    coordinate_destroy(c2);
    c2 = NULL;

    return 0;
}


Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow