Definizione di Tipi con typedef in Linguaggio C

La definizione di nuovi tipi di dato in linguaggio C è una pratica comune che permette di migliorare la leggibilità e la manutenibilità del codice. Utilizzando il costrutto typedef, i programmatori possono creare alias per tipi di dato esistenti, semplificando la scrittura del codice e rendendolo più intuitivo. Questa tecnica è particolarmente utile in progetti di grandi dimensioni, dove la chiarezza e la coerenza dei tipi di dato possono fare una grande differenza.

In questa lezione, esploreremo come utilizzare typedef per definire nuovi tipi di dato in C.

Definizione di Tipi

Nelle precedenti lezioni abbiamo visto che il linguaggio C, almeno fino allo standard C99, non metteva a disposizione un tipo propriamente booleano.

Per ovviare a questo problema, nella lezione apposita, abbiamo adoperato come stratagemma l'utilizzo di una macro per definire un tipo booleano:

#define BOOL int
#define TRUE 1
#define FALSE 0

In questo modo, ogniqualvolta avessimo avuto bisogno di un tipo booleano, avremmo potuto dichiarare una variabile in questo modo:

BOOL flag = TRUE;

In effetti, con questo stratagemma abbiamo creato un tipo nuovo, chiamato BOOL, che è in realtà un int.

Le macro, come vedremo, funzionano per sostituzione del testo. In altre parole, ogni volta che il preprocessore trova la stringa BOOL, la sostituisce con int. Il compilatore, però, non conosce il tipo BOOL, e quindi non può fare controlli di tipo su di esso.

Esiste, in linguaggio C, un modo più elegante per definire nuovi tipi: il costrutto typedef.

Proviamo ad usarlo per definire un tipo booleano:

typedef int BOOL;

La prima cosa da notare è che, rispetto alla macro, abbiamo invertito l'ordine di int e BOOL. Ossia, il nome del nuovo tipo va inserito alla fine.

Il vantaggio di adoperare typedef è che con esso creiamo un nuovo tipo riconosciuto dal compilatore. Adesso BOOL sarà aggiunto alla lista dei tipi gestiti dal compilatore e potrà essere adoperato allo stesso modo.

Possiamo definire variabili di tipo BOOL:

BOOL flag = TRUE;

Il compilatore tratterà BOOL come sinonimo di int. Per cui la variabile flag sarà nient'altro che un int.

Ricapitolando:

Definizione

Definizione di Tipi con typedef

Il costrutto typedef permette di definire nuovi tipi di dato in linguaggio C. La sintassi è la seguente:

typedef tipo nuovo_tipo;

Dove tipo è il tipo di dato da cui si vuole creare il nuovo tipo, e nuovo_tipo è il nome del nuovo tipo.

Vantaggi dell'uso di typedef

Sorge, a questo punto, una domanda: ma se un tipo definito con typedef non è altro che l'alias di un altro tipo, qual è il vantaggio di adoperare typedef?

  1. La prima risposta è che typedef rende i programmi più leggibili, ammesso che la scelta dei nomi dei nuovi tipi sia fatta con cura.

    Ad esempio, supponiamo di voler definire un tipo per rappresentare una quantità di denaro. Possiamo farlo in questo modo:

    typedef float Euro;
    

    Quindi possiamo dichiarare delle variabili di tipo Euro:

    Euro prezzo_articolo, saldo_conto_corrente;
    

    In questo modo, la riga di codice diventa sicuramente più leggibile rispetto a:

    float prezzo_articolo, saldo_conto_corrente;
    
  2. La seconda motivazione sta nel fatto che possiamo modificare il tipo di dato sottostante in modo più semplice e sicuro.

    Ritornando all'esempio di sopra, se ad un certo punto ci accorgiamo che vogliamo modificare il tipo Euro da float a double, possiamo farlo in modo molto semplice cambiando una singola riga di codice:

    typedef double Euro;
    

    La dichiarazione delle variabili prezzo_articolo e saldo_conto_corrente non cambierà, e il compilatore si occuperà di fare le conversioni necessarie.

    Se non avessimo usato typedef, avremmo dovuto cambiare il tipo di tutte le variabili dichiarate come float, con il rischio di dimenticarne qualcuna.

  3. Infine, il terzo vantaggio riguarda la semplicità di scrittura del codice quando abbiamo a che fare con strutture dati, enumerazioni e puntatori. Vedremo in seguito come typedef possa rendere più leggibile il codice in questi casi.

typedef e Portabilità del codice

Un'ultima considerazione riguarda la portabilità del codice.

Il linguaggio C è stato progettato per essere portabile. Questo significa che un programma scritto in C dovrebbe poter essere compilato su qualsiasi sistema, indipendentemente dall'architettura hardware o dal sistema operativo.

Tuttavia, non si ha la garanzia che i tipi di dato standard abbiano la stessa dimensione su tutti i sistemi. Ad esempio, il tipo int potrebbe essere a 16 bit su un sistema e a 32 bit su un altro.

Ad esempio:

int i = 100000;

Se int è a 16 bit, il valore di i sarà troncato a 16 bit, e il risultato sarà diverso rispetto a quando int è a 32 bit. Ossia, il codice di sopra funziona su una macchina a 32 bit, ma non su una a 16 bit.

Allora, in questi casi possiamo adoperare typedef per definire tipi interi e modificare la dimensione di essi a seconda delle necessità.

Per cui, tornando all'esempio di sopra, possiamo definire un tipo INTERO che è un int su di una macchina a 32 bit e un long su di una a 16 bit:

/* Macchina a 32 bit */
typedef int INTERO;

/* Macchina a 16 bit */
typedef long INTERO;

Facendo così, la dichiarazione:

INTERO i = 100000;

Funzionerà su entrambe le macchine, perché INTERO sarà un int su una macchina e un long sull'altra.

Questa tecnica non risolve tutti i nostri problemi di portabilità anche perché molti dipendono dal modo in cui le variabili vengono adoperate.

Ad esempio, il problema si pone con l'utilizzo delle funzioni scanf e printf, che dipendono dal tipo di dato passato come argomento. In questi casi, è necessario fare attenzione a come si scrive il codice per garantire la portabilità. Le due funzioni richiedono uno specificatore di formato differente a seconda del tipo di dato passato: %d per int e %ld per long.

La stessa libreria standard del C, tuttavia, adopera typedef per garantire la portabilità del codice. Ad esempio, il tipo size_t è definito come unsigned int su alcune macchine e unsigned long su altre.

In generale, l'uso di typedef può aiutare a rendere il codice più portabile, ma non risolve tutti i problemi di portabilità.

In Sintesi

In questa lezione abbiamo visto come definire nuovi tipi di dato in linguaggio C utilizzando il costrutto typedef:

  • Abbiamo visto come definire un tipo booleano con typedef e le differenze rispetto all'uso di macro.
  • Abbiamo discusso i vantaggi dell'uso di typedef per rendere il codice più leggibile e per facilitare la modifica dei tipi di dato.
  • Abbiamo parlato della portabilità del codice e di come typedef possa aiutare a rendere il codice più portabile.

In generale, l'uso di typedef è una pratica comune in linguaggio C per definire nuovi tipi di dato e rendere il codice più leggibile e manutenibile.