--- 02_sol1.c ------------------------------------------------------------------------------------
#include <stdio.h>
int main (void) {
int ore, min, min_tot;
printf ("Inserisci l'ora corrente (hh:mm): ");
scanf ("%d:%d", &ore, &min);
min_tot = ore * 60 + min;
printf ("Sono trascorsi %d minuti dalla mezzanotte.\n",
min_tot);
return (0);
}
--------------------------------------------------------------------------------------------------
La "difficoltà" di questo esercizio risiedeva
nell'usare il formato giusto per scanf (e nel ricordarsi
le '&'). Spero che questo concetto vi sia (ormai) chiaro!
Può darsi che qualcuno abbia avuto l'idea di calcolare
i SECONDI trascorsi dalla mezzanotte, anziché i minuti.
In tal caso sarà probabilmente impazzito nel tentativo
di capire perché a volte ottiene un risultato sbagliato.
Be', è perfettamente normale, e vi sarà chiaro
fra qualche lezione, non temete ;).
LEZIONE 2
Come promesso, in questa lezione parleremo di if e di cicli.
Una caratteristica FONDAMENTALE dei linguaggi di programmazione
è quella che ci permette di fare sì che un
programma si comporti in modo diverso a seconda di certe
condizioni. Senza questa caratteristica non potremmo fare
niente (provare per credere)! Consideriamo un compito banale,
come far introdurre da tastiera un numero intero e scrivere
sullo schermo se tale numero è positivo o negativo.
Una possibile soluzione è la seguente:
--- 02_sgn.c -------------------------------------------------------------------------------------
#include <stdio.h>
int main (void) {
int n;
printf ("Introduci un numero intero: ");
scanf ("%d", &n);
if (n > 0) {
printf ("Il numero che hai inserito è POSITIVO.\n");
} else if (n < 0) {
printf ("Il numero che hai inserito è NEGATIVO.\n");
} else {
printf ("Il numero che hai inserito è ZERO.\n");
}
return (0);
}
--------------------------------------------------------------------------------------------------
Fino alla riga di scanf il programma dovrebbe risultarvi
comprensibile (se non è così, rivedetevi la
lezione precedente!). Quel che segue è il famigerato
if: esso ci permette di valutare una condizione, che nella
fattispecie può essere una qualsiasi espressione,
e, se essa è vera (ossia, nel nostro caso, se il
numero è maggiore di 0), viene eseguito tutto quel
che è compreso tra le {} successive. Se essa è
falsa, si passa alla valutazione delle condizioni indicate
successivamente come else if (dato che non è maggiore
di 0, il nostro numero è forse minore di 0?) e, appena
se ne trova una vera, essa viene eseguita. Infine, se nessuna
è vera, si esegue quello indicato dal ramo else (se
il nostro numero non è né maggiore né
minore di 0, allora è proprio 0! Inutile fare altri
controlli). Nessun ramo, a parte il primo è necessario,
quindi sono perfettamente legali cose come le seguenti:
if (a == 0) {
printf ("a vale 0.\n");
}
che stampa un messaggio solo se a è uguale a 0, altrimenti
prosegue semplicemente con l'istruzione successiva;
if (b == 0) {
printf ("b vale 0.\n");
} else {
printf ("b non vale 0.\n");
}
che stampa un messaggio diverso a seconda che b sia uguale
a 0 o meno. Non c'è nessun ramo else if in questo
caso.
Il discorso mi pare abbastanza intuitivo, quindi passiamo
subito ai particolari: vi capiterà spesso di dover
valutare più di una condizione alla volta. Ad esempio,
vogliamo sapere se un certo anno è bisestile o meno.
Ricordiamo che un anno è bisestile se è divisibile
per 4 ma non per 100, oppure per 400. Quindi 1996 è
bisestile perchè 1996 % 4 == 0 e 1996 % 100 != 0
(ricordate l'operatore '%' dalla prima lezione, vero?).
2000 è bisestile perché 2000 % 400 == 0. 1900
invece no, perché 1900 % 4 == 0, ma anche 1900 %
100 == 0. Passiamo ad una realizzazione software; intuitivamente
potreste pensare di fare (supponendo che nella variabile
"anno" ci sia un anno valido, inserito ad esempio
mediante scanf):
if (anno % 400 == 0) {
printf ("L'anno è bisestile.\n");
} else if (anno % 4 == 0) {
if (anno % 100 == 0) {
printf ("L'anno non è bisestile.\n");
} else {
printf ("L'anno è bisestile.\n");
}
} else {
printf ("L'anno non è bisestile.\n");
}
Questo è talmente intricato che non so nemmeno io
se funzioni! Fortunatamente, qualsiasi linguaggio vi permette
di raggruppare più condizioni in un unico if. Per
farlo vi sono 2 operatori: il primo è di AND logico,
e fa sì che l'if sia eseguito solo se TUTTE le condizioni
specificate sono vere. Esso si indica con "&&".
L'altro operatore è di OR logico, e fa sì
che l'if sia eseguito se ALMENO una delle condizioni è
vera. Esso si indica con "||" (ALT+124 casomai
mancasse sulla vostra tastiera). Ovviamente potete raggruppare
più condizioni in una sola con un misto di and ed
or, usando le parentesi per maggior chiarezza. Quindi l'intricato
codice soprastante si può ridurre a:
if ((anno % 400 == 0) || (anno % 4 == 0 && anno
% 100 != 0)) {
printf ("L'anno è bisestile.\n");
} else {
printf ("L'anno non è bisestile.\n");
}
Sicuramente è un po' più semplice a scriversi
e a capirsi ;).
Un'altra considerazione che è necessario fare riguarda
gli operatori che potete utilizzare negli if: per testare
l'uguaglianza di due variabili dovete usare l'operatore
"==" (che sono DUE uguali, e NON uno, sia ben
chiaro!!); per testare la disuguaglianza di due variabili
dovete usare l'operatore "!=" (che potete chiamare
"diverso"); infine, il "maggiore o uguale"
ed il "minore o uguale" si indicano rispettivamente
con ">=" e "<=". Per ora non vi
serve sapere altro.
Veniamo al secondo grande argomento di questa lezione:
i cicli. Supponiamo che vogliate scrivere 5 volte su schermo
"SukkoPera è il mio idolo." (ihihih!).
Nessuno vi vieta di fare:
printf ("SukkoPera è il mio idolo.\n");
printf ("SukkoPera è il mio idolo.\n");
printf ("SukkoPera è il mio idolo.\n");
printf ("SukkoPera è il mio idolo.\n");
printf ("SukkoPera è il mio idolo.\n");
se non il buon senso. Immaginate di volerlo scrivere 50
volte. E poi 100. Insomma, sarete d'accordo con me che è
una soluzione assolutamente impraticabile ;). I cicli servono
appunto a darvi una mano in compiti del genere, ossia quando
volete ripetere un certo numero di volte determinate operazioni.
In questo caso possiamo usare un semplice ciclo FOR, che
in C si realizza come segue:
for (i = 0; i < 5; i = i + 1) {
printf ("SukkoPera è il mio idolo.\n");
}
Questo farà esattamente la stessa cosa del codice
di prima (chiaramente dovete avere dichiarato "int
i;"), ma in un modo più compatto e più
facilmente modificabile: se ora volete stampare la scritta
100 volte, vi basta cambiare "i < 5" in "i
< 100", semplice, no?
Analizziamo più da vicino la struttura del for: notiamo
che tra le () sono riconoscibili 3 parti, separate da ';':
la prima è la parte di INIZIALIZZAZIONE e contiene
istruzioni che verranno eseguite SOLO LA PRIMA VOLTA che
si entra nel ciclo. Qualcuno penserà: "Allora
tanto vale metterle prima del for.", ed in effetti
ha ragione, solo che specificarle dentro al for rende più
chiaro ciò su cui il ciclo lavora, anche se questo
non è per nulla vincolante.
La seconda parte, invece, contiene le CONDIZIONI DI USCITA
dal ciclo: il ciclo sarà ripetuto fino a quando la
condizione che esprimete qua sarà vera. Quando sarà
falsa il ciclo terminerà ed il programma proseguirà
con l'istruzione successiva al for. È importante
capire che questa condizione verrà testata soltanto
ALLA FINE del gruppo di istruzioni da ripetere, non in mezzo
ad esse. Faremo un esempio chiarificatore tra poco.
La terza parte, infine, contiene le istruzioni di INCREMENTO:
esse vengono eseguite ogni volta che si arriva al termine
del gruppo di istruzioni da ripetere, dopodiché vengono
testate le condizioni di uscita.
Solitamente tutte e 3 le parti lavorano sulle stesse variabili.
Se non è così sarebbe meglio utilizzare un
ciclo WHILE, che spiegheremo tra poco.
Dopo le () vi è un insieme di istruzioni racchiuse
tra {} come negli if, che è il blocco di istruzioni
che verrà ripetuto.
Vediamo ora un esempio un po' più complesso di for:
for (i = 1; i <= 10; i = i + 1) {
for (j = 1; j <= 10; j = j + 1) {
printf ("%d ", i * j);
}
printf ("\n");
}
Questi sono detti "cicli for NIDIFICATI", perché
uno è interamente compreso dentro all'altro. Questo
programmino (sempre a patto che abbiate dichiarato "int
i, j;") stamperà una simpatica tabellina pitagorica
(si chiama così, no?). Seguiamo il programma passo
per passo: all'inizio entriamo nel primo for, e i viene
inizializzata a 1. Dopo di che, entriamo anche nel secondo
for, e j viene inizializzata a sua volta a 1. Quindi viene
eseguita la prima istruzione del ciclo, ossia la prima printf,
che stamperà il valore di i * j, ossia 1 * 1, e quindi
1. Troviamo poi la } di chiusura del for più interno,
quindi viene eseguita la sua istruzione di incremento, ossia
j = j + 1, e quindi j passa a 2. Successivamente viene testata
la condizione di uscita del ciclo più interno: j
<= 10. Valendo j 2, la condizione è ovviamente
verificata, quindi il ciclo sarà ripetuto, e verrà
di nuovo eseguita la prima printf. È importante a
questo punto capire che niente ha modificato il valore di
i, e quindi essa varrà ancora 1, in quanto non siamo
ancora giunti alla fine del primo for (che in questo caso
è rappresentata dalla seconda printf), e quindi non
è ancora stata eseguita la sua operazione di incremento.
Il valore stampato dalla printf sarà ovviamente 1
* 2, quindi 2, dopo di che tutto andrà esattamente
come abbiamo appena spiegato, stampando su schermo i numeri
fino al 10 (che rappresentano la tabellina dell'1): a quel
punto, i varrà ancora 1, e j varrà appunto
10. Subito dopo la printf verrà eseguita l'istruzione
di incremento del for più interno, e j arriverà
a valere 11. Quindi sarà testata la condizione di
uscita: j <= 10. Ovviamente 11 non è <= 10,
e dunque il ciclo for più interno sarà finito,
e si passerà ad eseguire quello che segue, in questo
caso la seconda printf, che non farà altro che fare
andare a capo il cursore. Ora siamo arrivati alla fine del
ciclo for esterno, quindi sarà eseguita la sua istruzione
di incremento, che farà sì che i passi a valere
2, mentre per ora j continuerà a valere 11, ossia
il valore che ha provocato l'uscita dal for interno. Verrà
poi testata la condizione di uscita del for esterno: 2 è
<= 10, quindi esso verrà eseguito nuovamente.
Ora siamo di nuovo al punto di partenza del for interno:
j viene inizializzata ad 1 e man mano saranno stampati i
valori 2 * 1, 2 * 2, 2 * 3... fino a 2 * 10, valori che
rappresentano la tabellina del 2. Quindi sarà nuovamente
concluso il ciclo for interno, il cursore sarà mandato
a capo, sarà eseguita l'istruzione di incremento
del for esterno e quindi sarà stampata la tabellina
del 3, e così via, fino a quando sarà stampata
la tabellina del 10: a quel punto i passerà a valere
11, la condizione che ci mantiene dentro al for esterno
non sarà più verificata, e quindi si passerà
ad eseguire quel che segue, che in questo caso si suppone
sia un "return (0);" che conclude il programma.
Spero che questo discorso vi sia chiaro. Può darsi
che dobbiate rileggere un paio di volte quanto scritto sopra,
ma alla fine vi renderete conto che non è troppo
complesso. Se proprio non capite, provate a compilare ed
eseguire il seguente programma:
for (i = 1; i <= 10; i = i + 1) {
for (j = 1; j <= 10; j = j + 1) {
printf ("i = %d, j = %d, i * j = %d.\n", i, j,
i * j);
getchar ();
}
printf ("Fine ciclo FOR interno.\n\n");
getchar ();
}
printf ("Fine ciclo FOR esterno.\n\n");
Esso è praticamente identico al precedente, solo
che eseguirà un passo alla volta visualizzando i
valori man mano assunti dalle variabili, aspettando che
premiate invio (a causa di getchar ();) prima di passare
al successivo.
Un'ulteriore precisazione: potete utilizzare più
variabili nelle varie parti di un for, ad esempio:
for (i = 0, j = 10; i < 10 && j > 0; i = i
+ 1, j = j - 1) {
}
Da questo dovrebbe essere chiaro che dovete usare la virgola
',' per separare più elementi della stessa parte,
e che la condizione di uscita dal ciclo è tale quale
una condizione di un if, e quindi anche in essa potete usare
gli operatori logici come "&&" e "||".
Un altro tipo di ciclo è il ciclo WHILE. A dire il
vero, in C si può considerare un while un semplice
ciclo for che manca sia delle condizioni di inizializzazione,
sia delle istruzioni di incremento. In pratica il ciclo
while si preoccupa solo di ripetere un certo insieme di
operazioni finché una condizione è vera, senza
eseguire contestualmente inizializzazioni o incrementi.
Esempio:
while (x != 5) {
printf ("Inserisci 5 per uscire: ");
scanf ("%d", &x);
}
Come dicevamo, il while è molto simile ad un for
"mutilato", in effetti la riga del while potrebbe
equivalentemente essere scritta "for (; x != 5;). Non
esiste differenza (i compilatori dovrebbero produrre codice
assembler identico in entrambi i casi), se non una maggior
chiarezza.
Comunque, lo spezzone di codice soprastante chiede di inserire
un numero da tastiera, e finché non inseriamo '5',
la domanda viene ripetuta, in quando la condizione è
verificata. Da notare è che se x vale 5 quando arriviamo
al while, esso non sarà mai eseguito, in quanto la
condizione viene testata ANCHE LA PRIMA VOLTA che si entra
nel ciclo. Esiste tuttavia una forma leggermente diversa
del ciclo while:
do {
printf ("Inserisci 5 per uscire: ");
scanf ("%d", &x);
} while (x != 5);
Questo è un ciclo DO... WHILE, in tutto e per tutto
identico al while, con l'unica differenza che la condizione
viene testata solo DOPO che le istruzioni sono state eseguite,
e quindi esse verranno eseguite ALMENO una volta.
Presentiamo un esempio pratico dell'uso di questo tipo di
ciclo, in questo caso per effettuare il controllo di un
dato inserito e farlo reinserire se scorretto:
do {
printf ("Inserisci una data nel formato gg/mm/aaaa:
");
scanf ("%d/%d/%d", &giorno, &mese, &anno);
} while ((giorno > 0 && giorno <= 31) &&
(mese > 0 && mese <= 12));
Finché non viene inserita una data valida, la domanda
viene riproposta.
Questo è quanto è necessario sapere sull'if
e sui cicli. Per questa lezione lascio anche una piccola
appendice, che tratta 2 argomenti che possiamo definire
"avanzati": essi non sono fondamentali a questo
punto del corso, quindi siete liberi di ignorarli. Tuttavia
se avete capito tutto alla perfezione e volete altra carne
al fuoco, sapete dove trovarla ;)
APPENDICE:
Nell'uso degli if, del ciclo for e del ciclo while (non
nel do... while), il C permette di omettere le {} che delimitano
i vari blocchi di istruzioni, qualora si tratti di un'istruzione
sola. Ad esempio, l'if del primo programma si potrebbe tranquillamente
scrivere:
if (n > 0)
printf ("Il numero che hai inserito è POSITIVO.\n");
else if (n < 0)
printf ("Il numero che hai inserito è NEGATIVO.\n");
else
printf ("Il numero che hai inserito è ZERO.\n");
dato che l'istruzione da eseguire nei vari casi è
sempre una sola. Tuttavia, quando scriviamo cose del genere,
può capitare di aggiungere altre istruzioni successivamente,
quindi è necessario ricordarsi di aggiungere le {}
dove appropriato, oppure di usarle sempre. Abbondare non
fa mai male, anzi, spesso rende più chiaro il codice
alla lettura e, a volte, può anche rendere più
veloce la compilazione. Questo vale anche per le () nella
valutazione delle espressioni logiche e aritmetiche, Notate
anche che parentesi superflue NON influiranno negativamente
sulle performance del programma, in quanto eliminate automaticamente
in fase di compilazione, quindi non fatevi scrupoli ad usarle
;).
Un'ultima parola su come le espressioni vengono valutate:
in C, il valore logico VERO corrispondere ad un QUALSIASI
numero DIVERSO DA 0. Quindi, ovviamente, il valore logico
FALSO corrisponde SOLO ALLO 0. Siccome in un if può
comparire un'espressione qualsiasi, se il suo risultato
sarà diverso da 0, l'if verrà eseguito, altrimenti
si passerà a valutare gli else if e poi l'else. Ecco
quindi che cose senza senso come le seguenti sono perfettamente
legali:
if (5 + 4 - 9 / 3) {
/* Quello che mettete qua verrà SEMPRE eseguito,
dato che il risultato dell'espressione è 6, e quindi
corrisponde a VERO */
}
if (8 + 7 - 15) {
/* Quello che mettete qua non verrà MAI eseguito,
dato che il risultato dell'espressione è 0, e quindi
corrisponde a
FALSO */
}
Spero che questo discorso vi sia chiaro. Tra l'altro, ne
ho approfittato per introdurre i COMMENTI. In C, quando
compare la sequenza di caratteri "/*", tutto quel
che segue è considerato un commento, ed ignorato
dal compilatore, fino a quando non compare la sequenza "*/".
Un commento può comparire in un qualsiasi punto in
cui può comparire uno spazio. Imparate fin da subito
a commentare i vostri programmi, vi tornerà molto
utile quando rimetterete mano a del codice scritto tempo
prima. Credetemi, i commenti sono fondamentali, e non ne
scriverete mai abbastanza! Certi compilatori supportano
anche la sintassi per i commenti del C++, ossia "//",
che però definisce un commento su una sola riga:
in questo caso non serve una sequenza di caratteri per chiudere
il commento, basta andare a capo. Vi sconsiglio comunque
di utilizzarli: stiamo programmando in C, non in C++!
ESERCIZIO
Anche questa volta vi lascio con un esercizio che servirà
a farvi utilizzare i concetti esposti in questa lezione.
Ovviamente troverete una possibile soluzione nella prossima
lezione, nella quale, a proposito, parleremo di vettori.
Scrivere un programma che permetta di inserire due numeri
da tastiera. Il programma dovrà stabilire (e stampare
su schermo con un messaggio appropriato) quale di essi è
il maggiore (gestire anche il caso di uguaglianza) e, successivamente,
per quest'ultimo, calcolare la somma di tutti i numeri interi,
partendo da 1 ed arrivando fino ad esso.
Esempio:
Inseriti 4 e 7, il programma avvertirà che il numero
maggiore è 7 e che la somma richiesta è 1
+ 2 + 3 + 4 + 5 + 6 + 7 = 28.
Vi lancio anche una piccola sfida: dato il ciclo:
for (i = 0; i < 100; i = i + 1) {
/* ... */
}
scriverne una versione EQUIVALENTE al 100% usando il ciclo
while. Divertitevi ;).
### FINE LEZIONE 2 - Versione 1.01 (23/01/2003) ###
|