Cpp IO järjestelmän kehittyneemmät piirteet
Mureakuha
Tässä kappaleessa opitaan tekemään omia I/O-manipulaattoreita sekä tiedostojen käsittelyä. C++:n I/O järjestelmä on hyvin rikas ja joustava. Kaikkea ei käydä tässä läpi, mutta tärkeimmät osat kylläkin.
Sisällysluettelo |
Omien manipulaattoreiden luonti
Sovelluskohtaiset manipulaattorit ovat tärkeitä kahdesta syystä. Ensiksikin manipulaattoriin voidaan koota kokonainen joukko I/O-operaatioita. Usein ohjelmassa tarvitaan samaa sekvenssiä I/O-operaatioita useassa kohdassa. Omalla manipulaattorilla yksinkertaistetaan koodia sekä pienennetään virhemahdollisuutta. Toisaalta omat manipulaattorit voivat olla tärkeitä, kun suoritetaan I/O:ta ei-standardi laitteille. Manipulaattoria voidaan käyttää esimerkiksi lähettämään ohjauskoodeja erityistulostimelle tai optiselle tunnistimelle.
Sovelluskohtaiset manipulaattorit ovat C++:n ominaisuus joka tukee OOP:tä, mutta niistä on hyötyä myös ei oliopohjaisissa ohjelmissa. I/O-manipulaattoreiden avulla saadaan I/O-pohjaiset ohjelmat selkeämmiksi ja tehokkaammiksi.
I/O-manipulaattoreita on kahta tyyppiä: toiset vaikuttavat syöttövirtaan toiset tulostusvirtaan. Manipulaattorit voidaan jakaa myös sellaisiin, jotka tarvitsevat argumentin ja sellaisiin, jotka eivät tarvitse. Parametrillisen ja parametrittoman manipulaattorin luonnissa on merkittävä ero. Parametrillisten manipulaattoreiden luonti on huomattavasti vaikeampaa kuin parametrittomien, eikä sitä käsitellä tässä tekstissä lainkaan.
Kaikilla parametrittomilla tulostusmanipulaattoreilla on rakenne:
ostream &manip-name( ostream &stream ) { // manipulaattorin koodi return stream; }
Tässä manip-name on luotavan manipulaattorin nimi. Funktio palauttaa referenssin ostream-tyyppiseen tulostusvirtaan, tämä on välttämätöntä, mikäli manipulaattoria käytetään laajemmassa I/O-lauseessa. Vaikka manipulaattorin ainoana argumenttina on referenssi virtaan, jota manipulaattori käsittelee, mitään argumenttia ei käytetä, kun manipulaattori laitetaan tulostusoperaatioon.
Kaikilla parametrittomilla syöttömanipulaattoreilla on rakenne:
istream &manip-name( istream &stream ) { // manipulaattorin koodi return stream; }
Tässä manip-name on luotavan manipulaattorin nimi. Funktio palauttaa referenssin istream-tyyppiseen syöttövirtaan, tämä on välttämätöntä, mikäli manipulaattoria käytetään laajemmassa I/O-lauseessa. Vaikka manipulaattorin ainoana argumenttina on referenssi virtaan, jota manipulaattori käsittelee, mitään argumenttia ei käytetä, kun manipulaattori laitetaan syöttöoperaatioon.
Seuraavassa ohjelmassa luodaan manipulaattori nimeltä setup, joka asettaa kentän leveyden 10:ksi, tarkkuuden 4:ksi ja täyttömerkin *:ksi.
#include <iostream> ostream &setup( ostream &stream ) { stream.width(10); stream.precision(4); stream.fill('*'); return stream; } main() { cout.setf( ios::fixed ); cout << setup << 123.123456; return 0; }
Sovelluskohtaisten manipulaattoreiden ei tarvitse olla monimutkaisia ja silti ne voivat olla hyvin käyttökelpoisia. Esimerkiksi atn- ja note manipulaattoreilla hoidetaan usein tarvittujen tekstien tulostus.
#include <iostream> ostream &atn( ostream &stream ) { stream << "Attention: "; return stream; } ostream ¬e( ostream &stream ) { stream << "Please Note: "; return stream; } main() { cout << atn << "High voltage circuit\n"; cout << note << "Turn off all lights\n"; return 0; }
Seuraava getpass manipulaattori soittaa kelloa ja kehottaa antamaan salasanan.
#include <iostream> #include <string.h> istream &getpass( istream &stream ) { cout << '\a' << "Enter password: "; return stream; } main() { char pw[80]; do { cin >> getpass >> pw; } while (strcmp( pw, "password" )); cout << "Logon complete\n"; return 0; }
Tiedostojen käsittely-I/O:n perusteet
Tiedostojen käsittelyyn tarvitaan header file: fstream, jossa on määriteltynä useita luokkia kuten ifstream, ofstream ja fstream. Nämä luokat on johdettu istream ja ostream luokista, jotka ovat edelleen johdettu ios-luokasta. Näin ollen ifstream, ofstream ja fstream luokista on pääsy ios-luokkaan. Katso Turbo C++ Programming Tips & Techniques, ios.
C++:ssa tiedosto avataan kytkemällä tiedosto streamiin. Streameja on kolmea tyyppiä: input, output ja input/output. Ennen kuin tiedosto voidaan avata on ensin saatava stream käyttöön. Syöttövirta luodaan määrittelemällä ifstream-tyyppinen muuttuja. Tulostusvirta luodaan, määrittelemällä muuttuja, joka on ofstream tyyppinen ja syöttö/tulostusvirta syntyy fstream-tyyppisen muuttujan luonnilla. Esimerkiksi seuraavassa luodaan syöttövirta, tulostusvirta ja syöttö/tulostusvirta.
ifstream in; ofstream out; fstream io;
Virran kytkeminen tiedostoon tapahtuu esimerkiksi funktion open() avulla. Funktio on jokaisen edellisen kolmen virran jäsenfunktio. Seuraavassa open-funktion prototyyppi:
void open( char *filename, int mode, int access );
Tässä filename voi sisältää myös polun. Mode määrää, miten tiedosto avataan. Access määrää, miten tiedostoa käsitellään. Access on ympäristöstä riippuva ja DOS-ympäristössä sen arvot yleensä vastaavat tiedostoattribuuttien koodeja (useita OR:n avulla):
| Attribuutti | Selitys |
|---|---|
| 0 | normal file |
| 1 | read-only file |
| 2 | hidden file |
| 4 | system file |
| 8 | archive bit set |
Moden on oltava yksi tai useampi (OR) seuraavista arvoista:
| ios::app | kaikki kirjoitukset lisätään tiedoston loppuun |
| ios::ate | avauksessa luku/kirjoitin osoite siirtyy tiedoston loppuun |
| ion::in | syöttötiedosto (ei tarvita, jos avataan ifstream) |
| ios::nocreate | avaus epäonnistuu, jos tiedostoa ei ole olemassa |
| ios::noreplace | avaus epäonnistuu, jos tiedosto on jo olemassa |
| ios::out | tulostustiedosto (ei tarvita, jos avataan ofstream) |
| ios::trunc | olemassa oleva tiedosto tyhjentyy avauksessa |
Seuraava koodin pätkä avaa normaalin tulostustiedoston DOS-ympäristössä:
ofstream out; out.open( "test", ios::out, 0 );
Harvoin kuitenkaan näkee kutsuttavan open-funktiota kuten edellä, sillä sekä modella että accessilla on oletusarvot. ifstream:n moden oletusarvo on ios::in ja ofstream:n moden oletusarvo on ios::out. Access:n oletusarvona on normaalitidosto. Edellinen koodin pätkä on siis tavallisesti:
ofstream out; out.open( "test" );
Jos tiedosto avataan sekä lukemista että kirjoitusta varten, avaus tapahtuu seuraavasti:
fstream io; io.open( "test", ios::in | ios::out );
Jos avaus epäonnistuu virran arvoksi tulee nolla:
ifstream in; in.open( "test" ); if (!in) { cout << "Avaus epäonnistui\n"; // virheen käsittely }
Vaikka tiedostot voidaankin avata open-funktiolla, niin ei kuitenkaan useinkaan tehdä. ifstream, ofstream ja fstream luokissa on konstruktorit, jotka avaavat tiedostot automaattisesti. Konstruktorilla on samat argumentit ja samat oletusarvot kuin open funktiolla. Yleisimmin tiedosto avataan:
ifstream in( "test" ); if (!in) { cout << "Avaus epäonnistui\n"; // virheen käsittely }
Tiedosto suljetaan close-jäsenfunktiolla. Esimerkiksi in.close(); close-funktiolla ei ole parametreja eikä se palauta mitään.
Syöttötiedoston loppu selvitetään eof-jäsenfunktiolla:
int eof();
Funktio palauttaa nollasta poikkeavan arvon, mikäli lukuosoite on tiedoston lopussa.
Kun tiedosto on saatu auki, sitä luetaan ja sinne kirjoitetaan vastaavasti kuin käytetään cin ja cout-streameja. Data menee levylle samassa formaatissa kuin se menisi päätteelle ja data tulee levyltä samassa formaatissa kuin se tulisi näppäimistöltä. Edellä kuvattu vastaa C:n fprintf- ja fscanf-funktioita.
Huomaa, että joitain merkkimuunnoksia tapahtuu (kuten fprintf:ssäkin) talletettaessa levylle, esimerkiki '\n' merkki tallettuu kahtena merkkinä: CR+LF.
Ohjelma luo tulostustiedoston, kirjoittaa siihen, sulkee tiedoston ja avaa sen uudelleen syötetiedostoksi, lukee sisällön ja tulostaa sen näytölle
#include <iostream> #include <fstream> main() { ofstream tulostus("test"); // tulostustiedoston luonti if (!tulostus) { cout << "Tulostustiedosto ei aukea!"; return 1; } tulostus << "Terve!\n"; tulostus << 100 << ' ' << hex << 100 << endl; tulostus.close(); ifstream syote("test"); // syötetiedoston luonti if (!syote) { cout << "Syötetiedosto ei aukea!"; return 1; } char teksti[80]; int i; syote >> teksti >> i; cout << teksti << ' ' << i << endl; syote.close(); return 0; }
Ohjelma tulostaa:
Terve! 100 Tiedoston test sisältö: Terve! 100 64
Seuraava ohjelma lukee käyttäjän kirjoittamia rivejä ja kirjoittaa ne levytiedostoon, kunnes käyttäjä antaa tyhjän rivin.
#include <iostream> #include <fstream> #include <stdio.h> main() { char fname[80]; cout << "Filename: "; cin >> fname; ofstream out(fname); if (!out) { cout << "Cannot open outputfile: " << fname << '\n'; return 1; } char str[80]; cout << "Write lines to disk, RETURN to stop\n"; do { cout << ": "; gets(str); out << str << endl; } while (*str); out.close(); return 0; }
Seuraava ohjelma kopioi tekstitiedoston ja muuttaa kaikki tyhjät merkit | merkiksi. Huomaa, kuinka käytetään eof() ja skipws, jotta alussa olevat tyhjät huomioitaisiin.
#include <iostream> #include <fstream> main() { char Ifname[80], Ofname[80]; cout << "Input Filename: "; cin >> Ifname; ifstream in(Ifname); if (!in) { cout << "Cannot open inputfile: " << Ifname << '\n'; return 1; } cout << "Output Filename: "; cin >> Ofname; ofstream out(Ofname); if (!out) { cout << "Cannot open outputfile: " << Ofname << '\n'; return 1; } char ch; in.unsetf(ios::skipws); while (!in.eof()) { in >> ch; if (ch==' ') ch = '|';; out << ch; } out.close(); in.close(); return 0; }
Binäärinen I/O
Vaikka tekstitiedostot ovat hyödyllisiä, ne eivät ole yhtä joustavia kuin binääriset tiedostot. C++:ssa on laaja tuki binääristen tiedostojen käsittelyyn.
Alimman tason binääriset I/O-funktiot ovat get() ja put(). Jäsenfunktiolla put() kirjoitetaan tavu tiedostoon ja get():llä luetaan tavu tiedostosta. Näiden yleisimmin käytetyt muodot ovat:
istream &get( char &ch ); ostream &put( char ch );
get()-funktio lukee merkin vastaavasta streamista ja vie merkin muuttujaan ch. Se palauttaa referenssin streamiin ja tämän arvona on null, mikäli tiedosto oli loppu (EOF). put()-funktio kirjoittaa merkin ch streamiin ja palauttaa referenssin streamiin.
Datalohko kirjoitetaan ja luetaan funktioilla write() ja read():
istream &read( unsigned char *buf, int num ); ostream &write( const unsigned char *buf, int num );
read()-funktio lukee num tavua streamista ja sijoittaa niiden kopiot alkaen osoitteesta buf. write()-funktio kirjoittaa num tavua streamiin alkaen osoitteesta buf. Jos tiedosto loppuu ennen kuin num tavua on luettu, read lukee puskuriin merkkejä vain niin monta kuin tiedostossa oli. Toisella jäsenfunktiolla gcount() saadaan selville, montako merkkiä itseasiassa luettiin:
int gcount();
Funktio palauttaa lukumäärän, kuinka monta merkkiä luettu viimeisellä binäärisellä lukuoperaatiolla.
Seuraava ohjelma tulostaa näytölle minkä tahansa tiedoston sisällön.
#include <iostream> #include <fstream> main() { char Ifname[80], ch; cout << "Input Filename: "; cin >> Ifname; ifstream in(Ifname); if (!in) { cout << "Cannot open file " << Ifname; return 1; } while (!in.eof()) { in.get( ch ); cout << ch; } in.close(); return 0; }
Seuraava ohjelma tallettaa merkkejä tiedostoon, kunnes käyttäjä antaa dollari-merkin. Ohjelma käyttää get-funktiota lukiessaan merkkejä näppäimistöltä, jolloin myös alussa olevat white-space merkit saadaan mukaan.
#include <iostream> #include <fstream> main() { char Ofname[80], ch; cout << "Output Filename: "; cin >> Ofname; ofstream out(Ofname); if (!out) { cout << "Cannot open file " << Ofname; return 1; } cout << "Enter $ to stop\n"; // seuraava cin.get( ch ) lukisi entterin, jota painettu // tiedostonimen jälkeen, niinpä poistetaan se: cin.get(ch); do { cin.get( ch ); out.put( ch ); } while (ch != '$'); out.close(); return 0; }
Seuraava ohjelma käyttää write()-funktiota ja kirjoittaa test-nimiseen tiedostoon muutaman muuttujan arvon. Sitten ohjelma käyttää read()-funktiota ja lukee arvot tiedostosta.
#include <iostream> #include <fstream> #include <string.h> main() { ofstream out("test"); if (!out) { cout << "Cannot open file"; return 1; } double num = 100.45; char str[80] = "This is a test"; out.write( (char *)&num, sizeof( double ) ); out.write( str, strlen(str) ); out.close(); num = 0; for (int i=0; i<80; i++) str[i] = '\0'; ifstream in("test"); if (!in) { cout << "Cannot open file"; return 1; } in.read( (char *)&num, sizeof( double ) ); in.read( str, 10 ); in.close(); cout << num << ' ' << str; return 0; }
Seuraava ohjelma vie tiedostoon taulukollisen double-tyyppisiä lukuja ja lukee ne takaisin. Lopuksi ohjelma raportoi luettujen merkkien lukumäärän.
#include <iostream> #include <fstream> main() { ofstream out("test"); if (!out) { cout << "Cannot open file"; return 1; } double nums[4] = { 1.1, 2.2, 3.3, 4.4 }; out.write( (char *)nums, sizeof( nums ) ); out.close(); for (int i=0; i<sizeof(nums)/sizeof(double); i++) nums[i] = 0; ifstream in("test"); if (!in) { cout << "Cannot open file"; return 1; } in.read( (char *)nums, sizeof( nums ) ); for (i=0; i<sizeof(nums)/sizeof(double); i++) cout << nums[i] << ' '; cout << '\n'; cout << in.gcount() << " characters read\n"; in.close(); return 0; }
Lisää binäärisiä I/O-funktioita
Aiemmin esitetyn muodon lisäksi get()-funktiota on kuormitettu usealla tavalla. Kaksi tavallista muotoa seuraavassa:
istream &get( char *buf, int num, char delim = '\n' ); int get();
Ensimmäinen edellisistä lukee merkkejä buf:n ilmoittamaan taulukkoon, kunnes on joko luettu num kappaletta merkkejä tai delim:n ilmoittama merkki tulee vastaan. get() laittaa loppunollan puskuriin. Mikäli delim-parametria ei anneta, oletusarvona on rivinsiirto. Delim-merkkiä ei siirretä puskuriin vaan se jää paikalleen, kunnes se luetaan seuraavalla lukuoperaatiolla.
get():n jälkimmäinen muoto palauttaa seuraavan merkin streamista, se palauttaa EOF:n mikäli tiedosto loppuu.
Myös jäsenfunktiolla getline() luetaan tiedostoa:
istream &getline( char *buf, int num, char delim = '\n' );
getline on käytännössä samanlainen kuin edellä esitetty get()-funktion ensimmäinen muoto. getlinen ero get-funktioon nähden on siinä, että get lukee myös delim-merkin puskuriin.
Funktiolla peek() voidaan lukea seuraava merkki poistamatta sitä streamistä:
int peek();
peek palauttaa seuraavan merkin streamissa ja EOF:n mikäli streamissa ei ole merkkejä.
Viimeksi luettu merkki voidaan palauttaa streamiin putback() funktiolla:
istream &putback( char c );
Jossa c on viimeksi luettu merkki.
Tulostuksessa dataa ei kirjoiteta välittömästi streamiin kytketylle fyysiselle laitteelle, vaan dataa varastoidaan sisäiseen puskuriin, kunnes puskuri on täysi. Puskuri voidaan kuitenkin pakottaa tyhjäksi (data viedään fyysiselle laitteelle) flush() funktiolla.
ostream &flush();
Kuten tietänet, kun >>:n avulla luetaan merkkijonoa, lukeminen lopetetaan ensimmäiseen whitespace merkkiin. Näin ollen sellaisten merkkijonojen lukeminen, jotka sisältävät tyhjiä merkkejä, ei onnistu. Asia kuitenkin korjaantuu getline() funktion avulla:
#include <iostream> #include <fstream> #include <string.h> main() { char str[80]; do { cout << "Enter Your name: "; cin.getline( str, 79 ); cout << str << '\n'; } while (strcmp( "Ville Vallaton", str )); return 0; }
Todellisissa ohjelmointitilanteissa peek() ja putback() funktiot ovat erittäin hyödyllisiä, koska ne helpottavat sellaisten tilanteiden käsittelyä, joissa ei etukäteen tiedetä, millaista informaatiota on milloinkin tulossa. Seuraavassa luetaan tiedostosta joko merkkijonoja tai kokonaislukuja, jotka voivat olla tiedostossa missä tahansa järjestyksessä.
#include <iostream> #include <fstream> #include <ctype.h> #include <stdlib.h> main() { char ch; ofstream out("test"); if (!out) { cout << "Cannot open output file.\n"; return 1; } char str[80], *p; out << 123 << "This is a test" << 23; out << "Hello there!" << 99 << "sdf" << endl; out.close(); ifstream in("test"); if (!in) { cout << "Cannot open input file.\n"; return 1; } do { p = str; ch = in.peek(); // seuraavan merkin kurkistus if (isdigit( ch )) { while (isdigit( *p = in.get() )) p++; // integerin lukeminen in.putback( *p ); // viedään merkki takaisin streamiin *p = '\0'; // päätetään merkkijono cout << "Integer: " << atoi(str); } else if (isalpha( ch )) { while (isalpha( *p = in.get() )) p++; // merkkijonon lukeminen in.putback( *p ); // viedään merkki takaisin streamiin *p = '\0'; // päätetään merkkijono cout << "String: " << str; } else cout << "Other (code): " << in.get(); cout << '\n'; } while ( !in.eof() ); in.close(); return 0; }
Random access
C++:n I/O-järjestelmässä suorahakuun käytetään funktioita seekg() ja seekp(). Näiden yleisimmät muodot seuraavassa:
istream &seekg( streamoff offset, seek_dir origin ); ostream &seekp( streamoff offset, seek_dir origin );
streamoff on iostream:ssa määritelty tyyppi, joka ylläpitää tietoa offsetin suurimmasta mahdollisesta arvosta. seek_dir on lueteltu tyyppi (enum), joka voi saada arvot:
ios::beg // siirrytään tiedoston alusta offsetin verran ios::cur // siirrytään tiedoston tämän hetkisestä paikasta offsetin verran ios::end // siirrytään tiedoston lopusta offsetin verran alkuun päin
Vastaavasti kuin C:ssä C++:ssa käytetään luku- ja kirjoitusosoitteita tiedostoja käsiteltäessä. Jokainen lukuoperaatio siirtää lukuosoitetta automaattisesti eteenpäin ja jokainen kirjoitusoperaatio siirtää kirjoitusosoitetta automaattisesti eteenpäin. Seekp():n ja seekg():n avulla voidaan kuitenkin siirtää kyseiset osoitteet juuri haluttuun kohtaan tiedostossa.
seekg() siirtää lukuosoitetta (get pointer) ja seekp siirtää kirjoitusosoitetta (put pointer). Siirros on offsetin verran tavuja origin:sta lähtien.
Luku/kirjoitusosoitteen paikka saadaan selville tellp() ja tellg() funktioilla:
streampos tellg(); streampos tellp();
streampos on iostream:ssa määritelty tyyppi, joka ylläpitää tietoa suurimmasta mahdollisesta arvosta, mitä funktiot voivat palauttaa.
Seuraavalla ohjelmalla muutetaan tietty merkki tiedostossa.
#include <iostream> #include <fstream> #include <string.h> #include <stdlib.h> void tulosta( ifstream &in ) { char str[80]; int paikka = 0; in.seekg(0L, ios::beg ); do { cout << paikka << ":"; // cout << ' ' << in.tellg() << ":"; // jostain syystä en saa toimimaan???: "paikka" korvaa. in.get( str, 79, '\n'); cout << str; cout << '\n'; paikka = paikka + in.gcount() + 2; if (!in.eof()) in.get(); } while (!in.eof()); } main() { char ch1, fname[80], str[80]; int nbr, i; cout << "Filename: "; cin >> fname; cout << "\n\n"; ofstream out(fname); if (!out) { cout << "Cannot open file " << fname; return 1; } char *istr[] = { "1111111\n", "2222222\n", "3333333\n", "4444444\n", "5555555\n", "6666666\n" }; for (i=0; i<6; i++) out.write( istr[i], strlen( istr[i] ) ); cout << "\n\n"; out.close(); ifstream in(fname); in.seekg(0L, ios::end ); cout << str << " size = " << in.tellg()<< '\n'; tulosta( in ); in.close(); cout << "Enter byte number: "; cin >> nbr; cout << "Enter new character: "; cin >> ch1; out.open( fname, ios::out|ios::ate ); out.seekp( nbr, ios::beg ); out.put( ch1 ); out.close(); cout << "\n\n"; in.open( fname, ios::in ); tulosta( in ); in.close(); return 0; }
I/O-statuksen tarkistus
C++:n I/O-järjestelmä ylläpitää jokaisen I/O-operaation status informaatiota. I/O-järjestelmän tila on talletettu integeriin, johon on koodattu seuraavat liput (ios:ssa):
| goodbit | 0:OK, 1:error |
| eofbit | 1: eof, muuten 0 |
| failbit | 0:OK, 1: ei-fataali I/O-virhe |
| badbit | 0:OK, 1: fataali I/O-virhe |
I/O-informaatiota voidaan käsitellä kahdella tavalla. Voidaan kutsua rdstate() jäsenfunktiota:
int rdstate();
Funktio palauttaa tilatiedon koodattuna integeriin.
Toinen tapa status informaation käsittelyyn on käyttää seuraavia funktioita:
| int good(); | True jos goodbit=1 |
| int eof(); | True jos eofbit=1 |
| int fail(); | True jos failbit=1 |
| int bad(); | True jos badbit=1 |
Jos joudutaan virhetilanteeseen, virhetilanne täytyy joskus nollata. Se tapahtuu:
void clear( int flags=0 );
Flags:n oletusarvolla 0, nollataan kaikki liput. parametrissa voidaan määrittää nollattavat liput.
Ohjelma tulostaa tekstitiedoston sisällön, virhetilanne raportoidaan checkstatus-funktiossa:
#include <iostream> #include <fstream> void checkstatus( ifstream &in ) { int i; i = in.rdstate(); if ( i & ios::eofbit ) cout << "EOF encountered\n"; else if ( i & ios::failbit ) cout << "Non-fatal I/O-error\n"; else if ( i & ios::badbit ) cout << "Fatal I/O-error\n"; } main() { char fname[80]; cout << "Filename: "; cin >> fname; ifstream in(fname); if (!in) { cout << "Cannot open file " << fname; return 1; } char c; while (in.get( c )) { cout << c; checkstatus( in ); } checkstatus(in); in.close(); return 0; }
Seuraava-ohjelma käyttää good()-funktiota:
#include <iostream> #include <fstream> main() { char ch, fname[80]; cout << "Filename: "; cin >> fname; ifstream in(fname); if (!in) { cout << "Cannot open file " << fname; return 1; } while (!in.eof()) { in.get( ch ); if (!in.good() && !in.eof() ) { cout << "I/O-error ... terminating\n"; return 1; } cout << ch; } in.close(); return 0; }
Oma I/O-järjestelmä ja tiedostot
Edellisessä luvussa opittiin, kuinka insertio- ja extraktio-operaattoreita kuormitettiin omiin luokkiin. Tällöin suoritettiin kuitenkin vain konsoli-I/O:ta. Koska streamit ovat samoja riippumatta siitä, mikä fyysinen laite streamiin on kytketty ( näyttö/tiedosto ), voidaan käyttää samoja insertteri- tai extraktorifunktioita.
Kuten esitetty edellisessä luvussa, kuormitettuja inserttereitä ja extraktoreita, samoin kuin I/O-manipulaattoreita, voidaan käyttää minkä tahansa streamin yhteydessä, sillä edellytyksellä, että ne on kirjoitettu yleiseen muotoon.
Seuraavassa ohjelmassa coord-luokka kuormittaa <<- ja >>-operaattoreita. Huomaa, kuinka operaattoreita käytetään sekä näytölle että tiedostoon tulostukseen.
#include <iostream> #include <fstream> class coord { int x, y; public: coord( int i, int j ) { x = i; y = j; } friend ostream &operator<<( ostream &stream, coord ob ); friend istream &operator>>( istream &stream, coord &ob ); }; istream &operator>>( istream &stream, coord &ob ) { stream >> ob.x >> ob.y; return stream; } ostream &operator<<( ostream &stream, coord ob ) { stream << ob.x << ' ' << ob.y << '\n'; return stream; } main() { coord o1(1,2), o2(3,4); ofstream out("test"); if (!out) { cout << "Cannot open file\n"; return 1; } out << o1 << o2; out.close(); ifstream in("test"); if (!in) { cout << "Cannot open file\n"; return 1; } coord o3(0,0), o4(0,0); in >> o3 >> o4; in.close(); cout << o3 << o4; return 0; }
Kaikkia I/O-manipulaattoreita voidaan käyttää tiedostojen yhteydessä. Seuraavassa on esitetty uudelleen aiemmin esitetty ohjelma, siten että kirjoitetaan tiedostoon:
#include <iostream> #include <fstream> #include <iomanip> ostream &atn( ostream &stream ) { stream << "Attention: "; return stream; } ostream ¬e( ostream &stream ) { stream << "Please Note: "; return stream; } main() { ofstream out("test"); if (!out) { cout << "Cannot open file\n"; return 1; } cout << atn << "High voltage circuit\n"; cout << note << "Turn off all lights\n"; out << atn << "High voltage circuit\n"; out << note << "Turn off all lights\n"; out.close(); return 0; }
Kopio lisenssistä (englanniksi) löytyy täältä.
Alkuperäinen (c) Petteri Hämäläinen
