Un array contiene molti valori di un solo tipo. Una struct fa l'opposto: contiene pochi valori di tipi diversi e li tratta come un'unica cosa. Un punto è una x e una y. Un dipendente è un nome, un id e uno stipendio. Senza le struct ti porteresti dietro tre variabili sparse, sperando di non confondere mai lo stipendio di uno con quello di un altro.
Dichiarare un tipo struct
Una dichiarazione di struct indica un tag ed elenca i membri tra parentesi graffe:
struct Point {
int x;
int y;
};
Il punto e virgola dopo la graffa di chiusura è obbligatorio: dimenticarlo produce uno dei messaggi di errore più confusi che un principiante C possa incontrare, perché il compilatore dà la colpa alla riga successiva.
Questo dichiara un tipo, non una variabile. In C il nome completo del tipo è struct Point, parola chiave compresa:
I membri si leggono e si scrivono con l'operatore punto, p.x. Una variabile struct è una variabile normale: vive sullo stack, si può assegnare a un'altra struct dello stesso tipo con = e sparisce alla fine del suo scope.
Scrivere struct Point ovunque diventa faticoso, ed è per questo che quasi tutto il codice C reale avvolge la dichiarazione in un typedef, così che il tipo sia semplicemente Point.
Inizializzare una struct
Puoi riempire una struct nel momento in cui la dichiari. La forma per posizione elenca i valori nell'ordine di dichiarazione:
struct Point p = {3, 4}; // x = 3, y = 4
L'inizializzatore designato invece nomina ogni membro, ed è più chiaro e più sicuro:
Tre cose da notare:
- La forma designata può elencare i membri in qualsiasi ordine, quindi aggiungere o riordinare un membro in seguito non sposta in silenzio i valori negli slot sbagliati.
- Ogni membro che ometti viene inizializzato a zero:
{0}è il modo idiomatico per azzerare un'intera struct. - Un membro
char name[32]è un vero array dentro la struct, quindi la stringa è memorizzata nella struct stessa e non altrove. Significa anche chea.name = "Ada";a posteriori non è lecito; ti servestrcpy(vedi le funzioni sulle stringhe).
Una struct senza inizializzatore non è inizializzata, e leggerne i membri prima di scriverli è comportamento indefinito. = {0} non costa nulla ed elimina l'intera categoria di bug.
Assegnare e confrontare
L'assegnazione tra struct copia ogni membro:
a resta intatta: b ha ricevuto una sua copia. Ma nota cosa il C non ti dà: non esiste == per le struct. if (a == b) non compila. Confrontare significa confrontare i membri:
if (a.x == b.x && a.y == b.y) { /* uguali */ }
Non usare memcmp come scorciatoia. I compilatori inseriscono byte di padding invisibili tra i membri per l'allineamento, e quei byte possono contenere spazzatura, quindi due struct con membri identici possono risultare diverse confrontandole byte per byte.
Array di struct
Dato che una struct è un tipo come gli altri, puoi crearne un array: è il modo standard per tenere una tabella di record.
staff[i].salary si legge come "l'elemento i, poi il suo membro salary": l'indicizzazione lega più forte del punto, quindi non servono parentesi.
Le struct vengono copiate nelle funzioni
Questa è la regola che frega tutti. Quando passi una struct a una funzione, il C la copia. La funzione lavora sulla sua copia privata:
p vale ancora (3, 4). Leggere una struct per valore, come fa distance_from_origin, va benissimo ed è spesso la scelta più chiara per le struct piccole. Ma se la funzione deve modificare la struct del chiamante, o se la struct è abbastanza grande che copiarla sarebbe uno spreco, passa invece un puntatore. È l'argomento della pagina su struct e puntatori.
Restituire una struct per valore funziona allo stesso modo ed è del tutto lecito:
struct Point make_point(int x, int y) {
struct Point p = {x, y};
return p;
}
Struct annidate
Un membro di una struct può essere a sua volta una struct. Concatena i punti per entrare:
b.published.year entra un livello alla volta. L'annidamento è il modo in cui si costruiscono modelli di dati reali in C, e la struct interna è memorizzata dentro quella esterna, non puntata, quindi l'intero Book è un unico blocco contiguo di memoria.
Dimensione, padding e ordine dei membri
sizeof ti dice quanto è grande una struct, e la risposta è spesso maggiore della somma dei suoi membri:
Su una macchina tipica Wasteful occupa 12 byte e Tidy 8, anche se entrambe contengono gli stessi tre membri. Il compilatore inserisce del padding in modo che ogni membro stia a un indirizzo gradito al suo tipo: un int di solito vuole un multiplo di 4. Mettere i membri più grandi prima di quelli più piccoli tende a compattare meglio.
Conta di rado (un array da un milione di elementi, il layout di un pacchetto di rete) e mai per la correttezza del tuo codice. Quello che spiega è perché sizeof ti sorprende, e perché memcmp sulle struct è una cattiva idea.
Domande frequenti
Come si dichiara una struct in C?
Scrivi struct seguito da un nome di tag e da un elenco di membri tra graffe: struct Point { int x; int y; };. Questo dichiara un tipo, non una variabile. Per creare una variabile scrivi poi struct Point p;: in C la parola struct fa parte del nome del tipo, ed è per questo che quasi tutto il codice la abbina a un typedef.
Qual è la differenza tra . e -> nelle struct?
. e -> nelle struct?Usa . quando hai la struct vera e propria (p.x) e -> quando hai un puntatore a essa (ptr->x). La freccia è un'abbreviazione di (*ptr).x. Trovi i dettagli in struct e puntatori.
In C le struct si passano per valore o per riferimento?
Per valore, sempre. Passare una struct a una funzione copia ogni membro, quindi le modifiche dentro la funzione non toccano la variabile del chiamante. Per modificare l'originale, o per evitare di copiare una struct grande, passa invece un puntatore.
Come si inizializza una struct in C?
O per posizione, struct Point p = {3, 4};, oppure con gli inizializzatori designati, struct Point p = {.y = 4, .x = 3};. La forma designata nomina ogni membro, quindi resiste se qualcuno in seguito riordina la struct e lascia a zero i membri non nominati.