Files e stream
In C++ i programmi comunicano con i dispositivi periferici mediante appositi
canali virtuali attraverso i quali scorre il “flusso” (stream) di informazioni.
Quando inizia l'esecuzione di un programma vengono automaticamente aperti
quattro stream standard, uno di input, cin, associato
alla tastiera, e tre di output, cout, cerr
e clog, collegati con lo schermo. Per accedere ai
files su disco la Libreria Standard dispone di altri tipi di stream: ifstream
(input), ofstream (outpuf) e fstream
(input/output).
Apertura di un file
Per leggere e scrivere dei dati su un file occorre creare uno stream adeguato
e associarlo al file sul disco.
Questa associazione è denominata “apertura del file”.
ifstream dati;
// Apertura del file iscritti.txt
dati.open( "iscritti.txt " );
In alternativa è possibile dichiarare e aprire il file con un'unica istruzione:
ifstream dati( "iscritti.txt" );Se l'operazione di apertura è andata a buon fine lo stream assume il valore
true, altrimenti false.
I tipi di stream che consentono l'accesso ai file sono definiti nell' header
file <fstream>.
Modalità di apertura di un file
Quando si crea uno stream associato a un file occorre stabilire la direzione del flusso di dati. Le modalità previste sono:
★ Apertura
in lettura
● è la modalità predefinita per gli stream
di tipo ifstream;
● il file associato deve esistere;
● per impostazione predefinita la lettura
comincerà all’ inizio del file.
★ Apertura
in scrittura
● è la modalità predefinita per gli stream
di tipo ofstream;
● il file associato viene creato. Se esiste
già, il contenuto precedente è cancellato;
● i dati sono scritti apartire dall’inizio
del file.
★ Apertura
in append
● il file associato viene creato. Se esiste
già, il contenuto precedente è preservato;
● i nuovi dati sono aggiunti alla fine del
file.
Modalità di accesso ai files
Le modalità di accesso a un file possono essere specificate al momento della sua apertura mediante degli enumeratori predefiniti. I principali sono:
● ios::in : accesso
in lettura;
● ios::out : accesso
in scrittura;
● ios::app : accesso
in modalità append;
● ios::binary : apertura
di un file binario.
Gli enumeratori possono essere combinati mediante l'operatore bit a bit OR (|) per specificare modalità di apertura personalizzate.
Esempio:
// Apertura in lettura
fstream in("dati", ios::in);
// Apertura in lettura/scrittura
fstream in_out( "dati",ios::in | ios::out);
// Apertura in modalità append
fstream app("dati", ios::app);
Chiusura di un file
Quando uno stream non occorre più è possibile chiuderlo, ovvero disconnetterlo dal file cui era associato. La chiusura avviene mediante il metodo close().
Esempio:
dati.close();
Durante la chiusura di uno stream aperto in scrittura, i caratteri presenti
nel buffer interno vengono immediatamente scritti nel file associato.
Gli stream vengono chiusi automaticamente alla fine del programma o all'uscita
del blocco in cui sono dichiarati.
Una volta chiuso, io stesso stream può essere riaperto con qualunque modalità
(compatibile al suo tipo) e associato a qualunque file.
Files di testo
Sono file che solitamente contengono solo caratteri visualizzabili direttamente
sullo schermo.
Possono essere aperti e modificati da qualunque programma editor. I dati
scritti su questi file sono convertiti nei corrispondenti caratteri.
Per esempio, il numero reale 1.23 è memorizzato come la sequenza di caratteri
'1,' '.' '2' '3' .
Files binari
Sono file che hanno 1o scopo di memorizzare qualsiasi tipo di infomazione.I
dati scritti in questi file sono registrati così come sono rappresentati
in memoria, senza nessuna conversione o formattazione.
Spesso i file binari sono più compatti degli equivalenti file di testo e
consentono prestazioni migliori. Hanno pero lo svantaggio di essere meno
portabili e il loro listato normalmente è privo di significato.
Accesso a files di testo
Per leggere e scrivere su un file di testo si possono impiegare gli stessi operatori e le stesse modalità usati per gli stream di ingresso e di uscita standard cin e cout.
// Legge dallo stream f_inf_in >> articolo >> prezzo;
// Legge un'intera riga da f_in
f_in.getline(riga, 80);
// Legge un solo carattere
f_in.get(c);
// Scrittura sullo stream f_out
f_out << tempo << spazio << endl;
// Scrittura di un carattere
f_out.put(c);
Errori di lettura
Se durante la lettura di un file si verifica un errore, per esempio quando si cerca di leggere una sequenza di caratteri che non è possibile convertire nel tipo di variabile corrispondente, lo stream assume il valore false:
dati >> articolo >> prezzo;if(!dati)
cout << "Errore! " << endl;
In questi casi la prima operazione da compiere è ripristinare lo stato normale dello stream mediante il metodo clear():
dati.clear();In seguito si devono "saltare" i caratteri che hanno causato l'errore, per esempio mediante il metodo ignore():
dati.ignore(10000,'\n') ;Questa istruzione consente di "ignorare" 10000 caratteri o almeno tutti quelli fino al prossimo NEW-LINE compreso.
Accesso a files binari
Un file binario deve essere aperto specificando l'enumeratore predefinito
ios::binary.
Per scrivere in un file binario s'impiega il metodo write(),
per leggere il metodo read().
Per entrambi occorre specificare il "blocco" di byte che deve essere registrato
o letto, ovvero l’indirizzo iniziale e la sua dimensione.
// Scrittura in un file binario
f_out.write((char *)&peso,sizeof peso);
// Lettura da un file binario
f_out.read((char *)&peso,sizeof peso);
Il cast a char * dell'indirizzo del dato da leggere o scrivere è necessario.
Verifica della fine del file
Quando si leggono dei dati da un file non ha ovviamente più senso effettuare
altre letture dopo la sua fine.
Per verificare questa condizione è previsto il metodo eof()
(end of file), che assume il valore true dopo che è stato letto l'ultimo
carattere disponibile nello stream.
esempio:
do{
dati >> articolo >> prezzo;
if(dati)
cout<< articolo << '\t'<
}while( !dati.eof());
Lo stato di "fine" file va verificato sempre dopo la lettura dei dati, mai prima.
Accessi sequenziali
La posizione da cui leggere o in cui scrivere i dati in uno stream è definita da un puntatore interno che, a ogni accesso, viene incrementato automaticamente della dimensione del dato letto o scritto. Questo meccanismo consente un accesso predefinito di tipo sequenziale, i dati sono letti o scritti consecutivamente.
Accessi random
L'indicatore di posizione interno degli stream si può spostare impiegando il metodo seekg() per quelli di lettura e seekp() per quelli di scrittura. In questo modo il programmatore può stabilire quale sarà il prossimo dato da leggere o in quale posizione scriverlo ottenendo, pertanto, un accesso di tipo random.
Lo spostamento del puntatore può essere assoluto oppure relativo rispetto l'inizio del file, alla sua fine o
alla posizione corrente:
dati.seekg(100);
// Punta 10 byte dopo l'inizio
dati.seekg(10, ios::beg) ;
// Punta 4 byte prima della fine
dati.seekg(-4, ios::end) ;
// Punta 8 byte dopo
dati.seekg(8, ios::cur);
ios::beg, ios::end e ios::cur, sono degli enumeratori predefiniti.