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.
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():
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.
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.
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(lista
tipi 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).
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.
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.
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.
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.
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.
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:
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.
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 funzione
void 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.
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.
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.
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 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 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).
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.
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])
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.
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.
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.
#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 */
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");
}
}
/* intestazione */
#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");
}
}
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.
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 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 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.
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.
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);
}
void trova_max(int val[5])
{
int i, max = val[0];
for (i = 1; i <= 4; ++i)
if (max < val[i]) max = val[i];
printf("Il valore massimo è %d", max);
}
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.
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:
trova_max(int val[])
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.
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).
#include <stdio.h>
void main(void)
{
int num[5] = {2, 18, 1, 27, 16};
int trova_max(int [], int);
/* prototipo */
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);
}
Conversione Fahrenheit-Celsius. Il seguente programma chiede di
scrivere 4 temperature in gradi Fahrenheit e le converte in gradi
Celsius:
#include <stdio.h>
void main(void)
{
int cont;
double fahren;
double tempconv(double);
/* prototipo */
for (cont = 1; cont <= 4; ++cont)
{
printf("Scrivi i gradi Fahrenheit: ");
scanf("%lf", &fahren);
printf("Equiv. Celsius: %6.2f\n\n", tempconv(fahren)
}
}
double tempconv(double in_temp)
/* intestazione */
{
return( (5.0/9.0) * (in_temp - 32.0) );
}
);
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.
Osserviamo che la definizione della funzione chiamata potrebbe
trovarsi anche prima della funzione chiamante, e situarla prima o
dopo è solo una questione di scelta.
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.
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. . . . . .
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.
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);
}
Ecco la sua uscita:
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
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.
#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);
}
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.
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]);
}
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]);
}
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.
#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*/
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
Dolan, Edith
Lisi, Giovanni
0432 174973
02 385602
0556 390048
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.
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.
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.