Ripetizione La vera potenza dei programmi per computer risiede nella capacità di ripetere lo stesso calcolo o sequenza di istruzioni più volte, ogni volta usando dati diversi, senza la necessità di fare ripartire il programma per ogni nuovo insieme di dati. Le istruzioni di C che rendono ciò possibile sono: while for do-while. L’istruzione while realizza il diagramma di flusso già visto:
while (condizione) istruzione; La sua forma generale è: while (condizione) istruzione; La condizione racchiusa tra parentesi è valutata esattamente come quella contenuta in un’istruzione if-else, ma è usata in modo differente. Come abbiamo visto, quando la condizione è vera (ha un valore diverso da zero) in un’istruzione if-else, l’istruzione che la segue viene eseguita una volta. In un’istruzione while l’istruzione che segue la condizione viene eseguita ripetutamente fintanto che la condizione viene valutata a un valore diverso da zero. Naturalmente ciò significa che in qualche punto dell’istruzione while ci debba essere un’istruzione che modifichi il valore della condizione valutata. Il procedimento usato dal computer per valutare un’istruzione while è il seguente:
1. valuta la condizione 2. if la condizione ha un valore diverso da zero (vero) a. esegue l’istruzione che segue la parentesi b. torna al punto 1. else esce dall’istruzione while Si noti che il passo 2.b. forza a trasferire il controllo del programma indietro al passo 1. Tale trasferimento del controllo all’inizio dell’istruzione while al fine di ricalcolare la condizione è chiamato ciclo o loop di programma. L’istruzione while letteralmente torna indietro su se stessa per ricontrollare la condizione fino a che la valuta a zero (diventa falsa). Il fatto che un’istruzione while fornisca la ripetizione di una singola istruzione non impedisce di aggiungere un’ulteriore istruzione che cambi il valore di una variabile che controlla il ciclo.
Ciò si ottiene sostituendo l’istruzione singola con una composta, per esempio: { printf(“%d “, cont); ++cont; } Esercizio. Scrivere un programma che, utilizzando l’istruzione while, produca la seguente uscita: 1 2 3 4 5 6 7 8 9 10 Ecco una possibile risposta:
#include <stdio.h> void main(void) { int cont; cont = 1; /* inizializza cont */ while (cont <= 10) printf(“%d “, cont); ++cont; /* incrementa cont */ } Osservazione. In linea di principio l’istruzione ++cont può essere sostituita con una qualsiasi che cambi il valore di cont: per esempio con un’istruzione del tipo cont = cont + 2, che farebbe stampare un intero ogni due. Tuttavia ci si deve accertare che cont venga cambiata in modo tale da consentire un’uscita normale da while. Ad es., se sostituissimo l’espressione ++cont con --cont, il valore di cont non raggiungerebbe mai 11 e si creerebbe un loop infinito.
Ecco una possibile risposta: Esercizio. Utilizzando l’istruzione while, scrivere un programma che stampi la seguente tabella: Ecco una possibile risposta: #include <stdio.h> void main(void) { int num; printf(“NUMERO QUADRATO CUBO\n”); printf(“------ -------- ----\n”); num = 1; while (num < 6) printf(“%3d %3d %4d\n”,num,num*num,num*num*num); ++num; }
Si noti che l’espressione num < 6, essendo di tipo intero, è del tutto equivalente all’espressione num <= 5. Esercizio. Scrivere un programma che converta i gradi Celsius in gradi Fahrenheit applicando la solita formula F = 9/5*C + 32, stampando la seguente tabella:
Ecco una possibile risposta: #include <stdio.h> void main(void) { int celsius; float fahren; printf(“ GRADI GRADI\n”); printf(“CELSIUS FAHRENHEIT\n”); printf(“------- ----------\n”); celsius = 5; while (celsius <= 30) fahren = (9.0/5.0) * celsius + 32.0; printf(“%5d%11.2f\n”, celsius, fahren); celsius = celsius + 5; }
scanf() all’interno di un ciclo while La combinazione della funzione scanf() con le possibilità di ripetizione dell’istruzione while produce programmi adattabili e utili. Al riguardo, consideriamo il programma seguente, che usa un’istruzione while per accettare e quindi visualizzare quattro numeri immessi dall’utente, uno alla volta. #include <stdio.h> void main(void) { int cont; float num; printf(“Questo programma chiede di scrivere 4 numeri.\n”); cont = 1; while (cont <= 4) printf(“\nScrivi un numero: “); scanf(“%f”, &num); printf(“Il numero scritto è %f”, num); ++cont; }
Esso produce la seguente tabella: Questo programma chiede di scrivere 4 numeri. Scrivi un numero: 3 Il numero scritto è 3.000000 Scrivi un numero: 5 Il numero scritto è 5.000000 Scrivi un numero: 8 Il numero scritto è 8.000000 Scrivi un numero: 9 Il numero scritto è 9.000000 Esercizio. Anziché visualizzare semplicemente i numeri immessi, modifichiamo il programma precedente in modo che utilizzi i numeri immessi, sommandoli e stampandone la somma.
Ecco una possibile risposta: #include <stdio.h> void main(void) { int cont; float num, somma; printf(“Questo programma chiede 4 numeri e li somma.\n”); cont = 1; somma = 0; while (cont <= 4) printf(“Scrivi un numero: “); scanf(“%f”, &num); somma = somma + num; ++cont; } printf(“\nLa somma è %f”, somma);
Esso produce la seguente tabella: Sentinelle. Come abbiamo già visto, quando si immettono dei valori da tastiera, non è in genere noto a priori il loro numero. In tale caso si fa continuare il ciclo fino a che non venga immesso un valore speciale, detto sentinella, che di sicuro non si può presentare tra i valori immessi. Il programma seguente traduce l’algoritmo già visto che chiede di scrivere dei numeri da tastiera, termina quando si scrive 0 e fornisce il massimo dei numeri scritti.
#include <stdio.h> void main(void) { int max, dato; max = 0; printf("Scrivi un numero (0 per finire)\n"); scanf("%d", &dato); while (dato !=0) if (dato > max) max = dato; } printf("Il massimo è %d", max); Esso produce la seguente uscita: Scrivi un numero (0 per finire) 4 9 2 6 Il massimo è 9
Anche ora si può modificare il programma in modo che esegua la somma dei numeri immessi. Il programma seguente chiede in continuazione dei valori fino a che si immette un numero maggiore di 100, quindi ne stampa la somma. #include <stdio.h> void main(void) { float voto, somma; voto = 0; somma = 0; printf(“\nPer terminare, scrivi un numero > 100\n”); while (voto <= 100) printf(“Scrivi un voto: “); scanf(“%f”, &voto); somma = somma + voto; } printf(“\nLa somma dei voti è %f”, somma-voto);
Si noti che il programma termina quando si immette un voto >100. Dato però che anche tale voto-sentinella viene aggiunto alla somma, la successiva istruzione di stampa, esterna al ciclo, lo sottrae dalla somma che visualizza. Un’utile sentinella fornita da C è la costante EOF (da End Of File), il cui valore effettivo dipende dal particolare compilatore usato, ma alla quale è sempre assegnato un codice diverso da quelli degli altri caratteri. Nei sistemi operativi UNIX e LINUX il codice di EOF si ottiene con la pressione simultanea dei tasti Ctrl+D, in Windows con Ctrl+Z, e può essere usato in tutti i programmi nei quali sia incluso il file stdio.h. Perciò il programma precedente si può riscrivere come segue:
#include <stdio.h> void main(void) { float voto, somma = 0; printf(“\nPer terminare, premi F6 o Ctrl+z\n\n”); printf(“Scrivi un voto: “); while (scanf(“%f”, &voto) != EOF) somma = somma + voto; } printf(“\nLa somma dei voti e’ %f”, somma); L’espressione scanf(“%f”, &voto) != EOF utilizza il fatto che la funzione scanf() fornisce un valore EOF se si tenta di leggere un contrassegno End Of File, e questo è generato dalla pressione contemporanea dei tasti <Ctrl>+Z. Un vantaggio di questo programma, rispetto al precedente, è che il valore della sentinella non viene sommato al totale, cosicché non deve essere successivamente sottratto.
L’istruzione break (che abbiamo già visto nell’istruzione switch), Istruzioni break e continue. Due utili istruzioni che si possono impiegare nelle istruzioni di ripetizione sono break e continue. L’istruzione break (che abbiamo già visto nell’istruzione switch), provoca l’uscita immediata dal tutti i cicli switch, while, for e do-while. Essa è utile per uscire da un ciclo al verificarsi di una condizione inusuale. Ad es., l’esecuzione del seguente ciclo termina immediatamente se si immette un numero maggiore di 76: while(cont <= 10) { printf(“Scrivi un numero”); scanf(“%f”, &num); if (num > 76) printf(“Hai perso!”); break; } else printf(“Continua pure!”);
L’istruzione continue è simile alla break, ma si applica solo ai cicli creati dalle istruzioni while, for e do-while. Essa è utile per saltare dei dati che non vanno elaborati, rimanendo all’interno di un ciclo. Ad es., il seguente segmento di programma esegue la somma di 30 voti, ignorando quelli non validi (negativi o maggiori di 100) e aggiungendo al totale solo quelli validi: while (cont < 30) { printf(“Scrivi un voto: “); scanf(“%f”, &voto); if(voto < 0 || voto > 100) continue; somma = somma + voto; cont = cont + 1; }
Istruzione null. Un punto e virgola senza alcunché che lo preceda è un’istruzione valida, detta istruzione nulla. Essa è usata quando la sintassi richieda un’istruzione, ma non si debba compiere alcuna azione, tipicamente con le istruzioni while o for. Ad es., se in un programma C si inseriscono le istruzioni seguenti float cont . . . . . . while (scanf(“%f”, &cont) != EOF) ; il programma si pone in uno stato di attesa che termina quando si premono i tasti <Ctrl>+Z o F6. Questa tecnica permette di lanciare da Windows un programma in C, e di vedere lo schermo del Dos (che altrimenti scomparirebbe appena eseguito il programma) finché non si premano i tasti <Ctrl>+Z o F6.
Istruzione for L’istruzione for esegue gli stessi compiti dell’istruzione while, ma usa una forma differente. In molte situazioni, specie quelle che usano una condizione di conteggio fissa, il formato dell’istruzione for è più semplice da usare del suo equivalente while. Il suo diagramma di flusso è quello già visto:
for (inizializzazione; condizione; variazione) istruzione; La forma sintattica dell’istruzione for è: for (inizializzazione; condizione; variazione) istruzione; Nella parentesi vi sono tre voci, separate da punto e virgola, che corrispondono a quelle dell’istruzione while; ciascuna di esse è opzionale, ma i punti e virgola devono essere presenti. La voce centrale della parentesi, condizione, è una qualsiasi espressione valida in C, e viene usata nello stesso modo che nell’istruzione while. In entrambe le istruzioni, fin tanto che la condizione ha un valore diverso da zero (vero) la istruzione che segue la parentesi viene eseguita. Ciò significa che prima del primo controllo della condizione si devono assegnare i valori alle sue variabili che saranno testate. Prima che la condizione sia valutata di nuovo, ci devono essere una o più istruzioni che alterino questi valori.
Ricordiamo che la disposizione generale di queste istruzioni in un ciclo while segue lo schema seguente: istruzioni di inizializzazione; while (condizione) { istruzioni del ciclo; . istruzioni che modificano l’espressione; } La necessità di inizializzare variabili o compiere qualche altra valutazione prima di entrare in un ciclo ripetitivo è così comune che l’istruzione for consente di raggruppare insieme tutte le istruzioni di inizializzazione come primo gruppo di voci dentro le sue parentesi. Le voci di questo elenco di inizializzazione sono eseguite una sola volta, prima che la condizione sia valutata per la prima volta.
L’istruzione for fornisce anche un singolo posto per tutte le istruzioni che modificano la condizione. Esse vanno situate nell’ultima voce all’interno della parentesi for, e sono eseguite alla fine del ciclo, subito prima che la condizione sia valutata di nuovo. La seguente sezione di codice confronta la corrispondenza tra le istruzioni for e while. Come si vede, l’unica differenza è la posizione delle espressioni equivalenti. Il raggruppamento di inizializzazione, condizione, variazione nell’istruzione for è molto conveniente, specie nel caso di cicli con numero fisso di ripetizioni.
Un altro confronto si può eseguire tra il programma che stampava i primi 10 numeri interi e il seguente, che producono entrambi la stessa uscita. #include <stdio.h> void main(void) { int cont; for (cont = 1; cont <= 10; cont = cont + 1) printf("%d ", cont); } Tuttavia, come accennato, dentro le parentesi non sono obbligatorie né l’istruzione di inizializzazione né quella di variazione dei valori nelle espressioni delle istruzioni, ma solo la condizione e i due punti e virgola. Quindi, il programma precedente potrebbe essere variato inizializzando la variabile cont al di fuori delle parentesi:
o anche portando fuori dalle parentesi anche la variazione:
È possibile anche questa versione: nella quale: tutte le voci sono racchiuse all’interno delle parentesi, cosicché non è necessaria alcuna istruzione dopo la parentesi chiusa (l’istruzione null soddisfa la richiesta sintattica che una istruzione segua la parentesi del for) la variazione è costituita dalle ultime due voci in parentesi, separate da una virgola (essa è necessaria sia nella variazione, sia nella inizializzazione se esse sono costituite da più di una voce).
Quando si esce dal ciclo for, viene calcolata e visualizzata la media. scanf() all’interno di un ciclo for. Una chiamata alla funzione scanf() all’interno di un ciclo for produce lo stesso effetto della sua chiamata all’interno di un ciclo while. Ad es., il programma seguente usa una chiamata a scanf() per immettere 5 numeri, aggiungendo ciascuno di essi a un totale. Quando si esce dal ciclo for, viene calcolata e visualizzata la media. #include <stdio.h> void main(void) { int cont; float num, totale, media; totale = 0.0; for (cont = 0; cont < 5; ++cont) printf("\nScrivi un numero: "); scanf("%f", &num); totale = totale + num; } media = totale / cont; printf("\n\nLa media dei numeri immessi è %f", media);
Istruzione do Entrambe le istruzioni while e for valutano una condizione all’inizio del ciclo di ripetizione. Tuttavia vi sono dei casi in cui è più conveniente controllare la condizione alla fine del ciclo. Ad es., supponiamo di avere costruito il seguente ciclo while per calcolare le imposte sulle vendite: printf(“Scrivi un prezzo:”); scanf(“%f”, &prezzo); while (prezzo != SENTINEL) { impovendite = TASSO * prezzo; printf(“L’imposta sulle vendite è E%5.2f”, impovendite); printf(“\nScrivi un prezzo:”); }
In questo caso l’uso dell’istruzione while richiede: o di duplicare il prompt e la chiamata alla funzione scanf() prima del ciclo e quindi al suo interno, come abbiamo fatto, o di ricorrere a qualche altro artificio per forzare l’esecuzione iniziale delle istruzioni dentro il ciclo. L’istruzione do, come implica il suo nome, consente di eseguire alcune istruzioni prima che una condizione sia valutata. In molte situazioni ciò può essere usato per eliminare la duplicazione illustrata nell’esempio precedente. La forma generale dell’istruzione do è: do istruzione; while (espressione); Come sempre nel C, l’istruzione singola in do può essere sostituita con una composta. Il diagramma di flusso dell’istruzione do è quello già visto:
Pertanto, tutte le istruzioni del ciclo sono eseguite almeno una volta prima che la condizione sia valutata; quindi, se essa risulta diversa da zero, sono eseguite di nuovo. Il processo continua fino a che la condizione non raggiunga il valore zero. Come esempio consideriamo l’istruzione do seguente:
do { printf(“\nScrivi un prezzo: “); scanf(“%f”, &prezzo); impovendite = TASSO * prezzo; printf(“L’imposta sulle vendite è €5.2f”,impovendite); } while (prezzo != SENTINEL);
Esercizio. Scrivere un programma che, usando scanf() all’interno di un ciclo do-while, chieda di scrivere 5 numeri interi e li sommi. Ecco una possibile risposta: #include <stdio.h> void main(void) { int s, k, x; s = k = x = 0; do printf("Scrivi il numero da sommare: "); scanf("%d", &x); ++k; s = s + x; } while (k!= 5); printf("La somma è: %d", s);
Controlli di validità. L’istruzione do è particolarmente utile nel filtrare i dati immessi, fornendo un controllo sulla loro validità. Ad es., supponiamo che si debba immettere un numero di identificazione compreso tra 1000 e 1999; un numero esterno a questo intervallo deve essere respinto, e viene fatta una nuova richiesta di un numero valido. La seguente sezione di codice fornisce il filtro necessario per verificare l’immissione di un numero d’identificazione valido. do { printf(“\nScrivi un numero d’identificazione: “); scanf(“%f”, &num_id); } while (num_id < 1000 || num_id > 1999); Questo codice ripete la richiesta di un numero d’identificazione fino a che viene scritto un numero valido, ma non informa l’operatore della causa di una nuova richiesta di dato, né consente un’uscita prematura dal ciclo se non si può trovare un numero d’identificazione valido.
Una possibilità di eliminare il primo inconveniente è la seguente. do { printf(“\nScrivi un numero d’identificazione: “); scanf(“%f”, &num_id); if (num_id < 1000 || num_id > 1999) printf(“\nE’ stato scritto un numero sbagliato”); printf(“\nControlla il numero ID e riscrivilo”); } else break; /*prosegue se il num_id scritto è valido*/ while (1); /* questa espressione è sempre vera */ Qui si è usata un’istruzione break per uscire dal ciclo. Dato che l’espressione valutata dall’istruzione do è sempre 1 (vera), si è creato un loop infinito da cui si esce solo quando s’incontra l’istruzione break.