FILE DATI 16 lunedi 26 luglio giovedi ore 9
Come sappiamo, un file dati è un gruppo di informazioni tra loro
correlate, memorizzate in un dispositivo di memoria secondaria e
utilizzabili da un programma.
Dichiarazione (FILE). Ogni file dati è caratterizzato da un nome di
file, in genere non più lungo di 8 caratteri, seguito opzionalmente da
un punto e da una estensione di un massimo di 3 caratteri.
In un programma C, un file è sempre riferito da un nome di variabile
che deve essere dichiarata nel programma.
Tale variabile punta a una speciale struttura di file, e quindi deve
essere dichiarata come un puntatore a un file. Esempi di
dichiarazioni:
FILE *file_in;
FILE *prezzi;
Il nome del puntatore è il nome con il quale il programma farà
riferimento al file dati, e non deve essere necessariamente uguale al
nome esterno usato dal computer per memorizzare il file.
Il termine FILE è il nome tag di una speciale struttura dati usata dal C
per memorizzare le informazioni relative al file, tra le quali:
 se il file sia disponibile per lettura o scrittura;
 il prossimo carattere disponibile nel file:
 dove tale carattere sia memorizzato.
L’effettiva dichiarazione della struttura di un file e la sua equivalenza
al nome simbolico FILE sono contenute nel file d’intestazione
standard stdio.h, che deve essere incluso all’inizio di ogni
programma che usi un file dati.
Apertura (fopen()). L’apertura di un file è una procedura che
raggiunge due scopi, uno solo dei quali interessa direttamente il
programmatore:
1. Stabilisce un canale fisico di comunicazione tra il programma e il
file dati: dato che i dettagli di questo collegamento sono gestiti dal
sistema operativo e sono trasparenti al programma, il
programmatore di solito non li considera.
2. Uguaglia il nome del file esterno al computer al nome puntatore
usato internamente dal programma.
La funzione per l’apertura di un file è chiamata fopen(); dato che la
sua dichiarazione è inserita nel file d’intestazione stdio.h, esso
va incluso in ogni programma che usi fopen().
Vediamo come vada chiamata fopen() per collegare il nome
esterno di un file a quello interno.
Per usare fopen() occorre passarle due argomenti, racchiusi tra
doppi apici:
 il nome del file per il computer;
 il modo in cui il file va aperto.
I modi principali sono w, a, r, quelli secondari sono w+, a+, r+,
che hanno i seguenti significati:
L’apertura di un file per scrittura crea un nuovo file e lo rende
disponibile per l’uscita della funzione che lo ha aperto.
Ad es., l’istruzione:
out_file = fopen(“prezzi.dat”, “w”);
crea un file di nome esterno prezzi.dat in cui è possibile scrivere,
e permette al programma di accedervi usando il puntatore interno di
nome out_file.
#include <stdio.h>
.
out_file
.
prezzi.dat
se esiste già un file con il nome di uno
aperto per scrittura, esso viene cancellato.
L’apertura per aggiunta di un file esistente consente di aggiungervi
dati alla fine.
Se il file aperto per aggiunta non esiste, ne viene creato uno con il
nome indicato, in grado di ricevere l’uscita dal programma. Ad es.,
l’istruzione:
out_file = fopen(“prezzi.dat”, “a”);
apre il file prezzi.dat e lo rende disponibile per l’aggiunta di dati
alla sua fine.
L’unica differenza tra l’apertura un file in modo scrittura e in modo
aggiunta è il posto in cui i dati sono fisicamente messi nel file:
• nel modo scrittura sono scritti a partire dall’inizio del file,
• nel modo aggiunta sono scritti a partire dalla fine.
Per un file nuovo, i due modi sono identici.
Funzioni di scrittura (fputc(), fputs(), fprintf()). Per
scrivere i dati in un file aperto in modo scrittura o aggiunta si usano
le funzioni di biblioteca indicate in tabella, che sono simili a quelle
usate per visualizzare i dati sullo schermo (putchar(), puts(),
printf()).
In ciascuna di esse il nome-di-file è il nome del puntatore interno
specificato quando il file è stato aperto.
Ad es., se out_file è il nome di puntatore interno di un file aperto
in modo scrittura o aggiunta, sono valide le seguenti istruzioni di
uscita:
fputc(‘a’, out_file);
fputs(“Salve tutti”, out_file);
fprintf(out_file, “%s %d”, descriz, prezzo);
Osservazione. Le funzioni
fputc()
fputs()
fprintf()
sono usate nello stesso modo delle loro equivalenti
putchar() puts() printf()
con l’aggiunta di un nome-di-file come argomento. Esso dirige
l’uscita a un file specifico, anziché al dispositivo di uscita standard che
è lo schermo.
I prototipi di fputc(), fputs(), fprintf() sono contenuti nel file
stdio.h.
Chiusura (fclose()). Dopo essere stato aperto, un file va chiuso
con la funzione fclose(), che ha come argomento il nome del
puntatore usato quando il file è stato aperto.
Essa rompe il legame tra i nomi esterno e interno del file rilasciando il
nome del puntatore al file interno, che quindi può essere usato per
un altro file. Ad es., l’istruzione
fclose(out_file);
chiude il file puntato da out_file.
Dato che il numero di file che possono rimanere aperti
contemporaneamente è limitato, è bene chiudere i file non più
necessari (tutti i file aperti sono chiusi automaticamente dal sistema
operativo alla fine della normale esecuzione del programma).
Scrittura da programma. Il programma seguente illustra l‘uso della
funzione di scrittura file fprintf() per scrivere in un file un
elenco di descrizioni e prezzi contenuti nel programma stesso.
#include <stdio.h>
void main(void)
{
int i;
FILE *out_file;
/* dichiarazione di FILE */
float prezzo[] = {39.95, 3.22, 1.03};
char *descriz[] = {"Batterie", "Bulbi", "Fusibili"};
out_file = fopen("prezzi.dat","w");
/* apre il file */
if(out_file == NULL)
{
printf("\nErrore nell’apertura del file.\n");
exit(1);
}
for(i = 0; i < 3; ++i)
fprintf(out_file,"%-9s %5.2f\n", descriz[i], prezzo[i]);
fclose(out_file);
}
Se per qualche ragione non è possibile aprire il file, il puntatore
out_file assume il valore NULL (già visto nel caso delle liste
concatenate), viene visualizzato un messaggio di errore e la funzione
exit() passa il suo argomento al sistema operativo, determinando
la fine della esecuzione del programma.
Se invece il file viene aperto, viene creato e salvato il file prezzi.dat
consistente nelle tre linee seguenti:
Batterie 39.95
Bulbi
3.22
Fusibili 1.03
La sequenza di controllo di conversione %-9s nella chiamata alla
funzione printf() forza la giustificazione a sinistra delle
descrizioni in un campo di 9 caratteri.
Analogamente, i prezzi sono giustificati a destra in un campo di 5
caratteri, a partire da uno spazio dopo la fine del campo descrizioni.
Sebbene sembri che nel file siano memorizzati 42 caratteri,
corrispondenti alle descrizioni, ai caratteri vuoti e ai prezzi scritti nel
file, esso in effetti ne contiene 46.
I caratteri aggiuntivi consistono in sequenze di escape di linea nuova
alla fine di ogni linea, e in uno speciale marcatore di fine-file posto
come ultimo carattere nel file quando esso viene chiuso.
La memorizzazione effettiva dei caratteri nel file prezzi.dat è
costituita dai loro codici ASCII, come indicato in figura. Il codice per il
marcatore di fine-file ^Z dipende dal sistema usato: uno molto
comune è il codice esadecimale 26.
42
B
61
a
74
t
74
t
65
e
72
r
69
i
65
e
20
33
3
39
9
2E
.
32
2
35
5
0A
\n
42
B
75
u
6c
l
62
b
69
i
20
20
20
20
20
33
3
2E
.
32
2
32
2
0A
\n
46
F
75
u
73
s
69
i
62
b
69
i
6c
l
69
i
20
20
31
1
2E
.
30
0
33
3
0A
\n
26
^Z
Osservazione. Le due istruzioni
out_file = fopen("prezzi.dat","w");
if(out_file == NULL)
possono essere sostituite dall’unica istruzione
if((out_file = fopen("prezzi.dat","w")) == NULL)
L’esecuzione del programma non produce alcuna uscita sullo
schermo, e infatti la lettura del file andrà eseguita con un programma
specifico.
Lettura. L’apertura di un file per lettura richiama un file esistente e
ne rende i dati disponibili come ingresso per il programma.
Ad es., l’istruzione di apertura:
in_file = fopen(“prezzi.dat”, “r”);
apre il file prezzi.dat e rende i suoi dati disponibili per l’ingresso.
All’interno della funzione che apre il file, esso è letto usando il nome
di puntatore in_file.
Se un file aperto per lettura non esiste, fopen() restituisce il valore
di indirizzo NULL. Esso può essere usato per controllare se sia
stato aperto un file esistente.
Osservazione. Nelle precedenti istruzioni di apertura sia il nome di
file esterno, sia gli argomenti relativi al modo passati a fopen()
erano stringhe racchiuse tra doppi apici.
Se il nome di file esterno è dapprima memorizzato in un vettore di
caratteri o come una stringa, si può usare il nome di vettore o la
stringa, senza apici, come primo argomento per fopen().
Apertura. Il programma seguente illustra:
• le istruzioni necessarie per aprire un file in modo lettura: chiede
all’utente il nome di un file dati esterno e lo memorizza nel vettore
di caratteri nome_f; passa quindi questo nome di vettore alla
funzione fopen().
• l’uso del valore restituito da fopen() per controllare la corretta
apertura del file: l’utente viene avvertito se il nome file esterno non
esiste.
#include <stdio.h>
void main(void)
{
FILE *in_file;
char nomefile[12];
printf("\nScrivi il nome del file da leggere: ");
gets(nomefile);
/* oppure: scanf(“%s”, nomefile); */
in_file = fopen(nomefile, "r");
/* apre il file */
if (in_file == NULL)
{
printf("\nIl file non può essere aperto.");
printf("\nControlla che il file esista.");
exit(1);
}
printf("\nIl file è stato aperto per la lettura");
}
Ecco un esempio della sua esecuzione:
Scrivi un nome di file: pippo
Il file non può essere aperto.
Controlla che il file esista.
Osservazione. Anche adesso le istruzioni per aprire il file e
assegnare l’indirizzo restituito possono essere inserite direttamente
nell’istruzione if, sostituendo le due linee
in_file = fopen(nomefile, "r");
if (in_file == NULL)
con l’unica espressione
if ((in_file = fopen(nomefile, “r”)) == NULL)
Il programma precedente non contiene ancora le istruzioni
necessarie per leggere i dati nel file e per chiuderlo.
Funzioni di lettura (fgetc(), fgets(), fscanf()). La lettura dei
dati da un file è quasi identica a quella da una tastiera standard, con
l’aggiunta del nome-di-file per indicare la loro provenienza.
Le funzioni disponibili per leggere i dati da un file sono simili alle
funzioni getchar(), gets(), scanf() usate per l’ingresso dei
dati da tastiera, e sono indicate in tabella.
Ad es., se in_file è il nome interno di un puntatore a un file aperto
in modo lettura, per leggere dati dal file si possono usare le seguenti
espressioni:
fgetc(in_file);
/* legge il prossimo carattere nel file */
fscanf(in_file, “%f”, &prezzo);
/* legge un numero in virgola mobile */
fgets(messaggio, 10, in_file);
/*legge i 9 caratteri successivi dal file in messaggio*/
Tutte le funzioni di ingresso rilevano correttamente il marcatore di finefile; tuttavia, quando esso viene rilevato,
 le funzioni fgetc() e fscanf() restituiscono la costante di
nome EOF;
 la funzione fgets() restituisce il carattere NULL (\0).
Entrambe le costanti EOF e NULL sono sentinelle utili per rilevare la
fine di un file che viene letto, a seconda della funzione usata.
Uso di fgetc(). Come esempio di uso di fgetc(), scriviamo un
programma che visualizzi i codici ASCII dei caratteri memorizzati nel
file prezzi.dat. Per visualizzare i codici in formato esadecimale
usiamo la sequenza di controllo di conversione %x.
#include <stdio.h>
void main(void)
{
char var;
FILE *in_file;
if((in_file = fopen("prezzi.dat", "r")) == NULL)
{
printf("\nErrore nell'apertura del file.\n");
exit(1);
}
do
{
var = fgetc(in_file);
printf("%x %c", var, ' ');
}
while (var != EOF);
fclose(in_file);
}
Esercizio. Modificare il programma precedente, in modo che chieda
il nome del file da leggere e ne visualizzi i caratteri.
Ecco una possibile risposta.
#include <stdio.h>
void main(void)
{
char var, nomefile[12];
FILE *in_file;
printf(“Scrivi il nome del file da leggere”);
scanf(“%s”, nomefile);
/* oppure: gets(nomefile); */
if((in_file = fopen(nomefile, "r")) == NULL)
{
printf("\nErrore nell'apertura del file.\n");
exit(1);
}
do
{
var = fgetc(in_file);
printf("%c", var);
}
while (var != EOF);
fclose(in_file);
}
Se invece si vogliono leggere tutti i caratteri presenti in un file,
compresi quelli privi di “significato”, occorre leggere il file in
modalità binaria.
Il programma seguente legge in modalità binaria il file salve.doc,
contenente la frase “Salve a tutti” e creato con Microsoft
Word, di dimensione 24 kbyte.
#include <stdio.h>
void main(void)
{
char var;
int i;
FILE *in_file;
if((in_file = fopen("salve.doc","rb")) == NULL)
{
printf("\nErrore nell'apertura del file.\n");
exit(1);
}
for(i = 0; i < 24000; i++)
{
var = fgetc(in_file);
printf("%c", var);
}
fclose(in_file);
}
Uso di fscanf(). La lettura dei dati tramite fscanf() richiede
che si conosca il formato in cui essi sono memorizzati nel file
(esattamente come succedeva con scanf()). Ciò per una corretta
“strisciatura” dei dati dal file in variabili adatte per la
memorizzazione.
Tutti i file sono letti in modo sequenziale, cosicché, una volta letta una
voce, la successiva nel file diventa disponibile per la lettura.
Il programma seguente illustra la lettura del file prezzi.dat tramite
fscanf(): si osservi l’uso del marcatore EOF, che viene restituito
da fscanf() quando s’incontra la fine del file.
Lettura con fscanf()
#include <stdio.h>
void main(void)
{
char descriz[10];
float prezzi;
FILE *in_file;
if((in_file = fopen("prezzi.dat","r")) == NULL)
{
printf("\nErrore nell’apertura del file.\n");
exit(1);
}
while (fscanf(in_file,"%s %f",descriz, &prezzi) != EOF)
printf("%-9s %5.2f\n",descriz, prezzi);
fclose(in_file);
}
Il programma continua a leggere il file fino a che s’incontra il
marcatore EOF.
Ogni volta che si legge il file, sono immessi nel programma una
stringa e un numero in virgola mobile.
Ecco la visualizzazione prodotta:
Batterie 39.95
Bulbi
3.22
Fusibili 1.03
Uso di fgets(). In luogo della funzione fscanf() si può usare la
funzione fgets(), che richiede tre argomenti:
 l’indirizzo dove sarà memorizzato il primo carattere letto;
 il numero massimo di caratteri da leggere;
 il nome del file d’ingresso.
Ad es., la chiamata di funzione:
fgets(linea, 81, in_file);
determina la lettura di un massimo di 80 caratteri (1 in meno del
numero indicato) dal file in_file e la loro memorizzazione a
partire dall’indirizzo contenuto nel puntatore di nome linea.
fgets() continua a leggere i caratteri fino a che
• ne sono stati letti 80, oppure
• s’incontra un carattere di linea nuova (\n).
Nel secondo caso, il carattere viene incluso con gli altri caratteri
immessi prima che la stringa sia terminata con il marcatore di finestringa \0.
Anche fgets() rileva il marcatore di fine-file, ma quando lo
incontra restituisce il carattere NULL.
Il programma seguente illustra l’uso di fgets() in un programma
effettivo.
Lettura con fgets()
#include <stdio.h>
void main(void)
{
char linea[81];
FILE *in_file;
if((in_file = fopen("prezzi.dat", "r")) == NULL)
{
printf("\nImpossibile aprire il file.\n");
exit(1);
}
while (fgets(linea, 81, in_file) != NULL)
printf("%s", linea);
fclose(in_file);
}
Questo programma di lettura è in realtà un programma di copia di un
testo linea per linea, che legge una linea di testo dal file e la
visualizza sullo schermo. Perciò la sua uscita è identica a quella
del programma di lettura con fscanf().
Però, a differenza di esso, non usa le variabili individuali
descrizione e prezzo per memorizzare i valori.
Se ciò fosse necessario, si dovrebbe elaborare ulteriormente la
stringa restituita da fgets() tramite la funzione di scansione di
stringa, scanf().
Per estrarre la descrizione e il prezzo dalla stringa
memorizzata nel vettore di caratteri linea si potrebbe, ad es.,
aggiungere l’istruzione
scanf(linea, “%s %f”, descriz, &prezzo);
Ciò, naturalmente, dopo la dichiarazione delle variabili descriz e
prezzo:
char descriz[10];
float prezzo;
File standard di dispositivo (stdin, stdout). I puntatori a file di
dati che abbiamo usato erano tutti puntatori a file logici.
Un puntatore a un file logico fa riferimento a un file o a un dato
collegato che è stato salvato sotto un nome comune; cioè “punta
a” un file di dati.
In aggiunta ai puntatori a file logici, i C supporta anche puntatori a
file fisici.
Un puntatore a un file fisico “punta a” un dispositivo hardware quale la
tastiera, lo schermo o una stampante.
L’effettivo dispositivo fisico assegnato a un programma per l’ingresso
dei dati è detto formalmente file d’ingresso standard, ed è di solito la
tastiera.
Quando in un programma C s’incontra una chiamata alla funzione
scanf(), il computer va automaticamente a questo file d’ingresso
standard per l’ingresso atteso.
Analogamente, quando s’incontra una chiamata alla funzione
printf(), l’uscita è automaticamente visualizzata o “scritta su” un
dispositivo che è stato assegnato come file standard di uscita, in
genere lo schermo (ma talvolta una stampante).
Quando si esegue un programma, la tastiera usata per l’ingresso dei
dati è automaticamente aperta e assegnata al puntatore di file
interno di nome stdin.
Analogamente, il dispositivo di uscita usato per visualizzare è
assegnato al puntatore di file di nome stdout. Questi puntatori di
file sono sempre disponibili per l’uso del programmatore.
Le somiglianze tra printf() e fprintf(), e tra scanf() e
fscanf() non sono accidentali:
• printf() è un caso speciale di fprintf() che ha come
default il file di uscita standard, mentre
• scanf() è un caso speciale di fscanf() che ha come default il
file d’ingresso standard.
Così, si ottiene la stessa visualizzazione con le due espressioni:
fprintf(stdout, “Salve a tutti”);
e
printf(“Salve a tuti”);
come pure sono tra loro equivalenti le espressioni:
fscanf(stdin, “%d”, &num);
e
scanf(“%d”, &num);
Puntatore di file (stderr). Oltre ai puntatori di file stdin e
stdout, esiste un terzo puntatore, di nome stderr, assegnato al
dispositivo di uscita usato per i messaggi di sistema.
Sebbene stderr e stdout si riferiscano frequentemente allo
stesso dispositivo, l’uso di stderr fornisce un mezzo per
reindirizzare qualsiasi messaggio di errore dal file usato per la
normale uscita del programma.
Proprio come printf() e scanf() sono casi speciali
rispettivamente di fprintf() e fscanf(), anche le funzioni
getchar() e gets(), putchar() e puts() sono casi
speciali delle funzioni più generali elencate in tabella.
Le coppie di funzioni di carattere sulla stessa riga (evidenziate in verde)
possono essere usate in modo intercambiabile le une con le altre.
Ciò non è vero per le funzioni che manipolano le stringhe (evidenziate
in rosa), per le seguenti ragioni.
Confronto tra fgets()/gets(). . .
In fase d’ingresso, come osservato, la funzione fgets() legge i dati
da un file fino a una sequenza di escape di linea nuova (\n), o fino a
che sia stato letto uno specificato numero di caratteri.
Come sappiamo, la funzione gets()non memorizza nella stringa
finale la sequenza di escape di linea nuova (\n) (dato che la traduce
in un carattere NULL (\0)).
Invece, come abbiamo visto nel programma di lettura con fgets(),
se fgets() incontra una sequenza di escape di linea nuova
(\n) la memorizza insieme agli altri caratteri immessi.
Entrambe le funzioni terminano i caratteri immessi con un carattere
NULL di fine-stringa.
. . . e puts()/fputs()
In fase di uscita, sia puts() sia fputs() scrivono tutti i caratteri
nella stringa, tranne il carattere terminale di fine-stringa NULL.
Come sappiamo, la funzione puts() aggiunge automaticamente
una sequenza di escape di linea nuova (\n) alla fine dei caratteri
trasmessi.
Invece
fputs() non aggiunge la sequenza \n
alla fine dei caratteri trasmessi.
Scrittura di un file da tastiera (feof()). Mentre il precedente
programma di scrittura scriveva in un file i dati contenuti nel
programma stesso, di solito i dati vengono forniti in modo interattivo
da tastiera.
Il programma seguente crea il file ad accesso sequenziale
cliente.dat relativo alla situazione contabile dei clienti di una
banca, nel quale memorizza i dati relativi a numero di conto, nome e
saldo di ciascun cliente, immessi da tastiera.
Per terminare l’immissione dei dati si deve immettere il carattere di finefile (^Z in ambiente Windows).
I dati di ogni cliente costituiscono il suo “record”, mentre il numero di
conto costituisce la “chiave” del record, ossia il campo rispetto al
quale il file viene ordinato.
#include <stdio.h>
int main()
{
int conto;
char nome[30];
float saldo;
FILE *out_file;
if((out_file = fopen("cliente.dat","w")) == NULL)
printf("Impossibile aprire il file\n");
else
{
printf("Scrivi conto, nome, saldo (^Z per teminare).\n");
scanf("%d %s %f", &conto, nome, &saldo);
while(!feof(stdin))
{
fprintf(out_file, "%d %s %f\n", conto, nome, saldo);
scanf("%d %s %f", &conto, nome, &saldo);
}
fclose(out_file);
}
return(0);
}
Il programma contiene l’espressione
while(!feof(stdin))
che determina l’elaborazione di un nuovo record se in precedenza
non è stato immesso da tastiera il carattere di fine-file.
Per determinare la presenza del carattere di fine-file nel file al quale
punta stdin (ossia la tastiera), si è usata la funzione feof().
Essa ha come argomento il puntatore al file nel quale si vuole
esaminare la presenza dell’indicatore di fine-file (come detto, il file
è stdin, ossia la tastiera).
La funzione feof() restituisce un valore diverso da 0 (vero)
quando rileva nel file la presenza dell’indicatore di fine-file,
altrimenti restituisce 0.
Lettura del file immesso da tastiera. Se eseguiamo il programma
precedente, possiamo verificare che i record siano stati
memorizzati correttamente nel file eseguendo il programma
seguente, che legge il file cliente.dat e ne visualizza il
contenuto.
#include <stdio.h>
int main()
{
int conto;
char nome[30];
float saldo;
FILE *out_file;
if((out_file = fopen("cliente.dat", "r")) == NULL)
printf("Il file non può essere letto\n");
else
{
printf("%-10s %-16s %-12s\n", "Conto", "Nome", "Saldo");
fscanf(out_file, "%d %s %f", &conto, nome, &saldo);
while(!feof(out_file))
{
printf("%-10d %-13s %7.2f\n", conto, nome, saldo);
fscanf(out_file, "%d %s %f", &conto, nome, &saldo);
}
fclose(out_file);
}
return(0);
}
File offset. La struttura FILE, associata a ogni file, ha tra i suoi
membri il puntatore di posizione del file, detto anche file offset. Esso
non è un puntatore vero e proprio, ma un numero intero che specifica
la posizione del byte in corrispondenza del quale sarà eseguita la
prossima lettura o scrittura.
Il file offset viene utilizzato dalle funzioni di biblioteca standard
rewind(), fseek() e ftell().
Funzione rewind(). Di solito, per recuperare in modo sequenziale i
dati di un file, un programma incomincia dal suo inizio e legge tutti i
dati in modo consecutivo, finché non siano stati trovati quelli
desiderati.
Durante l’esecuzione di un programma potrebbe essere necessario
rielaborare più volte in modo sequenziale i dati di un file ripartendo
dal suo inizio. A tale scopo si usa una istruzione del tipo
rewind(in_file);
che riposiziona all’inizio (ossia al byte 0) del file puntato da in_file
il puntatore di posizione del file. In tal modo il prossimo carattere a cui
si avrà accesso sarà il primo nel file.
Quando si apre un file in modo lettura, viene eseguita automaticamente
una rewind().
Il programma seguente usa rewind() per trovare nel file
cliente.dat e visualizzare sullo schermo i record nei quali il saldo
sia zero o positivo, a seconda della scelta (1 o 2) dell’utente.
#include <stdio.h>
int main()
{
int scelta, conto;
float saldo;
char nome[30];
FILE *in_file;
if((in_file=fopen("cliente.dat", "r")) == NULL)
printf("Il file non può essere letto\n");
else
{
printf("Scegli una richiesta\n1 - Elenca i conti con
saldo 0\n2 - Elenca i conti con saldo positivo\n3 - Fine
del programma\n? ");
scanf(“%d", &scelta);
while(scelta != 3)
{
fscanf(in_file, "%d %s %f", &conto, nome, &saldo);
switch(scelta)
{
case 1:
printf("Conti con saldo zero:\n");
while(!feof(in_file))
{
if(saldo == 0)
printf("%-10d %-13s %7.2f\n", conto, nome, saldo);
fscanf(in_file, "%d %s %f", &conto, nome, &saldo);
}
break;
case 2:
printf("Conti con saldo positivo:\n");
while(!feof(in_file))
{
if(saldo > 0)
printf("%-10d %-13s %7.2f\n", conto, nome, saldo);
fscanf(in_file, "%d %s %f", &conto, nome, &saldo);
}
break;
}
rewind(in_file);
printf("\n? ");
scanf("%d", &scelta);
}
printf("Fine del programma.\n");
fclose(in_file);
}
return(0);
}
Funzione fseek(). La funzione fseek()
consente di muoversi in qualsiasi posizione nel file.
Per comprenderne il funzionamento, ricordiamo come avviene il
riferimento ai dati in un file.
Ogni carattere in un file dati è localizzato tramite la sua posizione nel
file: il primo nella posizione 0, il secondo nella posizione 1 e così
via.
La posizione di un carattere è detta anche il suo offset dall’inizio del
file. Così, il primo carattere ha offset 0, il secondo offset 1 e così
via.
La funzione fseek() richiede 3 argomenti:
 il nome puntatore del file;
 l’offset, in forma di intero lungo;
 la posizione a partire dalla quale va calcolato l’offset.
La forma generale di fseek() è la seguente:
fseek(nome-di-file, offset, origine)
I valori dell’argomento origine possono essere 0, 1 o 2, che
sono definiti in stdio.h come le costanti di nomi rispettivamente
SEEK_SET, SEEK_CUR e SEEK_END.
Un offset positivo fa muovere in avanti nel file; uno negativo fa muovere
indietro.
Ecco alcuni esempi di fseek() (nei quali in_file è il nome del
puntatore di file usato quando il file dati è stato aperto):
Funzione ftell(). La funzione ftell()
restituisce il valore di offset del prossimo
carattere che sarà letto o scritto.
Ad es., se sono stati già letti 10 caratteri dal file in_file, la
chiamata di funzione
ftell(in_file);
restituisce l’intero lungo 10. Ciò significa che il prossimo carattere
che sarà letto è alla posizione 10 byte di offset dall’inizio del file,
ossia è l’11° carattere del file.
Anche il prototipo di ftell() è contenuto in stdio.h.
Il programma seguente mostra l’uso congiunto di fseek() e
ftell() per leggere un file in ordine inverso dall’ultimo carattere al
primo, e visualizza ogni carattere letto.
#include <stdio.h>
void main(void)
{
int ch, n;
long int offset, ult, ftell();
FILE *in_file;
if((in_file = fopen(“prezzi.dat","r")) == NULL)
{
printf("\nErrore nell’aprire il file prezzi.dat.\n");
exit(1);
}
fseek(in_file, 0L, SEEK_END);
ult = ftell(in_file); /* salva l’offset dell’ultimo caratt. */
for(offset = 0; offset <= ult; ++offset)
{
fseek(in_file, -offset, SEEK_END); /* va al caratt. preced. */
ch = getc(in_file);
/* riceve il carattere */
switch(ch)
{
case '\n': printf("LF : ");
break;
case EOF : printf("EOF: ");
break;
default : printf("%c : ",ch);
break;
}
}
fclose(in_file);
}
Questo programma va inizialmente all’ultimo carattere nel file, che è
quello di fine-file, e salva il suo offset nella variabile ult (dato che
ftell() restituisce un intero lungo, ult è stata dichiarata come
intero lungo).
A partire dalla fine del file, si usa fseek() per posizionare il
prossimo crattere da leggere, con riferimento alla fine del file.
Via via che ogni carattere è letto, esso è visualizzato e l’offset
modificato in modo da accedere al prosimo carattere.
File ad accesso casuale
Con organizzazione di un file s’intende il modo con cui i dati vi sono
memorizzati.
Tutti i file usati finora avevano una organizzazione sequenziale, nel
senso che i caratteri erano memorizzati sequenzialmente, uno dopo
l’altro. Inoltre, abbiamo anche letto il file in modo sequenziale.
Il modo in cui si accede ai dati di un file è detto accesso al file.
Tuttavia, il fatto che i dati in un file siano memorizzati in modo
sequenziale non ci costringe anche ad accedere al file in modo
sequenziale.
Nell’accesso casuale ogni carattere nel file può essere letto
immediatamente, senza che sia necessario leggere prima tutti i
caratteri precedenti.
I record di un file creati con l’uscita formattata della funzione
fprintf() non hanno tutti necessariamente la stessa lunghezza.
Invece tutti i record di un file ad accesso casuale hanno la stessa
lunghezza, cosicché la posizione ciascuno di essi, relativamente
all’inizio del file, può essere calcolata con una funzione della chiave
del record.
Ciò consente di accedere a ogni record in modo diretto (e quindi più
velocemente) senza passare attraverso i precedenti.
Per questa ragione i file ad accesso casuale sono particolarmente
appropriati per i sistemi per l’elaborazione di transazioni che
richiedano un accesso rapido a dati specifici.
La figura seguente illustra un modo di implementare un file ad accesso
casuale: un file di questo tipo è come un treno merci con molti vagoni,
alcuni vuoti e altri con un carico, ognuno dei quali ha la stessa
lunghezza.
In un file ad accesso casuale i nuovi dati possono essere inseriti senza
distruggere quelli già immagazzinati; questi ultimi possono anche
essere aggiornati o eliminati, senza che si debba riscrivere l’intero file.
Creazione di un file ad accesso casuale (fwrite())
La funzione fwrite() scrive in un file un numero specificato di byte,
a partire da quello indicato dal puntatore di posizione del file.
La funzione fread() trasferisce un numero specificato di byte dalla
posizione indicata dal file offset a un’area di memoria che comincia a
un indirizzo specificato.
A questo punto, per scrivere un intero, invece di usare
fprintf(in_file, “%d”, numero);
che può stampare da un minimo di 1 a un massimo di 11 caratteri (10
cifre più il segno), ognuno dei quali richiede un byte di memoria, per
un intero di 4 byte possiamo usare:
fwrite(&numero, sizeof(int), 1, in_file);
che scrive in ogni caso 4 byte* dalla variabile numero al file puntato
da in_file. In seguito useremo fread() per leggere 4 di quei
byte nella variabile intera numero.
*o 2, su sistemi con interi di 2 byte
Se, da una parte, fread() e fwrite() leggono e scrivono dei
dati come gli interi in un formato con dimensione fissa, anziché
variabile, dall’altro i dati che esse gestiscono sono elaborati nel
formato “dati grezzi”, ossia in byte di dati, anziché nel formato
umanamente intelligibile di printf() e scanf().
Le funzioni fwrite() e fread() sono in grado di leggere e
scrivere su disco interi vettori di dati; in effetti il loro terzo
argomento è appunto il numero di elementi del vettore che
dovranno essere letti dal disco, o che vi dovranno essere scritti.
La precedente chiamata della funzione fwrite() scrive su disco
un singolo intero, dato che il terzo argomento è 1 (come per
scrivere un solo elemento di un vettore).
Di solito i programmi per l’elaborazione dei file non scrivono un solo
campo all’interno di un file, ma una intera struttura per volta, come
mostrato nell’esempio che segue.
Esempio. Vogliamo creare un sistema in grado di memorizzare fino
a 100 record di lunghezza fissa; ognuno dovrà consistere in:
• un numero (intero) di conto, che sarà usato come chiave del
record
• un cognome
• un nome
• un saldo.
Dovremo scrivere tre distinti programmi, in grado rispettivamente di:
1.creare il file dove saranno memorizzati i vari record
2.aggungere un record nuovo, aggiornarne o cancellarne uno
esistente
3.elencare tutti i record in un file di testo formattato per la
visualizzazione.
Il programma seguente mostra come:
•
•
•
•
aprire un file ad accesso casuale
definire un formato di record usando una struttura,
scrivere i dati sul disco e
chiudere il file.
Esso inizializza i 100 record del file credito.dat con una
struttura vuota usando la funzione fwrite(). Ogni struttura vuota
contiene 0 per il numero di conto, una “” (stringa vuota) per il
cognome e una per il nome, e 0 per il saldo.
Il file viene inizializzato in questo modo per creare sul disco lo spazio
nel quale sarà memorizzato, e per consentire di determinare se un
record contenga dei dati.
#include <stdio.h>
struct Dati_cli
{
int num_conto;
char cognome[15];
char nome[10];
int saldo;
};
int main()
{
int i;
struct Dati_cli vuoto = {0, "", "", 0.0};
FILE *out_file;
if((out_file = fopen("credito.dat", "wb")) == NULL)
{
printf("Impossibile creare il file.\n");
close(1);
}
else
{
for(i=1; i<=100; i++)
fwrite(&vuoto, sizeof(struct Dati_cli), 1, out_file);
fclose(out_file);
}
return(0);
}
Scrittura di un file ad accesso casuale
Il seguente programma consente di scrivere e di modificare dei
record nel file ad accesso casuale credito.dat appena creato.
#include <stdio.h>
struct Dati_cli
{
int num_conto;
char cognome[15];
char nome[10];
int saldo;
};
int main()
{
struct Dati_cli cliente = {0, "", "", 0.0};
FILE *out_file;
if((out_file = fopen("credito.dat", "rb+")) == NULL)
{
printf("Impossibile aprire il file.\n");
close(1);
}
else
{
printf("Scrivi un n° di conto (da 1 a 100, 0 per finire)\n? ");
scanf("%d", &cliente.num_conto);
while(cliente.num_conto != 0)
{
printf("Scrivi Cognome, Nome, saldo\n? ");
fscanf(stdin, "%s%s%d", cliente.cognome, cliente.nome, &cliente.saldo);
fseek(out_file,(cliente.num_conto-1)*sizeof(struct Dati_cli),SEEK_SET);
fwrite(&cliente, sizeof(struct Dati_cli), 1, out_file);
printf("Scrivi un n° di conto (0 per finire)\n? ");
scanf("%d", &cliente.num_conto);
}
fclose(out_file);
}
return(0);
}
Lettura di un file ad accesso casuale
Il seguente programma consente di leggere i record memorizzati nel
file ad accesso casuale credito.dat appena creato.
#include <stdio.h>
struct Dati_cli
{
int num_conto;
char cognome[15];
char nome[10];
int saldo;
};
int main()
{
struct Dati_cli cliente = {0, "", "", 0.0};
FILE *out_file;
if((out_file = fopen("credito.dat", "rb")) == NULL)
{
printf("Impossibile aprire il file.\n");
close(1);
}
else
{
printf("%-6s %-16s %-11s %10s \n", "Conto","Cognome","Nome","Saldo");
while(!feof(out_file))
{
fread(&cliente, sizeof(struct Dati_cli), 1, out_file);
if(cliente.num_conto != 0)
{
printf("%-6d %-16s %-11s %10d \n",cliente.num_conto,cliente.cognome,
cliente.nome, cliente.saldo);
}
}
fclose(out_file);
}
return(0);
}
Scarica

Fonda23