Introduzione al Linguaggio C

di C. Munisso


Capitolo 2 - Tipi di dati


2.7. Dati scalari

I dati scalari possono essere suddivisi in:

2.7.1 Interi e caratteri

Inizieremo trattando delle variabili e delle costanti numeriche intere e delle variabili e costanti di tipo carattere

2.7.1.1 Variabili Intere

Le variabili di tipo intero sono dichiarate con le seguenti parole chiave:

int
long
short
char
unsigned

I seguenti esempi dichiarano una variabile num di tipo intero con una dimensione di 32 bit:

long int num; /* non inizializzata */
long int num = 165435; /* inizializzata */

Una variabile carattere, chiamata car, con dimensione di 8 bit, viene dichiarata invece nei seguenti modi:

char car; /* non inizializzata */
char car = 'a'; /* inizializzata */

La seguente tabella illustra i tipi, la dimensione ed il range numerico di tipi interi.

Dimensione dei tipi interi
Tipo Dimensione Range
unsigned char 8 bit da 0 a 255
char 8 bit da -128 a 127
unsiged short 16 bit da 0 a 65535
short int 16 bit da -32768 a 32767
unsigned long 32 bit da 0 a 4294976295
long int 32 bit da -2147483648 a 2147483647

2.7.1.2 Costanti Intere

Il C ammette tre tipi di costanti intere: decimali, esadecimali, ottali; tutti possono essere con o senza segno.

Caratteri ammessi per l'assegnazione delle costanti intere
0,...,9 decimali
0,...,9 a,...,f A,...,F esadecimali
0,...,7 ottali
x,X,l,L,u,U caratteri opzionali

Il riconoscimento del tipo decimale, esadecimale, ottale, avviene sulla base della radice. Una costante intera viene assunta come decimale se non inizia per 0 o 0x; differentemente, se inizia per 0 viene assunta come ottale, se inizia per 0x viene assunta come esadecimale. Il carattere opzionale l e L specifica che la costante è del tipo long (32 bit); il carattere opzionale u o U specifica che la costante è senza segno. Non è ammesso per le costanti intere il punto decimale, che è riservato per le costanti di tipo double. È possibile assegnare a variabili intere costanti di tipo carattere in quanto il loro valore corrisponde al loro codice ASCII. I seguenti esempi sono alcune valide costanti intere:

124 intero decimale
3544L intero decimale long
0x2b intero esadecimale
040 intero ottale
4294967295ul intero decimale unsigned long
077u intero ottale unsigned
'a' decimale ASCII 97

Come abbiamo visto non esiste una sostanziale differenza tra le costanti intere e quelle tipo carattere in quanto queste ultime vengono interpretate con il valore del rispettivo codice ASCII.

2.7.1.3 Sequenze di Escape

Le sequenze di escape in C sono stringhe di caratteri che permettono di rappresentare singoli caratteri del range completo ASCII, e quindi anche di quelli con codifica da 0 a 31 (decimale) detti anche caratteri non di stampa. Le sequenze di escape iniziano con il carattere di conversione backslash (\) e sono interpretate come un singolo carattere.

null \0
newline \n
tab \t
backspace \b
carriage return \r
form feed \f

È inoltre possibile specificare la sequenza di escape per mezzo di un valore di byte, normalmente il codice ASCII, sia in forma ottale che in forma esadecimale come esemplificato:

\113 ottale, carattere 'K'
\x04d esadecimale, carattere 'M'

2.7.2 Numeri in virgola mobile

Trattiamo ora delle variabili e delle costanti numeriche in virgola mobile . È forse opportuno ricordare che nei sistemi digitali i numeri in virgola mobile (floating-point) vengono memorizzati in notazione esponenziale, riservando un certo numero di bit per la mantissa ed un altro per l'esponente. Le rispettive quantità e posizioni possono variare da sistema a sistema, è quindi necessario consultare la documentazione del sistema e del compilatore per avere più dettagliate informazioni al riguardo.

2.7.2.1 Variabili in virgola mobile

Le variabili in virgola mobile sono dichiarate con le seguenti parole chiave:

float
double

Le due diverse dichiarazioni determinano due diversi livelli di precisione dell'oggetto memorizzato, chiamati comunemente singola-precisione e doppia-precisione. Normalmente i numeri float mantengono una precisione alla settima cifra della mantissa, mentre in numeri double mantengono una precisione alla sedicesima cifra della mantissa. Le dimensioni generalmente usate per i tipi numerici in virgola mobile sono le seguenti:

Dimensione dei tipi numerici in virgola mobile
Tipo Dimensione Range
long 32 bit da 0.29E-38 a 1.7E38
double 64 bit da 0.29E-38 a 1.7E38

Possiamo vedere come tra i due tipi non varia l'intervallo tra il minimo ed il massimo numero rappresentabile, ma, essendo rappresentati in forma esponenziale, aumentando la dimensione della mantissa ne aumenta la precisione. Vi sono alcuni sistemi che per il tipo double consentono un range compreso tra 0.56E-308 e 0.899E308, mentre alcuni compilatori consentono da dichiarazione long float come sinonimo di double.

2.7.2.2 Costanti in virgola mobile

Le costanti in virgola mobile sono di tipo double e sono costituite da una parte intera, un punto decimale, una parte frazionaria, la lettera e o E ed un esponente intero opzionalmente con segno. I seguenti esempi sono validi per la definizione di costanti in virgola mobile:
4.0
3.23
2.0E7
0.34E-5
0.56E-10

2.7.3 Puntatori

I puntatori, come abbiamo visto in precedenza, sono variabili che contengono l'indirizzo di memoria di altre variabili; per quanto riguarda il dimensionamento di questi indirizzi, esso è dipendente dall'architettura hardware e dal sistema operativo, in molti sistemi Unix le variabili sono indirizzate a 32 bit, nel sistema DOS sono invece indirizzate a 16 bit. Il seguente esempio mostra come verificare la dimensione del puntatori sul proprio sistema; tratteremo successivamente in dettaglio le funzioni utilizzate nell'esempio:

Un'esecuzione di questo programma su un sistema Unix ha prodotto il seguente output:

x (rvalue) dimensione: 2 byte; valore: 34
x (lvalue) dimensione: 4 byte; valore: 7fffbb44
ptr (rvalue) dimensione: 4 byte; valore: 7fffbb44
ptr (lvalue) dimensione: 4 byte; valore: 7fffbb40


Mentre lo stesso programma su un sistema MS-DOS ha prodotto il seguente output:

x (rvalue) dimensione: 2 byte; valore: 34
x (lvalue) dimensione: 2 byte; valore: FFF4
ptr (rvalue) dimensione: 2 byte; valore: FFF4
ptr (lvalue) dimensione: 2 byte; valore: FFF2


Notiamo che l'unica differenza fra i due output è costituita dalla sintassi di indirizzamento delle variabili; in entrambi invece l'lvalue di x e l'rvalue di ptr contengono il medesimo valore.
Possiamo notare come la variabile x di tipo short int sia di 16 bit (2 byte) per il suo rvalue mentre il suo indirizzo sia, di 32 bit (4 byte) in un sistema Unix, di 16 bit (2byte) in un sistema MS-DOS, e come il puntatore ptr abbia per rvalue l'lvalue di x. Gli indirizzi in questo esempio sono in valori esadecimali.

Possiamo assegnare ad un puntatore l'indirizzo di qualsiasi tipo di dato e quindi avere puntatori a scalari (interi e in virgola mobile), ad altri puntatori, a strutture, a funzioni, ecc. È inoltre possibile effettuare operazioni aritmetiche di somma e sottrazione di valori interi a puntatori, così da incrementare o decrementare gli indirizzi di memoria delle variabili indirizzate. Per comprendere correttamente il funzionamento di questi incrementi e decrementi è necessario prendere in considerazione il concetto di visibilità del puntatore.

Con il termine visibilità del puntatore intendiamo, in pratica, la dimensione della variabile indirizzata; per esempio un puntatore a char avrà una visibilità di 1 byte, mentre un puntatore a long int avrà una visibilità di 4 byte. Questo comporta, a parità di operazione, un diverso valore dell'incremento, dipendentemente dal tipo di dato indirizzato dal puntatore. Consideriamo per esempio:

char c = 'a'; /* variabile carattere di 1 byte */
long int y = 120345 /* variabile intera di 4 byte */

dichiariamo e assegnamo ora i rispettivi puntatori:

char *pc = &c; /* puntatore a carattere*/
long int *py = &y /* puntatore a long int */

ora incrementiamo di una unità entrambi i puntatori:

pc = pc + 1; /* incremento di 1 di pc */
py = py + 1; /* incremento di 1 di py */

Il puntatore a char ora punterà alla prima successiva locazione di memoria mentre il puntatore a long int punterà alla quarta successiva locazione di memoria. Vedremo in seguito come questa particolarità consenta una efficace scansione degli array. Vediamo in esempio un programma che mostra i valori e gli incrementi di alcuni puntatori a tipi diversi:

Un'esecuzione di questo programma su un sistema Unix ha prodotto il seguente output:

pc valore: 7fffbb44
pc + 1 valore: 7fffbb45
px valore: 7fffbb3c
px + 1 valore: 7fffbb3e
py valore: 7fffbb34
py + 1 valore: 7fffbb38

e questo é l'output su un sistema MS-DOS:

pc valore: FFF5
pc + 1 valore: FFF6
px valore: FFF0
px + 1 valore: FFF2
py valore: FFEA
py + 1 valore: FFEE

Come possiamo vedere il valore esadecimale del puntatore a char pc è stato incrementato di una locazione di memoria mentre i valori dei puntatori a short int e a long int sono stati incrementati rispettivamente di due e quattro locazioni di memoria. È inoltre possibile dichiarare un tipo di puntatore generico detto puntatore a void:

void *generic_pointer;

e successivamente assegnargli un puntatore di altro tipo, utilizzando la proprietà dei puntatori di poter essere assegnati indifferentemente tra tipi diversi. L'utilizzo del puntatore a void si rivela utile nei casi di funzioni che possono restituire puntatori di tipi diversi, vedremo a proposito delle funzioni le possibilità di questi casi.

Torna all'indice