Sistemul de I/O in C++. Stream-uri

Introducere

In mod traditional, facilitatile de I/O ale unui limbaj de programare s-au marginit la prelucrarea unui numar redus de tipuri primare: date numerice (intregi si reale), caractere si string-uri.

C++ a fost conceput astfel incat sa ofere utilizatorului posibilitatea de a defini tipuri noi pe care sa le manipuleze intr-o maniera similara tipurilor primare. Ca urmare, s-a impus necesitatea ca si operatiile de I/O sa poata fi aplicate in mod natural tipurilor definite de utilizator.

Eforturile depuse in aceasta directie au avut ca rezultat crearea unui model al procesului de I/O care sa permita extinderea functiilor de citire/scriere si asupra obiectelor. Acest model se numeste stream si presupune convertirea obiectelor in secvente de caractere si vice-versa. Sarcina care revine programatorului este aceea de a specifica o corespondenta intre un obiect (adica o entitate tipizata) si un sir de caractere netipizate.

Implementarea modelului stream-urilor este inclusa in biblioteca limbajului C++, interfata ei cu utilizatorul fiind descrisa in <iostream.h>.
 

Operatiile de iesire

Se realizeaza prin intermediul metodelor clasei ostream. Principala functie membru a acestei clase este operator<< si ea este supraincarcata astfel incat sa admita toate tipurile primare ale limbajului:
 

class ostream : public virtual ios {
        //... 
public:
    ostream& operator<<(const char*); //stringuri
    ostream& operator<<(char); //caractere
    ostream&amp5; operator<<(int); //nr. intregi
    //s.a.m.d. pt. tipurile:
    //short, long, double
    //const void* (pt. scrierea pointerilor)
};
Modul general de operare a unei functii ostream::operator<< este descris de secventa:
 
ostream& ostream::operator<< (T e)

      // scrie valoarea 'e'
      return *this;
}
De fapt, operatorii << realizeaza scrierea formatata a informatiei, fiind un echivalent al functiilor printf din C.

Executia functiilor operator<< este invocata, bineinteles, prin intermediul unui obiect de tip ostream. Obiectul ostream va identifica dispozitivul spre care se directeaza iesirea. Atunci cand iesirea se realizeaza pe dispozitivele standard de afisare, respectiv de eroare, se utilizeaza doua obiecte ostream predefinite: cout, respectiv cerr.
 
 

    int i=100; 
    //... 
    cout << "i=" << i << "\n";
Ultima instructiune este interpretata ca:
((cout.operator<<("i=")).operator<<(i)).operator<<("\n");

Functia operator<< returneaza intotdeauna o referinta la obiectul pentru care a fost apelata, de unde rezulta posibilitatea aplicarii in lant a operatorului <<.
 

Operatiile de iesire aplicate asupra tipurilor definite de utilizator
Pentru ca obiectele claselor definite de utilizator sa poata fi "scrise" este necesara supraincarcarea unei functii operator<< globale, in modul urmator:
 

class o_clasa {
    //...
    friend ostream& operator<< (ostream& s, const o_clasa& ob)
    {
          // s << ob.membri 
          return s;
    }
};
Exemplu:
class complex {
private:
    double re, im;
public:
    complex(double r=0.0, double i=0.0): re(r), im(i) {}
    //...
    friend ostream& operator<<(ostream& s, const complex& z)
    { 
        s << z.re;
        if(z.im>=0.0) s << '+';
        s << z.im << "*i";
        return s;
    }
};
//...
complex a(1,2), b(-2,-5);
cout << "a=" << a << "\n";
cout << "b=" << b << "\n";
//se vor afisa secventele:  a=1+2*i
b=-2-5*i
Statutul de friend se acorda operatorului << din motive de optimizare a accesului la membrii clasei pentru care este supraincarcat.

Se poate observa ca definirea operatiei de iesire asupra claselor utilizatorului, cu ajutorul clasei ostream nu implica nici modificarea clasei ostream si nici accesul la datele ascunse ale acesteia.
 

Operatia de intrare

Este similara, din punct de vedere al modului de definire, operatiei de iesire. Se realizeaza prin intermediul metodelor clasei istream. Principala functie membru a acestei clase este operator>> si ea este supraincarcata astfel incat sa admita toate tipurile primare "citibile" ale limbajului:
 

class istream : public virtual ios {
    //... 
public:
    istream& operator>>(char*);//stringuri
    istream& operator>>(char&);//caractere
    istream& operator>>(int&);//nr. intregi
    //s.a.m.d. pt. tipurile:
    //short&, long&, double&, float&
};
Modul general de operare a unei functii istream::operator>> este descris de secventa:
istream& istream::operator>> (T& v)

    // neglijeaza spatiile albe (blank, tab, CR, LF, FF)
    // citeste o valoare de tip T in variabila v
    return *this;
}
Executia functiilor operator>> este invocata, bineinteles, prin intermediul unui obiect de tip istream. Obiectul istream va identifica dispozitivul de pe care se efectueaza intrarea. Atunci cand intrarea se realizeaza de pe dispozitivul standard, se utilizeaza obiectul istream predefinit cin.

Exemplu:

  int t[N]; 
  //. . .
  for(int i=0;i<N;i++) cin >> t[i];
Clasa istream este dotata cu un operator de conversie la tipul void*, care testeaza starea obiectului istream si returneaza o valoare non-NULL daca starea indica o desfasurare normala a intrarii si NULL in caz contrar. De aceea, secventa de mai sus se poate scrie:
 
  int t[N]; 
  for(int i=0;i<N;i++) 
  { 
      if(cin >> t[i]) continue;
      //eroare la citire
  }
In afara de operatorul >>, clasa istream contine si un set de functii get care realizeaza citirea de caractere fara a neglija spatiile albe ci tratandu-le pe acestea ca pe caratere obisnuite:
    istream& istream::get(char&);

    istream& istream::get(char *p,int n,char s='\n');

Prima varianta a lui istream::get realizeaza citirea unui singur caracter.

A doua varianta citeste un string de maximum n-1 caractere pe care le memoreaza la adresa indicata de p. Citirea se opreste la aparitia unuia din urmatoarele evenimente:

Se precizeaza ca dupa ultimul caracter valid citit, functia adauga in bufferul p terminatorul de sir '\0'. Se observa ca aceasta varianta a functiei get are un comportament similar functiei gets din C.

Exemple de utilizare:
 

  void copiere_char_by_char() 
  { 
      char c;
      while(cin.get(c)) cout<< c;
  }
  void citire_pe_linii() 
  { 
      char buf[100];

      while(cin.get(buf,100,'\n')) fa_ceva(buf);
  }

Obs: in exemplul al 2-lea utilizarea functiei get in locul operatorului >> (sub forma cin>> buf) este mai sigura, deoarece, in cazul unui sir de intrare mai lung de 99 de caractere, varianta cu >> ar duce la depasirea capacitatii tabloului buf.

Ca regula generala, operatorii >> se folosesc pentru citirea datelor formatate, ei fiind un echivalent al functiilor scanf din C. Functiile get se utilizeaza cel mai adesea pentru citirea liniilor de text care nu au o structura fixa si in care se vor identifica apoi, prin program, diversele segmente de informatie.

Operatiile de intrare aplicate asupra tipurilor definite de utilizator
Pentru ca obiectele claselor definite de utilizator sa poata fi "citite" este necesara supraincarcarea unei functii operator>> globale, in modul urmator:
 
 

class o_clasa {
    //...
    friend istream& operator>>(istream& s, o_clasa& ob)
    {
        // s >> ob.membri
        return s;
    }
};
Exemplu:
 
class complex {
private:
    double re, im;
public:
    complex(double r=0.0, double i=0.0): re(r), im(i) {}
    //...
    friend istream& operator>>(istream& s, complex& z)
    /* un numar complex se va introduce sub una din formele:
          (re)
          (re,im) 
    */
    {
        double re=0.0,im=0.0;
        char c=0;
        s >> c;
        if(c=='(')
        {
            s >> re >> c;
            if(c==',') s >> im >> c; 
            if(c!=')') {/*eroare*/}
        }
        else {/*eroare*/}
        if(s) z=complex(re,im);
        return s;
    }
};
Se poate observa ca definirea operatiei de intrare asupra claselor utilizatorului, cu ajutorul clasei istream nu implica nici modificarea clasei istream si nici accesul la datele ascunse ale acesteia.

Starea stream-urilor

Fiecare stream, fie istream, fie ostream, are asociata o stare care reflecta modul in care se desfasoara operatiile de I/O.  Starea unui stream poate fi examinata prin intermediul unor operatii ale clasei ios (care este baza pentru clasele istream si ostream):
 

clas ios {
    //... 
public:
    int eof() const;
        /*returneaza non-zero daca s-a intalnit sfarsitul sirului 
        de intrare */
    int fail() const;
        //returneaza non-zero daca ultima operatie I/O a esuat
    int bad() const;
        //returneaza non-zero daca streamul este corupt
    int good() const;
        //returneaza non-zero daca precedenta operatie I/O a reusit
    //... 
};
Daca starea stream-ului este good() sau eof() inseamna ca precedenta operatie de I/O s-a terminat cu succes.
Daca se incearca executia unei operatii de I/O asupra unui stream care nu este in starea good(), aceasta nu va avea nici un efect.
Daca o operatie de citire a unei variabile v se soldeaza cu starea fail(), valoarea lui v nu ar trebui sa se modifice. De altfel asa se si petrec lucrurile in cazul in care tipul lui v este unul dintre cele acceptate de functiile membru ale claselor istream si ostream.

Diferenta dintre starile fail() si bad() consta in principiu in aceea ca starea fail() presupune ca stream-ul implicat nu este corupt si ca nu s-au pierdut caractere. De exemplu, daca se incearca citirea unei valori intregi dintr-un stream de intrare in care la pozitia curenta se gaseste un caracter nenumeric, efectul va fi intrarea in starea fail(). Daca insa in cursul unei operatii I/O intervin defecte fizice ale dispozitivului implicat, este foarte probabil ca rezultatul va fi starea bad().

Valorile utilizate pentru a reprezenta starea unui stream apartin unui tip enumerare definit tot in clasa ios:
 

class ios {
    //... 
public:
    enum io_state {
        goodbit,
        eofbit,
        failbit,
        badbit
    };
    //...
}; 
Valorile reale ale constantelor de tip io_state depind de implementare. Ele pot fi utilizate in testarea starii unui stream ca in exemplul de mai jos:
 
switch(cin.rdstate()){
    case ios::goodbit:
        //precedenta operatie a reusit
        break; 
    case ios::eofbit:
        //sfarsitul sirului de intrare
        break;
    case ios::failbit:
        //foarte probabil o eroare de formatare
        break;
    case ios::badbit:
        //foarte probabil s-au pierdut caractere
        break; 
}
Cand un stream apare ca expresie conditionala, de fapt se realizeaza o conversie a obiectului respectiv la tipul void*, returnandu-se o valoare non-NULL daca starea stream-ului este good(). In particular, pentru stream-urile de intrare, o utilizare tipica a testului de stare il reprezinta secventa de mai jos:
 
while (cin >> v) fa_ceva(v);
if(cin.rdstate()!=ios::eofbit) trateaza_eroarea_IO();
Utilizatorul are posibilitatea de a seta starea unui stream la o anumita valoare, prin intermediul functiei ios::clear, ca in urmatoarea secventa:
cin.clear(ios::badbit)

Functia clear se poate apela si fara parametru, caz in care acesta este considerat ca avand valoarea implicita ios::goodbit.
 

Formatarea stream-urilor
In cele expuse pana aici s-au prezentat operatiile de I/O ca niste functii de citire/scriere avand un comportament similar functiilor scanf/printf din C, dar care ar contine in sirul lor de formatare doar specificatii de tip (de genul %d pentru intregi, %c pentru caractere, etc). Asa cum un sir de formatare utilizat de functiile scanf/printf poate sa cuprinda si alte detalii legate de modul de prezentare a informatiei citite/scrise (mai ales a celei scrise), si stream-urile pun la dispozitie facilitati de precizare a unor asemenea detalii, si anume, prin intermediul unui set de metode ale clasei ios.

Operatiile I/O realizate prin intermediul stream-urilor utilizeaza buffer-e pentru stocarea informatiilor. Fiecare "atom" de informatie poate fi considerat ca un camp din buffer-ul respectiv, iar metodele clasei ios de care aminteam mai sus au rolul de a seta/consulta diverse atribute ale unui asemenea camp. Atributele sunt reprezentate cu ajutorul unor date membru ale clasei ios.
 

class ios {
    //... 
    int x_fill;
    long x_flags;
    int x_precision;
    int x_width;
public:
    static const long adjustfield;
    static const long basefield;
    static const long floatfield;

    int width(int w); //seteaza latimea campului (x_width)
    int width() const; //returneaza latimea campului 

    char fill(char); //seteaza caracterul de umplere (x_fill)
    char fill() const; //returneaza caracterul de umplere

    long flags(long f);//seteaza flag-urile de control (x_flags)
    long flags() const;//returneaza flag-urile de control
    long setf(long setbits,long field);//seteaza flag-urile folosind 
            //unul din indicatoarele statice
    long setf(long);//seteaza un anumit bit din flagurile x_flags
    long unsetf(long);//reseteaza un anumit bit din x_flags

    int precision(int);//seteaza precizia nr. reale (x_precision)
    int precision() const;//returneaza valoarea preciziei
    //... 
};

  • Functia ios::width() - specifica numarul minim de caractere necesar pentru a scrie o informatie numerica sau de tip string.

  • Exemplu:
     
    cout.width(4);
    cout << '(' << 12 << ')';
    //efect: valoarea 12 se va scrie pe 4 pozitii
    // ( 12)
    Observatii:
    cout.width(4);
    cout << '(' << 12 << "),(" << 12 << ")\n";
    //efect: (    12),(12)
    //stringurile "),(" si ")\n", precum si al 2-lea '12'
    //se scriu pe lungimea strict necesara
  • Functia ios::fill() - Se foloseste de obicei in combinatie cu width() si specifica valoarea caracterului cu care se "umplu" pozitiile suplimentare, in cazul in care informatia scrisa are o lungime mai mica decat cea precizata prin width().

  •  
    cout.width(4);
    cout.fill('#');
    cout << '(' << 12 << ')';
    //efect: (##12)
    Valoarea implicita pentru caracterul de umplere este blank-ul.
     
  • Functiile flags() si setf() - Se folosesc pentru lucrul cu variabila x_flags. Aceasta variabila este interpretata ca o secventa de biti. Semnificatia bitilor este data cu ajutorul constantelor apartinand unui tip enumerare definit tot in clasa ios.

  • class ios {
        //... 
    public: 
        enum {
            skipws, //neglijeaza spatiile albe la citire

            left, //aliniere la stanga 
            right, //aliniere la dreapta
            internal, //semnul la stanga si numarul la dreapta

            dec, //utilizeaza baza 10 pt. scrierea nr.
            oct, //baza 8 
            hex, //baza 16

            showbase, //la afisare se pune si baza in fata nr.
            showpoint,//se afiseaza zero-urile de dupa virgula
            uppercase,//se utilizeaza 'E' si 'X' pt. exponent si hexa
            showpos,//se afiseaza semnul '+' pt. val. pozitive
            scientific,//se foloseste notatia stiintifica pt. reali
            fixed,//se foloseste notatia p_int.p_zec pt. reali
            unitbuf,//se face flush dupa fiecare oper. de scriere
            stdio //se face flush dupa fiecare caracter
        };
        //...
    };

    Valorile reale ale constantelor de mai sus sunt dependente de implementare.

    Functia flags() seteaza campul x_flags ca valoare long, nu la nivel de bit. Pentru a specifica ce biti dorim sa setam vom utiliza valorile tipului enumerare de mai sus intre care se aplica operatorul sau-logic pe biti:
     


    cout.flags(ios::left | ios::oct | ios::showpoint | ios::fixed)


     


    Functia returneaza intotdeauna valoarea anterioara a lui x_flags, aceasta putand fi utilizata pentru o restaurare ulterioara.
     

    long new_opt=ios::left | ios::oct | ios::showpoint | ios::fixed;
    long old_opt=cout.flags(new_opt);
    //...
    cout.flags(old_opt)

    Valoarea returnata de flags() poate fi utilizata si atunci cand, pe langa bitii deja setati se doreste setarea a inca unui bit:
     


    cout.flags(cout.flags() | ios::showpos);


     


    Functia setf() cu un parametru serveste la setarea doar a unui bit specificat prin intemediul constantelor tipului enumerare dat anterior, fara a afecta valoarea celorlalti biti. De exemplu, linia de mai sus este echivalenta cu:
     


    cout.setf(ios::showpos)


     


    Functia setf() cu 2 argumente serveste pentru specificarea unor parametri de formatare care nu pot fi reprezentati doar cu ajutorul cate unui singur bit, cum este cazul bazei utilizate pentru afisarea numerelor intregi sau a stilului de afisare a numerelor reale (parametri care pot lua cate 3 valori posibile). Al doilea parametru indica la ce optiune de formatare se face referirea si el poate fi una din variabilele statice basefield, adjustfield, respectiv floatfield ale clasei ios. Primul parametru indica valoarea pe care o poate lua optiunea selectata.

    Exemplu:
     

    cout.setf(ios::oct,ios::basefield);
    cout << 1234 << '\n';
    //efect: 2322
    cout.setf(ios::showbase);
    cout << 1234 << '\n';
    //efect : 02322
    cout.setf(ios::hex,ios::basefield);
    cout << 1234 << '\n';
    //efect : 0x4d2

    Optiunea adjustfield este coroborata cu setarea campului x_width, ea indicand modul de aliniere in cadrul unei latimi specificate pentru afisare.
     

    cout.width(4);
    cout << '(' << -12 << ")\n";
    //efect : ( -12)
    cout.width(4);
    cout.setf(ios::left,ios::adjustfield);
    cout << '(' << -12 << ")\n";
    //efect : (-12 )
    cout.width(4);
    cout.setf(ios::internal,ios::adjustfield);
    cout << '(' << -12 << ")\n";
    //efect : (- 12)
    Pentru optiunea floatfield se pot specifica valorile ios::scientific sau ios::fixed.
     
  • Functia ios::unsetf() - Pozitioneaza pe 0 bitul specificat ca parametru. Se precizeaza ca functia unsetf poate lua ca parametri valori similare celor acceptate de functia setf.

  •  
  • Functia ios::precision(int n)  - Stabileste precizia de afisare a numerelor reale, adica numarul de cifre zecimale, ca fiind n. In mod implicit precizia este 6. La afisare numarul va avea n-1 zecimale exacte, iar a n-a zecimala va fi rotunjita, in functie de a n+1-a zecimala.

  •  
    cout.precision(4);
    cout << 1234.56789 << "\n";
    //efect : 1234.5679

    Manipulatorii

    In contextul operatiilor de I/O exista o serie de actiuni pe care programatorul ar dori sa le specifice si sa le efectueze "din mers", si anume in aceeasi fraza care invoca operatiile de I/O. De regula aceste actiuni privesc stabilirea unor parametri ai procesului de I/O.

    Exemplu: secventa
     

    cout << x;
    cout.precision(4);
    cout << y;
    ar fi mai elegant si mai clar exprimata sub forma:
    cout << x << set_precision(4) << y;

    Claritatea provine din faptul ca se evidentiaza mai bine legatura logica dintre actiunea de specificare a preciziei si operatia de afisare a lui y afectata de aceasta actiune.

    Pentru ca scrierea unei secvente de felul celei de mai sus sa fie posibila este nevoie, in primul rand, ca operatorii de I/O (adica << si >>) sa fie supraincarcati astfel incat sa accepte si tipul ce corespunde apelului de functie din secventa. Supraincarcarea se realizeaza diferit, dupa cum functia respectiva are sau nu parametri de alt tip decat clasele istream/ostream. De exemplu, in cazul prezentat mai sus, functia set_precision trebuie sa primeasca un parametru de tip int.

    Conceptul de manipulator este cel care sta la baza facilitatii de a insera in mod direct apeluri de functii intr-o lista de operatii I/O.

    In cele ce urmeaza, pentru simplificarea expunerii, vom nota cu tip_stream numele claselor istream/ostream si cu 'op_IO' simbolurile >>/<<.

    Manipulatori fara parametri
    Crearea unor asemenea manipulatori presupune urmatoarele definitii:
     

    typedef tip_stream& (*pm)(tip_stream&);

    /* pm este tipul pointer la o functie care accepta un parametru de tip tip_stream& si returneaza tip_stream& */

    tip_stream& operator'op_IO'(tip_stream& s,pm f)
        return (*f)(s); 
    }
     

    tip_stream& func(tip_stream& s) {
        //fa ceva cu s
        return s;
    }

    //Cu definitiile de mai sus putem scrie:
        stream 'op_IO' x 'op_IO' func 'op_IO' y;
    //unde stream este un obiect al clasei tip_stream;

    //fraza de mai sus se interpreteaza ca:
    (operator'op_IO'(stream.operator'op_IO'(x),func)).operator'op_IO'(y) 

    In biblioteca de clase pentru lucrul cu stream-uri sunt deja prevazute definitii ale operatorilor << si >> de forma aratata, care se pot utiliza pentru clasele istream/ostream (v. manipulatorii standard simpli).

    Exemplu:

    ostream& set_hexa_base(ostream& os) {
        os.setf(ios::hex,ios::basefield);
        return os;
    }
    //...
    cout << x << set_hexa_base << y;
    //efect : y va fi afisat in baza 16

    Manipulatori cu parametri
    Pentru exemplificare se considera ca manipulatorii trebuie sa accepte un parametru de tip int. In acest caz va trebui sa prevedem urmatoarele definitii:
     

    class manip_int {
        int i;
        tip_stream& (*f)(tip_stream&,int)
        /* f este de tip pointer la o functie care accepta un parametru de tip tip_stream&
        si unul de tip int si returneaza tip_stream& */
    public:
        manip_int(tip_stream& (*ff)(tip_stream&, int), int ii): f(ff), i(ii) {}
        friend tip_stream& operator'op_IO'(tip_stream& s, manip_int& m)
        { return m.f(s,m.i); }
    };
     

    /*Pentru fiecare tip de stream (istream/ostream) se va defini cate o clasa de genul lui manip_int */

    //Fie o functie
    tip_stream& func(tip_stream& s,int i) {
        //fa ceva cu s si i
        return s;
    }

    //Se defineste manipulatorul
    manip_int manip(int i)
    { return manip_int(func, i); }

    //Cu definitiile de mai sus putem scrie:
    stream 'op_IO' x 'op_IO' manip(a) 'op_IO' y;
    /*unde stream este un obiect al clasei tip_stream, iar a este de tip int */

    //fraza de mai sus se interpreteaza ca:
    (operator'op_IO'(stream.operator'op_IO'(x), manip(i))). operator'op_IO'(y)

    Cu acestea, referindu-ne la exemplul prezentat la inceputul paragrafului, si considerand ca numele clasei manip_int definita pentru stream-urile de tip ostream este omanip_int, putem scrie:
     

    ostream& precision(ostream& os,int i) {
        os.precision(i);
        return os;
    }
    omanip_int setprecision(int i) 
    { return omanip_int(precision,i); }
    //...
    cout << x << set_precision(4) << y;
    //efect : y va fi afisat cu precizia de 4 zecimale
    O alternativa mai eleganta si mai generala de proiectare a manipulatorilor cu parametri este definirea, in locul claselor de genul manip_int, a unor clase generice in care tipul argumentelor manipulatorilor sa figureze ca tipuri generice. Aceasta solutie va fi prezentata si discutata in lucrarea nr. 8.
     

    Manipulatorii standard
    In biblioteca de clase pentru lucrul cu stream-uri sunt definiti o serie de manipulatori de uz curent, numiti manipulatori standard. Ei se pot utiliza in locul functiilor ios::flags() si ios::setf(), care necesita un efort de programare mai mare. Dam mai jos lista manipulatorilor standard:

    ios& oct(ios&);//stabileste baza octala
    ios& dec(ios&);//stabileste baza zecimala
    ios& hex(ios&);//stabileste baza hexa

    ostream& endl(ostream&);//scrie '\n' si executa un flush
    ostream& ends(ostream&);//scrie '\0' si executa un flush
    ostream& flush(ostream&);//executa flush asupra stream-ului

    istream& ws(istream&);//"sari" peste spatiile albe 

    setbase(int b);//stabileste baza b
    setfill(int f);//stabileste caracterul de umplere
    setprecision(int p);//stabileste precizia
    setw(int w);//stabileste latimea campului
    resetiosflags(long b);//pune pe 0 bitii de formatare 
    setiosflags(long b);//pune pe 1 bitii de formatare 

    Atentie: Pentru a putea lucra cu acesti manipulatori este necesara includerea fisierului header <iomanip.h>.
     
     

    Stream-urile si fisierele

    Pentru a specifica faptul ca un anumit stream va lucra cu un fisier de pe disc se utilizeaza obiecte ale claselor ifstream (fisiere de intrare), ofstream (fisiere de iesire) sau,respectiv, fstream (fisiere de intrare/iesire), clase derivate din istream si ostream. Operatiile definite in clasele istream si ostream pot fi utilizate pentru obiectele ifstream, ofstream, respectiv fstream. Interfata acestor clase se gaseste in <fstream.h>

    In cele ce urmeaza vom lucra mai mult cu fisiere ifstream si ofstream, aratand unde e cazul particularitatile pentru fstream.

    Deschiderea unui fisier se realizeaza pur si simplu prin crearea unui obiect de tip ifstream/ofstream. Constructorii acestor clase accepta urmatorii parametri:

    class ios {
        //... 
    public:
        enum open_mode{
            in, //deschidere pt. citire
            out, // deschidere pt. scriere
            ate, //deschidere cu pozitionare la sfarsitul fisierului
            app, //deschidere pt. adaugare
            trunc, //trunchiaza fisierul la lungime 0
            nocreate, //deschiderea esueaza daca fisierul nu exista
            noreplace //deschiderea esueaza daca fisierul exista
        };
        //...
    };

    Valorile reale ale constantelor de mai sus sunt dependente de implementare. Un fisier ofstream va fi deschis implicit in scriere, iar unul ifstream - in citire.

    In exemplul de mai jos este ilustrata utilizarea indicatorilor de deschidere:
     

    void o_func() { 
        ofstream un_fis("un_nume",ios::out | ios::nocreate); 
        if(un_fis.bad()) {/* probabil fisierul nu exista */}; 
        //...    fstream alt_fis("alt_nume",ios::in | ios::out); 
        //... 
    }

    Inchiderea unui fisier se realizeaza automat, prin distructorul claselor ifstream/ofstream. Daca se doreste insa ca inchiderea sa aiba loc inainte de disparitia obiectului ifstream/ofstream, se poate invoca metoda close():

    un_fisier.close();

    Obiectele de tip ifstream/ofstream pot fi utilizate intr-o maniera similara celor de tip istream/ostream deoarece mostenesc operatiile de baza de la acestea din urma.

    Pentru fisierele de intrare, pe langa operatiile clasice ( get si >>), se pot aplica si metodele urmatoare (cunoscute de altfel, de la lucrul cu fisiere obisnuite in C):
     

  • istream& istream::putback(char c); repune in stream caracterul c, pt. a fi citit la proxima operatie de citire

  •  
  • streampos istream::tellg();  - returneaza pozitia curenta in fisier

  •  
  • istream& istream::seekg(streampos p); - pozitionare la caracterul a carui pozitie in fisier este data de argumentul p; se precizeaza ca primul caracter dintr-un stream se afla la pozitia 0 .

  •  
  • istream& istream::seekg(streamoff d,seek_dir p); - pozitionare pe caracterul aflat la deplasament d fata de pozitia indicata de parametrul p. Sensul in care se efectueaza deplasarea fata de pozitia p este dat de semnul valorii d, si anume: semn pozitiv inseamna deplasare spre sfarsitul fisierului, iar semn negativ inseamna deplasare spre inceputul fisierului; tipul seek_dir este un tip enumerare definit in clasa ios, asa cum se arata mai jos:

  •  
    class ios {
        //... 
    public:
        enum seek_dir{
            beg, //cauta de la inceputul fisierului
            cur, //cauta de la pozitia curenta
            end //cauta de la sfaritul fisierului
        };
        //...
    }; 

    Probleme
    1.Se cere sa se scrie un program care, folosind operatiile cu stream-uri, citeste dintr-un fisier o secventa de perechi de numere reale din care creaza obiecte de tip complex. Apoi scrie intr-un alt fisier valoarea obiectelor complex sub forma: parte_reala+parte_imaginara*i

    2. La clasele definite in programul tema de la lucrarea nr. 6 se vor adauga functii de afisare folosind operatiile cu stream-uri (+mecanismul functiilor virtuale) astfel incat forma de afisare a obiectelor sa fie urmatoarea:

    De exemplu, daca avem un obiect col de tip Collection, format din 4 elemente: un Object , un Int cu valoarea 100, un String cu valoarea "cucu" si o multime de 2 elemente (un Int cu valoarea 2 si un Int cu valoarea 13), la afisarea acestui obiect prin cout << col ar trebui sa obtinem secventa:
    <<Object> -> <100> -> "cucu" -> {<2>, <13>}>

    Pentru intrebari legate de enuntul acestei probleme trimiteti un mail la [email protected]

    Bibliografie

    [Str91]        B. Stroustrup  - "The C++ programming language",  AT&T, 1991

    Hosted by www.Geocities.ws

    1