File binari in C++ con approccio OOP

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:

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:

  1. interpreta i 4 byte con cui l’intero della variabile n è rappresentato all’interno della RAM, ottenendo il valore 10;
  2. converte questo valore nei byte ASCII dei caratteri ‘1’ e ‘0’;
  3. 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 (*):

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:

  1. il metodo write(), per SCRIVERE un blocco di byte in un file binario;
  2. 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:

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:

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 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:

Un esempio di codice è quello (*) visto all’inizio dell’articolo in cui il numero intero 10 è stato scritto nel file di nome binario.dat.

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:

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:

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:

Continuando l’esempio fatto all’inizio dell’articolo (*), il numero intero 10 scritto nel file di nome binario.dat può essere letto dal file e memorizzato nella variabile n con le seguenti istruzioni:

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:

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:

  1. ios::beg, inizio del file;
  2. ios::end, fine del file;
  3. 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:

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.

NOTA  – Si noti che un file binario in ogni momento può essere aperto e utilizzato contemporaneamente sia in LETTURA sia in SCRITTURA, al contrario di un file di testo che può essere aperto e utilizzato o solo in lettura o solo in scrittura.