Corso Cplusplus Lezione 761
Corso Cplusplus Lezione 761
Lezione del corso C++ - Sviluppare software complessi con C++ e Code:Blocks
Fino a questo momento infatti, abbiamo assunto che l'utente comunichi col nostro programma tramite
tastiera (per fornire gli input) e schermo (per la visualizzazione degli output). A livello sintattico, tale
soluzione è stata resa possibile da cin (in lettura) e cout (in scrittura). In effetti, cin e cout sono esempi
specifici del concetto più generale di stream, ovvero di flusso informativo (in entrata o in uscita). Prima di
affrontare gli aspetti sintattici, riflettiamo su quali possano essere sorgenti e destinazioni alternative per le
informazioni elaborate dai nostri programmi e, successivamente, in quali contesti tali soluzioni risultino
preferibili rispetto all'accoppiata tastiera/schermo.
La principale alternativa a tastiera e schermo, sia in fase di input che in fase di output è rappresentata dai
file. In altre parole, le informazioni di interesse possono essere lette e/o stampate su file di testo.
Naturalmente, il controllo sull'esecuzione è leggermente minore, poichè non si ha una visualizzazione diretta
e immediata dei dati in ingresso e/o in uscita dal programma. Da cosa è compensato tale leggero
svantaggio?
Teniamo presente che, fino a questo momento, ci siamo occupati di programmi con valenza principalmente
didattica, focalizzandoci quindi, a ogni lezione, su uno specifico elemento del linguaggio, con limitate
quantità di dati da elaborare e poche, semplici operazioni da svolgere. In tal modo ci siamo proposti di
apprezzare appieno i dettagli che risultavano, di volta in volta, di interesse. Senz'altro è possibile però, che il
programma da realizzare debba essere progettato per la gestione di una quantità cospicua di dati. In questo
caso, è decisamente poco pratico procedere all'input tramite tastiera di tutti i dati. Analogamente, la
visualizzazione dell'output a schermo sarebbe senz'altro confusa. Meglio invece servirsi di file come
strumento di input e output.
Inquadrato il contesto operativo, procediamo con l'analisi degli elementi sintattici, cominciando dall'input.
Proponiamoci cioè di realizzare un programma per la lettura di un file di testo.
Lettura da file
Il codice a cui facciamo riferimento è contenuto in esercizio16a.cpp, mentre il file di testo di esempio, su cui
operiamo in lettura è test_input.txt. Un file che vogliamo utilizzare in lettura può essere rappresentato da un
oggetto di tipo ifstream. Nel programma contenuto in esercizio16a.cpp, assegniamo a tale oggetto il nome di
inputFile:
ifstream inputFile;
Per poterci servire degli oggetti di tipo ifstream è necessario includere la libreria fstream all'interno del nostro
programma. Otteniamo tale risultato con:
#include <fstream>
Il contenuto del file di testo è un semplice messaggio, ovvero un insieme di stringhe (come abbiamo visto
nel corso della lezione 10, una stringa è un insieme di caratteri alfanumerici). Sempre facendo riferimento
alla lezione 10, ricordiamo di dover includere quindi anche la libreria string nel nostro programma:
#include <string>
Associamo ora allo stream in input il file di testo di nome test_input.txt con:
inputFile.open("test_input.txt");
Osserviamo che il il file di testo di nome test_input.txt deve trovarsi nella cartella di esecuzione del nostro
programma, oppure riceveremo un messaggio di errore in esecuzione. Se volessimo servirci invece di un
file di testo salvato in un'altra posizione sul nostro disco rigido, dovremmo specificarne il percorso completo,
ad esempio con:
inputFile.open("C:\Users\Utente1\Documents\test_input.txt");
Dichiariamo inoltre una variabile di tipo string e di nome data, che servirà per memorizzare, una stringa alla
volta, i dati letti da test_input.txt:
string data;
A questo punto utilizzeremo tre metodi di ifstream, che ci consentiranno di leggere il contenuto del file:
• is_open: restituisce un valore di tipo bool uguale a true (vero) se l'apertura dello stream è avvenuta
correttamente, uguale a false (falso) in caso contrario
• eof: restituisce un valore di tipo bool uguale a true (vero) se la lettura ha raggiunto la fine del file,
uguale a false (falso) in caso contrario
• close: chiude lo stream. Per evitare risultati imprevedibili in esecuzione, è opportuno chiudere tutti gli
stream aperti prima della conclusione del programma.
Infine ci serviamo, in perfetta analogia con quanto visto fino a questo momento nel caso di cin,
dell'operatore >> (applicato in questo caso a inputFile) per l'effettiva lettura del contenuto di test_input.txt,
una stringa alla volta.
if (inputFile.is_open())
{
while (!inputFile.eof())
{
inputFile>>data;
cout<<data<<" ";
}
cout<<"\n";
inputFile.close();
}
Osserviamo che, mano a mano che il file di testo viene letto, il suo contenuto viene stampato a video tramite
cout. Ciò, semplicemente, ci consente di verificare il corretto funzionamento del programma. Nel corso del
prossimo esercizio, esercizio16b.cpp, ci focalizzeremo sulla scrittura su file.
Scrittura su file
Le operazioni da svolgere per la scrittura su file sono caratterizzate da una sintassi analoga a quella già
esaminata per la lettura su file: evidenziamo quindi soltanto le differenze (il corretto funzionamento
dell'esempio può essere verificato compilando ed eseguendo esercizio16b.cpp).
In particolare, lo stream di output viene associato al file di testo di nome test_output.txt e dichiarato come
oggetto di tipo ofstream e di nome output_file:
ofstream outputFile;
Ci serviremo poi dell'operatore << sia per la stampa di messaggi a video che indichino all'utente come
comportarsi (tramite cout) sia per l'effettiva scrittura del contenuto, letto da tastiera tramite cin, sul file di
testo test_output.txt. In questo secondo caso, l'operatore << sarà applicato ad output_file:
if (outputFile.is_open())
{
while (data.compare("exit")!=0)
{
cout<<"Digita il contenuto da inserire nel file di output, oppure 'exit' per terminare l'esecuzione\n";
cin>>data;
if (data.compare("exit")!=0)
{
outputFile<<data<<" ";
}
}
outputFile<<"\n";
outputFile.close();
}
Osserviamo inoltre che ci serviamo del metodo compare della classe string (v. lezione 10) per riconoscere
quando l'utente digita la la stringa exit, indicando con ciò di voler terminare l'esecuzione del programma.
Da tenere presente anche come, nel caso di output su file, se il file di testo indicato per l'output risultasse
inesistente, il programma non genererebbe un errore in esecuzione. Un file di testo vuoto, con nome
corrispondente a quello indicato, verrebbe invece creato automaticamente.
Nel prossimo e conclusivo esempio osserviamo come uno stesso stream possa essere utilizzato sia in input,
sia in output.
Esempio conclusivo
Il codice relativo a quest'ultimo esempio è contenuto in esercizio16c.cpp, il file di testo su cui lavoreremo, sia
in input che in output è test.txt. Inizialmente test.txt contiene una serie di numeri maggiori di 0, separati da
spazi. L'obiettivo del programma è di leggere tali numeri, individuare il valore massimo e dividere ciascuno
dei numeri letti dal file per tale valore. I risultati vanno stampati poi sullo stesso file test.txt.
Anche in questo caso, così come per esercizio16b.cpp, mettiamo in evidenza le differenze rispetto agli
esempi precedentemente trattati. Per servirsi di test.txt sia in input che in output, utilizziamo un oggetto di
tipo fstream. La modalità di accesso allo stream (lettura o scrittura) andrà specificata come parametro del
metodo open. Ad esempio, per la fase iniziale, il cui obiettivo è leggere tutti i numeri contenuti in test.txt,
scriveremo:
testFile.open("test.txt", ios::in);
La modalità di accesso in lettura viene specificata tramite ios::in. Si procede poi alla lettura di tutti i numeri
contenuti in test.txt, che vengono salvati in una coda (v. lezione 14) di nome data, che contiene variabili di
tipo double:
Come possiamo vedere, il valore massimo viene aggiornato mano a mano che si inseriscono nuovi dati e
viene salvato nella variabile di tipo double e di nome max_val.
In seguito, si procede all'elaborazione dei dati, dividendo ciascun valore per max_val. Ci avvaliamo, per
ottenere tale risultato, dell'uso di un iteratore (v. lezione 15), associato alla coda di nome data:
list<double>::iterator iter;
for (iter = data.begin(); iter != data.end(); iter++)
{
*iter = *iter/max_val;
}
A seguito dell'elaborazione, i nuovi dati devono essere stampati sul file test.txt. Per poter procedere alla
stampa, dobbiamo perciò aprire in scrittura uno stream associato proprio a test.txt:
La modalità di accesso in scrittura viene specificata tramite ios::out. Tramite ios::app invece si indica al
compilatore che il file di destinazione non deve essere sovrascritto, ma aggiornato. In altre parole, i nuovi
dati devono essere aggiunti in coda al file (operazione di append).
A questo punto, stampiamo il contenuto della coda di nome data sul file test.txt, servendoci ancora
dell'iteratore di nome iter, così come per l'elaborazione:
if (testFile.is_open())
{
testFile<<"\n";
for (iter = data.begin(); iter != data.end(); iter++)
{
testFile<<*iter<<" ";
}
testFile<<"\n";
testFile.close();
}
Per completezza, segnaliamo che il codice contenuto in esercizio16c.cpp è completato anche da due cicli
for destinati alla stampa a video dei dati, prima e dopo l'elaborazione, tramite cout. Tali cicli, ancora una
volta basati anche sull'uso dell'iteratore iter non sono trattati poichè non introducono nuovi elementi, dal
punto di vista concettuale o sintattico. Si tratta semplicemente di operazioni funzionali a offrire all'utente una
maggiore chiarezza circa il funzionamento del programma.