Zoeken…


Invoering

Structuren bieden een manier om een reeks gerelateerde variabelen van verschillende typen in een enkele geheugeneenheid te groeperen. Naar de structuur als geheel kan worden verwezen door een enkele naam of aanwijzer; de structuurleden zijn ook afzonderlijk toegankelijk. Structuren kunnen worden doorgegeven aan functies en worden geretourneerd vanuit functies. Ze worden gedefinieerd met behulp van het trefwoord struct .

Eenvoudige datastructuren

Structuurgegevenstypen zijn een handige manier om gerelateerde gegevens te verpakken en te laten gedragen als een enkele variabele.

Een eenvoudige struct met twee int leden:

struct point 
{
    int x;
    int y; 
};

x en y worden de leden (of velden ) van point struct genoemd.

Structen definiëren en gebruiken:

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

Structuren kunnen bij definitie worden geïnitialiseerd. Het bovenstaande komt overeen met:

struct point p = {5, 3};

Structuren kunnen ook worden geïnitialiseerd met behulp van aangewezen initializers .

Toegang krijgen tot velden gebeurt ook met de . operator

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

Typedef Structs

Door typedef combineren met struct kan code duidelijker worden. Bijvoorbeeld:

typedef struct 
{
    int x, y;
} Point;

in tegenstelling tot:

struct Point 
{
    int x, y;
};

kan worden verklaard als:

Point point;

in plaats van:

struct Point point;

Nog beter is om het volgende te gebruiken

typedef struct Point Point;

struct Point 
{
    int x, y;
};

voordeel hebben van beide mogelijke definities van point . Een dergelijke verklaring is het handigst als u eerst C ++ hebt geleerd, waarbij u het sleutelwoord struct kunt weglaten als de naam niet dubbelzinnig is.

typedef namen voor typedef kunnen in conflict zijn met andere ID's van andere delen van het programma. Sommigen beschouwen dit als een nadeel, maar voor de meeste mensen met een struct en een andere identifier is dit behoorlijk verontrustend. Berucht is bijvoorbeeld de stat POSIX

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

waar je een functie stat met één argument dat struct stat .

typedef 'd typedef zonder typedef leggen altijd op dat de hele struct declaratie zichtbaar is voor code die deze gebruikt. De volledige struct aangifte moet dan in een header-bestand worden geplaatst.

Overwegen:

#include "bar.h"

struct foo 
{
    bar *aBar;
};

Dus met een typedef d struct zonder bar.h , moet het bar.h bestand altijd de hele definitie van bar . Als we gebruiken

typedef struct bar bar;

in bar.h , de details van de bar kan worden verborgen structuur.

Zie Typedef

Verwijzingen naar structs

Als u een variabele hebt die een struct , kunt u de velden openen met de puntoperator ( . ). Als u echter een aanwijzer naar een struct , werkt dit niet. U moet de pijloperator ( -> ) gebruiken om toegang te krijgen tot de velden. Hier is een voorbeeld van een vreselijk eenvoudige (sommigen zeggen "vreselijke en eenvoudige") implementatie van een stapel die pointers gebruikt om te struct en de struct demonstreert.

#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);
    }
}

Flexibele matrixleden

C99

Type verklaring

Een structuur met ten minste één lid kan bovendien een enkel array-lid van niet-gespecificeerde lengte aan het einde van de structuur bevatten. Dit wordt een flexibel arraylid genoemd:

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[];
};

Effecten op grootte en opvulling

Een flexibel array-lid wordt behandeld als zijnde geen grootte bij het berekenen van de grootte van een structuur, hoewel opvulling tussen dat lid en het vorige lid van de structuur nog steeds kan bestaan:

/* 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));

Het flexibele sizeof wordt beschouwd als een onvolledig sizeof , dus de grootte kan niet worden berekend met sizeof .

Gebruik

U kunt een object declareren en initialiseren met een structuurtype dat een flexibel arraylid bevat, maar u moet niet proberen het flexibele arraylid te initialiseren, omdat het wordt behandeld alsof het niet bestaat. Het is verboden om dit te proberen en compileerfouten zullen het gevolg zijn.

Evenzo moet u niet proberen een waarde aan een element van een flexibel arraylid toe te wijzen wanneer u een structuur op deze manier declareert, omdat er mogelijk niet voldoende opvulling aan het einde van de structuur is om objecten toe te staan die vereist zijn door het flexibele arraylid. De compiler zal u echter niet noodzakelijk verhinderen dit te doen, dus dit kan leiden tot ongedefinieerd gedrag.

/* 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 */

U kunt in plaats daarvan ervoor kiezen om malloc , calloc of realloc te gebruiken om de structuur met extra opslag toe te wijzen en later vrij te maken, waardoor u het flexibele arraylid kunt gebruiken zoals u wilt:

/* 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

De 'struct hack'

Flexibele arrayleden bestonden niet vóór C99 en worden als fouten behandeld. Een gebruikelijke oplossing is om een array met lengte 1 aan te geven, een techniek die de 'struct hack' wordt genoemd:

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

Dit heeft echter invloed op de grootte van de structuur, in tegenstelling tot een echt flexibel arraylid:

/* 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));

Als u het flex lid wilt gebruiken als flexibel sizeof(*pe1) , wijst u het toe aan malloc zoals hierboven weergegeven, behalve dat sizeof(*pe1) (of de equivalente sizeof(struct ex1) ) zou worden vervangen door offsetof(struct ex1, flex) of de langere, type-agnostische expressie sizeof(*pe1)-sizeof(pe1->flex) . Als alternatief kunt u 1 aftrekken van de gewenste lengte van de "flexibele" array omdat deze al is opgenomen in de structuurgrootte, ervan uitgaande dat de gewenste lengte groter is dan 0. Dezelfde logica kan worden toegepast op de andere gebruiksvoorbeelden.

Compatibiliteit

Als compatibiliteit met compilers die geen flexibele FLEXMEMB_SIZE ondersteunen gewenst is, kunt u een macro gebruiken die is gedefinieerd als FLEXMEMB_SIZE hieronder:

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

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

Wanneer u objecten offsetof(struct ex1, flex) , moet u het offsetof(struct ex1, flex) om te verwijzen naar de structuurgrootte (exclusief het flexibele offsetof(struct ex1, flex) ), omdat dit de enige expressie is die consistent blijft tussen compilers die flexibele arrayleden ondersteunen en compilers die wel niet:

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

Het alternatief is om de preprocessor te gebruiken om 1 voorwaardelijk af te trekken van de opgegeven lengte. Vanwege het verhoogde potentieel voor inconsistentie en algemene menselijke fouten in deze vorm, heb ik de logica verplaatst naar een afzonderlijke functie:

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);

Structs doorgeven aan functies

In C worden alle argumenten doorgegeven aan functies op waarde, inclusief structs. Voor kleine constructen is dit een goede zaak omdat het betekent dat er geen overhead is om toegang te krijgen tot de gegevens via een pointer. Het maakt het echter ook heel gemakkelijk om per ongeluk een enorme structuur door te geven die resulteert in slechte prestaties, vooral als de programmeur gewend is aan andere talen waar argumenten ter referentie worden doorgegeven.

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;
}

Objectgebaseerd programmeren met behulp van structs

Structuren kunnen worden gebruikt om code objectgeoriënteerd te implementeren. Een struct is vergelijkbaar met een klasse, maar mist de functies die normaal ook deel uitmaken van een klasse, we kunnen deze toevoegen als variabelen voor functiepointerleden. Om bij onze coördinaten te blijven:

/* 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);

En nu het implementerende C-bestand:

/* 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");
    }
}

Een voorbeeld van het gebruik van onze coördinatenklasse is:

/* 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
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow