File di testo in C++ con approccio OOP

Il modo più semplice per introdurre l’uso dei file di testo in C++ secondo il paradigma della programmazione ad oggetti (OOP), è far riferimento alle ben note operazioni di INPUT e di OUTPUT STANDARD realizzate in C++ tramite gli operatori di LETTURA (>>) e SCRITTURA (<<).

Quando si esegue un input da tastiera, l’operatore di LETTURA (>>) opera sullo stream di input (flusso di caratteri) della periferica tastiera, attraverso l’oggetto cin associato in maniera predefinita alla tastiera dal linguaggio C++ (vedi figura sotto).

cin

Quando si esegue un output sul monitor, l’operatore di SCRITTURA (<<) opera sullo stream di output diretto verso la periferica monitor, attraverso l’oggetto cout associato di default al monitor dal linguaggio C++ (vedi figura sotto).

cout

In un altro articolo (link articolo) abbiamo visto come, grazie al servizio reso dal File System, anche un file di testo può essere visto a livello applicativo come una sequenza di byte di caratteri (ASCII o UNICODE, a seconda dei vari casi), cioè uno stream. Pertanto, le operazioni di SCRITTURA e di LETTURA su un file di testo possono essere ricondotte rispettivamente ad operazioni di OUTPUT e di INPUT di uno stream che agisce sulla periferica di I/O sulla quale il file è memorizzato (memoria di massa) e queste operazioni possono essere gestite allo stesso modo visto per le periferiche standard di I/O, a patto di utilizzare degli oggetti opportuni da associare alla periferica memoria di massa e, più in particolare, al file su cui si vuole realizzare l’accesso di I/O.

fstream

Il linguaggio C++ mette a disposizione la classe fstream che premette di istanziare oggetti che possono essere associati ad un file per eseguire operazioni sia di Input sia di Output. La sintassi per il suo utilizzo è la seguente:

Per poter eseguire operazioni di I/O su un file, esso innanzitutto deve essere aperto. Con l’apertura il file viene collegato alla memoria RAM mediante il meccanismo del buffer già discusso in un altro articolo (link articolo). Un file di testo può essere aperto e, quindi utilizzato, in tre modalità di apertura diverse: in scrittura dall’inizio (RISCRITTURA) con perdita di tutto il suo contenuto già presente; in scrittura in aggiunta a ciò che è già presente (APPEND); in LETTURA dall’inizio. La sua gestione, quindi, può avvenire solo secondo l’organizzazione sequenziale di cui abbiamo già parlato in un altro articolo (link articolo).

Ecco due semplici esempi di di scrittura e lettura di un file di testo:

Della sintassi precedente esiste anche una versione compatta, che unisce in un’unica istruzione le due istruzioni di dichiarazione dell’oggetto e apertura del file:

NOTA. Negli esempi di sopra, per il primo parametro del file da associare agli oggetti è stato utilizzato solo il nome del file senza pathname. In questo caso l’applicazione si aspetta di trovare il file nella stessa directory in cui si trova l’eseguibile del programma. Due esempi di utilizzo di un pathname assoluto e un pathname relativo sono i seguenti:

NOTA. Si faccia attenzione al fatto che all’interno di una stringa il carattere backslash deve essere scritto utilizzando la sequenza di escape ‘\\’, in quanto da solo viene interpretato come un carattere speciale che, appunto, introduce una sequenza di escape (si ricordino ad esempio i caratteri ‘\n’ e ‘\t’).

Si fa notare che un file di testo può essere aperto o solo in lettura o solo in scrittura ed è possibile passare alternativamente dall’una all’altra modalità chiudendolo e riaprendolo. La chiusura di un file si realizza utilizzando il metodo close() dell’oggetto associato a quel file:

Con la chiusura del file il buffer viene deallocato dalla memoria RAM e il suo contenuto viene memorizzato nel file.

ATTENZIONE. Quando un file viene aperto in SCRITTURA, se non esiste esso viene prima creato. Quando viene aperto in LETTURA, invece, se esso non esiste o per qualche ragione non è disponibile, il programma produce un errore.

Il linguaggio C++ mette a disposizione anche le classi ofstream e ifstream, delle quali la prima permette di istanziare un oggetto che è in grado di gestire solo le operazioni di scrittura, il secondo solo quelle di lettura. Con questi oggetti il parametro della modalità di apertura è opzionale. Per la gerarchia completa delle classi C++ per la gestione degli stream si rimanda al seguente articolo: link articolo. Riprendendo la sintassi nella versione compatta, nel caso di oggetti ofstream e ifstream essa diventa:

Lettura di righe intere

Come abbiamo già visto in un altro articolo (link articolo), se lo stream contiene caratteri quali spazi, tabulazioni o ritorno a capo (‘\n’), durante le operazioni di input si deve fare attenzione perché essi vengono interpretati dall’operatore di lettura C++ (>>) come dei caratteri di fine stringa. Pertanto se si vuole acquisire correttamente un input che potenzialmente può contenere caratteri di questo tipo, l’operatore di lettura non deve essere utilizzato. In questi casi si può utilizzare il metodo cin.getiline(), quando si opera con le stringhe in stile C (link articolo), o la funzione getline(), quando invece si opera con le stringhe in stile C++ (link articolo). Vediamo un semplice esempio.

Lettura fino alla fine del file

La fine di un file viene marcata con un opportuno carattere di fine file (EOF) e questo fatto può essere sfruttato quando si ha l’esigenza di leggere completamente un file di testo, dall’inizio fino alla fine. Per far ciò si ha a disposizione il metodo eof() degli oggetti di classe fstream e ifstream, che restituisce TRUE quando viene letto il carattere di fine file, FALSE altrimenti. Vediamo un semplice codice di esempio di lettura sequenziale di un file di testo dall’inizio fino alla fine del file:

 P.S. Affinché il ciclo while dell’esempio precedente funzioni correttamente in modo da leggere fino all’ultima riga compresa, il file di testo deve contenere come ultima riga una riga vuota (‘\n’).