CATEGORII DOCUMENTE |
DOCUMENTE SIMILARE |
|
TERMENI importanti pentru acest document |
|
: | |
Bazele sistemului I/O in C++
Pe langa faptul ca permite sistemul de I/O din C, C++ defineste propriul sau sistem I/O orientat pe obiecte. Ca si sistemul de I/O din C, cel din C++ este complet integrat. Aceasta inseamna ca diferitele tipuri de operatii I/O sunt doar perspective diferite ale aceluiasi mecanism. Aceasta perspectiva integratoare asupra operatiilor I/O are la baza, atat in C cat si in C++, conceptul de flux (stream).
1 Fluxuri in C si C++
Sistemul de fisiere din C si C++ este proiectat sa lucreze cu o mare varietate de echipamente, care include terminale, drivere de disc, drivere de unitate de banda, etc.
AChiar daca echipamentele difera, sistemul de fisiere din C si C++ le transforma intr-un instrument logic numit flux (stream)
Toate fluxurile se comporta la fel. Deoarece fluxurile sunt independente de echipamente, o functie care poate sa scrie intr-un fisier de pe hard poate fi folosita cu aceeasi sintaxa pentru a scrie la alt dispozitiv. Sistemul de fisiere C/C++ recunoaste doua tipuri de fluxuri:text si binar.
Fluxuri de tip text
Un flux de tip text este o secventa de caractere. Standardul ANSI C permite (dar nu impune) ca un flux de tip text sa fie organizat in linii terminate cu un caracter de linie noua. Totusi, caracterul de linie noua din ultima linie este optional, utilizarea sa fiind determinata de modul de implementare a compilatorului (Majoritatea compilatoarelor de C/C++ nu incheie fluxul de tip text cu un caracter de linie noua. Sa mai semnalam faptul ca, intr-un flux de tip text pot sa apara anumite transformari cerute de mediul de operare gazda (De exemplu, un caracter de linie noua poate fi inlocuit cu perechea inceput de rand-linie noua. Acesta este motivul pentru care nu exista o relatie biunivoca intre caracterele care sunt scrise sau citite si cele de la echipamentul extern.
Fluxuri binare
Un flux binar este o secventa de octeti intr-o corespondenta biunivoca cu cei de la echipamentul extern.
Fisiere
In C/C++ un fisier poate sa fie: un fisier de pe disc, tastatura, ecranul monitorului, imprimanta,etc. Un flux se asociaza cu un anumit fisier efectuand o operatie de deschidere. Odata deschis fisierul, este posibil schimbul de date intre el si programul utilizator care l-a deschis.
De observat faptul, trivial pentru cunoscatori, ca nu toate fisierele au aceleasi posibilitati. De exemplu, un fisier de pe disc poate sa admita un acces aleator la datele stocate in el, in timp ce imprimanta nu o poate face. Astfel ca, putem concluziona, pentru claritate:
A Pentru sistemul I/O din C/C++ toate fluxurile sunt la fel dar nu si fisierele.
Daca fisierul admite cereri de pozitionare, deschiderea fisierului initializeaza pointerul de fisier la o valoare care indica inceputul fisierului. Pe masura ce se fac operatii de citire/scriere, pointerul de fisier este incrementat corespunzator naturii operatiei.
Un fisier se disociaza de un flux in urma operatiei de inchidere. Daca este inchis un fisier deschis in operatii de scriere, continutul fluxului asociat este scris la dispozitivul extern (acest proces se numeste flushing=golire a fluxului ).
Toate fisierele se inchid automat cand programul se termina normal. In caz de blocaj sau daca programul se termina ca urmare a apelului functiei abort() fisierele nu se inchid.
Cu mentiunea ca in fisierul antet stdio.h se gasesc structurile de control de tip FILE, indispensabile pentru lucrul cu fisiere in C, prezentam, in continuare contextul C++ referitor la sistemul I/O.
2 Clasele de baza pentru fluxuri in C++
C++ asigura suportul pentru sistemul sau de I/O in fisierul antet IOSTREAM.H. In acest fisier antet sunt definite doua ierarhii de clase care admit operatii de I/O. Clasa cu nivelul de abstractizare cel mai inalt se numeste streambuf si asigura operatiile de baza de intrare/iesire. Ca programatori, nu folositi streambuf direct decat daca veti deriva propriile clase pentru efectuarea operatiilor I/O. A doua ierarhie porneste cu clasa ios, care accepta operatii I/O formatate. Din ea sunt derivate clasele istream, ostream si iostream. Aceste clase sunt folosite pentru a crea fluxuri capabile sa citeasca, sa scrie, respectiv sa citeasca/ sa scrie date din/ la echipamentele externe. Clasa ios contine o serie de alte ramuri relativ la lucrul cu fisiere pe care nu ne propunem sa le studiem in cadrul acestui curs.
Fluxuri predefinite in C++
Cand isi incepe executia un program C++, se deschid automat patru fluxuri predefinite, pe care le prezentam in tabelul de mai jos.
Flux |
Semnificatie |
Echipament implicit |
cin |
Intrare standard |
Tastatura |
cout |
Iesire standard |
Ecran |
cerr |
Iesire standard pentru eroare |
Ecran |
clog |
Versiune cu memorie tampon pentru cerr |
Ecran |
Tabelul 15. Fluxurile predefinite C++
Fluxurile cin, cout, cerr corespund fluxurilor stdin, stdout, stderr din C. Implicit, fluxurile standard sunt folosite pentru a comunica cu consola. Insa, in mediile care admit redirectionarea I/O, fluxurile standard pot fi redirectionate spre alte echipamente sau fisiere.
I/O formatate in C++
Sistemul de I/O din C++ va permite sa formatati operatiile I/O, asa cum se intampla si in cazul utilizarii functiilor C pentru operatii I/O, precum: printf, cprintf, scanf,etc. De exemplu, se poate specifica marimea unui camp, baza unui numar, numarul de cifre dupa punctul zecimal,etc. Operatorii din C++ utilizati pentru introducerea informatiilor de formatare sunt >> si <<.
Exista doua cai inrudite, dar conceptual diferite, prin care se pot formata datele. In primul rand, putem avea acces direct la diferiti membri ai clasei ios. In al doilea rand, putem folosi functii speciale numite manipulatori, care pot fi incluse in expresii de I/O.
Prezentam, in continuare modul de utilizare a manipulatorilor de formate, datorita accesibilitatii mai mari a acestora.
Manipulatorii standard sunt prezentati in tabelul de mai jos.
Manipulator |
Exemplu de folosire |
Efect |
dec |
cout<<dec<<intvar; |
Converteste intregi in cifre zecimale; corespunde formatului %d din C |
endl |
cout<<endl |
Trimite o noua linie in ostream si descarca bufferul |
ends |
cout<<ends |
Insereaza un caracter nul intr-un flux |
flush |
cout<<flush |
Descarca bufferul fluxului ostream |
hex |
cout<<hex<<intvar; cin>>hex>>intvar |
Conversie hexazecimala corespunzatoare formatului %x din ANSI C |
oct |
cout<<oct<<intvar; cin>>oct>>intvar; |
Conversie octala (formatul %o din C) |
resetiosflags(long f) |
cout<<resetioflags(ios::dec); |
Reinitializeaza bitii de formatare specificati de argumentul intreg de tip long |
setbase(int baza) |
cout<<setbase(10); cin>>setbase(8); |
Stabileste baza de conversie la argumentul intreg (trebuie sa fie 0,8,10 sau 16). Valoarea 0 este baza implicita. |
setfill(int ch) |
cout<<setfill('.'); cin>>setfill(' '); |
Stabileste caracterul folosit pentru completarea campurilor de marime specificata |
setiosflags(long f) |
cout<<setiosflags(ios::dec); cin>> setiosflags(ios::hex); |
Stabileste bitii de formatare specificati de argumentul intreg de tip long |
setprecision(int p) |
cout<<setprecision(6); cin>>setprecision(10); |
Stabileste precizia conversiei in virgula mobila la numarul specificat de zecimale |
setw(int w) |
cout<<setw(6)<<var; cin>>setw(24)>>buf |
Stabileste marimea unui camp la numarul specificat de caractere |
ws |
cin ws; |
Elimina spatiile libere din fluxul de intrare |
Tabelul 16. Manipulatori de formatare a operatiilor I/O in C++
Toti acesti manipulatori au prototipul in fisierul antet iomanip.h.
Pentru a utiliza manipulatorii setiosflags si resetiosflags trebuie cunoscuti indicatorii de formatare din tabelul de mai jos.
Nume indicator |
Ce efect are utilizarea indicatorului |
ios :: skipws |
Elimina spatiile goale din intrare |
ios :: left |
Aliniaza iesirea la stanga in interiorul latimii campului |
ios :: right |
Aliniaza iesirea la dreapta |
ios :: scientific |
Foloseste notatia stiintifica pentru numerele in virgula mobila. |
ios :: fixed |
Foloseste notatia zecimala pentru numere in virgula mobila |
ios :: dec |
Foloseste notatia zecimala pentru intregi |
ios :: hex |
Foloseste notatia hexazecimala pentru intregi |
ios :: oct |
Foloseste notatia octala pentru intregi |
ios :: uppercase |
Foloseste litere mari pentru iesire |
ios :: showbase |
Indica baza sistemului de numeratie in cadrul iesirii (prefixul 0x pentru hexazecimal si prefixul 0 pentru octal |
ios :: showpoint |
Include un punct zecimal pentru iesiri in virgula mobila |
ios :: showpos |
Include si semnul + la afisarea valorilor pozitive |
ios :: unitbuf |
Goleste toate fluxurile dupa inserarea caracterlor intr-un flux |
Tabelul 16. Indicatori de formatare a operatiilor I/O in C++
Notatia ios :: <Nume_indicator> este folosita pentru a identifica indicatorul ca pe un membru al clasei ios.
Exemplificam cele spuse mai sus prin codul C++ de mai jos.
#include<iostream.h>
#include<iomanip.h>
#include<conio.h>
#include<stdio.h>
void main
Fisiere utilizator in C++
Chiar daca abordarea operatiilor I/O din C++ formeaza un sistem integrat, operatiile cu fisiere (altele decat cele predefinite), sunt suficient de specializate pentru a fi necesar sa la discutam separat.
Pentru a efectua operatii I/O cu fisiere conform paradigmei C++, trebuie sa includeti in programul Dvs. fisierul antet FSTREAM.H. Acesta defineste mai multe clase, printre care ifstream, ofstream si fstream. Aceste clase sunt derivate din istream si, respectiv, din ostream la care ne-am referit si mai sus.
Deschiderea si inchiderea unui fisier
Un fisier se deschide in C++ legandu-l de un flux. Inainte de a putea sa deschideti un fisier, trebuie, pentru inceput, sa aveti un flux. Exista trei tipuri de fluxuri: de intrare, de iesire si de intrare/iesire. Pentru a crea un flux de intrare, trebuie sa-l declarati ca fiind din clasa ifstream. Pentru a crea un flux de iesire, trebuie sa-l declarati ca fiind din clasa ofstream. Fluxurile care efectuiaza atat operatii de intrare cat si operatii de iesire, trebuie declarate ca fiind din clasa fstream. Odata declarat fluxul, o modalitate de a-i asocia un fisier extern o reprezinta utilizarea functiei open() avand prototipul:
void open(const char *nume_fisier , int mod, int acces=filebuf::openprot);
nume_fisier este un nume extern de fisier care poate include si specificarea caii de acces.
Valoarea parametrului mod determina modul de deschidere a fisierului. Parametrul mod poate avea una sau mai multe din valorile prezentate in tabelul de mai jos.
Nume mod |
Operatie |
ios::app |
Adauga date in fisier |
ios::ate |
Cand se deschide pentru prima data, opereaza pozitionarea in fisier la sfarsitul fisierului (ate inseamna la sfarsit) |
ios::binary |
Deschide fisierul in mod binar, inhiband interpretarea caracterelor <CR> <LF> |
ios::in |
Deschide fisierul pentru citire |
ios::nocreate |
Nu efectueaza deschiderea fisierului daca acesta nu exista deja |
ios::noreplace |
Daca fisierul exista, incercarea de a-l deschide pentru iesire esueaza, cu exceptia cazului in care ios::app sau ios::ate sunt operate |
ios::out |
Deschide fisierul pentru scriere |
ios:trunc |
Trunchiaza fisierul daca el exista deja |
Tabelul 17. Valorile parametrului care stabileste modul de deschidere a unui fisier
Puteti specifica mai mult de un mod de lucru pentru un fisier, folosind operatorul pe biti SAU cu modurile respective. De exemplu, pentru deschiderea unui fisier pentru iesire si pozitionarea pointerului la sfarsitul lui se folosesc modurile ios::out si ios::ate astfel:
ofstream oflux("o_fisier",ios::out | ios::ate);
ceea ce ne arata al doilea procedeu de deschidere a unui fisier, utilizand constructorul clasei ofstream sau, de ce nu, ifstream, daca este cazul.
Pentru a inchide un fisier, folositi functia membru close(). Aceasta functie nu preia nici un parametru si nu returneaza nici o valoare. De analizat utilizarea functiei close() in exemplele care vor urma.
Scrierea si citirea fisierelor text
Sunt doua operatii foarte usoare, realizate apeland la operatorii >> si << intr-un mod asemanator operatiilor referitoare la consola sistemului, cu deosebirea ca in loc sa folositi cin si cout apelati la un flux legat de un fisier . Codul de mai jos arata cum poate fi afisat pe ecranul monitorului continutul unui fisier text.
#include <fstream.h>
#include <stdlib.h>
#include<conio.h>
//Functia principala a programului citeste linia de comanda a programului
void main(int argc,char *argv[])
// Deschidere fisier text de nume specificat in argv[1]
ifstream in(argv[1],ios::in);
if (!in)
char c;
clrscr();
while (in.get(c))
cout<<c;
in.close();
I/O in fisiere de tip binar
Exista doua modalitati de a scrie si citi date binare intr-un fisier. Prima modalitate se refera la utilizarea functiilor get() si put(). Aceste functii sunt orientate pe octeti, ceea ce inseamna ca get() va citi un octet de date iar put() va scrie un octet de date.
Functia get() are mai multe forme; o prezentam, in continuare, impreuna cu omoloaga ei put(), pe cea mai des folosita:
istream &get(char &ch);
ostream &put(char ch);
Functia get() citeste un singur caracter din streamul asociat si memoreaza valoarea sa in ch. De asemenea, se mai observa ca functia returneaza o referinta catre flux. Functia put() scrie ch in flux si returneaza fluxului o referinta.
Ca un exemplu de utilizare a functiilor get() si put() prezentam codul de mai jos, care realizeaza copierea unui fisier in altul. Daca numele executabilului asociat acestui cod este copyf, atunci sintaxa de lansare in executie a programului este:
copyf <fisier_sursa> <fisier_destinatie>
#include<stdlib.h>
#include <fstream.h>
void main(int argc, char *argv)
//Deschide fisierul de intrare si il conecteaza la fluxul ins
ifstream ins(argv[1]);
if(!ins)
//Deschide fisierul de iesire si il conecteaza la fluxul outs
ofstream outs(argv[2]);
if(!outs)
//Citeste din sursa si scrie in destinatie
char c;
while(ins.get(c) && outs) outs.put(c);
A doua modalitate de a citi si scrie blocuri de date in binar este folosirea functiilor din C++ read() si write(). Prototipurile lor sunt:
istream &read(unsigned char *buf, int numar);
ostream &write(const unsigned char *buf, int numar);
Functia read() citeste numar octeti din fluxul asociat si il pune in buffer-ul indicat de buf . Functia write() scrie in fluxul asociat numar octeti cititi din buffer-ul spre care indica buf.
Detectarea EOF
Puteti sa detectati sfarsitul fisierului folosind functia membru eof() ,care are acest prototip:
int eof();
Ea returneaza o valoare nenula cand a fost atins sfarsitul fisierului; altfel, returneaza zero. Utilizarea functiei eof() si alte elemente legate de lucrul cu fisiere, prezentam in codul de mai jos, care realizeaza afisarea continutului unui fisier atat in hexazecimal cat si in cod ASCII, atunci cand codul asociat este printabil.
#include <fstream.h>
#include <ctype.h>
#include <iomanip.h>
#include <stdlib.h>
#include<conio.h>
void main(int argc,char *argv[])
ifstream in(argv[1],ios::in|ios::binary);
if (!in)
register int i,j;
int count=0;
char c[16];
cout.setf(ios::uppercase);
clrscr();
while(!in.eof())
if (i<16) i--;
for(j=0;j<i;j++)
cout<<setw(3)<<hex<<(int) c[j];
for(;j<16;j++)
cout<<'t';
for(j=0;j<i;j++)
if(isprint(c[j])) cout<<c[j];
else cout<<'.';
cout<<endl;
count++;
if(count==16)
in.close();
Accesul aleator in fisiere
In sistemul de I/O din C++, accesul aleator se efectueaza folosind functiile
seekg() si seekp(). Formele lor cele mai uzuale sunt:
istream &seekg(streamoff offset, seek_dir origine);
ostream &seekp(streamoff offset, seek_dir origine);
streamoff este un tip definit in IOSTREAM.H, capabil sa contina cea mai mare valoare valida pe care o poate avea offset, iar seek_dir este o enumerare care are aceste valori:
ios::beg
ios::cur
ios::end
Sistemul de I/O din C++ opereaza cu doi pointeri asociati unui fisier. Unul este pointerul de get , care specifica unde va aparea urmatoarea operatie de intrare in fisier. Celalalt este pointerul de put si specifica unde va avea loc urmatoarea operatie de iesire. Dupa fiecare operatie de intrare sau iesire, pointerul corespunzator este avansat automat, secvential. Dar, folosirea functiilor seekg() si seekp() permite un acces nesecvential la datele din fisier.
seekg() si seekp() deplaseaza pointerul de inregistrare cu offset octeti fata de origine.
Cu mentiunea ca lucrul cu fisiere in C++ are nenumarate alte fatete pentru a caror prezentare nu dispunem de timpul si spatiul necesar, incheiem aceasta scurta excursie in problematica fisierelor. In fine, pentru curiosi facem si precizarea ca, insusi batranul C are propria filozofie, extrem de puternica, in ceea ce priveste lucrul cu fisiere.
3 Programare generica in C++
In programarea profesionala apar nenumarate situatii in care reutilizarea codului presupune o solutie de un anumit tip pentru o problema data. Situatia la care ne referim in aceasta sectiune este, potential vorbind, urmatoarea: Ce putem face pentru a comprima codul sursa in situatia in care structuri de date, diferite ca tip, suporta prelucrari similare.
Solutia acestei probleme de stil de programare o reprezinta programarea generica. Exprimandu-ne in termenii limbajului C, o functie generica defineste un set general de operatii care vor fi aplicate unor tipuri de date diferite.
Ca un exemplu, o solutie generica pentru modelarea unei stive este un pretext ideal pentru precizarea ideilor principale ale programarii generice.
Altfel spus, daca dorim o stiva, in care, de la caz la caz, sa putem pastra numere intregi, numere reale sau siruri de caractere (deci tipuri de date diferite), operatiile fiind aceleasi ( push() si pop() ), este clar ca ne aflam in situatia in care avem nevoie de suport pentru scrierea de cod cu proprietati generice.
Daca in Pascal programarea generica se baza pe tipuri procedurale si programarea la nivel de octet, in C++ exista suport evoluat pentru programare generica, sub forma sabloanelor. Cu un sablon, in C++ se poate crea o functie generica sau o clasa generica.
Functii TEMPLATE
O functie template este o functie sablon, avand unul sau mai multi parametri formali de un tip generic. In functie de nevoile de utilizare a acestei functii, compilatorul genereaza functii propriu-zise, inlocuind tipul generic cu un tip concret. Tipul concret poate fi orice tip fundamental, derivat sau clasa predefinita.
Consideram un exemplu. Fie functia max(x,y) care returneaza valoarea maxima a argumentelor sale. Tipul variabilelor x si y trebuie, obligatoriu, specificat in momentul compilarii. Solutia clasica consta in redefinirea (over-loading) functiei max pentru fiecare tip al argumentelor x si y (de observat si cele spuse la paragraful 2.2 relativ la functii supraincarcate in C++).
Trebuie, asadar, sa definim mai multe versiuni ale functiei max.
int max(int x, int y)
float max(float x, float y)
Mecanismul template permite definirea o singura data a sablonului de functii, dupa care se genereaza automat functiile propriu-zise in concordanta cu necesitatile de utilizare, dar ,evident, in faza de compilare.
Sintaxa la specificare este:
template <class Nume_tip_generic_1 [,.class Nume_tip_generic_n]>
Nume_sablon
definitie_sablon
De precizat urmatoarele:
YCaracterele < si > fac parte din sintaxa obligatorie.
YLista de parametri formali ai unei functii sablon trebuie sa utilizeze toate tipurile de date generice.
YIn cazul functiilor template nu se fac conversii
YFunctia care are acelasi nume si acelasi numar de parametri cu o functie sablon se numeste caz exceptat (Supraincarcarea explicita este prioritara).
Sintaxa la utilizare este:
Nume sablon (Expresie_1[, .,Expresie_n]);
Prezentam, in continuare, definitia functiei sablon max , urmata de o secventa client de utilizare.
template <class T>
T max(T x, T y)
#include <conio.h>
#include<iostream.h>
//Definire sablon functie
template<class T>
T max(T x,T y)
void main
Prezentam, totodata, un exemplu de functie generica pentru compararea unor date dupa valoarea unei chei incapsulate in aceste date.
#include <conio.h>
#include <stdio.h>
#include <string.h>
#include <iostream.h>
#include <ctype.h>
//Definirea unei <functii generice> pentru compararea
//unor date dupa valoarea unei chei incapsulate
//in aceste date
template <class T>
int comp(T i1,T i2)
//Structura aleasa pentru exemplificare
//cheia generica incapsulata este campul <key>
struct tpers
//Instantiere <struct tpers>
struct tpers tam,pers[50];
void main
//Listare persoane pe ecranul monitorului
//in ordinea citirii de la tastatura
while(toupper(getch())!='N');
clrscr();
cout<<'Listare persoane in ordinea citirii de la tastatura'<<endl;
cout<<'__________ ______ ____ ________________'<<endl;
for(int j=0;j<i;j++)
cout.width(30);cout.setf(ios::left);
cout<<pers[j].np<<' '<<pers[j].key<<endl;
getch();
//Sortare persoane
int sortat;
do
};
};
while(!sortat);
//Listare persoane dupa sortare in ordinea
//crescatoare a matricolelor
clrscr();
cout<<'Listare persoane dupa sortare.'<<endl;
cout<<'__________ ______ ____ _____________'<<endl;
for(int k=0;k<i;k++)
cout.width(30);cout.setf(ios::left);
cout<<pers[k].np<<' '<<pers[k].key<<endl;
getch();
Clase TEMPLATE
O clasa template defineste un sablon pe baza caruia se pot genera clase propriu-zise. Din acest motiv, o clasa template se mai numeste si clasa generica , clasa generator sau metaclasa.
AAstfel ca, o clasa template devine o clasa de clase, reprezentand cel mai inalt nivel de abstractizare admis de programarea obiect orientata
In cadrul clasei sablon se pot declara atribute informationale de un tip ambiguu, care sunt particularizate in cadrul clasei generata pe baza sablonului. Evident, si in acest caz, generarea se face in faza de compilare in concordanta cu cerintele clientului.
Sintaxa la specificarea clasei:
template <class T>
class nume_clasa
;
Sintaxa la implementarea functiilor membre ale unei clase template:
template <class T> Tip returnat nume_clasa <T>::nume_functie(.)
;
Pentru mai multa claritate prezentam si exemplul de mai jos.
#include <iostream.h>
#include <stdlib.h>
#include <conio.h>
const int SIZE = 10;
//Definire clasa matrice generica
template <class ATip>
class genmat
//Implementare constructor clasa generica
template <class ATip> genmat<ATip>::genmat()
//Implementare supraincarcare operator [ ]
template <class ATip> ATip &genmat<ATip>::operator[ ](int i)
return a[i];
//Functia principala
void main
Tot pentru exemplificare sa consideram si o situatie deosebit de simpla. Ni se cere sa construim o clasa template corespunzatoare conceptului de stiva, din care, ulterior, sa se poata concretiza clase care simuleaza stiva pentru tipuri de date diferite.
//Clasa sablon CSTack
template <Class T>
class Cstack
~Cstack()
void push(T a)
T pop()
Dupa ce o astfel de clasa template a fost declarata si definita, se poate trece deja la instantierea de obiecte. Singura deosebire fata de folosirea unei clase obisnuite consta in faptul ca trebuie specificat tipul concret care inlocuieste tipul generic T. Ca un exemplu, sa instantiem un obiect stiva (CStack) , in care incap maximum 100 elemente de tip char.
CStack<char> sc
In acest caz, compilatorul genereaza clasa ce rezulta prin inlocuirea lui T cu char, dupa care instantiaza obiectul sc. Constructorul acestuia primeste ca argument valoarea 100. Pentru mai multe detalii urmariti exemplul de mai jos.
#include <iostream.h>
#include <conio.h>
// Definirea clasei stack. Se vede ca instantele ei nu sunt protejate fata de //exceptii.
Despre exceptii urmeaza sa discutam.
template <class T>
class CStack
~CStack() //destructor
void push(T a) //Functia de inserare in stiva
T pop()
//Functia principala
void main
//Vizualizare continut stiva
clrscr();
for (i=0;i<=9;i++)
getch();
//Al doilea exemplu de instantiere a stivei generice; caractere, incepand cu "A"
CStack<char> st2(20);
//Incarcare stiva
for (i=65;i<75;i++)
//Vizualizare continut stiva
clrscr();
for (i=0;i<10;i++)
getch();
Tratarea exceptiilor
Programatorii adevarati trebuie sa ia in calcul si posibilitatea de a crea programe robuste, care fac fata atat cerintelor specificate dar nerafinate suficient, cat si cerintelor nespecificate dar formulate de utilizator, din diverse motive. Programele care au aceste calitati se numesc robuste.
In programarea clasica solutia acestei probleme se putea numi, destul de exact spus, programare defensiva. Seamana putin cu conducerea preventiva din soferie daca ne gandim ca programand defensiv, in fond punem raul inainte, deci nu ne bazam pe cumsecadenia si buna pregatire a utilizatorului.
Pentru a face fata
cerintelor legate de problema tratarii exceptiilor (asa se
numesc in jargon profesional erorile care apar in timpul executiei
programelor) anumite limbaje de programare ofera suport adecvat. As
include aici limbaje precum
Nu toate compilatoarele de C++ ofera suport, dar standardul ANSI C++ cere acest lucru in mod explicit. Compilatoarele din familia Borland incepand cu versiunea 4.0 ofera acest suport.
Esentialul din punctul de vedere al programatorului C++ este ca el sa-si formeze abilitatea de a scrie in jurul aplicatiilor cod C++ care indeplineste functia de handler de exceptii.
Suportul sintactic C++ pentru tratarea exceptiilor se rezuma la trei cuvinte cheie, a caror semantica preliminara o prezentam in Tabelul xxxxx.
Cuvantul cheie |
Semnificatie |
try |
Delimiteaza o portiune de cod in care se instituie controlul sistemului asupra exceptiilor in timpul rularii. |
throw |
Lanseaza o exceptie de un anumit tip |
catch |
Capteaza o exceptie lansata |
Tabelul 18 Cuvintele cheie ale limbajului C++ referitoare la tratarea exceptiilor
Forma de baza a tratarii exceptiilor
Asadar, atunci cand programele dumneavoastra efectueaza prelucrarea exceptiilor, trebuie sa includeti in cadrul unui bloc try instructiunile pe care doriti sa le monitorizati in vederea unei exceptii. Daca executia unei instructiuni se termina cu o eroare, trebuie sa lansati o eroare corespunzatoare actiunii functiei in care se afla instructiunea. Programul plaseaza instructiunea throw in cadrul blocului try-catch. Forma generalizata a blocului care capteaza si trateaza erorile este:
try
catch (Tip_exceptie Nume_variabila )
In cadrul acestei forme generalizate, valoarea valoare_exceptie lansata trebuie sa corespunda tipului Tip_exceptie .
Scrierea unui handler de exceptii simplu
Pentru a intelege mai bine semantica unui handler de exceptii, studiati programul de mai jos.
#include <iostream.h>
void main()
catch (int i)
cout<<'Sfarsit.';
Programul de mai sus implementeaza un bloc try-catch simplu. In loc sa se astepte ca programul sa esueze datorita unei erori, se utilizeaza instructiunea throw pentru lansarea erorii prezumtive. Dupa ce blocul try lanseaza eroarea, blocul catch o capteaza si prelucreaza valoarea transmisa de instructiunea throw. Este evident si din acest exemplu ca mecanismul try-throw-catch ofera suport pentru rezolvarea problemei tratarii exceptiilor dar nu rezolva de la sine aceasta problema. Altfel spus, tratarea corecta a exceptiilor unui program este o problema de atitudine ca proiectant si ca programator.
Lansarea exceptiilor cu o functie din cadrul blocului try
Atunci cand programele apeleaza functii din cadrul blocurilor try , C++ va transmite exceptia aparuta intr-o astfel de functie in afara functiei daca nu exista un bloc try in interiorul functiei.
Exemplul de mai jos arata cum se petrec lucrurile intr-o astfel de situatie.
#include <iostream>
void XHandler(int test)
void main()
catch(int i)
cout<<'Sfarsit';
Plasarea unui bloc try intr-o functie
Am vazut cum apare un bloc try in functia principala a unui program. C++ permite blocuri try si in alte functii ale unui program diferite de functia principala.
Atunci cand se plaseaza un bloc try intr-o functie C++ reinitializeaza blocul de fiecare data cand intrati se intra in acea functie. Programul urmator ilustreaza cele spuse.
#include <iostream.h>
void XHandler(int test)
catch(int i)
void main()
Un comentariu pe marginea celor prezentate pana acum ar fi urmatorul: o instructiune catch se executa numai daca programul lanseaza o exceptie in cadrul blocului try situat imediat inainte. In caz ca o astfel de exceptie nu se lanseaza blocul catch va fi ignorat.
Utilizarea mai multor instructiuni catch cu un singur bloc try
Pe masura ce tratarile exceptiilor devin tot mai complexe, uneori este necesar si posibil ca un singur bloc try sa lanseze exceptii de mai multe tipuri. In cadrul programelor dumneavoastra puteti construi un handler de exceptii astfel incat sa accepte captarea mai multor exceptii. Intr-o astfel de situatie sintaxa generala este:
try
catch (tip1)
catch(tip2)
catch(tipn)
Cu acest amendament sintactic deducem ca instructiunile catch pot capta orice tip returnat, nu numai tipurile de baza acceptate de C++. Acest 'fenomen' este ilustrat in codul de mai jos.
#include <iostream.h>
void XHandler(int test)
catch(int i)
catch(char *sir)
catch(double d)
void main()
Blocuri catch generice (utilizarea operatorului puncte de suspensie)
Programele scrise de dumneavoastra pot capta exceptii din cadrul mai multor blocuri try (de exemplu un bloc try care incapsuleaza mai multe functii care lanseaza exceptii diferite din blocuri try diferite sau sa utilizeze mai multe instructiuni catch intr-un singur bloc try. C++ permite, de asemenea, utilizarea operatorului puncte de suspensie (.) pentru a capta orice tip de eroare care apare intr-un singur bloc try. Sintaxa care permite captarea tuturor erorilor care apar intr-un bloc try este prezentata mai jos.
try
catch(.)
Pentru exemplificare propun codul de mai jos.
#include <iostream.h>
void XHandler(int test)
catch(.)
void main()
Evident, prelucrarile din cadrul blocului catch generic trebuie sa fie independente de tipul erorii.
Mecanismul captarii exceptiilor explicite poate fi combinat cu mecanismul exceptiilor generice ca in exemplul de mai jos.
#include <iostream.h>
void XHandler(int test)
catch(int i)
catch(.)
void main()
Restrictionarea exceptiilor
Pe masura ce programele dumneavoastra devin mai complexe, ele vor apela frecvent functii din cadrul unui bloc try. Atunci cand programele dumneavoastra apeleaza functii dintr-un bloc try, puteti restrictiona tipurile de exceptii pe care functia apelata le poate lansa. De asemenea puteti preveni lansarea oricarei exceptii dintr-o anumita functie.
Sintaxa pentru restrictionare este:
tip_returnat nume_functie(lista_arg) throw(lista_tipuri )
Sintaxa care inhiba lansare oricarei exceptii este:
tip_returnat nume_functie(lista_arg) throw()
Este bine sa subliniem ca atunci cand declarati o functie cu clauza throw ea poate sa lanseze doar acele tipuri precizate in lista. Daca functia lanseaza orice al tip programul este abortat.
Un exemplu in continuare.
#include <iostream.h>
void XHandler(int test) throw(int, char, double)
void main()
catch(int i)
catch(char c)
catch(double d)
cout<<'Sfarsit';
Relansarea unei exceptii
In anumite situatii poate fi necesar sa se relanseze o exceptie din interiorul unui handler de exceptii. Daca relansati o exceptie, C++ o va transmite unui bloc try exterior daca acesta exista. Cea mai probabila situatie in care puteti opta pentru aceasta varianta este atunci cand doriti sa tratati o exceptie in cadrul a doua programe handler distincte. Pentru mai multa claritate urmariti exemplul de mai jos.
#include <iostream.h>
void XHandler(void)
catch(char *)
void main()
catch(char *)
cout<<'Sfarsit.';
Mod de utilizare a exceptiilor
Toate elementele prezentate au incercat sa demonstreze ca C++ are o atitudine activa fata de problema tratarii exceptiilor. Suportul oferit de C++ il ajuta pe programator sa defineasca un comportament al programului cand se produc evenimente anormale sau neasteptate. O idee mai pragmatica de utilizare a suportului C++ in situatii efective o puteti desprinde din exemplul de mai jos.
#include <iostream.h>
void div (double a, double b)
catch(double b)
void main()
while (i!=0);
Politica de confidentialitate | Termeni si conditii de utilizare |
Vizualizari: 1505
Importanta:
Termeni si conditii de utilizare | Contact
© SCRIGROUP 2024 . All rights reserved