giovedì 1 novembre 2012

Piccolo tutorial sul linguaggio C - tipi di dato primitivi


Questo è il primo di una breve serie di post dedicati ad un piccolo tutorial sul linguaggio C. Durante questo breve viaggio nel C, come pretesto per introdurre i costrutti del linguaggio stesso, realizzeremo un semplice termostato ambiente in C.




 Le specifiche del progetto sono le seguenti: 

Problema: si scriva un programma in C per il controllo della temperatura ambientale (termostato). Il programma dovrà accettare da tastiera i seguenti comandi:
  • O hh mm ss - imposta orologio interno alle hh:mm:ss;
  • M tempmin  - imposta la temperatura minima ambientale, es. t 16,5 imposta la temperatura minima ammissibile a 16,5 °C;
  • T temp - imposta la temperatura desiderata nelle ore on, es. T 21,5;
  • H oraon - imposta l'ora durante la quale il termostato è in funzione e deve essere mantenuta la temperatura T, es. h 5 indica al termostato che dalle 5:00 alle 6:00 deve attivarsi e mantenere la temperatura T;
  • o - mostra a schermo l'orario corrente;
  • m - mostra a schermo la la temperatura minima ambientale impostata;
  • t - mostra a schermo la la temperatura desiderata impostata;
  • h - mostra a schermo le ore on impostate;
  • 1 - attiva il termostato;
  • 0 - spegni il termostato;
  • a - accensione manuale del riscaldamento;
  • s - spegnimento manuale del riscaldamento;
Il programma dovrà controllare un impianto di riscaldamento utilizzando le funzioni heaton() e heatoff che, rispettivamente, accendono e spengono l'impianto di riscaldamento. tali funzioni sono già state realizzate e disponibili nella libreria custom denominata HeatLib. i prototipi delle due funzioni sono i seguenti:
  • void heaton();
  • void heatoff();
Per ottenere la temperatura ambientale si dovrà usare la funzione dammitemperatura() che restituisce un valore intero compreso tra  -1000  e +1000 per valori di temperatura compresi tra -50 °C e +50 °C. il prototipo della funzione dammitemperatura() è il seguente:

  • int dammitemperatura();

Tipi di dato 

Cos'è un tipo di dato e perché dobbiamo capire i tipi di dato disponibili in un determinato linguaggio di programmazione? I tipi di dato ci consentono di rappresentare informazioni del mondo reale (analogico) all'interno di un programma, scritto in un determinato linguaggio di programmazione, che può essere eseguito da un calcolatore elettronico (digitale).
Vediamo di capire insieme come scegliere un tipo di dato adatto alla realtà che vogliamo rappresentare nel progetto del termostato ambientale.

Tipi di dato primitivi

Nota. In generale, il C non impone regole precise sullo spazio di memoria dedicato ai tipi di dato trattati. Nello standard sono indicati solo i valori minimi. Di conseguenza, a seconda del compilatore e/o della piattaforma hardware usata si potranno avere dimensioni fisiche dei tipi di dato diverse. Nella nostra trattazione faremo quindi riferimento ai valori minimi indicati dallo standard e a due combinazioni di compilatori e/o piattaforma hardware. 

Il tipo char

Per realizzare il nostro termostato, dovranno essere acquisiti dei comandi in ingresso di un solo carattere e, guarda caso :), il C ha un tipo di dato elementare denominato char che serve proprio a questo scopo.
All'inizio del nostro programma dovrà essere dichiarata una variabile che conterrà il comando immesso  da tastiera.

char comandotastiera;

Osserviamo subito che in C per dichiarare una variabile occorre prima esplicitare il tipo e poi il nome della variabile seguito da punto e virgola. Nel caso di più variabili dello stesso tipo è sufficiente elencarle separate da virgole.
Il tipo char consente il trattamento di caratteri alfanumerici e "speciali". Storicamente, nei calcolatori i caratteri alfanumerici e speciali sono stati rappresentati  utilizzando un codice numerico. Ci sono diversi sistemi di codifica dei caratteri, il più noto è il codice ASCII che comprende la definizione di codici, da 0 a 127, 
per 128 caratteri: 33 di questi sono caratteri di controllo non-stampabili che influiscono sul modo in cui vengono processati i testi e lo spazio vuoto (che è anch'esso un carattere rappresentato dal codice 32) e  95 caratteri stampabili, incluso il già citato spazio bianco. per rappresentare in binario un numero da 0 a 127 occorrono 7 bit e poiché la memoria dei calcolatori è in genere suddivisa in locazioni di almeno 8 bit, o un byte, ne deriva che un char occupa in memoria almeno un byte.  Una costante carattere, ad esempio la lettera h che ci servirà per verificare se l'utente del termostato ha premuto il tasto h, è rappresentata racchiudendo tra apici semplici il carattere costante. Ad esempio, per assegnare alla variabile di tipo char comandodidefault il carattere 1, dovremo utilizzare la seguente istruzione:

comandodidefault = '1';

Le costanti char predefine in C sono:

'\n' newline 
'\r' carriage return 
'\b' backspace 
'\f' formfeed 
'\t' horizontal tab
'\v' vertical tab

Infine, occorre osservare che, poiché con 8 bit si possono rappresentare interi tra -128 e + 127 in complemento a 2, il tipo char è fondamentalmente una rappresentazione binaria degli interi tra -128 e +127, Ne esiste poi una versione senza segno unsigned char che consente la memorizzazione di interi tra 0 e 255. 

Il tipo int

Le specifiche di progetto ci dicono che la funzione dammitemperatura() restituisce un intero compreso tra -1000 e +1000 che rappresenta valori di temperatura tra -50°C e +50°C. 
In C gli interi sono rappresentati, oltre che con il tipo char, con il tipo int e le sue varianti.  La dimensione in memoria del tipo int non è fissa, lo standard prevede solo che sia di almeno di 2 byte. Con 16 bit in complemento a due si possono rappresentare gli interi tra -32768 e 32767, più che sufficienti per rappresentare il dato restituito dalla funzione dammitemperatura(). Quindi se volessimo memorizzare il valore della temperatura corrente come intero dovremmo aggiungere all'inizio del nostro programma una dichiarazione come la seguente:

int temperaturacorrenteintera;

Il C prevede alcune varianti del tipo int che consentono di rappresentare numeri interi piccoli, grandi, molto grandi, molto molto grandi  e/o senza segno. In particolare,  

short int : dim. minima = 2 Byte, intervallo rappresentabile: –32.768..+32.767
unsigned short int : dimensione minima = 2 Byte, intervallo rappresentabile : 0..65.535
long int : dim. minima = 4 Byte, intervallo rappresentabile : –2.147.483.648.. +2.147.483.647
unsigned long int :  dim. minima = 4 Byte, intervallo rappresentabile : 0..4.294.967.295

il tipo float

I moderni termostati mostrano la temperatura con valori decimali approssimati, in genere, alla prima cifra dopo la virgola.  Ad esempio, 20,5 °C è un valore tipico da impostare sul termostato della nostra caldaia domestica. Il tipo di dato in virgola mobile float del C è la migliore approssimazione disponibile per rappresentare i numeri decimali in C, anche se, vedremo nel seguito perché, non sempre è la soluzione migliore dal punto di vista dell'uso efficiente delle risorse di calcolo e di memoria. 
Nel nostro progetto avremo sicuramente bisogno di una variabile temperaturaambienteminima, che conterrà il valore minimo di temperatura al disotto del quale il termostato deve attivare il riscaldamento dell'ambiente, di tipo float nel seguente modo:

float temperaturaambienteminima;

Potremmo inizializzare questa variabile all'inizio del nostro programma ad un valore di default di 17,5 °C:

temperaturaambienteminima = 17.5; // notare che il separatore dei decimali è il
                                  // punto '.' e non la virgola ',';
Notare che con // diciamo al compilatore di non tenere conto di quello che c'è scritto 
Anche il tipo float ha una  variante per rappresentare numeri "grandi" denominata double. Le dimensioni di memoria minime imposte dallo standard sono:

float : numeri in virgola mobile a precisione singola, dimensione minima = 32 bit
double : numeri in virgola mobile a precisione doppia, dimensione minima = 64 bit
long double : numeri in virgola mobile a precisione grande, dimensione tipica = 80 bit

Il tipo enumerativo 

Qualche volta è utile avere a disposizione una variabile che contenga qualcosa di diverso da un numero o da uno o più caratteri.. Nel caso del termostato ambiente potremmo avere la necessità di tenere traccia dello stato del termostato che potrebbe trovarsi in modalità 'spento', 'acceso', 'programmato','manuale' o altro. In questo caso ci vengono in aiuto i tipi enumerativi. Ad esempio, potremmo avere un tipo stato termostato definito come segue:

enum statoT = {Spento, Acceso, Programmato, Manuale, Errore};

e una variabile statoTermostato definita così:

statoT statoTermostato;

E' interessante osservare che i valori che può assumere la variabile statoTermostato non sono stringhe, quindi non sono ammesse le operazioni tipiche delle stringhe sulle variabili enumerative, ma nomi simbolici ai quali il compilatore associa delle costanti numeriche, con il primo simbolo (Spento) che vale 0, il secondo simbolo (Acceso) che vale 1 e così via.
E' spesso utile non lasciare al compilatore la decisione di quale valore intero associare ai simboli. In questi casi la dichiarazione del tipo enumerativo consente di assegnare valori arbitrari, purché interi e univoci, ad ogni simbolo. Ad esempio, nel nostro caso si potrebbe avere:

enum statoT = {Spento            = 0,
                           Acceso            = 1
                           Programmato = 2,
                           Manuale          = 4,
                           Errore             =127};

Le costanti

Quante costanti conoscete? Sono certo che la stragrande maggioranza di voi conosce almeno la costante π, che, come è noto, vale 3,1415926535...
In C una sequenza di cifre con o senza la virgola decimale è interpretata come una costante numerica di tipo double o int a seconda che sia presente o meno la virgola decimale. Quindi, ad esempio, i numeri 0, 1, 2, 4, 127 presenti nella dichiarazione del tipo statoT sono costanti di tipo int, mentre 3.14 è una costante di tipo float.
Se abbiamo bisogno di costanti di tipo int molto grandi dobbiamo aggiungere la lettera l o L alla fine del numero. Ad esempio 124589357900021l è una costante long int. Così, se abbiamo bisogno di numeri decimali con maggiore precisione dei double, basta aggiungere alla fine del numero la lettera l o L per avere delle costanti long double. Se invece, ci basta una dinamica minore aggiungiamo f o F alla fine del numero per avere una costante float.
Per indicare invece delle costanti intere in notazione ottale basta mettere uno 0 (zero) e le cifre ottali a seguire. Mentre, i numeri esadecimali sono formati da 0x e le cifre esadecimali a seguire.
Allo stesso modo si possono utilizzare i numeri ottali e esadecimali per le costanti char utilizzando il carattere di escape '\'. Ad esempio, come è noto la costante char 'A' è rappresentabile come numero intero 65, esadecimale 0x41 o ottale 0101. Si ha che, tutte le assegnazioni riportate qui di seguito producono lo stesso risultato, ovvero assegnare alla variabile a il carattere 'A':

a = 'A';
a = '\x41';
a = ''\0101';


In ultimo, anche la famigerata stringa "Hello, world!" è una costante, una costante stringa. In C le costanti stringa possono essere spezzate in quante parti si vuole senza che il compilatore se ne abbia a male così "Hello," " world!" è la stessa cosa di "Hello, world!". Basta stare attenti alla corretta apertura e chiusura delle "". A proposito, "" è la costante stringa vuota!
Tutti i testi sacri dicono che è meglio non sparpagliare nel codice le costanti numeriche o alfanumeriche che siano ma, di dichiararle tutte in un posto per facilitare la manutenzione.
Il meccanismo prediletto per creare delle costanti facilmente manutenibili è quello delle macro. E' un argomento che merita più di un post, qui però ci basta dire che per definire una costante con questo meccanismo basta usare la seguente sintassi:

#define nomecostante valore // Si noti che non c'è il punto a virgola dopo valore.
Ad esempio, se volessimo definire una costante per la temperatura di gelo potremmo inserire la seguente istruzione all'inizio del programma:

#define TGELO 0.0

Il compilatore, tutte le volte che troverà il simbolo TGELo lo sosotituirà con il valore double 0.0.

Per ora è tutto in un prossimo post vedremo insieme alcuni aspetti avanzati dei tipi di dato primitivi e il casting dei tipi in C.

Nessun commento:

Posta un commento