La classe istream
gestisce l'ingresso sul flusso, la ostream
l'uscita; abbiamo già incontrato due istanze di tali classi: cin
non è altro che un oggetto di tipo istream
collegato per default allo Standard Input (di solito la tastiera), cout
non è altro che un oggetto di tipo ostream
collegato allo Standard Output (di solito il video). Ci sono allora due modi semplici di utilizzare le classi di cui abbiamo appena appreso l'esistenza.
Il primo semplice metodo consiste nell'istanziare degli oggetti di tipo istream
e ostream
. I costruttori hanno le seguenti dichiarazioni:
ostream::ostream ()
ostream::ostream (streambuf* SB) istream::istream () istream::istream (streambuf* SB) |
ostream
o istream
in memoria. Più interessante è il secondo tipo di costruttore per ciascun tipo di stream, il quale accetta come argomento un puntatore a streambuf
. Vediamo brevemente di cosa si tratti. Ciascuno stream possiede una interfaccia utente astratta che il programmatore utilizza tramite delle funzioni membro, anch'esse di tipo astratto; tuttavia ciascun tipo di stream, come ogni altro tipo di dati, possiede una struttura interna che funga da tramite tra la risorsa utilizzata (video, tastiera, file, memoria) e le funzioni di interfaccia della classe. Tale implementazione interna delle classi di iostream è rappresentata dalla classe streambuf
; a questo punto è lecito chiedersi: come mai ``dare un nome'' alla implementazione interna di una classe? Non si tratta di un controsenso implicito della programmazione astratta del C++, che volutamente nasconde e rende inaccessibile l'interno di un tipo di dati? No. La classe streambuf
è fondamentale per effettuare operazioni a basso livello che, in processi di input/output, possono risultare fondamentali in termini di efficienza. Siccome tali operazioni solo al di là degli scopi di questo testo, l'unica cosa che ci interessa sapere degli streambuf
, oltre alla loro esistenza, è che esiste per le classi di iostream la funzione membro rdbuf()
, la quale restituisce un puntatore allo streambuf
che giace all'interno dello stream in questione. In questa maniera possiamo costruire degli oggetti i quali si colleghino internamente ad altri, già esistenti, come vediamo nel seguente semplice esempio:
// ex12_6_1.cpp #include <iostream.h> void main() { ostream out(cout.rdbuf()); istream in(cin.rdbuf()); int n; in >> n; out << n << "\n"; }
gli oggetti in
e out
non sono altro che degli stream internamente collegati a cin
e cout
, per cui qualunque cosa andrà scritta o letta da out
e in
subirà il medesimo processo su cin
e cout
.
Un metodo più efficace di sfruttare le classi istream
e ostream
consiste nel derivare le proprie classi di input/output da esse, eventualmente specializzandole per i nostri scopi. Vedremo un esempio per ciascun tipo di flusso.
Supponiamo di avere necessità di uno stream di uscita che tenga in memoria le prime uscite: basta derivare la nostra classe, MioOStream
, da ostream
(si tenga presente dunque che MioOStream
è un ostream
) e aggiungere la struttura dati che ci serve e le funzioni membro per accederla. Nell'esempio seguente abbiato deciso di immagazzinare le prime NUM
(una macro alla quale abbiamo assegnato il valore arbitrario 10) parole in un vettore di char*; le chiamate al costruttore e al distruttore vengono segnalate sullo standard output, sul quale avviene anche la stampa delle immissioni all'interno del distruttore.
// ex12_6_2.cpp #include <iostream.h> #include <string.h> #define NUM 10 class MioOStream : public ostream { friend MioOStream& operator<< (MioOStream& os, const char* word); char** parole; int index; public: MioOStream (streambuf* sb); ~MioOStream (); }; MioOStream::MioOStream (streambuf* sb) : ostream(sb) { cout << "MioOStream::MioOStream()\n"; parole = new char*[NUM]; for (int i = 0; i < NUM; i++) parole[i] = 0; index = 0; } MioOStream::~MioOStream () { cout << "\ninizio MioOStream::~MioOStream()\n"; cout << index << " parole:\n"; for (int i = 0; i < index; i++) cout << (i+1) << ": " << '\t' << parole[i] << '\n'; for (int i = 0; i < NUM; i++) delete[] parole[i]; delete[] parole; cout << "fine MioOStream::~MioOStream()\n"; }; MioOStream& operator<< (MioOStream& os, const char* word) { if (os.index < NUM) { os.parole[os.index] = new char[strlen(word)+1]; strcpy (os.parole[os.index], word); os.index++; } (ostream&)os << word; return os; } void main () { MioOStream out (cout.rdbuf()); out << "questa " << "e` " << "una " << "prova. " << "ciao!"; }
output:
MioOStream::MioOStream() questa e` una prova. ciao! inizio MioOStream::~MioOStream() 5 parole: 1: questa 2: e` 3: una 4: prova. 5: ciao! fine MioOStream::~MioOStream()
Per il costruttore di MioOStream
abbiamo mantenuto la sintassi del costruttore per un ostream
con argomento, al fine di potere collegare il nostro oggetto di tipo MioOStream
con lo stardard output. Come si vede, abbiamo ridefinito l'operatore `<<
', all'interno del quale poi effettuiamo la chiamata all'operatore della classe base tramite:
(ostream&)os << word; |
Un procedimento simile può essere utilizzato per classi derivate da istream
; nell'esempio che segue abbiamo supposto di volere una classe di stream in entrata, la quale sia del tutto simile alla istream
, tranne per il fatto che ponga a zero tutti gli interi inferiori, in valore assoluto, a minimo
, un campo membro privato di MioIStream
.
// ex12_6_3.cpp #include <iostream.h> class MioIStream : public istream { int minimo; friend MioIStream& operator>> (MioIStream& is, int& n); public: MioIStream (streambuf* sb, int m = 0); ~MioIStream (); }; MioIStream::MioIStream (streambuf* sb, int m) : istream(sb) { cout << "MioIStream::MioIStream()\n"; minimo = m; } MioIStream::~MioIStream () { cout << "MioIStream::~MioIStream()\n"; } MioIStream& operator>> (MioIStream& is, int& n) { int x; (istream&)is >> x; if (x >= is.minimo || x <= -is.minimo) { n = x; } else n = 0; return is; } void main() { MioIStream in(cin.rdbuf(), 10); int n; for (int i = 0; i < 4; i++) { in >> n; cout << n << '\t' ; } cout << '\n'; }
esempio di output:
MioIStream::MioIStream() 25 7 -4 -12 25 0 0 -12 MioIStream::~MioIStream()
In questa maniera possiamo disporre immediatamente di classi che siano altamente specializzate per i nostri scopi e, al contempo, potenti e testate sotto ogni altro profilo.
Presentiamo a questo punto alcune funzioni membro tipiche di ostream
e istream
. Cominciamo con due funzioni che permettono di ``scrivere'' su di uno stream di uscita, rispettivamente, un carattere ed una stringa:
ostream& ostream::put (char c)
ostream& ostream::write (char* s, int len) |
Un semplicissimo esempio che mostri l'utilizzo di tali funzioni è il seguente:
// ex12_6_4.cpp #include <iostream.h> void main() { char s[] = "abcdefghij"; for (int i = 0; i < 10; i++) cout.put(s[i]); // output: abcdefghij cout.write(s, 4); // output: abcd }
Per quanto riguarda l'input di singoli caratteri abbiamo le seguenti:
int istream::get ()
int istream::peek () istream& istream::get (char& c) |
Le prime due funzioni sono simili: entrambe restituiscono l'ultimo carattere letto dallo stream (attenzione: è un int); la differenza risiede nel fatto che, mentre get
estrae il carattere letto, peek
effettua unicamente la lettura. Chiariamo immediatamente la differenza tramite l'esempio cge segue.
// ex12_6_5.cpp #include <iostream.h> void main() { char c; for (int i = 0; i < 5; i++) { c = cin.get(); cout << c; } // input: abcde // output: abcde for (int i = 0; i < 5; i++) { c = cin.peek(); cout << c; } // input: fghjk // output: fffff }
La terza funzione presentata agisce come la prima versione di get
, con la differenza che viene restituito lo stream stesso e il carattere viene salvato in c.
Due funzioni riguardanti l'input di stringhe sono le seguenti:
istream& istream::get (char* s, int len)
istream& istream::getline (char* s, int len) |
'\0'
. Vedremo esempi di queste funzioni nelle prossime sottosezioni.