Sunteți pe pagina 1din 18

Capitolul 9.

Utilizarea claselor de fiiere secveniale

175

9. Utilizarea claselor de fiiere secveniale

Marea majoritate a aplicaiilor trebuie s pstreze un timp ndelungat, de obicei pe un suport magnetic, datele de intrare, sau rezultatul execuiei lor, sub forma unor fiiere. Cunoatem c ANSI C pune la dispoziia utilizatorilor diferite funcii pentru manipularea fiierelor, furnizate de biblioteca <stdio.h>, cum ar fi: fopen(), fcolse(), fprintf(), fscanf(), fflush(), etc. Aceste funcii construiesc sau utilizeaz un identificator ataat fiierului cu care se lucreaz, toate operaiile asupra fiierului facndu-se pe baza acestui identificator. De asemenea, apariia unor erori n manipularea fiierelor, este semnalizat tot de ctre acest identificator, fiind din pcate, mai dificil de determinat cauza care a produs eroarea. Aceste funcii pot fi utilizate i n programele a cror interfa este construit cu MFC, dar acest lucru nu este de dorit. MFC pune la dispoziie o ierarhie de clase pentru lucrul cu fiiere, mult mai uor de utilizat i mult mai clare n detectarea i identificarea erorilor. nainte de a prezenta care sunt aceste clase i cum se lucreaz cu ele, s mai studiem o tehnic de interceptare a erorilor, deosebit de des utilizat n limbajele moderne, cum ar fi C++ sau Java. 9.1 Excepii Complexitatea actual a aplicaiilor software, inclusiv a celor orientate pe obiecte, presupune o modularizare pe nivele ierarhice. Aceast situaie impune tratarea deosebit de riguroas a erorilor i a situaiilor deosebite care pot s apar la nivelul diferitelor componente ale unui proiect. n cele ce urmeaz, vom nelege prin excepie o situaie deosebit care poate s apar pe parcursul execuiei unei componente soft parte a unei aplicaii. Erorile pot fi privite ca i un caz particular de excepii, dar n general nu toate excepiile sunt erori. Excepiile se mpart n dou categorii: excepii sincrone i excepii asincrone. Excepiile sincrone sunt excepii a cror apariie poate fi prevzut de programator (de exemplu, un fiier care se dorete a fi prelucrat nu se afl n directorul ateptat). Excepiile asincrone sunt excepii a cror apariie nu poate fi prevzut n momentul implementrii programului (de exemplu, defectarea accidental a hard-disk-ului). Mecanismul de tratare a excepiilor ia n considerare numai situaia excepiilor sincrone. Problema se pune astfel: n faza de programare, se poate presupune c un modul de program va detecta n anumite situaii o excepie, dar nu va putea oferi o soluie general de tratare a acesteia, n timp ce un alt modul al aplicaiei poate oferi o soluie de tratare a excepiei, dar nu poate detecta singur apariia excepiilor. Un exemplu concret al unui astfel de caz este urmtorul: presupunem c avem o aplicaie care lucreaz cu fiiere. Un modul al aplicaiei (numit n cele ce urmeaz surs) declar o clas CFile care poate detecta o excepie din clasa fiier inexistent. Aceast clas ns nu poate s ntreprind o aciune general de tratare a excepiei prinse. Tratarea excepiei se face n funcie de modulul (numit destinaie) care folosete clasa CFile. Un modul ar putea, de exemplu, s invoce funcia exit() i s termine aplicaia, n

176

Programarea Aplicaiilor Windows n Visual C++. Vol. II

timp ce un alt modul ar putea doar s afieze o caset de dialog i s solicite utilizatorului selectarea unui alt fiier. Soluia obiectual a acestei probleme este bazat pe urmtoarea idee fundamental: modulul care detecteaz excepia pe care nu o poate rezolva o paseaz (throw) modulului client, n sperana c o component a acestuia o va prinde (catch) i o va trata.

9.1.1 Metode neobiectuale de tratare a excepiilor Urmtoarele metode sunt folosite n mod tradiional pentru tratarea excepiilor: terminarea aplicaiei - este o soluie folosit n special de sistemul de operare, n cazul n care o excepie aruncat nu este prins; returnarea unui cod de eroare; poziionarea unui indicator global; ignorarea excepiei - este o soluie periculoas. n cazul n care excepia nu este o eroare, aplicaia poate s mai ruleze pn cnd se produce o eroare. Din pcate, este foarte greu de depanat i de gsit eroarea n astfel de situaii, deoarece nu se poate detecta momentul producerii ei. invocarea unei funcii specializate de tratare a excepiei, numit handler;

9.1.2 Mecanismul C++ de tratare a excepiilor Alternativa oferit de limbajul C++ pentru tratarea excepiilor furnizeaz o soluie mbuntit, simplificnd cooperarea ntre componente scrise de mai muli programatori, dar integrate n aceeai aplicaie. Implementarea C++ a mecanismului de tratare a excepiilor are la baz urmtoarele trei cuvinte cheie: try care delimiteaz la nivelul modulului destinaie codul care ar putea s emit (raise) o excepie; throw - care se execut n cazul n care modulul surs a detectat o condiie de excepie; catch delimiteaz o secven de cod la nivelul modulului destinaie care va trata o situaie excepional n cazul detectrii ei; Aadar, tratarea excepiilor printr-un mecanism C++ se face n urmtoarele trei etape: se emite excepia prin throw; se detecteaz excepia prin try; se trateaz excepia prin catch;

Forma general a secvenei de program ce trateaz excepiile este


try { if (eroare) throw excepie_de_tipul_stabilit; }

Capitolul 9. Utilizarea claselor de fiiere secveniale catch (tip_excepie* variabil) { // prelucrarea excepiei }

177

Un exemplu banal de tratare a unei excepii este prezentat n continuare: s presupunem c scriem urmtorul program:
#include <iostream.h> double impart(double deimpartit, double impartitor) { return deimpartit/impartitor; } void main(void) { double de, im; cout << "\n Deimpartit= "; cin >> de; cout << "\n Impartitor= "; cin >> im; cout <<"\n Rezultat= " << impart(de,im) << " "; }

Este evident, programul ateapt dou valori reale i afieaz rezultatul mpririi lor. Dar, cum cele 2 valori sunt externe programului, furnizate de utilizator, nimic nu-l mpiedic pe acesta s dea mpritorului valoarea 0, ajungnd n situaia anormal de mprire a unui numr la 0. Astfel de situaii de excepie sunt uzual tratate de sistemele de operare prin terminarea forat a programelor. O astfel de situaie este prezentat n fig. 9.1 a i b, respectiv aciunile efectuate de MsDOS i Windows, n cazul programelor scrise n Borland C i Visual C++ ntr-un proiect de tip Win32 Console Application.

a b Figura 9.1. Terminarea forat a programului n MsDOS i Windows

Se poate observa c n ambele cazuri, sistemul de operare termin forat programul. Putem face prin mecanismul try throw catch, ca eroarea s nu fie pasat sistemului de operare, ci reinut n cadrul programului, pentru a fi tratat de acesta. Pentru aceasta, s modificm programul ca mai jos:
#include <iostream.h> #include <stdio.h> #include <stdlib.h> double impart(double deimpartit, double impartitor) { if (impartitor==0) throw "Eroare: impartire la 0"; return deimpartit/impartitor; }

178

Programarea Aplicaiilor Windows n Visual C++. Vol. II

void main(void) { double de, im; cout << "\n Deimpartit= "; cin >> de; while (1) { cout << "\n Impartitor= "; cin >> im; try { cout <<"\n Rezultat= " << impart(de,im) << " "; exit(1); } catch (char* exceptie) { cout << "\n " << exceptie << " "; } } }

Ce observm? n caz de funcionare normal, programul afieaz rezultatul mpririi i se termin, datorit instruciunii exit(1). n caz de mprire cu 0, programul se reia ciclic, semnalnd mprirea cu 0 i cernd un nou mpritor. Deci, programul nu mai execut instruciunea urmtoare apariiei excepiei (aruncat de funcia impart()), adic exit(1), ci sare n blocul catch, execut secvena de tratare a excepiei din acesta i continu cu secvena de program de dup blocul catch. (fig. 9.2).

impart()

while (1)

DA

Excepie aruncat?

NU

catch

afiare exit(1)

Figura 9.2. Execuia secvenei try - catch

Se impune i o a doua remarc: excepia trebuie considerat ca fiind o instaniere a unui obiect! n exemplul de mai sus, excepia este un obiect de un tip predefinit, anume ir de caractere. Dac excepia emis nu ar fi fost prins n blocul catch, sistemul de operare ar fi intervenit i ar fi stopat execuia programului. Excepiile pot s apar din diferite cauze, deci i tipurile excepiilor lansate n blocurile try pot fi diferite. Este absolut necesar ca excepia capturat de blocul catch s fie de acelai tip cu cea lansat anterior n blocul try. De menionat faptul c un bloc try trebuie s fie urmat de unul sau mai multe blocuri catch, cte unul pentru fiecare tip de excepie care s-ar putea emite n blocul try. Exemplul de mai jos prezint aceast situaie:

Capitolul 9. Utilizarea claselor de fiiere secveniale #include <iostream.h> #include <string.h> // clasa Exceptie implementeaza un obiect exceptie, care contine // informatie despre tipul exceptiei class ExceptieObiectuala { public: char* tip_exceptie; ExceptieObiectuala(char* arg=NULL); ~ExceptieObiectuala(); }; ExceptieObiectuala::ExceptieObiectuala(char* arg) { if(arg!=NULL) { tip_exceptie = new char[strlen(arg)+1]; strcpy(tip_exceptie, arg); } else tip_exceptie = NULL; } ExceptieObiectuala::~ExceptieObiectuala() { if(tip_exceptie!=NULL) delete tip_exceptie; } // clasa care emite exceptii.. class Test { public: // emite o exceptie de tip string void ThrowString(); //emite o exceptie de tip Exception void ThrowExceptieObiectuala();

179

};

void Test::ThrowString() { throw "EXCEPTIE TIP SIR DE CARACTERE!!"; } void Test::ThrowExceptieObiectuala() { throw *new ExceptieObiectuala("EXCEPTIE DE CLASA ExceptieObiectuala!!"); } void main() { Test t; // emite exceptie de tip string try{ t.ThrowString(); }

180

Programarea Aplicaiilor Windows n Visual C++. Vol. II catch(char* sir1_Exceptie){ cout<<sir1_Exceptie<<"\n"; } catch(ExceptieObiectuala& ref_exceptie1){ cout<<ref_exceptie1.tip_exceptie<<"\n"; } // asa se trateaza exceptiile neinterceptate n alte blocuri ... catch(...) { ; } // emite un obiect exceptie de clasa ExceptieObiectuala try{ t.ThrowExceptieObiectuala(); } catch(char* sir2_Exceptie) { cout<< sir2_Exceptie <<"\n"; } catch(ExceptieObiectuala& ref_exceptie2) { cout<< ref_exceptie2.tip_exceptie <<"\n"; }

Ce se poate remarca din acestui exemplu? n primul rnd, modul de tratare a excepiilor care nu sunt prinse n alte blocuri catch, cu construcia catch(). n al doilea rnd, blocurile catch prind o referin la un obiect ExceptieObiectuala i nu un obiect ExceptieObiectuala transmis prin valoare. Aceasta este necesar deoarece, n cazul transmiterii unui obiect ExceptieObiectuala prin valoare, s-ar apela constructorul de copiere (furnizat de compilator), cu efecte dezastruoase! 9.2 Clasele MFC pentru lucrul cu fiiere MFC furnizeaz dou clase pentru lucrul cu fiiere standard. Clasa CFile permite accesul la fiiere binare stocate pe disc. Clasa ncapsuleaz un identificator pentru accesul la fiiere i ofer metode de deschidere, citire, scriere i nchidere a fiierelor. Clasa CFile realizeaz un acces direct la fiier, fr stocarea prealabil ntr-o zon tampon (buffer) a datelor. Clasa CStdioFile, derivat din clasa CFile, implementeaz operaii cu fiiere n flux. n acest caz, datele sunt n prealabil stocate n buffere. Tot derivate din clasa CFile sunt i clasa CMemFile care asigur suport pentru fiierele stocate n memoria RAM, cum ar fi cele salvate pe RAM-disk-uri, utilizate pentru creterea vitezei de acces la date, sau clasa CSharedFile, care asigur suport pentru accesul la fiiere partajate, aflate n memorie. 9.2.1 Deschiderea fiierelor Clasa CFile permite deschiderea unui fiier pe mai multe ci. Cteva din acestea sunt prezentate mai jos:

Capitolul 9. Utilizarea claselor de fiiere secveniale

181

clasa CFile furnizeaz un constructor care permite specificarea fiierului de pe disc care urmeaz a fi deschis. Acest constructor permite declararea unui obiect CFile i l asociaz cu un fiier existent. Cum deschiderea fiierului se face n constructorul clasei, o situaie de incident, cum ar fi inexistena fiierului specificat, poate duce la erori fatale; o cale mai normal presupune deschiderea fiierului n doi pai. nti se creeaz un obiect CFile, apoi acestui obiect i se asociaz un fiier de pe disc. Pentru deschiderea fiierului, se utilizeaz funcia

virtual BOOL Open( LPCTSTR lpszFileName, UINT nOpenFlags, CFileException* pError = NULL )

n care parametri au urmtoarele semnificaii: lpszFileName - este numele fiierului ce urmeaz a fi deschis, coninnd eventual i calea; nOpenFlags este - o masc de bii care specific modul de acces la fiier. Variantele posibile pentru acest parametru sunt prezentate n tabelul 1. Este posibil combinarea mai multor opiuni, prin intermediul operaiei logice SAU (|);
Tabelul 9.1 Valoare CFile::modeCreate CFile::modeNoTruncate Semnificaie Specific crearea unui fiier nou. Dac exist deja un fiier cu acelai nume, acesta este trunchiat la dimensiune 0; Acest parametru, mpreun cu cel anterior, va permite pstrarea neschimbat a unui fiierului existent. Este avantajoas utilizarea lui, pentru c, n acest caz, fiierul va fi sigur deschis, cu coninut nul dac nu exista, sau cu coninutul curent, n caz contrar; Fiierul este deschis doar n citire; Fiierul este deschis n citire i scriere; Fiierul este deschis n scriere Fiierul nu va putea fi motenit de procese copil; Deschide fiierul astfel nct orice proces activ s poat scrie sau citi n el. Crearea fiierului eueaz dac un alt proces l-a deschis anterior; Deschide fiierul n aa fel nct alte procese active nu au drept de citire a datelor din fiier; Deschide fiierul n aa fel nct alte procese active nu au drept de scriere n fiier; Deschide fiierul n aa fel nct alte procese active nu au drept de scriere sau citire n/din fiier; Fiierul este interpretat ca text, fiind interpretate corespunztor caracterele CR, LF, FF, etc; Fiierul conine date binare

CFile::modeRead CFile::modeReadWrite CFile::modeWrite CFile::modeNoInherit CFile::shareDenyNone CFile::shareDenyRead CFile::shareDenyWrite CFile::shareExclusive CFile::typeText CFile::typeBinary

O secven tipic de deschidere n acest mod a unui fiier este prezentat mai jos:
CString strFileName=C:\\Users\\Date\\Fisier.dat; CfileException Fex; CFile Fisier; BOOL bResult=Fisier.Open(strFileName, CFile::modeCreate | CFile::modeNoTruncate, &Fex); if (!bResult) AfxMessageBox( Eroare deschidere fisier + strFileName); Fex.GetErrorMessage();

182

Programarea Aplicaiilor Windows n Visual C++. Vol. II

pError - este un pointer spre un obiect excepie de tip fiier, care va

intercepta excepiile aprute la deschiderea fiierului. Exist o mulime de cauze de apariie a erorilor la deschiderea unui fiier, cea mai des ntlnit fiind o ncercare de deschidere a unui fiier inexistent, sau a unui fiier deschis exclusiv de un alt proces. Funcia Open() returneaz valoarea TRUE dac deschiderea este reuit, sau FALSE n caz contrar, putndu-se detecta succesul sau insuccesul operaiei de deschidere. Din pcate, acest mod de testare a operaiei, nu d informaii, n caz de eec, asupra cauzei care a produs eecul. Pentru determinarea cauzei, MFC ofer clasa CFileException, care ncapsuleaz erorile de lucru cu fiierele. Clasa CFile furnizeaz i un constructor de deschidere a fiierelor, care utilizat, va arunca o excepie CFileException dac deschiderea eueaz. Aceasta poate fi interceptat ntr-un bloc try-catch tipic, ca n exemplul de mai jos:
try { CFile Fisier(C:\\Users\\Date\\Fisier.dat, CFile::modeCreate | CFile::modeNoTruncate); } catch (CFileException* fEx) { TCHAR buf[255]; fEx->GetErrorMessage(buf, 255); CString strMesErr(buf); AfxMessageBox(strMesErr); }

Funcia Open() nu arunc o excepie, dar primete ca parametru un pointer spre un obiect CExceptionFile. Dac deschiderea fiierului eueaz, acest obiect este actualizat cu informaii despre tipul erorii. Aceste informaii pot fi utilizate, ca n codul de mai jos:
CString strFileName=C:\\Users\\Date\\Fisier.dat; CFile Fisier; CFileException fEx; if (!Fisier.Open(strFileName, CFile::modeCreate | CFile::modeNoTruncate, &fEx) { TCHAR buf[255]; fEx.GetErrorMessage(buf, 255); CString strMesErr(buf); AfxMessageBox(strMesErr); }

9.2.2 nchiderea fiierelor nchiderea fiierelor se face prin intermediul funciei CFile::Close(). Nu ntotdeauna este absolut necesar utilizarea acestei funcii, pentru c destructorul clasei CFile apeleaz automat aceast funcie cnd obiectul CFile curent iese din domeniul de definiie. Dar, este totui recomandat ca orice apel al funciei CFile::Open() s fie pereche cu un apel al funciei CFile::Close().

Capitolul 9. Utilizarea claselor de fiiere secveniale

183

Funcia Close() poate fi de asemenea utilizat pentru a detaa obiectul CFile care o apeleaz de fiierul pe care acesta care l reprezint la un moment dat i a-l asocia unui alt fiier. Un exemplu tipic este prezentat mai jos, unde sunt create 3 fiiere cu nume diferite, stocate ntr-un tablou de nume, utiliznd acelai obiect CFile.
CString strFilename[3]={Fisier1.txt, Fisier2.txt, Fisier3.txt}; CFile Fisier; For (int i=0;i<3;i++) { Fisier.Open(strFilename[i], CFile::modeCreate); Fisier.Close(); }

9.2.3 Citirea i scrierea din/n fiiere Citirea i scrierea direct din fiiere se pot face cu funciile clasei CFile Read() i Write(), funcii care apeleaz funciile ReadFile() i WriteFile() ale Windows API. S ne reamintim c prin intermediul clasei CFile, scrierea i citirea se fac direct pe disc, lucru destul de riscant. De aceea, este preferabil s se utilizeze funcii care lucreaz cu fluxuri de date I/O. n C++, clasa care implementeaz aceste funcii este iostream, punnd la dispoziie funcii cum ar fi fopen(), fseek(), fread(), fwrite(), etc. ntr-un proiect MFC apelul acestor funcii este anacronic. n MFC, operaiile cu fiiere n flux, sunt implementate de clasa CStdioFile. Funciile Read() i Write() din aceast clas, spre deosebire de cele ale clasei CFile, ofer acces la fiiere prin intermediul unor fluxuri I/O i nu prin acces direct la disc. Fiierele asociate unui obiect CStdioFile pot fi deschise n mod text sau n mod binar. Fiierele deschise n mod text realizeaz interpretarea caracterelor de control speciale, cum ar fi CR, LF, etc. Pentru deschiderea unui fiier n mod text, se va utiliza opiunea CFile::typeText.
CStdioFile Fisier.Open(Filename, CFile::modeRead | CFile::typeText);

Pentru

deschiderea

fiierului

mod

binar,

se

va

utiliza

opiunea

CFile::typeBinary. n acest mod, toi octeii fiierului sunt interpretai n mod

identic, fr a exista coduri de control. Citirea datelor se poate face prin intermediul a dou funcii:
virtual UINT Read( void* lpBuf, UINT nCount ) virtual LPTSTR ReadString( LPTSTR lpsz, UINT nMax )

sau
BOOL ReadString(CString& rString)

Ambele funcii primesc ca i argumente un pointer la un buffer tampon n care se vor citi datele, respectiv la un ir de caractere, precum i dimensiunea maxim a acestora. n a doua variant a funciei ReadString() se specific un ir n care se stocheaz datele citite din fiier. Funcia returneaz valoarea TRUE dac citirea e reuit, respectiv FALSE dac citirea eueaz. Pentru toate funciile, n caz de eec se, va arunca o excepie de clas CFileException.

184

Programarea Aplicaiilor Windows n Visual C++. Vol. II

Scrierea datelor se face cu funciile:


virtual void Write( const void* lpBuf, UINT nCount ) virtual void WriteString( LPCTSTR lpsz )

cu aceleai semnificaii pentru argumente ca i n cazul anterior. De asemenea, n caz de insucces, este aruncat o excepie CFileException. Pentru exemplificare, vom crea un schelet de program de testare a cunotinelor. Acest program va prelua ntrebrile, rspunsurile posibile precum i rspunsul corect, din fiierul res.dat aflat n directorul implicit. Acest fiier de tip text, creat cu un editor oarecare, va conine informaii cu o structur ce va fi detaliat mai jos. Fiecare linie a fiierului reprezint informaia aferent unei ntrebri. Fiierul este citit iniial n memorie, pentru a asigura accesul rapid la informaie i pentru a evita meninerea pentru un timp ndelungat a fiierului deschis. Testul const dintr-un numr prestabilit de ntrebri. Acestea sunt alese prin generarea cte unui numr aleator mai mic dect numrul ntrebrilor memorate. Dup selectarea unei ntrebri, aceasta este eliminat din memorie, pentru a nu putea fi afiat din nou. Pentru fiecare ntrebare sunt afiate trei rspunsuri, doar unul fiind corect. Dup parcurgerea numrului prestabilit de ntrebri, se afieaz o not. Fie fis denumirea proiectului de tip Dialog Based. Macheta aplicaiei este cea de mai jos:

Figura 9.3. Macheta aplicaiei

Se parcurg paii: se atribuie machetei identificatorul IDD_FIS_DIALOG; se terg butoanele OK i Cancel; se atribuie casetei cu list identificatorul IDC_TEXT. Se selecteaz Single la Style-Selection; De asemenea, n ClassWizard, se asociaz casetei variabila de categorie Control, CListBox m_lbText; se insereaz caseta de grupare Raspunsuri, care conine grupul de butoane de opiune IDC_RASPUNS1, IDC_RASPUNS2, IDC_RASPUNS3. Primul buton va avea opiunile Group i Tab stop, iar celelalte dou doar Tab stop; n ClassWizard, se asociaz grupului de butoane, variabila de categorie Value, int m_nRaspuns; Rspunsurile posibile vor fi afiate ca i texte asociate butoanelor; se insereaz butoanele Start test i Accept, cu identificatorii IDC_START_TEST i respectiv IDC_ACCEPTA; deoarece caseta cu list nu interpreteaz caracterele de control, n fiierul cu date vom insera marcatori pentru delimitarea diferitelor componente ale ntrebrilor. Spre exemplu, fiierul de test res.dat va avea coninutul:

Capitolul 9. Utilizarea claselor de fiiere secveniale

185

Aceasta este prima intrebare. Urmeaza sa vad cum se @aseaza in caseta si sa generez raspunsuri@~Acesta este primul raspuns la prima intrebare^Acesta este al doilea raspuns la prima intrebare^Acesta este al treilea raspuns la prima intrebare^|2 Aceasta este a doua intrebare. Urmeaza sa vad cum se @aseaza in caseta si sa generez raspunsuri@~Acesta este primul raspuns la a doua intrebare^Acesta este al doilea raspuns la a doua intrebare^Acesta este al treilea raspuns la a doua intrebare^|2

Aceasta este a treia intrebare. Urmeaza sa vad cum se @aseaza in caseta si sa generez raspunsuri@~Acesta este primul raspuns la a treia intrebare^Acesta este al doilea raspuns de a treia intrebare^Acesta este al treilea raspuns la a treia intrebare^|2 Caracterul @ este utilizat ca delimitator de linie. La ntlnirea lui, n caseta cu list se va trece la linie nou. Sfritul textului afiat n caseta cu list este marcat de delimitatorul ~. Apoi, urmeaz cele trei rspunsuri posibile, delimitate prim ^. Delimitatorul | specific varianta corect de rspuns. se insereaz o machet de tip dialog (Resource View, click dreapta, Insert, Dialog, New, etc), ca n figura 9.4. Se insereaz n machet butoanele de opiune Da i Nu, avnd opiunea Push-like de la Styles selectat, cu identificatorii IDC_DA i IDC_NU, grupate ntr-o caseta de grupare. IDC_DA va avea setate opiunile Tab stop i Group, iar IDC_NU doar Tab stop. De asemenea n ClassWizard se asociaz grupului de butoane variabila de categorie Value int m_nDa. Aceast machet va fi lansat modal, pentru a relua testul dup terminarea lui, sau a renuna la programul de testare. Fie CModalDlg clasa care mapeaz aceast caset.

Figura 9.4. Macheta de reluare

se modific funcia OnInitDialog(), astfel nct dac fiierul de ntrebri este deschis corect testarea s fie posibil, altfel butoanele Start test i Accept s fie inactive:
BOOL CFisDlg::OnInitDialog() { CDialog::OnInitDialog(); // TODO: Add extra initialization here GetDlgItem(IDC_ACCEPTA)->EnableWindow(FALSE); if (!CompleteazaSetIntrebari()) GetDlgItem(IDC_START_TEST)->EnableWindow(FALSE); } return TRUE; // return TRUE unless you set

funcia CompleteazSetIntrebari() returneaz valoarea TRUE dac fiierul de ntrebri a fost deschis corect i FALSE n caz contrar. Ea se insereaz n clasa CFisDlg ca funcie membru (Add Member Function) i are implementarea de mai jos.

186

Programarea Aplicaiilor Windows n Visual C++. Vol. II

int CFisDlg::CompleteazaSetIntrebari() { CString strFilename("c:\\Html\\res.dat"); TCHAR cBuf[255]; CStdioFile dataFile; CFileException fEx; if (!dataFile.Open(strFilename,CFile::modeRead | CFile::typeText, &fEx)) { TCHAR cBufEx[255]; fEx.GetErrorMessage(cBufEx, 255); CString strMesErr(cBufEx); AfxMessageBox(strMesErr); return 0; } else { cstrDepozit.RemoveAll(); pos=cstrDepozit.GetHeadPosition(); while (dataFile.ReadString(cBuf,255)) { CString strBuf(cBuf); cstrDepozit.AddTail(strBuf); } dataFile.Close(); return 1; } }

Funcia deschide fiierul de test n mod text, n citire. Dac deschiderea eueaz, este aruncat o excepie CFileException care este interceptat i se afieaz mesajul de eroare corespunztor. n acest caz, funcia returneaz valoarea 0. Dac deschiderea este reuit, se terge coninutul variabilei CStringList cstrDepozit, n care va fi ncrcat coninutul fiierului. Apoi, atta timp ct se pot citi date din fiier, coninutul bufferului n care se citesc datele va fi adugat la coada variabilei cstrDepozit. n final, fiierul este nchis, iar funcia returneaz valoarea 1. Variabila cstrDepozit este adugat clasei CFisDlg ca variabil membru (Add Member Variable, ); De asemenea, se insereaz variabila POSITION pos ca membr a clasei CFisDlg. testul este lansat la apsarea butonului Start test. Funcia care implementeaz apsarea acestui buton este
void CFisDlg::OnStartTest() { // TODO: Add your control notification handler code here Nota=0; m_nRaspuns=0; UpdateData(FALSE); NumarIntrebariInTest=2; if (cstrDepozit.GetCount()) { GetDlgItem(IDC_ACCEPTA)->EnableWindow(TRUE);

Capitolul 9. Utilizarea claselor de fiiere secveniale GetDlgItem(IDC_START_TEST)->EnableWindow(FALSE); GenereazaTest();

187

} else { AfxMessageBox(" Nu mai exista intrebari! "); CDialog::OnOK(); } }

Variabilele int Nota i int NumarIntrebariInTest sunt adugate clasei CFisDlg ca variabile membru. Ele vor memora nota obinut i numrul de ntrebri n care const textul. Testul poate fi continuat atta timp ct mai exist ntrebri n memorie, altfel se afieaz un mesaj de terminare i se apeleaz funcia OnOK() pentru a termina programul. generarea testului se face cu funcia GenereazaTest(). Aceasta este adugat clasei CFisDlg ca i funcie membru i are implementarea:
void CFisDlg::GenereazaTest() { CString strAfiseaza; CString strTemp; srand((unsigned) time (NULL)); int nNumarIntrebare=rand() % (cstrDepozit.GetCount()); CautaPozitia(nNumarIntrebare); strAfiseaza=cstrDepozit.GetAt(pos); int i=0; m_lbText.ResetContent(); while (strAfiseaza[i] != '~') { strTemp.Empty(); for (i=i;strAfiseaza[i] != '@';i++) strTemp+=strAfiseaza[i]; m_lbText.AddString(strTemp); i++; } i++; int j=0; while (strAfiseaza[i] != '|') { strTemp.Empty(); for (i=i;strAfiseaza[i] != '^';i++) strTemp+=strAfiseaza[i]; GetDlgItem(IDC_RASPUNS1+j)->SetWindowText(strTemp); j++; i++;

} i++; RaspunsCorect=strAfiseaza[i]-'1'; cstrDepozit.RemoveAt(pos); }

188

Programarea Aplicaiilor Windows n Visual C++. Vol. II

Se genereaz un numr aleator, care va reprezenta numrul ntrebrii selectate. Apoi, se parcurge lista strDepozit pn la nregistrarea selectat, prin intermediul funciei CautaPozitia(). nregistrarea este stocat n irul strAfiseaza. Urmeaz interpretarea informaiei din aceast variabil, afiarea textului corespunztor n caseta cu list i afiarea etichetelor butoanelor de opiune. n final, nregistrarea selectat este eliminat din strDepozit, pentru ca aceeai ntrebare s nu poat fi selectat din nou, respectiv pentru a se genera un numr aleator ntr-o plaj de valori cu maximumul decrementat cu 1. Variabila int RaspunsCorect este adugat ca i variabil membru a clasei CFisDlg. funcia CautaPozitia() este adugat clase CFileDlg ca i funcie membru i are implementarea:
void CFisDlg::CautaPozitia(int pozitia) { pos=cstrDepozit.GetHeadPosition(); for (int i=0;i<pozitia;i++) cstrDepozit.GetNext(pos); }

dup selectarea unui rspuns, acesta este validat prin apsarea butonului Accepta. Implementarea funciei care rspunde la apsarea acestui buton este:
void CFisDlg::OnAccepta() { // TODO: Add your control notification handler code here UpdateData(); NumarIntrebariInTest--; if (m_nRaspuns==RaspunsCorect) Nota++; if (NumarIntrebariInTest) { if (cstrDepozit.GetCount()) GenereazaTest(); else { AfxMessageBox(" Nu mai exista intrebari! "); CDialog::OnOK(); } } else { CString strNota; CString strTemp; strTemp.Format(" %d ",Nota); strNota+="La acest test ati obtinut nota "+strTemp; MessageBox(strNota,"REZULTAT",MB_ICONSTOP); CRaspunsDlg dlgCasetaRaspuns(this); int nRetCode=dlgCasetaRaspuns.DoModal(); if (!dlgCasetaRaspuns.m_nDa) { CompleteazaSetIntrebari(); Nota=0; m_nRaspuns=0; UpdateData(FALSE); NumarIntrebariInTest=2; if (cstrDepozit.GetCount()) GenereazaTest(); else

Capitolul 9. Utilizarea claselor de fiiere secveniale {

189

} }

} } else CDialog::OnOK();

AfxMessageBox(" Nu mai exista intrebari! "); CDialog::OnOK();

Funcia decrementeaz numrul de ntrebri rmase, iar dac rspunsul a fost corect, incrementeaz nota. Dac mai exist ntrebri n test i mai exist ntrebri disponibile, genereaz o nou ntrebare, altfel termin programul. Dac nu mai exist ntrebri n test, se afieaz nota i se lanseaz caseta modal pentru a se vedea dac se ncepe un nou test sau se termin programul. Dac se dorete reluarea testului i mai exist ntrebri disponibile se reia testul, altfel se iese. se genereaz funciile OnDa() i OnNu() care rspund mesajului BN_CLICKED asociat butoanelor casetei derivate:
void CRaspunsDlg::OnDa() { // TODO: Add your control notification handler code here UpdateData(); CDialog::OnOK(); } void CRaspunsDlg::OnNu() { // TODO: Add your control notification handler code here UpdateData(); CDialog::OnOK(); }

Funciile vor actualiza valoarea m_nDa, testat n macheta printe, dup care vor nchide caseta modal. pentru a putea lansa modal macheta, va trebui ca la nceputul fiierului fisDlg.cpp s se insereze linia
#include #include #include #include "stdafx.h" "fis.h" "fisDlg.h" "ModalDlg.h"

#ifdef _DEBUG

n acest moment programul este complet funcional. Uzual, datele coninute n fiierul de teste trebuie codificate, n caz contrar putnd fi citite cu orice editor de texte. Vom crea un nou program, care, presupune c fiierul cu teste a fost deja creat cu un editor oarecare, fiind salvat pe disc cu un nume oarecare. Programul va primi ca intrare numele fiierului, iar la apsarea butonului Codifica va executa urmtoarele:

190

Programarea Aplicaiilor Windows n Visual C++. Vol. II

va deschide n citire fiierul cu teste; dac deschiderea este reuit, va crea fiierul res.dat; va citi linie cu linie fiierul cu teste, adunnd 1 la fiecare cod ASCII impar i scznd 1 din fiecare cod ASCII par; va scrie fiecare linie astfel codificat n fiierul res.dat; dac o operaie de deschidere eueaz, va afia un mesaj de eroare i va relua dialogul; Pentru aceasta se parcurg urmtorii pai:

Figura 9.5. Macheta programului

se deschide un proiect numit codif, realizndu-se pentru acesta macheta din fig. 9.5; se modific identificatorul casetei de editare de sus n IDC_NUMEFS, iar n ClassWizard se asociaz acestei casete variabila de categorie Value CString m_strNumefs; se modific identificatorul casetei de editare de jos n IDC_NUMEFD, iar n ClassWizard se asociaz acestei casete variabila de categorie Value CString m_strNumefd; se modific identificatorul butonului Codifica n IDC_CODIF; se asociaz mesajului BN_CLICKED generat de butonul Codifica funcia OnCodif() cu implementarea de mai jos:
void CCodifDlg::OnCodif() { // TODO: Add your control notification handler code here UpdateData(); if (m_strNumefd.IsEmpty()) m_strNumefd="res.cod"; if (!m_strNumefs.IsEmpty()) { TCHAR cBuf[255]; CStdioFile outputFile; try { CStdioFile inputFile(m_strNumefs,CFile::modeRead |CFile::typeText); BOOL bResult=outputFile.Open(m_strNumefd, CFile::modeCreate|CFile::modeWrite|CFile::typeText); if (!bResult) AfxMessageBox("Eroare creare fisier "+m_strNumefd); else { while (inputFile.ReadString(cBuf,255))

Capitolul 9. Utilizarea claselor de fiiere secveniale { CString strBuf(cBuf); CString outBuf; outBuf.Empty(); for (int i=0;i<strBuf.GetLength();i++) { char x; if (i%2) x=strBuf[i]+1; else x=strBuf[i]-1; outBuf+=x; } outputFile.WriteString(outBuf); } outputFile.Close(); AfxMessageBox("Fisier codificat ! Datele se afla in fisierul "+m_strNumefd);

191

} }

} inputFile.Close(); } catch (CFileException* fEx) { TCHAR cBufEx[255]; fEx->GetErrorMessage(cBufEx, 255); AfxMessageBox(cBufEx); }

Observaie: n cele 2 programe de prezentate anterior au fost folosite n scop didactic cele trei metode de detectare a erorilor aprute n manipularea fiierelor. n mod normal, ntr-un program se utilizeaz n mod consecvent doar una din ele. Observaie: Fiierele text sunt citite i scrie n mod secvenial. Pentru fiierele cu coninut binar, poate fi uneori nevoie de acces direct la nregistrri. Orice fiier deschis menine un pointer la nregistrarea curent care este accesat. Acest pointer poate fi poziionat cu funcia
virtual LONG Seek( LONG lOff, UINT nFrom )

unde lOff reprezint numrul de octei peste care se mut pointerul, iar nFrom modalitatea n care acesta se numr. Parametrul nFrom ia valorile din tabelul de mai jos:
Tabelul 9.2 Valoare CFile::begin CFile::current CFile::end Semnificaie Mut pointerul lOff octei nainte, numrai de la nceputul fiierului; lOff trebuie s fie pozitiv; Mut pointerul lOff octei, numrai de la poziia curenta; mutarea se face nainte dac lOff>0, respectiv napoi dac lOff<0; Mut pointerul lOff octei napoi, numrai de la sfritul fiierului; lOff trebuie s fie negativ;

ntrebri i probleme propuse 1. Implementai i executai toate exemplele propuse n capitolul 9;

192

Programarea Aplicaiilor Windows n Visual C++. Vol. II

2. Modificai programul de la pagina 179 ca mai jos:


... void Test::ThrowString() { // throw "EXCEPTIE TIP SIR DE CARACTERE!!"; throw int(4); } ... // catch(...){ // ; // } ...

Ce se ntmpla la execuie? Explicai. 3. Modificai din nou programul ca mai jos: ...
catch(...){ cout << "ALT FEL DE EXCEPTIE!\n"; } ...

Ce se ntmpla la execuie? Explicai. 4. Modificai n acelai program primul bloc catch ca mai jos:
... // catch(char* sir1_Exceptie){ // cout<<sir1_Exceptie<<"\n"; catch (int exceptie_intreaga) { cout << "EXCEPTIE DE VALOARE " << exceptie_intreaga << "\n"; } ...

Ce se ntmpla la execuie? Explicai. 5. Modificai programul de testare, astfel nct s lucreze cu fiierul codificat (implementai o faz de decodificare a fiierului nainte de salvarea acestuia n memorie); 6. Scriei un program de concatenare a dou fiiere de tip text;