18.3. Intrări formatate
18.3.1. Operatorul de extragere
Operaţiile de intrare se realizează cu ajutorul operatorului >>, care în acest caz se va numi operator de extragere.
Operandul din stânga al operatorului de extragere trebuie să fie un obiect al clasei istream, sau a unei clase derivate din clasa istream. Operandul din dreapta va fi o expresie, care poate aparţine atât unui tip standard, cât şi unui tip abstract. În cazul tipurilor standard se va apela o funcţie membru a clasei istream de forma:
istream& operator>>( tip_standard& );
În cazul tipurilor abstracte programatorul poate supraîncărca operatorul de extragere.
Pentru a prezenta legătura cu funcţia scanf, revenim la exemplul din secţiunea 15.1, în următoarea formă mai detaliată. Fişierul stream9.cpp:
Prin executarea programului obţinem următorul rezultat (s-au evidenţiat caracterele citite de la intrare).
Acelaşi rezultat se obţine şi prin executarea programului următor. Fişierul stream10.cpp:
Rezultă, că în cazurile de mai sus, nu există nici o diferenţă între citirea datelor de la intrarea standard folosind operatorul de extragere, respectiv cu funcţia scanf. Totuşi în anumite cazuri pot să apară diferenţe. De exemplu secvenţa
char c;
...
cin >> c;
nu este identică cu
char c;
...
scanf("%c", &c);
Diferenţa apare în cazul în care la intrare caracterul curent este un caracter alb, deci spaţiu, tab sau caracter newline (trecere la rând nou). Prezentăm în continuare un exemplu, din care rezultă, că într-adevăr cele două secvenţe de program nu sunt identice.
Vom defini o clasă c_alb pentru gestionarea caracterelor, şi vom supraîncărca operatorul de inserare pentru această clasă. Supraîncărcarea se va face astfel încât caracterele albe să fie afişate în mod vizibil (' ' pentru spaţii, '\t' pentru taburi şi '\n' pentru caractere newline). Caracterele diferite de cele albe vor fi afişate nemodificate. Fişierul stream11.cpp:
După executarea programului, obţinem un rezultat de forma:
La intrare s-a tastat mai întâi un caracter tab, după aceea caracterul 'q', urmat de trecerea la un rând nou. Acelaşi lucru s-a repetat şi în cazul citirii cu scanf. La scriere, caracterul v s-a convertit mai întâi într-un obiect anonim de tip c_alb, pentru a obţine o afişare corespunzătoare a caracterelor albe.
Din cele de mai sus rezultă că prin citirea cu operatorul de extragere s-a obţinut primul caracter diferit de caracterele albe. Dacă s-a apelat funcţia scanf, s-a citit caracterului curent, indiferent dacă el a fost caracter alb sau nu.
Menţionăm că diferenţa dintre cele două modalităţi de citire se poate înlătura, dacă se anulează bitul skipws al datei membru x_flags a clasei ios (valoarea acestui bit în mod implicit este unu).
În cazul funcţiei scanf, câmpul din care se face citirea începe cu primul caracter diferit de caracterele albe şi se termină, dacă următorul caracter este alb, sau caracterul respectiv nu mai corespunde formatului.
În cazul citirii unui şir de caractere, lungimea maximă a câmpului, din care se face citirea, se determină cu funcţia membru width a clasei ios. Este valabil tot ce s-a spus referitor la funcţia membru width în secţiunea 18.2.3, dar în acest caz valoarea datei membru x_width se interpretează ca şi lungimea maximă a câmpului, din care se face citirea, în loc de lungimea minimă a câmpului în care se face afişarea. Un avantaj important al funcţiei membru width, comparativ cu utilizarea funcţiei scanf, este că parametrul actual al funcţiei width poate fi orice expresie, în timp ce această valoare în cazul funcţiei scanf poate fi numai o constantă. Acest lucru se poate folosi pentru înlăturarea erorilor, care ar putea să apară din cauza citirii unui număr mai mare de caractere, decât zona de memorie alocată. Considerăm următorul exemplu pentru ilustrare. Fişierul stream12.cpp:
După executarea programului obţinem următorul rezultat (s au evidenţiat datele de intrare).
Se observă, că deşi la intrare s-au tastat mai multe caractere, nu s-a citit decât numărul de caractere, care se poate memora în spaţiul alocat şirului.
În cazul operaţiilor de extragere se pot folosi şi manipulatorii definiţi în paragraful 18.2.4, cu excepţia manipulatorilor endl, ends, flush şi setbase. Manipulatorul ws poate fi folosit numai în operaţii de extragere. Se pot folosi şi celelalte funcţii membru ale clasei ios, de exemplu funcţia membru setf.
18.3.2. Starea de eroare
Dacă citirea nu s-a terminat cu succes, atunci streamul ajunge într-o stare de eroare, despre care putem obţine informaţii cu ajutorul datei membru state a clasei ios.
Starea de eroare este caracterizată de biţii datei membru state. Data membru state este de tipul int, iar referirea la biţii ei se poate face cu ajutorul tipului enumerare io_state, definit în clasa ios în modul următor.
class ios {
public:
...
enum io_state {
goodbit = 0x00, // operaţie de intrare/ieşire corectă
eofbit = 0x01, // s-a ajuns la sfârşit de fişier
failbit = 0x02, // ultima operaţie de intrare/ieşire s-a
// terminat cu insucces
badbit = 0x04, // operaţie invalidă
hardfail = 0x80 // eroare irecuperabilă
};
...
};
Biţii eofbit, failbit, badbit şi hardfail se vor numi biţi de eroare. Valorile biţilor datei membru state pot fi determinate folosind următoarele funcţii membru ale clasei ios.
int good(); // goodbit este setat, deci nici unul din biţii de eroare
// nu este setat
int eof(); // bitul eofbit este setat
int fail(); // cel puţin unul din biţii failbit, badbit sau hardfail este setat
int bad(); // cel puţin unul din biţii badbit sau hardfail este setat
Aceste funcţii membru returnează valori diferite de zero, dacă condiţiile scrise sub formă de comentarii sunt îndeplinite. Funcţia membru
int rdstate();
returnează valoarea datei membru state. Modificarea valorilor biţilor datei membru state se poate efectua cu ajutorul funcţiei membru clear a clasei ios, funcţie declarată în următorul mod:
void clear(int = 0);
Dacă se apelează funcţia membru clear fără parametru, atunci toţi biţii de eroare se vor anula, în afară de bitul hardfail, care nu se poate anula. Dacă parametrul activ este prezent, atunci data membru state va lua valoarea parametrului. Pentru a seta un anumit bit se va folosi numele bitului precedat de numele clasei ios şi operatorul de rezoluţie. Dacă se foloseşte o construcţie de forma
cin.clear( ios::badbit | cin.rdstate() );
ceilalţi biţi vor rămâne nemodificaţi. În acest caz s-a setat numai bitul badbit, iar ceilalţi au rămas nemodificaţi. În următorul exemplu este prezentat modul de utilizare al funcţiei clear. Fişierul stare1.cpp:
Dacă se execută programul, se obţine următorul rezultat (sunt evidenţiate caracterele citite).
Funcţia binar afişează valoarea parametrului actual de tip int, în modul în care este memorat, iar funcţia nume_bit_1 afişează numele biţilor cu valoarea unu. Funcţia afisare apelează mai întâi funcţia binar, iar după aceea funcţia nume_bit_1. Deci un apel de forma
afisare( nume_state, cin.rdstate() );
afişează mai întâi data membru state în forma în care este memorată în calculator, iar după aceea numele tuturor biţilor setaţi ai datei membru state.
Observăm că în cazul citirii variabilei de tip întreg x, la intrare nu s-a aflat un număr întreg (s-a tastat caracterul a). De aceea streamul cin a intrat în stare de eroare, şi s-a setat bitul failbit. Funcţia fail a returnat o valoare diferită de zero (valoarea 2 corespunzătoare bitului failbit). După aceea s-a setat bitul badbit de către programator folosind funcţia membru clear. În continuare, anularea biţilor de eroare s-a făcut tot cu funcţia membru clear.
Înainte de o nouă citire trebuie vidat zona tampon corespunzătoare intrării standard. Acest lucru se poate efectua prin citirea unui şir de caractere. Dacă citirea s-ar fi făcut cu ajutorul operatorului de extragere, atunci şirul de caractere citit s-ar fi terminat la primul caracter alb. De aceea s-a folosit funcţia membru getline a clasei istream, funcţie care este declarată în următoarele forme:
istream& getline(signed char* z, int n, char c = '\n');
şi
istream& getline(unsigned char* z, int n, char c='\n');
Funcţia membru getline citeşte din streamul clasei istream un număr de cel mult n-1 caractere. Citirea se termină la întâlnirea caracterului c, sau dacă s au citit toate cele n-1 caractere. Menţionăm că nu se iau în considerare formatările referitoare la streamul din care se citeşte.
Deoarece funcţia membru getline citeşte şi caracterele albe, ea poate fi folosită pentru vidarea zonei tampon şi în cazul în care la intrare s-au tastat mai multe caractere albe. Observăm că într-adevăr după anularea biţilor de eroare şi vidarea zonei tampon, se poate citi şi valoarea altor date, în acest caz valoarea variabilei y.
Faptul că un stream se află în stare de eroare sau nu, poate fi verificat şi în următoarele două moduri:
-
folosind supraîncărcarea operatorului '!';
-
folosind conversia streamului într-un pointer de tip void*.
Operatorul '!' este supraîncărcat cu funcţia membru
int operator !();
a clasei ios. Valoarea returnată va fi diferită de zero, dacă cel puţin unul din biţii failbit, badbit sau hardfail este setat, deci dacă funcţia membru fail() returnează o valoare diferită de zero.
Operatorul '!' se va folosi în următorul exemplu. Să se definească o clasă pentru prelucrarea datelor de tip întreg. Pentru clasa respectivă se va defini o funcţie membru citeste, care va realiza citirea unei date de tip întreg. În cazul unei eventuale erori, citirea se va repeta până când se va citi o dată corectă, sau se ajunge la sfârşit de fişier. Pentru scrierea datei de tip întreg se va supraîncărca operatorul de inserare. Fişierul stare2.cpp:
Prin executarea programului obţinem următorul rezultat (s-au evidenţiat caracterele citite).
Clasa Intreg are următoarele trei date membru: întregul propriu zis; textul, care se va afişa înainte de citire şi mesajul, care va apare în cazul unei erori înainte de a relua citirea.
Observăm că în corpul funcţiei membru citeste s-a folosit operatorul '!' pentru a testa dacă a apărut o eroare la citire sau nu. În cazul în care nu s-a tastat un întreg, streamul intră în stare de eroare, deci biţii de eroare trebuie anulaţi şi trebuie vidată zona tampon. Anularea biţilor de eroare s-a făcut cu funcţia membru clear.
Vidarea zonei tampon s-a realizat cu ajutorul funcţiei membru get a clasei istream. Ea are mai multe forme, din care amintim următoarele:
int get();
istream& get(signed char*, int, char = '\n');
istream& get(unsigned char*, int, char = '\n');
Prima formă a funcţiei membru get extrage următorul caracter din streamul curent. În caz de sfârşit de fişier returnează EOF, deci valoarea -1. A doua, respectiv a treia formă a funcţiei membru get este asemănătoare cu cele două forme a funcţiei membru getline. Diferenţa este că în cazul funcţiei get caracterul terminal, determinat prin parametrul al treilea, nu se extrage din streamul curent, iar în cazul funcţiei getline se extrage şi caracterul terminal.
Vidarea zonei tampon s-ar fi putut efectua ca şi în cazul exemplului din fişierul stare1.cpp, folosind funcţia membru getline în modul următor:
s.getline(t, 255);
Dacă înlocuim secvenţa
s.get(t, 255); // vidarea zonei tampon la intrare
if ( s.get() == EOF ) //citeste '\n' sau EOF
return s;
din fişierul stare1.cpp, cu apelarea funcţiei getline, obţinem un rezultat asemănător, dar pot să apară şi diferenţe, de exemplu în cazul următor.
Rezultatul obţinut prin executarea programului stare2.cpp, varianta cu funcţia membru getline este
i = abc^Z
Eroare la citire.
i =
şi varianta cu funcţia membru get este
i = abc^Z
Deşi ambele rezultate pot fi considerate corecte, credem că în acest caz nu mai este necesară afişarea mesajului de eroare şi a textului pentru reluarea citirii. De aceea varianta cu funcţia membru get este cea preferată.
O altă modalitate de a verifica faptul că streamul este în stare de eroare sau nu, este conversia spre tipul void*. Rezultatul conversiei este pointerul NUL, deci valoarea zero, dacă cel puţin unul din biţii failbit, badbit sau hardfail este setat, adică funcţia membru fail() returnează o valoare diferită de zero. În caz contrar se obţine un pointer diferit de zero. De obicei această conversie se va efectua în cazul construcţiilor de forma următoare:
if ( stream >> data )
...
Rezultatul expresiei stream >> data este o referinţă la obiectul stream, care aparţine clasei istream. În cazul de mai sus rezultatul acestei expresii se va converti în mod automat într-un pointer de tip void*. Astfel se poate testa, dacă un bit de eroare, diferit de bitul eofbit, este setat sau nu.
De exemplu funcţia membru citeste a clasei Intreg se poate scrie în următoarea formă:
istream& Intreg::citeste(istream& s)
{
char t[255];
do {
cout << afisare_text;
if ( (s >> x) || (s.eof()) )
return s;
s.clear(); // anularea bitilor de eroare
s.get(t, 255); // vidarea zonei tampon la intrare
if ( s.get() == EOF ) //citeste '\n' sau EOF
return s;
cout << mesaj_eroare;
} while ( 1 );
}
şi rezultatul obţinut este identic cu cel al programului din fişierul stare2.cpp.
Ca şi în cazul operatorului de inserare, operatorul de extragere se poate supraîncărca pentru tipuri abstracte definite de programator. Pentru o clasă oareacare cu numele Clasa, operatorul de extragere se poate supraîncărca cu următoarea funcţie prieten:
class Clasa {
...
friend istream& operator>>(istream&, Clasa&);
...
};
În vederea măririi gradului de protecţie a datelor se va evita utilizarea funcţiilor prieten. De exemplu, se poate folosi o funcţie membru citire, care se va apela de către funcţia, care supraîncarcă operatorul de extragere, în modul următor:
class Clasa {
...
public:
istream& citire(istream& s);
...
};
istream& Clasa::citire(istream& s)
{
...
return s;
}
istream& operator>>(istream& s, Clasa& c1)
{
return c1.citire(s);
}
De exemplu, în cazul fişierului stare2.cpp operatorul de extragere se va supraîncărca în modul următor:
istream& operator >>(istream& s, Intreg& n)
{
return n.citeste(s);
}
Dacă se modifică fişierului stare2.cpp, astfel încât să fie supraîncărcat operatorul de extragere, atunci expresia
i.citeste( cin );
din funcţia main poate fi scrisă în forma:
cin >> i;
Observăm că funcţia, care supraîncarcă operatorul de extragere, nu are acces la datele protejate ale clasei Intreg. Deci s-a realizat o protecţie mai bună a datelor, decât prin folosirea unei funcţii prieten.
Dostları ilə paylaş: |