Note:
Inserire ampiamente commenti all'interno del sorgente prodotto.
Per il nome del file utilizzare la sintassi:
grXX_4a.c dove XX è il numero del
gruppo (es. 01, 05, 45, 64, etc).
Quindi, ad esempio, il gruppo numero 6 dovrà inviare per
mail al docente un file nominato:
gr06_4a.c
In caso di dubbio circa autori e/o autenticità del materiale
sottomesso, il docente si riserva il diritto di effettuare verifiche ed
eventualmente di annullare gli elaborati.
AGGIORNAMENTO
In merito al problema riscontrato da diversi gruppi nella gestione del file
'rubrica' utilizzato nel progetto, c'è da tenere conto del fatto che
in molte implementazioni i file "binari" devono essere aperti in lettura,
scrittura o aggiornamento utilizzando l'opzione 'b'. Consiglio quindi di
aprire (se il compilatore lo consente) il file in scrittura o modifica utilizzando
l'opzione "wb" o "rb+": in questo modo il programma dovrebbe funzionare correttamente
nei richiami di fread() e fwrite().
Esercizio:
Scrivere un programma per la gestione di una rubrica telefonica
memorizzata su file.
Definire a tal proposito una struttura di questo tipo:
struct scheda{
char cognome[ 15 ];
char nome[ 15 ];
char telefono[ 15 ];
int prossimo;
};
Il file utilizzato per memorizzare i record della rubrica dovrà
essere gestito ad accesso casuale (record a dimensione fissa) e dovrà
avere il seguente formato.
lista lista
numero
record record
record record record ..... record
attivi vuoti
totali n. 1 n. 2
n. M
(int)
(int) (int) (scheda)(scheda)
(scheda)
_________________________________________.....____________
|__LR__|__LV__|__NT__|________|________|_.....__|________|
^
|
inizio file ___|
(fp)
In un certo istante, supponendo che nel file siano presenti M record,
come nello schema di sopra, i primi tre campi del file, tutti di dimensione
pari ad un int, dovranno contenere rispettivamente la posizione all'interno
del file (valori da 1 a M) del primo elemento della lista dei record
attivi (LR), la posizione (valori da 1 a M) del primo elemento della
lista dei record liberi (LV) e il numero totale M di record (NT), sia attivi
che liberi, presenti nel file. In Particolare:
LISTA RECORD ATTIVI
- La lista dei record attivi dovrà essere ordinata sul campo
cognome utilizzando il campo LR e il campo prossimo di ogni record di tale
lista.
- LR conterrà la posizione (valori da 1 a M se ho M record
nel file, vedi schema precedente) all'interno del file del primo record
della lista ordinata per cognome.
- Il campo prossimo del primo record della lista indicherà
la posizione (valori da 1 a M, se ho M record nel file) all'interno del
file del secondo record ordinato per cognome e così via.
- L'ultimo record della lista avrà il campo prossimo settato
a zero.
- Se non esistono record attivi il campo LR varrà zero (lista
vuota).
LISTA RECORD VUOTI
- Tale lista servirà a gestire l'insieme dei record vuoti
(risultato di precedenti cancellazioni) e quindi riutilizzabili per ulteriori
inserimenti.
- LV conterrà la posizione del primo record all'interno del
file della lista.
- Il campo prossimo del primo record della lista indicherà,
se esiste, la posizione all'interno del file del secondo record libero,
e così via.
- L'ultimo record della lista avrà il campo prossimo settato
a zero.
- Se non esistono record vuoti il campo LV varrà zero (lista
vuota).
Supponendo che il campo LR, LV, o il campo prossimo di
qualunque dei record memorizzati nel file contenga un certo valore
N (relativo alla posizione all'interno del file di un record referenziato
da uno di questi tre campi), per determinare l'indirizzo fisico (in
byte) del record referenziato sarà sufficiente calcolare:
offset = 3 * sizeof(int) + (N-1) * sizeof (scheda)
|______________| |_____________________|
salto i 3 campi
salto gli N-1 record
LR, LV, NT
precedenti
e utilizzare tale offset per posizionare il puntatore a file nel punto
dove inizia tale record mediante la funzione fseek().
Il programma dovrà prevedere delle funzioni di inserimento,
cancellazione e stampa di record.
INSERIMENTO
Tale funzione dovrà richiedere in input i dati della nuova scheda
da inserire. A questo punto l'inserimento di un nuovo record all'interno
del file avverrà:
- Fisicamente in coda al file (utilizzo il campo NT, quindi se ho
già M record aggiungo il record M+1 esimo), se la lista dei record
vuoti è vuota (campo LV=0). E' anche necessario aggiornare il campo
NT, che deve essere posto a M+1.
- Nel record in posizione LV se la lista dei record vuoti contiene
almeno un elemento (LV!=0). In questo caso la lista dei record vuoti deve
essere aggiornata ponendo LV al campo prossimo del record in posizione
LV (equivalente ad una cancellazione in testa dalla lista dei record vuoti).
Una volta inserito 'fisicamente' il record, dovrà essere aggiornato
il campo "prossimo" dei record interessati al fine di gestire l'ordinamento
logico delle varie schede inserite (questo per ottimizzare la funzione di
ricerca di una data scheda). Quindi si dovrà:
- scandire la lista dei record attivi utilizzando il campo prossimo
di ogni record incontrato, fino a trovare la posizione di inserimento,
individuando il record precedente e quello successivo dal punto di vista
dell'ordinamento (e tra cui farò l'inserimento del nuovo record
per mantenere la lista ordinata)
- assegnare al campo prossimo della scheda precedente la posizione
su file del record appena inserito
- assegnare al campo prossimo della scheda appena inserita la posizione
della scheda seguente (vecchio campo prossimo della scheda precedente).
- Dovranno ovviamente essere gestiti anche i casi particolari relativi
ad inserimento in testa e in coda, se il record che deve essere inserito
è il primo o l'ultimo nell'ordinamento per cognome.
In sostanza il campo prossimo della struttura (valori compresi
tra 1 e M se nell'istante considerato ho M record nel file) dovrà
essere utilizzato e gestito per garantire l'ordinamento, esattamente come
nel caso del campo "prossimo" della struttura "nodo" vista a lezione per
gestire le liste.
STAMPA
Tale funzione dovrà stampare su video le informazioni correnti
relative all'archivio: campo LR, LV, NT e, formattati in modo tabellare,
i vari record attivi secondo l'ordinamento per cognome (seguendo quindi
l'informazione del campo prossimo e utilizzandola per posizionarsi fisicamente
sul record seguente) nella forma COGNOME, NOME, TELEFONO, PROSSIMO.
CANCELLAZIONE
Tale funzione dovrà richiedere in input un cognome e scandire
i record secondo l'ordinamento logico relativo a tale campo (seguendo quindi
l'informazione del campo prossimo e utilizzandola per posizionarsi fisicamente
sul record seguente), cancellando le informazioni della scheda associata
nel caso in cui questa venga trovata o stampando un messaggio informativo
nel caso tale scheda non sia presente nell'archivio.
La cancellazione dall'archivio dovrà essere fatta nel seguente
modo:
- Una volta trovato il record da cancellare, dovrà essere
assegnato al campo prossimo della scheda precedente dal punto di vista
dell'ordinamento il campo "prossimo" della scheda da cancellare. Gestire
anche i casi particolari relativi a cancellazione in testa e in coda, se
il record che deve essere cancellato è il primo o l'ultimo nell'ordinamento
per cognome.
- Azzerare su file i campi nome, cognome, telefono, relativi al
record appena eliminato.
- Inserire il record appena eliminato nella lista dei record vuoti,
operando un inserimento in testa in tale lista: il campo prossimo del record
in questione dovrà essere posto a LV e LV dovrà essere posto
alla posizione di tale record.
- Da notare come il campo NT non debba essere aggiornato (la somma
dei record liberi e di quelli attivi resta sempre la medesima, ossia M).
Utilizzare, oltre alle funzioni di printf(), scanf() per la stampa
di messaggi e l'input dei campi da inserire, le funzioni:
- strcmp() per il confronto di stringhe, al fine di gestire l'ordinamento
delle schede.
- fopen() per aprire il file, in modalità "r+" (aggiornamento/modifica).
ATTENZIONE: se il file non esiste, deve essere creato, creando i campi
LR, LV e NT.
- fwrite() per scrivere un nuovo record;
- fread() per leggere un record già esistente;
- fseek() per posizionarsi fisicamente sul file in una certa posizione,
utilizzando l'informazione di posizione rappresentata dai valori dei campi
LR, LV, o prossimo del record precedente nell'ordinamento.
ATTENZIONE: ricordarsi di tenere conto dei campi LR, LV, NT posti all'inizio
del file per calcolare l'offset di fseek(), come già spiegato in
precedenza !!!
- flose() per chiudere il file al termine delle operazioni.
- qualunque altra funzione ANSI C si renda necessario utilizzare.