La presentazione è in caricamento. Aspetta per favore

La presentazione è in caricamento. Aspetta per favore

Funzioni definite dallutente Scopo delle funzioni del C, sia fornite dal linguaggio (di biblioteca) sia scritte dallutente, è quello di ricevere in ingresso.

Presentazioni simili


Presentazione sul tema: "Funzioni definite dallutente Scopo delle funzioni del C, sia fornite dal linguaggio (di biblioteca) sia scritte dallutente, è quello di ricevere in ingresso."— Transcript della presentazione:

1 Funzioni definite dallutente Scopo delle funzioni del C, sia fornite dal linguaggio (di biblioteca) sia scritte dallutente, è quello di ricevere in ingresso uno o più valori, operare su essi e restituire direttamente un singolo valore in uscita: Prima di vedere le funzioni scritte dallutente, ricordiamo quanto già visto sulla chiamata e luso di due funzioni printf() e scanf(). Come già sappiamo, una funzione viene usata o chiamata indicandone il nome e passandole dei dati nelle parentesi che seguono il suo nome.

2 La funzione chiamata deve essere in grado di accettare i dati che le vengono passati dalla funzione chiamante, e solo dopo che sono stati ricevuti con successo, i dati possono essere manipolati per produrre un risultato utile. Una volta che sia stata definita in un programma, una funzione può essere chiamata dalle altre funzioni del programma. Naturalmente, nel creare una funzione propria, ci si deve preoccupare sia della funzione stessa, sia del modo in cui si interfaccia alle altre funzioni. Per chiarire il processo di inviare e ricevere dati, consideriamo il seguente segmento di programma, che chiama una funzione trova_max() :

3 Tale programma potrà essere eseguito solo dopo che sarà stata scritta e inserita in esso la funzione trova_max(), in grado di accettare i due dati che le vengono passati e determinare il più grande di essi. trova_max() è detta funzione chiamata, mentre la funzione che esegue la chiamata, in questo caso main(), è detta funzione chiamante.

4 Dichiarazione (prototipo) di funzione. Prima che una funzione possa essere chiamata deve essere dichiarata, allinterno della funzione chiamante, da unistruzione detta prototipo di funzione. Essa dichiara, nellordine: il tipo di valore che la funzione chiamata restituirà alla funzione chiamante (se presente); il tipo dei valori che la funzione chiamata si aspetta di ricevere dalla funzione chiamante. Nel nostro esempio, il prototipo di funzione float trova_max(float, float); dichiara che la funzione trova_max() restituirà un valore in virgola mobile; si aspetta che le vengano passati due valori pure in virgola mobile.

5 Un prototipo di funzione può essere situato: insieme alle istruzioni di dichiarazione delle variabili della funzione chiamante (come nel caso precedente), oppure prima del nome della funzione chiamante, ossia prima o dopo listruzione #include. La forma generale dellistruzione prototipo di funzione è: tipo-dati-restituito nome-funzione(lista tipi dati degli argomenti); Il tipo-dati-restituito dalla funzione deve corrispondere al tipo dati che sarà usato nella sua linea dintestazione. Analogamente, la lista tipi dati degli argomenti deve corrispondere a quelli che saranno usati nella definizione della funzione. Luso dei prototipi di funzione permette al compilatore il controllo di errore dei tipi di parametri: se il prototipo di funzione non si accorda con i tipi dati dei parametri restituiti, contenuti nella linea dintestazione della funzione, viene visualizzato un messaggio di errore (tipicamente TYPE MISMATCH ).

6 Il prototipo serve anche ad assicurare la conversione di tutti gli argomenti passati alla funzione nei tipi dati degli argomenti dichiarati quando la funzione è chiamata. Chiamata. Per chiamare una funzione è sufficiente scriverne il nome e racchiudere nelle parentesi che lo seguono i dati che sono passati alla funzione (detti argomenti attuali), come nellesempio seguente: Se uno degli argomenti in una chiamata di funzione è una varabile, la funzione chiamata riceve una copia del valore memorizzato nella variabile.

7 Ad es., listruzione: maxnum = trova_max(primonum, secnum); chiama la funzione trova_max; le passa i valori memorizzati nelle variabili primonum e secnum; assegna il valore restituito dalla funzone a maxnum. I nomi di variabili nelle parentesi sono gli argomenti attuali che forniscono i valori alla funzione chiamata. Dopo che i valori sono stati passati, il controllo è trasferito alla funzione chiamata. Come mostra la figura, la funzione trova_max() non riceve le variabili di nome primonum e secnum, e non ha conoscenza di questi nomi di variabile.

8 La funzione riceve piuttosto delle copie dei valori memorizzati in queste variabili, e deve a sua volta determinare dove memorizzare tali valori prima di compiere su essi qualsiasi operazione. Questa procedura di sicurezza garantisce che una funzione chiamata non cambi inavvertitamente i dati memorizzati in una variabile, ma cambi invece la sua copia della variabile. Perciò, a meno che non siano compiuti passi espliciti, a una funzione non è consentito cambiare i contenuti delle variabili dichiarate nella chiamata a essa.

9 I parametri dichiarati nella definizione della funzione sono usati per memorizzare i valori passati alla funzione quando essa viene chiamata. Come illustra la figura, la funzione trova_max tratta i suoi parametri x e y come delle variabili, la cui inizializzazione avviene al di fuori della funzione.

10 Definizione. Analogamente a main(), ogni funzione C consiste in due parti: unintestazione e un corpo. Scopo dellintestazione è: identificare il tipo dati del valore restituito dalla funzione; fornire un nome alla funzione; specificare numero, ordine e tipo degli argomenti che la funzione si aspetta. Scopo del corpo è: operare sui dati passati; restituire direttamente al massimo un valore alla funzione chiamante.

11 Linea dintestazione. Lintestazione di una funzione consiste in una singola linea che contiene, nellordine: il tipo dati del valore restituito dalla funzione; il nome della funzione; i nomi e i tipi dati degli argomenti della funzione. Dato che non è unistruzione, ma linizio del codice che definisce la funzione, la linea dintestazione termina senza ; Ad es., la seguente linea dintestazione di funzione dichiara:

12 I nomi degli argomenti sono detti parametri o argomenti formali, mentre il nome di funzione e i parametri sono detti dichiaratore di funzione. Tutti i parametri elencati nel dichiaratore di funzione devono essere separati da virgole e i loro tipi dati devono essere specificati separatamente. Se si omette un tipo dati, il parametro viene implicitamente assunto di tipo intero. Ad es., il dichiaratore float trova_max(float x, y) non dichiara entrambi i parametri, x e y, di tipo float, ma dichiara il parametro x di tipo float, e y di tipo intero. Analogamente, se si omette il tipo dati del valore restituito, la funzione restituisce implicitamente un valore intero.

13 Così, entrambe le intestazioni int val_max(float x, float y) e val_max(float x, float y) definiscono una funzione val_max che restituisce un valore intero. Allinterno di una intestazione di funzione si usa la parola chiave void per dichiarare o che la funzione non restituisce alcun valore, o che non ha argomenti. Ad es., lintestazione di funzione void display(int x, double y) dichiara che la funzione display() non restituisce alcun valore, mentre lintestazione di funzione double stampa_messaggio(void) dichiara che la funzione stampa_messaggio() non ha parametri, ma restituisce un valore di tipo double.

14 Corpo. Dopo avere scritto lintestazione della funzione trova_max(), possiamo costruirne il corpo, che conterrà eventuali dichiarazioni di variabili e istruzioni C racchiuse entro la solita coppia di parentesi { e }. In questo caso la funzione completa è: Quando sincontra listruzione return, viene calcolata lespressione entro parentesi, quindi il suo valore: viene convertito automaticamente nel tipo dati dichiarato allinizio della linea dintestazione della funzione e viene restituito alla funzione chiamante.

15 Dopo che il valore è stato restituito, il controllo del programma ritorna alla funzione chiamante. Quando si chiama la funzione trova_max, il parametro x è usato per memorizzare il primo valore che le viene passato, e y per il secondo. La funzione tuttavia non sa da dove provengano i valori quando è effettuata la chiamata. Osserviamo che nellistruzione return il tipo dati della variabile restituita corrisponde esattamente al tipo dati nella linea dintestazione della funzione. Ciò deve avvenire per ogni funzione che restituisca un valore. Il programma seguente inserisce la funzione trova_max allinterno del codice di programma presentato in precedenza.

16

17 Passaggio di vettori (1). Per passare a una funzione singoli elementi di un vettore, essi vanno inseriti come variabili con indici nellelenco degli argomenti nella chiamata alla funzione. Ad es., la chiamata di funzione trova_min(voti[2], voti[6]); passa i valori degli elementi voti[2] e voti[6] alla funzione trova_min(). Analogamente, per passare a una funzione un vettore completo si inserisce il suo nome nellelenco degli argomenti. Ad es., il vettore voti viene passato alla funzione trova_max con la chiamata di funzione trova_max(voti); Quando si passa un vettore completo a una funzione, essa riceve accesso al vettore effettivo, anziché a una copia dei suoi valori (come avviene quando vengono passati singoli elementi).

18 Infatti, come sappiamo, quando si passa un singolo scalare la funzione chiamata riceve una copia del valore memorizzato nella variabile, e ciò succede anche se si passa un singolo elemento di un vettore. Il passaggio di un vettore in questa maniera richiederebbe lesecuzione di una copia completa e separata di tutte le sue componenti; nel caso di vettori grandi lesecuzione di una copia a ogni chiamata di funzione sprecherebbe la memoria del computer, consumerebbe il tempo di elaborazione e vanificherebbe lo sforzo di ritornare cambiamenti multipli di elementi da parte del programma chiamato. Per evitare questi problemi, la funzione chiamata ha accesso diretto al vettore originale, cosicché ogni cambiamento da essa eseguito è apportato direttamente al vettore stesso.

19 In questa intestazione il nome dellargomento ( val ) è locale alla funzione, ma si riferisce ancora al vettore originario ( num ) creato al di fuori della funzione. Se, ad es., abbiamo dichiarato il vettore di 5 interi int num[5]; possiamo dichiarare la funzione void trova_max(int[5]) ed eseguire la seguente chiamata: trova_max(num); Lintestazione della funzione chiamata potrebbe allora essere: void trova_max(int val[5])

20 Passaggio di matrici. Il passaggio a una funzione di una matrice avviene in modo analogo a quello di un vettore a una dimensione: in particolare, anche adesso la funzione chiamata riceve accesso allintera matrice. Ad es., la chiamata mostra(val); rende disponibile lintera matrice val alla funzione di nome mostra(), cosicché ogni cambiamento eseguito da essa sarà fatto direttamente su val. Se è stata dichiarata la matrice char codice[26][9]; e la funzione char ottieni(int[26] [9]) è valida la seguente chiamata: ottieni(codice); mentre la funzione chiamata avrà unintestazione del tipo: char ottieni(char chiave[26][9]) In essa il nome dellargomento ( chiave ) è locale alla funzione, e tuttavia si riferisce ancora alla matrice originaria creata fuori da essa.

21 Se la matrice è globale, non è necessario passarla alla funzione, che può farvi riferimento tramite il nome globale. Il programma seguente illustra il passaggio di una matrice locale a una funzione che ne visualizza i valori. void mostra(int num[3][4]) /* intestazione */ { int ri, col; for (ri = 0; ri < 3; ++ri) { for(col= 0; col < 4; ++col) printf("%4d", num[ri][col]); printf("\n"); } #include void main(void) { int val[3][4] = {8,16,9,52, 3,15,27,6, 14,25,2,10}; } void mostra(int[3][4]); /* prototipo */ mostra(val); /* chiamata */

22 #include void main(void) { int val[3][4] = {8,16,9,52, 3,15,27,6, 14,25,2,10}; void mostra(int[3][4]); mostra(val); } void mostra(int num[3][4]) { int ri, col; for (ri = 0; ri < 3; ++ri) { for(col= 0; col < 4; ++col) printf("%4d", num[ri][col]); printf("\n"); }

23 Ecco luscita prodotta: Il programma crea un solo vettore, conosciuto come val in main() e come num in mostra(), per cui val[0][2] e num[0][2] si riferiscono allo stesso elemento.

24 Osservazione. Anche adesso la dichiarazione degli argomenti per num contiene uninformazione non necessaria alla funzione, ossia il numero di righe del vettore. Perciò è corretta anche lintestazione di funzione mostra(int num[][4]) La ragione per cui il numero di colonne vada indicato, mentre quello di righe è opzionale diventa ovvia quando si consideri che tutti gli elementi del vettore sono memorizzati in memoria in modo sequenziale, a partire dallelemento val[0][0]. Per accedere a un singolo elemento del vettore val, il computer si sposta di un certo numero di byte, detto offset, a partire dallinizio del vettore, ossia aggiunge allindirizzo della locazione iniziale del vettore un opportuno offset.

25 Per calcolare tale offset, supponiamo che: un intero richieda due byte di memoria, si voglia accedere allelemento val[1][3]. Come risulta dal seguente schema, loffset è in questo caso di 14 byte.

26 Componente massima. Questi concetti sono applicati nel seguente programma, dove si chiama una funzione che trova la componente di valore massimo di un vettore: #include void main(void) { int num[5] = {2, 18, 1, 27, 16}; void trova_max(int [5]); /*prototipo*/ trova_max(num); } { int i, max = val[0]; for (i = 1; i <= 4; ++i) if (max < val[i]) max = val[i]; printf("Il valore massimo è %d", max); } void trova_max(int val[5])

27 Osservazioni. 1. Il prototipo della funzione trova_max allinterno di main() dichiara che trova_max non restituisce un valore, in quanto la stampa del massimo è effettuata dalla funzione chiamata. 2. Il programma crea un solo vettore, che in main() è noto come num, in trova_max è noto come val. Come mostra la figura, entrambi i nomi si riferiscono allo stesso vettore.

28 Passaggio di vettori (2). La dichiarazione degli argomenti nella intestazione di trova_max() contiene in effetti uninformazione aggiuntiva non necessaria alla funzione. Tutto ciò che trova_max() deve sapere è che largomento val si riferisce a un vettore di interi; dato che esso è stato creato in main() e non richiede spazio aggiuntivo in trova_max(), la dichiarazione per val può omettere il numero dei suoi elementi. Quindi è corretta anche lintestazione: Questa forma si comprende meglio se si considera che quando si chiama trova_max() le viene passato un solo parametro, ossia lindirizzo di partenza del vettore num, che è &num[0] per tale ragione nella dichiarazione per il vettore val non è necessario inserire il numero dei suoi elementi. trova_max(int val[])

29 In effetti: nella dichiarazione degli argomenti è meglio omettere il numero degli elementi di un vettore Perciò la forma più generale della funzione trova_max è quella usata nel programma seguente; essa dichiara che trova_max restituisce un valore intero, si aspetta come argomenti lindirizzo di partenza di un vettore di interi e il numero dei suoi elementi (che usa come limite per la ricerca dellelemento massimo, eseguita dal ciclo for ).

30 printf("Il valore massimo è %d", trova_max(num,5)); } int trova_max(int val[], int num_elem) { int i, max = val[0]; for (i=1 ; i

31 double tempconv(double in_temp) /* intestazione */ { return( (5.0/9.0) * (in_temp ) ); } double tempconv(double); /* prototipo */ #include void main(void) { int cont; double fahren; for (cont = 1; cont <= 4; ++cont) { printf("Scrivi i gradi Fahrenheit: "); scanf("%lf", &fahren); printf("Equiv. Celsius: %6.2f\n\n", ); } tempconv(fahren) Conversione Fahrenheit-Celsius. Il seguente programma chiede di scrivere 4 temperature in gradi Fahrenheit e le converte in gradi Celsius:

32 Osserviamo che la definizione della funzione chiamata potrebbe trovarsi anche prima della funzione chiamante, e situarla prima o dopo è solo una questione di scelta. Fattoriale ricorsivo. Come altro esempio, osserviamo il programma seguente, che usa una funzione definita dallutente per calcolare il fattoriale di un numero intero secondo la sua definizione ricorsiva.

33 Serie di Fibonacci. Un altro esempio di funzione ricorsiva è costituito dal calcolo della serie di Fibonacci, scoperta dal matematico Leonardo Pisano nel 1202, come soluzione al problema della riproduzione dei conigli in circostanze ideali. Consideriamo una coppia di conigli neonati, maschio e femmina, in grado di riprodursi alletà di un mese, con un periodo di gestazione anchesso di un mese. Perciò, alla fine del secondo mese, la coppia iniziale ha generato una seconda coppia di conigli. Supponiamo che: i conigli non muoiano mai, e che la femmina generi sempre una nuova coppia (maschio e femmina) di conigli ogni mese, a partire dal secondo mese.

34 La domanda che si pose Fibonacci fu: quante coppie di conigli ci saranno dopo un anno? La risposta si trova considerando che: 1. alla fine del 1° mese i due conigli si accoppiano, ma cè ancora 1 sola coppia; 2. alla fine del 2° mese la femmina genera una 1^ coppia, cosicché ci sono 2 coppie; 3. alla fine del 3° mese la femmina di partenza genera una 2^ coppia, cosicché ci sono 3 coppie; 4. alla fine del 4° mese la femmina di partenza genera una 3^ coppia, mentre quella nata 2 mesi prima genera le sua 1^ coppia, cosicché ci sono 5 coppie;

35

36 Perciò il numero di coppie di conigli allinizio di ogni mese è: 1, 1, 2, 3, 5, 8, 13, 21, 34,... Questa successione di numeri interi si può generare in modo ricorsivo, anzi essa costituisce la prima sequenza numerica ricorsiva conosciuta in Europa. Infatti ogni termine è uguale alla somma dei due precedenti: fibo(n) = fibo(n-1) + fibo(n-2) La successione ha riscontri in vari fenomeni naturali e descrive, tra laltro, una particolare forma di spirale. Inoltre il rapporto tra un termine di Fibonacci e il precedente converge verso il valore 1, (o verso il suo reciproco 0, , se si considera il rapporto tra un termine e il successivo). Anche questo numero ha diversi riscontri in natura, ed è stato definito rapporto aureo o divina proportione.

37 Il programma seguente genera il termine n- esimo della successione di Fibonacci, a partire dal valore di n, chiamando una funzione ricorsiva: #include long fibo(int); int main() { int numero; long risult; printf("Scrivi un numero: "); scanf("%d", &numero); risult = fibo(numero); printf("Fibonacci( %d ) = %ld\n", numero, risult); } long fibo(int n) { if (n==0 || n==1) return n; else return fibo(n-1)+fibo(n-2); }

38 Scrivi un numero: 0 Fibonacci( 0 ) = 0 Scrivi un numero: 1 Fibonacci( 1 ) = 1 Scrivi un numero: 2 Fibonacci( 2 ) = 1 Scrivi un numero: 3 Fibonacci( 3 ) = 2 Scrivi un numero: 4 Fibonacci( 4 ) = 3 Scrivi un numero: 5 Fibonacci( 5 ) = 5 Scrivi un numero: 6 Fibonacci( 6 ) = 8 Ecco la sua uscita:

39 #include long fibo(int); int main() { int i, numero; printf("Scrivi un numero: "); scanf("%d", &numero); for (i = 0; i <= numero; i++) printf("\nFibonacci( %2d ) = %8ld", i, fibo(i)); } long fibo(int n) { if (n==0 || n==1) return n; else return fibo(n-1)+fibo(n-2); } Una leggera modifica del programma precedente permette di stampare tutti i termini della successione di Fibonacci fino a quello di numero dordine indicato dallutente.

40 Serie di Fibonacci. Calcolo iterativo. La precedente definizione ricorsiva della funzione fibo() è interessante perché, a ogni sua chiamata, essa chiama se stessa due volte. Vediamo quante volte la funzione è chiamata per piccoli valori del numero dordine dellelemento richiesto.

41 La chiamata della funzione numerose volte, o per elevati valori del numero dordine, appesantisce quindi lesecuzione di un programma. Perciò può essere utile scriverne una versione iterativa. Conviene scrivere dapprima un programma che calcoli e stampi una tabella di numeri di Fibonacci in modo iterativo, come il seguente, che usa un vettore di interi lunghi per memorizzare i numeri, un ciclo for per il calcolo e un ciclo for per la stampa. #include main() { long fib[24]; int i; fib[0] = 0; fib[1] = 1; for(i = 2; i < 24; i++) fib[i] = fib[i-1] + fib[i-2]; for (i = 0; i < 24; i++) printf("Fibonacci( %2d ) = %8ld\n", i, fib[i]); }

42 Adesso modifichiamo leggermente questo programma, in modo che, come prima, chieda il numero dordine di un elemento della successione e lo stampi. #include main() { int i, numero; printf("Scrivi il n° d'ordine: "); scanf("%d", &numero); long fib[numero]; fib[0] = 0; fib[1] = 1; for (i = 2; i <= numero; i++) fib[i] = fib[i-1] + fib[i-2]; printf("Fibonacci( %d ) = %ld\n", numero, fib[numero]); }

43 Naturalmente le funzioni possono operare anche sulle liste concatenate di strutture, ad es. per compiere un ciclo entro unintera lista per stamparne i valori. Il programa seguente definisce e popola la lista concatenata di strutture vista in precedenza, quindi chiama la funzione mostra() per visualizzarne gli elementi. La funzione mostra() contiene un ciclo while che usa gli indirizzi contenuti nel membro puntatore di ogni struttura per compiere un ciclo attraverso la lista e visualizzare in successione i dati contenuti in ogni struttura.

44 #include struct Tipo_tel { char nome[30]; char num_tel[15]; struct Tipo_tel *prossindir; }; void main(void) { struct Tipo_tel t1 = {"Aloisi, Sandro"," "}; struct Tipo_tel t2 = {"Dolan, Edith"," "}; struct Tipo_tel t3 = {"Lisi, Giovanni"," "}; struct Tipo_tel *primo; } void mostra(struct Tipo_tel *); /* prototipo */ primo = &t1; t1.prossindir=&t2; t2.prossindir=&t3; t3.prossindir=NULL; mostra(primo); /*chiamata di funzione*/

45 void mostra(struct Tipo_tel *contenuto) /*intestazione*/ { while (contenuto != NULL) { printf("%-30s %-20s\n",contenuto->nome,contenuto->num_tel); contenuto=contenuto->prossindir; } return; } Ecco luscita prodotta: Aloisi, Sandro Dolan, Edith Lisi, Giovanni

46 Osservazioni. Il programma precedente illustra luso degli indirizzi contenuti in una struttura per accedere ai membri della struttura che la segue nella lista. Quando si chiama la funzione mostra(), le viene passato il valore memorizzato nella variabile primo ; dato che primo è una variabile puntatore, il valore effettivamente passato è un indirizzo (quello della struttura t1 ). mostra() memorizza il valore passatole nellargomento contenuto. Per una corretta memorizzazione dellindirizzo passato, contenuto è dichiarato come un puntatore a una struttura di tipo Tipo_tel. mostra() esegue un ciclo while attraverso le strutture concatenate, a partire da quella il cui indirizzo è in contenuto. La condizione controllata nellistruzione while confronta il valore che si trova in contenuto (che è un indirizzo) con il valore NULL.

47 Per ogni indirizzo valido: sono visualizzati i membri nome e numero di telefono della struttura indirizzata, quindi lindirizzo che si trova in contenuto viene aggiornato con quello che si trova nel membro puntatore della struttura corrente, quindi si controlla di nuovo lindirizzo in contenuto, e il processo continua fino a quando lindirizzo non sia uguale al valore NULL. mostra() non sa nulla circa i nomi delle strutture dichiarate in main(), e neppure quante strutture esistano, ma si limita a compiere dei cicli attraverso la lista concatenata, struttura per struttura, fino a che incontra lindirizzo NULL di fine lista. Dato che il valore di NULL è zero, la condizione controllata può essere sostituita dallespressione equivalente !contenuto. Uno svantaggio del programma precedente è che in main() sono definite per nome esattamente tre strutture, per le quali viene riservata memoria in fase di compilazione. Se fosse necessaria una quarta struttura, essa andrebbe dichiarata e il programma ricompilato.

48 Vedremo più avanti come fare allocare e rilasciare dinamicamente al computer la memoria per le strutture in fase di esecuzione, via via che serva memoria. La memoria per una nuova struttura verrà creata solo quando si debba aggiungere una nuova struttura alla lisa, e mentre il programma è in esecuzione. Analogamente, quando una struttura non è più necessaria e può essere cancellata dalla lista, la memoria per un record cancellato sarà rilasciata e restituita al computer.


Scaricare ppt "Funzioni definite dallutente Scopo delle funzioni del C, sia fornite dal linguaggio (di biblioteca) sia scritte dallutente, è quello di ricevere in ingresso."

Presentazioni simili


Annunci Google