Uno stream è una sequenza di byte che all’interno di un programma può essere gestita utilizzando un oggetto di un’opportuna classe. Ossia all’interno di un programma è possibile interagire con uno stream attraverso l’oggetto ad esso associato e, pertanto, il programmatore può identificare uno stream con l’oggetto stesso. La figura seguente mostra la gerarchia delle classi C++ per gli stream.
In particolare la classe istream gestisce gli stream di INPUT e implementa i meccanismi per INTERPRETARE e CONVERTIRE sequenze di caratteri in valori di diverso tipo (vedi gli esempi più avanti); viceversa, la classe ostream gestisce gli stream di OUTPUT e implementa i meccanismi per CONVERTIRE valori di vario tipo in sequenze di caratteri.
Quando un programma è in esecuzione, per esso alcuni stream sono già pronti all’uso e sono:
- cin, lo STANDARD INPUT; tipicamente (di default) quest’oggetto è associato alla TASTIERA del terminale da cui il programma è mandato in esecuzione; esso è di classe istream e appartiene al namespace std.
- cout, lo STANDARD OUTPUT e cerr, lo STANDARD ERROR; questi oggetti tipicamente (di default) sono collegati al terminale (MONITOR) da cui il programma è mandato in esecuzione; entrambi sono di classe ostream e appartengono al namespace std.
Le periferiche a cui gli (I/O)STREAM STANDARD sono collegati, però, possono essere cambiate rispetto a quelle di default, come mostrato nel seguente esempio:
1 2 3 4 5 6 7 8 9 10 |
//modifica il collegamento dello standard input e output freopen("input.txt", "r", stdin); freopen("ouput.txt", "w", stdout); int n; //legge dal file input.txt (non più da tastiera) cin>>n; //scrive sul file output.txt (non più sul monitor) cout<<n; |
Come creare uno stream di input e/o di output su file (cioè di classe ifstream, ofstream e più in generale fstream), invece, l’abbiamo già visto in un altro articolo (link articolo).
Vediamo ora alcuni semplici esempi che ci permetteranno di fare alcune utili osservazioni. Consideriamo lo stream di input standard, ma considerazioni analoghe valgono anche per la lettura da un file di testo. Quando l’utente, per esempio, digita sulla tastiera i tasti 1 e 0 e successivamente preme l’INVIO, sullo stream dell’input standard vengono inviati i due caratteri ‘1’ e ‘0’ e:
- con l’esecuzione delle istruzioni seguenti:
1 2 |
int n; cin>>n; |
l’oggetto cin preleva i caratteri 1 e 0 e li CONVERTE nel numero intero 10, in modo che ci sia corrispondenza di tipo fra valore e tipo della variabile utilizzata per leggere con la cin, cosicché possa essere memorizzato correttamente nella variabile n di tipo intero;
- se le istruzioni, invece, sono le seguenti:
1 2 |
string s; cin>>s; |
l’oggetto cin preleva i caratteri 1 e 0, li INTERPRETA come caratteri e come tali essi vengono memorizzati nella stringa s.
Più in generale:
- quando in uno stream di input ci sono dei caratteri numerici (interi o reali), se per leggerli si utilizzano delle variabili di tipo numerico (rispettivamente, intere o reali), le cifre sono convertite in numeri saltando i CARATTERI SPECIALI, quali spazi, tab e newline. Ad esempio, digitando in input:
10 SPAZIO SPAZIO 12.67 TAB SPAZIO 13
dopo l’INVIO, con le seguenti istruzioni:
1 2 3 4 5 6 7 |
int a, c; float b; cin>>a>>b>>c; cout<<"a="<<a<<endl; cout<<"b="<<b<<endl; cout<<"c="<<c<<endl; cout<<"a+b="<<a+b<<endl; |
si ottiene di stampare a video:
1 2 3 4 |
a=10 b=12.67 c=13 a+b=22.67 |
- quando in uno stream di input ci sono dei caratteri alfanumerici, se per leggerli si utilizzano delle variabili di tipo char e/o stringa, i caratteri e le stringhe vengono prelevati saltando i CARATTERI SPECIALI, quali spazi, tab e newline. Ad esempio, digitando in input:
Mario SPAZIO SPAZIO Rossi TAB 01/05/1985
dopo l’INVIO con le seguenti istruzioni:
1 2 3 4 5 6 7 |
char c; string s1, s2, s3; cin>>c>>s1>>s2>>s3; cout<<"c="<<c<<endl; cout<<"s1="<<s1<<endl; cout<<"s2="<<s2<<endl; cout<<"s3="<<s3<<endl; |
si ottiene di stampare a video:
1 2 3 4 |
M ario Rossi 01/05/1985 |
Il problema degli errori di input e lo STREAM STATE
Quando l’utente inserisce un input numerico inaspettato, costituito da un valore non corrispondente al tipo della variabile con cui si legge, il programma va incontro a malfunzionamenti. Vediamolo con un semplice esempio:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#include <iostream> using namespace std; int main(){ int a; double b; cout<<"INSERISCI UN NUMERO INTERO: "; cin>>a; cout<<"INSERISCI UN NUMERO REALE: "; cin>>b; cout<<endl<<"a="<<a<<endl; cout<<"b="<<b<<endl; return 0; } |
- se l’utente inserisce come primo input il numero reale 12.5, subito dopo l’INVIO la seconda operazione di input da parte dell’utente viene saltata e si ottiene di visualizzare a video:
1 2 3 |
INSERISCI UN NUMERO REALE: a=12 b=0.5 |
Questo succede perché nello stream di input, la prima cin legge fino all’ultimo carattere interpretabile come un intero (ossia il 2), lasciando gli altri caratteri (ossia .5) nello stream di input; subito dopo va in esecuzione la seconda cin che converte nel numero reale 0.5 i caratteri lasciati nello stream dalla cin precedente e successivamente vengono eseguite le operazioni di output con le due cout. Sicuramente non è quello che volevamo!
- se l’utente, invece, inserisce come primo input la sequenza di caratteri: 12r, dopo l’invio si ottiene di visualizzare a video:
1 2 3 |
INSERISCI UN NUMERO REALE: a=12 b=0 |
Questo perché la prima cin, per lo stesso motivo di prima, legge fino al carattere 2. Subito dopo va in esecuzione la seconda cin che non legge correttamente in quanto non trova corrispondenza di tipo fra quello che c’è nello stream (ossia il carattere r) e la variabile utilizzata per leggere con la cin. Sicuramente non è quello che volevamo!
Queste situazioni di errore andrebbero gestite e per farlo può essere utile leggere lo stream state. Ciascun (i/o)stream si trova in uno STATO memorizzato in un insieme di flag (valori booleani) all’interno dell’oggetto associato a quello stream. Gli errori e le condizioni non standard di uno stream, possono essere gestite in diversi modi testandone opportunamente lo stato. Vediamone alcuni.
1. L’oggetto cin utilizzato con l’operatore di lettura (>>) fornisce un valore di ritorno booleano:
- FALSE, quando l’operazione di input NON ha avuto successo: formato dell’input completamente errato (ad esempio nel caso di una cin su una variabile di tipo numerico, quando già il primo carattere dello stream non corrisponde ad un carattere valido, cioè interpretabile come parte di un input numerico) o è stato incontrato EOF, per cui la prossima operazione di input fallirà.
- TRUE, quando l’operazione di input ha avuto successo.
Pertanto, le istruzioni cin>>… e !(cin>>…) possono essere utilizzate dove è atteso un valore booleano, ad esempio in una if() o in un while(), per controllare e gestire eventuali errori di input non validi (vedi l’esempio in fondo all’articolo).
2. Gli oggetti di classe fstream, ifstream e ofsream restituiscono FALSE in corrispondenza di errori che si possono verificare in fase di apertura del file. Un esempio di utilizzo è il seguente:
1 2 3 4 5 6 |
fstream miofile("prova.txt", ios::in); if(!miofile){ cout<<"Errore nell'apertura del file"<<endl; }else{ //operazioni sul file } |
3. Il metodo eof() dell’oggetto associato ad uno stream di input, permette di leggere il flag associato alla condizione di raggiungimento della fine dello stream. Tale metodo restituisce il valore booleano TRUE quando viene letto il carattere EOF. Il carattere EOF è generato con CTRL+Z, nel caso di input da un terminale WINDOWS, con CTRL+D, nel caso di un terminale UNIX. Nel caso si stia leggendo un file attraverso un oggetto di classe ifstream, il metodo eof() con TRUE segnala la lettura del carattere di fine file.
4. Tutti gli oggetti di un (i/o)stream, mettono a disposizione i seguenti metodi per recuperare il loro stato interno (stream state):
- fail() restituisce TRUE se si è verificato un errore nell’apertura dello stream, FALSE altrimenti;
- good() restituisce TRUE se l’operazione effettuata sullo stream è andata a buon fine, FALSE altrimenti;
- bad() restituisce TRUE se l’operazione effettuata sullo stream ha prodotto un errore irreversibile, FALSE altrimenti;
- is_open() restituisce TRUE se il file è aperto, FALSE altrimenti.
NOTA BENE – Attenzione, una volta che uno stream si porta in uno stato di errore, esso ci rimane finché i flag non sono esplicitamente resettati e in quel frattempo le operazioni di input su quello stream sono operazioni nulle. Quindi, prima di effettuare la successiva operazione di input, bisogno resettare lo stato dello stream utilizzando il metodo clear(). Bisogna, inoltre, tener presente che quando un’operazione di input fallisce, dallo stream di input non viene rimosso alcun carattere, pertanto bisogna ripulire anche lo stream richiamando il metodo ignore(). Vediamo un semplice esempio:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include <iostream> using namespace std; int main(){ double n; cout<<"Inserisci un numero: "; while(!(cin>>n)){ //si entra se cin incontra un input "completamente" non valido, es. la stringa "a21" cin.clear(); cin.ignore(80, '\n'); cout<<"Errore, formato non valido. Devi inserire un numero"<<endl; cout<<"Inserisci un numero: "; } return 0; } |