48
Funzioni definite dall’utente Scopo delle funzioni del C, sia fornite dal linguaggio (di biblioteca) sia scritte dall’utente, è 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 dall’utente, ricordiamo quanto già visto sulla chiamata e l’uso 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.

Funzioni definite dall’utente

  • Upload
    gilead

  • View
    55

  • Download
    1

Embed Size (px)

DESCRIPTION

Funzioni definite dall’utente Scopo delle funzioni del C, sia fornite dal linguaggio ( di biblioteca ) sia scritte dall’utente, è quello di ricevere in ingresso uno o più valori, operare su essi e restituire direttamente un singolo valore in uscita:. - PowerPoint PPT Presentation

Citation preview

Page 1: Funzioni  definite  dall’utente

Funzioni definite dall’utente

Scopo delle funzioni del C, sia fornite dal linguaggio (di biblioteca) sia scritte dall’utente, è 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 dall’utente, ricordiamo quanto già visto sulla chiamata e l’uso 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.

Page 2: Funzioni  definite  dall’utente

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():

Page 3: Funzioni  definite  dall’utente

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.

Page 4: Funzioni  definite  dall’utente

Dichiarazione (prototipo) di funzione. Prima che una funzione possa essere chiamata deve essere dichiarata, all’interno della funzione chiamante, da un’istruzione detta prototipo di funzione. Essa dichiara, nell’ordine:

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.

Page 5: Funzioni  definite  dall’utente

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

l’istruzione #include <stdio.h>.La forma generale dell’istruzione prototipo di funzione è:

tipo-dati-restituito nome-funzione(listatipi dati degli argomenti);

Il tipo-dati-restituito dalla funzione deve corrispondere al tipo dati che sarà usato nella sua linea d’intestazione.

Analogamente, la lista tipi dati degli argomenti deve corrispondere a quelli che saranno usati nella definizione della funzione.

L’uso 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 d’intestazione della funzione, viene visualizzato un messaggio di errore (tipicamente TYPE MISMATCH).

Page 6: Funzioni  definite  dall’utente

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 nell’esempio seguente:

Se uno degli argomenti in una chiamata di funzione è una varabile, la funzione chiamata riceve una copia del valore memorizzato nella variabile.

Page 7: Funzioni  definite  dall’utente

Ad es., l’istruzione:

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.

Page 8: Funzioni  definite  dall’utente

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.

Page 9: Funzioni  definite  dall’utente

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.

Page 10: Funzioni  definite  dall’utente

Definizione. Analogamente a main(), ogni funzione C consiste in due parti: un’intestazione e un corpo.

Scopo dell’intestazione è: 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.

Page 11: Funzioni  definite  dall’utente

Linea d’intestazione. L’intestazione di una funzione consiste in una singola linea che contiene, nell’ordine:

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 è un’istruzione, ma l’inizio del codice che definisce la

funzione, la linea d’intestazione termina senza “;”Ad es., la seguente linea d’intestazione di funzione dichiara:

Page 12: Funzioni  definite  dall’utente

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 dichiaratorefloat 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.

Page 13: Funzioni  definite  dall’utente

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.All’interno 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., l‘intestazione di funzionevoid display(int x, double y)

dichiara che la funzione display() non restituisce alcun valore, mentre l’intestazione di funzione

double stampa_messaggio(void)dichiara che la funzione stampa_messaggio() non ha parametri, ma restituisce un valore di tipo double.

Page 14: Funzioni  definite  dall’utente

Corpo. Dopo avere scritto l’intestazione 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 s’incontra l’istruzione return, viene calcolata l’espressione entro parentesi, quindi il suo valore: viene convertito automaticamente nel tipo dati dichiarato all’inizio

della linea d’intestazione della funzione e viene restituito alla funzione chiamante.

Page 15: Funzioni  definite  dall’utente

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 nell’istruzione return il tipo dati della variabile restituita corrisponde esattamente al tipo dati nella linea d’intestazione della funzione.

Ciò deve avvenire per ogni funzione che restituisca un valore.

Il programma seguente inserisce la funzione trova_max all’interno del codice di programma presentato in precedenza.

Page 16: Funzioni  definite  dall’utente
Page 17: Funzioni  definite  dall’utente

Passaggio di vettori (1). Per passare a una funzione singoli elementi di un vettore, essi vanno inseriti come variabili con indici nell’elenco degli argomenti nella chiamata alla funzione.

Ad es., la chiamata di funzionetrova_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 nell’elenco 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).

Page 18: Funzioni  definite  dall’utente

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 l’esecuzione di una copia completa e separata di tutte le sue componenti; nel caso di vettori grandi l’esecuzione 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.

Page 19: Funzioni  definite  dall’utente

In questa intestazione il nome dell’argomento (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);L’intestazione della funzione chiamata potrebbe allora essere:

void trova_max(int val[5])

Page 20: Funzioni  definite  dall’utente

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 all’intera matrice.Ad es., la chiamata mostra(val); rende disponibile l’intera 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à un’intestazione del tipo:

char ottieni(char chiave[26][9])In essa il nome dell’argomento (chiave) è locale alla funzione, e tuttavia si riferisce ancora alla matrice originaria creata fuori da essa.

Page 21: Funzioni  definite  dall’utente

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 <stdio.h>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 */

Page 22: Funzioni  definite  dall’utente

#include <stdio.h>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"); }}

Page 23: Funzioni  definite  dall’utente

Ecco l’uscita 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.

Page 24: Funzioni  definite  dall’utente

Osservazione. Anche adesso la dichiarazione degli argomenti per num contiene un’informazione non necessaria alla funzione, ossia il numero di righe del vettore.

Perciò è corretta anche l’intestazione di funzionemostra(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 dall’elemento 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 dall’inizio del vettore, ossia aggiunge all’indirizzo della locazione iniziale del vettore un opportuno offset.

Page 25: Funzioni  definite  dall’utente

Per calcolare tale offset, supponiamo che:• un intero richieda due byte di memoria,• si voglia accedere all’elemento val[1][3].Come risulta dal seguente schema,

l’offset è in questo caso di 14 byte.

Page 26: Funzioni  definite  dall’utente

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 <stdio.h>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])

Page 27: Funzioni  definite  dall’utente

Osservazioni.1. Il prototipo della funzione trova_max all’interno 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.

Page 28: Funzioni  definite  dall’utente

Passaggio di vettori (2). La dichiarazione degli argomenti nella intestazione di trova_max() contiene in effetti un’informazione aggiuntiva non necessaria alla funzione.

Tutto ciò che trova_max() deve sapere è che l’argomento 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 l’intestazione:

Questa forma si comprende meglio se si considera che quando si chiama trova_max() le viene passato un solo parametro, ossia l’indirizzo 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[])

Page 29: Funzioni  definite  dall’utente

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 l’indirizzo di partenza di un vettore di

interi e il numero dei suoi elementi (che usa come limite per la ricerca dell’elemento massimo, eseguita dal ciclo for).

Page 30: Funzioni  definite  dall’utente

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<num_elem ; ++i) if(max < val[i]) max = val[i]; return(max);}

#include <stdio.h>void main(void){ int num[5] = {2, 18, 1, 27, 16}; int trova_max(int [], int); /* prototipo */

Page 31: Funzioni  definite  dall’utente

double tempconv(double in_temp) /* intestazione */{ return( (5.0/9.0) * (in_temp - 32.0) );}

double tempconv(double); /* prototipo */

#include <stdio.h>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:

Page 32: Funzioni  definite  dall’utente

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 dall’utente per calcolare il fattoriale di un numero intero secondo la sua definizione ricorsiva.

Page 33: Funzioni  definite  dall’utente

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 all’età di un mese, con un periodo di gestazione anch’esso 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.

Page 34: Funzioni  definite  dall’utente

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;

5. . . . . .

Page 35: Funzioni  definite  dall’utente
Page 36: Funzioni  definite  dall’utente

Perciò il numero di coppie di conigli all’inizio 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 l’altro, una particolare forma di spirale. Inoltre il rapporto tra un termine di Fibonacci e il precedente converge verso il valore

1,61803 39887...(o verso il suo reciproco 0,61803 39887, 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.

Page 37: Funzioni  definite  dall’utente

Il programma seguente genera il termine n-esimo della successione di Fibonacci, a partire dal valore di n, chiamando una funzione ricorsiva:

#include <stdio.h>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);}

Page 38: Funzioni  definite  dall’utente

Scrivi un numero: 0Fibonacci( 0 ) = 0

Scrivi un numero: 1Fibonacci( 1 ) = 1

Scrivi un numero: 2Fibonacci( 2 ) = 1

Scrivi un numero: 3Fibonacci( 3 ) = 2

Scrivi un numero: 4Fibonacci( 4 ) = 3

Scrivi un numero: 5Fibonacci( 5 ) = 5

Scrivi un numero: 6Fibonacci( 6 ) = 8

Ecco la sua uscita:

Page 39: Funzioni  definite  dall’utente

#include <stdio.h>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 d’ordine indicato dall’utente.

Page 40: Funzioni  definite  dall’utente

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 d’ordine dell’elemento richiesto.

Page 41: Funzioni  definite  dall’utente

La chiamata della funzione numerose volte, o per elevati valori del numero d’ordine, appesantisce quindi l’esecuzione 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 <stdio.h>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]);}

Page 42: Funzioni  definite  dall’utente

Adesso modifichiamo leggermente questo programma, in modo che, come prima, chieda il numero d’ordine di un elemento della successione e lo stampi.

#include <stdio.h>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]);}

Page 43: Funzioni  definite  dall’utente

Naturalmente le funzioni possono operare anche sulle liste concatenate di strutture, ad es. per compiere un ciclo entro un’intera 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.

Page 44: Funzioni  definite  dall’utente

#include <stdio.h>struct Tipo_tel{ char nome[30]; char num_tel[15]; struct Tipo_tel *prossindir;};void main(void){ struct Tipo_tel t1 = {"Aloisi, Sandro","0432 174973"}; struct Tipo_tel t2 = {"Dolan, Edith","02 385602"}; struct Tipo_tel t3 = {"Lisi, Giovanni","0556 390048"}; 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*/

Page 45: Funzioni  definite  dall’utente

void mostra(struct Tipo_tel *contenuto) /*intestazione*/{ while (contenuto != NULL) { printf("%-30s %-20s\n",contenuto->nome,contenuto->num_tel); contenuto=contenuto->prossindir; } return;}

Ecco l’uscita prodotta:

Aloisi, Sandro 0432 174973Dolan, Edith 02 385602Lisi, Giovanni 0556 390048

Page 46: Funzioni  definite  dall’utente

Osservazioni. Il programma precedente illustra l’uso 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 nell’argomento contenuto. Per una corretta memorizzazione dell’indirizzo 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 nell’istruzione while confronta il valore che si trova in contenuto (che è un indirizzo) con il valore NULL.

Page 47: Funzioni  definite  dall’utente

Per ogni indirizzo valido:• sono visualizzati i membri nome e numero di telefono della struttura

indirizzata, quindi• l’indirizzo che si trova in contenuto viene aggiornato con quello

che si trova nel membro puntatore della struttura corrente, quindi• si controlla di nuovo l’indirizzo in contenuto, e il processo continua

fino a quando l’indirizzo 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 l’indirizzo NULL di fine lista. Dato che il valore di NULL è zero, la condizione controllata può essere sostituita dall’espressione 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.

Page 48: Funzioni  definite  dall’utente

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.