Lezione XVII           frames    noframes

 

Input /output  e gestione dei file

 

Iniziamo la lezione dicendo subito che per INPUT/OUTPUT si intende l’ insieme delle operazioni di ingresso ed uscita, ossia lo scambio di informazioni tra il programma e le unita’ periferiche del calcolatore.

Nel linguaggio C l’ I/O e’ interamente implementato mediante funzioni della libreria standard.

Il linguaggio C vede i file come un flusso (stream) sequenziale di byte. Uno stream dal punto di vista tecnico e’ una implementazione software in grado di gestire le informazioni relative all’interazione a basso livello con la periferica associata, in modo che il programma possa trascurare di che periferica si tratti.

Ogni file termina con un marcatore di fine file detto 

EOF( end-of-file) definito in <stdio.h> come una costante simbolica uguale a -1.

 

I file e gli stream

Un file e' una sequenza di lunghezza non prefissata di valori dello stesso tipo

Quando un file viene aperto sara’ associato uno stream . Tre file e i loro rispettivi stream, lo standard di input, lo standard di output, lo standard error (stdin, stdout, stderr) , sono aperti automaticamente quando inizia l’esecuzione di un programma.

 

Lo stream dello standard di input consente ad un programma di ricevere input dalla tastiera. (puo’ essere rediretto su altre periferiche).

Lo stream dello standard di output consente ad un programma di di scrivere sul video della macchina.(puo’ essere rediretto su altre periferiche)

Lo stream dello standard di errore consente ad un programma di scrivere sul video della macchina. (non puo’ essere rediretto su altre periferiche).

Un programma C puo’ servirsi degli stream standard ; basta che vi compaia la direttiva #include<stdio.h>

Gli stream forniscono un canale di comunicazione tra i file ed i programmi.

 

L’apertura di un file restituisce un puntatore ad una struttura  FILE  (definita in <stdio.h>) che contiene le informazioni utilizzate per elaborare il file. La struttura FILE ha un file descriptor (descrittore di file), cioe’ l’indice di un vettore del S.O. chiamata open file table (tabella dei file aperti). Ogni elemento del vettore contiene un file control block o FCB (blocco di controllo del file) il quale viene utilizzato dal S.O. per amministrare un particolare file.

Consideriamo due tipi di file i file di testo ed i file binari; i primi utilizzati quando l'informazione da memorizzarvi e' di tipo testo cioe' sequenze di caratteri; i secondi quando si vuole memorizzare nel file dati di un generico tipo (interi,strutture ecc...).

 

Il puntatore al file

 

Attraverso la definizione

FILE  *fp;

viene permessa l’associazione di un identificatore del programma ad un file, che si trova in memoria secondaria.

Con la sola definizione,  fp e’ soltanto una “variabile di tipo FILE”; l’effettiva associazione avviene in fase di apertura del file.

Il puntatore fp punta ad informazioni che descrivono alcune caratteristiche dei file come il nome, lo stato e la posizione corrente.

Scopo del puntatore al file e’ quello di identificare uno specifico file su disco e viene usato dal flusso associato per svolgere in modo corretto le operazioni di input/output.

 

 

Apertura di un file

 

Avendo la definizione FILE *fp;

Per aprire il file di avente nome “dati.in” ed associarlo ad fp si esegue la seguente operazione di apertura:

fp = fopen(“dati.in”, “r”);

 

Vediamo che l’access mode “r” implica l’apertura del file  dati.in, presente in memoria secondaria, in modalita’ di sola lettura.

Il prototipo della fopen() e’:

FILE *fopen(const char *nome_del_file, const char *modo);

 

nome_del_file deve essere un puntatore ad una stringa di caratteri che costituisce un nome valido secondo quanto definito dal S.O. e puo’ includere un percorso (path); la stringa a cui punta il modo definisce la modalita’ con cui sara’ aperto il file.

La fopen() restituisce un puntatore al file di tipo FILE oppure un puntatore nullo nel caso si verifichi un errore.

Si puo’ pensare che quando il file viene aperto, vi sia un cursore che segna il punto di accesso all’interno del file (viene letto un dato in corrispondenza della posizione del cursore). Cosi all’apertura del file il cursore si posiziona all’inizio del file; quando viene letto un dato, il cursore va alla posizione successiva; alla prossima lettura viene letto il dato successivo eccetera.

 

 

Un file puo’ essere aperto per operazioni di sola scrittura e sono necessarie le istruzioni:

 

FILE *fp;

fp = fopen(“dati.in”, “w”);

 

e’ possibile aprire sia file di testo che file binari.

 

Notare che conviene sempre nei programmi verificare che il file sia stato aperto correttamente (cioe’ che la funzione fopen() abbia avuto successo).

Per fare cio’ si deve usare, nel caso il file in uso fosse dati.in, un frammento di codice come il seguente:

FILE *fp;

char nome_del_file [] = “dati_in”

fp = fopen(nome_del_file, “r”);

if(fp!=NULL)

{

 /* istruzioni di utilizzo del file*/

}

else

{

 printf(“\n impossibile aprire il file”);

 exit(1);

}

 

La istruzione di uscita exit(1) viene utilizzata soltanto se si e’ nel main() altrimenti se si e’ all’interno di una altra funzione, al suo posto si utilizza la return.

 

 

Chiusura di un file

 

Per chiudere un file precedentemente aperto con la fopen() si utilizza la funzione fclose().

E’ molto importante chiudere tutti i file aperti in un programma prima che questi termini la sua esecuzione. Infatti l’operazione di chiusura scrive nel file fisico corrispondente possibili dati che siano rimasti nel buffer di scrittura.

Il prototipo della fclose e’:

int fclose(FILE *fp);

in cui fp e’ il puntatore che restituisce la fopen().

La fclose() tenta di chiudere il file puntato da fp e restituisce il valore 0 nel caso in cui la chiusura sia terminata con successo (altrimenti un valore diverso da 0 nel caso di chiusura fallita). Comunque di solito la fclose() da errore soltanto se un disco e’ stato tolto dall’unita’ di lettura prematuramente oppure quando non vi e’ piu’ spazio libero.

 

 esercizio

 

Le modalita’ di accesso dei file

 

La modalita’ di accesso (access mode) indica  come si dovra’ collegare un file ad un identificatore del programma e come si potra’ usare quel file.

Come visto sopra la modalita’ “r” implica che il file venga aperto soltanto in lettura (solo per leggerne i dati), se il file esiste naturalmente.

Mentre con “w” si indica (ad esempio nella fopen(“dati.in”,  “w”);) di aprire il file dati.in in modalita’ di scrittura. Se il file esiste, nella memoria secondaria, i dati in esso contenuti saranno persi e sostituiti con i nuovi dati che vi scriveremo; se il file non esiste viene creato.

 

Vediamo la tabella con i valori previsti per l’argomento modo nella fopen(.. , ..)

 

Tabella 17.1

modo

significato

“r”

Apre un file testuale esistente per sola lettura, posizionando il cursore all’inizio del file;

“w”

Apre un file testuale per sola scrittura: se il file gia’ esiste i dati in esso contenuti vengono perduti; se il file non esiste ne viene creato uno nuovo;

“a”

Apre un file di testuale esistente per scrittura, in modalita’ append; il cursore viene posizionato alla fine del file e si puo’ scrivere nel file solo in coda ai dati gia’ presenti; i dati gia presenti non vengono cancellati;

“r+”

Apre un file testuale esistente in lettura e scrittura; il cursore viene posto all’inizio;

“w+”

Apre un file testuale per lettura e scrittura; se il file gia’ esiste i dati in esso contenuti vengono perduti; se il file non esiste, ne viene creato uno nuovo;

“a+”

Apre un file testuale in modalita’ append; se il file non esiste, ne viene creato uno nuovo; si possono leggere tutti i dati del file; si possono scrivere dati solo in fondo al file.

“rb”

Apre un file binario esistente per sola lettura, posizionando il cursore all’inizio;

“wb”

Apre un file binario per sola scrittura; se il file gia’ esiste i dati in esso contenuti vengono persi; se il file non esiste , ne viene creato uno nuovo;

“ab”

Apre o crea un file binario per scrittura in modalita’ append; il cursore viene posizionato alla fine del file e si puo’ scrivere nel file solo in coda ai dati gia’ presenti;

“rb+”

Apre un file binario esistente in lettura e scrittura

“wb+”

Apre o crea un file binario in lettura e scrittura

“ab+”

Apre o crea in modalita’ append un file binario.

 

 

Creazione di un file sequenziale

 

Vediamo un esempio di programma in cui utilizzeremo le funzioni fopen() ed fclose():

 

/* creare un file sequenziale*/

#include<stdio.h>

int main ()

{

 int numero_di_conto;

 char nome[31];

 float saldo;

 FILE  *fpunt; /* puntatore al file clienti.dat*/

 if ((fpunt = fopen(“clienti.dat”, “w”)) = = NULL)

 printf(“Il file non puo’ essere aperto\n”);

 else {

  printf(“Inserite il numero di conto, il nome, ed il saldo.\n”);

  printf(“inserite EOF per finire gli inserimenti\n”);

  printf(“?”);

  scanf(“%d%s%f”,&numero_di_conto, nome, &saldo);

     while(!feof(stdin))  {

    fprintf(fpunt,”%d%s%.2f\n”, numero_di_conto, nome, saldo);

    printf(“?”);

    scanf(“%d%s%f”,&numero_di_conto, nome, &saldo);

    }

  fclose(fpunt);

  }

return 0;

 }

 

Questo programma creera’ un semplice file ad accesso sequenziale di nome clienti.dat

Nella schermata di input dovremo inserire ad esempio

? 10 roberta 10,5

? 11 roberto 22

? 12 jennifer 30

?

ed EOF per terminare l’immissione di nuovi dati.

 

Come anticipato FILE *fpunt stabilisce che fpunt e’ un puntatore ad una struttura FILE.

La fprintf e’ analoga alla funzione printf soltanto che la fprintf scrive nel file invece che sul video.

Il programma richiedera’ all’utente di immettere per ogni record i vari campi; quando viene conclusa l’immissione dei dati richiedera’ un end-of-file.

L’end-of-file per i diversi sistemi di computer e’ segnalato in tabella:

 

Tabella 17.2

Sistemi di computer

Combinazione di tasti

Sistemi UNIX

<return><ctrl>d

PC IBM e compatibili

<ctrl>z

Macintosh

<ctrl>d

VAX(VMS)

<ctrl>z

 

 

La riga di codice

while (!feof(stdin))

Usa la funzione feof per determinare se sia stato impostato EOF per il file al quale punta stdin.

EOF indichera’ al programma che non ci sono piu’ dati da elaborare.

 

Il nostro programma non mostra come i dati dei record appaiono realmente all’interno del file. Per fare cio’ e verificare quindi la correttezza dei dati creati necessita un programma che leggera’ il file e ne visualizza il contenuto.

 

 

 

 

Funzioni di lettura e scrittura

 

Si distinguono in due categorie lettura e scrittura per carattere e lettura e scrittura per linee (stringhe).

 

Fra le prime consideriamo la funzione fputc e la funzione fgetc.

 

La funzione fputc ha il seguente prototipo:

 

int fputc (int c , FILE *fpunt);

 

questa funzione preleva un carattere c e lo stampa(scrive) nel file fpunt,(file puntato da fpunt) nella posizione del cursore; naturalmente il file deve essere stato precedentemente aperto con la funzione fopen( ), ed fpunt e’ il puntatore restituito da quest’ultima funzione.

Il carattere c viene convertito in un unsigned char nella fputc.

Restituisce il carattere letto (il codice) oppure EOF (se la stampa e’ andata male)

 

La funzione fgetc ha il seguente prototipo:

 

int fgetc(FILE *fpunt);

 

la funzione restituisce (convertito in intero) il carattere letto dal file fpunt “file precedentemente aperto con la fopen()”;

il cursore avanza di un dato (il prossimo da leggere); in caso vi sia errore viene restituito EOF.

 

 

Nella lettura e scrittura per linee consideriamo la funzione fgets ed fputs ; con la fgets si legge al piu’ una intera linea da un file passato per argomento “file precedentemente aperto tramite la funzione fopen()”.

 

Il prototipo della funzione e’:

 

char *fgets (char *strin, int n, FILE *fpunt);

 

con questa funzione viene letto, dal file fpunt, una sequenza di caratteri, fermandosi al newline (oppure fermandosi ad n caratteri letti od all’EOF) e memorizza i caratteri letti nell’array strin; la fgets restituisce l’array letto oppure NULL se ha incontrato EOF.

L’eventuale EOL (‘\n’) letto verra’ incluso nella stringa risultante.

Con il parametro n si specifica il numero massimo di caratteri da leggere; se la linea del file contiene un numero di caratteri maggiore di n i caratteri in eccesso non verranno letti.

 

La funzione fputs ha il seguente prototipo:

 

int fputs(const char *strin, FILE *fpunt);

 

e stampa nel file fpunt(precedentemente aperto tramite la fopen(..) ) i caratteri contenuti nella stringa strin; se la stampa e’ andata a buon fine restituisce il valore 0 , altrimenti un numero diverso da zero.

 

Esempio

 

Nel seguente programma la funzione fputs() scrive nel file prova delle stringhe lette dalla tastiera, tramite la funzione gets(), e passategli come parametro.

 

#include "stdio.h"

#include "stdlib.h"

#include "string.h"

 

int main()

{char strin[81];

FILE *fpunt;

 if((fpunt=fopen("prova", "w"))==NULL)

 {

 printf("Errore nella lettura del file\n");

 exit(1);

 }

 do

 {

 printf("inserite una stringa e premere INVIO per uscire:\n");

 gets(strin);

 strcat(strin,"\n"); /*aggiunta di un newline*/

 fputs(strin,fpunt);

 }while(*strin !='\n');

return 0;

}

 

Per uscire dal programma introducete una stringa vuota.

Notare che la funzione gets non memorizza il carattere di newline, per cui ne aggiungiamo uno alla stringa letta da input, attraverso la funzione strcat, prima che tale stringa venga scritta nel file fpunt.

 

La funzione rewind()

 

La rewind() ripristina l’indicatore di posizione del file specificato ‘cursore’. Ossia il file e’ come se venisse ravvolto con la rewind().

Il prototipo di tale funzione e’:

 

void rewind (FILE *fpunt);

 

fpunt e’ un puntatore ad un file.

 

 

Le funzioni di lettura e scrittura formattata

 

Sono la fprintf() e la fscanf() analoghe alla printf e scanf, soltanto che accettano un argomento in piu’, cioe’ il file dove scrivere o da cui leggere.

 

I prototipi sono rispettivamente:

 

int fprintf(FILE *fpunt, const char *stringa_formato, <argomenti>);

 

la quale stampa nel file fpunt secondo le specifiche della stringa formato;

e la

int fscanf (FILE *fpunt, const char * stringa_formato, <argomenti>);

 

la fscanf legge dal file fpunt i dati specificati nella stringa_formato.

 

Cenni sui file ad accesso casuale

 

I  record di un file creati con l’output formattato della funzione fprintf non avranno necessariamente la stessa lunghezza. Ogni record di un file ad accesso casuale puo’ essere acceduto direttamente avendo di norma la stessa lunghezza. Dal momento che in un file ad accesso casuale ogni record ha lunghezza fissa, la sua posizione all’interno del file puo’ essere calcolata con una funzione della chiave del record.

 

Le funzioni di lettura e scrittura per blocchi

 

Attraverso le funzioni fread() ed fwrite() si possono leggere e scrivere rispettivamente interi blocchi di byte da un file.

 

I loro prototipi sono:

 

size_t fread(void *buffer, size_t num_byte, size_t contatore, FILE *fpunt);

 

size_t fwrite(void *buffer, size_t num_byte, size_t contatore, FILE *fpunt);

 

nella fread buffer e’ l’indirizzo di una locazione di memoria destinata a ricevere i dati letti dal file fpunt. Con essa si leggono dal file fpunt un blocco di contatore dati con ciascun dato avente dimensione num_byte. La fread restituisce il numero di elementi letti con successo.

Le funzioni fread ed fwrite sono in grado di leggere e scrivere su disco interi vettori di dati.

Il terzo argomento delle funzioni e’ il numero degli elementi del vettore che dovranno essere letti/scritti dal/sul disco.

 

Vediamo adesso un programma che se il file viene aperto in modo binario scrive un double un int e un long in un file di nome fle_test e li rilegge:

 

#include <stdio.h>

#include<stdlib.h>

 

int main ()

{

FILE *fpunt;

double d=11.23;

int i=101;

long l=113023L;

if((fpunt=fopen("file_test","wb+"))==NULL)

{

printf("errore di apertura del file\n");

exit(1);

}

fwrite(&d,sizeof(double), 1,fpunt);

fwrite(&i,sizeof(int), 1,fpunt);

fwrite(&l, sizeof(long),1,fpunt);

 

rewind(fpunt) ; /*riposiziona il puntatore all'inizio del file*/

 

fread(&d, sizeof(double),1,fpunt);

fread(&i, sizeof(int),1,fpunt);

fread(&l,sizeof(long),1,fpunt);

printf("%f %d %ld",d,i,l);

fclose(fpunt);

return 0; 

}

 

Il linguaggio C consente le operazioni di lettura e scrittura ad accesso casuale usando la funzione fseek(), che ha il compito di posizionare il cursore del file.

Il prototipo della fseek() e’:

 

int fseek(FILE *fpunt, long num_byte, int origine);

 

fpunt  e’ il puntatore al file come viene restituito dalla funzione fopen();

num_byte  e’ il numero di byte di distanza tra l’origine e la nuova posizione (detto offset o scostamento); notare che in questo caso occorre usare l'operatore sizeof per accedere all'elemento desiderato; mentre  origine  e’ una delle seguenti macro definite da stdio.h :

 

Tabella 17.3

Origine

Nome

Inizio del file

SEEK_SET

Posizione corrente

SEEK_CUR

Fine del file

SEEK_END

 

 

Per leggere l'ennesimo elemento del file fp, che supponiamo contenga elementi di tipo tipo_elementi, useremo:

fseek(fp,(n-1)*sizeof(tipo_elementi),SEEK_SET);

fread(&buffer,sizeof(tipo_elementi),1,fp);

 

 Esercizio

 

Test 17