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.
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).
ipfstream
(ärver
ifstream
) och opfstream
(ärver
ofstream
) som är identiska med
ifstream
respektive ofstream
förutom att man kan läsa från och skriva till s.k. pipes, d.v.s UNIX-kommandon.
Om man vill läsa innehållet i aktuell katalog t.ex. kan man öppna en
ipfstream
med
ipfstream f("ls * |");
.
Dessa båda klasser är deklarerade i pfstream.h
(ja, med .h, denna fil finns inte med i
standarden).
istream
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 felflaggorNotera 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
ostream
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.
endl
och
flush
? Jo, det är funktioner deklarerade
ostream& endl(ostream&)
respektive
ostream& flush(ostream&)
. Klassen
ostream
har en överlagrad operator för att "skriva ut"
denna typ av funktioner och det är denna som används: ingenting skrivs ut men
funktionen anropas och gör sitt jobb. Samma princip (men något, eller snarare
rätt mycket, krångligare) används för de manipulatorer med parametrar som
beskrivs senare i detta avsnitt.
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 flaggorDe 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 teckenEftersom 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.
ios
. Observera att flaggorna i sig alltså inte är
variabler utan bara pekar ut en bit i en
private
-deklarerad variabel. Detta är förklaringen till
syntaxen ios::xx
. På liknande sätt är de tre fälten
deklarerade som or
mellan de ingående flaggorna.
Senare kommer vi också att se exempel på sökriktningarna
beg
, cur
och
end
som är deklarerade på samma sätt som flaggorna.
Ytterligare ett exempel på samma sak är
filöppningsmod: in
, out
,
app
med flera.
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 flaggorEtt 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
ifstream
och ofstream
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) ): }
if (cin.get()) {}
eller ifstream a("a"); if (!a) return;
. Hur går detta
till? cin
och a
är ju instanser av
en klass. Det andra fallet med !
-operatorn är enklast
att förklara: det är helt enkelt fråga om en överlagring av operatorn
!
(som returnerar fail()
) i
klassen ios
. Det är litet mer komplicerat
med den första testen (eller tester som if (a) { doit; }
där a
är en ström av något slag. Även här är det en
överlagrad operator som spökar: nämligen konverteringsoperatorn
operator void*()
. Men vem blir egentligen klokare av den
vetskapen? Man måste också veta vad som kan användas som logiska uttryck i
if
-satsen. Stroustrup skriver: "The expression must
be of an arithmetic or pointer type or of a class type for which an
unambiguous conversion to arithmetic or pointer type exists". Även om
alltså de C++-implementationer jag sett implementerar denna
konverteringsoperator kan man lika gärna (och mycket mer logiskt) tänka sig
operatorn operator bool()
- det fungerar lika bra att
implementera den.
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 filenSist 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
istrstream
och ostrstream
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/typerObservera
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; }
friend
s 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
friend
s ä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.
person
på formen "efternamn, förnamn". Detta är som gjort för en manipulator. Men kan
man skriva egna sådana? Javisst, inga problem. Vi inför först en variabel
reversedname
i klassen person
(statisk för att få samma uppförande från alla personer). Vi har nu
tagit bort getfname()
och
getsname()
och använder print()
i stället.
class person { private: char *firstname; char *surname; static bool reversedname; public: person( char *f, char *s); virtual void print( ostream &os); friend ostream& surnamefirst( ostream &os) { reversedname = true; return os;} friend ostream& firstnamefirst(ostream &os) { reversedname = false; return os;} }; ostream& operator<< ( ostream &os, person &p);Observera speciellt de båda funktionerna
surnamefirst()
och firstnamefirst()
(implementerade i h-filen endast
för att spara skrivarbete) som är deklarerade på samma sätt som de tidigare
behandlade manipulatorerna endl
och
flush
. Implementationsfilen har nu utseendet (vi visar
bara relevanta delar):
#include "person.h" int person::reversedname = 0; person::person( char *f, char *s ) { firstname = new char [strlen(f)+1]; strcpy(firstname,f); surname = new char[strlen(s)+1]; strcpy(surname,s); } void person::print( ostream &os) { if (reversedname) os << surname << ", " << firstname; else os << firstname << " " << surname; } ostream& operator<< ( ostream &os, person &p) { p.print(os); return os; }Exempel på användning:
#include <iostream> #include "person.h" main() { person me("Bo", "Nordin"); cout << surnamefirst << me << endl; // ger "Nordin, Bo" cout << firstnamefirst << me << endl;// ger "Bo Nordin" }
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.
this
-pekaren om arvshierarkin saknar virtuella
funktioner. Å andra sidan: en arvshierarki utan virtuella funktioner
saknar oftast mening.
rstream
(random stream)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
.
fstream
ärver i verkligheten klassen
iostream
som verkligen är en hybrid eftersom den ärver
både istream
och ostream
.
Förutom iostream
ärver fstream
också en klass som heter fstreambase
som också ärvs av
både ifstream
och ofstream
.
Samma struktur finns i strstream: klassen strstream
är en hybrid av istrstream
och
ostrstream
.
////////////////////////////////////////////////////////////////////////// // // 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; }; # endifOch 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.
[]
-operatorn returnera instanser
av denna klass. Denna klass förses med operatorer för tilldelning
(operator=
) och för konvertering
(operator T
). Tilldelning används vid skrivning,
konverteringsoperatorn vid läsning. Tilldelningen
fil[i] = fil[j]
blir nu med operatorerna utskrivna i
funktionsform:fil.operator[](i).operator=(fil.operator[](j).operator T())
rstream
på detta sätt.
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 wrongTill 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
Skicka gärna ett brev med synpunkter och frågor till dina tdb3-lärare.