2. Modello cliente/servitore
Servitore:
• Un qualunque ente computazionale capace di nascondere la
propria organizzazione interna
• presentando ai clienti una precisa interfaccia per lo scambio
di informazioni
Cliente:
• qualunque ente in grado di invocare uno o più servitori per
svolgere il proprio compito
3. Modello cliente/servitore
Un servitore può
• essere passivo o
attivo
• servire molti clienti oppure costituire la risorsa privata di
uno specifico cliente
– in particolare: può servire un cliente alla volta, in
sequenza, oppure più clienti per volta, in parallelo
• trasformarsi a sua volta in cliente, invocando altri
servitori o anche se stesso.
4. Comunicazione cliente/servitore
•Lo scambio di informazioni tra un cliente e un servitore
può avvenire
in modo esplicito tramite le interfacce stabilite dal
servitore
in modo implicito tramite aree-dati accessibili ad
entrambi,
ossia l’ambiente condiviso
5. FUNZIONI COME SERVITORI
• Una funzione è un servitore
– passivo
– che serve un cliente per volta
– che può trasformarsi in cliente invocando altre funzioni o se
stessa
• Una funzione è un servitore dotato di nome che incapsula le
istruzioni che realizzano un certo servizio.
• Il cliente chiede al servitore di svolgere il servizio
– chiamando tale servitore (per nome)
– fornendogli le necessarie informazioni
• Nel caso di una funzione, cliente e servitore comunicano
mediante l’interfaccia della funzione.
6. Interfaccia di una funzione
•L’interfaccia (o prototipo) di una funzione comprende
nome della funzione
lista dei parametri
tipo del valore da essa denotato
• Esplicita il contratto di servizio fra cliente e servitore
•Cliente e servitore comunicano quindi mediante
i parametri trasmessi dal cliente al servitore all’atto della chiamata
il valore restituito dal servitore al cliente
7. ESEMPIO
• Calcolo del massimo di due valori
3,
• Sequenza operazioni: 5
– Il cliente comunica al
servitore i due valori
– Il servitore calcola il massimo
– Il servitore comunica al 5
cliente il valore del massimo
•Cliente e servitore devono essere d’accordo su:
–il tipo dei valori int
–quanti sono i valori 2
–qual’è il nome del servitore max
–qual’è il tipo del valore di ritorno int
•Il cliente non è interessato all’algoritmo che il servitore utilizza
8. ESEMPIO
• Quindi, dal lato del servitore • Il cliente dovrà mandare al
dovrà esserci scritto: servitore identificato da quel
– come si chiama nome
– di quanti dati ha bisogno e di – i dati di ingresso, in numero
che tipo e tipo giusti
– di che tipo è il valore calcolato – ricevere il risultato
9. Funzioni: esempio C
int max (int x, int y ){ /*interfaccia
if (x>y) return x ;
else return y;
}
• Il simbolo max denota il nome della funzione
• Le variabili intere x e y sono i parametri della funzione
• Il valore restituito è un intero int
10. Comunicazione cliente/servitore
• Il cliente passa informazioni al servitore mediante una serie di
parametri
Parametri formali:
sono specificati nella dichiarazione del servitore
esplicitano il contratto fra servitore e cliente
indicano che cosa il servitore si aspetta dal cliente
Parametri attuali:
sono trasmessi dal cliente all’atto della chiamata
devono corrispondere ai parametri formali in numero,
posizione e tipo
12. Comunicazione cliente/servitore
Legame tra parametri attuali e parametri
formali:
• effettuato al momento della chiamata, in
modo
dinamico
Tale legame:
vale solo per l’invocazione corrente
vale solo per la durata della funzione
13. Esempio
All’atto di questa
chiamata della funzione,
si effettua un legame tra:
xez
ye4
14. Esempio:
All’atto della chiamata
della funzione si effettua
il legame tra:
xe5
yez
15. Information hiding
• La struttura interna (corpo) di una funzione è
completamente inaccessibile dall’esterno
• Così facendo si garantisce protezione dell’informazione
(information hiding)
• Una funzione è accessibile solo attraverso la sua
interfaccia
• Quindi posso cambiare l’algoritmo della funzione senza
preoccuparmi di quello che succede dal lato del cliente
16. Definizione di funzione in C
<tipoValore> <nome>(<parametri-formali>) {
<corpo>
La forma base è:
}
return <espressione>;
<parametri-formali>
o una lista vuota: void
o una lista di variabili (separate da virgole)
visibili solo entro il corpo della funzione
<tipoValore>
deve coincidere con il tipo del valore restituito dalla funzione
Può non esservi valore restituiti, in tal caso il tipo è void
17. Definizione di funzione in C
<tipoValore> <nome>(<parametri-formali>) {
<corpo>
La forma base è:
}
return <espressione>;
• Nella parte corpo possono essere presenti:
definizioni e/o dichiarazioni locali (parte dichiarazioni)
e un insieme di istruzioni (parte istruzioni)
• I dati riferiti nel corpo possono essere costanti, variabili,
oppure parametri formali
• All'interno del corpo, i parametri formali vengono trattati come variabili
18. Funzioni: nascita e morte
• All’atto della chiamata:
l’esecuzione del cliente viene sospesa e il
controllo passa al servitore
• Il servitore “vive” solo per il tempo necessario a
svolgere il servizio
• Al termine, il servitore “muore”, e l’esecuzione
torna al cliente
19. Chiamata di funzione
• La chiamata di funzione è un’espressione della forma
<nomefunzione> ( <parametri-attuali> )
• dove:
<parametri-attuali> ::=
[ <espressione> ] { , <espressione> }
20. Funzioni: esempio
Parametri formali
int max (int x, int y ){
SERVITORE
if (x>y) return x ;
else return y; Definizione
}
della funzione
main() {
CLIENTE
int z = 8;
int m; Chiamata
m = max ( z, 4);
}
della funzione
Parametri attuali
21. Risultato di una funzione
• L’istruzione return provoca la restituzione del
controllo al cliente, unitamente al valore della
espressione che la segue.
• Eventuali istruzioni successive alla return non
saranno mai eseguite
int max (int x, int y ){
if (x>y) return x ;
else return y;
}
22. Funzioni: esempio
Parametri formali
int max (int x, int y ){
SERVITORE
if (x>y) return x ;
else return y; Definizione
}
della funzione
main() {
CLIENTE
int z = 8;
int m; Chiamata
m = max ( z, 4);
}
della funzione
Risultato
23. Riassumendo
All’atto dell’invocazione di una funzione:
• si crea una nuova attivazione (istanza) del servitore
• si alloca la memoria per i parametri
(e le eventuali variabili locali)
• si trasferiscono i parametri al servitore
• si trasferisce il controllo al servitore
• si esegue il codice della funzione
24. Passaggio dei parametri
In generale, un parametro può essere trasferito dal cliente al servitore:
• per valore o copia (by value)
si trasferisce il valore del parametro attuale
• per riferimento (by reference)
si trasferisce un riferimento al parametro attuale
25. Passaggio per valore
• Si trasferisce una copia del valore del parametro attuale
cliente
26. Passaggio per valore
• Si trasferisce una copia del valore del parametro attuale
Valore
(copiato) di z
copia
Ogni azione
fatta su w è
locale al
servitore
Istanza del
servitore
cliente servitore
28. Passaggio per valore
• Si trasferisce un riferimento al parametro attuale
Riferimento a z
(indirizzo)
riferimento
x
x
Ogni azione
fatta su w è
In realtà
fatta sulla
Istanza del
variabile z
servitore
del cliente
cliente servitore
29. Passaggio dei parametri in C
• In C, i parametri sono trasferiti sempre e solo per valore
si trasferisce una copia del parametro attuale, non l’originale
tale copia è strettamente privata e locale a quel servitore
il servitore potrebbe quindi alterare il valore ricevuto, senza
che ciò abbia alcun impatto sul cliente
30. Passaggio dei parametri in C
• In C, i parametri sono trasferiti sempre e solo per valore
Conseguenza:
• è impossibile usare un parametro per trasferire informazioni
verso il cliente
• per trasferire un’informazione al cliente si sfrutta il valore di
ritorno della funzione
31. Esempio
Perché il passaggio per valore non basta?
Problema:
scrivere una procedura che scambi i valori di due variabili intere
Specifica:
Dette A e B le due variabili, ci si può appoggiare a una variabile
ausiliaria T, e svolgere lo scambio in tre fasi
Frammento di codice:
int a,b,t;
...
t = a; a = b; b = t;
…
32. Esempio
• Supponendo di utilizzare, senza preoccuparsi, il passaggio per
valore usato finora, la codifica potrebbe essere espressa come
segue:
void scambia(int a, int b) {
int t;
t = a; a = b; b = t;
return;
}
33. Esempio
Il cliente invocherebbe quindi la procedura così:
main(){
int y = 5, x = 33;
scambia(x, y);
/* ora dovrebbe essere
x=5, y=33 ...
MA NON È VERO
*/
}
Perché non funziona?
34. Esempio: cosa è successo?
• La procedura ha effettivamente scambiato i valori di A e B al suo interno
• ma questa modifica non si è propagata al cliente, perché sono state
scambiate le copie locali alla procedura, non gli originali
• al termine della procedura, le sue variabili locali sono state distrutte,
quindi nulla è rimasto del lavoro svolto dalla procedura
35. Esempio: cosa è successo?
• Ogni azione fatta su a e b è strettamente locale al servitore.
Quindi a e b vengono scambiati, ma quando il servitore termina,
tutto scompare
Valori (copiati)
di a e b
copia
x
y
cliente servitore
36. Passaggio dei parametri in C
Il C adotta sempre il passaggio per valore!
È sicuro: le variabili del cliente e del servitore sono disaccoppiate
... ma non consente di scrivere componenti software il cui scopo
sia diverso dal calcolo di una espressione
Per superare questo limite occorre il passaggio per riferimento
37. Passaggio per riferimento
Il passaggio per riferimento (by reference):
• non trasferisce una copia del valore del parametro attuale
• ma un riferimento al parametro, in modo da dare al servitore
accesso diretto al parametro in possesso del cliente
• il servitore, quindi, accede direttamente al dato del cliente e
può modificarlo
38. Passaggio per riferimento
Si trasferisce un riferimento ai parametri attuali (cioè i loro indirizzi)
x
y
cliente
39. Passaggio per riferimento
Ogni azione fatta su a e b, in realtà
è fatta su x e y nell’environment del
cliente
Riferimenti a
x e y (indirizzi)
α riferimento
x
β riferimento
y
cliente servitore
40. Passaggio per riferimento
quindi, scambiando α e β, in
realtà si scambiano x e y
Riferimenti a
x e y (indirizzi)
α riferimento
x
β riferimento
y
cliente servitore
41. Realizzare il passaggio per riferimento in C
• Il C non fornisce direttamente un modo per attivare il
passaggio per riferimento -> a volte occorre costruirselo
• è una grave mancanza!
• il C lo fornisce indirettamente solo per alcuni tipi di dati
• quindi, occorre costruirselo quando serve.
(vedremo più avanti dei casi)
42. Funzioni e strutture
• Con le funzioni si possono usare come parametri e
come valore di ritorno le strutture (con gli array è un
po’ diverso, come vedremo)
• L’utilizzo delle funzioni permette di costruire
semplicemente applicazioni con la metodologia
top-down
• Si scriva un programma che permette di
– leggere due frazioni
– calcolarne la somma
– visualizzare il risultato
43. Esempio
• Per prima cosa, definiamo le strutture dati:
– una frazione è costituita da un numeratore ed un denominatore
typedef struct { int num; int den; } frazione;
• Poi scriviamo l’algoritmo partendo dalla versione più astratta.
Scriviamo il main invocando le varie funzioni che ci servono
main()
{ frazione f1, f2, somma;
f1 = leggiFrazione();
f2 = leggiFrazione();
somma = sum(f1,f2);
printf("%d/%d",somma.num,somma.den);
}
44. Esempio
• Poi implementiamo le funzioni che abbiamo invocato nel main:
frazione leggiFrazione()
{ frazione f;
scanf("%d",&f.num);
scanf("%d",&f.den);
return f;
}
45. Esempio
• La somma di due frazioni si calcola così:
– calcolo il minimo comun denominatore (minimo comune multiplo
dei denominatori); questo è il denominatore della somma
– porto la prima frazione al comun denominatore
– porto la seconda frazione al comun denominatore
– calcolo la somma dei numeratori: questo è il numeratore della somma
frazione sum(frazione f1, frazione f2)
{ int mcd; // minimo comun denominatore
frazione somma;
mcd = mcm(f1.den,f2.den);
somma.den = mcd;
f1 = portaDen(f1,mcd);
f2 = portaDen(f2,mcd);
somma.num = f1.num + f2.num;
return somma;
}
46. Esempio
• Infine implementiamo le funzioni che abbiamo usato nelle funzioni
• Per portare una frazione ad un denominatore, devo moltiplicare
numeratore e denominatore per la stessa quantità
nuovoNum/nuovoDen = vecchioNum/vecchioDen
• quindi
nuovoNum=vecchioNum*nuovoDen/vecchioDen
frazione portaDen(frazione f, int nDen)
{ frazione nuovo;
nuovo.den = nDen;
nuovo.num = f.num*nDen/f.den;
return nuovo;
}
47. Esempio
• Per calcolare il minimo comune multiplo di due
interi, posso farne il prodotto e dividere per il
massimo comun divisore dei due
int mcm(int a, int b)
{ return a*b/MCD(a,b);
}
48. Esempio
• Per calcolare il MCD di due numeri, posso usare
il metodo di Euclide
int MCD(int m, int n)
{ while (m != n)
if (m>n)
m=m-n;
else n=n-m;
return m;
}
49. Il programma risultante
frazione sum(frazione f1, frazione f2)
#include <stdio.h> { int mcd;
typedef struct { int num; int den; } frazione somma;
frazione; mcd = mcm(f1.den,f2.den);
int MCD(int m, int n) somma.den = mcd;
{ while (m != n) f1 = portaDen(f1,mcd);
if (m>n) f2 = portaDen(f2,mcd);
somma.num = f1.num + f2.num;
m=m-n;
return somma;
else n=n-m;
}
return m;
frazione leggiFrazione()
}
{ frazione f;
int mcm(int a, int b) scanf("%d",&f.num);
{ return a*b/MCD(a,b); scanf("%d",&f.den);
} return f;
frazione portaDen(frazione f, int }
nDen) main()
{ frazione nuovo; { frazione f1, f2, somma;
nuovo.den = nDen; f1 = leggiFrazione();
nuovo.num = f.num*nDen/f.den; f2 = leggiFrazione();
return nuovo; somma = sum(f1,f2);
} printf("%d/%d",somma.num,somma.den)
;
}
50. Il programma risultante
• E` abbastanza facile da scrivere e da capire
• E` facile da modificare
• Es: voglio assicurarmi che l’utente non inserisca una frazione che ha per
denominatore zero
• Intervengo in una sola funzione: la leggiFrazione
– è una funzione di 4 istruzioni, quindi facile da capire e da
modificare
frazione leggiFrazione()
{ frazione f;
do
{ scanf("%d",&f.num);
scanf("%d",&f.den);
if (f.den==0)
printf(“Re-inserire la frazionen");
} while (f.den == 0);
return f;
}
51. Modificabilità
• Ora voglio che mi fornisca solo frazioni ai minimi termini
• Aggiungo una funzione riduci. Posso invocarla nel main
main()
{ frazione f1, f2, somma;
f1 = leggiFrazione();
f2 = leggiFrazione();
somma = riduci(sum(f1,f2));
printf("%d/%d",somma.num,somma.den);
}
52. Modificabilità
• Poi definisco la nuova funzione riduci
• Per ridurre una funzione ai minimi termini, basta dividere
numeratore e denominatore per il loro MCD
frazione riduci(frazione f)
{ int m = MCD(f.num,f.den);
f.num = f.num/m;
f.den = f.den/m;
return f;
}