{Questo programma legge un intero da terminale e scrive su terminale il fattoriale dell'intero letto. Se l'intero digitato e' negativo restituisce 1.} var n:integer; {Memorizza l'input (l'intero che si legge da terminale)} f:integer; {Memorizza l'output (il fattoriale dell'intero memorizzato in n), e' anche usato per memorizzare risultati intermedi della computazione} i:integer; {E' usata come contatore in un ciclo while} begin writeln('Questo programma calcola il fattoriale di un intero non negativo.'); writeln('Digita l'intero:'); readln(n); i:=0; f:=1; {Ogni volta che viene effettuato il test del while il ciclo e' stato eseguito i volte e f contiene il fattoriale di i. Ad ogni iterazione il valore di i aumenta di 1, quindi, in un numero finito (possibilmente nullo) di passi i assumera' un valore non inferiore ad n e si uscira' dal ciclo} while (n>i) do begin i:=i+1; f:=f*i; end; {Analizziamo il valore di f all'uscita dal ciclo. Sappiamo che f contiene il fattoriale di i. Distinguamo tre casi possibili per il valore di n (l'input). a. n<0. Non si e' mai entrati nel ciclo, quindi i=0. Abbiamo f=1. Il risultato voluto per n negativo. b. n=0. Non si e' mai entrati nel ciclo, quindi i=0. Abbiamo i=n. Abbiamo f = fattoriale di n. c. n>0. Il ciclo e' stato eseguito n volte, quindi i=n. Abbiamo i=n. Abbiamo f = fattoriale di n.} end.
I commenti nel programma precedente sono piuttosto lunghi. Sicuramente risulteranno noiosi a programmatori dotati di una certa esperienza. Si puo' allora pensare di scrivere il programma senza commenti:
var n:integer; f:integer; i:integer; begin writeln('Questo programma calcola il fattoriale di un intero non negativo.'); writeln('Digita l'intero:'); readln(n); i:=0; f:=1; while (n>i) do begin i:=i+1; f:=f*i; end; writeln('Il fattoriale di',n); writeln('e`',f); end.
Tuttavia questa situazione non e' completamente soddisfacente. E' vero che le writeln aiutano a capire che cosa fa il programma, ma ci sono dei punti oscuri: Che cosa succede se si fornisce un numero negativo? Quale e' il ruolo della variabile i? Perche' i e' inizializzata a 0? Perche' f e' inizializzata a 1? Che cosa fa il ciclo? A cosa serve il test? Siamo sicuri che questo programma fa quello che dicono le writeln?
Occorre quindi trovare una via di mezzo tra la prima versione del programma (con commenti troppo lunghi e dettagliati) e la seconda (senza commenti). Cosa ne dite della seguente:
{Questo programma legge un intero da terminale e scrive su terminale il fattoriale dell'intero letto. Se l'intero digitato e' negativo restituisce 1.} var n:integer; {Memorizza l'input} f:integer; {Memorizza risultati intermedi e output} i:integer; {Contatore usato nel ciclo while} begin writeln('Questo programma calcola il fattoriale di un intero non negativo.'); writeln('Digita l'intero:'); readln(n); i:=0; f:=1; while (n>i) do {f=i!} begin i:=i+1; f:=f*i; end; {se n<0 allora i=0, quindi f=1. se n>=0 allora i=n, quindi f=n!.} writeln('Il fattoriale di',n); writeln('e`',f); end.
Forse il commento (invariante) dopo il do non dice abbastanza. Provate a scrivere qualcosa d'altro.
Inoltre, il programma potrebbe essere migliorato in modo da controllare che effettivamente l'utente digiti un intero non negativo. Ci sono diverse alternative. Elencate quelle che vi vengono in mente e implementate qualla che preferite.
Se cambiate il programma Ricordatevi di modificare i commenti in modo opportuno!!! Un programma con commenti sbagliati e' peggio di un programma senza commenti!!!
{Questa procedura calcola il fattoriale di m e lo restituisce in r. Se m<0 restituisce 1.} procedure fatt(m:integer; var r:integer); var i:integer; {Contatore usato nel ciclo while} begin i:=0; r:=1; while (m>i) do {r=i!} begin i:=i+1; r:=r*i; end; {se m<0 allora i=0, quindi r=1. se m>=0 allora i=m, quindi r=m!.} end;
Ora un qualunque programmatore puo' utilizzare la procedura fatt per calcolare il fattoriale di un numero iintero non negativo. L'unica cosa che il programmatore deve conoscere per poter utilizzare in modo appropriato la procedura fatt e' costituito dalle prime 4 linee di codice: le 3 linee di commento e la linea dell'intestazione della procedura.
{Questa procedura calcola il fattoriale di m e lo restituisce in r. Se m<0 restituisce 1.} procedure fatt(m:integer; var r:integer);
{Questa funzione restituisce il fattoriale di m. Se m<0 restituisce 1.} function fatt(m:integer;):integer; var r:integer; {Memorizza risultati intermedi e risultato finale} i:integer; {Contatore usato nel ciclo while} begin i:=0; r:=1; while (m>i) do {r=i!} begin i:=i+1; r:=r*i; end; {se m<0 allora i=0, quindi r=1. se m>=0 allora i=m, quindi f=m!.} fatt:=r; end;
Esercizi 1. Fate l'esercizio suggerito dopo l'esempio sull'uso di commenti e invarianti. Rifate l'esecizio usando un ciclo for. Rifate l'esecizio usando un ciclo repeat. ATTENZIONE: mettete gli invarianti!!! La cosa importante dell'esercizio e' proprio questa!!! 2. Riscrivete il programma sviluppato all'esecizio 1 utilizzando la procedura fatt vista a lezione. 3. Riscrivete il programma sviluppato all'esecizio 1 utilizzando la funzione fatt vista a lezione. 4. Scrivete un programma che legge da terminale un intero non negativo n, poi legge n interi non negativi, e stampa su terminale il fattoriale della media aritmetica (usate div per fare la divisione) degli n interi. ATTENZIONE: mettete i commenti. Usate procedure e/o funzioni: riutilizzate procedure che abbiamo gia' visto a lezione e, quando necessario, scrivetene di nuove.