GESTIONE DEI FILE Per poter mantenere disponibili i
GESTIONE DEI FILE Per poter mantenere disponibili i dati tra le diverse esecuzioni di un programma (persistenza dei dati) è necessario poterli archiviare su memoria di massa. · dischi · nastri · cd ·. . .
IL CONCETTO DI FILE • Un file è una astrazione fornita dal sistema operativo, il cui scopo è consentire la memorizzazione di informazioni su memoria di massa. • Concettualmente, un file è una sequenza di registrazioni (record) uniformi, cioè dello stesso tipo. • Un file è un’astrazione di memorizzazione di dimensione potenzialmente illimitata (ma non infinita), ad accesso sequenziale.
IL CONCETTO DI FILE • Una testina di lettura/scrittura (concettuale) indica in ogni istante il record corrente: – inizialmente, la testina si trova per ipotesi sulla prima posizione – dopo ogni operazione di lettura / scrittura, essa si sposta sulla registrazione successiva. • È illecito operare oltre la fine del file.
OPERARE SUI FILE • A livello di sistema operativo un file è denotato univocamente dal suo nome assoluto, che comprende il percorso e il nome relativo. • In certi sistemi operativi il percorso può comprendere anche il nome dell’unità. • in DOS o Windows: C: tempprova 1. c • in UNIX e Linux: /usr/temp/prova 1. c
APERTURA DI UN FILE • Poiché un file è un’entità del sistema operativo, per agire su esso dall’interno di un programma occorre stabilire una corrispondenza fra: • il nome del file come risulta al sistema operativo • un nome di variabile definita nel programma. • Questa operazione si chiama apertura del file ed è concettualmente un’operazione del modello di coordinazione.
APERTURA E CHIUSURA DI UN FILE • Una volta aperto il file, il programma può operare su esso operando formalmente sulla variabile definita al suo interno: il sistema operativo provvederà a effettuare realmente l’operazione richiesta sul file associato a tale simbolo. • Al termine, la corrispondenza fra nome del file e variabile usata dal programma per operare su esso dovrà essere soppressa, mediante l’operazione di chiusura del file.
FILE IN C • Per gestire i file, il modello di coordinazione del C definisce il tipo FILE. • FILE è una struttura definita nello header standard stdio. h, che l’utente non ha necessità di conoscere nei dettagli – e che spesso cambia da un compilatore all’altro! • Le strutture FILE non sono mai gestite direttamente dall’utente, ma solo dalle funzioni della libreria standard stdio. • L’utente definisce e usa, nei suoi programmi, solo dei puntatori a FILE.
IL MODELLO DI FILE DEL C · Libreria standard stdio · l’input avviene da un canale di input associato a un file aperto in lettura · l’output avviene su un canale di output associato a un file aperto in scrittura · Due tipi di file: file binari e file di testo · basterebbero i file binari, ma fare tutto con essi sarebbe scomodo · i file di testo, pur non indispensabili, rispondono a un’esigenza pratica molto sentita.
FILE IN C: APERTURA • Per aprire un file si usa la funzione: FILE* fopen(char fname[], char modo[]) Questa funzione apre il file di nome fname nel modo specificato, e restituisce un puntatore a FILE (che punta a una nuova struttura FILE appositamente creata). • ATTENZIONE alle convenzioni dipendenti dal sistema operativo usato ( nei percorsi oppure /, presenza o assenza di unità, etc)
FILE IN C: APERTURA Per aprire un file si usa la funzione: FILE* fopen(char fname[], char modo[]) modo specifica come aprire il file: · r apertura in lettura (read) · w apertura in scrittura (write) · a apertura in aggiunta (append) • seguita opzionalmente da: · t · b apertura in modalità testo (default) apertura in modalità binaria • ed eventualmente da · + apertura con possibilità di modifica.
FILE IN C: APERTURA Per aprire un file si usa la funzione: FILE* fopen(char fname[], char modo[]) • Il valore restituito da fopen() è un puntatore a FILE, da usare in tutte le successive operazioni sul file. – esso è NULL in caso l’apertura sia fallita – controllarlo è il solo modo per sapere se il file si sia davvero aperto: non dimenticarlo! • I tre canali predefiniti standard (stdin, stdout, stderr) sono in tutto e per tutto dei file già aperti: quindi, il loro tipo è FILE*.
FILE IN C: CHIUSURA Per chiudere un file si usa la funzione: int fclose(FILE*) • Il valore restituito da fclose() è un intero – 0 se tutto è andato bene – EOF in caso di errore. • Prima della chiusura, tutti i buffer vengono svuotati.
FILE BINARI · Un file binario è una sequenza di byte: come tale, può essere usato per archiviare su memoria di massa qualunque tipo di informazione · input e output avvengono sotto forma di una sequenza di byte · la lunghezza del file è registrata dal sistema operativo · la fine del file è rilevata basandosi sull’esito delle operazioni di lettura
FILE BINARI • Poiché un file binario è una sequenza di byte, sono fornite due funzioni per leggere e scrivere sequenze di byte • fread() legge una sequenza di byte • fwrite() scrive una sequenza di byte • Essendo pure sequenze di byte, esse non sono interpretate: l’interpretazione è “negli occhi di chi guarda”. • Quindi, possono rappresentare qualunque informazione (testi, numeri, immagini. . . )
OUTPUT BINARIO: fwrite() Sintassi: int fwrite(addr, int dim, int n, FILE *f); • scrive sul file n elementi, ognuno grande dim byte (complessivamente, scrive quindi n dim byte) • gli elementi da scrivere vengono prelevati dalla memoria a partire dall’indirizzo addr • restituisce il numero di elementi (non di byte!) effettivamente scritti, che possono essere meno di n.
INPUT BINARIO: fread() Sintassi: int fread(addr, int dim, int n, FILE *f); • legge dal file n elementi, ognuno grande dim byte (complessivamente, legge quindi n dim byte) • gli elementi da leggere vengono scritti in memoria a partire dall’indirizzo addr • restituisce il numero di elementi (non di byte!) effettivamente letti, che possono essere meno di n se il file finisce prima: al limite anche zero. Controllare il valore restituito è il solo modo per sapere se il file è finito.
ESEMPIO 1 Salvare su un file binario numeri. dat il contenuto di un array di dieci interi. #include <stdio. h> #include <stdlib. h> La funzione exit() fa terminare il programma anticipatamente. main(){ FILE *fp; int vet[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; if ((fp = fopen("numeri. dat", "wb"))==NULL) exit(1); /* Errore di apertura */ fwrite(vet, sizeof(int), 10, fp); fclose(fp); } L’operatore sizeof è essenziale per la portabilità
ESEMPIO 2 Leggere da un file binario numeri. dat una sequenza di interi, scrivendoli in un array. #include <stdio. h> fread tenta di leggere 40 interi, ma #include <stdlib. h> ne legge meno se il file finisce main(){ prima (come qui) FILE *fp; int vet[40], i, n; if ((fp = fopen("numeri. dat", "rb"))==NULL) exit(1); /* Errore di apertura */ n = fread(vet, sizeof(int), 40, fp); for (i=0; i<n; i++) printf("%d ", vet[i]); fclose(fp); n contiene il numero di interi } effettivamente letti
ESEMPIO 3 Scrivere su un file di caratteri testo. txt una sequenza di caratteri. #include <stdio. h> #include <stdlib. h> Dopo averlo creato, provare ad aprire questo file con un editor qualunque (es. blocco note). (. . e il terminatore? ) main(){ FILE *fp; int n; char msg[] = "Ah, l'esamensi avvicina!"; if ((fp = fopen("testo. txt", "wb"))==NULL) exit(1); /* Errore di apertura */ fwrite(msg, strlen(msg)+1, 1, fp); fclose(fp); } Un carattere in C ha sempre size=1 Scelta: salvare anche il terminatore.
ESEMPIO 4 Leggere da un file di caratteri testo. txt una sequenza di caratteri, ponendoli in una stringa. Idea: perché non provare ad #include <stdio. h> leggere un file (corto) creato con #include <stdlib. h> un editor qualunque? main(){ FILE *fp; int msg[80], n; if ((fp = fopen("testo. txt", "rb"))==NULL) exit(1); /* Errore di apertura */ n = fread(msg, 1, 80, fp); puts(msg); n contiene il numero di char effettifclose(fp); vamente letti (che non ci interessa, } perché c’è il terminatore. . )
OUTPUT DI NUMERI L’uso di file binari consente di rendere evidente la differenza fra la rappresentazione interna di un numero e la sua rappresentazione esterna come stringa di caratteri in una certa base. · Supponiamo che sia int x = 31466; · Che differenza c’è fra printf("%d", x); e fwrite(&x, sizeof(int), 1, stdout); ?
OUTPUT DI NUMERI · Se x è un intero che vale 31466, internamente la sua rappresentazione in complemento a due è (nell’ipotesi di interi lunghi 16 bit): 0111101010 · Perciò, · emettendo direttamente tale sequenza di byte, come fa fwrite(), si emettono due byte · questi byte non hanno alcuna relazione con la stringa “ 31466” che ai nostri occhi rappresenta l’intero scelto in base dieci · emettendo i caratteri corrispondenti alla stringa “ 31466”, come fa printf(), si emettono cinque byte
OUTPUT DI NUMERI · Se per ipotesi si emettessero a video (o su un file di testo) direttamente i due byte: 0111101010 si otterrebbero i caratteri corrispondenti al codice ASCII di quei byte: êz · Niente di anche solo vagamente correlato al numero di partenza!!
OUTPUT DI NUMERI · Per ottenere invece la stringa “ 31466”, che rappresenta il numero in base dieci, occorre convertire il numero in stringa · in Turbo. C, con la funzione itoa() [non standard] · oppure con la nostra num. To. S() e poi stampare la stringa così ottenuta: · puts() Questo è esattamente quello che fa printf().
INPUT DI NUMERI · Analogamente, che differenza c’è fra scanf("%d", &x); e fread(&x, sizeof(int), 1, stdin); nell’ipotesi di battere da tastiera “ 23” ? · Anche qui, · scanf() preleva una stringa di caratteri adeguati alla sintassi di un intero decimale e li converte in numero, ottenendo ventitre · viceversa, fread() prende due caratteri (la size di un int) e scrive dentro a x i loro codici ASCII, interpretandoli poi come intero. Risultato (assurdo): tredicimilacentosei !
FILE DI TESTO · È un caso particolare di file binario, che coinvolge una sequenza di caratteri · Ha senso trattarlo come caso a parte perché i caratteri sono un caso estremamente frequente, con caratteristiche proprie: · esiste un concetto di riga e di fine riga (‘n’) · certi caratteri sono stampabili a video (quelli di codice 32), altri no · la sequenza di caratteri è chiusa dal carattere speciale EOF
FILE DI TESTO (segue) · La lunghezza del file è sempre registrata dal sistema operativo (come per ogni file binario)… ·. . . ma è anche indicata in modo esplicito dalla presenza del carattere EOF. · Quindi, la fine del file può essere rilevata · o in base sull’esito delle operazioni di lettura · o perché si intercetta il carattere di EOF. Attenzione: lo speciale carattere EOF (End-Of-File) varia da una piattaforma all’altra.
FILE DI TESTO & CANALI STANDARD · I canali di I/O standard non sono altro che file di testo già aperti · stdin è un file di testo aperto in lettura, di norma agganciato alla tastiera · stdout è un file di testo aperto in scrittura, di norma agganciato al video · stderr è un altro file di testo aperto in scrittura, di norma agganciato al video · Le funzioni di I/O disponibili per i file di testo sono una generalizzazione di quelle già note per i canali di I/O standard.
CONFRONTO · tutte le funzioni da file acquistano una “f” davanti nel nome (qualcuna però cambia leggermente nome) · tutte le funzioni da file hanno un parametro in più, che è appunto il puntatore al FILE aperto · sempre davanti… tranne in fgets/fputs
PECULIARITÀ: fgets() vs. gets() · fgets() prevede anche un parametro intero n, che consente di leggere non più di n-1 di caratteri char* fgets(char s[], int n, FILE* f) È una caratteristica importante per non superare la lunghezza massima della stringa s. · A differenza di gets(), che lo elimina, fgets() mantiene il carattere di fine riga, se presente nella stringa letta; aggiunge comunque in fondo il terminatore ‘ ’.
PECULIARITÀ: fputs() vs. puts() · A differenza di puts(), che lo aggiunge sempre, fputs() non inserisce in fondo il carattere di fine riga · Comunque, nessuna trascrive il terminatore ‘ ’
PECULIARITÀ: getchar() vs. fgetc() putchar() vs. fputc() · getchar() e putchar() sono delle scorciatoie linguistiche per fgetc() e fputc() getchar() fgetc(stdin) putchar(c) fputc(stdout, c) · in effetti, getchar() e putchar() sono quasi sempre delle macro!
FUNZIONI SUI FILE: PECULIARITÀ · Esistono poi alcune funzioni per i file di testo che non hanno un analogo sui canali standard: feof() indica se si è già incontrato EOF perror() stampa un messaggio di errore sul canale standard di errore (stderr) sposta la testina di lettura/scrittura su una posizione a scelta nel file dà la posizione corrente della testina di lettura/scrittura nel file fseek() ftell()
ESEMPIO 1 Salvare su un file di testo prova. txt ciò che viene battuto sulla tastiera. #include <stdio. h> #include <stdlib. h> fp può essere NULL se non c’è spazio su disco o se il disco è protetto da scrittura. main(){ FILE *fp; if ((fp = fopen("prova. txt", "w"))==NULL) exit(1); /* Errore di apertura */ else { int c; while ((c=getchar())!=EOF) fputc(c, fp); fclose(fp); } Per generare EOF, CTRL+Z (DOS / } Windows) o CTRL+D (Unix/Linux)
ESEMPIO 2 Stampare a video il contenuto di un file di testo prova. txt. #include <stdio. h> #include <stdlib. h> fp può essere NULL se il file richiesto non esiste. main(){ FILE *fp; if ((fp = fopen("prova. txt", "r"))==NULL) exit(1); /* Errore di apertura */ else { int c; while ((c=fgetc(fp))!=EOF) putchar(c); fclose(fp); } }
ESEMPIO 3 Scrivere un programma che, dato un file di testo prova. txt, sostituisca tutte le minuscole in maiuscole. Occorre poter leggere e poi riscrivere un carattere apertura con modifica · "r+" presuppone che il file esista, lo apre in lettura ma consente anche (alternatamente) di scriverci sopra · "w+", pur consentendo delle letture, crea il file se non esiste o lo cancella se già esiste · "a+" è analoga a "r+", ma agisce in coda al file. Nel caso in esame serve la modalità r+.
ESEMPIO 3 (segue) È inoltre necessario potersi collocare in una ben precisa posizione sul file, per poter sostituire una minuscola nella corrispondente maiuscola. In particolare, quando si legge una minuscola: • si retrocede di una posizione • la si sovrascrive con la maiuscola. A questo provvedono fseek() e ftell(). ATTENZIONE: la modalità r+ consente di alternare letture e scritture, ma con l’obbligo di effettuare una fseek() per passare da lettura a scrittura o viceversa.
fseek() e ftell(): SINTASSI Sintassi int fseek(FILE* f, long offs, int orig) long ftell(FILE* f) dove: offs dà la posizione, rispetto a orig, a cui portarsi sul file orig dà la posizione rispetto a cui misurare offs, e può essere: – l'inizio del file – la posizione corrente nel file – la fine del file SEEK_SET SEEK_CUR SEEK_END NB: per un file di testo, offs deve valere o 0 o un valore restituito da ftell(), nel qual caso, orig deve essere SEEK_SET
ESEMPIO 3 int main() { FILE *file; char fname[20]; int ch; printf("Nome del file: "); scanf("%s", fname); if ((file=fopen(fname, "r+"))==NULL) { perror("Impossibile aprire file di inputn"); exit(1); } while((ch=fgetc(file))!=EOF) if(islower(ch)) { fseek(file, ftell(file)-1, SEEK_SET); fputc(toupper(ch), file); fseek(file, 0, SEEK_CUR); /* OBBLIGO! */ } non fa nulla, ma è obbligatoria per fclose(file); alternare letture e scritture. exit(0);
- Slides: 39