11 File
11 File
Ver. 3
I file
File: sequenza di byte a cui il Sistema
Operativo dà un nome ed una collocazione sul
dispositivo di memoria di massa (es. disco)
Può essere paragonato ad una lunga stringa
Uno stream è un generico flusso di caratteri
Un file può essere una sorgente di stream
(anche la tastiera è una sorgente di stream)
Un file può essere una destinazione di stream
(anche il video è una destinazione di stream)
2 3 3 1 . 1 9 C I A O \0
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ...
3
I file
I byte di un file sono idealmente raggruppati
in blocchi detti record o campi (field)
Un record è un blocco di byte che costituisce
un elemento (numero, stringa, struttura, etc.)
2 3 3 1 . 1 9 C I A O \0
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ...
File e stream
Dal punto di vista del linguaggio C, non c’è
differenza tra leggere da un file o dalla
tastiera, o tra scrivere su un file o sul video:
sono in ogni caso degli stream di caratteri
Le funzioni di I/O per i file richiedono che sia
indicato lo stream da utilizzare, mentre quelle
per video e tastiera utilizzano implicitamente
degli stream associati a questi dispositivi
7
La costante EOF
EOF (acronimo di End Of File) è una costante
simbolica di tipo int definita da una #define
in <stdio.h> e di solito pari al valore –1
Il valore EOF viene restituito da alcune
funzioni di I/O per segnalare il raggiungimento
della fine del file (alcune la usano anche come
generica segnalazione di errore)
I file di testo non hanno al fondo un carattere
di fine file (almeno in Windows, Linux e OSX),
le funzioni di input riconoscono di essere alla
fine del file grazie al Sistema Operativo che,
conoscendone la lunghezza, segnala ad esse
quando non ci sono più caratteri da leggere
9
Il tipo FILE
Per utilizzare un file è necessario che il sistema
legga dal disco e tenga in memoria le sue
caratteristiche (lunghezza, ecc.)
Queste informazioni sono memorizzate in una
struttura dati composta (struct) che ha tipo
FILE (in maiuscolo)
Ogni accesso a un file avviene attraverso una
variabile che si riferisce alla struttura FILE
associata a quel file, questa variabile è di tipo
puntatore ed è detta file pointer :
FILE *variabile;
FILE *fp;
10
Il tipo FILE
La struttura dati FILE contiene tra le
informazioni relative al file il cosiddetto offset
(o file position pointer ) che memorizza il
punto in cui la lettura o scrittura è arrivata
(indica la posizione del prossimo byte da
leggere o da scrivere, calcolata rispetto al
primo byte del file che ha offset=0)
Esempio per file in lettura
2 3 3 1 . 1 9 C I A O \0
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ...
Apertura di un file
Per utilizzare un file bisogna richiederne
l’accesso al Sistema Operativo indicando
anche per quale scopo (lettura o scrittura),
questa operazione è detta apertura del file
La funzione fopen apre un file, creando e
riempiendo la struttura FILE da associargli
fp=fopen(nome, modo);
nome è una stringa (costante o variabile)
contenente il nome del file e l’eventuale
percorso, la dimensione massima e tipica di
tale stringa è FILENAME_MAX (costante
definita in <stdio.h>)
modo è una stringa indicante la modalità di
apertura del file (lettura o scrittura)
12
Apertura di un file
Modi di apertura semplice per file di testo:
"r" – sola lettura (read), il file deve già esistere
"w" – sola scrittura (write), crea vuoto il file da
scrivere; se esiste già, lo elimina e lo ricrea
nuovo (quindi se ne perde il contenuto)
"a" – sola scrittura (append), se non esiste viene
creato nuovo e vuoto, se invece esiste NON
viene cancellato e i dati vengono aggiunti al
fondo di quelli già presenti
Le modalità "r+", "w+", "a+" sono dette
di aggiornamento e permettono di leggere e
scrivere lo stesso file (si veda più avanti)
13
Apertura di un file
fopen restituisce NULL se non riesce ad
aprire il file, altrimenti restituisce il puntatore
alla struttura FILE corrispondente al file
aperto, da assegnare al file pointer
fp=fopen(miofile,"r");
Bisogna verificare sempre il valore di fopen:
un file in input potrebbe non esistere (o il percorso
essere sbagliato)
un file in output potrebbe non poter essere creato
(ad es. si trova su un disco o su una chiavetta USB
protetta dalla scrittura o il file da scrivere esiste già
ed è protetto dalla cancellazione – read only)
14
Apertura di un file
FOPEN_MAX (definito in <stdio.h>) indica il
minimo numero di file che il processo può
avere contemporaneamente aperti, a seconda
dei sistemi può essere intorno a 20, ma
normalmente se ne possono aprire di più
(potrebbe creare problemi di portabilità)
È possibile aprire contemporaneamente più file
È possibile aprire contemporaneamente più
volte lo stesso file, sia con lo stesso modo sia
in modi diversi, ma bisogna fare attenzione
alla bufferizzazione dei file in output (vedere
più avanti)
15
Apertura di un file
Lo schema tipico di apertura del file è:
printf("File da aprire: ");
gets(miofile);
fp=fopen(miofile,"r");
if (fp==NULL)
{
fprintf(stderr, "Errore fopen\n");
return EXIT_FAILURE;
}
stderr indica il video (descritto più avanti).
Poiché il nome del file potrebbe contenere
spazi e scanf("%s") si ferma al primo
spazio, è preferibile usare una gets
16
Apertura di un file
Una forma più compatta di apertura è la
seguente :
if ((fp=fopen(miofile,"r"))==NULL)
{
fprintf(stderr, "Errore fopen\n");
return EXIT_FAILURE;
}
Notare le parentesi evidenziate: permettono di
assegnare prima il valore restituito dalla
fopen a fp e poi di verificare se fp è NULL
17
Apertura di un file
Se il nome del file è noto a priori, lo si può
indicare come stringa costante nella fopen:
if ((fp=fopen("file.txt","r"))==NULL)
{
fprintf(stderr, "Errore fopen\n");
return EXIT_FAILURE;
}
ma è preferibile anche in questo caso (come
per i Magic numbers) utilizzare una #define:
#define MIOFILE "file.txt"
if ((fp=fopen(MIOFILE,"r"))==NULL)
18
Apertura di un file
Nei sistemi operativi Windows le parti di un
percorso (pathname o path) sono separate
dal carattere backslash '\'; se in una stringa
costante si indica un percorso, poiché in C il
backslash '\' è il carattere di escape, questo
deve essere raddoppiato:
"C:\\documenti\\file.txt"
Non è invece necessario raddoppiarlo se viene
inserito dall’utente (es. con una gets)
19
Chiusura di un file
Informa il Sistema Operativo che è terminato
l’utilizzo di un certo file
fclose(fp);
Libera le risorse di sistema legate a quel file
È automatica al termine del programma (ma è
buona norma chiuderlo non appena non serve
più, in particolare se file di output)
Nel caso di file di output, prima della chiusura
effettua automaticamente il flush dei buffer
(vedere più avanti)
20
Buffer di output
Poiché le operazioni su disco sono lente,
normalmente i dati diretti a file di output
vengono temporaneamente accumulati in
memoria in uno spazio detto buffer
Quando il buffer è pieno o si esegue una
fclose i dati vengono inviati tutti insieme sul
disco e viene svuotato (flush)
Se è necessario effettuare un flush del buffer
associato a fp si usa la funzione:
fflush(fp);
Per svuotare i buffer di tutti gli stream aperti in
output si può usare:
fflush(NULL);
22
Buffer di input
Per efficienza, in una lettura da disco il sistema
provvede a leggere più byte di quelli richiesti e
li colloca in un buffer, così le prossime letture
avvengono da questo e sono più veloci
Non esiste una funzione standard che permetta
di svuotare i buffer di input (ossia non si può
usare fflush su stream di input)
Alcuni compilatori permettono di svuotare i
buffer di input utilizzando fflush, ma se si
vuole un programma standard non la si usi e si
trovino soluzioni alternative che non richiedano
flush (non è detto esistano sempre)
26
La funzione feof
Per verificare se si è raggiunta la fine del file si
può anche usare la funzione feof), ma non è
il metodo preferibile
La funzione feof(fp) dà un risultato:
diverso da 0 se il file è terminato (è stato letto tutto)
pari a 0 se c’è almeno un byte da leggere (ma non
indica quanti)
Attenzione che in C la fine del file è rilevata
solo quando una funzione di input cerca di
leggere dal file e la feof non è una funzione di
input: non la si può usare per stabilire se un
file è vuoto prima di iniziare a leggerlo
36
La funzione feof
Per determinare se un file è vuoto, si può
provare a leggerlo e verificare se la lettura è
andata a buon file. Poi o si “rimette il carattere
nel file” con ungetc o si riporta l’offset
all’inizio del file con la funzione rewind:
if (fgetc(fp)==EOF)
{ fprintf(stderr, "File vuoto\n");
return EXIT_FAILURE;
}
rewind(fp);
while (!feof(fp)) /* ancora prob! */
{
fscanf(fp, "%d", &x);
printf("%d", x);
}
37
La funzione feof
Il ciclo while precedente ha ancora un
problema: se l’ultimo valore del file è seguito
da uno o più white spaces, la feof indica
(correttamente) che il file non è terminato,
quindi il ciclo continua e l’esecuzione della
fscanf non va a buon fine (dà EOF ma non
viene controllata): x non viene assegnata e
mantiene il valore precedente, quindi la
printf lo visualizza una volta di troppo
Per risolvere il problema si può controllare
anche la printf interna, ma è molto meglio
evitare feof e preferire la forma:
while (fscanf(fp, "%d", &x) == 1)
38
La funzione feof
Anche la lettura effettuata con fgetc può
creare problemi:
while (!feof(fp))
{
x=fgetc(fp);
istruzioni che utilizzano x
}
La fgetc legge un solo carattere e non cerca
di leggere oltre questo, quando legge l’ultimo
non può sapere che è proprio l’ultimo e quindi
permette erroneamente un’altra iterazione,
dove la fgetc restituisce EOF e questo viene
usato come – 1 dalle istruzioni che utilizzano x
39
La funzione feof
Per risolvere il problema quando è necessario
leggere singoli caratteri, si può utilizzare uno
schema come il seguente:
while ((x=fgetc(fp) != EOF)
elabora x;
Quando possibile, si eviti di usare feof
40
Stream preesistenti
Quando il programma viene mandato in
esecuzione, il sistema operativo gli fornisce 3
stream già aperti:
stdin collegato alla tastiera
stdout collegato al video
stderr collegato al video (non bufferizzato)
Vi sono due stream di output perché:
stdout dovrebbe essere utilizzato solo per
visualizzare i risultati dell’elaborazione
stderr dovrebbe essere utilizzato solo per la
diagnostica (gli errori del programma, non sono i
dati prodotti come risultato dell’elaborazione)
fprintf(stderr, "File vuoto");
43
Stream preesistenti
Si noti che:
fprintf(stdout,"%d",…);
equivale in tutto alla funzione
printf("%d",…);
L’interprete dei comandi della console può essere
impostato in modo da mandare su dispositivi di
output diversi gli stream dei risultati (stdout) e
quello degli errori (stderr) mediante la redirezione
dell’I/O, le modalità dipendono dal SO utilizzato, a
titolo di esempio in DOS/Windows/Linux/OSX si ha:
programma >file_per_stdout
programma 2>file_per_stderr
programma >file_per_stdout 2>file_per_stderr
programma >file_per_stdout_e_stderr 2>&1
44
Modalità di aggiornamento
L’apertura di un file in modalità di
aggiornamento permette la lettura e la
scrittura dello stesso file mediante lo stesso
file pointer (fp)
Modi di apertura in aggiornamento:
"r+" – lettura/scrittura, il file deve esistere
"w+" – lettura/scrittura, crea il file vuoto
"a+" – lettura/scrittura al fondo, lo crea se non
esiste, aggiunge alla fine se esiste già
La differenza tra "r+" e "w+" è solo
nell’apertura:
"r+" se il file non esiste dà errore
"w+" se il file esiste già, lo ricrea vuoto
53
Modalità di aggiornamento
Il modo "a+" non cancella il contenuto di un
file già esistente, posiziona il file position
pointer alla fine del file e permette di scrivere
solo in fondo al file
Anche se con fseek si sposta l’offset indietro,
la scrittura avviene sempre aggiungendo
record in fondo al file e i dati precedenti non
possono quindi essere modificati (neppure
quelli appena scritti, nemmeno se il file non è
stato ancora chiuso)
Con opportuna fseek è possibile leggere i
dati da qualsiasi punto del file
54
Modalità di aggiornamento
Tra un’operazione di scrittura e una di lettura
(o viceversa) sullo stesso file è necessario che
ci sia una chiamata a fflush o a una
funzione di posizionamento dell’offset quale:
rewind, fseek o fsetpos
Se si deve leggere dal file un qualcosa che è
possibile sia ancora nei buffer, è necessario
chiamare la funzione fflush
55
Considerazioni sull’efficienza
Tutte le operazioni di I/O relative ai file sono
intrinsecamente e notevolmente più lente di
quelle che usano solo la memoria, nonostante
la presenza di buffer e cache
Conviene ridurre il più possibile l’accesso al
file (ma se è molto piccolo potrebbe essere
contenuto interamente nel buffer e l’accesso
al file essere un mero accesso alla memoria)
Quando è necessario utilizzare molte volte i
dati contenuti in un file, conviene in genere
caricarne il contenuto in memoria in
opportune strutture dati
56
File temporanei
Quando è necessario creare un file temporaneo
per la sola durata dell’esecuzione del
programma, si possono usare le seguenti
funzioni:
tmpfile() – crea un file temporaneo in modalità
"wb+" (non è un file di testo ma binario!) e
restituisce un file pointer; viene chiuso o con
fclose o automaticamente a fine; NULL in caso
non riesca ad allocare
57
File temporanei
tmpnam(val) – crea un nome di file temporaneo e
ne restituisce il puntatore char*, NULL se fallisce
Se il parametro val è NULL alloca il nome in una
stringa costante (memoria statica)
Se viene passata come argomento una stringa,
viene memorizzato in essa (tmpnam può generare
al massimo TMP_MAX stringhe temporanee)
La stringa deve essere definita di lunghezza
L_tmpnam (macro definita in <stdio. h>):
char filetemp(L_tmpnam);
…
tmpnam(filetemp);
58
Esercizi
1. Scrivere con un editor un file contenente dei
numeri, uno per riga. Sviluppare un
programma che chieda all’utente il nome di
questo file, lo apra e calcoli e stampi a video
quanti sono i valori letti, il valore massimo, il
valore minimo, la somma e la media di tutti i
valori presenti nel file.
2. Scrivere un programma che chieda all’utente il
nome di un file di testo e conti quante righe e
quanti caratteri esso contiene (non si
considerino i caratteri di ritorno a capo ed
eventuale fine file).
59
Esercizi
3. Scrivere un programma che legga un file
contenente coppie di numeri interi (x e y), una
per riga e scriva un secondo file che contenga
le differenze x – y, una per riga.
Si supponga che il file di input non abbia errori
e che nomi dei file vengano chiesti all’utente.
File 1 File 2
23 32 -9
2 11 -9
19 6 13
23 5 18
3 2 1
… … …
60
Esercizi
4. Si scriva un programma che chieda il nome di
un file contenente un testo qualsiasi e di
questo conti quante sono le parole che
iniziano con ciascuna lettera dell’alfabeto.
Esempio di output:
Parole che iniziano con A: 45
Parole che iniziano con B: 12
Parole che iniziano con C: 27
…
Parole che iniziano con Z: 3
61
Esercizi
5. Un file di testo contiene, in ciascuna riga
separati da spazi, 2 o 3 valori reali:
1. nel primo caso si tratta della base e dell’altezza di
un triangolo
2. nel secondo caso si tratta della base minore, della
base maggiore e dall’altezza di un trapezio
Si scriva un programma che chieda all’utente
il nome di questo file, lo analizzi e indichi qual
è la riga del file corrispondente alla figura
geometrica di area maggiore e il valore di
quest’area. Se si trovano figure di aree uguali,
si consideri la prima di queste.
62
Esercizi
6. Scrivere un programma che legga da un file
un testo e spezzi su più righe quelle più
lunghe di N caratteri (N chiesto all’utente). La
riga viene spezzata anche in mezzo ad una
parola o prima di uno spazio. L’output deve
essere salvato in un altro file. Ad esempio, se
viene letta la riga seguente (è una sola, qui è
scritta su due righe perché troppo lunga):
Mi devo recare all’ufficio postale
precipitevolissimevolmente
l’output (con N pari a 22) deve essere:
Mi devo recare all’uf
ficio postale precipi
tevolissimevolmente
63
Esercizi
7. Come il precedente, ma le righe si possono
spezzare solo dove c’è uno spazio (che non va
visualizzato a capo), se negli N caratteri non
c’è uno spazio, si spezzi la parola all’N-esimo
carattere come nell’esercizio precedente.
Esempio di output con N pari a 22:
Mi devo recare
all’ufficio postale
precipitevolissimevolm
ente
64
Esercizi
8. Merge di due file
Due file di testo contengono, disposti
variamente su più righe, dei numeri interi.
Ciascuno dei due file è già ordinato in senso
CRESCENTE e non è noto a priori quanti
elementi contenga (possono anche avere un
numero di valori differente).
Si scriva un programma che legga questi due
file e ne produca un terzo contenente i valori
dei due file ordinati tutti quanti in senso
CRESCENTE. Non si utilizzino vettori né
algoritmi di ordinamento. I nomi dei tre file
vengano chiesti all’utente.
65
Esercizi
9. Il cifrario di Cesare
Si scriva un programma che cifri/decifri un file
di testo in base a un codice segreto. Il
programma deve chiedere il nome del file di
input, il nome del file di output e il codice
segreto (un numero intero). La cifratura
avviene sostituendo le sole lettere (maiuscole
e minuscole separatamente) con altre lettere
in base al valore del codice.
66
Esercizi
(Continuazione)
Il valore del codice indica di quanti caratteri si
deve traslare ciascuna delle lettere: se ad es.
il codice è 3, allora ‘A’ ‘D’, ‘B’ ‘E’, ...
‘X’ ‘A’, ‘Y’ ‘B’, ‘Z’ ‘C’.
La corrispondenza completa delle lettere
maiuscole (per le minuscole è analogo) è:
A B C D E F GH I J K L MNO P Q R S T U VWX Y Z
D E F GH I J K L MNO P Q R S T U VWX Y Z A B C
Ad esempio, con codice=3 “Ciao” diventa
“Fldr”. Si noti che per decifrare un testo
basta inserire come codice il valore opposto a
quello usato per la cifratura (qui –3).
67
Esercizi
10. Simile
al precedente, ma la sostituzione delle
lettere avviene secondo il seguente algoritmo.
Il codice segreto di cifratura/decifratura è
costituito da una parola composta di sole
lettere tutte diverse. La prima lettera del
codice viene fatta corrispondere alla lettera ‘A’,
la seconda alla ‘B’, e così via fino all’ultima
lettera del codice. Le lettere non indicate nel
codice vengono riordinate alfabeticamente al
contrario (dalla Z alla A) e fatte corrispondere
alle restanti lettere dell’alfabeto (maiuscole e
minuscole separatamente).
68
Esercizi
(Continuazione)
Il programma chiede l’introduzione di una
parola chiave da cui, eliminando i caratteri
duplicati e le non-lettere, si ha il codice
segreto. Ad esempio, dalla parola chiave
“Fragola” si ottiene il codice segreto
“FRAGOL” e la corrispondenza delle lettere è:
A B C D E F GH I J K L MNO P Q R S T U VWX Y Z
F RAGO L Z Y XWV U T S Q P NM K J I H E D C B
Ad esempio, la parola “Ciao” viene cifrata in
“Axfq” (notare maiuscole e minuscole).
Il programma deve chiedere se si vuole cifrare
o decifrare il file.
69
Esercizi
11. Si scriva un programma che chieda all’utente
il nome di un file contenente la griglia di valori
di un Sudoku risolto (N righe con N valori).
Il programma deve verificare ciascuna riga,
ciascuna colonna e ciascun riquadro e
segnalare le righe, le colonne e i riquadri che
non soddisfano le condizioni di validità.
Condizione di validità per righe, colonne e
riquadri: non ci devono essere valori ripetuti.
Il programma accetti automaticamente griglie
di lato massimo 25 numeri.
70
Esercizi
12. Unasuperficie viene descritta da una matrice
di valori in floating-point in doppia precisione
composta di R righe e C colonne (costanti
note a priori). Ogni elemento della matrice
corrisponde a un quadrato di dimensioni
1cm x 1cm. I valori rappresentano l’altezza
media di quella superficie in quel punto e
sono letti da un file. Tale file contiene C valori
(separati da spazi) per ciascuna delle R righe
e non ha errori. Il primo elemento della
matrice corrisponde alla parte di superficie
situata più a nord-ovest e ha coordinate (0,0).
71
Esercizi
(Continuazione)
Si noti che le coordinate X corrispondono
alle colonne, le Y alle righe.
Si suppone di voler simulare il comportamento
di una pallina che viene collocata su uno dei
riquadri della superficie (le cui coordinate sono
richieste all’utente) e poi lasciata libera. Il
programma deve visualizzare la sequenza
delle coordinate di tutte le posizioni che
vengono attraversate dalla pallina fino a
quando si ferma, inclusa la posizione iniziale,
con l’indicazione dell’altezza in quel punto.
72
Esercizi
(Continuazione)
Si usa un modello fisico semplificato dove la
pallina:
1. può scendere in ciascuna delle 8 caselle che la
circondano (se ci sono, ossia non è ai bordi)
2. scende lungo il cammino più ripido, ossia delle 8
caselle raggiunge quella più in basso (se ce ne
sono più di una all’altezza minima si scelga la
prima trovata)
3. non ha inerzia (cioè scende sempre lungo il
cammino più ripido)
4. non può/deve uscire dai limiti della superficie
5. si ferma quando non ci sono caselle più in basso
della posizione che occupa
73
Esercizi
(Continuazione)
Posizione iniziale
- coordinata X (1-20): 5
- coordinata Y (1-30): 3
Esercizi
13. Simodifichi l’esercizio precedente
considerando un modello in cui la pallina
possa spostarsi solo in diagonale
14. Come il precedente, ma la pallina possa
spostarsi solo in direzione nord/sud/est/ovest
(orizzontale/verticale).
75
Homework 5
Si scriva un programma per gestire un’agenda
elettronica. I dati relativi alle persone sono:
nome, cognome, indirizzo, telefono e
nota; NON possono contenere spazi e sono
contenuti in un file di testo denominato
database.txt. Viene presentata un’interfaccia
a menu per a) inserire, b) togliere, c) cercare
i dati relativi ad una persona (solo in base al
cognome), d) visualizzare tutte le coppie
nome/cognome senza dettagli ed e) uscire dal
programma. Le persone sono al massimo 200, i
dati devono essere copiati tutti in memoria
quando il programma parte e scritti solo alla fine.