typedef fait une petite chose : il donne un autre nom à un type qui existe déjà. Aucun nouveau type n'est créé, aucune mémoire n'est réservée, et rien ne change dans le comportement de la valeur. C'est un outil de nommage - et en C, où les noms de types intégrés sont longs (unsigned long long, struct Employee, void (*)(int)), un bon nom vaut beaucoup.
La forme de base
La syntaxe se lit comme une déclaration de variable avec typedef greffé devant. Là où irait le nom de la variable, vous mettez le nouveau nom de type :
unsigned long count; // declare une variable nommee count
typedef unsigned long ulong; // declare un TYPE nomme ulong
C'est toute la règle, et elle explique chaque typedef que vous lirez un jour, y compris ceux d'allure étrange plus loin dans cette page.
Notez ce que la paire Celsius/Fahrenheit ne vous apporte pas : la sûreté de typage. Les deux ne sont que des double, donc passer une valeur Fahrenheit là où on attend des Celsius compile sans broncher. Les noms documentent l'intention pour les humains ; ils ne créent aucune vérification.
L'idiome typedef struct
C'est la raison pour laquelle la plupart des programmes C contiennent un typedef. En C, le nom de type d'une structure inclut le mot-clé :
struct Point { int x; int y; };
struct Point p; /* le mot "struct" a chaque fois */
Un typedef ramasse cela :
Lisez-le avec la règle d'avant : retirez le typedef et vous avez struct Point {...} Point;, une déclaration d'une variable nommée Point. Remettez le typedef et Point devient un nom de type.
Avec étiquette ou sans
Vous pouvez omettre entièrement l'étiquette, donnant une structure anonyme n'ayant que le nom du typedef :
typedef struct {
int x;
int y;
} Point; /* aucun "struct Point" n'existe - seulement "Point" */
C'est plus net, et pour un simple enregistrement de données, c'est le style courant. Mais cela échoue dès que la structure doit se mentionner elle-même, car le nom du typedef n'est pas utilisable tant que sa propre déclaration n'est pas terminée :
/* NE compile PAS : "Node" n'est pas encore un type dans ses propres accolades. */
typedef struct {
int value;
Node *next;
} Node;
Gardez l'étiquette et l'autoréférence fonctionne, car struct Node est utilisable dès que l'étiquette est vue :
La règle pratique : gardez l'étiquette. Cela coûte un mot, elle correspond au nom du typedef donc rien n'est déroutant, et elle laisse la porte ouverte à l'autoréférence et aux déclarations anticipées dans les en-têtes. Voir structures et pointeurs pour ce que ce nœud chaîné devient.
Déclarations anticipées et types opaques
Parce qu'un pointeur sur structure a une taille connue même quand le contenu de la structure est inconnu, un en-tête peut distribuer un type sans révéler ses membres :
/* stack.h */
typedef struct Stack Stack; /* declare, pas defini */
Stack *stack_create(void);
void stack_push(Stack *s, int value);
int stack_pop(Stack *s);
void stack_destroy(Stack *s);
Le struct Stack { ... }; complet vit dans stack.c et personne à l'extérieur ne peut toucher ses membres. Ce motif de type opaque est la façon dont les bibliothèques C imposent l'encapsulation, et c'est exactement ce qu'est FILE dans la bibliothèque standard - vous obtenez un FILE * de fopen et on ne vous dit rien de ce qu'il y a dedans. Voir les fichiers.
typedef pour les enums et les unions
Le même idiome s'applique aux deux autres sortes composites :
Sans le typedef, ce serait enum Color c; et union Number n;. Voir les enums et les unions pour ce qu'elles font réellement.
typedef pour un pointeur de fonction
C'est là que typedef cesse d'être une commodité pour devenir presque nécessaire. La syntaxe brute d'un pointeur de fonction est réputée difficile à lire :
void (*handler)(int, const char *); /* une variable */
void register_cb(void (*cb)(int, const char *)); /* un parametre */
Nommez le type une fois et les deux deviennent lisibles :
Appliquez de nouveau la règle du retrait du typedef : int (*IntBinaryOp)(int, int); seul déclarerait une variable nommée IntBinaryOp pointant sur une fonction prenant deux int et renvoyant un int. Avec typedef, cela devient le nom du type. Plus de détails dans pointeurs de fonction.
typedef contre #define
Ils semblent interchangeables pour de simples alias et ne le sont pas :
#define INT_PTR int *
typedef int *int_ptr;
INT_PTR a, b; /* se developpe en : int *a, b; -- b est un int simple ! */
int_ptr c, d; /* c et d sont tous deux des int * */
#define est une substitution de texte aveugle effectuée avant que le compilateur ne voie le code ; typedef est une vraie déclaration que le compilateur comprend, et elle s'applique à chaque nom de la liste. Utilisez typedef pour les types et réservez #define aux constantes et aux macros.
Quand un typedef nuit
Les typedef peuvent aussi cacher une information dont le lecteur a besoin. L'exemple le plus net est de cacher un pointeur :
typedef struct Node *NodeRef; /* l'etoile disparait */
NodeRef n = get_node();
En regardant NodeRef n, un lecteur ne peut pas dire si n peut valoir NULL, s'il doit être libéré, si n et une copie partagent le même objet, ou si les membres s'atteignent avec . ou ->. Les quatre questions trouvent une réponse instantanée avec struct Node *n.
La bibliothèque standard est d'accord : elle fait un typedef de la structure (FILE) et laisse l'étoile au point d'utilisation (FILE *fp). Deux lignes directrices raisonnables en découlent :
- Faites des typedef de structures, d'unions, d'enums et de pointeurs de fonction - le bruit supprimé ne porte aucune information.
- Ne faites pas de typedef d'un pointeur juste pour le raccourcir, et ne faites pas de typedef d'un type de base pour le déguiser (
typedef int Boolean;invite quelqu'un à y stocker7- utilisez plutôtboolde<stdbool.h>, couvert dans les booléens).
Les noms de largeur fixe de la bibliothèque standard dans <stdint.h> (uint32_t, int64_t, size_t) sont tous des typedef, et ils sont le meilleur argument en faveur de cette fonctionnalité : ils disent exactement ce qu'ils sont, ils sont portables, et rien n'est caché.
Questions fréquentes
Que fait typedef en C ?
Il donne un second nom à un type existant. typedef unsigned long ulong; signifie que ulong et unsigned long sont désormais le même type. Cela ne crée aucun nouveau type et aucun stockage - seulement une façon plus courte ou plus parlante d'écrire un type que vous avez déjà.
Qu'est-ce que l'idiome typedef struct en C ?
typedef struct Point { int x; int y; } Point; déclare la structure et nomme le type Point en une seule instruction, vous pouvez donc écrire Point p; au lieu de struct Point p;. Garder l'étiquette (struct Point) compte quand la structure doit se référencer elle-même, comme un nœud de liste chaînée.
Quelle est la différence entre typedef et #define ?
typedef est traité par le compilateur et crée un véritable alias de type ; #define est une substitution de texte faite par le préprocesseur avant la compilation. Cette différence mord avec les pointeurs : #define PTR int* fait que PTR a, b; se développe en int* a, b; - seul a est un pointeur. typedef int *PTR; fait des deux des pointeurs.
Faut-il faire un typedef d'un type pointeur en C ?
Généralement non. typedef struct Node *NodeRef; cache le fait que le type est un pointeur, donc les lecteurs ne peuvent pas savoir si une variable peut valoir NULL, doit être libérée, ou demande ->. Le FILE * de la bibliothèque standard garde l'étoile visible exactement pour cette raison. Faites un typedef de la structure, pas du pointeur sur elle.