Consideriamo l esercizio assegnato la scorsa lezione per rappresentare il libretto di uno studente. Per memorizzare i dati si sono utilizzati tre array: char* nomiesami[max ESAMI] Array dei nomi degli esami (MAX ESAMI è il massimo numero degli esami). int cfu[max ESAMI] Array con i crediti degli esami. int voti[max ESAMI] Array con i voti degli esami. Vogliamo ora migliorare la rappresentazione usando un unico array esami.
La struttura esame Un esame può essere rappresentato da una terna ( nome, cfu, voto ) dove nome è il nome dell esame (una stringa), cfu sono i crediti (un intero) e voto è il voto (un intero). Per rappresentare un esame usiamo la struttura struct esame, avente i tre campi nome, cfu e voto. struct esame{ char *nome; int cfu; int voto; }; // nome // crediti // voto Il campo nome è un puntatore all array di char contenente il nome dell esame.
L array esami Per memorizzare gli esami usiamo il seguente array esami: struct esame* esami[max_esami]; Per ogni k 0, esami[k] punta a una struttura che rappresenta il k-esimo esame inserito. La struttura associata a un esame va creata dinamicamente usando calloc.
L array esami Esempio Supponiamo che gli esami inseriti siano ("mat 1", 12, 28) ("prog 1 ", 12, 26) ("fisica", 6, 27) Allora: esami[0] punta a una struttura che rappresenta l esame il cui nome è "mat 1", i crediti sono 12 e il voto 28; esami[1] punta a una struttura che rappresenta l esame il cui nome è "prog 1", i crediti sono 12 e il voto 26; esami[2] punta a una struttura che rappresenta l esame il cui nome è "fisica", i crediti sono 6 e il voto 27.
L array esami 12 28 m a t 1 \0 esami[0] esami[1] esami[2]... 12 26 6 27 p r o g 1 \0 f i s i c a \0
La funzione newesame La funzione struct esame* newesame(char* nome, int cfu, int voto) crea una nuova struttura che rappresenta un esame il cui nome, crediti e voto sono specificati dai parametri e restituisce l indirizza della nuova struttura. Le operazioni da compiere sono: Creazione di una nuova struttura di tipo struct esame. Occorre definire una variabile newes per memorizzare l indirizzo della nuova struttura (notare che newes deve avere tipo struct esame*). struct esame* newes; newes = malloc(sizeof(struct esame)); Ora newes punta alla struttura creata da malloc.
La funzione newesame Inizializzazione dei campi della nuova struttura. Per i crediti e i voti occorre eseguire gli assegnamenti newes->cfu = cfu; newes->voto = voto; L espressione newes->cfu equivale a (*newes).cfu e denota il campo cfu della struttura a cui newes si riferisce. La prima istruzione assegna al campo cfu della nuova struttura il valore di cfu (secondo parametro della funzione). La seconda istruzione assegna al campo voto della nuova struttura il valore di voto (terzo parametro della funzione).
La funzione newesame Per inizializzare il campo nome della nuova struttura, occorre creare dinamicamente un nuovo array, da assegnare a newes->nome, e copiare in esso il nome dell esame (stringa a cui punta il parametro nome della funzione). newes->nome = calloc(strlen(nome)+1, sizeof(char)); strcpy(newes->nome, nome); Infine, la funzione deve restituire l indirizzo della nuova struttura (valore della variabile newes). return newes;
La funzione newesame struct esame* newesame(char* nome, int cfu, int voto){ struct esame* newes; newes = malloc(sizeof(struct esame)); newes->nome = calloc(strlen(nome)+1, sizeof(char)); strcpy(newes->nome, nome); newes->cfu = cfu; newes->voto = voto; return newes; }
Lettura e inserimento di un esame Per leggere un esame occorre compiere le seguenti operazioni: Vanno letti da standard input nome, crediti e voto dell esame. Per leggere il nome dell esame (che può contenere spazi) va usata una apposita funzione, ad esempio la funzione readesame definita la scorsa lezione. Quando i dati relativi a un esame sono stati letti, va creata una nuova struttura esame usando la funzione newesame in cui vanno inseriti i dati letti. Occorre assegnare poi a esame[k] l indirizzo della nuova struttura (k vale inizialmente 0 e va incrementato dopo ogni inserimento).
La funzione main int main(void){ struct esame* esami[max_esami]; char nome[max_len + 1]; // array usato per leggere il nome di un esame int c,k,cfu,voto; c = getchar(); // leggi il primo carattere k = 0; while( c!= q ){ // termina quando viene letto q switch(c){ case + : // inserisci un esame readesame(nome); // legge il nome dell esame scanf("%d%d", &cfu, &voto ); esami[k] = newesame(nome,cfu,voto); k++; break; case p :... // stampa libretto }// end switch c = getchar(); // leggi prossimo carattere }// end while return 0; }// end main
Esempio di esecuzione Supponiamo che l esame da inserire sia ("mat 1", 12, 28). Nella funzione main, dopo la lettura dei dati di input viene eseguita la chiamata newesame(nome,cfu,voto) Il primo argomento è l indirizzo dell array nome definito in main, che contiene il nome dell esame; Il secondo argomento è 12 (valore variabile cfu di main); Il terzo argomento è 28 (valore variabile voto di main).
Esempio di esecuzione newesame nome cfu 12 voto 28 newes main nome[0] m nome[1] a nome[2] t nome[3] nome[4] 1 nome[5] \0... esami[0] esami[1]...
Esempio di esecuzione Viene creata una nuova struttura esame e assegnata a newes newesame nome cfu 12 voto 28 newes main nome[0] m nome[1] a nome[2] t nome[3] nome[4] 1 nome[5] \0... esami[0] esami[1]...
Esempio di esecuzione L istruzione newes->nome = calloc(strlen(nome)+1,sizeof(char)); crea un nuovo array di 6 elementi ( strlen("mat 1") vale 5) e il suo indirizzo viene assegnato a newes->nome (ossia, al campo nome della struttura a cui la variabile newes punta). L istruzione strcpy(newes->nome, nome); copia nel nuovo array la stringa "mat 1". Infine, le istruzioni newes->cfu = cfu; newes->voto = voto; assegnano ai campi cfu e voto dell nuova struttura i valori delle variabili (parametri) cfu e voto definite in newesame.
Esempio di esecuzione newesame nome cfu 12 voto 28 newes m a t 1 \0 main nome[0] m nome[1] a nome[2] t nome[3] nome[4] 1 nome[5] \0... esami[0] esami[1]... 12 28
Esempio di esecuzione La funzione newesame termina l esecuzione con l istruzione return newes; che restituisce il valore della variabile newes, ossia l indirizzo della nuova struttura. Il controllo ritorna alla funzione main che completa l esecuzione dell istruzione esami[k] = newesame(nome,cfu,voto); // k vale 0 assegnando a esami[0] l indirizzo restituito dalla chiamata a newesame.
Esempio di esecuzione newesame nome cfu 12 voto 28 newes m a t 1 \0 main nome[0] m nome[1] a nome[2] t nome[3] nome[4] 1 nome[5] \0... esami[0] esami[1]... 12 28
Esempio di esecuzione Quando il record di attivazione di newesame è tolto dalla memoria, l array esami è: esami[0] esami[1] esami[2]... 12 28 m a t 1 \0 Esercizio Scrivere il codice della funzione printlibretto(esami, int n) che, dato l array degli esami come primo parametro e il numero n di esami sostenuti come secondo parametro, stampa il libretto.
Deallocazione della memoria Supponiamo di voler cancellare l esame appena inserito. Occorre rilasciare la memoria precedentemente allocata tramite le funzioni malloc e calloc in modo che possa essere riutilizzata per successive allocazioni. La chiamata free(p) rilascia la memoria a cui punta p. Si assume che il valore di p sia l indirizzo di un blocco di memoria precedentemente allocato tramite malloc o calloc.
Deallocazione della memoria Esempio La chiamata free(esami[0]) rilascia lo spazio di memoria occupato dalla struttura a cui esami[0] si riferisce m a t 1 \0 esami[0] esami[1] esami[2]...
Deallocazione della memoria Come si nota dalla figura, ci sono due problemi: (1) esami[0] contiene un indirizzo di memoria non più in uso (dangling reference). (2) È rimasto allocato in memoria il vettore usato per rappresentare la stringa "mat 1" a cui ora non è più possibile accedere. Per risolvere (1) si può porre esami[0] = NULL; // NULL e l indirizzo 0 che segnala che esami[0] non contiene alcun indirizzo valido Il punto (2) non è invece risolubile in quanto non è più noto l indirizzo del blocco di memoria da deallocare.
Deallocazione della memoria Il modo corretto di rilasciare tutta la memoria usata per rappresentare esami[0] è quello di rilasciare prima la memoria occupata dall array contenente la stringa "mat 1" e successivamente la memoria occupata dalla struttura. free(esami[0]->nome); free(esami[0]); esami[0] = NULL; Esercizio Aggiungere al programma un operazione che permetta di eliminare l esame in posizione k. Occorre poi modificare la funzione printlibretto facendo in modo che non vengano stampati gli elementi dell array esami (primo argomento della funzione) per cui esami[k] è NULL.