Sottoprogrammi e funzioni
Parte 5
Sottoprogrammi, funzioni,
e passaggio dei parametri.
Corso A: Prof. Stefano Berardi http://www.di.unito.it/~stefano
Corso B: Prof. Ugo de’ Liguoro http://www.di.unito.it/~deligu
Finalmente
arrivano le funzioni …
5-Funzioni
Indice Parte 5: Funzioni
1. I sottoprogrammi al tempo del GOTO.
2. I sottoprogrammi ai tempi della
programmazione strutturata: le funzioni.
3. Variabili locali e globali.
4. Passaggio per riferimento.
5. Come si descrive una funzione? Pre- e
Post- condizioni.
Le funzioni vengono usate in C++ per
scomporre un problema in sottoproblemi
e facilitarne così la soluzione.
1. I sottoprogrammi
• Salvo per le operazioni elementari (ad es. le
operazioni aritmetiche) ad ogni operazione di
un programma corrisponde un gruppo di
istruzioni in C++.
• Un’operazione più volte ripetuta in un
programma (es.: disegnare un cerchio con
certe caratteristiche) può essere realizzata
da un gruppo di istruzioni che viene eseguito
ogni volta che serve: un sottoprogramma.
• L’idea di sottoprogramma corrisponde all’idea
di scomporre un problema in sottoproblemi.
5-Funzioni
Perché i sottoprogrammi?
• La
scomposizione
di
un
programma
in
sottoprogrammi rende un programma più leggibile
e (di solito) più breve, ma il suo scopo principale è
quello di correggere separatamente anziché
tutte insieme le diverse parti del programma.
• I sottoprogrammi sono indispensabili per i
programmi lunghi. Non è effettivamente possibile
scrivere un programma di grandi dimensioni senza
scomporlo in sottoprogrammi, perché trovare un
errore nascosto in un lungo blocco di istruzioni
richiede troppo tempo.
5-Funzioni
Esempi di sottoprogrammi
• Vedremo nel Laboratorio del corso come anche
programmi
lunghi
poche
pagine,
come
l’implementazione dell’algoritmo di Gauss-Jordan
e degli Automi Cellulari, e addirittura programmi
brevi come uno che disegna coriandoli diventano
molto più semplici da scrivere se scomposti in
sottoprogrammi.
• Un’avvertenza su questa lezione. Per semplicità
di esposizione, nei prossimi lucidi scomporremo in
sottoprogrammi
anche
alcuni
programmi
brevissimi, che non richiederebbero affatto tale
scomposizione.
5-Funzioni
Un sottoprogramma per il
massimo al tempo dei GOTO
“Chiamata”
al sottoprogramma:
“salto” alle
istruzioni del
sottoprogramma per
il calcolo di
max(x,y)
100 LET X = 3
110 LET Y = 5
120 GOSUB 400
130 PRINT
MASSIMO
...
400 IF X > Y
THEN 430
410 LET
MASSIMO = Y
420 GOTO 440
430 LET
MASSIMO = X
440 RETURN
Assegno 3, 5
a X, Y
“Ritorno” dal
sottoprogramma per il
massimo:
“salto”
all’indietro,
ora
MASSIMO =
max(3,5)
=5
5-Funzioni
Come si ritornava al
programma principale
120 GOSUB 400
130 ...
400 ...
440 RETURN
Quando si eseguiva l’istruzione 120 che “chiama” il
sottoprogramma, prima di saltare alla linea 400
veniva memorizzato in una variabile l’indirizzo 120
da cui si proveniva. Quando poi si eseguiva
RETURN (ultima istr. 440 del sottoprogramma), si
eseguiva un JUMP o “salto” all’istruzione 130,
quella immediatamente successiva all’istruzione
120 ricordata.
5-Funzioni
I parametri dei sottoprogrammi
La X e la Y sono dette i 100 LET X = 3
110 LET Y = 5
valori
in
ingresso
del 120 GOSUB 400
sottoprogramma,MASSIMO
130 PRINT MASSIMO
è detto il valore in uscita.
...
X, Y sono detti parametri del 400 IF X>Y THEN 430
410 LET MASSIMO = Y
sottoprogramma
420 GOTO 440
I sottoprogrammi avevano un 430 LET MASSIMO = X
difetto: supponiamo di aver 440 RETURN
gia’ utilizzato X, Y altrove
perL’uso
salvare
dei valori
di variabili
X, Y per “passare” i valori 3, 5
verso un sottoprogramma provocava la perdita
5-Funzioni
dei valori precedenti di X, Y.
2. I sottoprogrammi ai tempi
della programmazione strutt.
• Una funzione è un sottoprogramma con
parametri propri
(non appartenenti al
programma principale) attraverso le quali riceve
l’ingresso (e a volte può anche restituire
l’uscita).
• L’uso di un sottoprogramma con parametri
propri evita il rischio di cancellare senza
volerlo il contenuto di altre variabili del
programma.
• Questa era una fonte comune di errori quando
i sottoprogrammi non avevano parametri propri.
5-Funzioni
I sottoprogrammi ai tempi
della programmazione strutt.
• Una “chiamata” a una funzione produce un salto
alle righe che contengono la funzione, mentre
l’indirizzo dell’istruzione che chiama la funzione
viene memorizzato.
• Terminata l’esecuzione della funzione, il flusso del
programma riprende dall’istruzione successiva
alla quella dove era avvenuta la chiamata.
• Tutto questo oggi avviene senza dover scrivere
esplicitamente istruzioni di salto né indirizzi: ci
pensa il compilatore ad aggiungerli nell’eseguibile.
5-Funzioni
Funzioni: sintassi
<tipo> <nome funzione> (<lista param.>) {<corpo>}
1. <tipo> è il tipo del valore restituito (si usa il tipo
void (vuoto) se non viene restituito alcun valore)
2. <lista param.> (eventualmente vuota) è una
sequenza di dichiarazioni di variabili, separate da
virgole, della forma: <tipo> <nome variabile>
int max (int x, int
Tipo
valore
di ritorno
corpo
della
funzione
y)
Lista parametri
{ if (x > y) return
x;
Valore di ritorno: viene definito da un
elsereturn.
returnIl y;
} deve essere
comando
suo tipo
uguale al tipo del valore di ritorno
Funzioni: dichiarazioni,
definizioni e chiamate
Le funzioni in C++ compaiono in tre modi:
• Dichiarazioni (fuori dal main). Corpo ridotto a “;”
double sq(double x);
Una dichiarazione precisa solo i tipi della funzione.
• Definizioni (fuori dal main). Sono dichiarazioni
seguite dal corpo {…} della funzione.
double sq(double x) {return x*x; /*corpo
funzione*/}
• Chiamate (nel main o in una funzione). Devono
essere precedute almeno dalla dichiarazione, non
dichiarano i tipi: scrivete b = sq(a); e non:
I parametri di una dichiarazione o
definizione sono detti “formali”
int max (int x,
La
In una
int y)
dichiarazione
{if (x > y) return dichiarazione
o definizione x;
o definizione,
di una funzione
else return y;}
x, y sono
sta prima o }
detti
dopo il main e
parametri
richiede i tipi
formali
sono propri
I parametri formali x, y vengono usati
della
per
passare
alla
funzione
le
funzione
informazioni di cui ha bisogno per il
calcolo richiesto.
I parametri di una chiamata di
funzione sono detti “attuali”
int main () {
int n = 7, m = 14,
k;
k = max(n, m);
cout<< " il massimo tra " << n << "
e " << m
<< "di vale In
" una
<< k <<
}
Unaendl;
chiamata
copia i
La chiamata
}
chiamata, n, valori parametri attuali
una funzione
m sono detti n, m del main nei
sta nel main o
parametri parametri formali x, y
dentro un’altra
attuali. Sono della funzione, che
funzione e non
riceve così tutte le
parametri
richiede di
informazioni di cui ha
propri del
ripetere i tipi
bisogno per il calcolo.
main
dei parametri
Chiamate di funzione in dettaglio
La chiamata di funzione k = max(n, m); crea gli
indirizzi di memoria per i parametri formali x,y e
assegna loro i valori dei parametri attuali n,m
n
7
m
14
main()
7
14
k
14
14
x
7
y
14
Return
value14
max(int
x, int
y)
Alla fine della chiamata gli indirizzi di x, y vengono
riutilizzati per altre variabili. Il valore di ritorno di
5-Funzioni
max(n,m) viene assegnato a k.
Funzioni: l’istruzione return
• Una funzione normalmente restituisce un valore
attraverso l’istruzione return, che compare nel
corpo della definizione della funzione, come segue:
return <espressione>;
double cube (double x)
{
return x*x*x;
}
Il tipo dell’espressione x*x*x argomento del
return deve coincidere con il tipo del valore di
ritorno (in questo caso devono essere entrambi
double).
5-Funzioni
Attenti a non perdere il
valore di una funzione
• Se ci dimentichiamo di salvare il valore di ritorno
di una funzione lo perdiamo. Infatti il valore
restituito da una funzione di solito sta in un
registro della CPU, e può essere salvato solo
assegnandolo ad una variabile, per esempio
scrivendo:
k = max(n, m);
In questo caso, il massimo tra i valori di n ed m è
ora il nuovo valore di k. Se invece scrivo soltanto:
max(n,m);
allora il valore di max(n,m) viene perso.
5-Funzioni
Le funzioni si possono
usare dentro espressioni
• Una funzione di tipo T definisce un’espressione di
tipo T. Quindi una funzione di tipo T può essere
usata in combinazione con qualunque funzione
definita o di libreria, e con qualunque operazione
aritmetica, per costruire nuove espressioni. Un
esempio:
7 + max(3, max(5, m*2) );
In una chiamata di funzione, il parametro
attuale di max può anche essere
un’espressione come max(5, m*2) che
contiene max
5-Funzioni
Le funzioni con tipo void
non indicano un valore
• Esistono funzioni C++ che non restituiscono un
valore, ma si limitano a svolgere un’azione (ad
esempio la stampa di un valore). Non hanno nessun
corrispondente tra le funzioni matematiche.
• Se una funzione svolge un’azione e non restituisce
alcun valore viene definita con il tipo void (vuoto):
void Ciao () {cout << "ciao! " << endl;}
void Pi () {cout << 3.14159265 << endl;}
• Ciao(); e Pi(); sono comandi di stampa, non
indicano nessun valore. È un errore scrivere
cout<<Ciao(); oppure Pi()+1; Non c’è un
valore di ritorno di Ciao() da stampare, né un
Funzioni: errori frequenti
• Il meccanismo di comunicazione tra una funzione e il
resto del programma non è semplice, ma deve venir
capito a fondo, altrimenti rischiamo errori come i
seguenti:
1. Usare una funzione senza valore di ritorno per
indicare un valore (abbiamo appena visto un
esempio);
2. Leggere da tastiera il valore di un parametro
formale.
3. Dimenticarci di salvare il valore di ritorno di una
funzione.
Nei prossimi lucidi spieghiamo gli errori 2 e 3.
È un errore leggere da tastiera il
valore di un parametro formale
La comunicazione tra una funzione e il resto del
programma avviene quando una chiamata assegna i
parametri attuali ai parametri formali. È un errore
assegnare noi stessi i parametri formali, per esempio
è un errore usare l’istruzione cin >> … come
segue:
int sq(int n){cout<<"dammi un valore per
n: ";cin>>n; return n*n; } // sq e’
scritta in modo errato
Infatti quando scriviamo sq(3), il valore 3 passato ad
sq attraverso il parametro formale 5-Funzioni
n viene
È un errore dimenticare di salvare
il valore di una funzione
Abbiamo già visto che se dimentichiamo di salvare
il valore di ritorno di una funzione lo perdiamo:
int f(int n){
...
return ...
}
int main() {int num; cin >> num;
f(num);}
/* il valore restituito da f(num)
viene si’ calcolato, ma non viene
assegnato a una variabile, quindi va
perso. */
5-Funzioni
3. Variabili locali
• All’interno del corpo di una funzione può essere
utile avere variabili che esistano solo durante
l’esecuzione, per evitare di sovrascrivere
variabili già in uso. Queste variabili si dicono
“variabili locali”: sono proprie alla funzione come
i parametri formali, e come essi nascono (nel
senso che ricevono un indirizzo di memoria) e
muoiono (nel senso che il loro indirizzo viene
riciclato) ad ogni chiamata della funzione.
int f(int n)
{ int r, s;
... }
Ambito di visibilità delle
variabili locali r ed s e
del parametro formale n:
queste variabili r,s,n
esistono solo qui 5-Funzioni
Quali variabili dobbiamo
definire come “locali”?
• Più variabili possibile! Per rendere indipendenti
sottoprogramma e programma, dobbiamo definire
come variabile locale di una funzione qualunque
variabile contenga un risultato intermedio delle
funzione, come un contatore o un accumulatore.
Inseriamo invece tra i parametri della funzione
solo le variabili indispensabili alla funzione.
SI: indispensabile
NO: contatore NO: accumulatore
int fattoriale(int n, int i, int prod)
{ for(i=2,prod=1;i<=n;++i)prod=prod*i
return prod;}
5-Funzioni
Cosa succede se inseriamo una
variabile locale tra i parametri?
• Il programma diventa inutilmente complicato,
illeggibile e a rischio di errore. Nell’esempio del
fattoriale,
se
dichiariamo
una
funzione
fattoriale(n,i,prod), per calcolare il fattoriale di
3 dobbiamo scrivere fatt(3,2,1), fornendo non
solo il 3, ma anche i valori iniziali i=2 e prod=1.
int fattoriale(int n) {int i; int prod;
for(i=2,prod=1;i<=n;++i)prod=prod*i;
return prod;}
SI: indispensabile
Contatore locale
Accumul.
locale
5-Funzioni
A cosa servono
le variabili locali?
Riassumendo, le variabili locali di una funzione:
1. Sono visibili solo dalle istruzioni nel corpo della
funzione.
2. Esistono solo finché la funzione è in esecuzione,
quindi spariscono. Lo spazio di memoria che esse
occupano viene indicato come “disponibile” e
viene assegnato ad altre variabili. Questa
operazione viene detta “riciclo” della memoria.
3. Evitano sovrascritture accidentali di variabili
con lo stesso nome in altre funzioni o nel main, e
nascondono i passi di calcolo della funzione
5-Funzioni
all’interno della funzione stessa.
Variabili locali e
Stack di sistema (cenni)
• Per capire meglio come funzionano le variabili
locali, vediamo come avviene la creazione e il
“riciclo” delle variabili locali. Il sistema gestisce un
gruppo di bytes consecutivi detto stack (pila).
• Ogni volta che dichiariamo una variabile nel main
o nella funzione appena chiamata, aggiungiamo alla
pila uno spazio di memoria con il valore della
variabile.
Parte della pila con le
x  1200CF (indirizzo esadec.) variabili generate da una
chiamata a una funzione f
z  1205B1A
Parte della pila con le
x  06A231
5-Funzioni
variabili del main()
y  105BA3
La ricerca di una variabile
locale nello Stack di sistema
La sola x visibile
durante
{ float x; …}
l’esecuzione di
int main()
f(…) e’ quella nel
{ double x, y; …
riquadro più in
f(x); … }
Per cercare il valore di x, leggiamo alto, la x di f
dall’alto in basso, fermandoci la
prima volta che incontriamo x
La x del main() non
x  1200CF (x di f)
e’ visibile durante
z  1205B1A
l’esecuzione di f(…),
x  06A231 (x del main)
ma continua ad
y  105BA3
5-Funzioni
esistere
double f(double z)
Parametri e variabili locali:
un errore tipico
Per errore
usiamo due volte
lo stesso nome x
in f: cosa
succede?
double f(double x)
{ float x; …}
int main()
{double x, y; … f(x);
…}
Per saperlo, leggiamo lo stack di sistema “dall’alto al
basso”, fermandoci alla prima x che troviamo
La x variabile locale,
x  1200CF
dichiarata per ultima,
x  1205B1A
viene trovata per prima e
x  06A231
nasconde
(in
inglese:
y  105BA3
“shadows”) il parametro
formale x
Variabili locali nei Blocchi
• La dichiarazione delle variabili locali può anche
essere annidata in blocchi delimitati da { } :
int f(int n) { int s = 0;
{ int r, s = 1; /* la seconda s,
definita in questo blocco, “fa ombra”
alla prima locale alla f */
cout << s; };
cout << r; /* ERRORE: fuori dal
blocco in cui è definita, r non esiste
più! E se anche esistesse, sarebbe
un’altra r … */
5-Funzioni
cout << s; /* s vale ancora 0, la
s
Errori frequenti
int f(int n)
Come
il
passaggio
dei
{ int s; ... parametri, anche la visibilità è
un meccanismo importante
s = ...}
nell’uso delle funzioni, e va
int main()
capita a fondo, altrimenti
rischiamo errori come questo:
{
int m = f(7);
int p = s; /* ERRORE: fuori dalla
funzione f in cui è definita, s non
esiste più! E se anche esistesse,
sarebbe un’altra s … */ ... }
5-Funzioni
Variabili globali
• Tutte le variabili sin qui viste sono parametri
oppure variabili locali: alcune sono locali al main(),
ma in C++ anche il main() è una funzione.
• Le variabili definite fuori dalle funzioni sono
visibili ovunque (tranne in blocchi che abbiano
variabili locali omonime) e sono persistenti per
tutta l’esecuzione del programma. Sono dette
variabili globali.
• Per esempio, costanti e parametri del
programma dovrebbero essere variabili globali.
#include <iostream>
double pi = 3.14159265;
/* pi esisterà per tutto il programma */5-Funzioni
Variabili globali:
evitare un uso eccessivo
• Le variabili globali servono per violare le limitazioni
di visibilità tipiche delle variabili locali, limitazioni
a volte troppo restrittive. L’uso delle variabili
globali deve essere moderato, perchè può causare
riscritture accidentali di variabili con lo stesso
nome, e perchè produce programmi poco leggibili,
come nel seguente esempio:
double k=0.0; // k esiste per tutto il
programma
void C(double x) { … k = sqrt(x);… … }
int
main(){C(25);
cout
eseguire
C(25)
assegna
<<
k;}
/*
5-Funzioni
k=5.0,
ma
5. Passaggio per riferimento
• Il passaggio dei parametri è detto per valore
perche’ comporta una copia del parametro attuale
in quello formale.
• Questo è un difetto quando vogliamo usare una
funzione per produrre una modifica permanente:
void scambia (int x, int y)
scambia dovrebbe
scambiare i suoi
{int temp = x; x = y; y = temp;}
argomenti ma agisce
int main() { int n = 45, m = 0;
solo sulle variabili
scambia(n, m);
temporanee x, y
cout << n << " \t " << m;} /* i
parametri formali x,y, copie di n,m,
5-Funzioni
sono stati scambiati, ma i parametri
Passaggio per riferimento
• In C++ si può passare l’indirizzo di parametro
attuale x definendo il parametro formale della
funzione come &x. &x denota l’indirizzo di x.
• Questo passaggio di parametri e’ detto per
riferimento e ed è usato per violare le limitazioni
di visibilità tipiche dei parametri di una funzione, e
per modificare il valore originale dell’argomento x.
void scambia (int & x, int & y)
{
int temp = x; x = y; y = temp;
indirizzo x
}
indirizzo y
5-Funzioni
Passaggio per riferimento
main()
scambia()
n
45
x
m
0
y
temp
45
x ed y in scambia() assumono lo stesso indirizzo di
n, m nel main(). x e n sono sinonimi, cioè sono la
“stessa” variabile con due nomi diversi (condividono
l’indirizzo di memoria). Questo accorgimento consente
a scambia() di modificare le x, y originali. 5-Funzioni
Usi del passaggio
per riferimento
Anche il passaggio per riferimento puo’ creare errori
difficili da scoprire: viola le limitazioni di visibilità e
consente riscritture accidentali di variabili, quindi va
usato con moderazione.
Il passaggio per riferimento può essere utile quando
si deve restituire più di un valore, per es.,
q=quoziente e r=resto di n/m.
In tal caso si passano gli indirizzi delle variabili q, r in
cui vogliamo che siano scritti i risultati. Le variabili q,
r originarie vengono modificate.
void Div (int n, int m, int & q, int &
r)
{ r = n%m; // calcola quoziente e resto
5-Funzioni
n:m
Usi del passaggio
per riferimento
• Nell’esempio seguente, la chiamata a Div
modifica i valori di quoziente e resto:
void Div (int n, int m, int & q,
int & r)
// calcola quoziente e resto di n
div. m
{ …
}
int main()
{int resto=0, quoziente=0;
Div(20, 3, quoziente, resto);
5-Funzioni
Errori frequenti
• Non possiamo passare per riferimento una
costante o una espressione. Un argomento
passato per riferimento deve avere un indirizzo
di memoria, dunque, per quanto ne sappiamo fin
qui, può solo essere una variabile:
int main ()
{ scambia(7, 5+9); } // ERRORE; né 7
né 5+9
// hanno un indirizzo di memoria
5-Funzioni
6. Come si descrive
una funzione?
In altre parole, “che cosa” calcola una funzione?
La risposta viene chiamata una specifica
dell’algoritmo, ed è fatta di due parti: una precondizione e una post-condizione
Vorrei calcolare valori
con la proprietà 
 Post-condizione
Sig. Utente
Posso farlo
purché i dati
soddisfino 
 Pre-condizione
Sig. Funzione
5-Funzioni
Pre e Post condizioni
• La pre-condizione è una richiesta, ossia
un’ipotesi sull’ingresso (spetta a chi fornisce i
dati controllarla)
• La post-condizione è una proprietà dell’uscita di
una funzione, che si deve garantire che valga
dopo l’esecuzione.
• Quando inseriamo nella funzione dei dati che
non rispettano la precondizione, otteniamo
delle risposte imprevedibili. Il calcolo della
funzione può anche continuare per sempre, o
produrre un “crash”.
5-Funzioni
Esempi di
Pre e Post condizioni
int MCD (int n, int m)
// pre: n, m >=0 e non entrambi nulli
// post: MCD restituisce il massimo comun
// divisore tra n ed m
void Div (int n, int m, int & q, int & r)
// pre: m != 0
// post: q quoziente, r resto della div. n:m
5-Funzioni
Riepilogo
• I sottoprogrammi sono parti di un programma la cui
esecuzione può essere ripetuta in punti diversi del
programma principale, o di altri sottoprogrammi.
• Le funzioni sono sottoprogrammmi con parametri
propri, parametri che esistono solo per la durata di
una esecuzione della funzione.
• Le funzioni compaiono in un programma in tre modi
diversi: in una dichiarazione, in una definizione e in
una o più chiamate.
• Le funzioni possono avere variabili locali.
• Le funzioni si descrivono con una specifica, fatta di
pre- e post-condizioni.
5-Funzioni
Riepilogo
• Le funzioni possono restituire un valore,
oppure no (in tal caso sono di tipo void)
• I parametri sono passati per valore oppure
per riferimento
• Le variabili locali oscurano sia le variabili
globali che i parametri
• Se una funzione restituisce un valore,
allora la funzione può essere usata in
un’espressione per indicare il valore da
essa restituito. Altrimenti no.
5-Funzioni
Scarica

Blocchi, sottoprogrammi e funzioni