LA PROGRAMMAZIONE NEL LINGUAGGIO C Cenni storici. • 1972: Prima implementazione del linguaggio C su elaboratore PDP11 da parte di D. M. Ritchie presso i laboratori AT&T Bell. È introdotto per potere riscrivere in un linguaggio di alto livello il codice del sistema operativo UNIX. È un’evoluzione del B e del BCPL in cui sono aggiunti i tipi e le strutture di controllo. • Anni ’80: Sviluppo del “C tradizionale”. • 1983: Inizia la definizione dello standard “ANSI C” da parte dell’American National Standards Institute. • 1990: L’International Standardization Organization (ISO) approva l’ANSI C (o “ANSI/ISO C”). È a questo standard che fa riferimento il corso e i libri consigliati. Per approfondire la storia del C, si veda l’articolo originale “The Development of the C Language”, di Dennis M. Ritchie, sul sito http://cm.bell-labs.com/cm/cs/who/dmr/chist.html Introduzione: la modularità. Un programma ben disegnato è costruito usando una filosofia di progetto simile a quella usata per costruire un edificio; anche nel caso di un programma una parte fondamentale del progetto è costituita dalla struttura. Nella programmazione il termine “struttura” ha due significati collegati. • il primo si riferisce alla costruzione globale del programma • il secondo alla forma usata per eseguire i singoli compiti all’interno del programma. In relazione al primo significato, i programmi la cui struttura consiste in segmenti correlati, disposti in un ordine logico e facilmente comprensibile a formare una unità integrata e completa sono detti programmi modulari. I programmi modulari sono più facili da sviluppare, correggere e modificare di quelli costruiti in altro modo. Nella terminologia generale della programmazione, i più piccoli segmenti usati per costruire un programma modulare sono detti moduli. Ogni modulo è progettato e sviluppato per svolgere un compito specifico, ed è in realtà un sotto-programma. Un programma in C completo è costruito combinando tutti i moduli che servono per produrre il risultato desiderato. Il vantaggio della costruzione modulare è che il progetto complessivo del programma può essere sviluppato prima di scrivere qualsiasi modulo singolo. Dato che un modulo è in realtà un piccolo sotto-programma, ogni modulo deve eseguire ciò che si richiede a tutti i programmi: ricevere ed elaborare i dati e produrre un risultato. Tuttavia, a differenza di un programma più grande, un modulo compie una operazione singola, ed è finalizzato a gestire, al massimo una o due funzioni richieste dal programma completo. Per questa ragione, in C i moduli sono detti funzioni. Funzioni È utile pensare a una funzione come a una macchina che trasforma i dati che riceve in un prodotto finito. La figura illustra una funzione che accetta due numeri in ingresso e li moltiplica per produrre l’uscita. Il modo in cui gli ingressi sono trasformati in risultati è incapsulato e nascosto all’interno della funzione Nel costruire una funzione è opportuno darle un nome che richiami l’idea di ciò che essa fa: ad es. si potrebbe chiamare gradi_rad() una funzione che converta i gradi in radianti. I nomi validi per le funzioni sono usati anche per denominare altri elementi del linguaggio C, e sono complessivamente detti identificatori. Gli identificatori sono composti da qualsiasi combinazione di lettere, cifre e carattere di sottolineatura (_), scelti secondo le seguenti regole: 1. il primo carattere deve essere una lettera o il segno di sottolineatura (_ ); 2. dopo il primo carattere ci possono essere solo lettere, cifre o segni di sottolineatura; non sono consentiti spazi vuoti, punti, virgole o simboli speciali quali ( ) & $ # ! \ ? 3. il nome non può avere più di 31 caratteri. 4. il nome non può essere una delle parole chiave indicate in tabella Oltre a rispettare le regole del C per gli identificatori, il nome di una funzione deve essere seguito da una coppia di parentesi tonde. Quindi, ad es.: Si tenga presente che i nomi delle funzioni sono tradizionalmente scritti in lettere minuscole, e che il C è un linguaggio sensibile al carattere (case sensitive) maiuscolo/minuscolo. La funzione main(). Una volta assegnato un nome alle funzioni, esse devono essere combinate in un programma completo. In C si può pianificare un programma decidendo dapprima quali funzioni servano e come vadano collegate, e solo successivamente scrivendo le funzioni effettive. Per fornire un ordinato posizionamento ed esecuzione delle funzioni, ogni programma in C deve avere una funzione detta main() che indica al compilatore il punto in cui inizia l’esecuzione del programma. main() è detta anche funzione pilota, dato che dice alle altre funzioni la sequenza in cui devono operare. Lo schema seguente illustra una funzione main() completa La prima linea della funzione, void main(void), è detta linea di intestazione della funzione. Essa contiene tre tipi di informazioni: La parola chiave void, posta prima del nome della funzione, indica che essa non restituirà alcun valore una volta eseguita. Invece, posta dentro le parentesi che seguono il nome della funzione, void indica che non verranno passati dati alla funzione quando sarà eseguita (i dati passati a una funzione sono detti i suoi argomenti). Le parentesi graffe { } determinano l’inizio e la fine del corpo della funzione e racchiudono le istruzioni che la costituiscono. Tali istruzioni determinano cosa farà la funzione, e ciascuna di esse va terminata con un punto e virgola (;). La precedente funzione main() consiste in quattro istruzioni, ciascuna delle quali è il comando di eseguire un’altra funzione, nell’ordine: gross_pay(), taxes(), net_pay(), output(). La funzione printf(). Il C utilizza numerose funzioni di biblioteca, tra le quali una delle più diffuse e utili è printf(). Essa, come suggerisce il nome, è una funzione di stampa che formatta i dati che le vengono forniti e li visualizza sullo schermo. A printf()s può fornire, ad es., un messaggio quale Salve a tutti!. Il fornire dati o messaggi a una funzione è detto anche passare dati alla funzione. Per passare il messaggio Salve a tutti! a printf()è sufficiente inserirlo nelle parentesi del nome della funzione come segue: printf(“Salve a tutti!”); Possiamo pensare alle parentesi nei nomi di funzione come a un imbuto attraverso il quale si passa l’informazione alla funzione. Come abbiamo già osservato, i dati passati alla funzione attraverso le parentesi sono detti gli argomenti della funzione. Possiamo mettere insieme tutti questi elementi nel seguente programma. #include <stdio.h> void main(void) { printf(“Salve a tutti!”); } La prima linea #include <stdio.h> è un comando al preprocessore. I comandi al preprocessore cominciano con il segno #, ed eseguono determinate azioni prima che il compilatore traduca il programma sorgente in codice macchina. In particolare, il comando al preprocessore #include inserisce il contenuto del file indicato - in questo caso stdio.h, - nel punto in cui compare il comando #include. Il file stdio.h è detto file header, dato che è collocato in cima a un programma C tramite il comando #include. Esso fornisce una interfaccia opportuna alla funzione printf(), e deve essere inserito in tutti i programmi che usano tale funzione. Come si nota nel programma precedente, i comandi al preprocessore non terminano con un punto e virgola. Dopo il comando al preprocessore c’è l’inizio della funzione main(), costituita da una sola istruzione. Ricordiamo che le istruzioni terminano con punto e virgola. L’istruzione in main() chiama la funzione prinf() e le passa un argomento, costituito dal messaggio Salve a tutti!. Dato che prinf() è una funzione predefinita, essa non va scritta, ma viene utilizzata semplicemente chiamandola in modo corretto. Come tutte le funzioni C, prinf() è stata scritta per svolgere un compito specifico, cioè stampare i risultati. È versatile e può stampare i risultati in molte forme differenti. In C i messaggi sono chiamati stringhe, dato che consistono in stringhe di caratteri costituite da lettere, numeri e caratteri speciali. L’inizio e la fine di una stringa di caratteri sono contrassegnati con doppi apici (“). Perciò, per passare un messaggio a prinf(), la stringa di caratteri che costituisce il messaggio deve essere racchiusa tra doppi apici, come abbiamo visto. Sequenza di escape \n. Ecco un altro programma che illustra le possibilità di prinf(): #include <stdio.h> void main(void) { printf(“Computer, computer”); printf(“\n dovunque io guardi”); } I due caratteri \ (barra trasversa) e n, se usati insieme, sono detti sequenza di escape di linea nuova. Essi non vengono visualizzati nella stampa del risultato, ma indicano a printf() di iniziare a stampare in una linea nuova. In particolare, il carattere \ indica al computer di “uscire” (escape) dalla interpretazione normale del carattere che lo segue, alterandone il significato. Altre sequenze di escape sono indicate in tabella, con i loro significati. Sebbene ciascuna sequenza sia costituita da due caratteri distinti, la loro combinazione fa memorizzare al computer il codice ASCII di un singolo carattere (vedi Fonda1, diap. 48). Una o più sequenze di escape di linea nuova possono essere inserite in un punto qualsiasi del messaggio passato a printf(). Ad es., il programma seguente #include <stdio.h> void main(void) { printf(“Computer, computer\n ovunque\n\nio guardi”); } produce la seguente visualizzazione: Computer, computer ovunque io guardi L’ultimo carattere stampato da qualsiasi funzione che esegua una visualizzazione dovrebbe essere sempre \n, in modo che la funzione lasci il cursore nella posizione iniziale di una linea nuova. Infatti ogni nuova visualizzazione comincia nel punto in cui è terminata la visualizzazione precedente. Commenti. I commenti sono note esplicative all’interno di un programma. Se usati con accortezza, aiutano a chiarire cosa fa l’intero programma, un gruppo di istruzioni o una singola linea. Un commento inizia con i caratteri /* (senza spazi intermedi), e termina con i caratteri */. I commenti possono essere situati in un punto qualsiasi del programma, e non hanno effetto sulla sua esecuzione, dato che il compilatore li ignora. Il loro uso è illustrato dal seguente programma. #include <stdio.h> void main(void) /* questo programma stampa un messaggio */ { printf(“Salve a tutti!”); /* chiamata a printf() */ } Esso produce la stessa uscita del programma precedente. Operatori aritmetici I numeri interi, in virgola mobile e in doppia precisione possono essere sommati, sottratti, divisi e moltiplicati. Sebbene sia preferibile non mescolare gli interi con gli altri due tipi dati numerici nell’eseguire operazioni aritmetiche, se nella medesima espressione aritmetica si usano tipi dati differenti si ottengono risultati prevedibili. Anche i dati carattere si possono sommare e sottrarre con i dati carattere e intero, ottenendosi risultati utili. Gli operatori usati per queste operazioni sono detti operatori aritmetici: Ciascun operatore aritmetico è di tipo binario e richiede due operandi. Una semplice operazione aritmetica consiste in un operatore aritmetico che collega due operandi aritmetici. Esempi: 18 – 3 12.62 + 9.8 .08 * 12.2 12.6 / 2. In questi esempi gli spazi prima e dopo gli operatori sono inseriti solo a scopo di chiarezza, e si possono omettere senza alterare il valore delle espressioni. Un’espressione che contenga solo operandi interi è detta espressione intera, e ha come risultato un valore intero. Analogamente, un’espressione che contenga solo operandi in virgola mobile (in singola e doppia precisione) è detta espressione in virgola mobile, e ha come risultato un valore in doppia precisione. Un’espressione che contenga operandi sia interi sia in virgola mobile è detta espressione mista, e ha come risultato un numero in doppia precisione. Tuttavia è meglio non mescolare nella stessa espressione operandi interi e in virgola mobile. Divisione intera (operatore modulo %). La divisione di due interi può produrre risultati strani, dato che anche il risultato fornito è un intero. Così 15/2 fornisce il risultato 7, con la parte decimale troncata. Per ottenere il resto di una divisione tra interi, C fornisce l’operatore %, detto operatore modulo. Esempi: 9 % 4 fornisce 1 17 % 3 fornisce 2 14 % 2 fornisce 0 Operatore unario (negazione). Oltre agli operatori binari visti, C fornisce anche degli operatori unari. Uno di essi impiega lo stesso segno meno della sottrazione binaria (-), e serve per cambiare segno a un numero positivo. La tabella seguente riassume le 6 operazioni aritmetiche viste finora, e mostra i tipi dati dei risultati prodotti da ciascun operatore, a seconda del tipo dati degli operandi coinvolti. Precedenza e associatività degli operatori. Il C, come tutti i linguaggi di programmazione, prevede delle regole per la precedenza e l’associativtà degli operatori. Esse sono: 1. I simboli di due operatori aritmetici binari non devono mai essere situati vicini. Ad es., 5 * %6 non è valido. 2. Si posono usare le parentesi per formare dei gruppi, e tutte le espressioni racchiuse tra parentesi sono valutate per prime. Si possono anche racchiudere gruppi di parentesi all’interno di altri gruppi di parentesi, e in tal caso vengono valutate per prime le espressioni dentro le parentesi più interne. Il numero di parentesi aperte deve essere uguale al numero di parentesi chiuse. 3. Per indicare le moltiplicazioni non si possono usare le parentesi, e va usato invece l’operatore*. Ad es., l’espressione (3+4)(5+1) non è valida e va sostituita con (3+4)*(5+1). La precedenza di un operatore stabilisce la sua priorità relativamente agli altri operatori. Nella tabella seguente, gli operatori in cima alla tabella hanno priorità più alta di quelli in fondo. Le espressioni contenenti operatori con la stessa precedenza sono valutate in base alla loro associatività, cioè o da sinistra a destra o viceversa. Vediamo, come esempio, come verrebbe valutata la seguente espressione: 8 8 8 8 + 5 * 7 % 2 * 4 = + 35 % 2 * 4 = + 1 * 4 = + 4 = 12 Sequenze di controllo di conversione In aggiunta alla visualizzazione di messaggi, la funzione printf() permette di valutare espressioni aritmetiche e di visualizzarne i risultati. A tale fine si devono passare a printf(), inserendoli nelle sue parentesi, almeno due argomenti, separati da una virgola: - una stringa di controllo, che dice alla funzione dove e in che forma visualizzare il risultato; - l’espressione di cui vogliamo che sia visualizzato il risultato. Sequenza %d. Consideriamo, ad es., l’istruzione printf(“La somma di 6 e 15 è %d”, 6 + 15); 1° argomento 2° argomento Il primo argomento passato a printf() deve essere sempre un messaggio. Un messaggio che comprenda anche una sequenza di controllo di conversione, quale %d, è detto stringa di controllo. Messaggio + Sequenza di controllo di = conversione Stringa di controllo Le sequenze di controllo di conversione (dette anche specificazioni di conversione e specificatori di formato) hanno un significato speciale per la funzione printf(), in quanto le dicono quale tipo di valore visualizzare e dove. Una sequenza di controllo di conversione inizia sempre con il simbolo % e termina con uno dei caratteri di conversione d, ld, u, f, e, c. Come vedremo, tra il simbolo % e il carattere di conversione si possono inserire caratteri aggiuntivi. Quindi, in definitiva, • il segno % in una sequenza di controllo di conversione dice a printf() di stampare un valore nella posizione del messaggio dove si trova il %. • la d, subito dopo il %, dice che il valore deve essere stampato come un intero. Quando printf() vede nella sua stringa di controllo la sequenza di controllo di conversione, sostituisce al suo posto il valore dell’argomento successivo. Dato che l’argomento successivo è l’espressione 6 + 15, che ha il valore 21, è questo il valore che viene visualizzato. Perciò la precedente istruzione printf(“La somma di 6 e 15 è %d”, 6 + 15); produce la visualizzazione La somma di 6 e 15 è 21 Interi lunghi (long, %ld). In genere i numeri interi sono usati nei programmi come contatori che tengono traccia del numero di volte che un evento si è verificato. Di solito i computer destinano agli interi 2 byte, con i quali si possono memorizzare numeri interi non superiori a 32.767; tale numero è più che sufficiente per la maggior parte dei conteggi necessari. Talvolta, tuttavia, sono necessari numeri interi più grandi. Ad es., in molte applicazioni finanziarie le date sono convertite nel numero di giorni dall’inizio del secolo, in modo da disporre di un solo numero per memorizzarle e ordinarle, e ciò può richiedere numeri maggiori di 32.767. Per tali esigenze il C fornisce i tipi dati intero lungo intero corto intero senza segno che si ottengono aggiungendo rispettivamente i qualificatori long short unsigned alle normali istruzioni di dichiarazione di interi. Ad es., l’istruzione di dichiarazione long int giorni; dichiara la variabile giorni come intero lungo. In essa la parola int è opzionale, cosicché si può scrivere anche long giorni; La quantità di memoria allocata per un intero lungo dipende dal computer, e può essere conosciuta usando l’operatore sizeof che vedremo in seguito. Una volta che una variabile sia stata dichiarata come intero lungo, un valore intero le può essere assegnato o come si fa con gli interi soliti, oppure aggiungendo una lettera l (o L) all’intero (senza spazi). Ad es., l’istruzione di dichiarazione long giorni = 38276L; dichiara la variabile giorni di tipo intero lungo, e le assegna la costante 38.276 di tipo intero lungo. Per visualizzare un valore intero lungo con la funzione printf() si deve usare la sequenza di controllo di conversione %ld. Interi corti e interi senza segno (short, unsigned, %u). In aggiunta al qualificatore long, C fornisce: - il qualificatore - il qualificatore short unsigned per gli interi corti; per gli interi senza segno. Anche la quantità di memoria allocata per gli interi corti dipende dal computer e può essere conosciuta con l’operatore sizeof. Il tipo dati intero senza segno è usato per memorizzare solo interi positivi, e in effetti raddoppia i valori positivi che possono essere memorizzati, senza aumentare il numero di byte allocati per un intero. Per dichiarare, ad es., la variabile giorni di tipo intero senza segno si scrive: unsigned int giorni; o anche unsigned giorni; Per visualizzare un intero senza segno con printf(), o leggerlo con scanf(), si usa la sequenza di controllo di conversione %u. Numeri in virgola mobile (%f). Come la sequenza di controllo di conversione %d fa visualizzare a printf() un valore intero, così la sequenza %f (f sta per floating point) fa visualizzare un numero in virgola mobile (ossia con il punto decimale). Ad es., l’istruzione printf(“La somma di %f e %f è %f.”, 12.2,15.754,12.2+15.754); produce la visualizzazione La somma di 12.200000 e 15.754000 è 27.954000. Come si vede, la sequenza di controllo di conversione %f fa visualizzare a printf() 6 cifre alla destra del punto decimale, aggiungendo degli zeri se il numero non ha 6 cifre decimali, arrotondando la parte decimale se il numero ne ha più di 6. Dato che printf() non controlla i valori che le sono passati, se si usa una sequenza di controllo di conversione intera (%d) ma il valore passato alla funzione è un numero in virgola mobile o a doppia precisione, la visualizzazione dipenderà dal computer. Analogamente, se si usa una sequenza di controllo di conversione in virgola mobile e il numero corrispondente è intero, si otterrà un risultato imprevisto. Il programma seguente illustra l’uso di printf() per visualizzare i risultati di quattro espressioni in virgola mobile. #include <stdio.h> void main(void) { printf(“%f più printf(“%f meno printf(“%f per printf(“%f diviso } %f %f %f %f uguale uguale uguale uguale %f\n”, %f\n”, %f\n”, %f\n”, 15.0, 15.0, 15.0, 15.0, 2.0, 2.0, 2.0, 2.0, 15.0 15.0 15.0 15.0 Esso produce la seguente visualizzazione: 15.000000 15.000000 15.000000 15.000000 più meno per diviso 2.000000 2.000000 2.000000 2.000000 uguale 17.000000 uguale 13.000000 uguale 30.000000 uguale 7.500000 + * / 2.0); 2.0); 2.0); 2.0); Si noti che ogni istruzione del programma precedente passa 4 argomenti alla funzione printf(): una stringa di controllo e 3 valori. In ogni stringa di controllo vi sono tre sequenze di controllo di conversione (una per ciascun valore che deve essere visualizzato). Notazione esponenziale (%e). La sequenza di controllo di conversione %e (e sta per exponent) fa visualizzare un numero in virgola mobile nella notazione esponenziale. Perciò, se si modifica il programma precedente come segue, #include <stdio.h> void main(void) { printf(“%f più printf(“%f meno printf(“%f per printf(“%f diviso } %f %f %f %f uguale uguale uguale uguale %e\n”, %e\n”, %e\n”, %e\n”, 15.0, 15.0, 15.0, 15.0, 2.0, 2.0, 2.0, 2.0, 15.0 15.0 15.0 15.0 + * / si ottiene la seguente visualizzazione: 15.000000 15.000000 15.000000 15.000000 più meno per diviso 2.000000 2.000000 2.000000 2.000000 uguale uguale uguale uguale 1.700000e+01 1.300000e+01 3.000000e+01 7.500000e+00 2.0); 2.0); 2.0); 2.0); Sequenza %c. I caratteri singoli sono visualizzati usando la sequenza di controllo di conversione %c. Ad es., l’istruzione printf(“La prima lettera dell’alfabeto è %c.”, ‘a’); produce la visualizzazione La prima lettera dell’alfabeto è a. Sequenza %s. Le stringhe di caratteri sono visualizzate usando la sequenza di controllo di conversione %s. Ad es., il programma #include <stdio.h> void main(void) { printf("Il mio nome è : %9s", "Angelo"); printf("\nIl mio cognome : %9s", "Gallippi"); } produce la visualizzazione Il mio nome è : Angelo Il mio cognome è: Gallippi Specificatori di larghezza di campo Numeri interi. Oltre a visualizzare risultati corretti, è importante che un programma li presenti in maniera ordinata. Il formato dei dati visualizzati da printf() può essere controllato da specificatori di larghezza di campo inclusi come parte di ciascuna sequenza di controllo di conversione. Ad es., l’istruzione printf(“La somma di%3d e%4d è%5d.”, 6, 15, 21); produce la visualizzazione La somma di 6 e 15 è 21 I numeri 3, 4 e 5 nella stringa di controllo sono gli specificatori di larghezza di campo. Lo specificatore di larghezza di campo per la prima sequenza di controllo di conversione, %3d, fa visualizzare il primo numero in una larghezza totale di campo di 3 spazi, in questo caso 2 spazi vuoti seguiti dal numero 6. Lo specificatore %4d fa visualizzare 2 spazi vuoti e il numero 15, per una larghezza totale di campo di 4 spazi. L’ultimo specificatore, %5d, fa visualizzare 3 spazi vuoti e il numero 21, per una larghezza totale di campo di 5 spazi, che comprende. Osservazione. Mentre in assenza di uno specificatore di larghezza di campo la giustificazione avveniva a sinistra, la presenza di uno specificatore di larghezza di campo determina la giustificazione a destra. Il programma seguente illustra come una colonna di interi viene allineata in assenza di specificatori di larghezza di campo #include <stdio.h> void main(void) { printf(“\n%d”, 6); printf(“\n%d”, 18); printf(“\n%d”, 124); printf(“\n---“); printf(“\n%d”, 6+18+124); } Esso produce l’uscita Invece il programma seguente illustra come gli specificatori di campo permettano di allineare i numeri secondo le cifre delle unità. #include <stdio.h> void main(void) { printf(“\n%3d”, 6); printf(“\n%3d”, 18); printf(“\n%3d”, 124); printf(“\n---“); printf(“\n%3d”, 6+18+124); } L’uscita prodotta è: Pertanto gli specificatori di larghezza di campo sono utili per visualizzare colonne di numeri in modo che essi risultino incolonnati correttamente. Numeri in virgola mobile. I numeri in virgola mobile formattati richiedono due specificatori di larghezza di campo: • il primo determina la larghezza di visualizzazione totale, compreso il punto decimale; • il secondo determina il numero di cifre visualizzate alla destra del punto decimale. Ad es., l’istruzione printf(“|%10.3f|” ,25.67); produce la visualizzazione | 25.670| Lo specificatore 10.3 fa visualizzare il numero in un campo totale di 10 spazi, che comprende il punto decimale e tre cifre alla sua destra. Dato che 25.67 contiene solo due cifre alla destra del punto decimale, a esse viene aggiunto uno zero. Se ne contenesse di più, esse verrebbero arrotondate. Modificatori di formato In aggiunta alle sequenze di controllo di conversione e agli specificatori di larghezza di campo che si possono usare con esse, il C fornisce anche dei modificatori di formato che forniscono due controlli di formato aggiuntivi: • la giustificazione sinistra e • la visualizzazione del segno +. Se usati, i modificatori di formato devono essere situati sempre subito dopo il simbolo %. Giustificazione a sinistra (%-). Abbiamo visto che uno specificatore di larghezza di campo fa giustificare a destra i dati visualizzati. Per forzare la giustificazione a sinistra, si usa il modificatore di formato segno meno (-). Ad es., l’istruzione printf(“|%-10d|”,59); produce la visualizzazione |59 | (con 8 spazi vuoti che seguono il numero 59). Visualizzazione del segno + (%+). Di solito il segno di un numero è visualizzato solamente se il esso è negativo. Per forzare la visualizzazione del segno, sia negativo sia positivo, si usa il modificatore di formato segno più (+). Ad es., l’istruzione printf(“|%+10d|”,59); produce l’uscita | +59| (con 7 spazi vuoti prima di +59). In assenza del segno + subito dopo il simbolo % nella chiamata della funzione printf(), la visualizzazione non conterrebbe il segno del numero positivo. I modificatori di formato possono essere combinati. Ad es., la sequenza di controllo di conversione %-+10d fa sì che un numero intero visualizzi il suo segno e sia giustificato a sinistra in un campo largo 10 spazi. Dato che l’ordine dei modificatori di formato non è critico, la stessa sequenza di controllo si sarebbe potuta scrivere %+-10d. Determinazione della occupazione di memoria (sizeof()). Per determinare la quantità di memoria che il computer alloca per ciascun tipo dati, il C fornisce l’operatore sizeof(). Esso fornisce il numero di byte dell’oggetto o tipo dati racchiuso tra parentesi. Un esempio del suo uso è fornito dal programma seguente: #include <stdio.h> void main(void) { char ch; int num1; printf(“Byte di memoria usati per un carattere: %d “, sizeof(ch)); printf(“\nByte di memoria usati per un intero: %d “, sizeof(num1)); } Esso produce l’uscita Byte di memoria usati per un carattere: 1 Byte di memoria usati per un intero: 4 Variabili e dichiarazioni Tutti i dati usati in un programma vanno memorizzati e richiamati dalla memoria del computer. Consideriamo i 4 byte di memoria illustrati in figura, supponendo che i due byte con indirizzi 1321 e 1322 siano usati per memorizzare un intero, e che i due byte con indirizzi 2649 e 2650 siano usati per memorizzare un secondo intero. Nei linguaggi di alto livello, quale il C, al posto degli indirizzi effettivi di memoria si usano nomi simbolici detti variabili. Le variabili sono semplicemente nomi assegnati dai programmatori alle locazioni di memoria del computer. Si usa il termine “variabile” perché il valore memorizzato nella locazione può cambiare. Per ogni nome usato dal programmatore, il computer tiene traccia dell’indirizzo effettivo. Dare un nome a una variabile equivale ad assegnare un nome a una stanza di albergo, e riferirsi a essa con questo nome - quale Suite imperiale - anziché con il suo numero effettivo. La scelta dei nomi delle variabili è lasciata al programmatore, purché segua le regole già indicate per i nomi di funzione. In particolare, i nomi delle variabili sono scritti di solito in minuscolo, ed è opportuno che siano il più possibile mnemonici, ossia indicativi del tipo dato memorizzato. Istruzioni di assegnazione. Osserviamo la seguente figura Supponiamo che: i due byte che iniziano all’indirizzo 45 abbiano il nome di variabile risultato i due byte che iniziano all’indirizzo 1321 abbiano il nome di variabile num1 i due byte che iniziano all’indirizzo 2649 abbiano il nome di variabile num2 Usando questi nomi di variabile, le operazioni di memorizzare 62 nella locazione 1321 sono eseguite memorizzare 17 nella locazione 2649 dalle seguenti sommare i contenuti delle due locazioni istruzioni num1 = 62; num2 = 17; risultato=num1+num2; Esse sono chiamate istruzioni di assegnazione, dato che dicono al computer di assegnare un valore a una variabile. Le istruzioni di assegnazione hanno sempre un segno di uguale (=) e un nome di variabile subito alla sua sinistra. Il valore alla sua destra viene dapprima determinato e quindi assegnato alla variabile alla sinistra del segno di uguale. Gli spazi vuoti sono inseriti a solo scopo di leggibilità. I nomi di variabili sono utili perché evitano al programmatore di doversi preoccupare di dove i dati siano fisicamente memorizzati nel computer. Istruzioni di dichiarazione. Tuttavia, prima di memorizzare i valori nelle variabili, il C richiede che venga indicato esplicitamente il tipo dati che sarà memorizzato in ciascuna variabile. Vanno indicati in anticipo al computer i nomi delle variabili che saranno usate per i caratteri, per gli interi e per gli altri tipi dati supportati dal C. In tal modo, quando si userà il nome di variabile, il computer saprà a quanti byte di memoria accedere. Per assegnare un nome a una variabile e definire il tipo dati che vi può essere memorizzato si usano le istruzioni di dichiarazione. Esse vanno collocate subito dopo la parentesi { di apertura di una funzione e, come tutte le istruzioni C, terminano con un punto e virgola. La forma generale è: nome della funzione() { istruzioni di dichiarazione; altre istruzioni; } Nella loro forma più semplice, le istruzioni di dichiarazione forniscono un tipo dati e un nome di variabile. Ad es., l’istruzione di dichiarazione int total; dichiara total come nome di una variabile che memorizza un valore di tipo intero. Le variabili usate per contenere valori in virgola mobile sono dichiarate con la parola riservata float quelle usate per valori in doppia precisione sono dichiarate con la parola riservata double e quelle usate per contenere un carattere sono dichiarate con la parola riservata char. Le variabili dello stesso tipo dati possono essere raggruppate insieme e dichiarate con una singola istruzione di dichiarazione. Ad es., le quattro dichiarazioni Le istruzioni di dichiarazione possono essere usate anche per memorizzare un valore iniziale nella variabile dichiarata. Ad es., l’istruzione int num1 = 15; dichiara la variabile num1 intera e le assegna il valore 15. La prima volta che si memorizza un valore in una variabile si dice che essa è inizializzata. Così, nell’esempio precedente si può dire che num1 è stata inizializzata a 15. Istruzioni di dichiarazione e di definizione In aggiunta al loro ruolo software, le istruzioni di dichiarazione svolgono anche un altro compito hardware. Dato che ogni tipo dati ha le sue proprie esigenze di memoria, il computer può allocare sufficiente memoria per una variabile solo dopo avere saputo il suo tipo dati. Poiché le dichiarazioni di variabile forniscono questa informazione, esse possono essere usate per forzare il computer a riservare sufficiente spazio di memoria fisica per ciascuna variabile. Le istruzioni di dichiarazione usate per questo scopo hardware sono dette anche istruzioni di definizione, dato che definiscono o indicano al computer quanta memoria è necessaria per la memorizzazione dei dati. Tutte le istruzioni di dichiarazione viste finora erano anche istruzioni di definizione. Più avanti vedremo istruzioni di dichiarazione che non causano alcuna nuova allocazione di memoria, e sono usate semplicemente per dichiarare o avvertire il programma dei tipi dati delle variabili create in precedenza ed esistenti. Assegnazione Come abbiamo visto, un’istruzione di assegnazione è un’espressione di assegnazione che termina con un punto e virgola (in C qualsiasi espressione che termini con un punto e virgola diventa un’istruzione). La forma generale di un’espressione di assegnazione è: variabile = operando dove il segno di uguale (=) è un operatore binario detto operatore di assegnazione. L’operando può essere una costante, una variabile o un’altra espressione valida. L’operatore di assegnazione fa sì che il valore dell’operando sia memorizzato nella variabile. Esempi di espressioni di assegnazione valide sono: anno = 2006 somma = 90.2+80.3 tasso = primo totale = totale + nuovoval L’operatore di assegnazione ha la precedenza più bassa tra tutti gli operatori aritmetici binari e unari; perciò ogni altro operatore che si trovi in un’espressione insieme a un operatore di assegnazione viene sempre valutato prima di esso. Ad es., nell’espressione imposta = netto * iva dapprima viene valutata l’espressione netto * iva, quindi il valore viene memorizzato nella variabile imposta. Valore di un’espressione di assegnazione. Come tutte le espressioni, anche quelle di assegnazione hanno un valore. Il valore dell’espressione di assegnazione completa è quello assegnato alla variabile a sinistra del segno di =. Ad es., l’espressione a = 5 assegna il valore 5 alla variabile a, e l’espressione stessa stessa ha valore 5. Il valore dell’espressione può sempre essere verificato usando un’istruzione quale printf(“Il valore dell’espressione è %d”, a = 5); Essa visualizza il valore dell’espressione stessa e non il contenuto della variabile a (anche se hanno entrambe lo stesso valore). Il fatto che le espressioni di assegnazione abbiano un valore sarà importante quando considereremo gli operatori relazionali. Assegnazioni multiple. Dato che il segno di uguale è un operatore, nella stessa espressione (o nella sua istruzione equivalente) sono possibili assegnazioni multiple. Ad es., l’espressione a = b = c = 25 ha l’effetto di assegnare il numero 25 a ciascuna variabile individualmente. Dato che l’operatore di assegnazione ha associatività da destra a sinistra, la valutazione procede nell’ordine c = 25 b = c a = b Operatori di assegnazione (+=, -=, *=, /=, %=). Sebbene in un’espressione di assegnazione sia consentita una sola variabile a sinistra del segno =, tale variabile può essere usata anche a destra del sego di =. Per esempio, è valida l’espressione di assegnazione somma = somma + 12 Se fosse un’equazione sarebbe impossibile, mentre come espressione viene valutata in due passaggi: • il primo calcola il valore di somma + 12, • il secondo memorizza tale valore in somma. Espressioni di questo tipo, che usano la stessa variabile da entrambi i lati dell’operatore di assegnazione, possono essere scritte usando i seguenti operatori di assegnazione += -= *= /= %= Quindi, Operatori di incremento e di decremento (++, --). Nel caso particolare in cui una variabile debba essere incrementata o decrementata di 1, il C fornisce due operatori unari. Tramite l’operatore di incremento, ++, l’espressione variabile = variabile + 1 può essere sostituita dall’espressione ++variabile Altri esempi: Tramite l’operatore di decremento, --, l’espressione variabile = variabile – 1 può essere sostituita dall’espressione --variabile. Altri esempi: Quando ++ si trova davanti a una variabile è detto operatore di incremento prefisso. Oltre a trovarsi prima di una variabile, l’operatore di incremento si può trovare anche dopo una variabile, per esempio nell’espressione n++; in tale caso esso è detto operatore di incremento postfisso. Entrambe le espressioni ++n e n++ corrispondono all’espressione più lunga n = n + 1, ma hanno effetti differenti quando sono usate in espressioni di assegnazione. Infatti, Analogamente, entrambe le espressioni --n e n-— diminuiscono di 1 il valore di n e sono equivalenti all’espressione più lunga n = n - 1. Tuttavia producono effetti differenti se usate in espressioni di assegnazione. Infatti, Si noti che, sebbene tutte e tre le seguenti istruzioni cont = cont + 1; cont += 1; ++cont; producano lo stesso effetto quando sono compilate per l’esecuzione, le loro esigenze di memoria sono rispettivamente 9, 4 e 3 byte.