Índex
Miguel A. Almarza
Departament d'Informàtica
IES Mare de Deu de la Merce

       

 
Capítol 6

Entrada i sortida.

  1. Introducció
  2. Insertors i estractors.
  3. Entrada i sortida amb format.
    1. Indicadors de formats. Funcions setf() i unsetf().
    2. La funció flags.
    3. Les funcions width(), precision() i fill().
    4. Els manipuladors.
  4. Sobrecàrrega dels operadors d'insercció i extracció.
  5. Creació de manipuladors.
  6. Entrada sortida a fitxers de disc
    1. Introducció
      1. Com obrir un fitxer.
    2. Descripció de les classes que conformen l'entrada/sortida
      1. Les classes relacionades amb els buffers.
        1. La classe filebuf
      2. La classe ostream.
      3. La classe istream.
      4. La classe iostream.
      5. La classe ofstream.
      6. La classe ifstream.
      7. La classe fstream.
    3. Exemple de fitxer amb accés seqüencial.
    4. Exemple de fitxer amb accés aleatori.
  7. La classe ios i l'estat d'un stream.
  8. Exercicis

6.1. Introducció

El C++ tracta l'entrada/sortida d'informació per mitjà de fluxos (streams). En realitat un flux és un dispositiu lògic que genera o consumeix informació. Aquest dispositiu lògic ha d'estar connectat a un dispositiu físic per mitjà dels objectes d'entrada/sortida del C++.

C++ té diverses classes que implementen tot el sistema d'entrada sortida. Existeixen dues jerarquies de classes que realitzen aquesta tasca.

Les classes relacionades amb l'entrada/sortida estàndard són ios, istream, ostream i iostream.

L'entrada/sortida estàndard són normalment teclat i monitor, encara que si el sistema permet redireccionar-les poden ser la impressora o altres dispositius.

També disposem de quatre fluxos (objectes) que s'executen automàticament, i ja estan programats. Aquests fluxos són objectes de la classe istream_withassign (cin) i ostream_withassign (cout, cerr, clog) i estan declarats a l'arxiu de capçalera iostream.h. Per tant, quan posem la instrucció #include <iostream.h> estem obrint aquests quatre fluxes:

Nom del fluxSignificatDisposotiu per defecte
cinEntrada estàndardTeclat
coutSortida estàndardMonitor
cerrError estàndardMonitor
clogVersió buffer de cerrMonitor

Fem notar que l'objecte cin disposa de totes les dades i funcions membre de la classe istream i de la classe ios. De la mateixa manera, els objectes cout, cerr i clog disposen de les dades i les funcions membre de les classes ostream i ios.

Els streams necessaris per a entrada o sortida de dades cap a un fitxer poden ser de una de les tres classes següents:

Nom de la classeTipus de fitxer
ofstreamper a fitxers oberts per a escriptura
ifstreamper a fitxers oberts per a lectura
fstreamper a fitxers oberts per a lectura i escriptura

Aquestes classes són també classes derivades de ios, istream i ostream.

6.2 Insertors i estractors.

Operador d'inserció <<

Ja hem fet ús al llarg d'aquests apunts d'instruccions semblants a la següent:

cout << "Hola món";

Veiem que l'operador << està sobrecarregat (dins la classe ostream) de manera que la informació que es troba a la dreta es inserida en el flux de sortida.

Aquesta és la raó de que a partir d'ara ens referirem com insertor per a l'operador <<.

Hem de dir que aquest operador està sobrecarregat per a tots els tipus de dades de C++ i que quan nosaltres programem una classe, també l'haurem de sobrecarregar per a aquest nou tipus de dades.

Operador d'extracció >>

També es fa ús de l'operador >> per extreure dades d'un flux i posar-les en un lloc de la memòria:

cin >> Valor;

on valor és una variable numèrica.

L'operador extractor es troba sobrecarregat a la classe istream i està sobrecarregat pera tots els tipus de dades de C++.

6.3 Entrada i sortida amb format.

6.3.1 Indicadors de formats. Funcions setf() i unsetf().

Els indicadors de format actuen sobre els fluxos de sortida i entrada per tal de executar diferents formats sobre les dades que entren o surten.

Aquests indicadors estan codificats en un enter llarg i podem actuar sobre ells mitjançant la funcions membre de la classe ios setf i unsetf. La primera activa l'indicador i la segona el desactiva.

Per a ajudar-nos a fer ús d'aquesta funció la classe ios disposa també d'una enumeració que posa nom a aquest indicadors de format i que es descriu a la taula següent:

IndicadorCom actua si activatValor
skipws Descarta espais en blanc, tabuladors i new line del fluxe d'entrada si està activat.0x0001
left La sortida s'alinea a l'esquerra0x0002
right La sortida s'alinea a la dreta0x0004
internal Els valors numèrics s'omplen amb espais en blanc fins a omplir tot el camp.0x0008
dec Conversió a decimal0x0010
oct Conversió a octal0x0020
hex Conversió a hexadecimal0x0040
showbase Mostra la base de conversió del número (decimal, octal o hexadecimal)0x0080
showpoint Mostra el punt decimal a tots els nombres.0x0100
uppercase La e d'exponencial i la x d'hexadecimal surten en majúscules.0x0200
showpos Mostra el signe + en els nombres positius.0x0400
scientific Els valors numèrics en coma flotant surten en notació científica.0x0800
fixed El contrari de scientific.0x1000
unitbuf El sistema d'entrada sortida dona sortida a les dades desprès de cada operació.0x2000


Així com veiem al programa següent les instruccions que fan ús de setf activen els indicadors uppercase i sohwcientífic.

Les funcions setf i unsetf
#include <iostream.h>

main()
{
    cout.setf(ios::uppercase);
    cout.setf(ios::scientific);
    cout << 16.;
    
    cout.unsetf(ios::uppercase);
    cout.unsetf(ios::scientific);
    cout << 16.;
    
    cout.setf(ios::showpos | ios::fixed);
    cout << 24;
}
La sortida del primer cout << 16.; per pantalla és

1.6E+01

La sortida del segon cout << 16.; per pantalla és

16

La sortida del tercer cout << 16.; per pantalla és

+24

Per activar més d'un indicador en una única instrucció veiem que es pot fer el or de bits.

6.3.2 La funció flags té dos formats.

Sempre és un bon hàbit restiruir els valors originals d'una variable després de fer-ne ús d'ella i així ho il·lustrem en el programa següent:

La funció flags
#include <iostream.h>

main()
{
    long IndicadorsAnteriors = cout.flags();

    cout.setf(ios::uppercase);
    cout.setf(ios::scientific);
    cout << 16. << endl;

    cout.setf(ios::showpos ¦ ios::fixed);
    cout << 24 << endl;

    cout.flags(IndicadorsAnteriors);
    cout << 16. << endl;
    cout << 24 << endl;

}
La sortida per pantalla és:

1.6E+01
+24
16
24


6.3.3 Les funcions width(), precision() i fill().


Aquestes funcions estableixen l'amplada del camp de sortida, el número de dígits de precisió i el caràcter per omplir els espais sense dades:

IndicadorTres funcions de ios
int width();
int width(int n);
Llegir i/o establir l'amplada de camp.
int precision();
int precision(int n);
Llegir i/o establir nombre de dígits significatius. La precisió per defecte és 6. En el d'un nombre real (float,...) la precisió és el nombre de decimals.
int fill();
int fill(int caracter);
Llegir i/o establir caràcter per omplir. Per defecte és l'espai en blanc.


Les funció precision, fill i width
#include <iostream.h>

main()
{
    //Llegir valors inicials
    int Precisio = cout.precision();
    int CaracterOmplir = cout.fill();
    int Amplada = cout.width();

    cout.width(15);
    cout.fill('$');
    cout.precision(2);
    cout << 3.458 << endl;


    //Torna a posar els valors inicials
    cout.precision(Precisio);
    cout.fill(CaracterOmplir);
    cout.width(Amplada);
}
La sortida per pantalla és:

$$$$$$$$$$$3.46


Fixeu-vos que tenim 11 caràcters $ i 4 caràcters en el número 3.46 i que com hem posat dos decimals de precisió ha arrodonit el número 3.458 que havia d'escriure.


6.3.4. Els manipuladors.


Són funcions globals, que no pertanyen a cap classe i que es poden inserir dins de les sentències d'entrada o de sortida. Hi ha alguns manipuladors que poden rebre paràmetres i altres que no. Per utilitzar els manipuladors que poden rebre paràmetres ha de incloure's l'arxiu de capçalera <iomanip.h>.

El propòsit d'alguns d'aquests manipuladors duplica la funció que ja ham vist en els indicadors, però a vegades és més fàcil fer ús d'aquests manipuladors.

ManipuladorPropòsitEntrada/sortida
decDades en format decimal.Entrada i sortida
endlDonar sortida a un caràcter nueva línia i volcar el flux.Sortida
endsDonar sortida a un NULLSortida
flushVolcar un fluxSortida
hexDades en format hexadecimalEntrada i sortida
octDades en format octalEntrada i sortida
resetiosflags(long f)Desactivar indicadors que es troben en fEntrada i sortida
setbase(int base)Fixa la base dels nomres amb el valor baseSortida
setfill(int ch)Posa el caràcter per omplir els campsSortida
setiosflags(long f)Activar indicadors que es troben en fEntrada i sortida
setprecision(int p)Posa el número de dígits de precisióSortida
setw(int amplada)Amplada de campSortida
wsDescartar espais en blanc incialsEntrada


Ús dels manipuladors
#include <iostream.h>

main()
{
    cout << 100 << endl;
    cout << oct << 100 << endl;
    cout << hex << 100 << endl;
}
La sortida per pantalla és:

100
144
64


6.4. Sobrecàrrega dels operadors d'insercció i extracció

Recordem la classe complex dels nombres complexos. Té dues dades (a i b) i també seria bo que per demanar o escriure les dades podéssim fer un programa main com el següent:

Funció mainSortida per pantalla
main()
{
    Complex z;

    cin >> z;
    cout << "Has introduït " << z;
}


Si seguim la lògica de la sobrecàrrega d'operadors binaris la forma general de la sobrecàrrega d'un operador d'entrada serà la següent:

friend ostream &operator<<(ostream &flux, classe Ob);

La crida cout << z; ens indica que l'objecte que crida a l'operador és el flux cout que és un flux de sortida i l'objecte que està a la dreta de l'operador és un número complex i per tant aquests dos són els paràmetres que es passen a la funció.

També recordem que ha de poder executar-se sentències com cout << z1 << z2; Per tant l'operador sobrecarregat ha de tornar el flux amb el que ha treballat per a que en faci ús el següent operador <<.

També es clar que l'operador no és una funció membre de la classe Complex sinó una funció amiga.

De la mateixa manera podem pensar que la forma general de la sobrecàrrega de l'operador d'extracció és:

friend istream &operator>>(istream &flux, classe &Ob);

La diferència ara és que passem una referència a l'objecte que ha de rebre les dades del flux a través d'aquest operador per tal d'actuar directament a sobre de la seva ubicació de memòria.

Veiem a continuació la implementació d'aquests operadors a la classe complex:

Sobrecàrrega dels operadors >> i << per a la classe complex
class Complex
{
    float a,b;
public:

    ·················································

    //Sobrecàrrega dels operadors insercció i extracció
    friend ostream &operator<<(ostream &flux, Complex z);
    friend istream &operator>>(istream &flux, Complex &z);
};

ostream &operator<<(ostream &flux, Complex z)
{
    flux << z.a;
    flux.setf(ios::showpos);
    flux << z.b << 'i';
    flux.unsetf(ios::showpos);
    return flux;
}

istream &operator>>(istream &flux, Complex &z)
{
    cout << "Dades d'un complex" << endl;
    cout << "Introdueix la part real: ";
    flux >> z.a;
    cout << "Introdueix la part imagin…ria: ";
    flux >> z.b;
    return flux;
}

main()
{
    Complex z;

    cin >> z;
    cout << "Has introduït " << z;
}

6.5 Creació de manipuladors.

Els manipuladors són funcions. També podem crear nosaltres funcions d'aquest tipus, funcions que realitzin una tasca determinada. Com a exemple tenim manipuladors que facin un format determinat per a sortida de números, o formats d'entrada de dades, etc.

En realitat aquest concepte es més un concepte de programació no orientada a objectes (procedimental) però que ens pot ajudar a donar formats dins els nostres programes C++.

La sintaxi general pels manipuladors és la següent:

Manipulador de sortidaManipulador d'entrada
ostream &NomManipulador(ostream &flux)
{
    ···················
    return flux;
}
istream &NomManipulador(istream &flux)
{
    ···················
    return flux;
}


Creació de manipuladors.
Exemple 1Exemple 2
#include <iostream.h>

ostream &PosaFrm1(ostream &Flux)
{
    Flux.width(20);
    Flux.precision(2);
    Flux.fill('@');

    return Flux;
}

main()
{
    cout << PosaFrm1 << 123.456789 << endl;
}
#include <iostream.h>
#include <string.h>

istream &DemanaPassword(istream &Flux)
{
    cout << '\a';
    cout << "Escriu la paralula de pas: ";

    return Flux;
}

main()
{
    char Password[100];

    do
    {
       cin >> DemanaPassword >> Password;
    }  while (strcmp(Password,"Pedro"));
    cout << "Molt b‚";
}

6.6. Entrada sortida a fiters de disc

La entrada/sortida en C++ es realitza mitjançant els objectes de la llibreria de streams.

El procés d'obrir un fitxer consisteix en declarar un stream (flux) que, per mitjà d'un buffer de memòria, ens connecta amb el fitxer físic de disc.

6.6.1 Introducció

Les classes mes usuals que ens donen accés a l'entrada sortida a fitxers de disc són ifstream, ofstream i fstream. Aquestes classes es deriven de les classe ios i per tant tenim accés a totes les dades i funcions de ios quan fem e/s cap a disc.

Això vol dir que tot el que hem estudiat fins ara de formats d'entrada sortida és vàlid també en fitxers.

Aquestes tres classes disposen de les funcions membre open i close. La funció membre open té la declaració general:

void open(const char *nom_fitxer, int mode, int accés=filebuf::openprot)


6.6.1.1. Com obrir un fitxer.

Per fer una sortida cap a disc primer es declara un flux de sortida (un objecte de tipus ofstream). Desprès aquest objecte ha d'executar el mètode open per tal d'associar el flux amb un fitxer de disc.

Així les dues instruccions següents obren el fitxer a:prova.txt amb el flux de sortida MiFitxer.
                    ofstream MiFitxer;
                    MiFitxer.open("a:prova.txt");
Podem crear directament el flux i la seva connexió amb el fitxer mitjançant una única instrucció:
                    ofstream MiFitxer("a:prova.txt");
Si la funció open no té èxit en obrir el fitxer llavors fa que el flux sigui igual a 0. Així la comprovació es fa mitjançant la instrucció següent:
                    MiFitxer.open("a:prova.txt");
                    if (!MiFitxer)
                    {
                    	cout << "No he pogut obrir a:prova.txt" << endl;
                    	exit(1);
                    }
Naturalment quan acabem d'utilitzar un flux hem de tancar el fitxer associat mitjançant la funció membre close:
                    MiFitxer.close();
Un programa senzill amb un fitxer de sortida és el següent:

Primer programa amb fitxers
Codi de creació d'un fitxerContingut del fitxer
#include <fstream.h>

main()
{
    ofstream MiFitxer;

    int PreuLlibre = 3456;

    MiFitxer.open("a:prova.txt");
    MiFitxer <<  "Prova de text en fitxers" << endl;
    MiFitxer << "El preu de un llibre es " << PreuLlibre << " pts." << endl;

    MiFitxer.close();
}
Si obrim el fitxer a:prova.txt amb un processador de textos veurem


Prova de text en fitxers
El preu de un llibre es 3456 pts.

Nota: No cal incloure l'arxiu de capçalera iostream.h car ja està fet aquest include dins el fitxer fstream.h

6.6.2. Descripció de les classes que conformen l'entrada/sortida

6.6.2.1 Les classes relacionades amb els buffers.

Són les classes que proveeixen de buffers de memòria per a les operacions d'entrada/sortida.

La classe strembuff és la classe arrel de les classes de buffers. És una classe abstracta que serveix de base per a les tres classes filebuf, strtstreambuf i stdiobuf, que proveeixen de buffers a les operacions d'entrada sortida.

ClasseSignificatClasses per a buffers
filebufProveeix el buffer associat i els mètodes adients als fluxos de la classe iostream per a entrada/sortida cap a fitxers de disc
strtstreambufProveeix la memòria adient per treballar les cadenes de caràcters.
stdiobufProveeix els buffers i les funcions per treballar l'entrada sortida cap a disc quan es treballa amb el fitxer de capçalera de C stdio.h

Un objecte streambuf manté un buffer de memòria, àrea fixa de memòria en aquest cas, que podem dividir en una zona de lectura i en una altra d'escriptura. També manté dos punters, un de lectura i altre d'escriptura que indiques la posició del fitxer on es farà la següent operació de lectura o escriptura respectivament.

6.6.2.1.1 La classe filebuf

La classe filebuf ens proveeix de buffers per a entrada/sortida cap a fitxers de disc. Només disposa d'una àrea per a la lectura i escriptura i els punters actuen com un únic punter.

Algunes funcions membre de la classe filebuf

filebuf * open(const char * NomFitxer, int mode, int protecció=filebuf::openprot);
       Aquesta funció obre un fitxer i l'associa amb l'objecte l'objecte que crida la funció. Si té èxit torna l'adreça de l'objecte i en cas contrari torna un NULL.

El paràmetre mode fa ús de la taula següent:

ModeTipus de mode per obrir un fitxerValor
ios::inObre un fitxer només per lectura.0x01
ios::outObre un fitxer només per escriptura.0x02
ios::ateEn obrir el fitxer es disposa per llegir al final. Es pot llegir i escriure en qualsevol part del fitxer.0x04
ios::appFa que les dades noves s'escriguin al final del fitxer. 0x08
ios::truncObre el fitxer del mateix nom destruint totes les dades i començant a la posició 0. 0x10
ios::nocreateLa funció torna error si el fitxer no existeix i no crea aquest fitxer. 0x20
ios::noreplaceLa funció torna error si el fitxer existeix i no obre el fitxer. 0x40
ios::binaryObre el fitxer en mode binary. Per defecte està obert en mode text.0x80

Es poden combinar dos o més d'aquests valors amb l'operador or de bits ¦.

El valor del paràmetre protecció indica que por defecte és compartit i té els valors possibles de sh_compat (compartit), sh_none (no compartit), sh_read (compartit per a lectura), sh_write (compartit per a escriptura).

filebuf * close();
       Aquesta funció buida el buffer, es a dir, descarrega les dades de sortida en el fitxer de disc i tanca el fitxer. Si la funció té èxit torna la direcció de l'objecte filebuf que la ha cridada. en cas contrari torna un NULL.

filebuf * atach(filedesc fd);
       Aquesta funció associa l'objecte filebuf que la crida amb el fitxer especificat amb el descriptor fd. Si el fitxer ja està obert torna un NULL i en cas contrari torna la direcció de l'objecte. El tipus filedesc és igual que el tipus int.

filedesc fd() const;
       Aquesta funció torna el descriptor d'un fitxer associat a l'objecte que la crida. Torna EOF si l'objecte no està associat a cap descriptor.

int is_open() const;
       Aquesta funció torna zero si el fitxer està tancat i diferent de zero si el fitxer està obert.

Nota:
int setmode(int modo=filebuf::text);
       Aquesta funció canvia permet posar el mode text o el mode binari per als fitxers fets en MS-DOS.

Recordem que en el mode text es fa una petita conversió, que consisteix en posar en el fitxer en lloc d'un '\n' un CR+LF. Quan llegim un fitxer en mode text es fa el canvi al contrari i també el Ctrl-Z s'interpreta com un EOF.

Es preferible fer ús del mode binari.

Aquesta és una funció global que es troba declarada a l'arxiu de capçalera IO.H.

6.6.2.2 La classe ostream

Aquesta classe té les funcions per accedir a les dades d'un fitxer obert per a escriptura. Té un constructor que ha de rebre una referència a un objecte de tipus streambuf de manera que la connexió amb el fitxer es fa amb els dos objectes, un ostream i un altre streambuf. El primer ens permet donar format a les dades i el segon ens permet fer ús d'un buffer.

Si mirem l'exemple següent veurem la forma de fer la obertura i escriptura amb objectes d'aquestes dues classes. És més normal fer ús dels objectes de la classe ofstream per realitzar aquesta tasca.

Si fem ús d'aquesta forma tenim accés a totes les funcions de les classes streambuf i filebuf, que són moltes, per treballar directament amb el buffer de memòria.

Obrir un fitxer d'escriptura amb buffer.
#include <fstream.h>
#include <stdlib.h>

main()
{
    filebuf Buffer;
    if (Buffer.open("a:fitxer02.dat", ios::out) == NULL)
    {
        cerr << "Error en obrir el fitxer"<<endl;
        exit(1);
    }
    ostream fSortida(&Buffer);

    fSortida << "Proba de fitxers amb buffer" << endl;
    fSortida << "i amb objectes de la classe ostream" << endl;

    Buffer.close();
}




Les funcions de la classe ostream
ostream &put(char ch) Insereix un caràcter a l'stream que la crida.
ostream &write(const char * pch,int n)Insereix n caràcters a l'stream que la crida.
ostream &flush()Buida dins el fitxer el contingut del buffer associat a l'stream que la crida.
lomg tellp()Ens dona la posició del punter del fitxer.
ostream &seekp(long)Mou el punter a una posició absoluta.
ostream &seekp(long, seek_dir)Mou el punter a una posició relativa a una de les tres possibles de la enumeració següent:

enum seek_dir {beg, cur, end}

6.6.2.3 La classe istream

Aquesta classe té les funcions per accedir a les dades d'un fitxer obert per lectura. Té un constructor que ha de rebre una referència a un objecte de tipus streambuf de manera que la connexió amb el fitxer es fa amb els dos objectes, un ostream i un altre streambuf. El primer ens permet llegir dades amb format i el segon ens permet fer ús d'un buffer.

Si mirem l'exemple següent veurem la forma de fer la obertura i escriptura amb objectes d'aquestes dues classes. És més normal fer ús dels objectes de la classe ofstream per realitzar aquesta tasca.

Si fem ús d'aquesta forma tenim accés a totes les funcions de les classes streambuf i filebuf, que són moltes, per treballar directament amb el buffer de memòria.

Obrir un fitxer de lectura amb un buffer.
#include <fstream.h>
#include <stdlib.h>
#include <iostream.h>

main()
{
    filebuf Buffer;
    char Frase[200];
    if (Buffer.open("a:fitxer02.dat", ios::in) == NULL)
    {
        cerr << "Error en obrir el fitxer"<<endl;
        exit(1);
    }
    istream fEntrada(&Buffer);

    fEntrada.getline(Frase,199,'\n');
    cout << Frase << endl;
    fEntrada.getline(Frase,199,'\n');
    cout << Frase << endl;

    Buffer.close();
}


Les funcions membre de la classe istream.
int get();
istream& get(signed char*, int len, char = '\n');
istream& get(unsigned char*, int len, char = '\n');
istream& get(unsigned char&);
istream& get(signed char&);
istream& get(streambuf&, char = '\n');
istream& get(signed char&);
Llegeix un caràcter de l'stream que la crida i ho posa en la variable ch.
Per a més informació sobre les altres formes de la funció llegir un manual.
istream &getline(char * cadena, int n, char delim = '\n')Llegeix caràcters de l'stream que la crida i els posa a cadena. La funció continua llegint fins a trobar un delimitador ('\n' per defecte) o fins a llegir n-1 caràcters. Després afegeix un '\0' com a darrer caràcter rebutjant el delimitador.
istream &read(char * cadena, int n)Aquesta funció llegeix caràcters de l'stream que la crida i els posa a cadena. S'atura de llegir quan ha llegit n caràcters o arriba al fina del fitxer. No afegeix '\0' al final.
istream &ignore(int n=1, int delim = EOF)Salta n caràcters de l'stream. Para de saltar si troba el delimitador que també es saltat.
int peek()Torna el següent caràcter sense quitar-lo del flux.
int gcount()Aquesta funció torna el número de caràcters llegits en la darrera operació de lectura sense format de dades.
istream &putback(char)Torna a posar un caràcter en el stream.
istream &seekg(long)
istream &seekg(long, seek_dir)
Mou el punter a una posició absoluta.
Mou el punter a una posició relativa a una de les tres possibles de la enumeració següent:

enum seek_dir {beg, cur, end}
long tellg()Ens dona la posició del punter del fitxer.

6.6.2.4 La classe iostream.

La classe iostream hereta de les classes istream i ostream simultàniament. Per tant és una classe que permet declarar streams que siguin simultàniament d'entrada i sortida.

És clar que s'hereten totes les funcions de les dues classes.

Té un constructor al que se li ha de passar una referència a un objecte de tipus filebuf que ens proveirà d'un bufer i les funcions corresponents.

És més usual crear streams de fitxers amb la classe fstream que com veurem ens evita la construcció del buffer encara que ja ho fa el constructor d'aquesta classe.

El codi per fer un stream amb aquesta classe és mol semblant al de les dos exemples anteriors. Només hem de pensar posar en el moment de cridar la funció open el mode d'obertura el paràmetre que voldrem, sortida out, entrada in, entrada/sortida out ¦ in.

6.6.2.5 La classe ofstream.

La classe ofstream hereta la classe ostream. La diferència principal és que aquesta classe té un constructor que associa directament un objecte de la classe filebuf amb l'stream que declarem.

Té un constructor sobrecarregat de diferents formes i que en la seva forma principal és

ofstream::ofstream(const char*, int=ios::out, int = filebuf::openprot);


que rep el nom del fitxer amb el seu camí, el mode d'obertura que per defecte és de sortida i el tipus d'accés al fitxer de disc que per defecte és openprot.

Les funcions membre de la classe ofstream.
void open(const char * NomFitxer, int modo=ios::out, int p=openprot)Una de les formes del constructor permet crear un ofstream sense associar li un fitxer de disc. Per aquesta raó la funció open ens permet associar un fitxer de disc amb un ofstream.
file * rdbuf()Torna un punter al l'objecte filebuf associat a l'stream


Com que aquesta classe hereta la classe ostream els objectes ofstream també accedeixen a les funcions de la classe ostream.

La forma d'obrir un fitxer amb un objecte de la classe ofstream la tenim a l'exemple següent.

Un fitxer amb ofstream
#include <fstream.h>
#include <stdlib.h>

main()
{
    ofstream fSortida("a:/fitxer04.dat");

    if (!fSortida)
    {
        cerr << "Error en obrir el fitxer"<<endl;
        exit(1);
    }

    fSortida << "Proba de fitxers amb ofstream" << endl;
    fSortida << "i sense haver de declarar esplícitament un buffer." << endl;

    fSortida.close();
}


6.6.2.6 La classe ifstream.

La classe ifstream hereta la classe istream. La diferència principal és que aquesta classe té un constructor que associa directament un objecte de la classe filebuf amb l'stream que declarem.

Té un constructor sobrecarregat de diferents formes i que en la seva forma principal és

ifstream::ifstream(const char*, int=ios::in, int = filebuf::openprot);


que rep el nom del fitxer amb el seu camí, el mode d'obertura que per defecte és d'entrada i el tipus d'accés al fitxer de disc que per defecte és openprot.

Les funcions membre de la classe ifstream.
void open(const char * NomFitxer, int modo=ios::in, int p=openprot)Una de les formes del constructor permet crear un ifstream sense associar-li un fitxer de disc. Per aquesta raó la funció open ens permet associar un fitxer de disc amb un ifstream.
file * rdbuf()Torna un punter al l'objecte filebuf associat a l'stream


Com que aquesta classe hereta la classe ostream els objectes ifstream també accedeixen a les funcions de la classe istream.

La forma d'obrir un fitxer amb un objecte de la classe ofstream la tenim a l'exemple següent.



Un fitxer amb la classe ifstream.
#include <fstream.h>
#include <stdlib.h>

main()
{
    char Frase[200];

    ifstream fEntrada("a:/fitxer04.dat");

    if (!fEntrada)
    {
        cerr << "Error en obrir el fitxer"<<endl;
        exit(1);
    }

    fEntrada.getline(Frase,199,'\n');
    cout << Frase << endl;
    fEntrada.getline(Frase,199,'\n');
    cout << Frase << endl;

    fEntrada.close();
}


6.6.2.7 La classe fstream.

Aquesta classe proveeix de les funcions per obrir fitxers d'entrada/sortida simultània però sense haver de declara explícitament un buffer.

Disposa d'un constructor amb diferents sobrecàrregues de manera que la forma principal és:

ifstream::ifstream(const char*, int , int = filebuf::openprot);

que com veiem té tres paràmetres:

  1. el nom del fitxer amb el seu camí
  2. un enter que correspon al mode d'obertura (si volem entrada/sortida simultània haurem de posar in|out)
  3. un enter amb el tipus d'accés que per defecte és openprot

Hereta de la classe iostream i per tant de les classes istream i ostream. Els streams d'aquesta classe tenen per tant accés a totes les funcions d'entrada/sortida que hereta d'aquestes classes.

Les funcions membre de la classe fstream.
void open(const char * NomFitxer, int modo, int p=openprot)Una de les formes del constructor permet crear un ifstream sense associar-li un fitxer de disc. Per aquesta raó la funció open ens permet associar un fitxer de disc amb un fstream.
file * rdbuf()Torna un punter al l'objecte filebuf associat a l'stream


La forma d'obrir un fitxer amb un objecte de la classe fstream la tenim a l'exemple següent.

Exemple de programa amb la classe fstrem.
#include <fstream.h>
#include <stdlib.h>

main()
{
	 char Frase[200];

	 fstream fES("a:/fitxer05.dat", ios::out);
	 if (!fES)
    {
        cerr << "Error en obrir el fitxer"<<endl;
        exit(1);
    }
	 fES << "Proba de fitxers d'entrada sortida amb la classe fstream" << endl;
	 fES << "i sense haver de declarar exp¡citament un buffer." << endl;
	 fES.close();


	 fES.open("a:/fitxer05.dat", ios::in);
	 if (!fES)
    {
        cerr << "Error en obrir el fitxer"<<endl;
        exit(1);
    }
	 fES.getline(Frase,199,'\n');
	 cout << Frase << endl;
	 fES.getline(Frase,199,'\n');
	 cout << Frase << endl;
	 fES.close();
}


6.6.3 Exemple de fitxer amb accés seqüencial

A continuació fem un petit problema escrivint un programa que escriu i llegeix en fitxers de forma seqüencial.

Hem fet que escrigui registres amb les dues dades d'un objecte Llibre que només té el títol i el preu. Així, per tal de bolcar les dades d'un objecte Llibre al final del fitxer hem fet ús de la funció write com indiquem a la línia següent en la que les dades de l'objecte RegProvisional s'escriuen a l'stream fSortida:

fSortida.write((char *) &RegProvisional, sizeof(RegProvisional));


L'operació inversa és llegir un registre sencer del fitxer i posar les seves dades a la variable RegProvisional, cosa que fem amb la instrucció:

fEntrada.read((char *) &RegProvisional, Mida)


Exemple de fitxer amb accés seqüencial
#include <iostream.h>
#include <fstream.h>
#include <stdlib.h>

class Llibre
{
     char Titol[450];
     int Preu;
public:
     Llibre() {Titol[0]='\0'; Preu=0;}

     friend ostream &operator<<(ostream &flux, Llibre l);
     friend istream &operator>>(istream &flux, Llibre &l);
};

ostream &operator<<(ostream &flux, Llibre l)
{
      flux << "Titol: ";
      flux << l.Titol << endl;
      flux << "Preu: ";
      flux << l.Preu << endl;
      return flux;
}

istream &operator>>(istream &flux, Llibre &l)
{
     cout << "Titol: ";
     cin.ignore();
     flux.getline(l.Titol,450,'\n');
     cout << "Preu: ";
     flux >> l.Preu;

     return flux;
}

int Menu();
void Introduir(char * NomF, int Opcio);
void LlegirTots(char * NomF);

main()
{

     int Opcio = 1;
     char NomFitxer[45];

     while (Opcio)
     {
          Opcio = Menu();
          switch (Opcio)
          {
               case 1:
                    cout << "Nom del fitxer: ";
                    cin >> NomFitxer;
                    break;
               case 2: case 3:
                    Introduir(NomFitxer, Opcio);
               break;
               case 4 :
                    LlegirTots(NomFitxer);
               break;
          }
     }
}

int Menu()
{
     int Opcio =-1;

     cout << "0   Acabar" << endl;
     cout << "1   Nom del fitxer" << endl;
     cout << "2   Crear fichero" << endl;
     cout << "3   Afegir" << endl;
     cout << "4   Llegir tots" << endl;

     while ((Opcio < 0) || (Opcio > 4)) cin >> Opcio;

     return Opcio;
}

void Introduir(char * NomF, int Opcio)
{
     fstream fSortida;
     Llibre RegProvisional;
     char Seguir = 'S';

     if (Opcio == 2)
          fSortida.open(NomF, ios::out | ios::binary);

     if (Opcio == 3)
          fSortida.open(NomF, ios::nocreate | ios::app | ios::binary);

     if (!fSortida)
     {
          cout << "Error en obrir el fitxer \n";
          exit(1);
     }

     while (Seguir == 'S' | Seguir == 's')
     {
          cin >> RegProvisional;
          fSortida.write((char *) &RegProvisional, sizeof(RegProvisional));
          cout << "Seguir? (s/n)";
          cin >> Seguir;
     }

     fSortida.close();
}

void LlegirTots(char * NomF)
{
     fstream fEntrada;
     Llibre RegProvisional;
     int Mida = sizeof(RegProvisional);

     fEntrada.open(NomF, ios::nocreate | ios::in | ios::binary);

     while ( fEntrada.read((char *) &RegProvisional, Mida) )
          cout << RegProvisional << endl;
     fEntrada.close();
}


6.6.4 Exemple de fitxer amb accés aleatori.

recordem que en un fitxer seqüencial tractem d'escriure o llegir en qualsevol posició del fitxer.

Tenim que disposem de dos punters que apunten a la posició del fitxer en que llegirem o escriurem respectivament. Podem moure aquests dos punters amb les funcions membre seekg de les classes d'entrada (istream) i seekg de les classes de sortida (ostream). La declaració d'aquestes funcions és:

istream &seekg(streamoff desplacament, ios::seek_dir);
ostream &seekp(streamoff desplacament, ios::seek_dir);

en la que desplaçament és el número de bytes que ha de moure's el punter des de la posició indicada en ios:seek_dir. Aquestes posicions recordem que poden ser una de les tres següents:

ios::beg      principi de l'stream
ios::curposició actual del punter
ios::endfinal de l'stream


Com a exemple afegim una funció, anomenada LlegirUn a l'exemple anterior. Aquesta funció demana el número del registre que volem llegir, desplaça el punter a la posició demanada i llegeix el registre.

Exemple de fitxer amb accés aleatori.
void LlegirUn(char * NomF)
{
     fstream fEntrada;
     Llibre RegProvisional;
     int Mida = sizeof(RegProvisional);
     int NumRegistre;

     fEntrada.open(NomF, ios::nocreate | ios::in | ios::binary);

     cout << "Introdueix el número del  registre: ";
     cin >> NumRegistre;
     fEntrada.seekp(NumRegistre*Mida, ios::beg);
     fEntrada.read((char *) &RegProvisional, Mida);
     cout << RegProvisional << endl;
     fEntrada.close();
}


6.7 La classe ios i l'estat d'un stream.

La classe ios disposa la dada sencera ios::state que conté l'estat actual de l'stream. Cada operació de lectura o escriptura s'actualitza aquesta variable i això ens permet, fent la consulta adient dels seus bits, saber que ha esdevingut en una d'aquestes operacions.

Els bits amb valors significatius són els següents:

eofbit       Es posa a 1 si hem arribat al final del fitxer i a 0 en cas contrari.
failbit       Es posa a 1 si ha succeït un error de E/S que no és fatal i a 0 en cas contrari.
badbit       Es posa a 1 si ha succeït un error fatal de E/S i a 0 en cas contrari.
La classe ios té les funcions següents que ens permeten consultar l'estat d'aquests bits:

bool bad()       Torna cert si eofbit està a 1, fals en cas contrari.
bool eof()       Torna cert si badbit està a 1, fals en cas contrari.
bool fail()       Torna cert si failbit està a 1, fals en cas contrari.
bool good()       Torna cert si no hi ha errors i fals en cas contrari.

6.8 Exercicis
  1. Implementa l'operador << per a la classe vector segur del capítol 4.
  2. Implementa l'operador >> per a la classe anterior de manera que demani tots els elements del vector un rera l'altre.
  3. Escriu un fitxer amb el Notepad que tingui de sis a vuit línies.
    1. Llegeix aquest fitxer amb un programa que tingui un objecte buffer.
    2. Afegeix a aquest fitxer tres frases de-de C++.
    3. Llegeix el fitxer amb el Notepad.
    4. Imprimeix per pantalla els bytes que té el fitxer.
    5. Llegeix en una instrucció la meitat del fitxer.
    6. Crea un fitxer nou en el que hagi intercalat un guió entre tots els caràcters.
  4. Escriu tots els exemples del capítol referents a entrada sortida cap a disc.
  5. Fes un programa en el que fas un fitxer de dades personals i ho gestiones com un fitxer d'accés aleatori.
    Aquest fitxer ha de tenir com a mínim les opcions d'afegir dades i llegir registre per número (posició del registre dins el fitxer.