INFORMATICA I file File • Per rendere più uniforme possibile il comportamento dei programmi in relazione alla grande varietà dei dispositivi periferici, i linguaggi moderni fanno riferimento ad un “modello”: i periferici sono visti come file o flusso sequenziale, cioè come una struttura dati. • Tuttavia nell’accezione più comune del termine il file è un archivio di dati memorizzato su un dispositivo periferico, normalmente il disco magnetico. • Un file è una sequenza, teoricamente illimitata, di componenti tutti dello stesso tipo. E’ una struttura ad accesso sequenziale: per accedere all'n-esimo elemento occorre accedere prima agli n-1 elementi che lo precedono. © Piero Demichelis 2 File • In C i periferici sono visti come streams, cioè sequenze di byte che fluiscono verso il programma (input streams) o vengono generati dal programma (output streams). • In particolare in C risultano predefiniti alcuni valori ai quali fanno riferimento diretto le funzioni di I/O: - stdin : periferico standard di input (associato alla tastiera); - stdout : periferico standard di output (associato al monitor); - stderr : periferico su cui inviare i messaggi d’errore (generalmente associato al monitor). © Piero Demichelis 3 File • I file si distinguono in due categorie: file di tipo testo e file binari. • Nei file di tipo testo i dati, che all’interno dell'elaboratore sono codificati in binario, vengono trasferiti all'esterno come sequenze di caratteri, dopo aver subito una conversione (da floating point a decimale, da complemento a 2 a decimale, ecc.). • Nei file binari i dati sono trasferiti all'esterno come pacchetti di bit senza alcuna manipolazione (i dati sono pertanto un’immagine perfetta della loro rappresentazione in memoria). © Piero Demichelis 4 File • Mentre i file di tipo testo si possono visualizzare e stampare, i file binari si possono solo conservare e rileggere. • Nei file di tipo testo sono previste informazioni esplicite di “fine linea ” (new-line, lo stesso carattere utilizzato per l’output sul monitor) e di “fine file ” (end-of-file) • Sostanzialmente scrivere su un file di tipo testo è assai simile a scrivere sul monitor così come leggere da un file di testo corrisponde a leggere da tastiera. © Piero Demichelis 5 Funzione fopen • Per poter operare sui file, siano essi dispositivi periferici o file su disco, occorre instaurare una connessione che renda possibile la comunicazione tra programma e file: questa predisposizione è detta apertura del file. • Lo standard del C prevede che gli stream standard (stdin, stdout, stderr ) siano sempre predisposti e non necessitino quindi di essere “aperti”. • Gli altri file possono essere utilizzati solo dopo averli aperti mediante la funzione di libreria fopen la quale effettua l'aggancio con il file e restituisce un certo numero di informazioni inerenti il file stesso, in una struttura dati a cui si può accedere tramite un puntatore. © Piero Demichelis 6 Funzione fopen • Per questo, prima di chiamare la fopen, occorre dichiarare un “puntatore al file”, utilizzando una struttura predefinita in stdio.h come nel seguente esempio: FILE *miofile; dove miofile è un puntatore ad un file. • FILE deve essere scritto in maiuscolo: in realtà si tratta di una struttura definita nel file stdio.h. • La chiamata della fopen ha la seguente forma: fopen ("nome_file ", "tipo_apertura"); © Piero Demichelis 7 Funzione fopen • “nome_file ” è una stringa contenente il nome del file nel formato richiesto dal sistema che si utilizza (ad esempio, per MSDOS è un nome preceduto dal “path ”, come "C:\\WORK\\MIOFILE.DAT", dove il doppio backslash è la notazione richiesta dal C per il singolo backslash); • “tipo_apertura ” è una stringa che specifica il tipo di azioni che si vogliono compiere sul file e può assumere i valori seguenti: r : apre un file già esistente in lettura (for reading). w : crea ed apre un file in scrittura (for writing): se il file non esiste, lo crea; se esiste, lo rimpiazza (attenzione: il contenuto del file precedente viene irrimediabilmente perso); a : apre un file per aggiungere dati (for appending): i dati vengono aggiunti in coda ad un file esistente oppure viene creato un nuovo file; © Piero Demichelis 8 Funzione fopen r+ : apre un file in lettura e scrittura; w+ : crea ed apre un file in lettura e scrittura: se il file esiste già, viene rimpiazzato; a+ : apre un file per leggere ed aggiungere: i dati vengono aggiunti in coda ad un file esistente oppure viene creato un nuovo file. • La fopen restituisce l'indirizzo di una struttura di tipo FILE (puntatore al file ) se l'operazione è andata bene; se invece si è verificato qualche inconveniente (file inesistente, disco protetto in scrittura, disco pieno, ecc.), restituisce un puntatore con valore NULL. © Piero Demichelis 9 Funzione fopen • Esempio di apertura di un file: FILE *inp_file; ........................... /* dichiarazione del puntatore al file */ /* apertura: se capita errore scrive il messaggio su stderr */ if ((inp_file = fopen("C:\\WORK\\MIOFILE.DAT", "r") == NULL) { fprintf (stderr, "Errore apertura MIOFILE.DAT in input\n"); /* oppure printf ( “Errore apertura MIOFILE.DAT in input \n”); */ } else /* apertura completata con successo: ora si può operare sul file */ ......................... © Piero Demichelis 10 Funzione fclose • Al termine delle operazioni, è necessario che il file venga “chiuso”: si utilizza per questa operazione la funzione fclose che ha come parametro il puntatore al file. • Sintassi: fclose (puntatore); puntatore : puntatore al file aperto in precedenza con la fopen • La fclose restituisce un valore intero pari a 0 se la chiusura è avvenuta correttamente, pari a EOF in caso di errore. © Piero Demichelis 11 Funzione fclose • Questa funzione non solo libera la struttura che era stata creata per immagazzinare le informazioni relative a quel file, ma effettua le operazioni di “completamento ” e “chiusura ” del file. • Le operazioni di “completamento ” assumono rilevanza quando il file è in scrittura: infatti le operazioni fisiche di scrittura dei dati non vengono eseguite immediatamente: i dati vengono prima immagazzinati in appositi buffer di memoria gestiti dal sistema operativo. • Il trasferimento al dispositivo esterno avviene solo quando in questi buffer si forma almeno un “blocco ” completo di dati da scrivere (ad esempio un settore del disco). © Piero Demichelis 12 Funzione fclose • Può succedere che quando il programma completa le operazioni di scrittura sul file siano ancora presenti in memoria (nei buffer) dei dati che devono essere trasferiti sul disco prima di poter chiudere definitivamente il file “fisico”. • Questa operazione è nota col termine di flush del file: la funzione fclose svolge appunto questo compito. • Le vere e proprie operazioni di “chiusura ” del file sono demandate al sistema operativo (registrazione del file nel proprio direttorio, “rilascio” del file se era bloccato, ecc.). © Piero Demichelis 13 Lettura e scrittura di file di testo • Quando nella fopen manca l'esplicita richiesta che il file sia di tipo binario (aggiunta della lettera b nella stringa “tipo_apertura ”), il file viene aperto by default di tipo testo. • Per leggere e scrivere sui dispositivi non standard, quelli cioè che devono essere aperti con la fopen, e quindi, ovviamente, sui file residenti su disco, si devono usare funzioni con lo stesso nome di quelle degli standard streams ma precedute dalla lettera f. • Pertanto a scanf corrisponde fscanf, a printf corrisponde fprintf, ecc. • Occorre inoltre specificare il puntatore al file su cui devono operare. © Piero Demichelis 14 Formato delle funzioni di I/O su file inp_file = fopen (“nome”, “r”); out_file = fopen (“nome”, “w”); /* apertura in lettura */ /* apertura in scrittura */ fscanf (inp_file, “format", lista variabili); fprintf (out_file, “format”, lista variabili); /* lettura da inp_file */ /* scrittura su out_file */ carattere = fgetc (inp_file); /* lettura di un carattere da inp_file */ fputc (carattere, out_file); /* scrittura di un carattere su out_file */ fgets (stringa, n, inp_file); /* legge una riga (fino a n car.) in stringa */ fputs (stringa, out_file); /* scrive stringa in out_file */ fclose (inp_file); fclose (out_file); /* chiusura dei file */ © Piero Demichelis 15 Funzione feof • Nella lettura di un file, spesso non si conosce con precisione quanti dati contiene. • D’altra parte un tentativo di lettura oltre la fine del file (oltre l’ultimo record) provoca un errore irrimediabile e il programma viene interrotto. • E’ necessario dunque sapere quando ci si deve fermare nella lettura, ovvero quando si sta leggendo l’ultimo record. • A questo scopo il C possiede la funzione feof (puntatore) che restituisce il valore vero quando il puntatore è posizionato sull’EOF, falso in caso contrario. © Piero Demichelis 16 Funzioni di I/O su file • Tutte le funzioni che operano su file richiedono ovviamente il puntatore al file. • Queste funzioni si comportano alla stessa stregua di quelle operanti sugli standard streams, anche nei confronti dei fine linea e fine file. • Non deve trarre in inganno il fatto che il linguaggio tratti le informazioni di fine linea e di fine file come caratteri: ad esempio, non è detto che per tutti i sistemi operativi la terminazione dei file di tipo testo sia costituita proprio da un carattere. • Per questo motivo queste informazioni sono state definite con valori simbolici (EOF, \n)! © Piero Demichelis 17 Esempio • Esempio: programma che richiede il nome di un file, lo crea, lo riempie con i valori interi da 1 a 10 (uno per record) e successivamente ne visualizza il contenuto. #include <stdio.h> #include <stdlib.h> #define NUM_DATI 10 main() { int dato, ndati; FILE *fdati; char nomefile[50]; printf (“\nIntroduci il nome del file: "); scanf ("%s", nomefile); © Piero Demichelis 18 Esempio if ((fdati = fopen (nomefile, "w")) == NULL) /* crea il file */ { printf ("\nErrore apertura del file %s", nomefile); exit (0); } for (dato = 1; dato <= NUM_DATI; dato++) { fprintf (fdati, "%d", dato); /* scrive un dato nel file */ if (dato != NUM_DATI) /* \n per tutte le righe tranne l’ultima */ fprintf (fdati, "\n"); } fclose (fdati); /* chiude il file */ © Piero Demichelis 19 Esempio printf ("\nVisualizza il contenuto del file\n"); if ((fdati = fopen (nomefile, "r")) == NULL) { printf ("\nErrore apertura del file %s", nomefile); exit (1); } while (!feof (fdati)) { fscanf (fdati, "%d", &dato); printf ("%d\n", dato); } /* apre il file */ /* Finché non si raggiunge EOF */ /* legge un dato dal file */ /* visualizza il dato sul monitor */ fclose (fdati); } /* chiude il file */ © Piero Demichelis 20 Schema generale di apertura di un file • Spesso nei programmi deve essere elaborato più di un file. Lo schema proposto in precedenza (a proposito della fopen) per l’apertura potrebbe in qualche caso risultare scomodo. • Ricordando che il main in definitiva è una funzione (l’unica che può avere quel nome), possiamo strutturare l’apertura nel modo seguente: FILE *inp_file; /* dichiarazione del puntatore al file */ ........................... if ((inp_file = fopen (nomefile, "r") == NULL) { printf (“\nErrore in apertura di %s”, nomefile); exit (0); } /* apertura completata con successo: ora si può operare sul file */ © Piero Demichelis 21 Schema generale di lettura da file • Le lettura di un file è in genere regolata mediante un ciclo: leggi un dato dal file; finché (non è finito il file ) { processa il dato; leggi un dato dal file; } • La condizione “non è finito il file ” può essere realizzata in vari modi: - usando i valori restituiti dalle funzioni di input; - usando feof. © Piero Demichelis 22 Schema di lettura da file • Lettura di un file formattato (es. un intero per riga). while ( !feof (fp)) { fscanf (fp, “%d”, &val); elabora il dato (val) } res = fscanf (fp, “%d”, &val); while (res != EOF) { elabora il dato (val) res = fscanf (fp, “%d”, val); } © Piero Demichelis 23 Lettura da file • Sfruttando il fatto che fscanf restituisce il numero di valori letti correttamente è possibile usare quale condizione del while direttamente la funzione o, meglio, il valore che la funzione restituisce, così: while ( fscanf (fp, “%d”, &val) != EOF ) { elabora val; } • In questo modo non è necessario eseguire una lettura preventiva prima di entrare nel ciclo! © Piero Demichelis 24 Esempio • Nel file estremi.dat sono registrate coppie di numeri interi (x, y), una per riga. Leggere le coppie di numeri e scrivere in un secondo file diff.dat le differenze (x – y), una per riga. • Esempio: diff.dat estremi.dat 23 2 19 23 3 … -9 -9 13 18 1 … 32 11 6 5 2 … © Piero Demichelis 25 Esempio #include <stdio.h> int main() { FILE *fpin, *fpout; int x, y; /* apertura del primo file */ if ((fpin = fopen (“estremi.dat”, ”r”)) == NULL) { fprintf (stderr, “Errore nell’apertura di estremi.dat\n”); exit (0); } © Piero Demichelis 26 Esempio /* apertura del secondo file */ if (( fpout = fopen (“diff.dat”, ”w”)) == NULL) { fprintf (stderr, “Errore nell’apertura di diff.dat\n”); exit (1); } while ( fscanf (fpin, “%d%d”, &x, &y) != EOF) { /* ora ho a disposizione x e y */ fprintf (fpout, “%d\n”, x - y); } fclose (fpin); fclose (fpout); } © Piero Demichelis 27 Avvertenza • E’ in generale sbagliato tentare di memorizzare il contenuto di un file in un vettore. • La dimensione (numero di righe o di dati) di un file non è quasi mai nota a priori e un file è, per definizione, una “sequenza illimitata di dati”. • Tuttavia, anche se la dimensione è nota, tipicamente è molto grande. © Piero Demichelis 28 Scrittura di un file • La struttura di scrittura di un file non è diversa dalla struttura di lettura: si tratterà tipicamente di un ciclo che scrive un certo numero di dati ad ogni iterazione. • Non è necessario scrivere EOF. Infatti la “marca” di EOF è automaticamente scritta dal linguaggio alla fine della scrittura del record corrente. • E’ invece importante il formato dei dati. Se si scrive un file è perché prima o poi si desidera rileggerlo! Occorre dunque che i dati siano scritti in modo compatibile con le istruzioni di lettura (ad esempio inserire almeno uno spazio tra un dato e il successivo, ecc.) © Piero Demichelis 29