Datorer och programmering TDB3, VT 1999

Kort om strömmar i C++

För att läsa och skriva data i C++ används s.k. streams. Begreppet (eller snarare verkligheten bakom begreppet) är mycket kraftfullt bland annat genom att streams ingår i en arvshierarki med allt vad detta innebär av fördelar med återanvänd kod och likheter i hur olika sorters input/output hanteras. I kursmaterialet finns ingen sammanhållen beskrivning av hur de olika stream-klasserna ser ut och hur man använder dem, en brist som dessa sidor försöker avhjälpa. Mycket av det som står här är att betrakta som överkurs men det kan kanske vara av ett visst intresse ändå.

Vid referenser till header-filer används i detta dokument den nya standard som antagits, d.v.s utan ".h": iostream i stället för iostream.h t.ex. Alla kompilatorer implementerar inte den nya standarden, en del gör det bara delvis.

Arvshierarki

I toppen (botten?) på hierarkin finns klassen ios (deklarerad i streambuf). De båda klasserna istream (av vilken cin är en instans) och ostream (av vilken cout, cerr och clog är instanser) ärver ios. För filhantering finns sedan ifstream (ärver istream) och ofstream (ärver ostream) och för skrivning till och läsning från interna strängar finns ostringstream (och ostrstream) respektive istringstream (och istrstream) som ärver istream respektive ostream. Header-filerna heter iostream, fstream respektive stringstream (strstream). Skillnaden mellan klasserna i stringstream och de i strstream är att de förra opererar på instanser av den inbyggda klassen string medan de senare opererar på variabler av typen char * (s.k. C-strängar).

I släktträdet anger de streckade linjerna dels att klasserna inte alltid finns, dels också att det är fråga om överkurs.

Klassen istream

Vi börjar med att titta på de viktigaste metoderna och operatorerna i klassen istream som används för att läsa från terminalen (eller filer eller interna strängar genom arvet). För alla inbyggda typer som t.ex. int och double finns operatorn >> för läsning från instanser av klassen istream. Detta bör vara välbekant:
  int a,b:
  double c;
  cin >> a >> b >> c:
läser t.ex. från terminalen två int och en double. Alla operatorerna returnerar en referens till sig själv (en instans av istream) vilket är förklaringen till att flera variabler kan läsas in i ett uttryck som i exemplet ovan. Observera att operatorn >> binder från vänster till höger; variablerna läses alltså i ordningen a, b, c och cin >> a returnerar cin vilket "lämnar kvar" uttrycket cin >> b >> c o.s.v.

Operatorena har egenskapen att alla separatorer (s.k. whitespace d.v.s. mellanslag, tab och newline) ignoreras. Detta är oftast naturligt och det är väl egentligen endast vid läsning tecken för tecken som det ibland kan förefalla ologiskt. För att läsa alla tecken inklusive separatorer finns metoderna get() och get(char &).

Några metoder i istream:

  int get();
  istream& get( char &s);
  istream& get( char *ptr, int len, char delim='\n');
  istream& getline( char *ptr, int len, char delim='\n'):
  int peek();
  istream& putback( char c);
  istream& ignore( int n=1, int delim=EOF);
  istream& read( void *ptr, int n);

Värdet EOF (ofta -1) signalerar slut på "filen", i fallet istream att man skrivit EOF-värdet som i UNIX oftast är CTRL/D. Det används dels som returvärde för get(), dels också som delimiter till t.ex. ignore() och getline(). Viktigt att tänka på om man vill kunna detektera EOF när man läser med get() är att läsa till en int så att -1 "får plats".

Metoderna get() och get(char &) läser ett tecken. Skillnaden är att get() returnerar tecknet som funktionsresultat (som en int för att kunna returnera EOF) medan get(char&) lägger tecknet i en referensparameter och inte i sig talar om att det är slut på filen. Hur kan då tester som t.ex. while( cin.get( c) ) {} fungera? Detta är överkurs men om du är intresserad, läs vidare så kommer förklaringen.

Skillnaden mellan get(char*,int,char) och getline(char*,int,char) är att den förstnämnda inte läser bort newline-tecknet. Observera att det finns en tredje parameter till dessa båda: man kan alltså läsa till nästa tab eller till EOF t.ex. genom att ändra defaultvärdet '\n'.

Metoderna peek() och putback() är bra att ha vid exempelvis tolkning av uttryck: peek() låter oss tjuvkika på nästa tecken utan att ta bort det från input-bufferten medan putback() låter oss lägga tillbaka det senast lästa tecknet eller i och för sig vilket tecken som helst (fungerar tyvärr inte med mer än ett tecken). Med ignore() kan vi kasta bort ett antal tecken utan att läsa dem. Parametern n är det maximala antalet tecken vi kastar och delim är ett tecken som avslutar ignorerandet.

Även i ios finns några metoder av intresse. Eftersom de finns i ios ärvs de också av ostream och kan alltså användas även av instanser av den klassen (och genom arvet av alla klasser i hierarkin). Exempel på metoder:

  int good() const; // sann om inget fel inträffat
  int bad() const;  // sann om man försökt göra något otillåtet 
  int fail() const; // sann om man t.ex skriver bokstäver i tal
  int eof() const;  // sann om man nått slutet av "filen"
  void clear();     // nollställer alla felflaggor
Notera att metoden eof() returnerar sant först sedan man försökt läsa efter att ha nått filslut, det räcker inte att filpekaren har hamnat vid filens slut.

En del metoder i istream,som t.ex. read(), behandlas i beskrivningen av ifstream eftersom de oftast används på filer. En specialitet är den s.k. manipulatorn ws som hoppar över allting fram till nästa icke-whitespace:

  int a;
  char s[10];
  cin >> a >> ws;
  cin.getline(s, 10):
I exemplet vill vi vara säkra på att inga blanka inleder strängen s vilket vi åstadkommer genom manipulatorn ws. Problem uppkommer endast när man blandar operatorer och metoder. Manipulatorer är viktigare när man skriver data (ostream) och vi återkommer där till hur de används och vad de egentligen är. Följande manipulatorer kan vara användbara vid läsning med instanser av klassen istream och dess arvingar:
   oct, dec, hex - bestämmer hur indata tolkas (oktalt, decimalt, hexadecimalt)
   setw(int)     - anger hur många tecken som ryms i teckenfältet man läser till
   ws            - hoppa fram till nästa icke vita tecken
   boolalpha     - indata av typen bool anges true eller false
   noboolalpha   - indata av typen bool anges 1 eller 0

Klassen ostream

Precis som istream har också ostream en överlagrad operator (<<) för alla inbyggda typer:
  int a = 0, b = 10;
  double d = 1.2;
  cout << a << " "<< b << " " << d << endl;
Redan i detta enkla exempel ser vi exempel på en manipulator: endl som skriver ut '\n', d.v.s. newline. En annan viktig manipulator är flush som tömmer skrivbuffeten. Viktiga metoder i ostream (deklarerade i ios men används knappast av istream) är:
  width(int);    // sätter minimalt antal tecken för nästa utskrift 
  precision(int);// anger antal signifikanta siffror i utskrift av flyttal
  precision() const; // returnerar aktuell precision
  fill( char);   // sätter tecken att använda vid utfyllnad (default ' ')
  long flags(long);     // sätter alla formatflaggor
  long flags() const;   // returnerar formatflaggorna
  long setf( long );    // sätter en eller flera formatflaggor
  long unsetf( long);   // nollställer en eller flera formatflaggor
  long setf(long, long);// nollställer ett formatfält, sätter sen flaggor
De metoder som sätter flaggor returnerar också den aktuella flagguppsättningen. Om man tillfälligt vill ändra t.ex. flaggan showpos (se nedan för definition), skriva ut något med denna flagga satt, och sedan återställa flaggorna efter sig gör man alltså ungefär så här:
  long savedflags = cout.flags():
  long tempflags = savedflags | ios::showpos;
  cout.flags( tempflags);
  cout << a;
  cout.flags( savedflags);
Följande formatflaggor finns:
  skipws     - skippa separatorer vid inmatning (gäller alltså istream).
  left	     - vänsterjustering
  right      - högerjustering
  internal   - utfyllnad mellan tecken och värde
  dec        - talbas 10
  oct        - talbas 8
  hex        - talbas 16
  showbase   - visa talbas vid utskrift
  showpoint  - visa avslutande nollor
  uppercase  - E,X i.st.f. e,x
  showpos    - visa explicit plustecken
  scientific - exponentnotation
  fixed      - med decimalpunkt, ej exponentnotation
  unitbuf    - visa utmatning efter varje operation
  stdio      - ... eller efter varje tecken
Eftersom flaggorna är definierade som bitar i en long kan man kombinera flaggor med operatorn |. Vissa av flaggorna (dec, oct, hex t.ex.) kan inte vara satta samtidigt. Detsamma gäller grupperna left, right och internal respektive scientific och fixed. För att göra det möjligt att enkelt sätta exakt en av dessa flaggor definieras tre grupper (fields) av flaggor: basefield, adjustfield och floatfield. Den andra varianten av setf() (den med två parametrar) används för att sätta exakt en av dessa inbördes oförenliga flaggor: den andra parametern skall vara ett av fälten ovan, den första den flagga vi vill sätta. Om vi vill ha utskrift i hexadecimal notation t.ex. skriver vi:
  cout.setf( ios::hex, ios::basefield);
Som den uppmärksamme sett har flaggorna i programexemplen prefixet ios::. Det skall vara så och beror på att flaggorna är definierade i klassen ios.

Fältet ios::floatfield och precision() samverkar på ett mindre uppenbart sätt: om scientific notation används anger parametern till precision antalet signifikanta siffror medan om fixed notation används betyder parametern antalet decimaler.

Att använda metoder för att sätta flaggor, antal siffror, m.m. fungerar bra men skiljer själva utskrifterna från formateringsfunktionerna på ett olyckligt sätt. Därför finns också en mängd manipulatorer med samma funktion som metoderna. För att använda dessa bör filen iomanip inkluderas (det är nödvändigt att inkludera filen om manipulatorer med parame- trar används). De manipulatorer som finns är (förutom ws i istream):
  oct, dec, hex       - sätter talbas
  endl, ends          - skriver ny rad respektive strängavslutning ('\0')
  flush               - töm utskriftsbufferten
  setbase(int)        - sätter talbas
  setfill(int)        - sätter fyllkaraktär (ej för nykterister)
  setprecision(int)   - sätter precision
  setw(int)           - sätter width
  resetiosflags(long) - nollställer en eller flera flaggor
  setiosflags(long)   - sätter en eller flera flaggor
Ett programexempel med tillhörande utskrift:
#include <iostream> 
#include <iomanip>

main() {  
  int i1 = 15;
  int i2 = 3;
  int i3 = -12;
  double d1 = 3.45;
  double d2 = 13.457123;

  char c1[] = "Donald Duck";

  cout << i1 << " " << i2 << " " <<   d1 << " " << d2 <<              // 1
    " " << c1 << endl; 

  cout << hex << setw(4) << i1 <<   setw(4) << oct << i1 << endl;     // 2

  cout << dec << setfill('0') << setw(4) << i1 << endl;               // 3

  cout << setfill(' ') << setiosflags(ios::right) <<  "'" <<          // 4
    setw(20) << c1 << "'" << endl;

  cout << setfill(' ') << setiosflags(ios::left) <<  "'" <<           // 5
    setw(20) << c1 << "'" << endl;

  cout << resetiosflags(ios::left) <<  resetiosflags(ios::right) <<   // 6
    setfill(' ') << setiosflags(ios::internal) << setw(5) << i3 << endl;

  cout << setprecision(8) << d2 << endl;                              // 7

  cout << setiosflags(ios::fixed) << d2 << endl;                      // 8

  cout << setprecision(2) << d2 << endl;                              // 9

  cout << setiosflags(ios::scientific) << d2 << endl;                 //10

  cout << setprecision(4) <<                                          //11
    setiosflags(ios::showpos|ios::showpoint) << d1 << endl;

  cout << setiosflags(ios::showbase) << dec << i1 <<   hex << " "<<   //12
    i1 << oct << " " << i1 << endl;

  cout << resetiosflags(ios::showbase) << dec << i1 <<   hex << " "<< //13 
    i1 << oct << " " << i1 << endl; 
}
Utskriften från programmet blir (siffrorna och kommentarerna till höger kommer inte ut utan är ditmonterade i efterhand för att underlätta referenser till programmet):
15 3 3.45 13.4571 Donald Duck	( 1) - default, t.ex. precision = 6
  f 17				( 2) - olika talbaser
0015 				( 3) - annan fyllkaraktär
'         Donald Duck'		( 4) - högerjsutering
'Donald Duck         '		( 5) - vänsterjustering
-  12 				( 6) - "mellan tecken och värde" just.
13.457123 			( 7) - precision 8
13.45712300 			( 8) - precision 8 och ios::fixed
13.46 				( 9) - precision 2 och ios::fixed
13 				(10) - precision 2, ios::scientific
+3.450 				(11) - visa tecken och avslutande 0:or
+15 0xf 017 			(12) - visa talbas (0x och 0)
+15 f 17	                (13) - visa ej talbas

Klasserna ifstream och ofstream

De båda klasserna ifstream och ofstream används för att läsa från och skriva till filer. Några av de metoder som tas upp här är definierade på lägre (högre?) nivå i hierarkin men jag tycker att de logiskt hör hemma här. Kom också ihåg att allt det som deklarerats i ostream och istream (och ios) också finns i instanser av klasserna ofstream respektive ifstream.

Filer kan öppnas på två sätt, dels redan i konstruktorn som i:

  ifstream filetoreadfrom("indata.txt");
  ofstream filetowriteto("utdata.txt");
dels också genom metoden open:
  ifstream file1;
  file1.open("indata.txt);
Vilket man använder beror på omständigheterna. På samma sätt kan man också stänga filer på två sätt: endera genom att destruktorn implicit anropas, något som som vanligt sker endera när filvariabeln hamnar out-of-scope (statiska variabler) eller när delete anropas (dynamiska variabler). Det andra sättet att stänga filer är med metoden close:
  file1.close();
Vid läsning av binärdata används med fördel metoden read(), vars första parameter pekar ut en dataarea där vi kan lägga data och parameter två är antalet bytes som ska läsas. Om vi vill läsa in data till en klass t.ex. kan vi skriva en metod read() till klassen:
  void myclass::read( ifstream &is ) 
   {
    is.read( (char*) this, sizeof(*this) ):
   }
Metoden gcount() (i istream egentligen) returnerar antalet bytes som lästes i den senaste "binära" läsoperationen (d.v.s. med metoderna get(), getline() och read() ).

Motsvarande read finns metoden write() för skrivning i ostream:

  void myclass::write( ofstream &os ) 
   {
    is.write( (char*) this, sizeof(*this) ):
   }
Det finns också metoder för positionering i filer. Dessa kan användas för filer där vi vill kunna läsa data icke-sekventiellt (länkade listor på fil t.ex.). För filer som vi skriver till finns:
  seekp( streampos);          //flytta till positionen streampos
  seekp( streamoff, seek_dir);//flytta streamoff bytes från början eller slutet eller..
  streampos tellp();          //returnera var vi befinner oss (räknat i bytes)
För filer som vi läser från:
  seekg(streampos);
  seekg(streamoff, seek_dir);
  streampos tellg();
Med dessa metoder kan vi flytta oss i filer samt ta reda på var i filen vi just nu befinner oss. Parametern seek_dir kan vara en av (deklarerade i ios):
  ios::beg      - från början av filen
  ios::cur      - från aktuell position
  ios::end      - från slutet av filen
Sist i detta dokument finns ett exempel (rstream) där dessa metoder används. Typerna streampos och streamoff är normalt long.

Som avslutning om filer kan nämnas att det finns ytterligare en frivillig parameter till konstruktorerna och metoden open(): möjlighet att ange hur filen ska öppnas genom att ange en kombination av logiska flaggor (deklarerade i ios):

  ios::in        - för läsning
  ios::out       - för skrivning
  ios::ate       - positionera vid filens slut
  ios::app       - append, skriv i slutet av filen
  ios::trunc     - töm filen först
  ios::nocreate  - kräver att filen finns
  ios::noreplace - kräver att filen inte finns

Klasserna istrstream och ostrstream

De båda klasserna för internformatering har ingen ny funktionalitet utan förlitar sig i stort sett helt på arvet från istream respektive ostream. Exempel på användning av ostrstream:
  char buf[256];
  ostrstream oss(buf,256);//  längden av strängen måste vara med !!
  oss << i << j << ends;  // i och j antas vara instanser av klasser/typer
Observera ends; strängarna termineras inte automatiskt med '\0'.

Klassen istrstream används på likartat sätt (i exemplet antar vi att s är en sträng med int's):

  int sumofints( char *s ) 
   {
    istrstream is(s)  // längden behöver inte vara med
    int i, sum = 0;
    while (is >> i) sum += i;
    return sum;
   }

Att överlagra stream-operatorer

Den kanske vanligaste användningen av överlagring av operatorer är överlagring av utskrift cch inläsning. Eftersom vi inte kommer åt stream-klasserna måste dessa operatorer skrivas som globala funktioner, d.v.s. med två parametrar, den första alltid stream-klassen, den andra den klass vi vill kunna skriva ut eller läsa in instanser av. Oftast deklareras dessa operatorer som friends till klassen. Detta måste ske i klassdeklarationen: varje klass bestämmer enväldigt vilka vänner den vill ha. Om klassen ger användaren möjlighet att komma åt data genom t.ex. index-operator eller på annat sätt behöver inte friend-deklarationer tillgripas.

Ett enkelt exempel på hur stream-operatorer kan överlagras i en personklass (först person.h):

#include <iostream>

class person {
  private:
    char *firstname;
    char *surname;
  public:
    person( char *f, char *s);
    const char *getfname() { return firstname;}
    const char *getsname() { return surname; }
    friend ostream& operator<<( ostream &os, person &p);
    friend istream& operator>>( istream &is, person &p):
};
Implementationsfilen person.cpp:
#include "person.h"

person::person( char *f, char *s ) 
 {
  firstname = new char [strlen(f)+1]; strcpy(firstname,f);
  surname = new char[strlen(s)+1]; strcpy(surname,s);
 }

ostream& operator<<( ostream &os, person &p)
 {
  return os << p.getfname() << " " << p.getsname();
 }

istream& operator>>( istream &is, person &p) 
 {
  delete[] p.firstname; delete[] p.lastname;
  char tmp[256];
  is >> tmp; p.firstname = new char[strlen(tmp)+1]; strcpy(p.firstname,tmp):
  is >> tmp; p.surname = new char[strlen(tmp)+1]; strcpy(p.surname,tmp);
  return is;
 }
Observera här att deklarationerna måste se ut så här med referenser till ostream och istream och att referenser till en av dessa också måste returneras - detta för att inläsning och skrivning av ens egna klasser skall fungera på samma sätt som de inbyggda. Dessa båda operatorer fungerar nu till såväl terminalen som till filer och interna strängar i programmet. Operatorerna deklareras i header-filen så att andra programmoduler "ser" dem och vet att de finns.

De båda operatorerna ovan är inte metoder varför de inte kan göras virtuella. Men går det då inte att få ett virtuellt uppförande för dessa operatorer? Jo, det gör det: man kan skriva en virtuell funktion som anropas från en operator. Som exempel kan vi skriva ut personen ovan med en funktion som vi kan kalla virtual print( ostream &os). Efter att ha deklarerat denna i person.h och skrivit implementationen i person.cpp kan vi skriva om operator<< enligt (med detta trick skulle vi också slippa deklarera operatorerna som friends även om funktionerna getfname() och getsname() inte funnes):

print( ostream &os) 
 {
  os << firstname << " " << surname;
 }
ostream& operator<<( ostream &os, person &p) 
 {
  p.print(os);
  return os;
 }
En eventuell klass som ärver denna klass kan sedan förses med en egen print() som kommer att anropas av utskriftsoperatorn.

Speciellt om filer och arv

Filer och arv, hur fungerar det? Inte speciellt bra, tyvärr. Antag att vi har klasser som ärvt person. Vi kan t.ex. tänka oss student (med litet betygskrafs som nya attribut) och teacher (med någonting om vilka kurser hon eller han ansvarar för som nya attribut). Man skulle kanske vilja kunna skriva och och läsa data till/från instanser av dessa klasser utan att behöva hålla ordning på vilken typ av person man läser eller skriver.

Det man tvingas göra är att definiera en unik kod för varje typ av person. Vid utskrift av en person skrivs denna kod ut följt av attributen ett efter ett. Försök inte skriva ut alla attribut i ärvda klasser med hjälp av this-pekaren - det fungerar inte beroende på att klasser med arv innehåller en "hemlig" pekare (som används vid anrop av virtuella funktioner för att välja rätt funktion) som också skrivs ut. Vid läsning sedan skall man ha en enorm tur om denna pekare skulle peka på någonting av intresse.

Vid läsning läses först den unika klasskoden utanför läsoperatorn om sådan finns, sedan skapas en instans av rätt objekt varefter attributen läses ett efter ett. Inte roligt, men vad gör man...

Ett exempel: rstream (random stream)

Som avslutning presenteras ett exempel på en random stream kallad rstream. Med denna kan man skriva och läsa objekt var som helst i en fil: man får en indexerad fil i vilken posterna (som kan vara enkla typer eller instanser av klasser eftersom vi givetvis använder template) indexeras med ()-operatorn. För att allt skall fungera måste filen vara öppen för läsning och skrivning samtidigt. Detta åstadkoms genom att vi deklarerar filen som en instans av klassen fstream, en hittills ej behandlad klass som är en hybrid av ifstream och ofstream. Header-filen:
   //////////////////////////////////////////////////////////////////////////
   // 
   // File: rstream.h (Bo Nordin 951128) 
   // 
   // The class rstream implements random access streams 
   // Adapted and simplified from Budd: Classical Data Structures in C++ 
   //

   #ifndef RSTREAM_H 
   #define RSTREAM_H

   #include <fstream>

   template <class T> class rstream {
    public:
     rstream         ( char *filename); 
     ~rstream        ( void ); 
     T operator()    ( void );	                           // read current
     T operator()    ( unsigned int index);	           // read
     void operator() ( unsigned int index, const T &value);// write
     void operator=  ( const T &value );                   // write current
     bool operator!  ( void ) const;                       // NOT OK
     operator void*  ( void ) const;  	                   // OK
     int operator++  ( void );                             // move to next
     int operator--  ( void );                             // move to previous
     int numelements ( void ) const;	                   // # of elements
    private:  
     int good        ( void ) const;
     unsigned int index;
     fstream theStream;
   };

   # endif
Och implementationsfilen:
//////////////////////////////////////////////////////////////////////////
// 
// File: rstream.C (Bo Nordin 19951128) 
// 
// The class rstream implements random access streams 
// Adapted and simplified from Budd: Classical Data Structures in C++ 
//

#include <fstream>

#include "rstream.h"

template <class T> rstream<T>::rstream( char *name) 
 {
  theStream.open(name, ios::in | ios::out);
 }

template <class T> rstream<T>::~rstream() { theStream.close(); }

template <class T> T rstream<T>::operator()() 
 {
  T value;
  theStream.seekg(sizeof( T ) * index);
  theStream.read( (char*) &value, sizeof( T ));
  return value;
 }

template <class T> T rstream<T>::operator()(unsigned int ix) 
 {
  T value;
  index = ix;
  theStream.seekg( sizeof( T ) * index );
  theStream.read( (char*) &value, sizeof( T ));
  return value;
 }

template <class T> void rstream<T>::operator()(unsigned int ix,
                                               const T &value) 
 {
  index = ix;
  theStream.seekg(sizeof( T ) * index);
  theStream.write( (const char *) &value, sizeof( T ));
 }

template <class T> void rstream<T>::operator=( const T &value) 
 {
  theStream.seekg(sizeof( T ) * index);
  theStream.write( (const char *) &value, sizeof( T ));
 }

template <class T> rstream<T>::operator void*( void ) const 
 {
  return good(); 
 }

template <class T> bool rstream<T>::operator !( void ) const 
 {
  return (!good()); 
 }

template <class T> int rstream<T>::operator++( void ) 
 {
  index++;  
  return good(); 
 }

template <class T> int rstream<T>::operator--( void ) 
 {
  index--;
  return good();
 }

template <class T> int rstream<T>::numelements( void ) const 
 { 
  if (!theStream) return 0;
  else {  
    theStream.seekg(0, ios::end);
    return theStream.tellg() / sizeof( T );  
  }  
 }

template <class T> int rstream<T>::good( void ) const 
 {
  return ( (index >= 0) && (index < numelements()) );
 }
Några kommentarer till koden är på sin plats. Vi kan inte använda uttryck som fil[i] = a; för att skriva till vår fil eftersom index-operatorn returnerar ett värde av typen T (inte en referens eftersom referenser till saker på fil blir litet konstigt). Man skulle kunna tänka sig att skriva en []-operator men det finns inget sätt att få en sådan att inse att en tilldelning här betyder skrivning av värdet medan en läsning betyder att värdet ska returneras. Däremot kan fil = a; fungera för skrivning under förutsättning att vår klass håller sig med en aktuell position att skriva i (och läsa från med operatorn ()(void)). För skrivning av en post använder vi i stället()-operatorn med två argument: index och värde. Samma operator med bara ett argument låter vi betyda läsning.

Operatorerna void* och ! har använts för att uppnå kompabilitet med andra stream-klasser: man kan testa direkt på instanser av rstream på följande sätt:

  rstream fil("filename");
  if (fil) {}  // everything is fine
  if (!fil) {} // something is wrong
Till sist, ett litet program som använder rstream:
   // Filen testclass.h
   //
   #include <iostream>

   class testclass {
     double a[2];
     int b;
    public:
     testclass( void ) { a[0] = a[1] = 0.0; b = 0; }
     testclass( double a1, double a2, int i) { a[0] = a1; a[1] = a2; b = i; } 
     friend ostream& operator<< ( ostream &os, const testclass &tc) 
      {
       return os << "{ " << tc.a[0] << ", " << tc.a[1] << ", " << tc.b << " }";     
      } 
   };

   // Filen main.cpp
   // 
   #include <iostream>
   #include "rstream.cpp"
   #include "testclass.h"

   main() 
    {
     rstream<testclass> f1("testclass.data");
     rstream<int> f2("int.data");

     testclass t1(0.0, 1.0, 1);
     testclass t2(0.0, 2.0, 2);
     testclass t3(0.0, 3.0, 3);

     f1(2,t1);  f1(1,t2);  f1(0,t3);

     cout << f1(0) << endl << f1(1) << endl << f1(2) << endl;
     f2(12,1200);
     f2(6,600);
     f2(0,0);

     cout << f2(0) << endl << f2(6) << endl << f2(12) << endl;
    }
Utskrift från programmet:
{ 0, 3, 3 }
{ 0, 2, 2 }
{ 0, 1, 1 } 
0
600
1200

Detta dokument är en HTML-översättning av ett kursmaterial som ursprungligen skrevs hösten 1995 av Bo Nordin. Viss uppdatering av innehållet gjordes vid översättningen.
[kursens hemsida]
Senast ändrad: 99 03 04 av Bo Nordin

Skicka gärna ett brev med synpunkter och frågor till dina tdb3-lärare.