typedef fa una cosa piccola: dà un altro nome a un tipo che esiste già. Non viene creato alcun tipo nuovo, non viene riservata memoria e il comportamento del valore non cambia in nulla. È uno strumento per i nomi, e in C, dove i nomi dei tipi integrati sono lunghi (unsigned long long, struct Employee, void (*)(int)), un buon nome vale molto.
La forma base
La sintassi si legge come la dichiarazione di una variabile con typedef appiccicato davanti. Dove andrebbe il nome della variabile, metti il nome del nuovo tipo:
unsigned long count; // dichiara una variabile chiamata count
typedef unsigned long ulong; // dichiara un TIPO chiamato ulong
Questa è tutta la regola, e spiega ogni typedef che leggerai, compresi quelli dall'aspetto strano più avanti in questa pagina.
Nota cosa la coppia Celsius/Fahrenheit non ti dà: la sicurezza dei tipi. Entrambi sono semplicemente double, quindi passare un valore Fahrenheit dove ci si aspetta un Celsius compila senza problemi. I nomi documentano l'intenzione per le persone; non creano un controllo.
L'idioma typedef struct
È il motivo per cui la maggior parte dei programmi C contiene almeno un typedef. In C il nome del tipo di una struct include la parola chiave:
struct Point { int x; int y; };
struct Point p; /* la parola "struct" ogni singola volta */
Un typedef la accorcia:
Leggilo con la regola di prima: togli il typedef e ottieni struct Point {...} Point;, la dichiarazione di una variabile chiamata Point. Rimetti typedef e Point diventa invece il nome di un tipo.
Con il tag o senza
Puoi omettere del tutto il tag, ottenendo una struct anonima con il solo nome del typedef:
typedef struct {
int x;
int y;
} Point; /* non esiste nessuna "struct Point", solo "Point" */
È più ordinato, e per un semplice record di dati è lo stile comune. Ma smette di funzionare nel momento in cui la struct deve nominare se stessa, perché il nome del typedef non è utilizzabile finché la sua dichiarazione non è terminata:
/* NON compila: "Node" non e ancora un tipo dentro le sue stesse graffe. */
typedef struct {
int value;
Node *next;
} Node;
Mantieni il tag e l'autoriferimento funziona, perché struct Node è utilizzabile appena il tag viene visto:
La regola pratica: mantieni il tag. Costa una parola, coincide con il nome del typedef così non crea confusione e lascia aperta la porta all'autoriferimento e alle dichiarazioni anticipate negli header. Nella pagina su struct e puntatori vedi in cosa si trasforma quel nodo collegato.
Dichiarazioni anticipate e tipi opachi
Dato che un puntatore a una struct ha una dimensione nota anche quando il contenuto della struct è sconosciuto, un header può fornire un tipo senza rivelarne i membri:
/* stack.h */
typedef struct Stack Stack; /* dichiarata, non definita */
Stack *stack_create(void);
void stack_push(Stack *s, int value);
int stack_pop(Stack *s);
void stack_destroy(Stack *s);
La struct Stack { ... }; completa sta in stack.c e nessuno all'esterno può toccarne i membri. Questo schema a tipo opaco è il modo in cui le librerie C impongono l'incapsulamento, ed è esattamente ciò che è FILE nella libreria standard: ricevi un FILE * da fopen e non ti viene detto nulla di cosa c'è dentro. Vedi la gestione dei file.
typedef per enum e union
Lo stesso idioma vale per gli altri due tipi composti:
Senza il typedef sarebbero enum Color c; e union Number n;. Consulta gli enum e le union per capire cosa fanno davvero.
typedef per un puntatore a funzione
Qui typedef smette di essere una comodità e diventa quasi una necessità. La sintassi grezza di un puntatore a funzione è notoriamente difficile da leggere:
void (*handler)(int, const char *); /* una variabile */
void register_cb(void (*cb)(int, const char *)); /* un parametro */
Dai un nome al tipo una volta e diventano leggibili entrambi:
Applica di nuovo la regola del togliere il typedef: int (*IntBinaryOp)(int, int); da solo dichiarerebbe una variabile chiamata IntBinaryOp che punta a una funzione che riceve due int e restituisce un int. Con typedef, quello diventa il nome del tipo. Trovi di più nei puntatori a funzione.
typedef e #define
Per alias semplici sembrano intercambiabili, ma non lo sono:
#define INT_PTR int *
typedef int *int_ptr;
INT_PTR a, b; /* si espande in: int *a, b; quindi b e un semplice int! */
int_ptr c, d; /* sia c sia d sono int * */
#define è una sostituzione di testo cieca eseguita prima che il compilatore veda il codice; typedef è una vera dichiarazione che il compilatore capisce, e si applica a ogni nome dell'elenco. Usa typedef per i tipi e riserva #define alle costanti e alle macro.
Quando un typedef fa danni
I typedef possono anche nascondere informazioni che servono a chi legge. L'esempio più netto è nascondere un puntatore:
typedef struct Node *NodeRef; /* l'asterisco sparisce */
NodeRef n = get_node();
Guardando NodeRef n, chi legge non può capire se n può essere NULL, se va liberato, se n e una sua copia condividono lo stesso oggetto o se ai membri si accede con . o con ->. Con struct Node *n tutte e quattro le domande hanno una risposta immediata.
La libreria standard è d'accordo: fa il typedef della struct (FILE) e lascia l'asterisco nel punto d'uso (FILE *fp). Ne derivano due linee guida ragionevoli:
- Fai il typedef di struct, union, enum e puntatori a funzione: il rumore che elimini non porta alcuna informazione.
- Non fare il typedef di un puntatore solo per accorciarlo, e non fare il typedef di un tipo base per mascherarlo (
typedef int Boolean;invita qualcuno a metterci dentro7: usa invecebooldi<stdbool.h>, trattato nei booleani).
I nomi a larghezza fissa della libreria standard in <stdint.h> (uint32_t, int64_t, size_t) sono tutti typedef, e sono il miglior argomento a favore di questa funzionalità: dicono esattamente cosa sono, sono portabili e non nascondono nulla.
Domande frequenti
Cosa fa typedef in C?
Dà un secondo nome a un tipo esistente. typedef unsigned long ulong; significa che da lì in poi ulong e unsigned long sono lo stesso tipo. Non crea alcun tipo nuovo né nuova memoria: solo un modo più breve o più descrittivo di scrivere un tipo che hai già.
Cos'è l'idioma typedef struct in C?
typedef struct Point { int x; int y; } Point; dichiara la struct e dà al tipo il nome Point in un'unica istruzione, così puoi scrivere Point p; invece di struct Point p;. Mantenere il tag (struct Point) conta quando la struct deve fare riferimento a se stessa, come fa il nodo di una lista concatenata.
Qual è la differenza tra typedef e #define?
typedef è gestito dal compilatore e crea un vero alias di tipo; #define è una sostituzione di testo fatta dal preprocessore prima della compilazione. La differenza morde con i puntatori: #define PTR int* fa espandere PTR a, b; in int* a, b;, dove solo a è un puntatore. Con typedef int *PTR; sono puntatori entrambi.
Conviene fare typedef di un tipo puntatore in C?
Di solito no. typedef struct Node *NodeRef; nasconde il fatto che il tipo è un puntatore, quindi chi legge non può capire se una variabile può essere NULL, se va liberata o se serve ->. Il FILE * della libreria standard lascia visibile l'asterisco proprio per questo motivo. Fai il typedef della struct, non del puntatore a essa.