next up previous contents index
Next: ios Up: Cenni sulla IOStream Previous: Cenni sulla IOStream   Indice   Indice analitico

iostream

Le prime due12.3 classi che incontriamo riguardano l'input e l'output in generale; la filosofia che è alla base della scelta di fornire al programmatore delle classi le quali gesticano dei ``flussi'' intesi in senso astratto, consiste nel fornire una interfaccia del tutto indipendente dal tipo in senso stretto di stream; si vuole lasciare al programmatore la possibilità di gestire l'uscita a video con lo stesso strumento utilizzato per la scrittura su disco e nella memoria o su qualunque altra cosa possa servirgli; idem per l'input.

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)
entrambe le classi posseggono un costruttore default, il quale semplicemente alloca un oggetto del tipo 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;
in maniera tale che avvenga comunque l'uscita a video del dato immesso nello stream.

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)
Il funzionamento delle due è simile: entrambe estraggono dallo stream len caratteri e li immagazzinano nella stringa s; ambedue restituiscono in uscita lo stream stesso. Nel caso si raggiunga la condizione di end of file prima che vengano letti len caratteri, la funzione termina restituendo la stringa della massima dimensione possibile; in ogni caso, la stringa viene automaticamente terminata con '\0'. Vedremo esempi di queste funzioni nelle prossime sottosezioni.


next up previous contents index
Next: ios Up: Cenni sulla IOStream Previous: Cenni sulla IOStream   Indice   Indice analitico
Claudio Cicconetti
2000-09-06