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 ndim 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 ndim 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.