C Language
typedef
Ricerca…
introduzione
Il meccanismo typedef
consente la creazione di alias per altri tipi. Non crea nuovi tipi. Le persone usano spesso typedef
per migliorare la portabilità del codice, per fornire alias per i tipi di struttura o unione, o per creare alias per i tipi di funzione (o puntatore di funzione).
Nello standard C, typedef
è classificato come "classe di memoria" per comodità; si verifica sintatticamente dove potrebbero apparire classi di memoria come static
o extern
.
Sintassi
- typedef nome_esistente nome_alias;
Osservazioni
Svantaggi di Typedef
typedef
potrebbe portare all'inquinamento del namespace nei grandi programmi in C.
Svantaggi di Typedef Structs
Inoltre, le strutture typedef
senza un nome di tag sono una delle cause principali di imposizione inutile delle relazioni di ordinamento tra i file di intestazione.
Tenere conto:
#ifndef FOO_H
#define FOO_H 1
#define FOO_DEF (0xDEADBABE)
struct bar; /* forward declaration, defined in bar.h*/
struct foo {
struct bar *bar;
};
#endif
Con tale definizione, non usando typedefs
, è possibile che un'unità di compilazione includa foo.h
per ottenere la definizione FOO_DEF
. Se non tenta di dereferenziare il membro della bar
della struttura foo
non sarà necessario includere il file bar.h
Typedef vs #define
#define
è una direttiva per il pre-processore C che viene anche utilizzata per definire gli alias per vari tipi di dati simili a typedef
ma con le seguenti differenze:
typedef
è limitato a dare nomi simbolici a tipi solo dove come#define
può essere usato per definire alias anche per valori.typedef
interpretazionetypedef
viene eseguita dal compilatore mentre le istruzioni#define
vengono elaborate dal pre-processore.Si noti che
#define cptr char *
seguito dacptr a, b;
non fa lo stesso ditypedef char *cptr;
seguito dacptr a, b;
. Con il#define
,b
è un semplicechar
variabile, ma è anche un puntatore con iltypedef
.
Typedef per strutture e unioni
Puoi dare nomi alias a una struct
:
typedef struct Person {
char name[32];
int age;
} Person;
Person person;
Rispetto al modo tradizionale di dichiarare le strutture, i programmatori non avrebbero bisogno di avere struct
ogni volta che dichiarano un'istanza di quella struttura.
Si noti che il nome Person
(al contrario di struct Person
) non è definito fino al punto e virgola finale. Pertanto, per gli elenchi collegati e le strutture ad albero che devono contenere un puntatore allo stesso tipo di struttura, è necessario utilizzare:
typedef struct Person {
char name[32];
int age;
struct Person *next;
} Person;
o:
typedef struct Person Person;
struct Person {
char name[32];
int age;
Person *next;
};
L'uso di un typedef
per un tipo di union
è molto simile.
typedef union Float Float;
union Float
{
float f;
char b[sizeof(float)];
};
Una struttura simile a questa può essere utilizzata per analizzare i byte che costituiscono un valore float
.
Usi semplici di Typedef
Per dare nomi brevi a un tipo di dati
Invece di:
long long int foo;
struct mystructure object;
si può usare
/* write once */
typedef long long ll;
typedef struct mystructure mystruct;
/* use whenever needed */
ll foo;
mystruct object;
Ciò riduce la quantità di digitazione necessaria se il tipo viene utilizzato più volte nel programma.
Migliorare la portabilità
Gli attributi dei tipi di dati variano tra diverse architetture. Ad esempio, un int
può essere un tipo a 2 byte in un'implementazione e un tipo a 4 byte in un altro. Supponiamo che un programma debba utilizzare un tipo di 4 byte per funzionare correttamente.
In un'implementazione, la dimensione di int
sia 2 byte e quella di long
4 byte. In un altro, la dimensione di int
sia 4 byte e quella di long
8 byte. Se il programma è scritto usando la seconda implementazione,
/* program expecting a 4 byte integer */
int foo; /* need to hold 4 bytes to work */
/* some code involving many more ints */
Affinché il programma venga eseguito nella prima implementazione, tutte le dichiarazioni int
dovranno essere modificate a long
.
/* program now needs long */
long foo; /*need to hold 4 bytes to work */
/* some code involving many more longs - lot to be changed */
Per evitare ciò, si può usare typedef
/* program expecting a 4 byte integer */
typedef int myint; /* need to declare once - only one line to modify if needed */
myint foo; /* need to hold 4 bytes to work */
/* some code involving many more myints */
Quindi, solo l'istruzione typedef
dovrebbe essere cambiata ogni volta, invece di esaminare l'intero programma.
L'intestazione <stdint.h>
e l'intestazione <inttypes.h>
correlata definiscono nomi di tipi standard (usando typedef
) per numeri interi di varie dimensioni, e questi nomi sono spesso la scelta migliore nel codice moderno che ha bisogno di numeri interi fissi. Ad esempio, uint8_t
è un tipo intero a 8 bit senza segno; int64_t
è un tipo intero a 64 bit con int64_t
. Il tipo uintptr_t
è un tipo intero senza segno abbastanza grande da contenere qualsiasi puntatore all'oggetto. Questi tipi sono teoricamente facoltativi, ma è raro che non siano disponibili. Ci sono varianti come uint_least16_t
(il più piccolo tipo di intero senza segno con almeno 16 bit) e int_fast32_t
(il tipo di intero con int_fast32_t
più veloce con almeno 32 bit). Inoltre, intmax_t
e uintmax_t
sono i tipi interi più grandi supportati dall'implementazione. Questi tipi sono obbligatori.
Per specificare un utilizzo o migliorare la leggibilità
Se un insieme di dati ha uno scopo particolare, si può usare typedef
per dargli un nome significativo. Inoltre, se la proprietà dei dati cambia in modo tale che il tipo di base deve essere modificato, solo l'istruzione typedef
deve essere modificata, invece di esaminare l'intero programma.
Typedef per puntatori di funzioni
Possiamo usare typedef
per semplificare l'uso dei puntatori di funzione. Immagina di avere alcune funzioni, tutte con la stessa firma, che usano il loro argomento per stampare qualcosa in modi diversi:
#include<stdio.h>
void print_to_n(int n)
{
for (int i = 1; i <= n; ++i)
printf("%d\n", i);
}
void print_n(int n)
{
printf("%d\n, n);
}
Ora possiamo usare un typedef
per creare un tipo di puntatore a funzione denominata chiamato stampante:
typedef void (*printer_t)(int);
Questo crea un tipo, denominato printer_t
per un puntatore a una funzione che accetta un singolo argomento int
e non restituisce nulla, che corrisponde alla firma delle funzioni che abbiamo sopra. Per usarlo creiamo una variabile del tipo creato e assegniamo un puntatore a una delle funzioni in questione:
printer_t p = &print_to_n;
void (*p)(int) = &print_to_n; // This would be required without the type
Quindi chiamare la funzione puntata dalla variabile del puntatore della funzione:
p(5); // Prints 1 2 3 4 5 on separate lines
(*p)(5); // So does this
Pertanto, typedef
consente una sintassi più semplice quando si gestiscono i puntatori di funzione. Ciò diventa più evidente quando i puntatori di funzione vengono utilizzati in situazioni più complesse, come gli argomenti per le funzioni.
Se si utilizza una funzione che accetta un puntatore di funzione come parametro senza un tipo di puntatore di funzione definito, la definizione della funzione sarebbe,
void foo (void (*printer)(int), int y){
//code
printer(y);
//code
}
Tuttavia, con typedef
è:
void foo (printer_t printer, int y){
//code
printer(y);
//code
}
Allo stesso modo, le funzioni possono restituire i puntatori di funzione e, di nuovo, l'uso di un typedef
può semplificare la sintassi quando lo si fa.
Un classico esempio è la funzione di signal
da <signal.h>
. La dichiarazione per questo (dallo standard C) è:
void (*signal(int sig, void (*func)(int)))(int);
Questa è una funzione che accetta due argomenti: un int
e un puntatore a una funzione che accetta un int
come argomento e non restituisce nulla, e che restituisce un puntatore che funziona come il suo secondo argomento.
Se abbiamo definito un tipo SigCatcher
come alias per il puntatore al tipo di funzione:
typedef void (*SigCatcher)(int);
quindi potremmo dichiarare signal()
usando:
SigCatcher signal(int sig, SigCatcher func);
Nel complesso, questo è più facile da capire (anche se lo standard C non ha scelto di definire un tipo per fare il lavoro). La funzione signal
accetta due argomenti, un int
e un SigCatcher
, e restituisce un SigCatcher
- dove un SigCatcher
è un puntatore a una funzione che accetta un argomento int
e non restituisce nulla.
Sebbene l'utilizzo di nomi typedef
per i tipi di puntatore a funzioni semplifica la vita, può anche portare a confusione per gli altri che manterranno il codice in un secondo momento, quindi utilizzare con cautela e documentazione adeguata. Vedi anche Function Pointers .