Cpp IO järjestelmän kehittyneemmät piirteet

Mureakuha

Loikkaa: valikkoon, hakuun

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 &note( 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):

AttribuuttiSelitys
0normal file
1read-only file
2hidden file
4system file
8archive bit set

Moden on oltava yksi tai useampi (OR) seuraavista arvoista:

ios::appkaikki kirjoitukset lisätään tiedoston loppuun
ios::ateavauksessa luku/kirjoitin osoite siirtyy tiedoston loppuun
ion::insyöttötiedosto (ei tarvita, jos avataan ifstream)
ios::nocreateavaus epäonnistuu, jos tiedostoa ei ole olemassa
ios::noreplaceavaus epäonnistuu, jos tiedosto on jo olemassa
ios::outtulostustiedosto (ei tarvita, jos avataan ofstream)
ios::truncolemassa 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):

goodbit0:OK, 1:error
eofbit1: eof, muuten 0
failbit0:OK, 1: ei-fataali I/O-virhe
badbit0: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 &note( 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;
}
Tämän dokumentin kopiointi, levittäminen sekä muokkaaminen on sallittua GNU Free Documentation Licensen version 1.2 tai uudemman Free Software Foundationin julkaiseman version mukaisesti, ilman muuttumattomuuslauseketta tai kansitekstejä. Tätä koskee vastuuvapaus.
Kopio lisenssistä (englanniksi) löytyy täältä.

Alkuperäinen (c) Petteri Hämäläinen

Henkilökohtaiset työkalut