Lezione 10 (21 marzo 2012)3

Post on 13-Jun-2015

290 views 1 download

Transcript of Lezione 10 (21 marzo 2012)3

Funzioni in C

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

Modello cliente/servitore

Modello cliente/servitoreUn servitore può• essere passivo o attivo

• servire molti clienti oppure costituire la risorsa privata diuno 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 altriservitori o anche se stesso.

•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

Comunicazione cliente/servitore

• 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 comunicanomediante l’interfaccia della funzione.

FUNZIONI COME SERVITORI

•L’interfaccia (o prototipo) di una funzione comprende

nome della funzionelista dei parametritipo del valore da essa denotato

• Esplicita il contratto di servizio fra cliente e servitore

•Cliente e servitore comunicano quindi mediantei parametri trasmessi dal cliente al servitore all’atto della chiamatail valore restituito dal servitore al cliente

Interfaccia di una funzione

• Calcolo del massimo di due valori

• Sequenza operazioni:– Il cliente comunica al

servitore i due valori– Il servitore calcola il massimo– Il servitore comunica al

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

5

3,5

ESEMPIO

ESEMPIO

• Quindi, dal lato del servitore dovrà esserci scritto:

– come si chiama– di quanti dati ha bisogno e di

che tipo– di che tipo è il valore calcolato

• Il cliente dovrà mandare alservitore identificato da quelnome– i dati di ingresso, in numero

e tipo giusti– ricevere il risultato

int max (int x, int y ){ /*interfacciaif (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

Funzioni: esempio C

• Il cliente passa informazioni al servitore mediante una serie di parametri

Parametri formali:sono specificati nella dichiarazione del servitoreesplicitano il contratto fra servitore e clienteindicano che cosa il servitore si aspetta dal cliente

Parametri attuali:sono trasmessi dal cliente all’atto della chiamatadevono corrispondere ai parametri formali in numero, posizione e tipo

Comunicazione cliente/servitore

Funzioni: esempio parametri

Legame tra parametri attuali e parametri formali:• effettuato al momento della chiamata, in modo

dinamico

Tale legame:vale solo per l’invocazione correntevale solo per la durata della funzione

Comunicazione cliente/servitore

Esempio

All’atto di questa chiamata della funzione, si effettua un legame tra:x e zy e 4

Esempio:

All’atto della chiamata della funzione si effettua il legame tra:x e 5y e z

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 suainterfaccia

• Quindi posso cambiare l’algoritmo della funzione senza preoccuparmi di quello che succede dal lato del cliente

Definizione di funzione in C

<tipoValore> <nome>(<parametri-formali>) {

<corpo>}

<parametri-formali>o una lista vuota: voido una lista di variabili (separate da virgole)

visibili solo entro il corpo della funzione

<tipoValore>deve coincidere con il tipo del valore restituito dalla funzionePuò non esservi valore restituiti, in tal caso il tipo è void

La forma base è:return <espressione>;

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

Funzioni: nascita e morte

• All’atto della chiamata:l’esecuzione del cliente viene sospesa e ilcontrollo 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

Chiamata di funzione

• La chiamata di funzione è un’espressione della forma

<nomefunzione> ( <parametri-attuali> )

• dove:

<parametri-attuali> ::=[ <espressione> ] { , <espressione> }

Funzioni: esempioFunzioni: esempio

int max (int x, int y ){if (x>y) return x ;

else return y;}

SERVITORE

Definizione della funzione

CLIENTE

Chiamata della funzione

main() {int z = 8;int m;m = max ( z, 4);

}

Parametri formali

Parametri attuali

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;

}

Funzioni: esempioFunzioni: esempio

int max (int x, int y ){if (x>y) return x ;

else return y;}

SERVITORE

Definizione della funzione

CLIENTE

Chiamata della funzione

main() {int z = 8;int m;m = max ( z, 4);

}

Parametri formali

Risultato

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

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

Passaggio per valore

• Si trasferisce una copia del valore del parametro attuale

cliente

Passaggio per valore

• Si trasferisce una copia del valore del parametro attuale

cliente servitore

Istanza del servitore

Ogni azionefatta su w èlocale al servitore

Valore (copiato) di z

copia

Passaggio per riferimento

• Si trasferisce un riferimento al parametro attuale

cliente

Passaggio per valore

• Si trasferisce un riferimento al parametro attuale

cliente servitore

Istanza del servitore

Ogni azionefatta su w èIn realtàfatta sulla variabile z del cliente

Riferimento a z(indirizzo)

riferimento

xx

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

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

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;…

Esempio

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;

}

EsempioIl 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?

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

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

cliente servitore

Valori (copiati) di a e b

copia

x

y

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 scoposia diverso dal calcolo di una espressione

Per superare questo limite occorre il 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

Passaggio per riferimento

Passaggio per riferimento

cliente

x

y

Si trasferisce un riferimento ai parametri attuali (cioè i loro indirizzi)

cliente servitore

Riferimenti a x e y (indirizzi)

riferimento

x

yriferimentoβ

α

Passaggio per riferimentoOgni azione fatta su a e b, in realtàè fatta su x e y nell’environment del

cliente

cliente servitore

Riferimenti a x e y (indirizzi)

riferimento

x

yriferimentoβ

α

Passaggio per riferimento

quindi, scambiando α e β, in realtà si scambiano x e y

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)

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

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);

}

Esempio

• Poi implementiamo le funzioni che abbiamo invocato nel main:

frazione leggiFrazione(){ frazione f;

scanf("%d",&f.num);scanf("%d",&f.den);return f;

}

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 denominatorefrazione 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;}

Esempio

• Infine implementiamo le funzioni che abbiamo usato nelle funzioni

• Per portare una frazione ad un denominatore, devo moltiplicarenumeratore e denominatore per la stessa quantità

nuovoNum/nuovoDen = vecchioNum/vecchioDen

• quindinuovoNum=vecchioNum*nuovoDen/vecchioDen

frazione portaDen(frazione f, int nDen){ frazione nuovo;

nuovo.den = nDen;nuovo.num = f.num*nDen/f.den;return nuovo;

}

Esempio

• Per calcolare il minimo comune multiplo di dueinteri, 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);}

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;

}

#include <stdio.h>typedef struct { int num; int den; }

frazione;int MCD(int m, int n){ while (m != n)

if (m>n)m=m-n;

else n=n-m;return m;

}int mcm(int a, int b){ return a*b/MCD(a,b);}frazione portaDen(frazione f, int

nDen){ frazione nuovo;nuovo.den = nDen;nuovo.num = f.num*nDen/f.den;return nuovo;

}

frazione sum(frazione f1, frazione f2){ int mcd;

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;

}frazione leggiFrazione(){ frazione f;

scanf("%d",&f.num);scanf("%d",&f.den);return f;

}main(){ frazione f1, f2, somma;

f1 = leggiFrazione();f2 = leggiFrazione();somma = sum(f1,f2);printf("%d/%d",somma.num,somma.den);

}

Il programma risultante

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 perdenominatore 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 frazione\n");} while (f.den == 0);

return f;}

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);

}

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;

}