Oggi parleremo di un argomento semplice e molto utile, la gestione dei File in C. Salvare dei dati su un file è un’operazione frequente e molto utile che vi permette di fare un passo avanti nella scrittura di programmi. Adesso potrete scrivere ad esempio rubriche, dizionari e così via.
STREAM
In C quando usiamo il termine stream, indichiamo una qualsiasi sorgente di input o qualsiasi destinazione di output. I programmi che abbiamo scritto fin’ora ottengono degli input dallo stream di input che fin’ora è stato la tastiera. Essi, scrivono il loro output su uno stream di output che è lo schermo.
In questo capitolo ci concentreremo sullo stream file, molto semplice e facile da capire.
Innanzi tutto c’è da dire che nel linguaggio C l’accesso a un file può essere effettuato con un PUNTATORE A FILE, che è di tipo FILE *. Ricordiamo che il tipo FILE è definito nella libreria standart input/output <stdio.h>. Questo header contiene altri tre stream che sono già pronti all’uso, e dunque non c’è bisogno nè di dichiararli, nè di aprirli o altro. Questi sono:
stdin (standard input che di default è la tastiera)
stdout (standard output che di default è lo schermo)
stderr (standard error che di default è lo schermo)
Tutte le funzioni usate fin’ora come: printf, scanf, gets, puts e getchar, sono funzioni che come stream di input hanno la tastiera e come stream di output hanno lo schermo.
E’ possibile però modificare il significato di default, mediante una tecnica chiamata: REINDIRIZZAMENTO.
REINDIRIZZAMENTO DELL’INPUT
Questa tecnica fa in modo che lo stream stdin rappresenti un file, ad esempio, informazioni.dat, invece che la tastiera. ESEMPIO: programma <informazioni.dat
REINDIRIZZAMENTO DELL’OUTPUT
Questa tecnica è simile a quella di input. Possiamo reindirizzare lo stream stdout in questo modo: programma >informazioni.dat
IL REINDIRIZZAMENTO E’ PRIVO DI PROBLEMI?
Non esattamente. Infatti QUALSIASI cosa venga scritta su stdout mediante reindirizzamento, viene scritta anche su file. Quindi se per esempio il programma andasse fuori strada e iniziasse a scrivere messaggi d’errore, non ce ne accorgeremmo finché non andremo a leggere il file. Una possibile soluzione sarebbe quella di far scrivere i messaggi d’errore anche su stderr in modo che vengano stampati anche su schermo e non solo su file.
TIPI DI FILE IN C
La libreria stdio.h supporta due tipi di file, i file testuali e i file binari. All’interno di un file testuale i byte rappresentano dei caratteri, cosa che rende possibile leggere, per un umano, cosa contiene il file. Invece in un file binario, i byte non rappresentano necessariamente dei caratteri. In sintesi, i file testuali possiedono due cose che i file binari non posseggono: I file testuali sono suddivisi per righe, e ogni riga termina con uno o due caratteri speciali. Il codice dipende dal sistema operativo ovviamente. Inoltre i file testuali possono contenere il terminatore di file (END OF FILE), ad esempio in Windows questo corrisponde a Ctrl-Z.
QUALI OPERAZIONI POSSIAMO EFFETTUARE SUI FILE?
Su un file posso effettuare operazioni come: l’apertura, la chiusura, modifica della modalità di gestione del buffer associato al file, cancellarlo e modificare il suo nome.
Aprire un file: ciò richiede una chiamata alla funzione fopen che ha sintassi: FILE *fopen(const char* filename, const char* mode); Il primo argomento della funzione è il nome del file che vogliamo aprire o creare. Il secondo argomento, è una stringa di modalità di apertura di un file
Modalità di apertura di un file: La modalità di apertura dipende non solo da quali operazioni vogliamo effettuare su file, ma anche dal tipo di file, binario o testuale.
MODALITA’ APERTURA FILE TESTUALE
“r” – Apre il file in LETTURA (read)
“w” – Apre il file in SCRITTURA (write) (Non è necessario che il file esista)
“a” – Apre il file in APPEND (accodamento) (Non è necessario che il file esista)
“r+” – Apre il file in LETTURA e SCRITTURA e comincia dall’inizio
“w+” – Apre il file in LETTURA e SCRITTURA (Tronca il file se è già presente)
“a+” – Apre il file in LETTURA e SCRITTURA (Accoda se il file esiste già)
MODALITA’ APERTURA FILE BINARIO
“rb” – Apre il file in LETTURA (read)
“wb” – Apre il file in SCRITTURA (write) (Non è necessario che il file esista)
“ab” – Apre il file in APPEND (accodamento) (Non è necessario che il file esista)
“rb+” – Apre il file in LETTURA e SCRITTURA e comincia dall’inizio
“wb+” – Apre il file in LETTURA e SCRITTURA (troca il file se esiste)
“ab+” – Apre il file in LETTURA e SCRITTURA (accoda se il file esiste già)
CHIUDERE UN FILE
Il C fornisce una funzione che deve essere assolutamente usata per chiudere un file che non viene più utilizzato: la funzione int fclose(FILE * stream); L’argomento di questa funzione è il puntatore a file che abbiamo ottenuto da una precedente chiamata ad una funzione fopen. Questa funzione restituisce 0 se il file è stato chiuso con esito positivo, altrimenti restituisce la macro EOF (End of file).
ALTRE VARIE OPERAZIONI SUI FILE
Abbiamo due funzioni che permettono rispettivamente di rimuovere un file oppure di rinominarlo, esse sono: int remove(const char *filename) e int rename(const char*old, const char *new); Queste funzioni a differenza delle altre lavorano con i nomi dei file anzichè con i puntatori! Entrambe restituiscono zero nel caso le loro chiamate si concludano con successo, altrimento un valore diverso da zero. ESEMPIO:
remove(“studenti”); //Cancella il file chiamato “studenti”
rename(“studenti”,”professori”); // Rinomina il file chiamato studenti con professori
INPUT/OUTPUT FORMATTATO
Abbiamo varie funzioni per l’input/output formattato. Procediamo per ordine:
Abbiamo fprintf e printf (che conosciamo già, quindi discuteremo della fprintf soltanto)
La fprintf ha prototipo: int fprintf(FILE *stream, const char*format); Essa scrive un numero variabile di dati nello stream di output usando una stringa di formato che controlla il modo di presentarsi dell’output. Questa funzione (come la printf) restituisce il numero di caratteri scritti. Un valore negativo restituito, è indice di qualche errore in fase di scrittura. Inoltre la fprintf può scrivere in qualsiasi stream le venga fornito in input dal suo primo argomento. ESEMPIO:
fprintf(fp,”Numero: %d”, i); // Scrive su file
Poi, oltre a queste abbiamo la fscanf e scanf (che, come la printf non discuteremo qui perchè già la conosciamo e dovrebbe essere chiara)
La fscanf ha prototipo: int fscanf(FILE *stream, const char*format); Essa legge da un determinato stream, dei dati. Queste funzioni, terminano prematuramente se si verifica un INPUT FAILURE (non possono essere letti altri caratteri di input), un MATCHING FAILURE (i caratteri in input non si adattano alle stringhe di formato) o un ERRORE DI CODIFICA (se c’è stato un tentativo di leggere un carattere multibyte ma i caratteri in input non corrispondono a nessun carattere multibyte valido).
Poi, abbiamo le funzioni di input/output però di singoli caratteri come la putchar (che scrive un carattere nello stream stdout. ESEMPIO:
putchar(c) //Scrive un carattere nello stream stdout
Poi abbiamo la fputc e la putch che sono versioni generalizzati della putchar, che possono scrivere in uno stream qualsiasi dei caratteri:
fputch(c,fp); //Scrive c nello stream fp
putch(c,fp); // Scrive c nello stream fp
Dopo abbiamo le funzioni getchar (che legge un carattere dallo stream stdin) ESEMPIO:
getchar(c); //Legge un carattere dallo stream stdin
Oltre a questa abbiamo la fgetc e getc che leggono caratteri da uno stream qualsiasi:
c=fgetc(fp) //Legge un carattere dallo stream fp
c=getc(fp) // Legge un carattere dallo stream fp
Da non dimenticare sono le funzioni di input/output di righe come:
puts(“Ciao mondo!”); // Scrive nello stream stdout
fputs(“Ciao mondo!”,fp); // Scrive nello stream fp
gets(stringa); //Legge una riga da stdin
gets(stringa,sizeof(stringa),fp) //Legge una riga dallo stream fp
Quest’ultima funzione continua a leggere finchè non incontra un carattere new-line, oppure finchè non ha letto (stringa)-1 caratteri
Importanti, sono anche le funzioni di input/output a blocchi come:
fwrite(a,sizeof(a[0]), sizeof(a)/sizeof(a[0]), fp);
Questa funzione copia un vettore dalla memoria ad uno stream. Il primo argomento in una chiamata a questa funzione è l’indirizzo del vettore, il secondo argomento è la dimensione di ogni elemento in byte, metre il terzo rappresenta il numero di elementi che devono essere scritti. La fwrite restituisce il numero di elementi che ha scritto effettivamente. Se questo numero è minore del terzo argomento, c’è stato un errore in scrittura.
Poi c’è la funzione:
n=fread(a,sizeof(a[0]), sizeof(a)/sizeof(a[0]), fp);
Questa funzione legge gli elementi di un vettore da uno stream qualsiasi. I suoi argomenti in ordine sono: l’indirizzo del vettore, la dimensione di ogni elemento in byte, il numero di elementi da leggere e un puntatore a file. Per leggere il contenuto di un file e salvarlo all’interno del vettore possiamo usare la chiamata a funzione che ho scritto prima. Controllate sempre il valore restituito dalla funzione, perché deve essere uguale al terzo argomento che corrisponde ai caratteri da leggere! Se ciò non si verifica, si è verificato un errore in lettura! Le fread e fwrite vanno bene particolarmente per le strutture, ma possono essere usate tranquillamente con tutti i tipi di variabile.ATTENZIONE ALLA FWRITE USATA PER SCRIVERE STRUTTURE CONTENENTI DEI PUNTATORI! NON C’E’ NESSUNA GARANZIA CHE I VALORI DI QUESTI ULTIMI SIANO ANCORA VALIDI DOPO LA LETTURA!