Szukaj…


Wprowadzenie

Struktury umożliwiają grupowanie zestawu powiązanych zmiennych różnych typów w jednej jednostce pamięci. Do całej struktury może odnosić się pojedyncza nazwa lub wskaźnik; dostęp do elementów struktury można również uzyskać indywidualnie. Struktury mogą być przekazywane do funkcji i zwracane z funkcji. Są one definiowane za pomocą słowa kluczowego struct .

Proste struktury danych

Typy danych struktury są użytecznym sposobem na spakowanie powiązanych danych i sprawienie, aby zachowywały się jak jedna zmienna.

Deklaracja prostej struct która zawiera dwa elementy int :

struct point 
{
    int x;
    int y; 
};

x i y są nazywane członków (lub pola) point struktury.

Definiowanie i używanie struktur:

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

Struktury mogą być inicjowane przy definicji. Powyższe jest równoważne z:

struct point p = {5, 3};

Struktury mogą być również inicjalizowane przy użyciu wyznaczonych inicjatorów .

Dostęp do pól odbywa się również za pomocą . operator

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

Struktury Typedef

Połączenie typedef ze struct może uczynić kod bardziej przejrzystym. Na przykład:

typedef struct 
{
    int x, y;
} Point;

w przeciwieństwie do:

struct Point 
{
    int x, y;
};

może być zadeklarowany jako:

Point point;

zamiast:

struct Point point;

Jeszcze lepiej jest użyć następujących

typedef struct Point Point;

struct Point 
{
    int x, y;
};

mieć zalety obu możliwych definicji point . Taka deklaracja jest najwygodniejsza, jeśli nauczyłeś się najpierw C ++, w którym możesz pominąć słowo kluczowe struct jeśli nazwa nie jest dwuznaczna.

nazwy typedef dla struktur mogą być w konflikcie z innymi identyfikatorami innych części programu. Niektórzy uważają to za wadę, ale dla większości osób mających struct i inny identyfikator to samo jest dość niepokojące. Notorious jest np POSIX” stat

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

gdzie widzisz funkcję stat która ma jeden argument, który jest struct stat .

typedef 'd structs bez nazwy znacznika zawsze narzuca, że cała deklaracja struct jest widoczna dla kodu, który go używa. Całą deklarację struct należy następnie umieścić w pliku nagłówkowym.

Rozważać:

#include "bar.h"

struct foo 
{
    bar *aBar;
};

Więc z typedef d struct , który ma nazwę znacznika The bar.h plik ma zawsze obejmować całą definicję bar . Jeśli użyjemy

typedef struct bar bar;

w bar.h szczegóły struktury bar można ukryć.

Zobacz Typedef

Wskaźniki do struktur

Gdy masz zmienną zawierającą struct , możesz uzyskać dostęp do jej pól za pomocą operatora kropki ( . ). Jednak jeśli masz wskaźnik do struct , to nie zadziała. Musisz użyć operatora strzałki ( -> ), aby uzyskać dostęp do jego pól. Oto przykład strasznie prostej (niektórzy mogliby powiedzieć „straszne i proste”) implementacji stosu, który używa wskaźników do struct i demonstruje operator strzałki.

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

Członkowie elastycznej macierzy

C99

Deklaracja typu

Struktura z co najmniej jednym elementem może dodatkowo zawierać pojedynczy element macierzy o nieokreślonej długości na końcu struktury. Nazywa się to elastycznym elementem tablicy:

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

Wpływ na rozmiar i wypełnienie

Elastyczny element tablicy jest traktowany jako nieposiadający rozmiaru podczas obliczania rozmiaru struktury, chociaż wypełnienie między tym elementem a poprzednim elementem struktury może nadal istnieć:

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

Uważa się, że elastyczny element tablicy ma niekompletny typ tablicy, więc jego rozmiaru nie można obliczyć za pomocą sizeof .

Stosowanie

Można zadeklarować i zainicjować obiekt o typie struktury zawierającym elastyczny element tablicy, ale nie wolno podejmować prób zainicjowania elastycznego elementu tablicy, ponieważ jest on traktowany tak, jakby nie istniał. Próbowanie tego jest zabronione, co spowoduje błędy kompilacji.

Podobnie, nie powinieneś próbować przypisywać wartości do żadnego elementu elastycznego elementu tablicy podczas deklarowania struktury w ten sposób, ponieważ na końcu struktury może nie być wystarczającej ilości wypełnienia, aby umożliwić dowolne obiekty wymagane przez elastyczny element tablicy. Jednak kompilator niekoniecznie nie pozwoli ci tego zrobić, więc może to prowadzić do nieokreślonego zachowania.

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

Zamiast tego możesz użyć malloc , calloc lub realloc aby przydzielić strukturę z dodatkową pamięcią, a później ją zwolnić, co pozwala na użycie elastycznego elementu macierzy według realloc uznania:

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

„Strack strukturalny”

Elastyczne elementy tablicy nie istniały przed C99 i są traktowane jako błędy. Częstym obejściem jest zadeklarowanie tablicy o długości 1, techniki zwanej „strukturą hack”:

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

Wpłynie to na rozmiar struktury, jednak w przeciwieństwie do prawdziwego elastycznego elementu tablicy:

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

Aby użyć elementu flex jako elastycznego elementu macierzy, należy przydzielić go malloc jak pokazano powyżej, z tym wyjątkiem, że sizeof(*pe1) (lub równoważny sizeof(struct ex1) ) zostałby zastąpiony offsetof(struct ex1, flex) lub dłuższe wyrażenie agnostyczne sizeof(*pe1)-sizeof(pe1->flex) . Alternatywnie możesz odjąć 1 od pożądanej długości tablicy „elastycznej”, ponieważ jest ona już uwzględniona w rozmiarze struktury, zakładając, że pożądana długość jest większa niż 0. Ta sama logika może być zastosowana do innych przykładów użycia.

Zgodność

Jeśli wymagana jest kompatybilność z kompilatorami, które nie obsługują elastycznych elementów tablicy, możesz użyć makra zdefiniowanego jak FLEXMEMB_SIZE poniżej:

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

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

Podczas przydzielania obiektów należy użyć formularza offsetof(struct ex1, flex) aby odnieść się do wielkości struktury (z wyłączeniem elementu elastycznej tablicy), ponieważ jest to jedyne wyrażenie, które pozostanie spójne między kompilatorami obsługującymi elastyczne elementy tablicy i kompilatory nie:

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

Alternatywą jest użycie preprocesora do warunkowego odjęcia 1 od określonej długości. Ze względu na zwiększony potencjał niespójności i ogólny błąd ludzki w tej formie przeniosłem logikę do osobnej funkcji:

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

Przekazywanie struktur do funkcji

W C wszystkie argumenty są przekazywane do funkcji według wartości, w tym struktur. W przypadku małych struktur jest to dobra rzecz, ponieważ oznacza, że nie ma narzutu związanego z dostępem do danych za pomocą wskaźnika. Jednak bardzo ułatwia także przypadkowe przekazanie ogromnej struktury, co powoduje słabą wydajność, szczególnie jeśli programista jest przyzwyczajony do innych języków, w których argumenty są przekazywane przez referencję.

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

Programowanie obiektowe za pomocą struktur

Struktury mogą być używane do implementacji kodu w sposób obiektowy. Struktura jest podobna do klasy, ale brakuje jej funkcji, które normalnie również stanowią część klasy, możemy dodać je jako zmienne składowe wskaźnika funkcji. Aby pozostać przy naszym przykładzie współrzędnych:

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

A teraz implementujący plik C:

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

Przykładem użycia naszej klasy współrzędnych byłoby:

/* 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
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow