Che cosa sono i file binari l’abbiamo già visto in un altro articolo (link articolo), ora vediamo com’è possibile operare con essi in C++ secondo il paradigma della programmazione ad oggetti (OOP).
Il modo più semplice per capire come si lavora con un file binario, può essere partire da ciò che accade con i file di testo e che abbiamo già discusso in un altro articolo (link articolo). Se, ad esempio, consideriamo le seguenti istruzioni che operano con un file di testo:
1 2 3 4 |
int n=10; fstream fileDiTesto("testo.txt", ios::out); //apre il file di testo in SCRITTURA fileDiTesto<<n; //scrive il valore della variabile n nel file di testo fileDiTesto.close(); |
quello che accade è che durante l’operazione di scrittura nel file di testo di nome testo.txt, l’oggetto fileDiTesto ad esso associato esegue le seguenti operazioni:
- interpreta i 4 byte con cui l’intero della variabile n è rappresentato all’interno della RAM, ottenendo il valore 10;
- converte questo valore nei byte ASCII dei caratteri ‘1’ e ‘0’;
- scrive questi due byte dei caratteri nel file di testo.
Un file binario, invece, in generale permette di scrivere in esso dei blocchi di byte cosiddetti “grezzi”, ossia così come sono scritti in una variabile, qualunque sia il suo tipo, senza alcuna preventiva operazione di interpretazione e conversione. Ossia, riprendendo l’esempio precedente, se il contenuto della variabile n venisse scritto questa volta in un file binario con le seguenti istruzioni (*):
1 2 3 4 |
int n=10; fstream fileBinario("binario.dat", ios::out|ios::binary); //apre il file binario in SCRITTURA fileBinario.write((char*) &n, sizeof(int)); //scrive i byte della variabile n nel file binario fileBinario.close(); |
quello che accade è che il blocco dei 4 byte contenuti nella variabile n vengono scritti nel file così come sono, ossia secondo la rappresentazione interna della macchina. Per la sintassi utilizzata nell’esempio vedi più avanti.
Un file binario è una sequenza pura di byte che possono rappresentare qualunque tipo di informazione (testi, numeri, immagini, video, ecc.) e per operare con esso il linguaggio C++ mette a disposizione due metodi fondamentali, uno per LEGGERE e l’altro per SCRIVERE blocchi di byte:
- il metodo write(), per SCRIVERE un blocco di byte in un file binario;
- il metodo read(), per LEGGERE un blocco di byte da un file binario.
Scrittura in un file binario
Per poter scrivere in un file binario, innanzitutto il file deve essere aperto. Con l’apertura in scrittura di un file che non esiste, il file viene prima creato e poi aperto. Cosi come visto con i file di testo, l’apertura in scrittura può avvenire posizionandosi all’inizio del file, scegliendo la modalità di apertura ios::out, o alla fine del file in aggiunta (append) ai byte già presenti, scegliendo la modalità di apertura ios::app. La modalità di apertura, inoltre, va completata specificando che si ha intenzione di utilizzare il file come un file binario con la clausola ios::binary, con la seguente sintassi:
1 2 3 |
fstream nomeOggetto("pathnameFile", ios::out|ios::binary); //apertura del file binario con posizionamento all'inizio //OPPURE fstream nomeOggetto("pathnameFile", ios::app|ios::binary); //apertura del file binario con posizionamento alla fine del file |
nomeOggetto è l’oggetto che viene creato e associato al file pathnameFile. Per quanto riguarda il pathname del file e la sintassi estesa di questa istruzione, si rimanda alle considerazioni fatte con i file di testo (link articolo).
La scrittura si può eseguire utilizzando il metodo write() di classe ofstream, che ha la seguente sintassi:
1 |
nomeOggetto.write(const char* indirizzo, int dimensione); |
Il metodo scrive nel file binario associato all’oggetto nomeOggetto, a partire dalla posizione in cui in quel momento si trova il puntatore interno al file, un numero di byte pari al valore del secondo parametro, dimensione. Questo valore può essere fornito al metodo utilizzando la funzione sizeof(v) che restituisce il numero di byte occupati dal parametro v che gli passiamo. Il parametro v può essere o direttamente la variabile utilizzata per il blocco di byte da scrivere nel file oppure il suo tipo di dato. Il primo parametro, indirizzo, fornisce al metodo l’indirizzo della prima locazione (di un byte) della memoria RAM a partire dalla quale il blocco di byte da scrivere nel file binario verrà letto nella memoria RAM. Il parametro indirizzo, infatti, è di tipo puntatore ad un char. Si fa notare, pertanto, che se la variabile utilizzata per il blocco di byte da scrivere nel file è diversa dal tipo char, occorre eseguire un operazione di casting per convertire l’indirizzo di quella variabile ad un tipo puntatore ad un char, con la seguente sintassi:
1 |
(char*) &nomeVariabile |
Lettura da un file binario
Per poter leggere in un file binario, innanzitutto il file deve essere aperto. Quando un file viene aperto in lettura, se esso non esiste o per qualche ragione non è disponibile, il programma produce un errore. Per aprire un file binario in lettura bisogna specificare la modalità di apertura ios::in che va completata specificando che si ha intenzione di utilizzare il file come un file binario con la clausola ios::binary, con la seguente sintassi:
1 |
fstream nomeOggetto("pathnameFile", ios::in|ios::binary); //apertura in lettura del file binario |
nomeOggetto è l’oggetto che viene creato e associato al file pathnameFile. Per quanto riguarda il pathname del file e la sintassi estesa di questa istruzione, di nuovo si rimanda alle considerazioni fatte con i file di testo (link articolo).
La lettura si può eseguire utilizzando il metodo read() di classe ifstream che ha la seguente sintassi:
1 |
nomeOggetto.read(const char* indirizzo, int dimensione); |
Il metodo read() ha una sintassi molto simile al metodo write() già descritto. Esso legge nel file associato all’oggetto nomeOggetto, a partire dalla posizione in cui in quel momento si trova il puntatore interno al file, un numero di byte pari al valore del secondo parametro, dimensione. Questo valore, come spiegato prima, può essere fornito al metodo utilizzando la funzione sizeof(v). Il primo parametro, indirizzo, fornisce al metodo l’indirizzo della prima locazione (di un byte) della memoria RAM a partire dalla quale il blocco di byte letto dal file binario verrà scritto nella RAM. Il parametro indirizzo, infatti, è di tipo puntatore ad un char. Come prima si pone l’accento sul fatto che se la variabile utilizzata per memorizzare il blocco di byte letto dal file, è diversa dal tipo char, occorre eseguire un operazione di casting per convertire l’indirizzo di quella variabile ad un tipo puntatore ad un char, con la seguente sintassi:
1 |
(char*) &nomeVariabile |
1 2 3 4 |
int n; fstream fileBinario("binario.dat", ios::in|ios::binary); //apre il file binario in LETTURA fileBinario.read((char*) &n, sizeof(int)); //memorizza i 4 byte (del numero intero) letti dal file binario nella variabile n fileBinario.close(); |
Posizionamento in un file binario
In un file binario dopo un’operazione di lettura/scrittura, la successiva operazione di lettura/scrittura viene realizza a partire dal primo byte successivo all’ultimo che è stato letto/scritto dal/nel file. Quindi, operazioni successive di letture/scritture operano sul file in maniera sequenziale. In un file binario, inoltre, è possibile leggere/scrivere a partire direttamente da un punto a piacere del file eseguendo un’operazione di riposizionamento del puntatore interno al file gestito dal File System. Ciò è possibile farlo con i metodi:
- seekg(), per il riposizionamento del puntatore delle operazioni di LETTURA (la g finale sta per get); esso ha la seguente sintassi:
1 |
nomeOggetto.seekg(int offset, const start); |
dove offset rappresenta il numero di byte di cui riposizionare il puntatore di lettura in avanti, nel caso di un valore positivo, o all’indietro, nel caso di un valore negativo, a partire dalla posizione espressa dalla costante start che può assumere uno dei seguenti valori:
- ios::beg, inizio del file;
- ios::end, fine del file;
- ios::cur, posizione corrente del puntatore del file;
Ad esempio, se nel file binario.dat di prima scriviamo altri 150 interi, potremmo leggere direttamente il centesimo intero (senza cioè fare una scansione sequenziale di tutti gli interi partendo dal primo), con le seguenti istruzioni:
1 2 3 4 5 |
int posizione=100, n; fstream fileBinario("binario.dat", ios::in|ios::binary); //apre il file binario in LETTURA fileBinario.seekg((pos-1)*sizeof(int), ios::beg); //si posiziona per leggere il centesimo intero fileBinario.read((char*) &n, sizeof(n)); //legge il centesimo intero fileBinario.close(); |
Si noti che (pos-1) ci dà il numero di interi che precedono quello che vogliamo leggere, che moltiplicato per quanti byte misura ciascun intero, ci dà il numero di byte che precedono l’intero che vogliamo leggere. Il valore appena calcolato rappresenta l’offset di cui spostare in avanti il puntatore di lettura interno al file, partendo a contare dall’inizio (ios::beg) del file.
- seekp(), per il il riposizionamento del puntatore delle operazioni di SCRITTURA (la p finale sta per put). Per esso valgono esattamente le stesse cose dette per il metodo seekg().
Esempio
Scrivi in C++ un programma che scrive in un file binario i numeri interi da 1 a 100 e che successivamente visualizza l’intero letto nel file in una posizione fornita in input dall’utente.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
/*FILE BINARIO: scrive in un file binario gli interi da 1 a 100 e legge l'intero che si trova nella posizione inserita dall'utente*/ #include <iostream> #include <fstream> const int MAX_INT = 100; using namespace std; int main(){ int n, pos; //apre il file binario in lettura e scrittura dall'inizio fstream fileBinario("interi.dat", ios::out|ios::in|ios::binary); //scrive nel file binario gli interi da 1 a 100 for(int i=1; i<=MAX_INT; i++){ fileBinario.write((char*) &i, sizeof(int)); } do { cout<<"In quale posizione vuoi leggere (max=100)?"<<endl; cin>>pos; } while(pos>100); fileBinario.seekg((pos-1)*sizeof(int), ios::beg); fileBinario.read((char*) &n, sizeof(int)); fileBianrio.close(); cout<<"L'intero nella posizione "<<pos<<" e' "<<n<<endl; return 0; } |