Iniziamo la lezione
dicendo subito che per INPUT/OUTPUT si intende l’ insieme delle operazioni di
ingresso ed uscita, ossia lo scambio di informazioni tra il programma e le
unita’ periferiche del calcolatore.
Nel linguaggio C l’
I/O e’ interamente implementato mediante funzioni della libreria standard.
Il linguaggio C vede
i file come un flusso (stream) sequenziale di byte. Uno stream dal punto di
vista tecnico e’ una implementazione software in grado di gestire le
informazioni relative all’interazione a basso livello con la periferica
associata, in modo che il programma possa trascurare di che periferica si
tratti.
Ogni file termina con un marcatore di fine file detto
EOF( end-of-file) definito in
<stdio.h> come una costante simbolica uguale a -1.
Un file e' una
sequenza di lunghezza non prefissata di valori dello stesso tipo.
Quando un file viene
aperto sara’ associato uno stream . Tre file e i loro rispettivi stream, lo
standard di input, lo standard di output, lo standard error (stdin, stdout,
stderr) , sono aperti automaticamente quando inizia l’esecuzione di un
programma.
Lo stream dello standard di input consente ad un programma di ricevere input dalla tastiera. (puo’ essere
rediretto su altre periferiche).
Lo stream dello standard di output consente ad un programma di di scrivere sul video della macchina.(puo’
essere rediretto su altre periferiche)
Lo stream dello standard di errore consente ad un programma di scrivere sul video della macchina. (non puo’
essere rediretto su altre periferiche).
Un programma C puo’
servirsi degli stream standard ; basta che vi compaia la direttiva #include<stdio.h>
Gli stream
forniscono un canale di comunicazione tra i file ed i programmi.
L’apertura di un file restituisce un puntatore ad una struttura FILE (definita in <stdio.h>) che contiene le informazioni utilizzate per elaborare il file. La struttura FILE ha un file descriptor (descrittore di file), cioe’ l’indice di un vettore del S.O. chiamata open file table (tabella dei file aperti). Ogni elemento del vettore contiene un file control block o FCB (blocco di controllo del file) il quale viene utilizzato dal S.O. per amministrare un particolare file.
Consideriamo due
tipi di file i file di testo ed i file binari; i primi utilizzati quando
l'informazione da memorizzarvi e' di tipo testo cioe' sequenze di caratteri; i
secondi quando si vuole memorizzare nel file dati di un generico tipo
(interi,strutture ecc...).
Attraverso la
definizione
FILE *fp;
viene permessa
l’associazione di un identificatore del programma ad un file, che si trova in
memoria secondaria.
Con la sola
definizione, fp e’ soltanto una
“variabile di tipo FILE”; l’effettiva associazione avviene in fase di apertura
del file.
Il puntatore fp
punta ad informazioni che descrivono alcune caratteristiche dei file come il
nome, lo stato e la posizione corrente.
Scopo del puntatore
al file e’ quello di identificare uno specifico file su disco e viene usato dal
flusso associato per svolgere in modo corretto le operazioni di input/output.
Avendo la
definizione FILE *fp;
Per aprire il file
di avente nome “dati.in” ed associarlo ad fp si esegue la seguente operazione
di apertura:
fp = fopen(“dati.in”,
“r”);
Vediamo che l’access
mode “r” implica l’apertura del file
dati.in, presente in memoria secondaria, in modalita’ di sola lettura.
Il prototipo della
fopen() e’:
FILE *fopen(const char
*nome_del_file, const char *modo);
nome_del_file deve essere un puntatore ad una stringa di
caratteri che costituisce un nome valido secondo quanto definito dal S.O. e
puo’ includere un percorso (path); la stringa a cui punta il modo
definisce la modalita’ con cui sara’ aperto il file.
La fopen()
restituisce un puntatore al file di tipo FILE oppure un puntatore nullo nel
caso si verifichi un errore.
Si puo’ pensare che quando il file viene aperto, vi sia un cursore che segna il punto di accesso all’interno del file (viene letto un dato in corrispondenza della posizione del cursore). Cosi all’apertura del file il cursore si posiziona all’inizio del file; quando viene letto un dato, il cursore va alla posizione successiva; alla prossima lettura viene letto il dato successivo eccetera.
Un file puo’ essere
aperto per operazioni di sola scrittura e sono necessarie le istruzioni:
FILE *fp;
fp = fopen(“dati.in”, “w”);
e’ possibile aprire
sia file di testo che file binari.
Notare che conviene
sempre nei programmi verificare che il file sia stato aperto correttamente
(cioe’ che la funzione fopen() abbia avuto successo).
Per fare cio’ si
deve usare, nel caso il file in uso fosse dati.in, un frammento di codice come
il seguente:
FILE *fp;
char nome_del_file [] =
“dati_in”
fp = fopen(nome_del_file,
“r”);
if(fp!=NULL)
{
/* istruzioni di utilizzo del file*/
}
else
{
printf(“\n impossibile aprire il file”);
exit(1);
}
La istruzione di
uscita exit(1) viene utilizzata soltanto se si e’ nel main() altrimenti se si
e’ all’interno di una altra funzione, al suo posto si utilizza la return.
Per chiudere un file
precedentemente aperto con la fopen() si utilizza la funzione fclose().
E’ molto importante
chiudere tutti i file aperti in un programma prima che questi termini la sua
esecuzione. Infatti l’operazione di chiusura scrive nel file fisico
corrispondente possibili dati che siano rimasti nel buffer di scrittura.
Il prototipo della
fclose e’:
int fclose(FILE *fp);
in cui fp e’ il
puntatore che restituisce la fopen().
La fclose() tenta di
chiudere il file puntato da fp e restituisce il valore 0 nel caso in cui la
chiusura sia terminata con successo (altrimenti un valore diverso da 0 nel caso
di chiusura fallita). Comunque di solito la fclose() da errore soltanto se un
disco e’ stato tolto dall’unita’ di lettura prematuramente oppure quando non vi
e’ piu’ spazio libero.
La modalita’ di
accesso (access mode) indica come si
dovra’ collegare un file ad un identificatore del programma e come si potra’
usare quel file.
Come visto sopra la
modalita’ “r” implica che il file venga aperto soltanto in lettura (solo per
leggerne i dati), se il file esiste naturalmente.
Mentre con “w” si
indica (ad esempio nella fopen(“dati.in”,
“w”);) di aprire il file dati.in in modalita’ di scrittura. Se il file
esiste, nella memoria secondaria, i dati in esso contenuti saranno persi e
sostituiti con i nuovi dati che vi scriveremo; se il file non esiste viene
creato.
Vediamo la tabella
con i valori previsti per l’argomento modo nella fopen(.. , ..)
Tabella 17.1
modo |
significato |
“r” |
Apre un file
testuale esistente per sola lettura, posizionando il cursore all’inizio del
file; |
“w” |
Apre un file
testuale per sola scrittura: se il file gia’ esiste i dati in esso contenuti
vengono perduti; se il file non esiste ne viene creato uno nuovo; |
“a” |
Apre un file di
testuale esistente per scrittura, in modalita’ append; il cursore viene
posizionato alla fine del file e si puo’ scrivere nel file solo in coda ai
dati gia’ presenti; i dati gia presenti non vengono cancellati; |
“r+” |
Apre un file
testuale esistente in lettura e scrittura; il cursore viene posto all’inizio; |
“w+” |
Apre un file
testuale per lettura e scrittura; se il file gia’ esiste i dati in esso
contenuti vengono perduti; se il file non esiste, ne viene creato uno nuovo; |
“a+” |
Apre un file
testuale in modalita’ append; se il file non esiste, ne viene creato uno
nuovo; si possono leggere tutti i dati del file; si possono scrivere dati
solo in fondo al file. |
“rb” |
Apre un file
binario esistente per sola lettura, posizionando il cursore all’inizio; |
“wb” |
Apre un file
binario per sola scrittura; se il file gia’ esiste i dati in esso contenuti
vengono persi; se il file non esiste , ne viene creato uno nuovo; |
“ab” |
Apre o crea un
file binario per scrittura in modalita’ append; il cursore viene posizionato
alla fine del file e si puo’ scrivere nel file solo in coda ai dati gia’
presenti; |
“rb+” |
Apre un file
binario esistente in lettura e scrittura |
“wb+” |
Apre o crea un
file binario in lettura e scrittura |
“ab+” |
Apre o crea in
modalita’ append un file binario. |
Vediamo un esempio
di programma in cui utilizzeremo le funzioni fopen() ed fclose():
/* creare un file
sequenziale*/
#include<stdio.h>
int main ()
{
int numero_di_conto;
char nome[31];
float saldo;
FILE *fpunt;
/* puntatore al file
clienti.dat*/
if ((fpunt = fopen(“clienti.dat”,
“w”)) = = NULL)
printf(“Il file non puo’ essere aperto\n”);
else {
printf(“Inserite il numero di conto, il nome, ed il saldo.\n”);
printf(“inserite EOF per finire gli inserimenti\n”);
printf(“?”);
scanf(“%d%s%f”,&numero_di_conto, nome, &saldo);
while(!feof(stdin)) {
fprintf(fpunt,”%d%s%.2f\n”, numero_di_conto, nome, saldo);
printf(“?”);
scanf(“%d%s%f”,&numero_di_conto, nome, &saldo);
}
fclose(fpunt);
}
return 0;
}
Questo programma
creera’ un semplice file ad accesso sequenziale di nome clienti.dat
Nella schermata di
input dovremo inserire ad esempio
? 10 roberta 10,5
? 11 roberto 22
? 12 jennifer 30
?
ed EOF per terminare
l’immissione di nuovi dati.
Come anticipato FILE
*fpunt stabilisce che fpunt e’ un puntatore ad una struttura FILE.
La fprintf e’
analoga alla funzione printf soltanto che la fprintf scrive nel file invece che
sul video.
Il programma
richiedera’ all’utente di immettere per ogni record i vari campi; quando viene
conclusa l’immissione dei dati richiedera’ un end-of-file.
L’end-of-file per i
diversi sistemi di computer e’ segnalato in tabella:
Tabella 17.2
Sistemi di
computer |
Combinazione di
tasti |
Sistemi UNIX |
<return><ctrl>d |
PC IBM e compatibili |
<ctrl>z |
Macintosh |
<ctrl>d |
VAX(VMS) |
<ctrl>z |
La riga di codice
while (!feof(stdin))
Usa la funzione feof
per determinare se sia stato impostato EOF per il file al quale punta stdin.
EOF indichera’ al
programma che non ci sono piu’ dati da elaborare.
Il nostro programma non
mostra come i dati dei record appaiono realmente all’interno del file. Per fare
cio’ e verificare quindi la correttezza dei dati creati necessita un programma
che leggera’ il file e ne visualizza il contenuto.
Funzioni di lettura e scrittura
Si distinguono in
due categorie lettura e scrittura per carattere e lettura e scrittura per linee
(stringhe).
Fra le prime
consideriamo la funzione fputc e la funzione fgetc.
La funzione fputc ha
il seguente prototipo:
int fputc (int c , FILE
*fpunt);
questa funzione
preleva un carattere c e lo stampa(scrive) nel file fpunt,(file puntato da
fpunt) nella posizione del
cursore; naturalmente il file deve essere stato precedentemente aperto con la
funzione fopen( ), ed fpunt e’ il puntatore restituito da quest’ultima
funzione.
Il carattere c viene
convertito in un unsigned char nella fputc.
Restituisce il
carattere letto (il codice) oppure EOF (se la stampa e’ andata male)
La funzione fgetc ha
il seguente prototipo:
int fgetc(FILE *fpunt);
la funzione restituisce
(convertito in intero) il carattere letto dal file fpunt “file precedentemente
aperto con la fopen()”;
il cursore avanza di
un dato (il prossimo da leggere); in caso vi sia errore viene restituito EOF.
Nella lettura e
scrittura per linee consideriamo la funzione fgets ed fputs ; con la fgets si
legge al piu’ una intera linea da un file passato per argomento “file
precedentemente aperto tramite la funzione fopen()”.
Il prototipo della
funzione e’:
char *fgets (char *strin,
int n, FILE *fpunt);
con questa funzione
viene letto, dal file fpunt, una sequenza di caratteri, fermandosi al newline
(oppure fermandosi ad n caratteri letti od all’EOF) e memorizza i caratteri
letti nell’array strin; la fgets restituisce l’array letto oppure NULL se ha
incontrato EOF.
L’eventuale EOL
(‘\n’) letto verra’ incluso nella stringa risultante.
Con il parametro n
si specifica il numero massimo di caratteri da leggere; se la linea del file
contiene un numero di caratteri maggiore di n i caratteri in eccesso non
verranno letti.
La funzione fputs ha
il seguente prototipo:
int fputs(const char
*strin, FILE *fpunt);
e stampa nel file
fpunt(precedentemente aperto tramite la fopen(..) ) i caratteri contenuti nella
stringa strin; se la stampa e’ andata a buon fine restituisce il valore 0 ,
altrimenti un numero diverso da zero.
Esempio
Nel seguente
programma la funzione fputs() scrive nel file prova delle stringhe lette
dalla tastiera, tramite la funzione gets(), e passategli come parametro.
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
int main()
{char strin[81];
FILE *fpunt;
if((fpunt=fopen("prova", "w"))==NULL)
{
printf("Errore nella lettura del file\n");
exit(1);
}
do
{
printf("inserite una stringa e premere INVIO per
uscire:\n");
gets(strin);
strcat(strin,"\n"); /*aggiunta di un newline*/
fputs(strin,fpunt);
}while(*strin !='\n');
return
0;
}
Per uscire dal
programma introducete una stringa vuota.
Notare che la
funzione gets non memorizza il carattere di newline, per cui ne aggiungiamo uno
alla stringa letta da input, attraverso la funzione strcat, prima che tale
stringa venga scritta nel file fpunt.
La funzione rewind()
La rewind() ripristina l’indicatore di posizione del file specificato ‘cursore’.
Ossia il file e’ come se venisse ravvolto con la rewind().
Il prototipo di tale
funzione e’:
void rewind (FILE *fpunt);
fpunt e’ un
puntatore ad un file.
Le funzioni di lettura e scrittura formattata
Sono la fprintf() e
la fscanf() analoghe alla printf e scanf, soltanto che accettano un argomento
in piu’, cioe’ il file dove scrivere o da cui leggere.
I prototipi sono
rispettivamente:
int fprintf(FILE *fpunt,
const char *stringa_formato, <argomenti>);
la quale stampa nel
file fpunt secondo le specifiche della stringa formato;
e la
int fscanf (FILE *fpunt,
const char * stringa_formato, <argomenti>);
la fscanf legge dal
file fpunt i dati specificati nella stringa_formato.
Cenni sui file ad
accesso casuale
I record di un file creati con l’output
formattato della funzione fprintf non avranno necessariamente la stessa
lunghezza. Ogni record di un file ad accesso casuale puo’ essere acceduto
direttamente avendo di norma la stessa lunghezza. Dal momento che in un file ad
accesso casuale ogni record ha lunghezza fissa, la sua posizione all’interno
del file puo’ essere calcolata con una funzione della chiave del record.
Le funzioni di lettura e scrittura per blocchi
Attraverso le
funzioni fread() ed fwrite() si possono leggere e scrivere rispettivamente
interi blocchi di byte da un file.
I loro prototipi
sono:
size_t fread(void *buffer, size_t num_byte, size_t contatore, FILE *fpunt);
size_t fwrite(void *buffer, size_t num_byte, size_t contatore, FILE *fpunt);
nella
fread buffer e’ l’indirizzo di una locazione di memoria destinata
a ricevere i dati letti dal file fpunt. Con essa si leggono dal file fpunt un
blocco di contatore dati con ciascun
dato avente dimensione num_byte. La fread
restituisce il numero di elementi letti con successo.
Le funzioni fread ed fwrite sono in grado di leggere e
scrivere su disco interi vettori di dati.
Il terzo argomento delle funzioni e’ il numero degli
elementi del vettore che dovranno essere letti/scritti dal/sul disco.
Vediamo adesso un programma che se il file viene aperto
in modo binario scrive un double un int e un long in un file di nome fle_test e
li rilegge:
#include <stdio.h>
#include<stdlib.h>
int main ()
{
FILE *fpunt;
double d=11.23;
int i=101;
long l=113023L;
if((fpunt=fopen("file_test","wb+"))==NULL)
{
printf("errore di
apertura del file\n");
exit(1);
}
fwrite(&d,sizeof(double), 1,fpunt);
fwrite(&i,sizeof(int), 1,fpunt);
fwrite(&l, sizeof(long),1,fpunt);
rewind(fpunt) ; /*riposiziona il puntatore
all'inizio del file*/
fread(&d, sizeof(double),1,fpunt);
fread(&i, sizeof(int),1,fpunt);
fread(&l,sizeof(long),1,fpunt);
printf("%f %d %ld",d,i,l);
fclose(fpunt);
return
0;
}
Il linguaggio C consente
le operazioni di lettura e scrittura ad accesso casuale usando la funzione
fseek(), che ha il compito di posizionare il cursore del file.
Il prototipo della
fseek() e’:
int fseek(FILE *fpunt, long num_byte, int origine);
fpunt
e’ il
puntatore al file come viene restituito dalla funzione fopen();
num_byte
e’ il
numero di byte di distanza tra l’origine e la nuova posizione (detto offset o
scostamento); notare che in questo caso occorre usare l'operatore sizeof per
accedere all'elemento desiderato; mentre origine
e’ una delle seguenti macro definite da stdio.h :
Tabella 17.3
Origine |
Nome |
Inizio del file |
SEEK_SET |
Posizione corrente |
SEEK_CUR |
Fine del file |
SEEK_END |
Per leggere l'ennesimo elemento del file fp, che supponiamo contenga elementi di tipo tipo_elementi, useremo:
fseek(fp,(n-1)*sizeof(tipo_elementi),SEEK_SET);
fread(&buffer,sizeof(tipo_elementi),1,fp);