Struct come tipo di dato in Linguaggio C

Nella lezione precedente abbiamo visto come dichiarare una variabile struct in linguaggio C composta da più campi di tipo eterogeneo.

In questa lezione vedremo come definire un tipo di dato a partire da una struttura dati in linguaggio C. In questo modo saremo in grado di dichiarare variabili multiple a partire da un unico tipo di dato.

Il vantaggio è che le variabili dichiarate in questo modo saranno compatibili tra di loro e saremo in grado di assegnare il contenuto di una struttura dati ad un'altra.

Il problema della dichiarazione di una struct

Nella lezione precedente abbiamo visto come dichiarare variabili struct. Tuttavia, nel dichiararle, abbiamo dovuto sempre definire anche come la struttura fosse composta.

Ritornando all'esempio della lezione precedente, abbiamo definito le variabili "contatto telefonico" in questo modo:

struct {
    char nome[20];
    char cognome[20];
    int eta;
    char numero_di_telefono[20];
    char indirizzo_email[20];
} contatto1, contatto2;

Ora, supponiamo di dover dichiare molte variabili di questo tipo. Nell'esempio ne abbiamo dichiarate due contatto1 e contatto2. Tuttavia potremmo avere la necessità di doverne dichiarare molte.

Fintanto che le dichiariamo nello stesso punto il problema non si pone, ma se dobbiamo dichiararle in punti diversi del programma, dobbiamo dichiarare la struttura ogni volta.

Ad esempio, in un punto dovremmo dichiarare contatto1:

struct {
    char nome[20];
    char cognome[20];
    int eta;
    char numero_di_telefono[20];
    char indirizzo_email[20];
} contatto1;

e, successivamente, in un secondo punto dovremmo dichiarare contatto2:

struct {
    char nome[20];
    char cognome[20];
    int eta;
    char numero_di_telefono[20];
    char indirizzo_email[20];
} contatto2;

In sostanza, abbiamo dovuto riscrivere l'intera definizione dei campi delle due strutture. Risulta ovvio che nel lungo termine possiamo incappare in problemi molto seri:

  • La dimensione del codice sorgente aumenta con informazioni ripetute;
  • Nel caso in cui dobbiamo modificare la struttura, ad esempio aggiungendo campi o modificando il tipo dei membri, dobbiamo modificare il codice in tutti i punti in cui è stata dichiarata. Questo è un procedimento incline ad errori.

Il problema più grave, tuttavia, è il fatto che secondo lo standard C le variabili contatto1 e contatto2 non hanno lo stesso tipo. La conseguenza è che non possiamo assegnare il valore della variabile contatto1 alla variabile contatto2:

/* ERRORE */
contatto2 = contatto1;

Non solo, ma non avendo un nome non possiamo usare il tipo di contatto1 o contatto2 come tipo del parametro di una funzione.

Per ovviare a questi problemi dobbiamo trovare il modo di definire il tipo rappresentato da una struttura struct e non dichiarare una variabile struct. Fatto questo, successivamente, possiamo dichiarare variabili di quello specifico tipo.

Lo standard del linguaggio C mette a disposizione due meccanismi:

  • Le strutture o struct etichettate (dall'inglese tagged struct);
  • Definire un tipo attraverso typedef.

In questa lezione vedremo questi due meccanismi nel dettaglio.

struct etichettate

L'etichetta (o dall'inglese tag) di una struttura è, sostanzialmente, un nome che serve per identificare un particolare tipo di struttura.

La sintassi per definire l'etichetta di una struttura è molto semplice: basta interporre l'etichetta tra la parola chiave struct e la definizione della struttura stessa.

Ad esempio, volendo associare alla struttura struct che abbiamo definito nella lezione precedente l'etichetta contatto_telefonico, dobbiamo scrivere:

struct contatto_telefonico {
    char nome[20];
    char cognome[20];
    int eta;
    char numero_di_telefono[20];
    char indirizzo_email[20];
};

Da notare che nella definizione abbiamo inserito un punto e virgola alla fine della definizione della struttura. Questo è necessario per indicare al compilatore che la definizione della struttura è terminata.

Nota

Attenzione al punto e virgola alla fine della definizione di una struttura

Omettere il punto e virgola alla fine della definizione di una struttura dati può portare ad errori di compilazione difficili da interpretare.

Prendiamo l'esempio che segue:

struct esempio {
    float membro1;
    int membro2;
}                   /* ERRORE: Abbiamo dimenticato il punto e virgola */

funzione(void) {
    /* ... */
}

In questo caso il compilatore riconosce, in modo errato, che la funzione funzione restituisce un valore di tipo struct esempio. Questo perché il compilatore non ha trovato il punto e virgola alla fine della definizione della struttura.

Il problema è che spesso i compilatori se ne accorgono solo raggiunto il punto in cui la funzione termina restituendo errori criptici.

Una volta che abbiamo assegnato un'etichetta ad una struct possiamo usarla per dichiarare variabili di quel tipo.

Ad esempio, volendo dichiarare due variabili di tipo contatto_telefonico dobbiamo scrivere in questo modo:

struct contatto_telefonico contatto1, contatto2;

Da tenere presente che la parola chiave struct va inserita comunque. Infatti, se avessimo scritto in questo modo il compilatore restituirebbe un errore:

/* ERRORE */
contatto_telefonico contatto1, contatto2;

La motivazione sta nel fatto che contatto_telefonico non identifica un tipo; senza la parola chiave struct la dichiarazione è senza significato.

Questo ha delle importanti conseguenze. Dal momento che l'etichetta di una struct è priva di significato per il compilatore, essa non potrà andare in conflitto con altri nomi utilizzati all'interno del nostro programma.

Per tal motivo, il codice che segue è perfettamente legale:

struct contatto_telefonico {
    char nome[20];
    char cognome[20];
    int eta;
    char numero_di_telefono[20];
    char indirizzo_email[20];
};

struct contatto_telefonico contatto1, contatto2;

/* Dichiarazione legale */
int contatto_telefonico;

In questo caso, contatto_telefonico è una variabile di tipo int mentre contatto1 e contatto2 sono variabili di tipo struct contatto_telefonico. Il codice è legale anche se poco leggibile.

Definizione

Strutture Etichettate

Una Struttura Etichettata è una struttura dati a cui è stato associato un nome, o tag, che permette di identificarla.

La sintassi per definire una struttura etichettata è:

struct nome_struttura {
    /* ... */
};

Una volta che una struct è stata etichettata si possono dichiarare variabili di quel tipo utilizzando la sintassi seguente:

struct nome_struttura nome_variabile;

Combinazione di struct etichettate e dichiarazioni di variabili

Abbiamo visto come possiamo definire una struct etichettata e come possiamo dichiarare variabili di quel tipo.

Queste due operazioni possono essere combinate in un'unico passaggio. Ad esempio, volendo dichiarare due variabili di tipo contatto_telefonico dobbiamo scrivere in questo modo:

struct contatto_telefonico {
    char nome[20];
    char cognome[20];
    int eta;
    char numero_di_telefono[20];
    char indirizzo_email[20];
} contatto1, contatto2;

Con questo codice abbiamo dichiarato sia una struttura etichettata di tipo struct contatto_telefonico che due variabili di quel tipo: contatto1 e contatto2.

Compatibilità tra variabili di tipo struct etichettate

Uno dei vantaggi principali nell'etichettare una struct è che le variabili di quel tipo sono compatibili tra di loro.

Per questo motivo il codice che segue è perfettamente legale:

struct contatto_telefonico contatto1 = {
    "Mario",
    "Rossi",
    30,
    "1234567890",
    "mario.rossi@prova.com"
};

struct contatto_telefonico contatto2 = contatto1;

L'assegnazione di contatto1 a contatto2 è perfettamente legale. Questo perché contatto1 e contatto2 sono variabili dello stesso tipo.

typedef e struct

In alternativa alle strutture etichettate, il linguaggio C mette a disposizione un altro meccanismo per definire nuovi tipi di dati: typedef.

Possiamo, infatti, usare typedef per definire un nuovo tipo di dato che corrisponde ad una struct.

Tornando all'esempio di contatto_telefonico possiamo usare typedef in questo modo:

typedef struct {
    char nome[20];
    char cognome[20];
    int eta;
    char numero_di_telefono[20];
    char indirizzo_email[20];
} contatto_telefonico;

In questo modo abbiamo definito un nuovo tipo di dato contatto_telefonico che corrisponde ad una struct con i campi che abbiamo definito.

La cosa da notare è che il nome del nuovo tipo va messo al termine della definizione della struct e non all'inizio come avviene con le strutture etichettate.

A questo punto possiamo usare contatto_telefonico come se fosse un tipo qualunque del linguaggio C, per cui possiamo dichiarare due variabili, contatto1 e contatto2, in questo modo:

contatto_telefonico contatto1, contatto2;

Dal momento che contatto_telefonico è un tipo a tutti gli effetti, non è più necessario usare la parola chiave struct prima del nome del tipo.

In generale, nel linguaggio C, possiamo usare sia le strutture etichettate che typedef per definire nuovi tipi di dati. Non vi sono vincoli particolari a riguardo. Esistono, tuttavia, casi particolari in cui dobbiamo necessariamente usare le strutture etichettate come vedremo in futuro.

Definizione

typedef di una struttura dati

In linguaggio C è possibile definire un nuovo tipo di dati a partire da una struct usando la parola chiave typedef.

La sintassi per definire un nuovo tipo di dato a partire da una struct è:

typedef struct {
    /* ... */
} nome_nuovo_tipo;

Una volta dichiarato il nuovo tipo di dato, possiamo usare nome_nuovo_tipo come se fosse un tipo qualunque del linguaggio C. La sintassi per dichiarare una variabile è:

nome_nuovo_tipo nome_variabile;

In Sintesi

In questa lezione abbiamo esaminato due modi per definire un tipo a partire da una struct o struttura dati:

  • Le strutture etichettate;
  • I tipi di dato struttura definiti con typedef.

In questo modo siamo in grado di dichiarare variabili appartenenti allo stesso tipo. Il vantaggio principale risulterà ovvio nella prossima lezione in cui studieremo come passare una struttura dati come argomento di una funzione e come restituirla come valore di ritorno.