Strutture

Gli array, come si è visto, si possono vedere come un insieme di variabili, tutte dello stesso tipo. Dato un array, le variabili al suo interno sono identificate da un numero intero. Le strutture sono in un certo senso un concetto simile: una struttura è un insieme di variabili che possono anche avere tipi diversi. Al contrario dei vettori, per accedere agli elementi di una struttura si usa un nome invece che un numero.

Array Strutture
Tipi degli elementi tutti uguali qualsiasi tipo
Accesso al singolo elemento attraverso un numero attraverso un nome

Supponiamo di voler memorizzare in una variabile i dati di alcune persone. Per ogni persona, ci serve una stringa per il nome, una stringa per il cognome e un intero per l'età. Un semplice programma che usa i dati per una singola persona è il seguente nostruct.c

/*
  Dati di una persona, senza usare le strutture.
*/

#include<stdlib.h>

/* stampa i dati di una persona */

void stampapersona(char *nome, char *cognome, int eta) {
  printf("%s %s, eta' %d\n", nome, cognome, eta);
}


/* main */

int main() {
  char *nome;
  char *cognome;
  int eta;

  nome="Diego";
  cognome="Calvanese";
  eta=12;

  stampapersona(nome, cognome, eta);

  return 0;
}

Questo programma non presenta nessuna difficoltà, e funziona perfettamente: il nome e il congnome della persona vengono messi nelle variabili di tipo char * che abbiamo chiamato nome e cognome, rispettivamente. L'età viene invece memorizzata nella variabile intera eta. Questo programma presenta però un problema di leggibilità, e di modificabilità.

leggibilità
non risulta subito chiaro che le tre variabili nome, cognome, ed eta sono tre attributi dello stesso oggetto;
modificabilità
se vogliamo aggiungere la data di nascita, bisogna aggiungere una variabile, e poi modificare tutte le chiamate di funzione.

Il primo problema può causare errori se per esempio si chiama la funzione stampapersona commettendo l'errore di passare il nome di una persona, il cognome di un'altra e l'età di una altra persona ancora. Il secondo problema può rendere difficile modificare il codice, e questo può a sua volta portare a errori se il codice non viene modificato nel modo giusto.

Una soluzione a questi problemi è quella di definire una variabile di tipo persona, che contiene al suo interno tutti i dati di una persona, ossia nome, cognome ed età. Se non ci fosse l'età, questo si potrebbe fare con un vettore:

  char *p[2];

In questo modo, potremmo assumere che p[0] è il nome di una persona, mentre p[1] è il suo cognome. Si può quindi dire che la variabile p contiene sia il nome che il cognome di una singola persona. Il problema che resta è la variabile età, che è di tipo diverso dalle altre due: intero invece che puntatore a carattere.

Il C mette a disposizione la primitiva struct che consente di definire una variabile come un gruppo di variabili anche di tipo diverso. La definizione di una struttura avviene nel modo seguente:

struct Persona {
  char *nome;
  char *cognome;
  int eta;
};

Questa definizione ci dice che servono delle variabili composte, i cui elementi sono due puntatori a caratteri e un intero. In altre parole, dopo aver messo questo frammento di codice, possiamo definire delle variabili che sono composte da questi tre elementi. Per dichiarare che una variabile rappresenta una persona, ossia è composta da questi tre dati, usiamo una dichiarazione di questo tipo:

struct Persona x;

Si faccia attenzione: la prima dichiarazione dice che ci occorrono delle variabili composte da due stringhe e un intero. La seconda dice che la variabile x è una di queste variabili. Quindi, se si vogliono dichiarare tre variabili, ognuna delle quali rappresenta una persona, la prima dichiarazione si mette una sola, volta, e poi si dichiarano separatamente le tre variabili. Le seguenti dichiarazioni sono ``quasi'' equivalenti.

/* definizione della
struttura Persona */
struct Persona {
  char *nome;
  char *cognome;
  int eta;
};

/* dichiara tre
variabili strutture */
struct Persona x;
struct Persona a;
struct Persona w;
/* dichiarazione di x */
char *x_nome;
char *x_cognome;
int x_eta;

/* dichiarazione di y */
char *y_nome;
char *y_cognome;
int y_eta;

/* dichiarazione di z */
char *z_nome;
char *z_cognome;
int z_eta;

Si noti che in C non è in effetti possibile dichiarare delle variabili che contengono il punto nel nome. Questo è possibile solo dichiarando nelle strutture, in cui il punto separa il nome della variabile dal nome dell'elemento della struttura. Le due dichiarazioni di sopra sono quasi equivalenti nel senso che dove quella di destra dichiara una variabile stringa x_nome, quella di destra implicitamente definisce x.nome come una variabile stringa, ecc.

Per poter usare (leggere o scrivere) le tre componenti di una di queste variabili, si usa il nome della variabile seguita da un punto e dal nome che abbiamo scritto dopo il tipo nella definizione di struttura. In altre parole, dopo le dichiarazioni di sopra, sono definite le due variabili x.nome e x.cognome di tipo stringa, e la variabile x.eta di tipo intero.

La dichiarazione della struttura Persona contiene al suo interno tre righe, ognuna delle quali ha esattamente la forma di una dichiarazione di variabile. Tuttavia, nessuna di queste dichiarazioni definisce una variabile. Al contrario, servono a indicare che, ogni volta che si definisce una nuova variabile struttura con quel nome, essa è costituita da tre parti, e fornisce il tipo e il nome di ciasuna delle tre parti. Si noti la differenza fra dichiarazione di struttura, di variabile, e di variabile struttura:

                /* definizione della struttura Persona */
struct Persona {
  char *nome;		/* indica che ogni variabile struttura ha
			una parte di tipo char *, e si puo' accedere
			a questa parte usando .nome */
  char *cognome;	/* ogni variabile struttura contiene un char *
			a cui si puo' accedere con .cognome */
  int eta;		/* ogni variabile struttura contiene un intero
			a cui si puo' accedere con .eta */
};

int max_eta;		/* definisce una variabile di tipo int */

struct Persona x;	/* definisce la variabile struttura x, ed
			e' equivalente a definire tre variabili x.nome,
			x.cognome, e x.eta (le prime due di tipo char *,
			la terza di tipo int */

In altre parole, quello che sta dentro la definizione di struttura non è la dichiarazione di una variabile, ma indica che quando si dichiara una variabile struttura si dichiarano automaticamente le tre variabili che hanno il nome della variabile seguito da .nome, da .cognome e da .eta, di tipi char * e int.

È possibile passare una intera struttura a una funzione: basta che la definizione di struttura preceda la definizione della funzione. Nella funzione la definizione del parametro è uguale alla dichiarazione di una variabile, come sempre. Nell'esempio, possiamo fare in modo che la funzione stampapersona prenda come parametro una variabile struttura invece delle tre variabili separate. Questo si fa semplicemente mettendo fra parentesi tonde una dichiarazione di variabile struttura:

void stampapersona(struct Persona p) { ...

All'interno della procedura risulta definita una variabile struttura p, i cui campi coincidono con quelli della variabile con cui la procedura è stata chiamata. In altre parole, le variabili struttura si comportano come tutte le altre variabili anche rispetto alle chiamate di procedura: si può passare una variabile struttura a una funzione, e la cosa avviene come per tutte la altre variabili.

Il programma construct.c è una versione modificata del precedente, in cui si usa una struttura invece di tre variabili.

/*
  Definizione di una struttura che contiene tutti
i dati di una persona.
*/

#include<stdlib.h>

/* definizione della struttura */

struct Persona {
  char *nome;
  char *cognome;
  int eta;
};


/* stampa i dati di una persona */

void stampapersona(struct Persona p) {
  printf("%s %s, eta' %d\n", p.nome, p.cognome, p.eta);
}


/* main */

int main() {
  struct Persona x;

  x.nome="Diego";
  x.cognome="Calvanese";
  x.eta=12;

  stampapersona(x);

  return 0;
}

Per quello che riguarda la modificabilità, notiamo che aggiungere una nuova componente a una struttura comporta solo la modifica della dichiarazione della struttura. Per esempio, per aggiungere la data di nascita, basta questo:

struct Persona {
  char *nome;
  char *cognome;
  int eta;
  int giorno_nascita;
  int mese_nascita;
  int anno_nascita;
};

e automaticamente tutte le variabili struttura dichiarate nel programma avranno una componente di tipo int e nome giorno_nascita, ecc.

Terminologia: le componenti di una struttura si chiamano campi della struttura. Quindi, il campo di una struttura è una parte delle variabili struttura. Nell'esempio di sopra, la struttura Persona ha tre campi: nome, cognome e eta.