C Language
Structs
Ricerca…
introduzione
Le strutture forniscono un modo per raggruppare una serie di variabili correlate di tipi diversi in una singola unità di memoria. La struttura nel suo insieme può essere referenziata da un singolo nome o puntatore; anche i membri della struttura possono essere consultati individualmente. Le strutture possono essere passate alle funzioni e restituite dalle funzioni. Sono definiti utilizzando la struct
parole chiave.
Strutture dati semplici
I tipi di dati strutturali sono utili per raggruppare i dati correlati e comportarsi come una singola variabile.
Dichiarazione di una struct
semplice che contiene due membri int
:
struct point
{
int x;
int y;
};
x
e y
sono chiamati membri (o campi ) della struttura point
.
Definire e usare le strutture:
struct point p; // declare p as a point struct
p.x = 5; // assign p member variables
p.y = 3;
Le strutture possono essere inizializzate alla definizione. Quanto sopra è equivalente a:
struct point p = {5, 3};
Le strutture possono anche essere inizializzate usando gli inizializzatori designati .
L'accesso ai campi viene fatto anche usando il .
operatore
printf("point is (x = %d, y = %d)", p.x, p.y);
Typedef Structs
Combinare typedef
con struct
può rendere il codice più chiaro. Per esempio:
typedef struct
{
int x, y;
} Point;
al contrario di:
struct Point
{
int x, y;
};
potrebbe essere dichiarato come:
Point point;
invece di:
struct Point point;
Ancora meglio è usare il seguente
typedef struct Point Point;
struct Point
{
int x, y;
};
avere il vantaggio di entrambe le possibili definizioni di point
. Tale dichiarazione è più comoda se hai imparato prima il C ++, dove puoi omettere la parola chiave struct
se il nome non è ambiguo.
typedef
nomi typedef
per le strutture potrebbero essere in conflitto con altri identificatori di altre parti del programma. Alcuni considerano questo uno svantaggio, ma per la maggior parte delle persone che hanno una struct
e un altro identificatore lo stesso è piuttosto fastidioso. Notoria è ad esempio POSIX ' stat
int stat(const char *pathname, struct stat *buf);
dove vedi una funzione stat
che ha un argomento che è struct stat
.
typedef
'd senza nome di tag impongono sempre che l'intera dichiarazione struct
sia visibile al codice che la usa. L'intera dichiarazione della struct
deve quindi essere inserita in un file di intestazione.
Tenere conto:
#include "bar.h"
struct foo
{
bar *aBar;
};
Quindi con una typedef
d struct
che non ha il nome del tag, il file bar.h
deve sempre includere l'intera definizione di bar
. Se usiamo
typedef struct bar bar;
in bar.h
, i dettagli della struttura della bar
possono essere nascosti.
Vedi Typedef
Puntatori alle strutture
Quando hai una variabile contenente una struct
, puoi accedere ai suoi campi usando l'operatore punto ( .
). Tuttavia, se hai un puntatore a una struct
, questo non funzionerà. Devi usare l'operatore della freccia ( ->
) per accedere ai suoi campi. Ecco un esempio di un'implementazione terribilmente semplice (alcuni potrebbero dire "terribile e semplice") di uno stack che utilizza i puntatori per struct
e dimostrare l'operatore della freccia.
#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);
}
}
Membri di array flessibili
Tipo di dichiarazione
Una struttura con almeno un membro può contenere inoltre un singolo membro di matrice di lunghezza non specificata alla fine della struttura. Questo è chiamato membro di un array flessibile:
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[];
};
Effetti su dimensioni e riempimento
Un membro di array flessibile viene considerato privo di dimensioni quando si calcola la dimensione di una struttura, sebbene il riempimento tra quel membro e il membro precedente della struttura possa ancora esistere:
/* 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));
Si considera che il membro di array flessibile abbia un tipo di array incompleto, quindi la sua dimensione non può essere calcolata utilizzando sizeof
.
uso
È possibile dichiarare e inizializzare un oggetto con un tipo di struttura contenente un membro di array flessibile, ma non si deve tentare di inizializzare il membro di matrice flessibile poiché viene trattato come se non esistesse. È vietato provare a fare ciò e si verificheranno errori di compilazione.
Allo stesso modo, non si dovrebbe tentare di assegnare un valore a qualsiasi elemento di un membro di matrice flessibile quando si dichiara una struttura in questo modo poiché potrebbe non esserci abbastanza riempimento alla fine della struttura per consentire qualsiasi oggetto richiesto dal membro di array flessibile. Tuttavia, il compilatore non ti impedirà necessariamente di farlo, quindi questo può portare a un comportamento indefinito.
/* 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 */
Puoi invece scegliere di usare malloc
, calloc
o realloc
per allocare la struttura con spazio di archiviazione aggiuntivo e in seguito liberarla, che ti consente di utilizzare il membro della matrice flessibile come desideri:
/* 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 */
La 'struct hack'
I membri di array flessibili non esistevano prima di C99 e sono considerati come errori. Una soluzione comune consiste nel dichiarare una matrice di lunghezza 1, una tecnica chiamata 'struct hack':
struct ex1
{
size_t foo;
int flex[1];
};
Ciò influenzerà la dimensione della struttura, tuttavia, a differenza di un vero membro della matrice flessibile:
/* 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));
Per usare il membro flex
come membro di un array flessibile, lo sizeof(*pe1)
con malloc
come mostrato sopra, eccetto che sizeof(*pe1)
(o la dimensione equivalente sizeof(struct ex1)
) verrebbe sostituito con offsetof(struct ex1, flex)
o il più lungo, type-agnostic expression sizeof(*pe1)-sizeof(pe1->flex)
. In alternativa, è possibile sottrarre 1 dalla lunghezza desiderata della matrice "flessibile" poiché è già inclusa nella dimensione della struttura, assumendo che la lunghezza desiderata sia maggiore di 0. La stessa logica può essere applicata agli altri esempi di utilizzo.
Compatibilità
Se si desidera la compatibilità con i compilatori che non supportano membri di array flessibili, è possibile utilizzare una macro definita come FLEXMEMB_SIZE
seguito:
#if __STDC_VERSION__ < 199901L
#define FLEXMEMB_SIZE 1
#else
#define FLEXMEMB_SIZE /* nothing */
#endif
struct ex1
{
size_t foo;
int flex[FLEXMEMB_SIZE];
};
Quando si assegnano gli oggetti, è necessario utilizzare il offsetof(struct ex1, flex)
per fare riferimento alle dimensioni della struttura (escluso il membro della matrice flessibile) poiché è l'unica espressione che rimarrà coerente tra i compilatori che supportano membri di array flessibili e compilatori che eseguono non:
struct ex1 *pe10 = malloc(offsetof(struct ex1, flex) + n * sizeof(pe10->flex[0]));
L'alternativa è usare il preprocessore per sottrarre condizionatamente 1 dalla lunghezza specificata. A causa dell'aumentato potenziale di incoerenza e errore umano generale in questa forma, ho spostato la logica in una funzione separata:
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);
Passare le strutture alle funzioni
In C, tutti gli argomenti vengono passati alle funzioni in base al valore, incluse le strutture. Per le piccole strutture, questa è una buona cosa in quanto significa che non vi è alcun sovraccarico di accesso ai dati attraverso un puntatore. Tuttavia, rende anche molto facile passare accidentalmente un'enorme struttura con prestazioni scadenti, in particolare se il programmatore viene utilizzato in altre lingue in cui gli argomenti vengono passati per riferimento.
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;
}
Programmazione basata su oggetti mediante le strutture
Le strutture possono essere utilizzate per implementare il codice in modo orientato agli oggetti. Una struct è simile a una classe, ma mancano le funzioni che normalmente fanno parte di una classe, possiamo aggiungerle come variabili dei membri del puntatore di funzione. Per rimanere con il nostro esempio di coordinate:
/* 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);
E ora il file C di implementazione:
/* 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");
}
}
Un esempio di utilizzo della nostra classe di coordinate sarebbe:
/* 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;
}