Ingresso interattivo (scanf(); %lf) Se un programma deve essere eseguito una sola volta, i dati possono essere inseriti direttamente nel programma stesso. Tuttavia, nella maggior parte dei casi, è necessario immettere i dati in un programma mentre esso è in esecuzione. A questo scopo si usa la funzione scanf(). Mentre printf() visualizza una copia del valore memorizzato in una variabile, scanf() consente di immettere da tastiera un valore che viene visualizzato sullo schermo e memorizzato direttamente in una variabile. Come printf(), anche scanf() richiede una stringa di controllo come primo argomento dentro le parentesi del nome di funzione. La stringa di controllo dice alla funzione il tipo dati che viene immesso e usa le stesse sequenze di controllo di printf(). Tuttavia, a differenza della stringa di controllo usata in printf(), quella passata a scanf() consiste tipicamente in sole sequenze di controllo di conversione. Inoltre, a differenza di printf(), dove la stringa di controllo può essere seguita da un elenco di NOMI di variabile, scanf() richiede che la stringa di controllo sia seguita da un elenco di INDIRIZZI di variabile. Ad es., una chiamata alla funzione scanf() potrebbe essere l’istruzione scanf(“%d”, &num1); La sequenza di controllo di conversione %d è la stessa usata in printf(), e dice a scanf() che opererà su un numero intero. Come si osserva, in scanf() si è usato l’operatore di indirizzo & davanti alla variabile num1 (ricordiamo che &num1 si legge l’indirizzo di num1). Quando incontra un’istruzione del tipo scanf(“%d”, &num1); il computer sospende l’esecuzione del programma e scansiona in continuazione la tastiera per ricevere dati (scanf è un’abbreviazione per scan function). Quando viene digitato un dato, scanf() lo memorizza nell’indirizzo che le viene fornito. Quindi il programma continua l’esecuzione con l’istruzione successiva a scanf(). A questo proposito consideriamo il programma seguente: #include <stdio.h> void main(void) { float num1, num2, prodotto; printf(“Scrivi un numero: “); scanf(“%f”, &num1); printf(“Scrivi un altro numero: “); scanf(“%f”, &num2); prodotto = num1 * num2; printf(“%f per %f uguale %f”, num1, num2, prodotto); } Esso produce la seguente uscita: Scrivi un numero: 22 Scrivi un altro numero: 4 22.000000 per 4.000000 uguale 88.0000 La prima chiamata a printf() produce un prompt, cioè un messaggio che dice all’utente cosa deve scrivere, in questo caso un numero. Quindi il computer esegue l’istruzione successiva, che è una chiamata a scanf(); questa pone il computer in uno stato di attesa temporanea (o wait) fino a che l’utente digita un valore e lo invia a scanf() premendo il tasto <Invio>. Il valore viene assegnato alla variabile num1, inviandolo al suo indirizzo (&num1) che è stato passato a scanf(). Quindi il computer esce dallo stato di attesa e il programma prosegue con l’istruzione successiva. Questa è un’altra chiamata a printf(), che fa visualizzare il messaggio successivo. Di nuovo la seconda chiamata a scanf() pone il computer in uno stato di attesa temporanea finché l’utente digita un secondo valore, che viene inviato all’indirizzo della variabile num2. Sebbene nel programma ogni chiamata a scanf() sia stata usata per memorizzare un valore in una variabile, si può usare scanf() per immettere e memorizzare tanti valori quante sono le sequenze di controllo di conversione presenti nella sua stringa di controllo. Ad es., l’istruzione scanf(“%f %f”, &num1, &num2); fa sì che due valori siano letti da tastiera e inviati agli indirizzi delle variabili num1 e num2. Nella stringa di controllo lo spazio tra le due sequenze di controllo di conversione %f e %f è stato inserito solo per una migliore leggibilità, e anche la stringa di controllo %f%f avrebbe funzionato ugualmente. Tuttavia, quando si immettono i numeri da tastiera, va lasciato almeno uno spazio tra essi (per indicare la fine di un numero e l’inizio del successivo), indipendentemente da quale stringa sia stata usata (%f %f o %f%f). L’unico caso in cui uno spazio influenzi i valori immessi si ha quando scanf() si aspetta un tipo dati carattere. Ad es., l’istruzione scanf(“%c%c%c”, &car1,&car2,&car3); fa sì che scanf() memorizzi i prossimi tre caratteri digitati rispettivamente nelle variabili car1, car2, car3. Se si scrive x y z x è memorizzato in car1, uno spazio vuoto è memorizzato in car2 e y in car3. Se invece si usa l’istruzione scanf(“%c %c %c”, &car1,&car2,&car3); scanf() si aspetta tre caratteri separati esattamente da uno spazio. Si tenga presente che, se va immesso un numero in precisione doppia, con scanf() si deve usare la sequenza di controllo di conversione %lf Come printf(), nemmeno scanf() controlla i tipi dati dei valori immessi, ma deve essere l’utente ad assicurarsi che tutte le variabili siano dichiarate corettamente e che tutti numeri immessi siano del tipo corretto. Tuttavia, scanf() è abbastanza versatile da eseguire qualche conversione di tipi dati. Per esempio, se viene fornito un intero al posto di un numero in virgola mobile o in doppia precisione, scanf() aggiunge automaticamente il punto decimale alla fine del numero prima di memorizzarlo. Analogamente, se viene immesso un numero in virgola mobile o in doppia precisione quando è atteso un intero, scanf() usa solo la parte intera del numero. Costanti simboliche (#define). Un dato letterale è un qualsiasi dato all’interno di un programma che identifichi esplicitamente se stesso. Ad es., le costanti 2 e 3.1416 nell’istruzione di assegnazione circonf = 2 * 3.1416 * raggio si dicono anche letterali, perché sono letteralmente inserite direttamente nell’istruzione. In molti casi uno stesso letterale compare più volte in un programma: per esempio un programma che calcola gli interessi bancari potrebbe usare spesso il tasso d’interesse. In tale caso, se fosse necessario cambiare il valore del tasso, si dovrebbero effettuare numerose correzioni al programma, con il rischio di commettere errori. Conviene allora sfruttare la possibilità, offerta dal C, di definire il valore una sola volta, uguagliando il numero a un nome simbolico e usando tale nome, in tutto il programma, al posto del numero. Per uguagliare un numero a un nome simbolico si usa l’istruzione #define, come nei due esempi seguenti: #define TASSO 0.05 #define PIGR 3.1416 Osservazioni. 1.L’istruzione #define, detta anche istruzione di equivalenza non viene elaborata dal compilatore C che trasforma le istruzioni in linguaggio macchina, ma è un’istruzione al preprocessore di C (come indica il simbolo #), e pertanto termina senza punto e virgola. Essa ovviamente si deve trovare prima delle istruzioni che usano i relativi nomi simbolici, e di solito viene scritta all’inizio del file, prima della funzione main(). 2. I nomi simbolici, detti anche costanti nominate o costanti simboliche, sono scritti di solito in lettere maiuscole, per distinguerli dai nomi di variabili dichiarati nelle istruzioni di dichiarazione. Ecco un esempio di uso dell’istruzione #define: #define TASSO 0.15 #include <stdio.h> void main(void) { float netto, imposta, totale; printf(“\nScrivi il netto da pagare: “); scanf(“%f”, &netto); imposta = TASSO * netto; totale = netto + imposta; printf(“L’imposta da pagare è E %4.2f”, imposta); printf(“\nIl totale da pagare è E %5.2f”, totale); } Selezione Espressioni relazionali. Oltre a eseguire le operazioni di addizione, sottrazione, moltiplicazione e divisione, tutti i computer hanno la capacità di confrontare i numeri. Giacché molte situazioni in cui vengono prese decisioni apparentemente “intelligenti” possono essere ridotte al livello di scegliere tra due valori, la capacità di confronto di un computer può essere usate per creare una prestazione più o meno simile all’intelligenza. Le espressioni usate per confrontare gli operandi sono dette espressioni relazionali. Una semplice espressione relazionale consiste in un operatore relazionale che collega due operandi, che possono essere variabili o costanti, come Gli operatori relazionali disponibili in C sono indicati in tabella, e possono essere usati con dati interi, in virgola mobile, doppia precisione o caratteri. Le espressioni relazionali sono spesso dette condizioni, e come tutte le espressioni in C sono valutate per fornire un risultato numerico, che può essere solamente un valore intero 0 o 1. Una condizione interpretata come vera ha valore intero 1, e una falsa ha valore 0. Ad esempio, dato che la relazione 3 < 4 è sempre vera, essa ha valore 1, mentre la relazione 2.0 > 3.3, che è sempre falsa, ha valore 0. Ciò può essere verificato eseguendo le istruzioni printf(“Il valore di 3 < 4 è %d”, 3<4); printf(“\nIl valore di 2.0 > 3.3 è %d”, 2.0>3.3); che producono appunto i risultati Il valore di 3 < 4 è 1 Il valore di 2.0 > 3.3 è 0 Il valore di un’espressione relazionale quale ore > 0 dipende dal valore memorizzato nella variabile ore. In un programma C, non è tanto importante il valore di un’espressione relazionale quanto l’interpretazione che C fornisce del valore quando si usa l’espressione come parte di un’istruzione di selezione. In tali espressioni, che vedremo tra poco, C usa il valore zero per rappresentare una condizione falsa, e qualsiasi valore diverso da zero per rappresentare una condizione vera. La scelta di quale azione svolgere si baserà quindi sul valore ottenuto. In aggiunta agli operandi numerici, con gli operatori relazionali si possono confrontare anche dati carattere. Ricordando che, per esempio, il codice ASCII per la lettera A è minore di quello per la lettera B, e così via, le seguenti espressioni sono valutate come indicato. Confronare le lettere è essenziale quando si debbano ordinare alfabeticamente dei nomi, o quando si usino i caratteri per selezionare una particolare scelta nelle situazioni in cui vadano prese decisioni. Operatori logici. Oltre a usare come condizioni espressioni relazionali semplici, si possono creare condizioni più complesse usando gli operatori logici AND (&&), OR (||) e NOT (!), già visti in precedenza. Quando si usa l’operatore AND, &&, con due espressioni semplici, la condizione composta è vera (ha valore 1) solo se entrambe le espressioni sono vere. Perciò la condizione composta peso > 60 && peso < 70 è vera solo se la variabile peso ha un valore compreso fra 61 e 69. Quando si usa l’operatore OR, ||, la condizione composta è vera se almeno una delle condizioni è vera. Perciò la condizione composta peso < 60 || peso >70 è vera se la variabile peso ha un valore non compreso fra 61 e 69. L’operatore NOT, !, viene usato per cambiare lo stato o valore di un’espressione. Cioè se allora espressione !espressione è falsa è vera. Gli operatori relazionali e logici hanno una gerarchia di esecuzione simile a quella degli operatori aritmetci. La tabella seguente elenca la precedenza di questi operatori in relazione agli altri che abbiamo visto. Vediamo, come esempio, come verrebbe valutata la seguente espresione: (6 * 3 == 36 / 2) || (13 < 3 * 3 + 4) && !((6 - 2) < 5) ( 18 == 18 ) 1 1 1 || (13 < 9 || (13 < 13) || 0 || 1 + 4) && !( && !1 && 0 0 4 < 5) Istruzione if-else. Questa istruzione fa scegliere al computer una sequenza di una o più istruzioni a seconda del risultato di un confronto. La sua forma generale è: if (condizione) istruzione1; else istruzione2; Prima di tutto viene valutata la condizione: • se il suo valore è diverso da zero viene eseguita l’istruzione1 • se è uguale a zero viene eseguita l’istruzione2. Così, a seconda del valore della condizione, viene sempre eseguita una delle due istruzioni. Per maggiore chiarezza, l’istruzione if-else si può scrivere anche su quattro linee: if (condizione) istruzione1; else istruzione2; L’uso di if-else è illustrato nel seguente programma: #include <stdio.h> void main(void) { float imponibile, tasse; printf(“Scrivi l’importo tassabile: “); scanf(“%f”, &imponibile); if (imponibile <= 10000.0) tasse = 0.2 * imponibile; else tasse = 0.25 * (imponibile - 10000.0) + 2000.0; printf(“Le tasse sono E %7.2f”, tasse); } Ecco l’uscita prodotta: Istruzioni composte. Le parti if ed else della istruzione ifelse possono in realtà contenere una istruzione composta, ossia un numero qualsiasi di istruzioni semplici racchiuse tra le parentesi { e }. Lo schema è il seguente: if (condizione) { istruzione1; istruzione2; . . . } else { istruzioneA; istruzioneB; . . . } L’uso di una istruzione composta è illustrato nel programma seguente, che converte una temperatura da gradi Celsius in gradi Fahrenheit o viceversa, secondo la formula F = 9/5*C + 32. #include <stdio.h> void main(void) { char tipo_temp; float temp, fahren, celsius; printf(“Scrivi la temperatura da convertire: “); scanf(“%f”, &temp); printf(“Scrivi f se la temperatura è in Fahrenheit”); printf(“\n o c se la temperatura è in Celsius ”); scanf(“\n%c”, &tipo_temp); if (tipo_temp == ‘f’) { celsius = (5.0/9.0) * (temp - 32.0); printf(“\nLa temperatura Celsius equivalente è %6.2f”, celsius); } else { fahren = (9.0/5.0) * temp + 32.0; printf(“\nLa temperatura Fahrenheit equivalente è %6.2f”, fahren); } } Ecco l’uscita prodotta: Scrivi la temperatura da convertire: 40 Scrivi f se la temperatura è in Fahrenheit o c se la temperatura è in Celsius c La temperatura Fahrenheit equialente è 104.00 Scrivi la temperatura da convertire: 104 Scrivi f se la temperatura è in Fahrenheit o c se la temperatura è in Celsius f La temperatura Celsius equivalente è 40.00 Istruzione if a una via. Una modifica talvolta utile dell’istruzione if-else consiste nell’omettere la sua parte else. In tale caso l’istruzione assume la forma abbreviata: if (condizione) istruzione; l’istruzione che segue la condizione viene eseguita solamente se la condizione ha valore diverso da zero (è vera). Anche adesso l’istruzione può essere composta. Questa forma modificata dell’istruzione if è detta istruzione if a una via, ed è illustrata nel programma seguente, che chiede la targa e il chilometraggio di una vettura, e stampa un messaggio se la vettura ha percorso più di 30000 kilometri. #define LIMITE 3000.0 #include <stdio.h> void main(void) { int id_targa; float km; printf(“Scrivi la targa e il chilometraggio della vettura: “); scanf(“%d %f”, &id_targa, &km); if(km > LIMITE) printf(“ La vettura %d ha superato il limite.\n”, id_targa); printf(“Fine dell’esecuzione del programma.\n”); } Esso produce l’uscita seguente: Scrivi la targa e il chilometraggio della vettura: 12345 50000 La vettura 12345 ha superato il limite. Fine dell’esecuzione del programma. Istruzioni if nidificate. Come abbiamo visto, un’istruzione ifelse può contenere una qualsiasi istruzione valida in C, semplice o composta, quindi anche un’altra istruzione if-else. Ad es., in un’istruzione if-else si può inserire una selezione a una via come nel seguente segmento di programma: if (ore < 9) { if (ore > 6) printf(“dentro”); } else printf(“fuori”); Esso stampa la parola “dentro” se la variabile ore ha valore 7 o 8, stampa “fuori” se ha un valore minore di 7 o maggiore di 8. Osservazione. Le parentesi {} che racchiudono l’if a una via interno sono essenziali, perché in loro assenza C assocerebbe else con il più vicino if non appaiato e non con quello più esterno, interpretando la precedente istruzione nel seguente modo: if (ore < 9) if (ore > 6) printf(“dentro”); else printf(“fuori”); In questo caso si avrebbe la stessa stampa di prima se ore è minore di 9, e nessuna stampa se è uguale o maggiore di 9. Catena if-else. Come si vede, il caso in cui l’istruzione nella parte if di un’istruzione if-else sia un’altra istruzione if crea confusione, e viene quindi evitato. Tuttavia, può essere utile situare un’altra istruzione if-else nella parte else di un’istruzione if-else. Ciò assume la forma: Questa costruzione è detta catena if-else, ed è ampiamente usata nei programmi applicativi. Ogni condizione è valutata nell’ordine, e se qualcuna è vera viene eseguita l’istruzione corrispondente e il seguito della catena termina. L’ultima istruzione else viene eseguita solamente se nessuna delle condizioni precedenti è soddisfatta, ed è utile per rivelare una condizione impossibile o di errore. È ovvio che la catena può continuare indefinitamente, inserendo quante si vogliano istruzioni else if prima dell’ultima else, come pure che ciascuna istruzione individuale può essere sostituita da un’istruzione composta, racchiusa tra le parentesi { e }. Esercizio. Scrivere un programma che: • chieda di immettere la lettera iniziale dello stato civile di una persona (S=sposato/a, C/N=celibe/nubile, D=divorziato/a, V=vedovo/a; • stampi il relativo stato civile. Il programma seguente realizza quanto richiesto: #include <stdio.h> void main(void) { char codstatociv; printf(“Scrivi un codice di stato civile (S, C, N, D o V): “); scanf(“%c”, &codstatociv); if (codstatociv == ‘S’) printf(“\nLa persona è sposata”); else if (codstatociv == ‘C’ || codstatociv == ‘N’) printf(“\nLa persona è celibe o nubile”); else if (codstatociv == ‘D’) printf(“\nLa persona è divorziata”); else if (codstatociv == ‘V’) printf(“\nLa persona è vedova”); else printf(“È stato scritto un codice non valido”); } Istruzione switch La catena if-else è usata in applicazioni di programmazione dove si deve scegliere un gruppo di istruzioni tra più alternative possibili. L’istruzione switch fornisce un’alternativa alla catena ifelse nei casi in cui si confronti il valore di un’espressione intera con un valore specifico. La forma generale dell’istruzione switch è: switch (condizione) { case valore_1: istruzione1; istruzione2; . break; case valore_2: istruzionea . break; case valore_n: istruzionex; . break; default: istruzioneaa; . } . L’istruzione switch usa quattro nuove parole chiave: switch, case, default, break. Vediamone i compiti. La parola chiave switch identifica l’inizio dell’istruzione. La condizione tra parentesi che la segue viene valutata e il risultato confrontato con i diversi valori alternativi contenuti nell’istruzione composta. La condizione deve avere un valore intero, altrimenti si verifica un errore di compilazione. All’interno dell’istruzione switch si usa la parola chiave case per identificare o etichettare i singoli valori che vengono confrontati con quello della condizione switch. I valori sono confrontati nell’ordine con cui sono scritti, fino a che si trova una corrispondenza; in tale caso l’esecuzione comincia con la prima istruzione che segue il valore di corrispondenza. Se il valore della condizione non corrisponde ad alcuno dei valori case, non viene eseguita alcuna istruzione, a meno che non s’incontri la parola chiave default. In tale caso l’esecuzione del programma inizia dall’istruzione che segue la parola default. default è opzionale, e opera nella stessa maniera dell’ultima else in un’istruzione if-else. Una volta che l’istruzione switch abbia localizzato un punto d’ingresso, non vengono più valutate altre espressioni case, e vengono eseguite tutte le istruzioni che seguono all’interno delle parentesi { e }, fino a che non si incontri l’istruzione break. Essa quindi identifica la fine di un particolare case e determina l’uscita immediata dall’istruzione switch. In altri termini, come la parola case identifica i possibili punti di partenza dell’istruzione composta, così l’istruzione break determina i punti terminali. Se si omettono le istruzioni break, vengono eseguite tutte le case che seguono quella con il valore di corrispondenza, compresa la default. Esercizio. Scrivere un programma che: • richieda due operandi e un codice numerico di operazione; • a seconda del codice immesso, esegua sui due operandi una delle operazioni di addizione, moltiplicazione o divisione. Un programa che usa l’istruzione switch è il seguente: #include <stdio.h> void main(void) { int sceltaoper; double num1, num2; printf(“Scrivi due numeri: “); scanf(“%lf %lf”, &num1, &num2); printf(“Scrivi il codice operazione: “); printf(“\n 1 per addizione”); printf(“\n 2 per moltiplicazione”); printf(“\n 3 per divisione”); scanf(“%d”, &sceltaoper); switch (sceltaoper) { case 1: printf(“La somma dei numeri scritti è %6.3lf”, num1+num2); break; case 2: printf(“Il prodotto dei numeri scritti è %6.3lf”,num1*num2); break; case 3: printf(“Il quoziente dei numeri scritti è %6.3lf”,num1/num2); break; } }