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:\temp\prova1.c
• in UNIX e Linux:
/usr/temp/prova1.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'esame\nsi 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):
01111010 11101010
 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:
01111010 11101010
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 TurboC, con la funzione itoa() [non standard]
 oppure con la nostra numToS()
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
Funzione da console
Funzione da file
int
getchar(void);
int
fgetc(FILE* f);
int
putchar(int c);
int
fputc(int c, FILE* f);
char* gets(char* s);
char* fgets(char* s, int n, FILE* f);
int
puts(char* s);
int
fputs(char* s, FILE* f);
int
printf( ... );
int
fprintf(FILE* f, ... );
int
scanf( ... );
int
fscanf(FILE* f, ... );
 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 ‘\0’.
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 ‘\0’
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 input\n");
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! */
}
fclose(file);
exit(0);
non fa nulla, ma è obbligatoria per
}
alternare letture e scritture.
Scarica

20-File