|
Appunti informatica |
|
Visite: 1445 | Gradito: | [ Medio appunti ] |
Leggi anche appunti:Le variabiliLe variabili E' il momento di ripescare CIAO.C e complicarlo un poco. #include Contenuto del floppy diskContenuto del floppy disk Il floppy disk allegato costituisce una raccolta di L'i/o e la gestione dei fileL'I/O e la gestione dei file Per Input/Output (I/O) si intende l'insieme delle |
Le costanti, in senso lato, sono dati che il programma non può modificare. Una costante è, ad esempio, la sequenza di caratteri 'Ciao Ciao!n' vista in precedenza: per la precisione, si tratta di una costante stringa. Essa non può essere modificata perché non le è associato alcun nome simbolico a cui fare riferimento in un'operazione di assegnazione. Una costante è un valore esplicito, che può essere assegnato ad una variabile, ma al quale non può essere mai assegnato un valore diverso da quello iniziale.
Ad esempio, una costante di tipo character (carattere) è un singolo carattere racchiuso tra apici.
char c1, c2 = 'A';
c1 = 'b';
c2 = c1;
'c' = c2; //ERRORE! impossibile assegnare un valore a una costante
Una costante intera con segno è un numero intero:
int unIntero = 245, interoNegativo = -44;
Una costante intera senza segno è un numero intero seguito dalla lettera U, maisucola o minuscola, come ci insegna il nostro CIAO2.C
unsigned int anni = 31U;
Per esprimere una costante di tipo long occorre posporle la lettera L, maiuscola o minuscola
long abitanti = 987553L;
Omettere la L non è un reato grave il compilatore segnala con un warning che la costante è long e procede tranquillamente. In effetti, questo è l'atteggiamento tipico del compilatore C: quando qualcosa non è chiaro tenta di risolvere da sé l'ambiguità, e si limita a segnalare al programmatore di avere incontrato qualcosa di poco convincente. Il compilatore C 'presume' che il programmatore sappia quel che sta facendo e non si immischia nelle ambiguità logiche più di quanto sia strettamente indispensabile.
Una U (o u) individua una costante unsigned; le costanti unsigned long sono identificate, ovviamente, da entrambe le lettere U e L, maiuscole o minuscole, in qualsivoglia ordine. Le costanti appartenenti ai tipi integral possono essere espresse sia in notazione decimale (come in tutti gli esempi visti finora), sia in notazione esadecimale (anteponendo i caratteri 0x o 0X al valore) sia in notazione ottale (anteponendo uno al valore).
char beep = 07; // ottale; 7
unsigned long uLong = 12UL; // decimale; 12 unsigned long
unsigned maxUInt = 0xFFFFU; // esadecimale; 65535 unsigned
Una costante di tipo floating point in doppia precisione (double) può essere espressa sia in notazione decimale che in notazione esponenziale: in questo caso si scrive la mantissa seguita dalla lettera E maiuscola o minuscola, a sua volta seguita dall'esponente. Per indicare che la costante è in singola precisione (float), occorre posporle la lettera F, maiuscola o minuscola. Per specificare una costante long double occorre la lettera L
float varF = 1.0F;
double varD = 1.0;
double varD_2 = 1.; // lo 0 dopo il punto decimale puo' essere omesso
long double varLD = 1.0L; // non e' un long int! C'e' il punto decimale!
double varD_3 = 2.34E-2; // 0.0234
Dagli esempi si deduce immediatamente che la virgola è espressa, secondo la convenzione anglosassone, con il punto ('
Il C non riconosce le stringhe come tipo di dato, ma ammette l'utilizzo di costanti stringa (seppure con qualche limite, di cui si dirà): esse sono sequenze di caratteri racchiuse tra virgolette, come si è visto in più occasioni. Quanti byte occupa una stringa? Il numero dei caratteri che la compongono più uno (pag. ). In effetti le stringhe sono sempre chiuse da un byte avente valore zero binario , detto terminatore di stringa. Il NULL finale è generato automaticamente dal compilatore, non deve essere specificato esplicitamente.
Attenzione: le sequenze di caratteri particolari, come 'n', sono considerate un solo carattere (ed occupano un solo byte). I caratteri che non rientrano tra quelli presenti sulla tastiera possono essere rappresentati con una backslash (barra inversa) seguita da una 'x' e dal codice ASCII esadecimale a due cifre del carattere stesso. Ad esempio, la stringa 'x07x0Dx0A' contiene un 'beep' (il carattere ASCII 7) e un ritorno a capo (i caratteri ASCII 13 e 10, questi ultimi equivalenti alla sequenza 'n
I codici ASCII possono essere utilizzati anche per esprimere un singolo carattere:
char beep = 'x07';
E' del tutto equivalente assegnare ad una variabile char un valore decimale, ottale o esadecimale o, ancora, il valore espresso con x tra apici. Attenzione, però: la rappresentazione ASCII di un carattere è cosa ben diversa dal suo valore ASCII; 0x07 e 'x07' sono tra loro equivalenti, ma diversi da
La differenza tra un singolo carattere rispetto ad una stringa di un solo carattere sta negli apici, che sostistuiscono le virgolette. Inoltre, 'x07' occupa un solo byte, mentre 'x07' ne occupa due, uno per il carattere ASCII 7 e uno per il NULL che chiude ogni stringa.
Non esistono costanti di tipo void
Supponiamo di scrivere un programma per la gestione dei conti correnti bancari. E' noto (e se non lo era ve lo dico io) che nei calcoli finanziari la durata dell'anno è assunta pari a 360 giorni. Nel sorgente del programma si potrebbero perciò incontrare calcoli come il seguente:
interesse = importo * giorniDeposito * tassoUnitario / 360;
il quale impiega, quale divisore, la costante intera
E' verosimile che nel programma la costante compaia più volte, in diversi contesti (principalmente in formule di calcolo finanziario). Se in futuro fosse necessario modificare il valore della costante (una nuova normativa legale potrebbe imporre di assumere la durata dell'anno finanziario pari a 365 giorni) dovremmo ricercare tutte le occorrenze della costante ed effettuare la sostituzione con . In un sorgente di poche righe tutto ciò non rappresenterebbe certo un guaio, ma immaginando un codice di diverse migliaia di righe suddivise in un certo numero di file sorgenti, con qualche centinaio di occorrenze della costante, è facile prevedere quanto gravoso potrebbe rivelarsi il compito, e quanto grande sarebbe la possibilità di non riuscire a portarlo a termine senza errori.
Il preprocessore C consente di aggirare l'ostacolo mediante la direttiva #define , che associa tra loro due sequenze di caratteri in modo tale che, prima della compilazione ed in modo del tutto automatico, ad ogni occorrenza della prima (detta manifest constant) è sostituita la seconda.
Il nome della costante manifesta ha inizio col primo carattere non‑blank che segue la direttiva #define e termina con il carattere che precede il primo successivo non‑spazio; tutto quanto segue quest'ultimo è considerato stringa di sostituzione.
Complicato? Solo in apparenza
#define GG_ANNO_FIN 360 //durata in giorni dell'anno finanziario
interesse = importo * giorniDeposito * tassoUnitario / GG_ANNO_FIN;
L'esempio appena visto risolve il nostro problema: modificando la direttiva #define in modo che al posto del compaia il e ricompilando il programma, la sostituzione viene effettuata automaticamente in tutte le righe in cui compare GG_ANNO_FIN
Va sottolineato che la direttiva #define non crea una variabile, né è associata ad un tipo di dato particolare: essa informa semplicemente il preprocessore che la costante manifesta, ogniqualvolta compaia nel sorgente in fase di compilazione, deve essere rimpiazzata con la stringa di sostituzione. Gli esempi che seguono forniscono ulteriori chiarimenti: in essi sono definite costanti manifeste che rappresentano, rispettivamente, una costante stringa, una costante in virgola mobile, un carattere esadecimale e una costante long integer, ancora esadecimale.
#define NOME_PROG 'Conto 1.0' //nome del programma
#define PI_GRECO 3.14 //pi greco arrotondato
#define RETURN 0x0D //ritorno a capo
#define VIDEO_ADDRESS 0xB8000000L //indirizzo del buffer video
Le costanti manifeste possono essere definite utilizzando altre costanti manifeste, purché definite in precedenza:
#define N_PAG_VIDEO 8 //numero di pagine video disponibili
#define DIM_PAG_VIDEO 4000 //4000 bytes in ogni pagina video
#define VIDEO_MEMORY (N_PAG_VIDEO * DIM_PAG_VIDEO) //spazio memoria video
Una direttiva #define può essere suddivisa in più righe fisiche mediante l'uso della backslash
#define VIDEO_MEMORY
(N_PAG_VIDEO * DIM_PAG_VIDEO)
L'uso delle maiuscole nelle costanti manifeste non è obbligatorio; esso tuttavia è assai diffuso in quanto consente di individuarle più facilmente nella lettura dei sorgenti.
Come tutte le direttive al preprocessore, anche la #define non si chiude mai con il punto e virgola (un eventuale punto e virgola verrebbe inesorabilmente considerato parte della stringa di sostituzione); inoltre il crosshatch (' ', cancelletto) deve trovarsi in prima colonna.
La direttiva #define, implementando una vera e propria tecnica di sostituzione degli argomenti, consente di definire, quali costanti manifeste, vere e proprie formule, dette macro, indipendenti dai tipi di dato coinvolti:
#define min(a,b) ((a < b) ? a : b) // macro per il calcolo del minimo tra due
Come si vede, nella macro min(a,b) non è data alcuna indicazione circa il tipo di a e b: essa utilizza l'operatore , che può essere applicato ad ogni tipo di dato . Il programmatore è perciò libero di utilizzarla in qualunque contesto.
Le macro costituiscono dunque uno strumento molto potente, ma anche pericoloso: in primo luogo, la mancanza di controlli (da parte del compilatore) sui tipi di dato può impedire che siano segnalate incongruenze logiche di un certo rilievo (sommare le pere alle mele, come si dice). In secondo luogo, le macro prestano il fianco ai cosiddetti side‑effect , o effetti collaterali. Il C implementa un particolare operatore, detto di autoincremento , che accresce di una unità il valore della variabile a cui è anteposto: se applicato a uno dei parametri coinvolti nella macro, esso viene applicato più volte al parametro, producendo risultati indesiderati:
int var1, var2;
int minimo;
minimo = min(++var1, var2);
La macrosotituzione effettuata dal preprocessore trasforma l'ultima riga dell'esempio nella seguente:
minimo = ((++var1 < var2) ? ++var1 : var2);
E' facile vedere che esso si limita a sostituire alla macro min la definizione data con la #define, sostituendo altresì i parametri a e b con i simboli utilizzati al loro posto nella riga di codice, cioè ++var1 e var2. In tal modo var1 è incrementata due volte se dopo il primo incremento essa risulta ancora minore di var2, una sola volta nel caso opposto. Se min() fosse una funzione il problema non potrebbe verificarsi (una chiamata a funzione non è una semplice sostituzione di stringhe, ma un'operazione tradotta in linguaggio macchina dal compilatore seguendo precise regole); tuttavia una funzione non accetterebbe indifferentemente argomenti di vario tipo, e occorrerebbe definire funzioni diverse per effettuare confronti, di volta in volta, tra integer, tra floating point, e così via. Un altro esempio di effetto collaterale è discusso a pag.
Diamo un'occhiata all'esempio che segue:
#define PROG_NAME 'PROVA'
printf(PROG_NAME);
#undef PROG_NAME
printf(PROG_NAME); // Errore! PROG_NAME non esiste piu'
Quando una definizione generata con una #define non serve più, la si può annullare con la direttiva #undef . Ogni riferimento alla definizione annullata, successivamente inserito nel programma, dà luogo ad una segnalazione di errore da parte del compilatore.
Da notare che PROG_NAME è passata a printf() senza porla tra virgolette, in quanto esse sono già parte della stringa di sostituzione, come si può vedere nell'esempio. Se si fossero utilizzate le virgolette, printf() avrebbe scritto PROG_NAME e non PROVA: il preprocessore, infatti, ignora tutto quanto è racchiuso tra virgolette o apici. In altre parole, esso non ficca il naso nelle costanti stringa e in quelle di tipo carattere.
Vale la pena di citare anche la direttiva #ifdef#else#endif , che consente di includere o escludere dalla compilazione un parte di codice, a seconda che sia, o meno, definita una determinata costante manifesta:
#define DEBUG
#ifdef DEBUG
// questa parte del sorgente e' compilata
#else
// questa no (lo sarebbe se NON fosse definita DEBUG)
#endif
La direttiva #ifndef e' analoga alla #ifdef, ma lavora con logica inversa:
#define DEBUG
#ifndef DEBUG
// questa parte del sorgente NON e' compilata
#else
. // questa si (NON lo sarebbe se NON fosse definita DEBUG)
#endif
Le direttive #ifdef e #ifndef risultano particolarmente utili per scrivere codice portabile (vedere pag. ): le parti di sorgente differenti in dipendenza dal compilatore, dal sistema o dalla macchina possono essere escluse o incluse nella compilazione con la semplice definizione di una costante manifesta in testa al sorgente.
E' di recente diffusione, tra i programmatori C, la tendenza a limitare quanto più possibile l'uso delle costanti manifeste, in parte proprio per evitare la possibilità di effetti collaterali, ma anche per considerazioni relative alla logica della programmazione: le costanti manifeste creano problemi in fase di debugging , poiché non è possibile sapere dove esse si trovino nella memoria dell'elaboratore (come tutte le costanti, non hanno indirizzo conoscibile); inoltre non sempre è possibile distinguere a prima vista una costante manifesta da una variabile, se non rintracciando la #define (l'uso delle maiuscole e minuscole è libero tanto nelle costanti manifeste quanto nei nomi di variabili, pertanto nulla garantisce che un simbolo espresso interamente con caratteri maiuscoli sia effettivamente una costante manifesta).
Il C consente di definire delle costanti simboliche dichiarandole come vere e proprie variabili, ma anteponendo al dichiaratore di tipo la parola chiave const. Ecco un paio di esempi:
const int ggAnnoFin = 360;
const char return = 0x0D;
E' facile vedere che si tratta di dichiarazioni del tutto analoghe a quelle di variabili; tuttavia la presenza di const forza il compilatore a considerare costante il valore contenuto nell'area di memoria associata al nome simbolico. Il compilatore segnala come illegale qualsiasi tentativo di modificare il valore di una costante, pertanto ogni costante dichiarata mediante const deve essere inizializzata contestualmente alla dichiarazione stessa.
const int unIntCostante = 14;
unIntCostante = 26; //errore: non si puo' modificare il valore di una costante
Il principale vantaggio offerto da const è che risulta possibile accedere (in sola lettura) al valore delle costanti così dichiarate mediante l'indirizzo delle medesime (come accade per tutte le aree di memoria associate a nomi simbolici): ancora una volta rimandiamo gli approfondimenti alla trattazione dei puntatori (pag.
Infine, le costanti simboliche possono essere gestite dai debugger proprio come se fossero variabili.
E' preferibile utilizzare la L maiuscola, poiché, nella lettura dei listati, quella minuscola può facilmente essere scambiata con la cifra
Le espressioni di controllo generate da un carattere preceduto dalla backslash sono anche dette sequenze ANSI.
Degli operatori C parleremo diffusamente a pag. e seguenti. Il significato di può essere, per il momento, dedotto dall'esempio. Per ora merita attenzione il fatto che molti compilatori implementano max() e min() proprio come macro, definite in uno dei file .H di libreria.
La fase, cioè, di ricerca e correzione degli errori di programmazione. Questa è effettuata con l'aiuto di sofisticati programmi, detti debuggers, che sono spesso in grado di visualizzare il contenuto delle variabili associandovi il nome simbolico; cosa peraltro impossibile con le costanti manifeste, che ne sono prive.
Appunti su: |
|