- Programmazione » Programmazione » Guida C - Manuale programmazione con articoli e risorse interessanti
Gli operatori: cast e conversioni di tipo
Cast e conversioni di tipo
In una espressione è sempre possibile avere operandi
di tipo diverso. Non è poi così strano
dividere, ad esempio, un numero in virgola mobile per un
numero intero, oppure, anche se a prima vista può
sembrare meno ovvio, moltiplicare un intero per un carattere.
In ogni caso, comunque, il risultato dell'operazione deve
essere di un unico tipo, di volta in volta ben determinato:
in tali casi è sempre necessario, perciò,
procedere a conversioni di tipo su almeno uno degli operandi
coinvolti.
Il C, al riguardo, fissa un ordine "gerarchico"
dei tipi di dato intrinseci, e stabilisce due semplici regole
che consentono di conoscere sempre a priori come verranno
effettuate le necessarie conversioni.
L'ordine gerachico dei tipi, decrescente da sinistra a
destra, è il seguente:
long double > double > float > long > int > short > char
Ne risulta che ogni tipo è di "grado"
superiore ad ogni altro tipo elencato alla sua destra e di
grado inferiore a quello dei tipi elencati alla sua sinistra.
Sulla scorta di tale gerarchia, la prima regola stabilisce
che nelle espressioni che non coinvolgono operatori di
assegnamento, in ogni coppia di operandi l'operando di
grado inferiore è convertito nel tipo
dell'operando avente grado superiore. Così, ad
esempio, in una operazione di confronto tra un float
e un long, quest'ultimo è convertito in
float prima che sia effettuato il
confronto.
La seconda regola riguarda invece le operazioni di
assegnamento: l'espressione a destra dell'operatore
di assegnamento è sempre convertita nel tipo della
variabile che si trova a sinistra del medesimo,
indipendentemente dal livello gerarchico dei dati
coinvolti.
Naturalmente le due regole possono trovare contemporanea
applicazione quando ad una variabile sia assegnato il
risultato di un'espressione che coinvolge operandi di
tipi differenti:
int iVar;
long lVar;
float fVar;
char cVar;
....
iVar = fVar + lVar * cVar;
Nell'esempio, l'operatore di moltiplicazione ha
precedenza rispetto a quello di somma, perciò viene
dapprima calcolato il prodotto di lVar per
cVar, dopo avere convertito cVar in
long. Il valore ottenuto è poi sommato a
quello contenuto in fVar, ma solo dopo averlo
convertito in float. Il risultato, infine, viene
convertito in int ed assegnato a
iVar.
Si tenga presente che le conversioni effettuate in modo
automatico dal compilatore C implicano un troncamento della
parte più significativa del valore convertito quando
esso viene "degradato" ad un livello inferiore, ed
un'aggiunta di bit nulli quando è
"promosso" ad un tipo di livello superiore. Nel
secondo caso il valore originario del dato può sempre
venire conservato; nel primo, al contrario, esiste il rischio
di perdere una parte (la più significativa) del valore
convertito.
L'affermazione risulta palese se si pensa, ad esempio,
al caso di una conversione da int a long ed
una viceversa: consideriamo due variabili, la prima di tipo
int (16 bit) e la seconda di tipo long (32 bit),
contenenti, rispettivamente, i valori 5027 (che in
codice binario è 0001001110100011) e
2573945 (in binario
00000000001001110100011001111001): la conversione
della prima in long implica l'aggiunta di 16 bit
nulli alla sinistra di quelli "originali". Lo
spazio occupato è ora di 32 bit, ma il valore di
partenza non viene modificato. Nel convertire il
long in int, al contrario, vengono
eliminati i 16 bit più significativi (quelli
più a sinistra): i 16 bit rimanenti sono
0100011001111001, che equivalgono, in notazione
decimale, a 18041.
Conversioni di tipo automatiche sono effettuate anche quando
il tipo dei parametri passati ad una funzione non corrisponde
al tipo dei parametri che la funzione "desidera".
Inoltre, in questo caso, i char sono sempre
convertiti in int, anche se la funzione si aspetta
di ricevere proprio un char[3]. Va anche sottolineato che il
compilatore, in genere, emette un messaggio di warning quando
la conversione di tipo generata in modo automatico comporta
il rischio di perdere una parte del valore
coinvolto.
Vi sono però spesso situazioni in cui il compilatore
non è in grado di effettuare la conversione in modo
automatico; ad esempio quando sono coinvolti tipi di dato non
intrinseci, definiti dal programmatore (quali strutture,
campi di bit, etc.). Altre volte, invece, si desidera
semplicemente esplicitare una conversione che il compilatore
potrebbe risolvere da sé, al fine di rendere
più chiaro il codice o per evitare il warning ad essa
correlato.
In tutti questi casi si può ricorrere
all'operatore di cast, il quale forza un qualunque
valore ad appartenere ad un certo tipo. La notazione è
la seguente:
(tipo)espressione
dove tipo può essere una qualsiasi delle parole chiave del C utilizzate nelle dichiarazioni di tipo ed espressione dev'essere una qualsiasi espressione sintatticamente corretta. Ad esempio:
int iVar;
iVar = (int)3.14159;
La conversione illustrata può essere automaticamente eseguita dal compilatore, ma l'esplicitarla mediante l'operatore di cast incrementa la chiarezza del codice ed evita il messaggio di warning. Un altro caso in cui si effettua spesso il cast è l'inizializzazione di un puntatore far o huge con una costante a 32 bit:
char far *colVbuf = (char far *)0xB8000000L; // ptr buffer video testo col.
La conversione automatica, in questo caso, non comporterebbe
alcun errore, dal momento che la costante assegnata al
puntatore è un dato a 32 bit, esattamente come il
puntatore stesso: il compilatore emetterebbe però una
segnalazione di warning, per evidenziare al programmatore che
un dato di tipo long viene assegnato ad un puntatore
far a carattere: una questione di forma, insomma. Di
fatto la costante potrebbe essere scritta anche senza la
"L" che ne indica inequivocabilmente la
natura long, ma in quel caso il compilatore
segnalerebbe, con un altro warning, che vi è una
costante che, per il valore espresso, deve essere considerata
long senza che ciò sia stato esplicitamente
richiesto.
Più significativo può essere l'esempio
seguente:
struct FARPTR {
unsigned offset;
unsigned segment;
};
....
char far *cFptr;
struct FARPTR fPtr;
....
(char far *)fPtr = cFptr;
In questo caso la struttura di tipo FARPTR è utilizzata per accedere separatamente alla parte segmento e alla parte offset di un puntatore far. In pratica, il valore contenuto nel puntatore far è copiato nell'area di memoria occupata dalla struttura: si tratta di un'operazione che potrebbe provocare l'emissione di un messaggio di errore e l'interruzione della compilazione. La presenza dell'operatore di cast tranquillizza il compilatore; dal canto nostro sappiamo che struttura e puntatore occupano entrambi 32 bit, perciò siamo tranquilli a nostra volta.
- Articolo precedente Gli operatori: autoincremento e autodecremento
- Articolo successivo Gli operatori: operatore sizeof()