Sunteți pe pagina 1din 111

Adrian DEACONU

2008 - 2009
REPROGRAFIA UNIVERSITII "TRANSILVANIA" DIN BRAOV
2


Cuvnt nainte



Cartea de fa se dorete a fi, n principal, un ghid pentru studenii din domeniul
Informatic, dar, evident, ea poate fi util tuturor celor care vor s nvee s programeze
orientat pe obiecte, n general i n C++, n particular. Este bine ca cel care citete aceast
lucrare s nu fie nceptor n programare i, mai mult, este trebuie s aib cunotiinte avansate
despre limbajul C. Anumite concepte generale cum ar fi constante, variabile, funcii, tipuri
numerice, caractere, string-uri, pointeri, tablouri etc. se consider cunoscute.
Lucrarea este structurat pe dou pri.
n prima parte se prezint elementele introduse odat cu apariia limbajului C++, care
nu existau n C i care nu au neaparat legatur cu programarea orientat pe obiecte.
n partea a doua este facut o prezentare teoretic a programarii orientate pe obiecte,
introducand i conceptele POO. Dup aceast scurt prezentare pur teoretic se prezint
programarea orientat pe obiecte din C++. Tot aici sunt prezentate i o parte din clasele care
se instaleaz odat cu mediul de programare (clase pentru lucrul cu fluxuri, clasa complex etc.).
La fiecare capitol, sunt date exemple sugestive, care ilustreaz din punct de vedere
practic elemente de noutate. Este bine ca aceste exemple s fie nelese i, acolo unde este
nevoie, s fie scrise i rulate de ctre cititor. Programele din aceast carte nu conin erori,
deoarece ele au fost nti testate i abia apoi introduse n lucrare.
n general, tot ceea ce este prezentat n aceast carte (teorie i aplicatii) este recunoscut
att de compilatorul C++ al firmei Borland, ct i de compilatorul Visual C++ al companiei
Microsoft.



Autorul.
3
CUPRINS

PARTEA NTI

1.1. Ce este limbajul C++ ? ................................................................................5
1.2. Elemente introduse de C++ .........................................................................5
1.3. Declaraia variabilelor n C++ ....................................................................6
1.4. Fluxuri standard n C++ .............................................................................7
1.5. Manipulatori .................................................................................................7
1.6. Indicatori de formatare ...............................................................................8
1.7. Alocarea dinamic a memoriei n C++ ....................................................10
1.8. Funcii n C++ .............................................................................................15
1.8.1. Funcii cu acelai nume i cu parametrii diferii ................................................. 15
1.8.2. Transmiterea parametrilor prin referin ........................................................... 16
1.8.3. Valori implicite pentru parametrii funciilor ...................................................... 18
1.8.4. Funcii inline ........................................................................................................... 19
1.8.5. Funcii ablon (template) ....................................................................................... 20
1.9. Suprancrcarea operatorilor ...................................................................22
1.10. Tratarea excepiilor .................................................................................28

PARTEA A DOUA

2.1. Prezentare teoretic a P.O.O. ...................................................................33
2.2. Programarea orientat pe obiecte n C++ ...............................................35
2.2.1. Neajunsuri ale POO n C ....................................................................................... 35
2.2.2. Declaraia unei clase n C++ .................................................................................. 36
2.2.3. Declaraia i descrierea funciilor membre .......................................................... 36
2.2.4. Constructori ............................................................................................................ 38
2.2.5. Destructori ............................................................................................................... 38
2.2.6. Funcii prietene (friend) unei clase ........................................................................ 40
2.2.7. Declaraia, descrierea operatorilor pt. o clas...................................................... 42
2.2.8. Membri statici ......................................................................................................... 44
2.2.9. Pointerul this ........................................................................................................... 45
2.2.10. Constructorul de copiere ...................................................................................... 49
2.2.11. Motenirea n C++ ................................................................................................ 51
2.2.12. Funcii virtuale ...................................................................................................... 53
2.2.13. Destructori virtuali ............................................................................................... 55
2.2.14. Funcii pur virtuale .............................................................................................. 56
2.2.15. Motenire multipl ............................................................................................... 60
4
2.2.16. Clase virtuale .........................................................................................................60
2.2.17. Constructori pentru clase virtuale .......................................................................62
2.2.18. Clase imbricate ......................................................................................................66
2.2.19. Clase ablon (template) ..........................................................................................67
2.3. Fluxuri n C++ ........................................................................................... 71
2.3.1. Ierarhia streambuf ...................................................................................................71
2.3.2. Ierarhia ios ...............................................................................................................72
2.4. Fiiere n C++ ............................................................................................. 76
2.5. Prelucrarea string-urilor n C++ ............................................................. 91
2.6. Clasa complex din C++ ............................................................................. 94


Indicaii i rspunsuri ...................................................................................... 96
ANEXE .............................................................................................................. 98
BIBLIOGRAFIE ............................................................................................ 104






5
PARTEA NTI

Obiective

n aceasta prim parte ne propunem s vedem ce este limbajul C++ i s studiem
elementele introduse de C++, care nu au neaparat legatur cu programarea orientat pe obiecte.

1.1. Ce este limbajul C++ ?

Limbajul C++ este o extensie a limbajului C. Aproape tot ce ine de limbajul C este
recunoscut i de ctre compilatorul C++. Limbajul C++ a aprut ca o necesitate, n sensul c el a
adus completri limbajului C care elimin cteva neajunsuri mari ale acestuia. Cel mai important
neajuns al limbajului C este lipsa posibilitii de a scrie cod orientat pe obiecte n adevratul sens
al cuvantului. n C se poate scrie ntr-o manier rudimentar cod orientat pe obiecte folosind
tipul struct, n interiorul cruia putem avea att cmpuri, ct i metode. Orientarea pe obiecte cu
tipul struct are cteva mari lipsuri: membrii si se comport toi ca nite membri publici (accesul
la ei nu poate fi restrictionat), nu avem constructori, destructori, motenire etc.
Limbajul C a fost lansat n anul 1978 i s-a bucurat nc de la nceput de un real succes.
Acest lucru s-a datorat uurinei cu care un programator avansat putea scrie programe n
comparaie cu restul limbajelor ce existau atunci pe pia, datorit n special modului abstract i
laconic n care se scrie cod. De asemenea, modul de lucru cu memoria, cu fiiere este mult mai
transparent. Acest lucru are ca mare avantaj viteza crescut de execuie a aplicaiilor, dar poate
foarte uor conduce (mai ales pentru ncepatori) la erori greu de detectat, datorate clcrii n
afara zonei de memorie alocate.
La sfritul anilor 80 a aprut limbajul C++ ca o extensie a limbajului C. C++ preia
facilitile oferite de limbajul C i aduce elemente noi, dintre care cel mai important este
noiunea de clas, cu ajutorul creia se poate scrie cod orientat pe obiecte n toat puterea
cuvantului. Limbajul C++ ofer posibilitatea scrierii de funcii i clase ablon, permite
redefinirea (suprancarcarea) operatorilor i pentru alte tipuri de date dect pentru cele care exist
deja definii, ceea ce ofer programatorului posibilitatea scrierii codului ntr-o manier mult mai
elegant, mai rapid i mai eficient.
n anul 1990 este finalizat standardul ANSI-C, care a constituit baza elaborrii de ctre
firma Borland a diferitelor versiuni de medii de programare.
n prezent sunt utilizate ntr-o mare msur limbajele Java (al crui compilator este
realizat firma Sun) i Visual C++ (care face parte din pachetul Visual Studio al firmei
Microsoft), care au la baza tot standardul ANSI-C. Exist ns i competitori pe masur. Este
vorba n prezent n special de limbajele ce au la baz platforma .NET - alternativa Microsoft
pentru maina virtual Java. Poate cel mai puternic limbaj de programare din prezent este C#
(creat special pentru platforma .NET). Limbajul C# seamn cu C/C++, dar totui el nu este
considerat ca facnd parte din standardul ANSI-C.
n C++ se poate programa orientat pe obiecte inndu-se cont de toate conceptele:
abstractizare, motenire, polimorfism etc.
Odat cu mediul de programare al limbajului C++ (fie el produs de firma Borland sau de
firma Microsoft) se instaleaz i puternice ierarhii de clase, pe care programatorul le poate folosi,
particulariza sau mbogi.

1.2. Elemente introduse de C++

n acest capitol vom face o scurt enumerare a elementelor introduse de C++, care nu se
gseau n limbajul C.
6
Lista celor mai importante nouti aduse de limbajul C++ este:

- tipul class cu ajutorul cruia se poate scrie cod adevrat orientat pe obiecte
- posibilitatea declarrii variabilelor aproape oriunde n program
- o puternic ierarhie de clase pentru fluxuri
- alocarea i eliberarea dinamic a memoriei cu ajutorul operatorilor new i delete
- posibilitatea de a scrie mai multe funcii cu acelai nume dar cu parametrii diferii
- valori implicite pentru parametrii funciilor
- funciile inline
- funciile i clasele ablon (suport adevrat pentru programare generic)
- tratarea excepiilor stil modern (folosind instruciunea try catch)
- suprancarcarea operatorilor
- clasa complex
etc.

Aceste elemente introduse de C++ vor fi prezentate pe larg n cele ce urmeaz.

1.3. Declaraia variabilelor n C++

n C variabilele locale trebuie s fie declarate pe primele linii ale corpului funciei
(inclusiv n funcia principal). n C++ declaraiile variabilelor pot fi fcute aproape oriunde n
program. Ele vor fi cunoscute n corpul funciei din locul n care au fost declarate n jos.
Declaraiile de variabile pot aprea chiar i n interiorul instruciunii for. Iat un exemplu n acest
sens:

int n=10,a[10];
for (int s=0,i=0;i<n;i++)
s+=a[i];
float ma=s/n;

Ce se ntmpl ns dac declarm o variabil n corpul unei instruciuni de decizie,
compus sau repetitiv? Este posibil ca grupul de instruciuni ce alctuiete corpul s nu se
execute niciodat. Pentru a se rezolva aceast problem orice declaraie de variabil n interiorul
unui grup de instruciuni delimitat de acolade sau din corpul unei instruciuni este cunoscut
numai n interiorul grupului, respectiv corpului. Varibilele declarate n interiorul blocurilor intr
n stiva de execuie a programului de unde ies la terminarea execuiei blocului.
Dm un exemplu:

for (int i=0;i<n;i++)
for (int j=0;j<n;j++)
{
int k=0;
...
}
cout<<i; // corect, variabila i exista
cout<<j; // incorect, variabila j nu mai exista
cout<<k; // incorect, variabila k nu mai exista

Spre deosebire de C++, n Java nici variabila i din exemplul de mai sus nu ar fi fost
cunoscut dup ce se iese din instruciunea for.
7

1.4. Fluxuri standard n C++

Pentru a lucra cu fluxuri n C++ exist o puternic ierarhie de clase. Pentru a folosi
facilitaile C++ de lucru cu fluxuri este necesar i n general suficient s se includ fiierul antet
iostream.h, care reprezint o alternativ la ceea ce oferea fiierul antet stdio.h n C.
Pentru extragerea de date dintr-un flux n C++ n modul text se folosete n general
operatorul >>, iar penntru introducerea de date ntr-un flux n modul text folosim operatorul <<.
Aadar, celor doi operatori care erau folosii n C numai pentru shift-area biilor unei valori
ntregi, n C++ li s-a dat o nou semnificaie.
n C++ avem obiectul cin care corespunde n C fluxului standard de intrare stdin
(tastatura). De exemplu, pentru citirea de la tastatur a dou variabile procedm astfel:

int n;
float x;
cin>>n>>x;

Instruciunea de mai sus are urmtoarea semnificaie: din fluxul standard de intrare se
extrag dou valori (una ntreag i apoi una real). Cele dou valori se depun n variabilele n i
respectiv x.
Pentru afiarea pe ecranul monitorului se folosete obiectul cout care corespunde fluxului
standard de ieire stdout. Iat un exemplu.

cout<<"Am citit: "<<n<<" si "<<x<<endl;

n fluxul standard de ieire se trimit: o constant de tip string, o valoare ntreag (cea
reinut n variabila n), un alt string constant i o valoare real (reinut n variabila y). Dup
afiare, manipulatorul endl introdus de asemenea n flux face salt la nceputul urmtoarei linii de
pe ecran.
n C++ avem dou obiecte pentru fluxuri de erori. Este vorba de cerr i clog. Primul
obiect corespunde fluxului standard de erori stderr din C. Introducerea de date n fluxul cerr are
ca efect afiarea lor imediat pe ecranul monitorului, dar pe alt cale dect cea a fluxului cout.
Al doilea flux de erori (clog) nu are corespondent n C. Acest flux este unul buffer-izat, n sensul
c mesajele de erori se colecteaz ntr-o zon de memorie RAM, de unde pot ajung pe ecran
numai cnd se dorete acest lucru, sau la terminarea execuiei programului. Golirea buffer-ului
pe ecran se poate face apelnd metoda flush(), sub forma:

clog.flush();

1.5. Manipulatori

Manipulatorii pot fi considerai nite funcii speciale care se introduc n lanurile de
operatori << sau >> n general pentru formatare. n exemplul din capitolul anterior am folosit
manipulatorul endl, care face salt la linie nou.
Manipulatorii fr parametri sunt descrii n fiierul antet iostream.h, iar cei cu
parametri apar n fiierul antet iomanip.h. Dm n continuare lista manipulatorilor:

Manipulator Descriere
dec Pregtete citirea/scrierea ntregilor n baza 10
hex Pregtete citirea/scrierea ntregilor n baza 16
8
oct Pregtete citirea/scrierea ntregilor n baza 8
ws Scoate toate spaiile libere din fluxul de intrare
endl Trimite caracterul pentru linie nou n fluxul de ieire
ends Insereaz un caracter NULL n flux
flush Golete fluxul de ieire
resetiosflags(long) Iniializeaz biii de formatare la valoarile date de argumentul long
setiosflags(long) Modific numai biii de pe poziiile 1 date de parametrul long
setprecision(int)
Stabilete precizia de conversie pentru numerele n virgul mobil
(numrul de cifre exacte)
setw(int)
Stabilete lungimea scrierii formatate la numrul specificat de
caractere
setbase(int)
Stabilete baza n care se face citirea/scrierea ntregilor (0, 8, 10
sau 16), 0 pentru baz implicit
setfill(int)
Stabilete caracterul folosit pentru umplerea spaiilor goale n
momentul scrierii pe un anumit format

Biii valorii ntregi transmise ca parametru manipulatorilor setiosflags i resetiosflags
indic modul n care se va face extragerea, respectiv introducerea datelor din/n flux. Pentru
fiecare dintre aceti bii n C++ exist definit cte o constant.

Formatrile cu setiosflags i resetiosflags au efect din momentul n care au fost introduse
n flux pn sunt modificate de un alt manipulator.

1.6. Indicatori de formatare

Dup cum le spune i numele, indicatorii de formatare arat modul n care se va face
formatarea la scriere, respectiv la citire n/din flux. Indicatorii de formatare sunt constante ntregi
definite n fiierul antet iostream.h. Fiecare dintre aceste constante reprezint o putere a lui 2,
din cauz c fiecare indicator se refer numai la un bit al valorii ntregi n care se memoreaz
formatarea la scriere, respectiv la citire. Indicatorii de formatare se specific n parametrul
manipulatorului setiosflags sau resetiosflags. Dac vrem s modificm simultan mai muli bii de
formatare, atunci vom folosi operatorul | (sau pe bii).
Dm n continuare lista indicatorilor de formatare:

Indicator Descriere
ios::skipws Elimin spaiile goale din buffer-ul fluxului de intrare
ios::left Aliniaz la stnga ntr-o scriere formatat
ios::right Aliniaz la dreapta ntr-o scriere formatat
ios::internal Formateaz dup semn (+/-) sau indicatorul bazei de numeraie
ios::scientific Pregtete afiarea exponenial a numerelor reale
ios::fixed Pregtete afiarea zecimal a numerelor reale (fr exponent)
ios::dec Pregtete afiarea n baza 10 a numerelor ntregi
ios::hex Pregtete afiarea n baza 16 a numerelor ntregi
ios::oct Pregtete afiarea n baza 8 a numerelor ntregi
ios::uppercase
Folosete litere mari la afiarea numerelor (lietra e de la exponent i
cifrele n baza 16)
ios::showbase Indic baza de numeraie la afiarea numerelor ntregi
ios::showpoint Include un punct zecimal pentru afiarea numerelor reale
ios::showpos Afieaz semnul + n faa numerelor pozitive
9
ios::unitbuf Golete toate buffer-ele fluxurilor
ios::stdio Golete buffer-ele lui stdout i stderr dup inserare
De exemplu, pentru a afia o valoare real fr exponent, cu virgul, aliniat la dreapta, pe
8 caractere i cu dou zecimale exacte procedm astfel:

float x=11;
cout<<setiosflags(ios::fixed|ios::showpoint|ios::right);
cout<<setw(8)<<setprecision(2)<<x;

Menionm faptul c n exemplul de mai sus formatrile date de setw i de setprecision se
pierd dup afiarea valorii x, iar formatrile specificate prin intermediul manipulatorului
setiosflags rmn valabile.
Dm n continuare un alt exemplu ilustrativ pentru modul de utilizare al manipulatorilor
i al indicatorilor de formatare:

#include<iostream.h>
#include<iomanip.h>

void main()
{
int i=100;
cout<<setfill('.');

cout<<setiosflags(ios::left);
cout<<setw(10)<<"Baza 8";
cout<<setiosflags(ios::right);
cout<<setw(10)<<oct<<i<<endl;

cout<<setiosflags(ios::left);
cout<<setw(10)<<"Baza 10";
cout<<setiosflags(ios::dec | ios::right);
cout<<setw(10)<<i<<endl;

cout<<setiosflags(ios::left);
cout<<setw(10)<<"Baza 16";
cout<<setiosflags(ios::right);
cout<<setw(10)<<hex<<i<<endl;
}

n urma execuiei programului de mai sus, pe ecranul monitorului se vor afia:

Baza 8...........144
Baza 10..........100
Baza 16...........64


Rezumat

Am fcut cunotin cu primele elemente introduse de C++ n plus fa de limbajul C.
Astfel, variabilele pot fi declarate aproape oriunde n program (chiat i n instruciunea for). De
asemenea, am vzut cum se citesc date de la tastatur (folosind obiectul cin), cum se afieaz (cu
10
cout), cum se face o formatare la citire i respectiv la afiare (folosind manipulatori i indicatori
de formatare). Ce este poate cel mai important este faptul c alternativa C++ de lucru cu fluxuri
este obiect orientat.


Teme

1. De la tastatur se citete o matrice de valori reale. S se afieze pe ecran matricea,
astfel nct fiecare element al matricei s fie scris aliniat la dreapta, pe opt caractere i
cu dou zecimale exacte. Dm ca exemplu afiarea unei matrici cu trei linii i dou
coloane:

11.17 2.00
-23.05 44.10
12345.78 0.00

2. De la tastatur se citesc numele i vrstele unor persoane. S se afieze tabelar, sortat
dup nume, aceste date precum i media vrstelor, conform modelului din exemplul
de mai jos:

-------------------------------------------------------
|Nr. | NUMELE SI PRENUMELE |Varsta|
|crt.| | |
|----|-----------------------------------------|------|
| 1|Ion Monica | 19|
| 2|Ionescu Adrian Ionel | 25|
| 3|Popescu Gigel | 17|
| 4|Popescu Maria | 28|
|----------------------------------------------|------|
| Media varstelor: | 22.25|
-------------------------------------------------------


1.7. Alocarea dinamic a memoriei n C++


Obiective

n acest capitol vom studia modul n care se face alocarea i eliberarea memoriei n C++,
ntr-o manier elegant i modern cu ajutorul noilor operatori introdui n C++: new i delete.

Limbajul C++ ofer o alternativ la funciile C calloc, malloc, realloc i free pentru
alocarea i eliberarea dinamic a memoriei, folosind operatorii new i delete.
Schema general de alocare dinamic a memoriei cu ajutorul operatorului new este:

pointer_catre_tip = new tip;

Eliberarea memoriei alocate dinamic anterior se poate face cu ajutorul operatorului delete
astfel:

11
delete pointer_catre_tip;

Prezentm n continuare alocarea i eliberarea dinamic a memoriei pentru rdcina unui
arbore binar:

struct TArbBin
{
char info[20];
struct TArbBin *ls,*ld;
}*rad;

// ....

if (!(rad=new struct TArbBin))
{
cerr<<"Eroare! Memorie insuficienta."<<endl;
exit(1); // parasirea program cu cod de eroare
}

//....

delete rad;

Alocarea i eliberarea dinamic a memoriei pentru un vector de numere ntregi folosind
facilitile C++ se face astfel:

int n,*a;

cout<<"Dati lungimea vectorului: ";
cin>>n;
if(!(a = new int[n]))
{
cerr<<"Eroare! Memorie insuficienta." <<endl;
exit(1);
}

//....

delete [] a;

Pentru a elibera memoria ocupat de un vetor se folosete operatorul delete urmat de
paranteze ptrate, dup cum se poate observa n exemplul de mai sus. Dac nu punem paranteze
ptrate nu este greit, dar este posibil ca n cazul unor compilatoare C++ s nu se elibereze
memoria ocupat de vector corect i mai ales complet.
n C++ alocarea i eliberarea dinamic a memoriei pentru o matrice de numere reale se
poate face ceva mai uor dect n C folosind operatorii new i delete.
Prezentm n continuare dou posibile metode de alocare dinamic a memorie pentru o
matrice de ordin 2 (cu m linii i n coloane).

#include<iostream.h>
#include<malloc.h>
12

int main(void)
{
int m,n;
float **a;

cout<<"Dati dimensiunile matricii: ";
cin>>m>>n;

if (!(a = new float*[m]))
{
cerr<<"Eroare! Memorie insuf." <<endl;
return 1; // parasirea program cu eroare
}
for (int i=0;i<m;i++)
if(!(a[i] = new float[n]))
{
cerr<<"Eroare! Memorie insuf." <<endl;
return 1;
}

//....

for (i=0;i<m;i++)
delete [] a[i];
delete [] a;
return 0;
}

Pentru a intelege mai bine ideea de mai sus de alocare a memoriei pentru o matrice, s
vedem ce se ntampl n memorie.
Prima data se aloc memorie pentru un vector (cu m elemente) de pointeri ctre tipul float
(tipul elementelor matricii). Adresa ctre aceast zon de memorie se reine n pointerul a. Dup
prima alocare urmeaz m alocri de memorie necesare stocrii efective a elementelor matricei. n
vectorul de la adresa a (obinut n urma primei alocri) se rein adresele ctre nceputurile celor
m zone de memorie carespunztoare liniilor matricei. Tot acest mecanism de alocare a memoriei
este ilustrat n figura 1.

13

Eliberarea memoriei necesare stocrii matricei se face evident tot n m+1 pai.
Avantajul alocrii dinamice pentru o matrice n stilul de mai sus este dat de faptul c nu
este necesar o zon de memorie continu pentru memorarea elementelor matricei. Dezavantajul
const ns n viteza sczut de execuie a programului n momentul alocrii i eliberrii
memoriei (se fac m+1 alocri i tot attea eliberri de memorie).
Propunem n continuare o alt metod de alocare a memoriei pentru o matrice cu numai
dou alocri de memorie (i dou eliberri).

#include<iostream.h>
#include<malloc.h>

int main(void)
{
int m,n;
float **a;

cout<<"Dati dimensiunile matricii: ";
cin>>m>>n;

if (!(a = new float*[m]))
{
cerr<<"Eroare! Memorie insuf." <<endl;
return 1;
}
if(!(a[0] = new float[m*n]))
{
cerr<<"Eroare! Memorie insuf." <<endl;
return 1;
}
. a[m-1]
a
.
a[0]
a[0][n-1]
.
a[1]
a[1][n-1]
.
a[m-1]
a[m-1][n-1]
a[0][0] a[0][1]
a[m-1][0] a[m-1][1]
a[1][0] a[1][1]
.
a[0] a[1]
Fig. 1: Prima schem de alocare a memoriei pentru o matrice
14
for (int i=1;i<m;i++)
a[i]=a[i-1]+n;

//....

delete [] a[0];
delete [] a;
return 0;
}


n cazul celei de a doua metode, nti alocm de asemenea memorie pentru a reine cele
m adrese de nceput ale liniilor matricei, dup care alocm o zon de memorie continu necesar
stocrii tuturor celor m*n elemente ale matricei (nti vom reine elementele primei linii, apoi
elementele celei de a doua linii etc.). Adresa de nceput a zonei de memorie alocate pentru
elementele matricei este reinut n pointerul a[0]. n a[1] se reine adresa celei de a (n+1)-a
csute de memorie (a[1]=a[0]+n), adic nceputul celei de-a doua linii a matricei. n general, n
a[i] se reine adresa de inceput a liniei i+1, adic a[i]=a[i-1]+n=a[0]+i*n. Schema de
alocare a memoriei este prezentat n figura 2.
Este evident c al doilea mod de alocare a memoriei este mai rapid dect primul (cu
numai dou alocri i dou eliberri) i, cum calculatoarele din prezent sunt nzestrate cu
memorii RAM de capacitate foarte mare, alocarea unei zone mari i continue de memorie nu mai
reprezint un dezavantaj. Aa c n practic preferm a doua modalitate de alocare dinamic a
memorie pentru o matrice.









n final trebuie s recunoatem ns c alocarea dinamic a memoriei pentru o matrice n
alte limbaje de programare (cum ar fi Java sau C#) este mai uoar dect n C++, deoarece ea se
poate face printr-o singur utilizare a operatorului new.

Rezumat

. a[m-1]
a

a[0]
a[1][0] a[0][0] a[0][n-1]
a[0] a[1]
Fig. 2: A doua schem de alocare a memoriei pentru o matrice
a[m-1][0] a[m-1][n-1]
a[1] a[m-1]
15
C++ ofer o alternativ mai elegant i modern pentru alocarea dinamic a memoriei,
folosind operatorii new i delete. Cu ajutorul acestor operatori alocm memorie mai uor, fr a
mai fi nevoie de conversii i fr apeluri de funcii.

Teme

1. S se aloce dinamic memorie pentru un vector de vectori de elemente de tip double cu
urmtoarea proprietate: primul vector are un element, al doilea are dou elemente, n
general al k-lea vector are k elemente, ke{1, 2, , n}. S se citeasc de la tastatur n
(numrul de vectori) precum i elementele vectorilor. S se construiasc un nou
vector format cu mediile aritmetice ale celor n vectori. n final s se elibereze toate
zonele de memorie alocate dinamic.

2. De la tastatur se citete un vector a cu n elemente ntregi pozitive. S se aloce
memorie pentru un vector de vectori cu elemente ntregi. Primul vector are a[0]
elemente, al doilea are a[1] elemente, n general al k-lea vector are a[k-1] elemente
(ke{1, 2, , n}). S se citeasc de la tastatur elementele vectorilor. S se
construiasc un nou vector (alocat tot dinamic) format cu elementele ptrate perfecte
ale celor n vectori. n final se elibereaz toate zonele de memorie alocate dinamic.

3. Sa se aloce dinamic memorie pentru o matrice tridimensional de dimensiuni m, n i p
de elemente ntregi, unde m, n i p sunt valori ntregi pozitive citite de la tastatur. S
se citeasc de la tastatur elementele matricii. S se construiasc un vector de triplete
(i, j, k), unde i, j i k (ie{0, 1, , m-1}, je{0, 1, , n-1}, ke{0, 1, , p-1}) sunt
indicii elementelor matricii care sunt cele mai apropiate de un ptrat perfect. n final
s se elibereze toate zonele de memorie alocate dinamic.

4. Scriei funcii pentru introducerea unui element ntr-o stiv de caractere, scoaterea
unui element din stiv, afiarea coninutului stivei i eliberarea meoriei ocupate de
stiv. Stiva se va memora dinamic folosind pointeri ctre tipul struct.

5. Scriei aceleai funcii pentru o coad.

6. Scriei aceleai funcii pentru o coad circular.

7. Scriei o funcie pentru introducerea unei valori reale ntr-un arbore binar de cutare i
o funcie pentru parcurgerea n inordine a arborelui binar. Folosii aceste funcii
pentru a sorta un vector de numere reale citit de la tastatur. Pentru memorarea
arborelui se vor folosi pointeri ctre tipul struct.


1.8. Funcii n C++

Obiective

n acest capitol ne propunem s studiem elementele introduse de C++ cu privire la modul
de scriere al funciilor. n C++ putem avea funcii cu acelai nume i cu parametrii diferii, valori
implicite pentru parametri. Transmiterea parametrilor se poate face i prin referin. Vom
prezenta i noiunile de funcie inline i funcie ablon.

16
1.8.1. Funcii cu acelai nume i cu parametrii diferii

n C++ se pot scrie mai multe funcii cu acelai nume, dar cu parametri diferii (ca numr
sau/i ca tip), n engleza overloading. La apelul unei funcii se caut varianta cea mai apropiat
de modul de apelare (ca numr de parametrii i ca tip de date al parametrilor).
De exemplu, putem scrie trei funcii cu acelai nume care calculeaz maximul dintre
dou, respectiv trei valori:

# include <iostream.h>

int max(int x,int y)
{
if (x>y) return x;
return y;
}

int max(int x,int y,int z)
{
if (x>y)
{
if (x>z) return x;
return z;
}
if (y>z) return y;
return z;
}

double max(double x,double y)
{
if (x>y) return x;
return y;
}

void main(void)
{
int a=1,b=2,c=0,max1,max2;
float A=5.52f,B=7.1f,max3;
double A2=2,B2=1.1,max4;

max1=max(a,b); // apelul primei functii
max2=max(a,b,c); // apelul celei de-a doua fct
max3=(float)max(A,B); // apelul functiei 3
max4=max(A2,B2); // apelul tot al functiei 3
cout<<max1<<", "<<max2<<", ";
cout<<max3<<", "<<max4<<endl;
}

1.8.2. Transmiterea parametrilor prin referin

n C transmiterea parametrilor n funcie se face prin valoare (pentru cei de intrare) sau
prin adres (pentru cei de ieire). Transmiterea parametrilor prin adres este pretenioas (la apel
17
suntem obligai n general s utilizm operatorul adresa &, iar n corpul funciei se folosete
operatorul *).
n C++ transmiterea parametrilor de ieire (care se returneaz din funcii), se poate face
ntr-o manier mult mai elegant, i anume prin referin. n definiia funciei, fiecare parametru
transmis prin referin este precedat de semnul &.
Dm ca exemplu interschimbarea valorilor a dou variabile n ambele forme (transmitere
prin adres i prin referin).

# include <iostream.h>

void intersch(int* a, int* b) // transmitere prin adr.
{
int c=*a;
*a=*b;
*b=c;
}

void intersch(int& a, int& b) // transmitere prin ref.
{
int c=a;
a=b;
b=c;
}

void main()
{
int x=1,y=2;

cout<<"primul nr: "<<x<<", al doilea nr: "<<y<<endl;
intersch(&x,&y); // apelul primei functii
cout<<"primul nr: "<<x<<", al doilea nr: "<<y<<endl;
intersch(x,y); // apelul celei de-a doua functii cout<<"primul nr:
"<<x<<", al doilea nr: "<<y<<endl;
}

Ne propunem acum s scriem o funcie care primete ca parametru un vector de valori
ntregi i care construiete i returneaz ali doi vectori formai cu elementele nenegative i
respectiv cu cele negative ale vectorului iniial. Mai mult, dup separare se elibereaz zona de
memorie ocupat de vectorul iniial.
Se tie c n C cand alocm memorie (sau n general atunci cnd schimbm adresa
reinut ntr-un pointer primit ca argument), adresa de memorie n general se returneaz ca
valoare a funciei i nu printre parametrii funciei (vezi de exemplu funciile C de alocare a
memoriei: calloc, malloc, realloc). Cum procedm atunci cnd alocm mai multe zone de
memorie n interiorul funciei i vrem s returnm adresele spre zonele alocate? Acesta este i
cazul problemei propuse mai sus. Singura soluie oferit de C este scrierea unor parametri ai
funciei de tip pointer ctre pointer. Iat rezolvarea problemei n aceast manier pentru
problema separrii elementelor nenegative de cele negative:

# include <iostream.h>

void separare(int m,int **a,int *n,int **b,int *p,int **c)
18
{
*n=0;
*p=0;
for (int i=0;i<m;i++)
if ((*a)[i]>=0) (*n)++;
else (*p)++;
*b=new int[*n];
*c=new int[*p];
int k=0,h=0;
for (i=0;i<m;i++)
if ((*a)[i]>=0) (*b)[k++]=(*a)[i];
else (*c)[h++]=(*a)[i];
delete [] *a;
}

void main()
{
int i,n,n1,n2,*a,*a1,*a2;

cout<<"Nr. de elemente al sirului: ";
cin>>n;
a=new int[n];
cout<<"Dati elemente al sirului:"<<endl;
for (i=0;i<n;i++)
{
cout<<"a["<<(i+1)<<"]=";
cin>>a[i];
}
separare(n,&a,&n1,&a1,&n2,&a2); // transmitere prin adr
cout<<"Sirul elementelor nenegative: ";
for (i=0;i<n1;i++)
cout<<a1[i]<<" ";
cout<<endl<<"Sirul elementelor negative: ";
for (i=0;i<n2;i++)
cout<<a2[i]<<" ";
delete [] a1;
delete [] a2;
}

Pentru a nelege obligativitatea folosirii parantezelor n funcia separare din exemplul de
mai sus trebuie sa cunoatem ordinea de aplicare a operatorilor ntr-o expresie. Astfel, operatorul
de incrementare ++ are prioritatea cea mai mare, el se aplic naintea operatorului * (valoare de
la o adres) pus n faa pointerului. De asemenea, operatorul [] (de referire la un element al unui
ir) are prioritate mai mare dect * aplicat unui pointer. De aceea am folosit paranteze pentru a
indica ordinea n care dorim s aplicm aceti operatori n situaiile: (*a)[i] i respectiv
(*n)++. S observm i faptul c la atribuirea *n=0 nu este necesar utilizarea parantezelor,
deoarece operatorul = are prioritate mai mic dect * aplicat pointerilor.
Dup cum se vede descrierea funciei separare cu transmitere a vectorilor prin adres
(stil C) este foarte de pretenioas. Trebuie s fim foarte ateni la o mulime de detalii datorate n
special necesitii utilizrii operatorului * cnd ne referim n corpul funciei la parametrii
transmii prin adres.
19
S vedem n continuare cum putem rescrie mult mai elegant funcia separare cu
transmitere a vectorilor prin referin ctre pointer (aa cum este posibil numai n C++):

# include <iostream.h>

void separare(int m,int *&a,int &n,int *&b,int &p,int *&c)
{
n=0;
p=0;
for (int i=0;i<m;i++)
if (a[i]>=0) n++;
else p++;
b=new int[n];
c=new int[p];
int k=0,h=0;
for (i=0;i<m;i++)
if (a[i]>=0) b[k++]=a[i];
else c[h++]=a[i];
delete [] a;
}

void main()
{
int i,n,n1,n2,*a,*a1,*a2;

cout<<"Nr. de elemente al sirului: ";
cin>>n;
a=new int[n];
cout<<"Dati elemente al sirului:"<<endl;
for (i=0;i<n;i++)
{
cout<<"a["<<(i+1)<<"]=";
cin>>a[i];
}
separare(n,a,n1,a1,n2,a2); // transmitere prin referinta
cout<<"Sirul elementelor nenegative: ";
for (i=0;i<n1;i++)
cout<<a1[i]<<" ";
cout<<endl<<"Sirul elementelor negative: ";
for (i=0;i<n2;i++)
cout<<a2[i]<<" ";
delete [] a1;
delete [] a2;
}

1.8.3. Valori implicite pentru parametrii funciilor

n C++ exist posibilitatea ca la definirea unei funcii o parte dintre parametri (transmii
prin valoare) s primeasc valori implicite. n situaia n care lipsesc argumente la apelul
funciei, se iau valorile implicite dac exist pentru acestea. Numai o parte din ultimele
argumente din momentul apelului unei functii pot lipsi i numai dac exist valorile implicite
20
pentru acestea n definiia funciei. Nu poate lipsi de exemplu penultimul argument, iar ultimul
s existe n momentul apelului funciei.
Dm un exemplu simplu n care scriem o funcie inc pentru incrementarea unei variabile
ntregi (similar procedurii cu acelai nume din limbajele Pascal i Delphi):

void inc(int &x,int i=1)
{
x+=i;
}

Funcia inc poate fi apelat cu unul sau doi parametri. Astfel, apelul inc(a,5) este
echivalent cu x+=5 (n corpul funciei variabila i ia valoarea 5, valoare transmis din apelul
funciei). Dac apelm ns funcia sub forma inc(a), atunci pentru i se ia valoarea implicit
1, situaie n care x se mrete cu o unitate.
Scriei o functie care primete 5 parametri de tip int care returneaz maximul celor 5
valori. Dai valori implicite parametrilor aa nct funcia s poat fi folosit pentru a calcula
maximul a dou numere ntregi (cnd se apeleaz cu 2 parametri), a trei, patru i respectiv cinci
valori ntregi.

1.8.4. Funcii inline

n C++ exist posibilitatea declarrii unei funcii ca fiind inline. Fiecare apel al unei
funcii inline este nlocuit la compilare cu corpul funciei. Din aceast cauz funciile inline se
aseamn cu macrocomenzile. Spre deosebire ns de macrocomenzi, funciile inline au tip
pentru parametrii i pentru valoarea returnat. De fapt, ele se declar i se descriu ca i funciile
obinuite, numai c n faa definiiei se pune cuvntul rezervat inline. Modul de apel al
macrocomenzilor difer de cel al funciilor inline. n acest sens dm un exemplu comparativ n
care scriem o macrocomand pentru suma a dou valori i respectiv o funcie inline pentru suma
a dou valori ntregi.

# include <iostream.h>

# define suma(a,b) a+b // macrocomanda

inline int suma2(int a, int b) // functie inline
{
return a+b;
}

void main()
{
int x;
x=2*suma(5,3);
cout<<x<<endl;
x=2*suma2(5,3);
cout<<x<<endl;
}

Pe ecran se vor afia valorile 13 i respectiv 16. Primul rezultat poate fi pentru unii
neateptat. Dac suntem familiarizai cu modul de utilizare al macrocomenzilor rezultatul nu mai
21
este ns deloc surprinztor. La compilare, apelul suma(5,3) este nlocuit efectiv n cod cu 5+3,
ceea ce nseamn c variabilei x i se va atribui valoarea 2*5+3, adic 13.
Din cauz c apelurile funciei inline se nlocuiesc cu corpul ei, codul funciei inline
trebuie s fie n general de dimensiuni mici. n caz contrar i/sau dac apelm des n program
funciile inline, dimensiunea executabilului va fi mai mare.
De reinut este faptul c n cazul compilatorului Borland C++ nu se accept intruciuni
repetitive i nici instruciuni throw (vezi capitolul dedicat tratrii excepiilor) n corpul funciei
inline. Dac incercm totui utilizarea lor n corpul unei funcii declarate inline, atunci la
compilare funcia va fi considerat obinuit (ignorndu-se practic declaratia inline) i se va
genera un warning.
Funciile inline sunt foarte des utilizate n descrierea claselor. Astfel, funciile mici,
fr cicluri repetitive, pot fi descrise inline, adic direct n corpul clasei.

1.8.5. Funcii ablon (template)

Unul dintre cele mai frumoase suporturi pentru programare generic este oferit de
limbajul C++ prin intermediul funciilor i claselor ablon (template). Astfel, n C++ putem scrie
clase sau funcii care pot funciona pentru unul sau mai multe tipuri de date nespecificate. S
lum spre exemplu sortarea unui vector. Ideea algoritmului este aceeai pentru diverse tipuri de
date: ntregi, reale, string-uri etc. Fr a folosi abloane ar trebui s scriem cte o funcie de
sortare pentru fiecare tip de date.
naintea fiecrei funcii ablon se pune cuvntul rezervat template urmat de o enumerare
de tipuri de date generice (precedate fiecare de cuvntul rezervat class). Enumerarea se face ntre
semnele < (mai mic) i > (mai mare):

template <class T1, class T2, ... >
tipret NumeFctSablon(parametri)
{
// corpul functiei sablon
}

T1, T2, sunt tipurile generice de date pentru care scriem funcia ablon. Este esenial
de reinut faptul c toate tipurile de date generice trebuie folosite n declararea parametrilor
funciei.
n cele mai multe cazuri se folosete un singur tip generic de date.
Iat cteva exemple simple de funcii ablon:

template <class T>
T max(T x,T y)
{
if (x>y) return x;
return y;
}

template <class T>
void intersch(T &x,T &y)
{
T aux=x;
x=y;
y=aux;
}
22

template <class T1,class T2>
void inc(T1 &x,T2 y)
{
x+=y;
}

template <class T>
T ma(int n,T *a)
{
T s=0;
for (int i=0;i<n;i++) s+=a[i];
return s/n;
}

Dac n interiorul aceluiai program avem apeluri de funcii ablon pentru mai multe
tipuri de date, atunci pentru fiecare dintre aceste tipuri de date compilatorul genereaz cte o
funcie n care tipurile generice de date se nlocuiesc cu tipurile de date identificate la ntlnirea
apelului. De asemenea, la compilare se verific dac sunt posibile instanele funciilor ablon
pentru tipurile de date identificate. De exemplu, compilarea codului funciei ablon inc nu
genereaz erori, dar tentativa de apelare a ei cu doi parametrii de tip string se soldeaz cu eroare
la compilare pentru c cele dou string-uri se transmit prin doi pointeri (ctre char), iar
operatorul += nu este definit pentru doi operanzi de tip pointer.
Dm n continuare cteva posibile apeluri ale funciilor ablon de mai sus:

int i=2,j=0,k;
double m,a[4]={1.5,-5E2,8,0},x=2,y=5.2;
char *s1=Un string,*s2=Alt string,*s;

k=max(i,j); // T este int
m=max(x,y); // T este double
intersch(i,j); // T este int
intersch(x,y); // T este double
inc(x,i); // T1 este double, T2 este int
m=ma(4,a); // T este double
s=max(s1,s2); // apel incorect, T nu poate fi char*

Funciile ablon i clasele ablon alctuiesc fiecare cte o clas de funcii, respectiv de
clase, n sensul c ele au aceeai funcionalitate, dar sunt definite pentru diverse tipuri de date. O
funcie template de sortare putem spune c este de fapt o clas de funcii de sortare pentru toate
tipurile de date care suport comparare. n cazul sortrii, tipului nespecificat este cel al
elementelor vectorului.
Clasele ablon le vom prezenta ntr-un capitol urmtor.

Rezumat

Am studiat principalele elemente introduse de limbajul C++ cu privire la modul de
redactare al funciilor. Astfel, putem avea funcii cu acelai nume i parametri diferii, valori
implicite pentru funcii, funcii inline, parametrii de ieire pot fi transmii prin referin ntr-o
manier mult mai elegant i mai uor de redactat. De asemenea, funciile ablon (alturi de
23
clasele ablon) ofer unul dintre cele mai frumoase suporturi pentru programarea generic (cod
care s funcioneze pentru diverse tipuri de date).

Teme

Propunem aplicativ la transmiterea parametrilor prin referin scrierea a dou funcii:

1. Funcie care primete ca primi parametrii lungimea unui vector de numere ntregi
precum i pointerul ctre acest vector. S se construiasc un alt vector (alocat
dinamic) format cu elementele care sunt numere prime ale vectorului iniial. S se
returneze prin referin lungimea vectorului construit precum i pointerul ctre noul
vector.

2. Funcie care primete ca primi parametri datele despre o matrice de dimensiuni m i
n. Pornind de la aceast matrice s se construiasc un vector format cu elementele
matricii luate n spiral pornind de la elementul de indici 0 i 0 i mergnd n sensul
acelor de ceasornic. Funcia va returna prin referin pointerul ctre vectorul
construit. Menionm faptul c, dup construcia vectorului, n interiorul funciei se
va elibera memoria ocupat de matrice.
De exemplu, din matricea
|
|
|
.
|

\
|
12 11 10 9
8 7 6 5
4 3 2 1
se obine vectorul (1, 2, 3, 4, 8, 12, 11,
10, 9, 5, 6, 7).

S sescrie funcii ablon pentru:

1. Minimul a trei valori;
2. Maximul elementelor unui ir;
3. Cutare secvenial ntr-un ir;
4. Cutare binar ntr-un ir;
5. Interclasare a dou iruri sortate cresctor;
6. Sortare prin interclasare (utiliznd funcia de interclasare de mai sus);
7. Produs a dou matrici;
8. Ridicare matrice la o putere.


1.9. Suprancrcarea operatorilor

Obiective

n C++ exist posibilitatea suprancrcrii operatorilor (n englez overloading), adic un
operator poate fi definit i pentru alte tipuri de date dect cele pentru care exist deja definit.
Vom vedea c suprancrcarea operatorilor ofer o alternativ la scrierea unor funcii pentru
diverse operaii. Un operator este mult mai uor introdus ntr-o expresie, oferind elegan i
abstractizare codului.

Nu toi operatorii pot fi suprancrcai. Operatorii care nu pot fi redefinii i pentru alte
tipuri de date sunt:

24
1. x.y operator acces la membru
2. x.*y operator acces la date de la adresa dat de un cmp de tip pointer
3. x::y operator de rezoluie
4. x?y:z operator ternar ?:
5. # operator directiv preprocesor
6. ## operator preprocesor de alipire token-i

Lista operatorilor care pot fi suprancrcai este:

1. Operatorii unari de incrementare i decrementare: ++x, x++, --x, x--
2. Operatorul adres &x
3. Operatorul de redirecionare *x
4. Operatorii unari semn plus i minus: +x, -x
5. Operatorul not la nivel de bit ~x
6. Operatorul not logic !x
7. Operatorul pt. aflarea numrului de octei pe care se reprezint valoarea unei expresii
sizeof expresie
8. Operatorul de conversie de tip (type)x
9. Operatorii binari aritmetici: +, -, *, /, %
10. Operatorii de deplasare (shift-are) pe bii, respectiv pentru introducere, scoatere n / dintr-
un flux C++: x<<y, x>>y
11. Operatorii relaionari: <, >, <=, >=, = =, !=
12. Operatorii binari la nivel de bit: x&y (i), x^y (sau exclusiv), x|y (sau)
13. Operatorii logici && (i), || (sau)
14. Operatorul de atribuire =
15. Operatorul de atribuire combinat cu un operator binar: +=, -=, *=, /=, %=,
<<=, >>=, &=, ^=, |=
16. Operatorii de acces la date: x[y] (indice), x->y (membru y de la adresa x), x->*y
(referire membru pointer)
17. Operator de tip apel funcie x(y)
18. Operatorul virgul x,y
19. Operatorii de alocare i eliberare dinamic a memoriei: new, delete.

Dup cum putem obseva din lista de mai sus, operatorii care pot fi suprancrcai sunt
unari sau binari, adic au aritatea 1 sau 2. De altfel, singurul operator C/C++ ternar ?: nu poate fi
redefinit.
Un operator se definete asemntor cu o funcie. La definirea operatorului trebuie s se
in ns cont de aritatea lui, care trebuie s coincid cu numrul parametrilor, dac funcia este
extern unei clase, iar n loc de numele funciei apare cuvntul rezervat operator urmat de
simbolul sau simbolurile care caracterizeaz acel operator:

tipret operator<simbol(uri)>(parametri)
{
// corpul operatorului
}

S considerm urmtoarea structur n care vom memora elementele unei mulimi de
numere ntregi:

25
struct Multime
{
int n,e[1000];
};

n campul n vom reine numrul de elemente al mulimii, iar n e vom reine elementele
mulimii.
n C++ la tipul struct Multime ne putem referi direct sub forma Multime.
Pentru tipul Multime vom arta vom suprancrca urmtorii operatori: + pentru reuniunea
a dou mulimi i pentru adugarea unui element la o mulime, << pentru introducerea
elementelor mulimii ntr-un flux de ieire i >> pentru extragerea elementelor unei mulimi
dintr-un flux de intrare.
Cea mai simpl metod (de implementat) pentru reuniunea a dou mulimi este:

int cautSecv(int x,int n,int *a)
{
for (int i=0;i<n;i++)
if (x==a[i]) return 1;
return 0;
}

Multime operator +(Multime a,Multime b)
{
Multime c;

for (int i=0;i<a.n;i++) c.e[i]=a.e[i];
c.n=a.n;
for (i=0;i<b.n;i++)
if (!cautSecv(b.e[i],a.n,a.e))
c.e[c.n++]=b.e[i];
return c;
}

Complexitatea algoritmului folosit mai sus pentru a reuni mulimile a i b este evident
O(a.n b.n). Se poate ns i mai bine. n loc de cutarea secvenial a elementului b.e[i] n
vectorul a.e putem aplica o cutare rapid, bineneles dac sortm rapid n prealabil vectorul a.e.
Complexitatea algoritmului devine: O(a.n log(a.n) + b.n log(a.n)) = O((a.n + b.n)
log(a.n)). Dac reunim ns mulimea b cu a (n ordine invers), atunci complexitatea devine
O((a.n + b.n) log(b.n)). Este evident c, pentru a mbunti complexitatea algoritmului, vom
reuni a cu b dac b.n > a.n i, respectiv b cu a n situaia n care a.n > b.n. Complexitatea devine
atunci O((a.n + b.n) log(min{b.n, a.n})) = O(max{a.n, b.n} log(min{b.n, a.n})).
Pentru sortarea mulimii cu mai puine elemente vom aplica algoritmul de sortare prin
interclasare pentru c el are n orice situaie complexitatea O(m log(m)), unde m este numrul
de elemente.
Reuniunea rapid a dou mulimi este:

void intercls(int m,int *a,int n,int *b,int *c)
{
int i=0,j=0,k=0,l;

while (i<m && j<n)
26
if (a[i]<b[j]) c[k++]=a[i++];
else c[k++]=b[j++];
for(l=i;l<m;l++) c[k++]=a[l];
for(l=j;l<n;l++) c[k++]=b[l];
}

void sortIntercls(int s,int d,int *a,int *b)
{
if (s==d) {b[0]=a[s]; return;}
int m=(s+d)/2,*a1,*a2;

a1=new int[m-s+1];
a2=new int[d-m];
sortIntercls(s,m,a,a1);
sortIntercls(m+1,d,a,a2);
intercls(m-s+1,a1,d-m,a2,b);
delete [] a1;
delete [] a2;
}

int cautBin(int x,int s,int d,int *a)
{
if (s==d)
{
if (a[s]==x) return 1;
else return 0;
}
int m=(s+d)/2;
if (a[m]==x) return 1;
if (x<a[m])
return cautBin(x,s,m,a);
return cautBin(x,m+1,d,a);
}

Multime operator +(Multime a,Multime b)
{
int i;
Multime c;

if (a.n<b.n)
{
if(a.n)sortIntercls(0,a.n-1,a.e,c.e);
c.n=a.n;
for (i=0;i<b.n;i++)
if (!cautBin(b.e[i],0,a.n-1,c.e))
c.e[c.n++]=b.e[i];
}
else
{
if(b.n)sortIntercls(0,b.n-1,b.e,c.e);
c.n=b.n;
for (i=0;i<a.n;i++)
27
if (!cautBin(a.e[i],0,b.n-1,c.e))
c.e[c.n++]=a.e[i];
}
return c;
}

S implementm acum operatorul + pentru adugarea unui element la o mulime:

Multime operator +(Multime a,int x)
{
Multime b;

for (int i=0;i<a.n;i++) b.e[i]=a.e[i];
b.n=a.n;
if (!cautSecv(x,b.n,b.e)) b.e[b.n++]=x;
return b;
}

Implementarea operatorilor << i >> este:

ostream& operator <<(ostream& fl,Multime& a)
{
for (int i=0;i<a.n;i++) fl<<a.e[i]<<" ";
return fl;
}

istream& operator >>(istream& fl,Multime& a)
{
fl>>a.n;
for (int i=0;i<a.n;i++) fl>>a.e[i];
return fl;
}

Pentru testarea operatorilor care i-am scris pentru tipul Multime propunem urmtoarea
funcie principal:

void main()
{
int x;
Multime a,b,c;

cout<<"Dati prima multime:"<<endl;
cin>>a;
cout<<"Dati a doua multime:"<<endl;
cin>>b;
cout<<"Dati un numar intreg:";
cin>>x;
c=a+b+x;
cout<<"Reuniunea este: "<<c<<endl;
}

28
Pentru tipul Multime nu putem suprancrca operatorul = pentru c el este definit deja
pentru operanzi de tipul struct !
n final prezentm cteva reguli de care trebuie s inem seama atunci cnd
suprancrcm un operator:

1. Trebuie pstrata aritatea operatorului. Astfel, spre exemplu, aritatea operatorului de
adunare + este 2 (operator binar). Dac suprancrcm acest operator, atunci el va trebui
gndit i implementat s funcioneze pentru doi operanzi.
2. Modul de folosire al operatorilor se pstreaz. De exemplu, operatorul de adunare + va fi
folosit numai sub forma obiect1 + obiect2, iar operatorul de conversie de tip se va folosi
sub forma (tip)obiect.
3. Nu pot fi definii noi operatori, ci pot fi suprancrcai numai cei enumerai n lista de mai
sus. De exemplu nu putem defini operatorul **, care n alte limbaje (cum ar fi Basic) este
folosit pentru ridicare la putere.
4. Se pstreaz prioritatea de aplicare a operatorilor i dup ce acetia au fost suprancrcai.
5. Nu se pstreaz comutativitatea, astfel a*b nu este acelai lucru cu b*a.
6. Un operand se poate defini cel mult o dat pentru un tip de date (dac este unar) i cel
mult o dat pentru o pereche de tipuri de date (dac este binar).
7. Un operator nu poate fi suprancrcat pentru tipuri de date pentru care exist deja definit
(de exemplu operatorul + nu poate fi redefinit pentru doi operanzi ntregi).
8. Pentru a se face distincie ntre forma prefixat i cea post fixat a operatorilor de
incrementare i decrementare, la definirea formei postfixate se pune un parametru
suplimentar de tip int.
9. Un operator, ca n cazul unei funcii, poate fi definit n 2 feluri: ca fiind membru unei
clase sau ca fiind extern unei clase, dar nu simultan sub ambele forme pentru aceleai
tipuri de date.
10. Operatorii <<, >> se definesc de obicei ca funcii externe unei clase pentru c primul
operand este de obicei un flux.

Facem meniunea c n cele mai multe cazuri operatorii sunt suprancrcai pentru obiecte
n momentul n care scriem o clas nou. Vom ilustra acest lucru la momentul potrivit.

Rezumat

n C++ operatorii pot fi redefinii i pentru alte tipuri de date dect cele pentru care exist
deja. Suprancrcarea unui operator se face foarte asemntor cu descrierea unei funcii. Practic
diferena esenial const n faptul c numele funciei ce descrie operatorul este obligatoriu
format din cuvntul rezervat operator urmat de simbolurile ce definesc acel operator.

Teme

Propunem cititorului s implementeze pentru tipul Multime definit mai sus urmtorii
operatori:

1. operatorul pentru diferena a dou multimi i pentru scoaterea unui element dintr-o
mulime
2. operatorul * pentru intersecia a dou mulimi
3. operatorii +=, -=, *=
4. operatorul ! care returneaz numrul de elemente al mulimii
29
5. operatorii <= i < pentru a verifica dac o mulime este inclus, respectiv strict
inclus n alt mulime
6. operatorii >= si > pentru a verifica dac o mulime conine, respectiv conine strict
alt mulime
7. operatorul < pentru a verifica dac un element aparine unei mulimi
8. operatorii == i != pentru a verifica dac dou mulimi coincid ca i coninut,
respectiv sunt diferite.

Scriei tipul Multime, funciile (fr main) i operatorii de mai sus ntr-un fiier cu numele
Multime.cpp. n viitor, de fiecare dat cnd vei avea nevoie ntr-o aplicaie s lucrai cu mulimi
vei putea include acest fiier.

1.10. Tratarea excepiilor

Obiective

Ne propunem se vedem ce propune C++ n comparaie cu limbajul C pentru tratarea
situaiilor n care apar excepii (erori) pe parcursul execuiei programului. Vom vedea de
asemenea cum putem genera propriile excepii cu ajutorul instruciunii throw.

Excepiile se refer n general la situaii deosebite ce pot aprea n program, n special n
cazul erorilor ce apar n urma alocrii de memorie, deschiderii unui fiier, calculelor matematice
(de exemplu mprire prin 0 sau logaritm dintr-o valoare care nu este pozitiv) etc. Cnd este
detectat o excepie este dificil de decis ce trebuie fcut n acel moment. n cele mai multe cazuri
se prefer afiarea unui mesaj i se prsete programul.
Excepiile se mpart n excepii sincrone i asincrone. Excepiile ce pot fi detectate cu
uurin sunt cele de tip sincron. n schimb, excepiile asincrone sunt cauzate de evenimente care
nu pot fi controlate din program.
Este mai puin cunoscut faptul c n limbajul C exist posibilitatea tratrii excepiilor
folosind funciile setjmp i longjmp:

int setjmp(jmp_buf jmbb)
void longjmp(jmp_buf jmbb, int retval)

Funcia setjmp salveaz starea (contextul) execuiei (stiva) programului n momentul n
care este apelat i returneaz valoarea 0, iar funcia longjmp utilizeaz starea salvat cu setjmp
pentru a se putea reveni cu stiva de execuie a programului din momentul salvrii. Starea
programului se salveaz ntr-un buffer de tip jmp_buf, care este definit n fiierul antet setjmp.h.
Cnd se apeleaz funcia longjmp, se face practic un salt cu execuia programului n locul n care
s-a apelat funcia setjmp. n acest moment funcia setjmp returneaz valoarea 1. Funcia longjmp
se comport asemntor cu un apel goto la locul marcat de setjmp.
Funcia longjmp se apeleaz cu al doilea parametru avnd valoarea 1. Dac se ncearc
apelul cu o alt valoare, parametrul retval va fi setat tot ca fiind 1.
Dm un exemplu de tratare a excepiilor legate de deschiderea unui fiier aa cum se face
n C (cu ajutorul celor dou funcii):

# include <conio.h>
# include <stdio.h>
# include <stdlib.h>
# include <process.h>
# include <setjmp.h>
30

void mesaj_eroare()
{
perror("Au fost erori in timpul executiei!");
getch();
}

void mesaj_ok()
{
perror("Program incheiat cu succes!");
getch();
}

void main()
{
char numef[100];
FILE *fis;
jmp_buf stare;

if (!setjmp(stare)) // salvare stare
{
printf("Dati numele unui fisier: ");
scanf("%s",numef);
fis=fopen(numef,"rb");
if (fis==NULL)
longjmp(stare,1); // salt la setjmp si
} // se trece pe ramura else
else
{
perror("Eroare! Nu am putut deschide fis.");
atexit(mesaj_eroare); // mesaj er. parasire progr.
exit(1);
}
fclose(fis);
atexit(mesaj_ok); // mesaj parasire program cu succes
}

n exemplul de mai sus am folosit funcia atexit care specific funcia ce se va apela
imediat nainte de prsirea programului.
Dac dorim s semnalm faptul c un program s-a ncheiat cu insucces, putem apela
funcia abort n locul funciei exit. Funcia abort prsete programul i returneaz valoarea 3, nu
nainte ns de a afia mesajul Abnormal program termination.
n C++ tratarea excepiilor este mult mai modern. Tratarea excepiilor se face cu ajutorul
instruciunii try. Dup cuvntul rezervat try urmeaz un bloc de instruciuni neaprat delimitat de
acolade. Se ncearc execuia (de unde i denumirea) instruciunilor din blocul try i dac se
genereaz o excepie, atunci execuia instruciunilor blocului try este ntrerupt i excepia este
captat (prins) i tratat eventual pe una din ramurile catch ce urmeaz dup blocul try. Facem
meniunea c instruciunile ce se execut pe o ramur catch sunt de asemenea delimitate
obligatoriu de acolade.
Dm un exemplu de tratare a excepiei ce apare n urma mpririi prin zero:

31
#include<iostream.h>

void main()
{
int a=1,b=0;

try
{
cout<<(a/b)<<endl;
}
catch (...)
{
cerr<<"Impartire prin zero."<<endl;
}
}

La prima apariie a unei excepii n interiorul blocului try se genereaz o valoare a crei
tip va face ca excepia s fie tratat pe o anumit ramura catch. ntre parantezele rotunde ce
urmeaz dup cuvntul rezervat catch apare o variabil sau apar trei puncte (...). Ramura catch
cu trei puncte prinde orice excepie generat n blocul try care nu a fost prins de alt ramur
catch de deasupra.
n momentul n care detectm o excepie putem arunca o valoare cu ajutorul instruciunii
throw.
Ne propunem s scriem o funcie pentru scoaterea rdcinii ptrate dintr-un numr real cu
ajutorul metodei numerice de njumtire a intervalului. Vom trata eventualele excepii ce pot
aprea:

#include<iostream.h>

double fct(double x,double v)
{
return x*x-v;
}

double radical(double v,double precizie)
{
if (precizie<=0) throw precizie;
if (v<0) throw "Radical din numar negativ.";
if (!v || v==1) return v;

double s,d,m,fs,fd,fm;

if (v>1)
{
s=1;
d=v;
}
else
{
s=v;
d=1;
32
}
fs=fct(s,v);
fd=fct(d,v);
while (d-s>precizie)
{
m=(s+d)/2;
fm=fct(m,v);
if (!fm) return m;
if (fs*fm<0) {d=m; fd=fm;}
else {s=m; fs=fm;}
}
return m;
}

void main()
{
double x,p;

cout<<"Dati un numar real: ";
cin>>x;
cout<<"Precizia calcularii radacinii patrate: ";
cin>>p;
try
{
double r=radical(x,p);
cout<<"Radical din "<<r<<" este "<<r<<endl;
}
catch(char *mesajEroare)
{
cerr<<mesajEroare<<endl;
}
catch(double prec)
{
cerr<<"Precizia nu poate fi: ";
cerr<<prec<<endl;
}
}

n funcia radical parametrul real precizie indic distana maxim ntre soluia numeric
(valoarea aproximativ a rdcinii ptrate din v) gsit i soluia exact (n situaia noastr
valoarea exact a rdcinii ptrate din v). Funcia radical genereaz excepii ntr-una din
situaiile: dac v este negativ sau dac precizia dat nu este un numr pozitiv.
n situaia n care n funcia radical trimitem o valoare negativ sau zero pentru precizie,
execuia instruciunilor din corpul funciei este intrerupt i se genereaz o valoare real care
reine precizia gsit ca fiind invalid.
Dac n funcia radical trimitem o valoare v care este negativ, atunci execuia
instructiunilor din corpul funciei este ntrerupt i se genereaz mesajul Radical din numar
negativ..
Evident c la apelul funciei radical, n situaia n care precizia nu este pozitiv, se va
intra pe a doua ramur catch a instruciunii try, iar dac valoarea din care vrem s extragem
33
rdcina ptrat este negativ se va ajunge pe prima ramur catch. n ambele situaii se trimit
mesaje n fluxul standard de erori (cerr), mesaje ce vor aprea pe ecran.
Dac folosim ramura catch cu trei puncte, ea trebuie pus ultima. Astfel, numai
eventualele excepii care scap de ramurile catch de deasupra ajung pe ramura catch().
Instruciunea throw poate s nu returneze nici o valoare, situaie n care se ajunge cu execuia
programului pe ramura catch(), dac exist.
Dup cum am vzut, instruciunea throw folosit n funcia radical are un efect
asemntor cu un apel al instruciunii return n sensul c execuia instructiunilor din corpul
funciei este ntrerupt. Deosebirea este c nstructiunea throw dac returneaz o valoare, atunci
ea este interceptat numai n interiorul unui bloc try, iar instruciunea return returneaz valoarea
funciei.
Pot exista situaii n care o excepie este identificat direct n interiorul blocului throw. Ea
poate fi aruncat spre a fi captat de una dintre ramurile catch. n exemplul urmtor testm dac
dempritul este zero nainte de a efectua mprirea. Dac dempritul este zero, aruncm o
excepie prin intermediul unui mesaj:

#include<iostream.h>

void main()
{
int a=1,b=0;

try
{
if (!b) throw "Impartire prin zero.";
cout<<(a/b)<<endl;
}
catch (char *mesajEroare)
{
cerr<<mesajEroare<<endl;
}
}

Menionm faptul c instruciunea try ... catch nu este implementat n versiunile
inferioare Boland C++.
Firma Microsoft a adugat faciliti noi instructiunii try n Visual C++. Este vorba de
ramurile except i finally. Pentru detalii despre aceste ramuri consultai documentaia MSDN
pentru Visual Studio.

Rezumat

Am vzut ce este o excepie, cum se trateaz o excepie n C i n C++. Tratarea
excepiilor n C++ se poate face ntr-o manier modern i elegant folosind instruciunea try ...
catch. Eventuala excepie generat n interiorul blocului try poate fi captat pe una din ramurile
catch n funcie de valoarea aruncat de instruciunea throw.

Tem

Modificai funciile legate de tipul Multime din capitolul anterior aa nct s fie tratate i
eventualele erori ce pot aprea n utilizarea lor.
34
PARTEA A DOUA


Obiective

n partea a doua ne propunem s studiem programarea orientat pe obiecte dintr-o
perspectiv pur teoretic, dup care vom vedea cum se poate efectiv programa orientat pe obiecte
n C++. Vom studia modul n care se scrie o clas, o metod, un constructor, un destructor, cum
se instaniaz o clas, cum se motenete etc.

2.1. Prezentare teoretic a P.O.O.

La baza programrii orientate pe obiecte (POO) st conceptul de clas. Clasa este o
colecie de cmpuri (date) i metode (funcii) care n general utilizeaz i prelucreaz datele
clasei. Metodele se mai numesc i funcii membre clasei. n C++, o clas se redacteaz cu
ajutorul tipului class, care poate fi considerat o mbuntire a tipului struct din C.
O clas este un tip abstract de date, la facilitile creia avem acces prin intermediul unui
obiect definit ca o instan a acelei clase. Obiectul este de fapt o variabil de tip clas. Un obiect
poate fi comparat cu o cutie neagr n care introducem i extrage informaii despre care ne
asigurm cnd proiectm i redactm clasa c se prelucreaz corect. Obiectele sunt nite piese
ce le asamblm pe o plac de baz (programul principal) pentru a obine aplicaia dorit.
Programarea orientat pe obiecte este mai mult dect o tehnic de programare, ea este un
mod de gndire. Dup cum am mai spus, ntr-o clas gsim cmpuri i metode. A programa
orientat pe obiecte nu nseamn ns a scrie o mulime de date i funcii grupate ntr-o clas. Este
mult mai mult dect att. Cnd se scrie un program trebuie simit o mprire fireasc n module
de sine stttoare a codului aa nct ele s conduc apoi la o mbinare natural i facil.
Programarea orientat pe obiecte (n engleza object oriented programming - OOP), pe
scurt POO, crete modularitatea programului, iar depanarea i modificrile programelor ale caror
cod este scris orientat pe obiecte se realizeaz mult mai uor. De asemenea, codul redactat
orientat pe obiecte poate fi oricnd refolosit atunci cnd se scriu programe noi n care apar idei
asemntoare.
POO devine indispensabil atunci cnd se scriu programe de dimensiuni cel puin medii.
n cazul acestor programe este necesar aportul mai multor programatori, care pot fi specializai
pe diferite domenii. Problema se mparte n probleme mai mici, care sunt repartizate la
programatori diferii. Redactarea obiect orientat permite mbinarea mult mai uoar a
secvenelor de program, fr a fi nevoie de conversii sau adaptri. Scriind codul orientat pe
obiecte crem o trus de unelte care crete n timp, unelte pe care le putem refolosi ulterior.
n continuare vom prezenta restul conceptelor care stau la baza programrii orientate pe
obiecte: abstractizarea datelor, motenirea, polimorfismul, ncapsularea datelor.
Abstractizarea datelor, n englez - data abstraction, reprezint procesul de definire a
unui tip de date denumit tip abstract de date (n englez abstract data type, sau pe scurt
ADT), recurgnd i la ascunderea datelor.
Definirea unui tip abstract de date implic specificarea reprezentrii interne a datelor
pentru acel tip, precum i un set suficient de funcii cu ajutorul crora putem utiliza acel tip de
date fr a fi nescesar cunoaterea structurii sale interne. Ascunderea datelor asigur
modificarea valorilor acestor date fr a altera buna funcionare a programelor care apeleaz
funciile scrise pentru tipul abstract de date.
Abstractizarea datelor nu este un concept legat neaprat de POO. Limbajul C ofer cteva
exemple de tipuri abstracte de date. De exemplu tipul FILE este o structur complex de date
scris pentru lucrul cu fiiere n C, pentru care nu avem nevoie s cunoatem cmpurile sale,
35
atta timp ct avem definite suficiente funcii care lucreaz cu acest tip de date: fopen, fclose,
fwrite, fread, fprintf, fscanf, fgets, fputs, fgetc, fputc, feof, fseek, ftell etc. Toate aceste funcii
realizeaz o interfa de lucru cu fiiere, la baza creia st ns tipul FILE.
Clasele sunt de departe ns cele mai bune exemple de tipuri abstracte de date.
Datorit faptului c o clas este un tip de date deosebit de complex, crearea unui obiect
(alocarea memoriei, iniializarea datelor etc.) se face prin intermediul unei funcii membre
speciale numite constructor.
n majoritatea limbajelor eliberarea memoriei alocate pentru un obiect se face prin
intermediul unei alte funcii membre speciale denumite destructor.
Cnd vorbim despre programarea orientat pe obiecte, prin ncapsularea datelor
nelegem faptul c accesul la date se poate face doar prin intermediul unui obiect. Mai mult,
datele declarate ca fiind private (cele care in de buctria intern a unei clase) nu pot fi accesate
dect n momentul descrierii clasei.
Motenirea (n englez - inheritance) este un alt concept care st la baza reutilizrii
codului orientat pe obiecte. O clas poate moteni caracteristicile uneia sau mai multor clase.
Noua clas poate extinde sau/i particulariza facilitaile motenite. Clasa motenit se numete
clasa de baz n englez - base class, iar cea care motenete se numete clasa derivat, n
englez - derived class. O colecie de clase realizate pe principiul motenirii se numete ierarhie
de clase. ntr-o ierarhie clasele sunt organizate arborescent, rdcina arborelui este n general o
clas abstract (ce nu poate fi instaniat) care traseaz specificul ierarhiei.
Dup cum am spus, motenirea este folosit n dou scopuri: pentru a particulariza o clas
(de exemplu ptratul motenete dreptunghiul) sau/i pentru a mbogi o clas prin adugarea de
faciliti noi clasei derivate (de exemplu unei clase ce lucreaz cu un polinom i putem aduga o
nou metod cum ar fi cea pentru calculul valorii ntr-un punct). n cele mai multe situaii ns
clasa derivat particularizeaz o clas deja existent i o mbogete n acelai timp (de
exemplu, la clasa dedicat ptratului putem aduga o metod pentru calcularea razei cercului
nscris, metod ce nu putea exista n clasa scris pentru dreptunghi).
Limbajul C++ este unul dintre puinele limbaje care accept motenirea multipl, adic
o clas poate fi derivat din mai multe clase.
n sens general, prin polimorfism inelegem proprietatea de a avea mai multe forme.
Cnd vorbim ns despre POO, polimorfismul const n faptul c o metod cu acelai nume i
aceeai parametri poate fi implementat diferit pe nivele diferite n cadrul aceleai ierarhii de
clase.
n continuare prezentm o posibil reet de scriere a unei clase noi:

1. Cutm dac exist ceva faciliti ntr-o clas sau n mai multe clase deja existente pe
care s le putem adapta clasei noastre. Dac exist, atunci tot ceea ce avem de fcut
este s motenim aceste clase.
2. Trebuie s alegem bine nc de la nceput datele membre clasei. De obicei majoritatea
datelor membre clasei (dac nu chiar toate) se declar private sau protected, n cazul
n care clasa este posibil s fie reutilizat n alt scop.
3. Scriem suficieni constructori pentru clas. Acetia vor crea obiecte sub diverse
forme, iniializnd diferit cmpurile clasei.
4. Trebuie scrise suficiente funcii (metode) pentru prelucrarea cmpurilor. Nu trebuie
s fim zgrcii n a scrie metode, chiar dac par a nu fi de folos ntr-o prim faz. Nu
se tie niciodat cnd va fi nevoie de ele n proiectul nostru sau dac vom refolosi
clasa alt dat. Scriem metode de tipul set i get. Ele modific valorile
cmpurilor i, respectiv, le interogheaz. Acest lucru este necesar deoarece, dup cum
am vzut, n general cmpurile sunt ascunse (private). Prin intermediul lor limitm
accesul i ne asigurm de atribuirea corect de valori cmpurilor.
36
5. O parte dintre metode, cele care in de buctria intern a clasei (sunt folosite numai
n interiorul clasei), le vom ascunde, adic le vom declara private sau protected.
6. Scriem metode aa nct obiectul clasei s poat interaciona cu alte obiecte. Pregtim
astfel posibilitatea de a lega modulul nostru la un proiect (un program).

Urmrind reeta de mai sus s vedem cum proiectm o clas care lucreaz cu un numr
raional, n care numrtorul i numitorul sunt memorai separat, n dou cmpuri ntregi:

1. Clasa nu va fi derivat din alta clas.
2. Cmpurile clasei sunt numrtorul i numitorul, ambele private i de tip ntreg.
3. Este util a scrie cel puin un constructor cu ntrebuinri multiple. El iniializeaz
numrtorul i numitorul cu dou valori ntregi primite ambele ca parametri. Putem
fixa valorile implicite 0 i 1 pentru numrtor i respectiv numitor.
4. Scriem funcii care returneaz numrtorul i numitorul i eventual funcii pentru
modificarea numrtorului i a numitorului.
5. Scriem o funcie ascuns (privat sau protejat) care simplific fracia aa nct s se
obin una ireductibil. Vom folosi aceast funcie de fiecare dat cnd va fi nevoie,
aa nct n permanen fracia s fie memorat n forma ireductibil. Metoda va fi
folosit practic numai atunci cnd apar modificri ale numrtorului sau/i a
numitorului.
6. Scriem metode pentru adunarea, scderea, nmulirea, mprirea numrului nostru cu
un alt obiect raional i cu un numr ntreg etc.

Dup ce vom avea suficiente cunotine vom implementa aceast clas ca un prim
exemplu.

2.2. Programarea orientat pe obiecte n C++

Obiective

Ne propunem s vedem cum se scrie cod orientat pe obiecte n C++. Mai nti o s vedem
care sunt neajunsurile POO din C folosind tipul struct i vom vedea efectiv cum se declar i
descrie o clas n C++, o metod, un constructor, un destructor, ce este i cum se scrie o funcie
prieten unei clase etc. Ne vom concentra apoi asupra motenirii din C++, care este una dintre
cele mai pretenioase, n special datorit faptului c n C++ exist motenire multipl.

2.2.1. Neajunsuri ale POO n C

Cu ajutorul tipului struct din C putem scrie cod orientat pe obiecte, ns, dup cum vom
vedea, cod adevrat orientat pe obiecte putem scriem numai n C++ cu ajutorul tipului class. S
prezentm ns pe scurt neajunsurile programrii orientate pe obiecte din C, folosind tipul struct.
n interiorul tipului struct, pe lng cmpuri putem avea i funcii.
Membrii unei structuri (fie ei date sau funcii) pot fi accesai direct, adic toi se
comport ca fiind publici, declararea unor date sau funcii interne private sau protected fiind
imposibil.
Trebuie avut grij asupra corectitudinii proiectrii unui obiect n C (reinut ntr-o
variabil de tip struct), astfel nct acesta s suporte motenirea. Este necesar scrierea unor
funcii suplimentare pentru a permite unui obiect s-i acceseze corect datele.
Motenirea comportrii obiectului ca rspuns la diferite mesaje necesit funcii suport
pentru a se realiza corect.
37
Toate aceste probleme nu exist dac utilizm tipul class din C++, care rspunde n
totalitate cerinelor ridicate de POO.

2.2.2. Declaraia unei clase n C++

Declaraia unei clase n C++ este:

class nume_cls [:[virtual] [public/protected/private] clasa_baza1,
[virtual] [public/protected/private] clasa_baza2,]
{
public:
// declaratii de date, functii membre publice
protected:
// declaratii de date, functii membre protejate
private:
// declaratii de date, functii membre private
}[obiect, *obiect2 ];

Menionm c tot ceea ce apare ntre paranteze ptrate este interpretat a fi opional.
Ca i n cazul tipurilor struct, union i enum din C, descrierea unei clase se ncheie cu ;
(punct i virgul). ntre acolada nchis i caracterul ; (punct i virgul) pot fi definite obiecte
sau/i pointeri ctre tipul class.
Dup numele clasei urmeaz opional : (dou puncte) i numele claselor motenite
desprite prin virgul. Vom reveni cu detalii cnd vom discuta despre motenire.
Declaraiile datelor i metodele pot fi precedate de unul dintre specificatorii de acces
(care sunt cuvinte cheie, rezervate) public, protected sau private. n lipsa unui specificator de
acces, datele i metodele sunt considerate a fi implicit private !
Membrii de tip public pot fi accesai n interiorul clasei, n interiorul eventualelor clase
derivate i prin intermediul unui obiect al clasei respective sau al unei clase derivate sub forma
obiect.membru_public.
Membrii declarai protected ai unei clase pot fi accesai n interiorul clasei sau n
interiorul unei eventuale clase derivate, dar nu pot fi accesai sub forma
obiect.membru_protected, unde obiect este o instan a clasei respective sau a unei clase derivate
din aceasta.
Datele i metodele de tip private pot fi accesate numai din interiorul clasei la care sunt
membre.
n general, datele unei clase se declar private sau protected, iar majoritatea metodelor se
declar publice. Accesul la datele clasei se va face prin intermediul unor funcii membre special
definite n acest scop. Aceste funcii sunt de tipul set (pentru setarea valorilor cmpurilor),
respectiv get (care returneaz valorile cmpurilor obiectului).
Ordinea definirii membrilor n funcie de modul lor de acces este opional. Pot aparea
mai multe declaraii sau nici una de tip public, protected sau private pe parcursul descrierii
clasei.

2.2.3. Declaraia i descrierea funciilor membre

Funciile membre unei clse pot fi descrise inline sau nu.
Funciile care nu conin instruciuni repetitive pot fi descrise n locul n care se definete
clasa, n aceast situaie ele fiind considerate a fi inline (vezi capitolul dedicat funciilor inline):

class nume_clasa
38
{
//....
tip_returnat functie(parametri) // fct. membra inline
{
// descriere fct. (fara instructiuni repetitive)
}
//....
};

Descrierea funciei membre de mai sus este echivalent cu:

class nume_clasa
{
//....
tip_returnat functie(parametri);
//....
};

inline tip_returnat nume_clasa::functie(parametri)
{
// corpul functiei
}

Orice funcie membr poate fi descris separat de locul n care este definit clasa, mai
mult, funciile care conin instruciuni repetitive trebuie neaprat descrise n acest mod:

class nume_clasa
{
//....
tip_returnat functie(parametri);
//....
};

tip nume_clasa :: functie(parametri) // descriere metoda
{ // care nu este inline
// corpul functiei
}

Denumirile parametrilor funciei n momentul descrierii clasei trebuie s coincid cu
denumirile parametrilor din momentul n care descriem funcia. Putem da ns numai tipul
parametrilor n momentul definirii funciei n interiorul descrierii clasei i ulterior s dm efectiv
denumire acestor parametri cnd descriem funcia:

class Test
{
void fct(Test,int);
void fct2(Test t, int k);
};

void Test::fct(Test t,int n) // aici capatata nume param.
{
39
// corpul functiei
}

void Test::fct2(Test t,int k) // aceleasi denumiri pt. param.
{
// corpul functiei
}

Dac o funcie care conine instruciuni repetitive este descris n momentul definirii
clasei, atunci la compilare suntem atenionai c aceast funcie nu va fi considerat a fi inline.

2.2.4. Constructori

n C++ constructorii se descriu ca i funciile membre obinuite, dar ei poart numele
clasei i nu au tip returnat, nici mcar void:

class nume_clasa
{
//....
nume_clasa(parametri); // declaratie constructor
//....
};

Pot exista mai muli constructori definii pentru o clas, dar cu parametri diferii. n lipsa
scrierii de constructori de ctre programator, la compilare se genereaz constructori implicii.
Un obiect se creaz static folosind unul dintre constructorii clasei astfel:

nume_clasa ob(valori_parametri); // constructie obiect static

Dac folosim constructorul fr parametri al clasei, atunci un obiect se creaz static
astfel:

nume_clasa ob; // creare obiect static folosind constructorul fara parametri

Un membru al obiectului creat static se accesez n felul urmtor:

ob.membru

Pentru a construi un obiect dinamic definim un pointer ctre clas i folosim operatorul
new:

nume_clasa *pob=new nume_clasa(valori_parametri); // constructie ob. dinamic

Crearea unui obiect dinamic folosind constructorul fr parametri se realizeaz astfel:

nume_clasa *pob=new nume_clasa; // constructie obiect dinamic

Un membru al obiectului creat dinamic se accesez n felul urmtor:

ob->membru

40
Exist o categorie special de constructori denumii constructori de copiere, despre care
vom vorbi mai trziu.


2.2.5. Destructori

Destructorul n C++ poart numele clasei precedat de semnul ~ (tilda), nu are parametri
i nici tip returnat:

class nume_clasa
{
//....
~nume_clasa(); // definire destructor
//....
};

Orice clas are un singur destructor.
Dac nu scriem destructor, atunci la compilare se genereaz unul implicit.
Destructorul clasei se apeleaz direct pentru a se distruge obiectele create static (la
prsirea unei funcii se distrug obiectele statice primite ca parametri transmii prin valoare, de
asemenea se distrug obiectele create static n corpul funciei).
Este evident c pentru a putea distruge un obiect cnd nu mai avem nevoie de el pe
parcursul execuiei programului, el trebuie creat dinamic. Distrugerea unui obiect creat anterior
dinamic se face astfel:

delete pob; // se apeleaza destructorul clasei pentru
// a distruge obiectul de le adresa pob

Prezentm n continuare o clas pentru dreptunghiuri cu laturile paralele cu axele de
coordonate. Clasa va avea dou metode: una pentru calcularea ariei i cealalt pentru perimetru.

# include <math.h> // fisierul antet ce contine definitia
functiei fabs
# include <iostream.h>

class dreptunghi
{
private:
float x1,y1,x2,y2; // datele interne clasei ce definesc dreptunghiul.
public: // (coordonatele a doua varfuri diagonal opuse)
dreptunghi(float X1,float Y1,float X2,float Y2) // constructor
{
x1=X1; // se initializeaza datele interne clasei: x1, y1, x2 si y2
x2=X2; // cu valorile primite ca parametri: X1, Y1, X2 si Y2
y1=Y1;
y2=Y2;
}
float get_x1()
{
return x1;
}
41
float arie() // metoda descrisa inline
{
return fabs((x2-x1)*(y2-y1));
}
float perimetru(); // functie membra care va fi descrisa ulterior
};

float dreptunghi::perimetru() // descrierea functiei perimetru,
{ // membra clasei dreptunghi
return 2*(fabs(x2-x1)+fabs(y2-y1));
}

Funciile membre arie i perimetru se aplic obiectului curent, cel din care se apeleaz
aceste funcii. De aceea nu este nevoie s transmitem ca parametru obiectul dreptunghi pentru
care vrem s calculm aria, respectiv perimetrul, aa cum se ntampl n cazul funciilor externe
clasei.
Pentru testarea clasei dreptunghi propunem urmtoarea funcie principal:

void main(void)
{
dreptunghi d(20,10,70,50); // se creaza obiectul d folosind constructorul
//primeste ca param. X1=20, Y1=10, X2=70, Y2=50
cout<<"Aria dreptunghiului: "<<d.arie()<<endl;
cout<<"Perimetrul dreptunghiului: "<<d.perimetru()<<endl;
cout<<"Dreptungiul are ordonata: "<<d.get_x1()<<endl;
// cout<<" Dreptungiul are ordonata: "<<d.x1;
}

Dac se ncearc accesarea uneia dintre datele membre clasei: x1, y1, x2 sau y2, atunci se
obine eroare la compilare, deoarece ele sunt declarate private. De aceea afiarea valorii reinute
n x1 din obiectul d este greit (linia comentat). Pentru a putea fi accesat valoarea x1 direct
sub forma d.x1, ea ar fi trebuit s fie declarat public n interiorul clasei. Datele membre unei
clase se declar n general cu unul dintre specificatorii private sau protected. Dac dorim s se
poat obine valoarea unui cmp privat sau protejat scriem o funcie public de tip get, aa cum
este cazul funciei get_x1 din clasa dreptunghi. Pentru modificarea valorii reinute ntr-un cmp
putem scrie o funcie public a crui nume de regul ncepe cu set. Pentru cmpul x1, funcia
membr set (definit n interioul clasei dreptunghi) este:

public:
void set_x1(float X1)
{
x1=X1; // campul privat
}

Evident, functia set_x1 poate fi apelat n funcia main de mai sus astfel:

d.set_x1(10);

2.2.6. Funcii prietene (friend) unei clase

42
Funciile prietene unei clase se declar n interiorul clasei cu ajutorul specificatorului
friend, care este un cuvnt rezervat i precede definiia funciei. Cu toate c sunt definite n
interiorul clasei, funciile prietene sunt externe clasei. Spre deosebire de functiile externe
obinuite, funciile declarate friend pot accesa ns datele private i protected ale clasei n care
sunt definite.
Schema de declarare a unei funcii prietene este:

class nume_clasa
{
//....
friend tip_returnat functie_prietena(parametri)
{ // functie prietena inline
// descriere functie
}
//....
};

Definirea funciei prieten clasei de mai sus este echivalent cu:

class nume_clasa
{
//....
friend tip_returnat functie_prietena(parametri);
//....
};

inline tip_returnat functie_prietena(parametri)
{ // descrere functie prietena inline
// corpul functiei
}

Ca i n cazul funciilor membre, funciile friend care conin instruciuni repetitive trebuie
descrise n exteriorul descrierii clasei (adic nu inline):

class nume_clasa
{
//....
friend tip_returnat functie_prietena(parametri);
//....
};

tip_returnat functie_prietena(parametri)
{
// descriere functie
}

Rescriem n continuare clasa dreptunghi de mai sus cu funcii prietene n loc de funcii
membre:

# include <math.h>
# include <iostream.h>
43

class dreptunghi
{
private:
float x1,y1,x2,y2;
public:
dreptunghi(float X1,float Y1,float X2,float Y2)
{
x1=X1; x2=X2; y1=Y1; y2=Y2;
}
float get_x1(dreptunghi d)
{
return d.x1;
}
float set_x1(dreptunghi,float);
friend float arie(dreptunghi d) // descrierea functiei prietena clasei
// dreptunghi. Se primeste ca argument
{ // un obiect de tipul clasei dreptunghi
return fabs((d.x2-d.x1)*(d.y2-d.y1));
}
friend float perimetru(dreptunghi); // functie prietena ce va fi
// descrisa ulterior
};

float set_x1(dreptunghi d,float X1)
{
d.x1=X1;
}

float perimetru(dreptunghi d)
{
return 2*(fabs(d.x2-d.x1)+fabs(d.y2-d.y1));
}

void main(void)
{
dreptunghi d(20,10,70,50);
cout<<"Aria dreptunghiului: "<<arie(d)<<endl;
cout<<"Perimetrul dreptunghiului: "<<perimetru(d)<<endl;
cout<<"Dreptungiul are ordonata: "<<get_x1(d)<<endl;
}

ntr-o funcie prieten, din cauz c este extern clasei, de obicei transmitem ca
parametru i obiectul asupra cruia se refer apelul funciei. Astfel, de exemplu n cazul functiei
set_x1 trimitem ca parametru obiectul d pentru care dorim s setm valoarea cmpului x1.
Aadar, n general o funcie prieten unei clase are un parametru suplimentar fa de situaia n
care aceeai funcie ar fi fost scris ca fiind membr clasei. Aceai regul se aplic i suprancrii
unui operator ca fiind prieten clasei.
Din cauz c sunt funcii prietene clasei, datele interne private ale obiectului d de tip
dreptunghi au putut fi accesate sub forma: d.x1, d.y1, d.x2 i d.y2. Evident, dac funciile nu erau
prietene clasei, ci erau funcii externe obinuite (definiiile lor nu apreau declarate friend n
interiorul definiei clasei), accesul la datele membre obiectului d nu era posibil.
44

2.2.7. Declaraia, descrierea operatorilor pt. o clas

Operatorii pot fi supraincrcai ca membrii unei clase sau ca fiind prieteni unei clase.
Regulile legate de modul de definire i descriere ale unui operator sunt aceleai ca i pentru orice
funcie membr, respectiv prieten unei clase. Un operator nu poate fi ns definit sub ambele
forme (interior i exterior clasei) pentru aceleai tipuri de operanzi. Operatorii pot fi definii de
mai multe ori n interiorul aceleai clase, dar cu tipuri pentru parametri diferite.
Dac un operator de aritate n este definit ca fiind membru unei clase, atunci el va avea n-
1 parametri. Aadar, un operator unar nu va avea nici un parametru i se va aplica obiectului care
l apeleaz. Pentru un operator binar avem un singur parametru. Operatorul binar se aplic ntre
primul obiect, considerat a fi obiectul curent, din care se apeleaz operatorul, i valoarea primit
ca argument (vezi operatorul + din clasa complex prezentat mai jos).
Dac un operator de aritate n este definit ca fiind prieten unei clase, atunci el va avea n
parametri.
n general operatorii <<, >> vor fi definii ca fiind prieteni clasei pentru c primul
argument este de obicei un flux n care se trimit, respectiv din care se extrag valorile cmpurilor
obiectului.
n continuare prezentm o clas care lucreaz cu un numr complex:

# include <iostream.h>
# include <conio.h>

class complex
{
private:
float re,im;
public:
complex(float x=0,float y=0) // constructor cu parametri cu valori implicite
{
re=x;
im=y;
}
complex operator+(complex z2) // operator membru clasei pentru adunare
{
complex z;
z.re=re+z2.re;
z.im=im+z2.im;
return z;
}
complex operator+(float r) // adunare cu un numar real
{
complex z;
z.re=re+r;
z.im=im;
return z;
}
friend complex operator+(float r,complex z2) // real+complex
{
complex z;
z.re=r+z2.re;
45
z.im=z2.im;
return z;
}
friend complex operator-(complex z1,complex z2) // operator prieten
{
complex z;
z.re=z1.re-z2.re;
z.im=z1.im-z2.im;
return z;
}
friend istream& operator>>(istream &fl,complex &z);
friend istream& operator<<(istream &fl,complex &z);
};

istream& operator>>(istream &fl,complex &z)
{
fl>>z.re>>z.im;
return fl;
}

ostream& operator<<(ostream &fl,complex &z)
{
if (z.im>=0) fl<<z.re<<"+"<<z.im<<"i";
else fl<<z.re<<z.im<<"i";
return fl;
}

Pentru testarea clasei complex propunem urmtoarea funcie principal:

void main(void)
{
complex z,z1(1),z2(2,5);

cout<<"Suma este: "<<(z1+z2)<<endl;
cout<<"Diferenta este: "<<(z1-z2)<<endl;
cout<<"Complex+real: "<<(z1+7)<<endl;
cout<<"Real+complex: "<<(8.2+z2)<<endl;
}

n exemplul de mai sus, constructorul are valori implicite pentru cei doi parametri. Astfel,
pentru obiectul z din programul principal datele interne re i im se vor iniializa cu valorile
implicite, adic ambele vor fi 0. Obiectul z1 din programul principal va avea re iniializat cu 1 i
im cu valoarea implicit 0. n fine, obiectul z2 va avea re iniializat cu 2, iar im cu 5 (valorile
transmise ca parametri n constructor).
Pentru a suprancrca operatorii de atribuire corect, i nu numai pentru acetia, folosim
pointerul this.

2.2.8. Membri statici

n C (i n C++), o variabil local ntr-o funcie poate fi declarat ca fiind static. Pentru
aceast variabil se pstrez valoarea i, cnd se reapeleaz funcia, variabila va fi iniializat cu
46
valoarea pe care a avut-o la apelul anterior. De fapt pentru variabila local static este alocat o
zon de memorie de la nceputul pn la sfritul execuiei programului, dar accesul la variabil
nu este posibil dect din interioul funciei n care este declarat. Iat un exemplu simplu:

#include<stdio.h>

void fct(void)
{
static int x=1;
x++;
printf("%d ",x);
}

void main(void)
{
int i;
for (i=0;i<10;i++) fct();
}

Variabila static x este iniializat la nceputul execuiei programului cu valoarea 1. La
fiecare apel al funciei fct variabila x se incrementez cu o unitate i se afieaz noua sa valoare.
Aadar pe ecran n urma execuiei programului va aprea:

2 3 4 5 6 7 8 9 10 11

n C++ un membru al unei clase poate fi declarat ca fiind static. Un membru static este
folosit n comun de toate obiectele clasei. Pentru un cmp static se aloc o zon (unic) de
memorie nc de la nceputul execuiei programului. Cmpul static va putea fi interogat i
modificat de toate obiectele clasei, modificri ce vor fi vizibile n toate obiectele clasei. Mai
mult, un membru static poate fi accesat direct (dac este vizibil n acel loc) sub forma:

NumeClasa::MembruStatic;

n continuare vom da un exemplu n care ntr-un cmp static vom memora numrul de
instane create pentru o clas:

#include<iostream.h>

class test
{
private:
static int n;
public:
test()
{
n++;
}
static int NrInstante()
{
return n;
}
47
};

int test::n=0; // definire si initializare camp static

void main()
{
test t1,t2;
cout<<test::NrInstante(); // apel metoda statica
}

Evident, n urma execuiei programului, pe ecran se va afia numrul 2.
Orice cmp static trebuie definit n exteriorul descrierii clasei. n momentul definirii se
folosete operatorul de rezoluie pentru indicarea clasei la care este membru cmpul static (vezi
cmpul x din exemplul de mai sus).

2.2.9. Pointerul this

Pointer-ul this (acesta n englez) a fost introdus n C++ pentru a indica adresa la care
se afl memorat obiectul curent. El este folosit numai n corpul funciilor membre, inclusiv n
constructori i destructor, pentru a ne putea referi la obiectul din care s-a apelat funcia, respectiv
la obiectul care se construiete sau se distruge.
Compilatorul C++ modific fiecare funcie membr dintr-o clas astfel:

1) Transmite un argument n plus cu numele this, care este de fapt un pointer ctre
obiectul din care s-a apelat funcia membr (this indic adresa obiectului care a apelat
metoda).
2) Este adugat prefixul this-> tuturor variabilelor i funciilor membre apelate din
obiectul curent (dac nu au deja dat de programator).

Apelul unui membru (dat sau funcie) al unei clase din interiorul unei funcii membre se
poate face sub forma this->membru, scriere care este echivalent cu membru (fr
explicitarea this->) dac nu exist cumva un parametru al funciei cu numele membru.
O instruciune de forma return *this returneaz o copie a obiectul curent (aflat la
adresa this).
S vedem n continuare cum se definete corect operatorul = (de atribuire) pentru dou
numere complexe. Dup cum se tie, expresia a=b are valoarea ce s-a atribuit variabilei a.
Aadar, o atribuire de numere complexe de forma z1=z2 va trebui sa aib valoarea atribuit lui
z1, adic operatorul = definit n clasa complex va trebui s returneze valoarea obiectului curent:

complex operator=(complex &z) // obiect_curent = z
{
a=z.a;
b=z.b;
return *this;
}

Evident, suprancrcarea operatorului = de mai sus va fi descris n interiorul clasei
complex.
Pentru a nelege mai bine ceea ce a fost prezentat pn acum legat de programarea
orientat pe obiecte din C++, dm un exemplu mai amplu. Este vorba despre o clas ce tie s
48
lucreze cu un numr raional, considerat a fi memorat printr-o pereche de numere ntregi de tip
long, reprezentand numrtorul, respectiv numitorul numrului raional.

# include <iostream.h>

class rational
{
private:
long a,b;
void simplificare();
public:
rational(long A=0,long B=1)
{
a=A;
b=B;
simplificare();
}
long numarator()
{
return a;
}
long numitor()
{
return b;
}
rational operator+(rational x)
{
rational y;
y.a=a*x.b+b*x.a;
y.b=b*x.b;
y.simplificare();
return y;
}
rational operator-(rational x)
{
rational y;
y.a=a*x.b-b*x.a;
y.b=b*x.b;
y.simplificare();
return y;
}
rational operator*(rational x)
{
rational y;
y.a=a*x.a;
y.b=b*x.b;
y.simplificare();
return y;
}
rational operator/(rational x)
{
rational y;
49
y.a=a*x.b;
y.b=b*x.a;
y.simplificare();
return y;
}
rational operator=(rational&x)
{
a=x.a;
b=x.b;
return *this;
}
rational operator+=(rational x)
{
return *this=*this+x;
}
rational operator-=(rational x)
{
return *this=*this-x;
}
rational operator*=(rational x)
{
return *this=*this*x;
}
rational operator/=(rational x)
{
return *this=*this/x;
}
int operator==(rational x)
{
return a==x.a && b==x.b;
}
int operator!=(rational x)
{
return !(*this==x);
}
rational operator++() // preincrementare ++r
{
a+=b;
return *this;
}
rational operator--() // predecrementare --r
{
a-=b;
return *this;
}
rational operator++(int) // postincrementare r++
{
rational c=*this;
a+=b;
return c;
}
rational operator--(int) // postdecrementare r--
50
{
rational c=*this;
a-=b;
return c;
}
long operator!()
{
return !a;
}
friend ostream& operator<<(ostream& fl,rational x)
{
fl<<"("<<x.a<<","<<x.b<<")";
return fl;
}
friend istream& operator>>(istream& fl,rational &x)
{
fl>>x.a>>x.b;
return fl;
}
};

void rational::simplificare()
{
long A=a,B=b,r;

while (B) // algoritmul lui Euclid pentru c.m.m.d.c.(a,b)
{
r=A%B;
A=B;
B=r;
}
if (A) // simplificare prin A = c.m.m.d.c.(a,b)
{
a/=A;
b/=A;
}
}

void main(void)
{
rational x(7,15),y(1,5),z;
z=x+y;
cout<<x<<"+"<<y<<"="<<z<<endl;
cout<<x<<"-"<<y<<"="<<(x-y)<<endl;
cout<<x<<"*"<<y<<"="<<(x*y)<<endl;
cout<<x<<"/"<<y<<"="<<(x/y)<<endl;
}

Propunem cititorului:

1. Implementarea operatorilor >>, <<, *, /, =, +=, -=, *=, /=, !, == i != ca membri sau
prieteni clasei complex.
51
2. Implementarea operatorilor <, <=, > i >= pentru compararea a dou numere
raionale.

2.2.10. Constructorul de copiere

Scrierea unui constructor de copiere este necesar numai ntr-o unei clas n care exist
alocare dinamic a memoriei. Constructorul de copiere trebuie s asigure copierea corect a
instanelor unei clase.
Constructorul de copiere poate fi folosit direct de programator pentru crearea unui obiect
nou (ca orice constructor), dar el este apelat n general automat n timpul execuiei programului
atunci cnd se transmite un obiect ca parametru ntr-o funcie prin valoare i la returnarea unui
obiect prin valoare dintr-o funcie.
n lipsa unui constructor de copiere definit de programator, compilatorul creaz un
constructor de copiere implicit, dar care nu va ti ns s fac alocare dinamic de memorie.
Dac n clas avem un cmp de tip pointer, atunci, dup copierea unui obiect, pentru ambele
obiecte (cel vechi i cel nou construit) cmpurile de tip pointer vor indica aceeai zon de
memorie. Astfel, dac modificm ceva la aceast adres prin intermediul cmpului pointer al
unui obiect, modificarea va fi vizibil i din celellat obiect. Acest lucru nu este n general dorit.
De aceea programatorul trebuie s scrie constructorul de copiere care va aloca o zon nou de
memorie pe care o va reine n cmpul de tip pointer al obiectului creat i n aceast zon de
memorie va copia ce se afl la adresa cmpului obiectului care este copiat.
Un constructor de copiere are urmtoarea structur:

nume_clasa (const nume_clasa &);

Constructorul de copiere primete ca argument o constant referin ctre obiectul care
urmeaz a fi copiat.
Dac vrem ca o valoarea unui parametru s nu poat fi modificat n interiorul unei
funcii, punem n faa parametrului cuvntul rezervat const. Cu alte cuvinte un astfel de
parametru devine o constant n corpul funciei.
Spre exemplificare prezentm o clas ce lucreaz cu un string alocat dinamic.

#include<stdio.h>
#include<string.h>
#include<iostream.h>

class string
{
private:
char *s; // sirul de caractere retinut in string
public:
string(char *st="") // constructor
{
s=new char[strlen(st)+1];
strcpy(s,st);
}
string(const string &str) // contructor de copiere
{
delete [] s;
s=new char[strlen(str.s)+1];
strcpy(s, str.s);
52
}
~string() // destructor
{
delete [] s;
}
string operator+(string str) // apelare constructor copiere
{
char *st;
st=new char[strlen(s)+strlen(str.s)+1];
string str2(st);
sprintf(str2.s,"%s%s",s,str.s);
return str2; // apelare constructor de copiere
}
string operator=(const string &str) // atribuire
{
delete [] s;
s=new char[strlen(str.s)+1];
strcpy(s, str.s);
return *this; // se apeleaza constructorul de copiere
}
string operator+=(const string &str)
{
*this=*this+str;
return *this; // apelare constructor de copiere
}
int operator==(const string &str) // identice ?
{
if (!strcmp(s,str.s)) return 1;
return 0;
}
int operator<(string str) // apelare constructor de copiere
{
if (strcmp(s,str.s)<0) return 1;
return 0;
}
int operator<=(const string &str)
{
if (strcmp(s,str.s)<=0) return 1;
return 0;
}
int operator>(const string &str)
{
if (strcmp(s,str.s)>0) return 1;
return 0;
}
int operator>=(const string &str)
{
if (strcmp(s,str.s)>=0) return 1;
return 0;
}
void set(char *st) // setararea unui string
{
53
delete [] s;
s=new char[strlen(st)+1];
strcpy(s,st);
}
void get(char *st) // extragere sir caractere din obiectul string
{
strcpy(st,s);
}
int operator!() // se returneaza lungimea string-ului
{
return strlen(s);
}
char operator[](int i) // returneaza caracterul de pe pozitia i
{
return s[i];
}
friend ostream& operator<<(ostream &fl,const string &str)
{
fl<<str.s;
return fl;
}
friend istream& operator>>(istream &fl,const string &str)
{
fl>>str.s;
return fl;
}
};

void main(void) // testarea clasei string
{
string s1("string-ul 1"),s2,s;
char st[100];

s2.set("string-ul 2");
s=s1+s2;
cout<<"Concatenarea celor doua string-uri: "<<s<<endl;
s+=s1;
cout<<"Concatenarea celor doua string-uri: "<<s<<endl;
cout<<"Lungimea string-ului: "<<!s<<endl;
cout<<"Pe pozitia 5 se afla caracterul: "<<s[4]<<endl;
if (s1==s2) cout<<"String-urile sunt identice"<<endl;
else cout<<"String-urile difera"<<endl;
if (s1<s2) cout<<"s1 < s2"<<endl;
s.get(st);
cout<<"String-ul extras: "<<st<<endl;
}

Constructorul de copiere se apeleaz cnd se transmite un obiect de tip string prin valoare
(vezi operatorii + i <). De asemenea, de fiecare dat cnd se returneaz un obiect de tip string
(vezi operatorii +, = i +=), este apelat constructorul de copiere definit n interiorul clasei, care
spune modul n care se face efectiv copierea (cu alocrile i eliberrile de memorie aferente).
54
Pentru a vedea efectiv traseul de execuie pentru programul de mai sus, propunem
cititorului rularea acestuia pas cu pas. Rularea pas cu pas n mediul de programare Borland se
face cu ajutorul butonului F7 sau F8. Lsarea liber a execuiei programului pn se ajunge la
linia curent (pe care se afl cursorul) se face apsnd butonul F4. Pentru ca s fie posibil
urmrirea execuiei programului, n meniul Options, la Debugger, trebuie bifat On n Source
Debugging. n Visual C++ urmrirea execuiei pas cu pas a programului se face apasand butonul
F11, iar lsarea liber a execuiei programului pn la linia curent se face apsnd Ctrl+F10.
Lsm plcerea cititorului de a completa alte i funcii n clasa string cum ar fi pentru
cutarea unui string n alt string, nlocuirea unui ir de caractere ntr-un string cu un alt ir de
caractere, extragerea unui subir de caractere dintr-un string etc.


2.2.11. Motenirea n C++

n C++ o clas poate sa nu fie derivat din nici o alt clas, sau poate deriva una sau mai
multe clase de baz:

class nume_clasa [ : [virtual] [public/protected/private] clasa_baza1,
[virtual] [public/protected/private] clasa_baza2, ... ]
{
//
};

n funcie de specificarea modului de derivare (public, protected sau private), accesul la
datele i metodele clasei de baz este restricionat mai mult sau mai puin n clasa derivat (cel
mai puin prin public i cel mai mult prin private).
Specificarea modului de derivare este opional. Implicit, se ia, ca i la definirea datelor
i metodelor interne clasei, modul private (n lipsa specificrii unuia de ctre programator).
n continuare prezentm ntr-un tabel efectul pe care l au specificatorii modului de
derivare asupra datelor i metodelor clasei de baza n clasa derivat:

Tip date i metode
din clasa de baz
Specificator mod de
derivare
Modul n care sunt vzute n
clasa derivat datele i
metodele clasei de baz
public public public
public protected protected
public private protected
protected public protected
protected protected protected
protected private protected
private public private
private protected private
private private private

Dup cum se poate vedea din tabelul de mai sus pentru a avea acces ct mai mare la
membrii clasei de baz este indicat folosirea specificatorului public n momentul derivrii.
Constructorul unei clase derivate poate apela constructorul clasei de baz, crendu-se n
memorie un obiect al clasei de baz (denumit sub-obiect al clasei derivate), care este vzut ca o
particularizare a obiectului clasei de baz la un obiect de tipul clasei derivate. Apelul
constructorului clasei de baz se face astfel:
55

class deriv: public baza
{
// ....
deriv(parametri_constructor_deriv):baza(parametri_constructor_baza)
{
// ....
}
// ....
};

Parametrii constructorului baza (la apelul din clasa derivat) se dau n funcie de
parametrii constructorului deriv. De exemplu, pentru o clas patrat derivat dintr-o clas
dreptunghi (ambele cu laturile paralele cu axele de coordonate), apelul constructorului
dreptunghi la definirea constructorului patrat se poate face astfel:

class patrat: public dreptunghi
{
private:
float x,y,l;
public:
patrat(float X, float Y, float L): public dreptunghi(X, Y, X+L, Y+L)
{
x=X;
y=Y;
l=L;
}
};

Dreptunghiul din exemplul de mai sus este definit prin coordonatele a dou vrfuri
diagonal opuse, iar ptratul prin coordonatele vrfului stnga-sus i prin lungimea laturii sale. De
aceea, ptratul este vzut ca un dreptunghi particular, avnd cele dou vrfuri diagonal opuse de
coordonate (X,Y), respectiv (X+L,Y+L).
Asupra motenirii multiple o s revenim dup ce introducem noiunea de virtual.

2.2.12. Funcii virtuale

n clase diferite n cadrul unei ierarhii pot aprea funcii cu aceeai semntur (acelai
nume i aceiai parametri), n englez overriding. Astfel, putem avea situaia n care ntr-o clas
derivat exist mai multe funcii cu aceeai semnatur (unele motenite din clasele de pe nivele
superioare ale ierarhiei i eventual una din clasa derivat). n aceast situaie se pune problema
crei dintre aceste funcii se va rsfrnge apelul dintr-un obiect alocat static al clasei derivate.
Regula este c se apeleaz funcia din clasa derivat (dac exist), iar dac nu exist o functie n
clasa derivat, atunci se caut funcia de jos n sus n ierarhie. Dac dorim s apelm o anumit
funcie de pe un anumit nivel, atunci folosim operatorul de rezoluie pentru a specifica din ce
clas face parte funcia dorit:

clasa::functie(parametri_de_apel);

Dup cum putem vedea, problemele sunt rezolvate ntr-o manier elegant atunci cnd se
lucreaz cu obiecte alocate static.
56
Dac lucrm ns cu pointeri ctre clas, problemele se complic. Putem defini un pointer
p ctre clasa de baza B care reine adresa unui obiect dintr-o clas derivat D. Cnd se apeleaz o
funcie sub forma p.functie(), funcia este cutat mai nti n clasa B ctre care este definit
pointerul p, ci nu n clasa D aa cum ne-am putea atepta. Mai mult, dac funcia exist n clasa
D i nu exist n B, vom obine eroare la compilare. De fapt, pointerul p reine adresa ctre
subobiectul din clasa B, construit odat cu obiectul clasei derivate D, din cauza c p este un
pointer ctre clasa B.
Iat n continuare situaia descris mai sus:

#include<iostream.h>

class B
{
public:
B()
{
cout<<"Constructor clasa de baza"<<endl;
}
void functie()
{
cout<<"Functie clasa de baza"<<endl;
}
};

class D:public B
{
public:
int x;
D()
{
cout<<"Constructor clasa derivata"<<endl;
}
void functie()
{
cout<<"Functie clasa derivata"<<endl;
}
};

void main()
{
B *p=new D;
p->functie();
}

Dup cum am vzut, este evident c pe ecran n urma execuiei programului de mai sus
vor aprea mesajele:

Constructor clasa de baza
Constructor clasa derivata
Functie clasa de baza

57
Dac functie nu era implementat n clasa de baza B, obineam eroare la compilare pentru
c pointerul p reine adresa subobiectului i este evident c n aceast situaie la adresa indicat
de p nu exist nimic referitor la clasa D. Din aceleai considerente, dac ncercm referirea la
campul x sub forma p->x, vom obine de asemenea eroare la compilare.
Exist posibilitatea ca o funcie membr unei clase s fie declarat ca fiind virtual.

virtual tip_ret functie_membra(parametri)

S precizm i faptul c numai funciile membre unei clase pot fi declarate ca fiind
virtuale.
Declaraia unei funcii din clasa de baz ca fiind virtuale se adreseaz situaiilor de tipul
celei de mai sus (clasa D este derivat din B i B *p=new D;). Astfel, dac n faa declaraiei
metodei functie din clasa B punem cuvntul rezervat virtual, atunci n urma execuie
programului de mai sus pe ecran se vor afia mesajele:

Constructor clasa de baza
Constructor clasa derivata
Functie clasa derivata

Deci, declaraia virtual a funciei din clasa de baz a ajutat la identificarea corect a
apelului funciei din clasa derivat.
S vedem ce se ntmpl de fapt atunci cnd declarm o funcie ca fiind virtual.
Cuvntul cheie virtual precede o metod a unei clase i semnaleaz c, dac o funcie
este definit ntr-o clas derivat, aceasta trebuie apelat prin intermediul unui pointer.
Compilatorul C++ construiete un tabel n memorie denumit tablou virtual (n englez Virtual
Memory Table VMT) cu pointerii la funciile virtuale pentru fiecare clas. Fiecare instan a
unei clase are un pointer ctre tabelul virtual propriu. Cu ajutorul acestei reguli compilatorul C++
poate realiza legarea dinamic ntre apelul funciei virtuale i un apel indirect prin intermediul
unui pointer din tabelul virtual al clasei. Putem suprima mecanismul apelrii unei funcii virtuale
explicitnd clasa din care face parte funcia care este apelat folosind operatorul de rezoluie.
n C++, n cadrul unei ierarhii nu pot aprea funcii virtuale cu acelai nume i aceeai
parametri, dar cu tip returnat diferit. Dac ncercm s scriem astfel de funcii este semnalat
eroare la compilare. Astfel, dac n exemplul de mai sus metoda functie din clasa de baz ar fi
avut tipul returnat int i era virtual, obineam eroare la compilare. Putem avea ns ntr-o
ierarhie funcii care nu sunt virtuale, cu acelai nume i aceai parametri, dar cu tip returnat
diferit.
Datorit faptului c apelul unei funcii virtuale este localizat cu ajutorul tabelei VMT,
apelarea funciilor virtuale este lent. De aceea preferm s declarm funciile ca fiind virtuale
numai acolo unde este necesar.
Concluzionnd, n final putem spune c declararea funciilor membre ca fiind virtuale
ajut la implementarea corect a polimorfismului n C++ i n situaiile n care lucrm cu pointeri
ctre tipul clas.

2.2.13. Destructori virtuali

Constructorii nu pot fi declarai virtuali, n schimb destructorii pot fi virtuali.
S vedem pe un exemplu simplu ce se ntmpl cnd destructorul clasei de baz este i
respectiv nu este declarat virtual:

#include<iostream.h>

58
class B
{
public:
B()
{
cout<<"Constructor clasa de baza"<<endl;
}
~B()
{
cout<<"Destructor clasa de baza"<<endl;
}
};

class D:public B
{
public:
D()
{
cout<<"Constructor clasa derivata"<<endl;
}
~D()
{
cout<<"Destructor clasa derivata"<<endl;
}
};

void main()
{
B *p=new D;
delete p;
}

Pe ecran vor aprea mesajele:

Constructor clasa de baza
Constructor clasa derivata
Destructor clasa de baza

Dup cum am vzut, pointerul p reine adresa obiectului clasei B construit odat cu
obiectul clasei D. Neexistnd nici o legatur cu obiectul clasei derivate, distrugerea se va face
numai la adresa p, adic se va apela numai destructorul clasei B. Pentru a se distruge i obiectul
clasei derivate D, trebuie s declarm destructorul clasei de baz ca fiind virtual. n aceast
situaie la execuia programului, pe ecran vor aprea urmatoarele patru mesaje:

constructor clasa baza
constructor clasa derivata
destructor clasa derivata
destructor clasa baza

Aadar, este indicat ca ntr-o ierarhie de clase n care se aloc dinamic memorie
destructorii s fie declarai virtuali !
59


2.2.14. Funcii pur virtuale

ntr-o ierarhie ntlnim situaii n care la un anumit nivel, o funcie nu este implementat.
Cu aceast situaie ne ntlnim mai ales n clasele abstracte care pregtesc ierarhia, traseaz
specificul ei. Funciile care nu se implementeaz pot fi declarate ca fiind pur virtuale. Astfel,
tentativa de apelare a unei pur virtuale se soldeaz cu eroare la compilare. n lipsa posibilitii
declarrii funciilor pur virtuale, n alte limbaje de programare, pentru metodele neimplementate
se dau mesaje.
O funcie pur virtual se declar ca una virtual numai c la sfrit punem =0.
Compilatorul C++ nu permite instanierea unei clase care conine metode pur virtuale. Dac o
clas D derivat dintr-o clas B ce conine funcii pur virtuale nu are implementat o funcie care
este pur virtual n clasa de baz, atunci problema este transferat clasei imediat derivate din D,
iar clasa D la rndul ei devine abstract, ea nu va putea fi instaniat.
Dm n continuare un exemplu n care clasa patrat este derivat din clasa dreptunghi,
care la randul ei este derivat din clasa abstract figura. Pentru fiecare figur dorim s reinem
denumirea ei i vrem s putem calcula aria i perimetrul. Dreptunghiul i ptratul se consider a
fi cu laturile paralele cu axele de coordonate. Dreptunghiul este definit prin coordonatele a dou
vrfuri diagonal opuse, iar ptratul prin coordonatele unui varf i prin lungimea laturii sale.
# include <math.h> // pentru functia "fabs"
# include <string.h> // pentru functia "strcpy"
# include <iostream.h>

class figur // clasa abstracta, cu functii pur virtuale
{
protected:
char nume[20]; // denumirea figurii
public: // functii pur virtuale
virtual float arie() = 0;
virtual float perimetru() = 0;
char* getnume()
{
return nume;
}
};

class dreptunghi: public figura
{
protected:
float x1, y1, x2, y2; // coordonate varfuri diagonal opuse
public:
dreptunghi(float X1, float Y1, float X2, float Y2)
{
strcpy(nume,"Dreptunghi");
x1=X1;
y1=Y1;
x2=X2;
y2=Y2;
}
virtual float arie()
60
{
return fabs((x2-x1)*(y2-y1));
}
virtual float perimetru()
{
return 2*(fabs(x2-x1)+fabs(y2-y1));
}
};

class patrat: public dreptunghi
{
protected:
float x, y, l; // coordonate vf. stanga-sus si lungime latura
public:
patrat(float X, float Y, float L): dreptunghi(X, Y, X+L, Y+L)
{ // constructorul patrat apeleaza dreptunghi
strcpy(nume,"Patrat");
x=X;
y=Y;
l=L;
}
virtual float perimetru()
{
return 4*l;
}
};

void main()
{
dreptunghi d(100,50,200,200);
patrat p(200,100,80);

cout<<"Arie "<<d.getnume()<<": "<<d.arie()<<endl;
cout<<"Perimetru "<<d.getnume()<<": "<<d.perimetru()<<endl;
cout<<"Arie "<<p.getnume()<<": "<<p.arie()<<endl;
cout<<"Perimetru "<<p.getnume()<<": "<<p.perimetru()<<endl;
}

n exemplul de mai sus, n interiorul clasei patrat, dac dorim s apelm funcia
perimetru din clasa dreptunghi folosim operatorul de rezoluie:

dreptunghi :: perimetru();

Funcia perimetru din clasa patrat se poate rescrie folosind funcia perimetru din clasa
dreptunghi astfel:

class patrat: public dreptunghi
{
// ....
virtual float perimetru()
{
return dreptunghi::perimetru();
61
}
};

S facem cteva observaii legate de programul de mai sus:

1. Cmpul nume reine denumirea figurii i este motenit n clasele dreptughi i patrat.
Funcia getnume returneaz numele figurii i este de asemenea motenit n clasele
derivate.
2. Funcia arie nu este definit i n clasa patrat. n consecin, apelul d.arie() se va
rsfrnge asupra metodei din clasa de baza dreptunghi.
3. Clasa figura conine metode pur virtuale, deci este abstract. Ea traseaz specificul
ierarhiei (clasele pentru figuri derivate din ea vor trebui s implementeze metodele
arie i perim). Clasa figura nu poate fi instaniat.

Este evident c n exemplul de mai sus puteam s nu scriem clasa figura, cmpul nume i
funcia getnume putnd fi redactate n interiorul clasei dreptunghi.
Definirea clasei abstracte figura permite tratarea unitar a conceptului de figur n cadrul
ierarhiei. Astfel, tot ceea ce este descendent direct sau indirect al clasei figura este caracterizat
printr-un nume (care poate fi completat direct i interogat indirect prin intermediul funciei
getnume) i dou metode (pentru arie i perimetru).
O s dm n continuare o posibil aplicaie la tratarea unitar a conceptului de figur. Mai
nti ns o s scriem nc o clas derivat din figura clasa cerc caracterizat prin coordonatele
centrului i raza sa:

#define PI 3.14159

class cerc: public figura
{
protected:
float x, y, r;
public:
cerc(float x, float y, float r)
{
strcpy(nume,"Cerc");
this->x=x;
this->y=y;
this->r=r;
}
virtual float arie()
{
return PI*r*r;
}
virtual float perimetru()
{
return 2*PI*r;
}
};

Scriem un program n care construim aleator obiecte de tip figur (dreptunghi, ptrat sau
cerc). Ne propunem s sortm crescator acest vector de figuri dup arie i s afim denumirile
figurilor n aceast ordine:
62

void qsort(int s,int d,figura **fig)
{
int i=s,j=d,m=(s+d)/2;
figura *figaux;

do
{
while (fig[i]->arie()<fig[m]->arie()) i++;
while (fig[j]->arie()>fig[m]->arie()) j--;
if (i<=j)
{
figaux=fig[i]; fig[i]=fig[j]; fig[j]=figaux;
if (i<m) i++;
if (j>m) j--;
}
}
while (i<j);
if (s<m) qsort(s,m-1,fig);
if (m<d) qsort(m+1,d,fig);
}

void main(void)
{
figura **fig;
int i,n;

randomize();
cout<<"Dati numarul de figuri:";
cin>>n;
fig = new figura*[n];
for (i=0;i<n;i++)
switch (random(3))
{
case 0: fig[i]=new patrat(random(100),random(100)
,random(100)+100);
break;
case 1: fig[i]=new dreptunghi(random(100),random(100)
,random(100)+100,random(100)+100);
break;
case 2: fig[i]=new cerc(random(100),random(100)
,random(100)+100);
break;
}
qsort(0,n-1,fig);
cout<<"Figurile sortate dupa arie:"<<endl;
for (i=0;i<n;i++)
cout<<fig[i]->getnume()<<" cu aria: "<<fig[i]->arie()<<endl;
for (i=0;i<n;i++) delete [] fig[i];
delete [] fig;
}

63
2.2.15. Motenire multipl

Limbajul C++ suport motenirea multipl, adic o clas D poate fi derivat din mai
multe clase de baz B1, B2, , Bn (n > 0):

class D: [mod_derivare] B1, [mod_derivare] B2, ... , [mod_derivare] Bn
{
// descriere clasa D
};

Un constructor al unei clase derivate apeleaz cte un constructor al fiecrei clasei de
baz. Apelurile constructorilor claselor de baz sunt desprite prin virgul. Iat un exemplu:

class Derivata: public Baza1, private Baza2
{
Derivata(...):Baza1(...),Baza2(...)
{
// descriere constructor Derivata
}
};

O clas nu poate avea o clas de baz de mai multe ori. Deci, nu putem avea spre
exemplu o derivare de forma:

class Derivata: public Baza1, public Baza2, public Baza1
{
// descriere clasa Derivata
};

Ce se ntmpl dac dou clase B1 i B2 sunt baze pentru o clas D, iar B1 i B2 sunt
derivate din aceeai clas C ? n aceasta situaie, aa cum vom vedea n subcapitolul urmator,
clasa C se declar n general ca fiind virtual.


2.2.16. Clase virtuale

O clas C se moteneste virtual dac poate exista situaia ca la un moment dat dou clase
B1 i B2 s fie baze pentru o aceeai clas derivat D, iar B1 i B2 s fie descendente (nu
neaparat direct) din aceeai clas C.
Dm un exemplu:
class C
{
protected:
void fct() {}
};

class B1: virtual public C
{
};

class B2: virtual public C
64
{
};

class D: public B1, public B2
{
public:
void test()
{
fct();
}
};

n exemplul de mai sus, clasa D este derivat din B1 i B2, iar B1 i B2 sunt ambele
derivate din clasa C. Astfel, facilitatile oferite de clasa C (functia fct) ajung n D de dou ori: prin
intermediul clasei B1 i prin intermediul clasei B2. De fapt, orice obiect al clasei D va avea n
aceast situaie dou sub-obiecte ale clasei C. Din cauz c funcia fct este apelat n interiorul
clasei D nu se poate decide asupra crei dintre cele dou funcii din cele dou subobiecte se va
rsfrnge apelul. Astfel, obinem eroare la compilare. Pentru a elimina aceast problema, clasa C
trebuie s fie declarat virtual n momentul n care ambele clase, B1 i B2, deriveaz pe C:

class C
{
protected:
void fct() {}
};

class B1: virtual public C
{
};

class B2: virtual public C
{
};

class D: public B1, public B2
{
public:
void test()
{
fct();
}
};



2.2.17. Constructori pentru clase virtuale

S vedem n ce ordine se apeleaz constructorii claselor de baz la construcia unui obiect
al clasei derivate. Regula este c nti se apeleaz constructorii claselor virtuale (de sus n jos n
ierarhie i de la stnga spre dreapta n ordinea enumerrii lor) i apoi cei din clasele care nu sunt
virtuale, evident tot de sus n jos i de la stnga spre dreapta.
65
Dac o clas este derivat din mai multe clase de baz, atunci clasele de baz nevirtuale
sunt declarate primele, aa nct clasele virtuale s poat fi construite corect. Dm un exemplu n
acest sens:

class D: public B1, virtual public B2
{
// descriere clasa D
};

n exemplul de mai sus, nti se va apela constructorul clasei virtuale B2, apoi al clasei
B1 i n final se apeleaz constructorul clasei derivate D.
Pentru motenirea multipl considerm situaia n care clasa patrat este derivat din
clasele dreptunghi i romb, iar clasele dreptunghi i romb sunt derivate ambele din clasa
patrulater, care este derivat la randul ei din clasa abstract figura. Dreptunghiul i ptratul au
laturile paralele cu axele de coordonate, iar rombul are dou laturi paralele cu axa Ox.

# include <math.h>
# include <string.h>
# include <stdlib.h>
# include <iostream.h>

# define PI 3.14159

class figura
{
protected:
char nume[20]; // denumirea figurii
public: // functii pur virtuale
virtual float arie() = 0;
virtual float perimetru() = 0;
char* getnume()
{
return nume;
}
};

class patrulater:public figura
{
protected:
float x1,y1,x2,y2,x3,y3,x4,y4;
public:
patrulater(float X1,float Y1,float X2,float Y2,
float X3,float Y3,float X4,float Y4)
{
strcpy(nume,"Patrulater");
x1=X1;
y1=Y1;
x2=X2;
y2=Y2;
x3=X3;
y3=Y3;
66
x4=X4;
y4=Y4;
}
virtual float arie()
{
return fabs(x1*y2-x2*y1+x2*y3-x3*y2+x3*y4-x4*y3+x4*y1-x1*y4)/2;
}
virtual float perimetru()
{
return sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1))+
sqrt((x3-x2)*(x3-x2)+(y3-y2)*(y3-y2))+
sqrt((x4-x3)*(x4-x3)+(y4-y3)*(y4-y3))+
sqrt((x1-x4)*(x1-x4)+(y1-y4)*(y1-y4));
}
};

class dreptunghi: virtual public patrulater // clasa patrulater virtuala
{
protected:
float x1, y1, x2, y2;
public:
dreptunghi(float X1,float Y1,float X2,float Y2):
patrulater(X1,Y1,X2,Y1,X2,Y2,X1,Y2)
{
strcpy(nume,"Dreptunghi");
x1=X1;
y1=Y1;
x2=X2;
y2=Y2;
}
virtual float arie()
{
return fabs((x2-x1)*(y2-y1));
}
virtual float perimetru()
{
return 2*(fabs(x2-x1)+fabs(y2-y1));
}
};

class romb: virtual public patrulater // clasa patrulater virtuala
{
protected:
float x, y, l, u;
public:
romb(float X,float Y,float L,float U):
patrulater(X,Y,X+L,Y,X+L*cos(U)+L,Y+L*sin(U),X+L*cos(U),Y+L*sin(U))
{
strcpy(nume,"Romb");
x=X;
y=Y;
l=L;
67
u=U;
}
virtual float arie()
{
return l*l*sin(u);
}
virtual float perimetru()
{
return 4*l;
}
};

class patrat: public dreptunghi, public romb
{
protected:
float x, y, l;
public:
patrat(float X,float Y,float L):dreptunghi(X,Y,X+L,Y+L),
romb(X,Y,L,PI/2),patrulater(X,Y,X+L,Y,X+L,Y+L,X,Y+L)
{
strcpy(nume,"Patrat");
x=X;
y=Y;
l=L;
}
virtual float arie() // calcul arie patrat ca fiind dreptunghi
{
return dreptunghi::arie();
}
virtual float perimetru() // calcul perimetru patrat ca fiind romb
{
return romb::perimetru();
}
};

void main(void)
{
patrulater P(10,10,100,40,110,100,20,30);
dreptunghi d(10,20,200,80);
romb r(20,50,100,PI/3);
patrat p(20,10,100);

cout<<"Aria patrulaterului: "<<P.arie()<<endl;
cout<<"Perimetrul patrulaterului: "<<P.perimetru()<<endl<<endl;
cout<<"Aria dreptunghiului: "<<d.arie()<<endl;
cout<<"Perimetrul dreptunghiului: "<<d.perimetru()<<endl<<endl;
cout<<"Aria rombului: "<<r.arie()<<endl;
cout<<"Perimetrul rombului: "<<r.perimetru()<<endl<<endl;
cout<<"Aria patratului: "<<p.arie()<<endl;
cout<<"Perimetrul patratului: "<<p.perimetru()<<endl<<endl;
}

68
Observaii:
1) Patrulaterul este dat prin coordonatele celor 4 vrfuri ale sale (n sens trigonometric
sau invers trigonometric): (x1,y1), (x2,y2), (x3,y3) i (x4,y4).
2) Dreptunghiul (paralel cu axele de coordonate) este considerat ca fiind definit prin
dou vrfuri diagonal opuse avnd coordonatele: (x1,y1) i (x2,y2).
3) Rombul (cu dou laturi paralele cu abscisa) este caracterizat prin coordonatele
vrfului stnga-sus (x,y), lungimea laturii sale l i unghiul din vrful de coordonate
(x,y) avnd msura u n radiani.
4) Ptratul (paralel cu axele de coordonate) are vrful stnga-sus de coordonate (x,y) i
latura de lungime l.
5) Perimetrul patrulaterului e calculat ca suma lungimilor celor 4 laturi:

. ) ( ) ( ) ( ) (
) ( ) ( ) ( ) (
2
4 1
2
4 1
2
3 4
2
3 4
2
2 3
2
2 3
2
1 2
2
1 2
y y x x y y x x
y y x x y y x x
+ + +
+ + + +


6) Pentru patrulater am folosit o formul din geometria computaional pentru calculul
ariei. Aria unui poligon oarecare A
1
A
2
A
n
(n cazul nostru n = 4) este:

. ) ... 1 ( ) , ( ,
2
1 1
1
1 1
...
2 1
A A si n i y x A unde
y x y x
S
n i i i
n
i
i i i i
n
A A A
= =

=
+
=
+ +



7) Pentru a considera un romb ca fiind un patrulater oarecare (vezi constructorul clasei
patrulater apelat de constructorul clasei romb), trebuie s determinm coordonatele
celor 4 vrfuri ale sale.


Vrful A are coordonatele (x,y), iar B are (x+l,y) (translaia lui A pe axa Ox cu l).
Pentru a gsi coordonatele punctului D am aplicat o rotaie a punctului B n jurul lui
A cu un unghi u n sens trigonometric:

.
) sin( ) cos( 0 ) sin(
) cos( ) sin( 0 ) cos(

+ = + + =
+ = + =
u l y y u u l
D
y
u l x x u u l
D
x


Pentru varful C al rombului am realizat o translaie a punctului D pe axa Ox cu l i
am obinut coordonatele (x+lcos(u)+l, y+lsin(u)).



B
(x,y)
u
l
l
l
l
A
C D
69
Rezumat

Am vzut pn acum cum se scrie cod orientat pe obiecte: cum se scrie o clas, o metod,
un constructor, destructorul clasei, o funcie prieten. De asemenea, am fcut cunotin cu
motenirea din C++ i problemele pe care le ridic motenirea multipl. Am vzut cum se
rezolv elegant aceste probleme cu ajutorul claselor virtuale.


Tem

Lsm ca exerciiu cititorului implementarea urmtoarei ierarhii de figuri:

figura


poligon elipsa


patrulater triunghi cerc



romb dreptunghi triunghi_isoscel triunghi_dreptunghic



patrat triunghi_echilateral triunghi_dreptunghic_isoscel


2.2.18. Clase imbricate

C++ permite declararea unei clase n interiorul (imbricat) altei clase. Pot exista clase
total diferite cu acelai nume imbricate n interiorul unor clase diferite.
Dm un exemplu:

class X
{
//....
class Y // clasa Y imbricata in X
{
//....
};
//....
};

class Z
{
//....
class Y // clasa Y imbricata in Z
{
70
//....
};
//....
};

La instanierea claselor X sau Z (din exemplul de mai sus) nu se creaz instane ale
claselor Y imbricate n acestea. Instanierea clasei imbricate trebuie fcut explicit:

X::Y y1; // y1 este obiect al clasei Y imbricate n X
Z::Y y2; // y2 este obiect al clasei Y imbricate n Z

Clasa imbricat nu are acces asupra datelor private sau protected ale clasei n care este
imbricat. De asemenea, o clas nu are acces asupra datelor private sau protected ale
eventualelor clase imbricate.
2.2.19. Clase ablon (template)

Obiective

Ne propunem s relum discuia despre abloane, introducnd acum noiunea de clas
template. O s vedem cum alturi de funciile ablon, clasele ablon permit scrierea de cod
generic n C++, cod care se funcioneaz pentru diverse tipuri de date.

Ca i n cazul unei funcii ablon, unul sau mai multe tipuri de date pot s nu fie
explicitate n momentul definirii unei clase ablon. n momentul compilrii, n locul unde se
instaniaz clasa, se identific aceste tipuri de date i se nlocuiesc cu tipurile identificate. O clas
ablon se declar astfel:

template <class T1, class T2, ... , class Tn>
class nume_clasa
{
// descriere clasa nume_clasa
};

Instanierea unei clase ablon se face astfel:

nume_clasa<tip1, tip2, ... , tipn> obiect;

La instaniere, tipurile generice de date T1, T2, , Tn se nlocuiesc cu tipurile de date
specificate ntre semnele mai mic < i mai mare >, adic cu tip1, tip2, , tipn.
Fiecare funcie membr unei clase ablon este la rndul ei funcie ablon. Astfel,
descrierea fiecrei funcii membre n exteriorul definiiei clasei (adic nu inline) se va face ca
orice funcie ablon obinuit.
n C++ i tipurile struct i union pot fi template.
Spre exemplificare, scriem o clas ablon pentru o stiv:

#include<conio.h>
#include<stdlib.h>
#include<iostream.h>

template <class T>
struct NodListaSimpluInlantuita
71
{
T info;
NodListaSimpluInlantuita<T> *leg;
};

template <class T>
class Stiva
{
private:
NodListaSimpluInlantuita<T> *cap;
public:
Stiva() // constructor
{
cap=NULL;
}
int operator!() // verificare stiva goala
{
return cap==NULL;
}
void operator<<(T x) // introducere in stiva
{
NodListaSimpluInlantuita<T> *p=cap;
cap=new NodListaSimpluInlantuita<T>;
cap->info=x;
cap->leg=p;
}
void operator>>(T &); // scoatere din stiva
friend ostream& operator<<(ostream&,Stiva<T>&);
// tiparire continut stiva
void golire(); // golire stiva
~Stiva() // destructor
{
golire();
}
};

template <class T>
void Stiva<T>::operator>>(T &x)
{
if (cap==NULL) throw "Stiva goala!";
x=cap->info;
NodListaSimpluInlantuita<T> *p=cap->leg;
delete cap;
cap=p;
}

template <class T>
ostream& operator<<(ostream& fl,Stiva<T>& st)
{
NodListaSimpluInlantuita<T> *p=st.cap;
while (p!=NULL)
{
72
fl<<p->info;
p=p->leg;
}
return fl;
}

template <class T>
void Stiva<T>::golire()
{
NodListaSimpluInlantuita<T> *p=cap;
while (cap!=NULL)
{
p=cap->leg;
delete cap;
cap=p;
}
}

Utilizm clasa ablon pentru lucra cu o stiv de caractere:

void main()
{
char x;
Stiva<char> st;

do
{
cout<<endl<<endl;
cout<<" 1. Introducere element in stiva"<<endl;
cout<<" 2. Scoatere element din stiva"<<endl;
cout<<" 3. Afisare continut stiva"<<endl;
cout<<" 4. Golire stiva"<<endl<<endl;
cout<<"Esc - Parasire program"<<endl<<endl;
switch (getch())
{
case '1':
cout<<"Dati un caracter: "<<flush;
st<<getche();
break;
case '2':
try
{
st>>x;
cout<<"Am scos din stiva: "<<x;
}
catch (char *mesajeroare)
{
cerr<<"Eroare: "<<mesajeroare;
}
break;
case '3':
if (!st) cout<<"Stiva este goala!";
73
else cout<<"Stiva contine: "<<st;
break;
case '4':
st.golire();
cout<<"Stiva a fost golita!";
break;
case 27: return;
default:
cerr<<"Trebuie sa apasati 1,2,3,4 sau Esc";
}
cout.flush();
getch();
}
while (1);
}

Dup ce avem scris clasa ablon, nu trebuie dect s specificm tipul de date pentru care
vrem s o folosim. Astfel, clasa Stiva de mai sus o putem folosi pentru orice tip de date.



Rezumat

Am vazut cum se scrie o clas ablon i cum se folosete pentru diverse tipuri de date.
Am dat un exemplu ilustrativ: clas ablon de lucru cu o stiv. Astfel, aceast clas poate fi
folosit pentru a lucra cu o stiv de caractere, o stiv de numere ntregi, de numere reale etc.


Teme

n finalul prezentrii din punct de vedere teoretic al programarii orientate pe obiecte din
C++, propunem s se scrie:

1) Un program care utilizeaz clasa ablon Stiva i pentru alt tip de date dect char.
2) O clas ablon de lucru cu o coad (similar clasei Stiva). S se utilizeze acest clas
pentru a rezolva urmtoarea problem:

Se deschide un fiier text. Se citesc caracterele fiierului unul cte unul. Cnd se
ntlnete o consoan, se introduce n coad. Cnd se ntlnete o vocal, dac nu
este goal coada, se scoate o consoan din list. Restul caracterelor (cele care nu
sunt litere) se vor ignora. La sfrit s se afieze coninutul cozii.

3) Clasa ablon de lucru cu o mulime ce reine elementele ntr-un vector alocat dinamic.
Clasa va avea implementai operatorii: +, - i * pentru reuniune, diferen i
respectiv intersecie, operatorii << i >> pentru introducerea unui element n mulime
i respectiv scoaterea unui element din mulime, operatorul << pentru introducerea
coninutului mulimii ntr-un flux de ieire, operatorul ! care returneaz numrulde
elemente al mulimii, operatorii =, +=, -=, *=, ==, !=, constructor fr
parametri, constructor de copiere, destructor etc. Se vor folosi metode rapide pentru
reuniune, diferen i intersecie.

74
4) Clas ablon pentru prelucrarea unui vector. Pentru citire se va folosi >>, pentru
scriere <<, atribuirea se va face cu =, compararea de doi vectori cu == i !=,
calcularea normei cu !, suma vectorial cu +, diferena vectorial cu -, amplificarea
cu scalar cu *, produsul scalar cu *, se vor defini operatorii +=, -= i *=, functii
pentru calcularea mediei aritmetice, pentru sortare etc. S se deriveze clasa vector
pentru lucrul cu un vector tridimensional. S se defineasc n aceast clas n plus
operatorul / pentru produs vectorial.

5) Clas ablon de lucru cu matrici. Clasa va conine: operatorul >> pentru citire,
scrierea se va face cu <<, atribuirea cu =, compararea cu == i !=. Determinantul se
va calcula cu !, suma cu +, diferena cu -, produsul cu *. De asemenea, se vor defini
operatorii +=, -= i *= i funcii pentru transpus, ridicarea la putere i inversarea
matricii. S se foloseasc aceast clas pentru calcularea sumei: A
t
+ (A
t
)
2
+ +
(A
t
)
n
, unde n este un numr ntreg pozitiv.

6) Clas de lucru cu polinoame. Clasa va conine operatorii << i >> pentru introducere
i scoatere n / din flux, operatorii =, ==, !=, operatorii +, - i * pentru operaiile
intre doua matrici, * pentru amplificare cu o valoare, / pentru ctul mpririi i %
pentru restul mpririi. De asemenea, se vor descrie operatorii +=, -=, *=, /=,
%= i o funcie pentru calcularea valorii polinomului ntr-un punct. S se foloseasc
aceasta clas pentru calcularea celui mai mare divizor comun i a celui mai mic
multiplu comun pentru dou polinoame.

7) Clas de lucru cu numere ntregi mari ntr-o baza b. Cifrele numrului (valori ntre 0
i b-1) se vor memora ntr-un ir de numere ntregi. Se vor descrie operatorii: <<
(afiare), >> (citire), = (atribuire); <, >, <=, >=, ==, != pentru comparare; +, -, *, / i
% pentru operaii aritmetice, operatorii: +=, -=, *=, /= i %=. S se testeze
clasa pentru calcularea lui n! pentru un numr ntreg pozitiv relativ mare n (de
exemplu pentru n = 20).

8) Clas pentru transformri elementare aplicate unui punct din plan, de coordonate (x,
y). Cu ajutorul clasei s se poat aplica translaii, rotaii n jurul originii, simetrii fa
de axele de coordonate unui punct din plan. Folosind aceast clas s se calculeze
pentru un punct bidimensional simetricul fa de o dreapt oarecare dat sub forma
general ax+by+c=0.


2.3. Fluxuri n C++

Obiective

Ne propunem s studiem dou ierarhii de clase: streambuf i ios pentru o mai bun
nelegere a modului de lucru cu fluxuri n C++.

Un limbaj de programare este proiectat sa lucreze cu o varietate mare de periferice.
Datele se transmit spre echipamentele periferice prin intermediul unei zone de memorie tampon.
Gestionarea acestei zone de memorie se face prin intermediul unui aa numit flux. Un flux este
un instrument logic care permite tratarea unitar a operaiilor de intrare/ieire.
75
Pentru lucrul cu fluxuri n C++ sunt definite n fiierul antet iostream.h dou ierarhii de
clase: una pentru buffer-ul (zona de memorie tampon) cu care lucreaz un flux, ierarhie pornit
de clasa streambuf i una pentru operaiile pe fluxuri de intrare sau / i de ieire (clasa ios
pornete aceast ierarhie). Ierarhia ios reprezint o alternativ orientat pe obiecte la funciile din
fiierul antet stdio.h.


2.3.1. Ierarhia streambuf

Din clasa streambuf sunt derivate dou clase: filebuf (care gestioneaz buffer-ele pentru
fluxurile de fiiere) i strstreambuf care gestioneaz buffer-ele pentru fluxurile de lucru cu
string-uri:


Clasa streambuf are 2 constructori:

streambuf()
streambuf(char *s,int n)

Primul construiete un obiect de tipul clasei streambuf cu un buffer vid.
Al doilea constructor specific adresa s spre o zon de memorie care se va folosi ca
buffer pentru flux, iar n reprezint lungimea acestei zone de memorie.
Clasa streambuf conine o mulime de metode pentru prelucrarea cu buffer-ului unui flux.
Pentru a lucra cu un flux nu este nevoie s cunoatem facilitile clasei ce gestioneaz buffer-ul
asociat fluxului, acestea fiind exploatate indirect de ctre obiectele de tip ierarhiei ios. De aceea
nu vom intra n detalii cu privire la coninutul clasei streambuf (metode i cmpuri) sau al
vreunei clase derivate din aceasta. Dac se dorete acest lucru, se poate consulta documentaia
Borland C++ sau/i Visual C++.
Clasa filebuf este scris pentru buffer-e de fiiere. Ea are 3 constructori:

filebuf()
filebuf(int fd)
filebuf(int fd,char *s,int n)

Primul constructor creaz un buffer de fiier, dar nu-l asociaz cu nici un fiier.
Al doilea constructor creaz un buffer i l asociaz unui fiier avnd descriptorul fd, care
este un numr ntreg.
Al treilea constructor creaz un buffer de caractere al crui adres de memorie este s,
buffer-ul are lungimea n, iar constructorul asociaz acest buffer unui fiier cu descriptorul fd.
Clasa filebuf are un destructor care nchide eventualul fiier asociat fluxului:

~filebuf()

Clasa strstreambuf este scris pentru operaii de intrare / ieire n / din zone de memorie
RAM. Clasa are 5 constructori i nici un destructor.

2.3.2. Ierarhia ios
streambuf
filebuf strstreambuf
76

Ierarhia de clase pornit de ios este scris pentru a lucra cu fluxuri de date. Transmiterea
i primirea datelor se face prin intermediul unei zone tampon prelucrate prin intermediul unui
obiect instan al unei clase din ierarhia streambuf.
Clasa ios are 2 constructori:

ios(streambuf *buf)
ios()

Primul constructor asociaz un buffer buf, obiect al clasei streambuf unui flux,
constructorul primete ca argument adresa spre obiectul de tip buffer cu care va lucra fluxul. Al
doilea constructor creaz un obiect al clasei ios, fr a-l lega la un buffer. Legarea se poate face
ulterior cu ajutorul funciei init, care este membr clasei ios.
Clasa ios conine urmtoarele cmpuri:

1) adjustfield este o constant static de tip long transmis de obicei n al doilea
parametru al funciei setf membre clasei ios pentru a cura biii de formatare legai de modul de
aliniere la stnga, la dreapta i internal (vezi capitolul dedicat indicatorilor de formatare i
functia setf). Exemplu: cout<<setf(ios::right, ios::adjustfield)<<n;
2) basefield tot o constant static de tip long i este transmis ca al doilea parametru al
funciei setf pentru a cura biii de formatare legai de baza de numeraie (8, 10 i 16).
3) bp memoreaz adresa ctre buffer-ul fluxului, un obiect de tip streambuff.
4) floatfield este o constant static de tip long transmis n al doilea parametru al funciei
setf pentru a cura biii de formatare legai de modul de afiare al valorilor n virgul mobil
(forma fix i tiinific, fr i respectiv cu exponent).
5) state este o valoare de tip ntreg int ce memoreaz starea buffer-ului de tip streambuf.
6) x_fill este o valoare ntreag int care reine caracterul folosit pentru umplerea spaiilor
libere n afiarea formatat.
7) x_flags este o valoare de tip long care reine biii de formatare la afiarea valorilor
numerice ntregi (aliniere, baza de numeraie etc.).
8) x_precision este o valoare de tip long care reine precizia la afiarea valorilor n
virgul mobil (numrul de cifre exacte).
9) x_width este o valoare de tip int care memoreaz lungimea (numrul de caractere) pe
care se face afiarea.

n clasa ios sunt definite urmtoarele funcii membre de lucru cu fluxuri:

1) int bad() returneaz o valoare ntreag nenul dac a aprut cel puin o eroare n
prelucrarea fluxului. Verificarea se face interognd biii ios::badbit i ios::hardfail ai valorii din
cmpul state al clasei ios.
2) void clear(int st=0); seteaz cmpul state la valoarea ntreag primit ca argument.
Dac st este 0 (valoare de altfel implicit), atunci starea fluxului se iniializeaz din nou ca fiind
bun. Un apel de forma flux.clear() readuce fluxul n stare bun (fr erori).
3) int eof() returneaz o valoare nenul, dac nu mai sunt date n flux (sfrit de fiier).
De fapt funcia interogheaz bitul ios::eofbit al cmpului state.
4) int fail() returneaz o valoarea nenul, dac o operaie aplicat fluxului a euat. Se
verific biii ios::failbit, ios::badbit i ios::hardfail ai cmpului state (n plus fa de funcia bad
verific i bitul ios::failbit).
5) char fill() returneaz caracterul de umplere al spaiile libere de la scrierea formatat.
6) char fill(char c) seteaz caracterul de umplere al spaiile goale de la scrierea formatat
la valoarea c i returneaz caracterul folosit anterior n acest scop.
77
7) long ios_flags() returneaz valoarea reinut n x_flags.
8) long ios_flags(long flags) seteaz cmpul x_flags la valoarea primit ca argument i
returneaz vechea valoare.
9) int good() returneaz o valoare nenul dac nu au aprut erori n prelucrarea fluxului.
Acest lucru se realizeaz consultnd biii cmpului state.
10) void init(streambuf *buf); transmite adresa buffer-ului de tip streambuf cu care va
lucra fluxul.
11) int precision(int p); seteaz precizia de tiprire a numerelor reale la valoarea primit
ca argument i returneaz vechea valoare. De fapt cmpul ios::precision se seteaza la valoarea p
i se returneaz vechea sa valoare.
12) int precision() returneaz valoarea preciziei la tiprire a valorilor reale (reinut n
cmpul ios::precision).
13) streambuf* rdbuf() returneaz adresa ctre obiectul responsabil cu buffer-ul fluxului.
14) int rdstate() returneaz starea fluxului (valoarea cmpului state).
15) long setf(long setbits, long field); reseteaz biii (i face 0) din x_flags pe poziiile
indicate de biii cu valoare 1 ai parametrul field i apoi seteaz biii din x_flags la valoarea 1 pe
poziiile n care biii sunt 1 n parametrul setbits. Funcia returneaz vechea valoare a lui x_flags.
16) long setf(long flags) modific biii cmpului x_flags la valoare 1 pe poziiile n care
biii parametrului flags sunt 1 i returneaz vechea valoare a lui x_flags.
17) void setstate(int st); seteaz biii cmpului state la valoarea 1 pe poziiile n care biii
parametrului st sunt 1.
18) void sync_with_stdio(); sincronizeaz fiierele stdio cu fluxurile iostream. n urma
sincronizrii viteza de execuie a programului scade mult.
19) ostream* tie() returneaz adresa ctre fluxul cu care se afla legat fluxul curent. De
exemplu, fluxurile cin i cout sunt legate. Legtura dintre cele dou fluxuri const n faptul c
atunci cnd unul dintre cele dou fluxuri este folosit, atunci mai nti cellalt este golit. Dac
fluxul curent (din care este apelat funcia tie) nu este legat de nici un flux, atunci se returneaz
valoarea NULL.
20) ostream* tie(ostream* fl) fluxul fl este legat de fluxul curent, cel din care a fost
apelat aceast funcie. Este returnat fluxul anterior legat de fluxul curent. Ca efect al legrii unui
flux de un alt flux este faptul c atunci cnd un flux de intrare mai are caractere n buffer sau un
flux de ieire mai are nevoie de caractere, atunci fluxul cu care este legat este nti golit. Implicit,
fluxurile cin, cerr i clog sunt legate de fluxul cout.
21) long unsetf(long l) seteaz biii din x_flags la valoarea 0 pe pozitiile n care
parametrul l are biii 1.
22) int width() returneaz lungimea pe care se face afiarea formatat.
23) int width(int l); seteaz lungimea pe care se face afiarea formatat la valoarea
primit ca argument i returneaz vechea valoare.

Din clasa ios sunt derivate 4 clase: istream, ostream, fstreambase i strstreambase:


Clasa istream realizeaz extrageri formatate sau neformatate dintr-un buffer definit ca
obiect al unei clase derivate din streambuf.
Constructorul clasei istream este:

ios
istream ostream strstreambase fstreambase
78
istream(strstream *buf);

Constructorul asociaz un obiect de tip buffer buf, primit ca argument, unui flux de
intrare.
Funciile membre clasei istream:

1) int gcount() returneaz numrul de caractere neformatate ultima dat extrase.
2) int get() extrage urmtorul caracter din flux. Dac s-a ajuns la sfritul fluxului se
returneaz valoarea EOF, adic 1.
3) istream& get(signed char *s, int l, char eol=\n) extrage un ir de caractere
interpretate a fi cu semn de lungime maxim l-1, pe care l depune la adresa s dac nu este
sfritul fluxului sau dac nu s-a ajuns la caracterul ce delimiteaz sfritul de linie.
Delimitatorul de sfrit de linie este implicit \n, dar programatorul poate specifica un alt
caracter dac dorete. La sfritul irului de caractere depus n s este adugat \0 (sfrit de
string). Delimitatorul nu este extras din flux. Se returneaz fluxul istream din care s-a fcut
citirea.
4) istream& get(unsigned char *s, int l, char eol=\n) se extrage din flux un ir de
caractere fr semn n maniera explicat mai sus.
5) istream& get(unsigned char &c) extrage un singur caracter interpretat a fi fr semn i
returneaz fluxul istream din care s-a fcut citirea.
6) istream& get(signed char &c) extrage un caracter cu semn i returneaz fluxul istream
din care s-a fcut citirea.
7) istream& get(strstreambuf &buf, int c=\n) extrage un ir de caractere pn se
ntlnete caracterul de delimitare c (al crui valoare implicit este \n), ir pe care l depune n
bufferul buf.
8) istream& getline(signed char* s, int l, char c=\n) extrage un ir de caractere cu semn
de lungime maxim l pe care l depune la adresa s, fr a pune n s delimitatorul, care implicit
este \n. Delimitatorul este ns scos din flux.
9) istream& getline(unsigned char* s, int l, char c=\n) extrage un ir de caractere fr
semn n maniera de mai sus.
10) istream& ignore(int n=1, int delim=EOF) se ignor maxim n (implicit 1) caractere
din fluxul de intrare sau pn cnd delimitatorul delim (care implicit este valoarea constantei
EOF) este ntlnit.
11) int peek() returneaz urmtorul caracter din flux, fr a-l scoate.
12) istream& putback(char c) pune napoi n flux caracterul c. Se returneaz fluxul de
intrare.
13) istream& read(signed char* s, int n) extrage din fluxul de intrare un numr de n
caractere interpretate a fi cu cu semn pe care le depune la adresa s.
14) istream& read(unsigned char* s, int n) extrage din fluxul de intrare un numr de n
caractere fr semn pe care le depune la adresa s.
15) istream& seekg(long poz) se face o poziionare n flux de la nceputul acestuia pe
pozitia poz. Se returneaz fluxul de intrare.
16) istream& seekg(long n, int deunde) se face o poziionare n flux cu n caractere de la
poziia specificat n al doilea parametru (deunde). Se returneaz fluxul de intrare. Parametrul
deunde poate lua valorile: ios::beg (de la nceputul fluxului), ios::cur de la poziia curent n
flux, sau ios::end de la sfritul fluxului. Pentru valoarea ios::cur, n poate fi pozitiv (poziionare
la dreapta poziiei curente) sau negativ (poziionarea se face napoi de la poziia curent). Pentru
ios::beg, n trebuie sa fie nenegativ, iar pentru ios::end, n trebuie sa fie nepozitiv.
10) long tellg() returneaz poziia curent n flux, de la nceputul acestuia.

79
n clasa istream este suprancrcat i operatorul >> pentru extragere de date n modul
text din fluxul de intrare.

Clasa ostream realizeaz introduceri formatate sau neformatate ntr-un buffer definit ca
obiect al unei clase derivate din streambuf. Clasa are un singur constructor cu urmtoarea
structur:

ostream(streambuf* buf);

Constructorul asociaz un buffer, obiect al unei clase derivate din streambuf, fluxului de
ieire.
Metodele clasei ostream sunt:

1) ostream& flush(); foreaz golirea buffer-ului. Returneaz fluxul de ieire.
2) ostream& put(char c); introduce caracterul primit ca argument n flux. Se returneaz
fluxul de ieire.
3) ostream& seekp(long poz); se face poziionare pe poziia poz de la nceputul fluxului.
Se returneaz fluxul de iesire.
4) ostream& seekp(long n, int deunde); Se mut poziia curent n flux cu n caractere din
locul specificat de parametrul deunde. Se returneaz fluxul de ieire. deunde poate lua valorile
ios::beg, ios::cur, respectiv ios::end.
5) long tellp(); returneaz poziia curent n flux de la nceputul fluxului.
6) ostream& write(const signed* s, int n); se introduc n caractere de la adresa s (irul s
este de caractere cu semn) n fluxul de ieire curent (din care este apelat funcia write).
7) ostream& write(const signed* s, int n); se introduc n flux n caractere luate din irul s.

n clasa ostream este suprancrcat operatorul << pentru introducere de valori n modul
text n flux.

Clasa iostream este derivat din clasele istream i ostream i are ca obiecte fluxuri care
suport operaii de intrare i ieire. Clasa iostream are un singur constructor care asociaz unui
buffer buf, obiect al clasei streambuf, un flux, obiect al clasei iostream:

iostream(streambuf* buf);

Clasa iostream nu are funcii membre (numai un constructor), ea motenind metodele
claselor istream i ostream.

Rezumat

Am studiat pe scurt ierarhia streambuf pentru buffer-ele fluxurilor i clasele superioare
din ierarhia ios scrise pentru prelucrarea fluxurilor. Aceste clase sunt baza prelucrrii fiierelor i
a string-urilor (capitolele urmtoare). Din aceste clase practic se motenesc majoritatea datelor i
metodelor necesare pentru a lucra cu fiiere i string-uri.

2.4. Fiiere n C++

Obiective

80
Ne propunem acum s continum studierea ierarhiei ios pentru a vedea cum se lucreaz
n C++ cu fiiere. Majoritatea facilitilor de prelucrare a fiierelor este motenit din clasele
superioare ale ierarhiei ios, ns pentru a lucra efectiv cu un fiier trebuie s instaniem una din
clasele ifstream, ofstream i fstream.

Tot din clasa ios este derivat i clasa fstreambase, specializat pe fluxuri ataate unor
fiiere. Clasa fstreambase are 4 constructori.
Primul constructor creaz un obiect al clasei, pe care nu-l ataeaz nici unui fiier:

fstreambase();

Al doilea constructor creaz un obiect al clasei fstreambase, deschide un fiier i l
ataeaz obiectului creat:

fstreambase(const char* s,int mod,int prot=filebuf::openprot);

Parametrul mod specific modul de deschidere al fiierului (text sau pe octei, pentru
scriere sau pentru citire etc.). Pentru modurile de deschidere sunt definite constante ntregi n
interiorul clasei ios. Acestea sunt:

Constant mod
deschidere
Semnificaie
ios::in Deschidere fiier pentru citire
ios::out Deschidere fiier pentru scriere
ios::ate Se face poziionare la sfritul fiierului care e deja deschis
ios::app Deschidere fiier pentru adugare la sfrit
ios::trunc Trunchiaz fiierul
ios::nocreate Deschiderea fiierului se face numai dac acesta exist
ios::noreplace Deschiderea fiierului se face numai dac acesta nu exist
ios::binary Deschiderea fiierului se face n modul binar (pe octeti)
Cu ajutorul operatorului | (ori pe bii) se pot compune modurile de deschidere. Astfel, cu
modul compus ios::binary|ios::in|ios::out se deschide fiierul pentru citire i scriere pe octei.
Parametrul prot corespunde modului de acces la fiier. Valoarea acestui parametru este
luat n considerare numai dac fiierul nu a fost deschis n modul ios::nocreate. Implicit, acest
parametru este setat aa nct s existe permisiune de scriere i de citire asupra fiierului.
Al treilea constructor creaz obiectul i l leag de un fiier deschis deja, al crui
descriptor d este primit ca argument:

fstreambase(int d);

De asemenea, exist i un constructor care creaz obiectul i l leag de fiierul al crui
descriptor este d. Se specific buffer-ul s cu care se va lucra, mpreun cu lungimea n a acestuia:

fstreambase(int d, char* s, int n);

Ca i funcii membre clasei fstreambase avem:

1) void attach(int d); face legatura ntre fluxul curent (din care se apeleaz funcia attach)
cu un fiier deschis, al crui descriptor este d.
2) void close(); nchide buffer-ul filebuf i fiierul.
81
3) void open(const char* s, int mod, int prot=filebuf::openprot); deschide un fiier n
mod similar ca i n cazul celui de-al doilea constructor care are aceeai parametri cu funcia
open. Ataeaz fluxului curent fiierul deschis.
4) filebuf* rdbuf() returneaz adresa ctre buffer-ul fiierului.
5) void setbuf(char* s, int n); seteaz adresa i lungimea zonei de memorie cu care va
lucra obiectul buffer al clasei filebuf ataat fiierului.

Din clasele istream i fstreambuf este derivat clasa ifstream, care este o clas
specializat pentru lucrul cu un fiier de intrare (pentru extrageri de date).


Clasa ifstream are 4 constructori (asemntori cu cei din clasa fstreambuf).
Un prim constructor creaz un flux (obiect al clasei ifstream) de lucru cu fiiere de
intrare, flux pe care nu-l ataeaz unui fiier:

ifstream();

Al doilea constructor creaz un obiect al clasei ifstream, deschide un fiier pentru operatii
de citire i l ataeaz obiectului creat. Pentru ca deschiderea fiierului s se ncheie cu succes,
trebuie ca fiierul s existe. Semnificaia parametrilor este aceeai ca la constructorul al doilea al
clasei fstreambase:

ifstream(const char* s,int mod=n,int prot=filebuf::openprot);

Al treilea constructor creaz obiectul i l leag de un fiier deschis deja, al crui
descriptor d este primit ca argument:

ifstream(int d);

Al patrulea constructor creaz obiectul i l leag de fiierul al crui descriptor este d. Se
specific adresa de memorie s i lungimea n acesteia ce va fi folosit ca memorie tampon pentru
fiier:

ifstream(int d, char* s, int n);

Funcii membre clasei ifstream:

1) void open(const char* s, int mod, int prot=filebuf::openprot); deschide un fiier pentru
citire n mod similar cu al doilea constructor al clasei.
2) filebuf* rdbuf() returneaz adresa ctre buffer-ul fluxului curent, obiect al clasei
filebuf.

Din clasele ostream i fstreambuf este derivat clasa ofstream specializat pentru
operaii de iesire pe fiiere.

ifstream
istream fstreambuf
82

Clasa ofstream are, de asemenea, 4 constructori asemntori cu cei din clasa fstreambuf.
Primul constructor creaz un flux pe care nu-l ataeaz unui fiier:

ofstream();

Al doilea constructor creaz un obiect al clasei ofstream, deschide un fiier pentru scriere
i l ataeaz obiectului creat.

ofstream(const char* s,int mod=out,int prot=filebuf::openprot);

Semnificaia parametrilor este aceeai ca la constructorul al doilea constrctor al clasei
fstreambase.
Al treilea constructor creaz obiectul i l leag de un fiier deschis pentru operaii de
scriere, al crui descriptor d este primit ca argument:

ofstream(int d);

Exist i constructorul care creaz obiectul i l leag de fiierul al crui descriptor este d.
Se specific n plus i adresa zonei de memorie tampon ce se va utiliza precum i lungimea
acesteia:

ofstream(int d, char* s, int n);

Ca funcii membre n clasa ofstream avem:

1) void open(const char* s, int mod, int prot=filebuf::openprot); deschide un fiier pentru
scriere n mod similar cu al doilea constructor al clasei.
2) filebuf* rdbuf() returneaz adresa ctre buffer-ul buf cu care lucreaz fluxul de ieire.
Din clasele iostream, ifstream i ofstream este derivat clasa fstream, care este
specializat pentru a lucra cu un fiier n care sunt posibile att operaii de intrare, ct i ieire:


Clasa fstream are, de asemeanea, 4 constructori (asemntori cu cei din clasa fstreambuf).
Un prim constructor creaz un flux pe care nu-l ataeaz nici unui fiier:

fstream();

Al doilea constructor creaz un obiect al clasei fstream, deschide un fiier pentru operaii
de citire i de scriere i l ataeaz obiectului creat. Semnificaia parametrilor este aceeai ca la
constructorul al doilea al clasei fstreambase:
ofstream
ostream fstreambuf
fstream
ifstream ofstream iostream
83

fstream(const char* s,int mod,int prot=filebuf::openprot);

Al treilea constructor creaz obiectul i l leag de un fiier I/O deschis deja, al crui
descriptor d este primit ca argument:

fstream(int d);

Al patrulea constructor creaz obiectul i l leag de fiierul al crui descriptor este d. Se
specific adresa i lungimea zonei de memorie tampon:

fstream(int d, char* s, int n);

Ca funcii membre clasei fstream avem:

1) void open(const char* s, int mod, int prot=filebuf::openprot); deschide un fiier pentru
citire i scriere n mod similar cu al doilea constructor al clasei, care are aceeai parametri cu
funcia open.
2) filebuf* rdbuf() returneaz adresa ctre buffer-ul fiierului.

Pentru a lucra cu un fiier, se instaniaz una dintre clasele: ifstream, ofstream sau
fstream. Prelucrarea fiierului se face cu ajutorul metodelor motenite din clasele: ios, istream,
ostream, fstreambase.
Pentru a exemplifica modul de lucru cu fiiere dm listingul ctorva programe:

1) Afiarea pe ecran a coninutului unui fiier text:

# include <iostream.h>
# include <fstream.h>
# include <conio.h>
# define lmaxlinie 79 // lungimea maxima a liniei fisierului text

int main()
{
char numef[100],linie[lmaxlinie+1];
long n=0;

cout<<"AFISARE CONTINUT FISIER TEXT"<<endl<<endl;
cout<<"Dati numele fisierului text: ";
cin>>numef;
ifstream fis(numef); // creare obiect si deschidere fisier pentru citire
if (fis.bad()) // verificare daca fisierul nu a fost deschis cu succes
{
cerr<<"Eroare! Nu am putut deschide '"<<numef<<"' pt citire";
getch();
return 1;
}
while (!fis.eof())
{
n++; // numarul de linii afisate
fis.getline(linie,lmaxlinie); // citire linie din fisier text
84
cout<<linie<<endl; // afisare pe ecran
if (n%24==0) getch(); // dupa umplerea unui ecran se
} // asteapta apasarea unei taste
fis.close();
cout<<endl<<"AM AFISAT "<<n<<" LINII"<<endl<<flush;
getch();
return 0;
}

Observaii:
1) Cu ajutorul funciei bad() am verificat dac fiierul nu a fost deschis cu succes (dac
fiierul nu exist pe disc).
2) Funcia getline (din clasa istream) citete o linie de lungime maxim lmaxlinie=79
caractere dintr-un fiier text. Dac linia are mai mult de lmaxlinie caractere, atunci
sunt citite exact lmaxlinie caractere, restul caracterelor de pe linia curent a fiierului
fiind citite data urmtoare.


2) Program pentru concatenarea a dou fiiere:

# include <iostream.h>
# include <fstream.h>
# include <conio.h>
# define lbuf 1000 // lungimea buffer-ului de citire din fisier

int main()
{
ifstream fiss; // fisier pentru operatii de citire
ofstream fisd; // fisier pentru operatii de scriere
char numefs1[100],numefs2[100],numefd[100],s[lbuf];
cout<<"CONCATENARE DE DOUA FISIERE"<<endl<<endl;
cout<<"Dati numele primului fisier sursa: ";
cin>>numefs1;
cout<<"Dati numele celui de-al doilea fisier sursa: ";
cin>>numefs2;
cout<<"Dati numele fisierului destinatie: ";
cin>>numefd;
fisd.open(numefd,ios::binary); // deschidere fisier pe octeti (mod binar)
if (fisd.bad()) // verificare daca fisierul nu s-a deschis cu succes
{
cerr<<"Eroare! Nu am putut deschide fisierul '"<<numefd<<"' pt scriere.";
getch(); return 1;
}
fiss.open(numefs1,ios::binary); // deschidere fisier pe octeti (mod binar)
if (fiss.bad()) // verificare daca fisierul nu s-a deschis cu succes
{
fisd.close();
cerr<<"Eroare! Nu am putut deschide fisierul '"<<numefs1<<"' pt citire.";
getch(); return 1;
}
while (!fiss.eof())
85
{
fiss.read(s,lbuf); // se citesc maxim lbuf baiti din fisier
fisd.write(s,fiss.gcount());// se scriu baitii cititi mai sus in fisier
}
fiss.close(); // inchidere fisier
fiss.open(numefs2,ios::binary); // deschidere fisier pe octeti (mod binar)
if (fiss.bad()) // verificare daca fisierul nu s-a deschis cu succes
{
fisd.close();
cerr<<"Eroare! Nu am putut deschide fisierul '"<<numefs2<<"' pt citire.";
getch(); return 1;
}
while (!fiss.eof())
{
fiss.read(s,lbuf); // se citesc maxim lbuf baiti din fisier
fisd.write(s,fiss.gcount());// se scriu baitii cititi mai sus in fisier
}
fiss.close(); // inchidere fisier
if (fisd.good()) cout<<"Concatenarea s-a incheiat cu succes!"<<endl;
fisd.close(); // inchidere fisier
getch();
return 0;
}

Observaii:
1) Pentru a deschide un fisier n modul binar trebuie specificat explicit acest lucru
folosind ios::binary n parametrul al doilea al funciei open. Dac nu se face
specificarea ios::binary, atunci fiierul este deschis n modul text.
2) Funcia gcount() (din clasa istream) returneaz numrul de caractere efectiv citite
ultima dat cu ajutorul funciei read. n programul de mai sus valoarea returnat de
gcount() este lbuf, adic 1000, excepie face ultima citire din fiier.
3) Cu ajutorul funciei good() am verificat dac fiierul a fost prelucrat fr s apar
erori.


3) Sortarea unui ir citit dintr-un fiier text:

# include <iostream.h>
# include <fstream.h>
# include <conio.h>
# include <stdlib.h>

int sort_function(const void *a,const void *b)
{
float *x=(float *)a,*y=(float *)b;

if (*x<*y) return -1;
if (*x==*y) return 0;
return 1;
}

86
int main()
{
char numef[100];
int i,n;
float *a;
fstream fis;

cout<<"QUICKSORT"<<endl<<endl;
cout<<"Dati numele fisierului text cu elementele sirului: ";
cin>>numef;
fis.open(numef,ios::in); // deschidere fisier pentru citire
if (fis.bad())
{
cerr<<"Eroare! Nu am putut deschide fisierul '"<<numef<<"' pt citire.";
getch();
return 1;
}
fis>>n; // citirea numarului intreg n din fisier
a=new float[n];
if (a==NULL)
{
cerr<<"Eroare! Memorie insuficienta.";
getch();
return 1;
}
for (i=0;i<n;i++)
{
if (fis.eof())
{
cerr<<"Eroare! Elemente insuficiente in fisier.";
getch();
return 1;
}
fis>>a[i]; // citirea unui numar real (float)
}
fis.close();
qsort((void *)a, n, sizeof(float), sort_function); // sortare QuickSort
cout<<"Dati numele fisierului text in care se va depune sirul sortat: ";
cin>>numef;
fis.open(numef,ios::out); // deschidere fisier pentru scriere
if (fis.bad())
{
cerr<<"Eroare! Nu am putut crea fisierul '"<<numef<<"'.";
getch();
return 1;
}
fis<<n<<endl;
for (i=0;i<n;i++) fis<<a[i]<<" ";
delete [] a;
if (fis.good()) cout<<"Am sortat sirul !"<<endl;
fis.close();
getch();
87
return 0;
}
Observaii:
1) Variabila fis este folosit nti pentru a prelucra un fiier de intrare i apoi pentru unul
de ieire.
2) Citirea unor valori dintr-un fiier n modul text a fost realizat cu ajutorul
operatorului >>, iar scrierea cu operatorul <<.
3) Sortarea irului am fcut-o cu ajutorul funciei qsort, a crei definiie o gsim n
fiierul antet stdlib.h sau n search.h. Cu ajutorul acestei funcii se pot sorta iruri de
elemente de orice tip. Noi am folosit-o pentru sortarea unui ir de valori de tip float.
Funcia sort_function compar dou valori de tipul elementelor din ir. Dac se
dorete o sortare cresctoare, funcia trebuie s returneze o valoare negativ. Se
returneaz 0, dac sunt egale valorile i, respectiv, se returneaz un numr pozitiv
dac prima valoare e mai mare dect a doua.


4) Gestiunea stocului unei firme:

# include <iostream.h>
# include <fstream.h>
# include <stdlib.h>
# include <process.h>
# include <conio.h>
# include <stdio.h>
# include <string.h>

struct tstoc
{
char cod_prod[10],den_prod[50];
double cant,pret;
};

char *numef="stoc.dat"; // numele fisierului in care se retine stocul

void VanzCump() // Vanzare / cumparare dintr-un produs
{
char *s,cod[10];
int gasit=0;
long n=0;
double cant;
struct tstoc *st;
fstream fis;

s=new char[sizeof(struct tstoc)];
fis.open(numef,ios::binary|ios::in|ios::out|ios::nocreate);
if (fis.bad())
{
cerr<<endl<<"Eroare! Nu am putut deschide fisierul '"<<numef<<"'.";
getch(); exit(0);
}
cout<<"Dati codul produsului care se vinde / cumpara: ";
88
cin>>cod;
while (!fis.eof())
{
fis.read(s,sizeof(struct tstoc));
if (fis.good())
{
st=(struct tstoc *)s;
if (!strcmp(cod,st->cod_prod))
{
gasit=1;
cout<<"Denumire: "<<st->den_prod<<endl;
cout<<"Cantitate: "<<st->cant<<endl;
cout<<"Pret: "<<st->pret<<endl<<endl;
cout<<endl<<"Dati cantitatea cu care se modifica stocul: ";
cin>>cant;
st->cant+=cant;
fis.seekp(n*sizeof(struct tstoc),ios::beg);
fis.write(s,sizeof(struct tstoc));
cout<<endl<<"Cod produs: "<<st->cod_prod<<endl;
cout<<"Denumire: "<<st->den_prod<<endl;
cout<<"Cantitate: "<<st->cant<<endl;
cout<<"Pret: "<<st->pret<<endl;
cout<<"Valoare: "<<st->pret*st->cant<<endl;
getch();
}
}
n++;
}
if (!gasit)
{
cerr<<endl<<"Nu am gasit produs cu acest cod!"<<endl;
getch();
}
fis.close(); delete [] s;
}

void Adaugare() // Introducerea unui nou produs in stoc
{
char *s,*s2;
int gasit=0;
struct tstoc *st,*st2;
fstream fis;

s=new char[sizeof(struct tstoc)];
st=(struct tstoc*)s;
s2=new char[sizeof(struct tstoc)];
st2=(struct tstoc*)s2;
fis.open(numef,ios::binary|ios::in);
if (fis.bad())
{
cerr<<endl<<"Eroare! Nu am putut deschide fisierul '"<<numef<<"'.";
getch(); exit(0);
89
}
cout<<endl<<"Dati codul produsului care se introduce in stoc: ";
cin>>st->cod_prod;
while (!fis.eof() && !gasit)
{
fis.read(s2,sizeof(struct tstoc));
if (fis.good())
if (!strcmp(st->cod_prod,st2->cod_prod)) gasit=1;
}
if (!gasit)
{
fis.close();
fis.open(numef,ios::binary|ios::app);
cout<<"Denumire: ";
cin>>st->den_prod;
cout<<"Cantitate: ";
cin>>st->cant;
cout<<"Pret: ";
cin>>st->pret;
fis.write(s,sizeof(struct tstoc));
}
else
{
cerr<<"Eroare! Mai exista un produs cu acest cod:"<<endl;
cout<<endl<<"Cod produs: "<<st->cod_prod<<endl;
cout<<"Denumire: "<<st->den_prod<<endl;
cout<<"Cantitate: "<<st->cant<<endl;
cout<<"Pret: "<<st->pret<<endl;
cout<<"Valoare: "<<st->pret*st->cant<<endl;
getch();
}
fis.close(); delete [] s2; delete [] s;
}

void AfisProd() // Afisarea unui produs cu un anumit cod
{
char *s,cod[10];
int gasit=0;
struct tstoc *st;
ifstream fis;
s=new char[sizeof(struct tstoc)];
st=(struct tstoc*)s;
fis.open(numef,ios::binary);
if (fis.bad())
{
cerr<<endl<<"Eroare! Nu am putut deschide fisierul '"<<numef<<"'.";
getch();
exit(0);
}
cout<<endl<<"Dati codul produsului care se afiseaza: ";
cin>>cod;
while (!fis.eof())
90
{
fis.read(s,sizeof(struct tstoc));
if (fis.good())
{
if (!strcmp(st->cod_prod,cod))
{
gasit=1;
cout<<endl<<"Cod produs: "<<st->cod_prod<<endl;
cout<<"Denumire: "<<st->den_prod<<endl;
cout<<"Cantitate: "<<st->cant<<endl;
cout<<"Pret: "<<st->pret<<endl;
cout<<"Valoare: "<<st->pret*st->cant<<endl;
getch();
}
}
}
if (!gasit)
{
cerr<<endl<<"Nu am gasit produs cu acest cod!"<<endl;
getch();
}
fis.close();
delete [] s;
}

void ModifPret() // Modificarea pretului unui produs
{
char *s,cod[10];
int gasit=0;
long n=0;
double pret;
struct tstoc *st;
fstream fis;

s=new char[sizeof(struct tstoc)];
fis.open(numef,ios::binary|ios::in|ios::out|ios::nocreate);
if (fis.bad())
{
cerr<<endl<<"Eroare! Nu am putut deschide fisierul '"<<numef<<"'.";
getch();
exit(0);
}
cout<<"Dati codul produsului pentru care se modifica pretul: ";
cin>>cod;
while (!fis.eof())
{
fis.read(s,sizeof(struct tstoc));
if (fis.good())
{
st=(struct tstoc *)s;
if (!strcmp(cod,st->cod_prod))
{
91
gasit=1;
cout<<"Denumire: "<<st->den_prod<<endl;
cout<<"Cantitate: "<<st->cant<<endl;
cout<<"Pret: "<<st->pret<<endl<<endl;
cout<<endl<<"Dati noul pret al produsului: ";
cin>>pret;
st->pret=pret;
fis.seekp(n*sizeof(struct tstoc),ios::beg);
fis.write(s,sizeof(struct tstoc));
cout<<endl<<"Cod produs: "<<st->cod_prod<<endl;
cout<<"Denumire: "<<st->den_prod<<endl;
cout<<"Cantitate: "<<st->cant<<endl;
cout<<"Pret: "<<st->pret<<endl;
cout<<"Valoare: "<<st->pret*st->cant<<endl;
getch();
}
}
n++;
}
if (!gasit)
{
cerr<<endl<<"Nu am gasit produs cu acest cod!"<<endl;
getch();
}
fis.close();
delete [] s;
}

void Stergere() // Stergerea unui produs cu un anumit cod din stoc
{
char *s,cod[10];
int gasit=0;
struct tstoc *st;
ifstream fis1;
ofstream fis2;

s=new char[sizeof(struct tstoc)];
fis1.open(numef,ios::binary);
if (fis1.bad())
{
cerr<<endl<<"Eroare! Nu am putut deschide fisierul '"<<numef<<"'.";
getch();
exit(0);
}
cout<<"Dati codul produsului care se sterge: ";
cin>>cod;
fis2.open("stoc.tmp",ios::binary);
if (fis2.bad())
{
cerr<<endl<<"Eroare! Nu am putut deschide fisierul 'stoc.tmp'.";
getch(); exit(0);
}
92
while (!fis1.eof())
{
fis1.read(s,sizeof(struct tstoc));
if (fis1.good())
{
st=(struct tstoc *)s;
if (strcmp(st->cod_prod,cod))
fis2.write(s,sizeof(struct tstoc));
else
{
gasit=1;
cout<<endl<<"S-a sters produsul:"<<endl<<endl;
cout<<"Cod produs: "<<st->cod_prod<<endl;
cout<<"Denumire: "<<st->den_prod<<endl;
cout<<"Cantitate: "<<st->cant<<endl;
cout<<"Pret: "<<st->pret<<endl;
cout<<"Valoare: "<<st->pret*st->cant<<endl;
getch();
}
}
}
fis2.close();
fis1.close();
if (!gasit)
{
remove("stoc.tmp");
cerr<<endl<<"Nu am gasit produs cu acest cod!"<<endl;
getch();
}
else
{
remove(numef);
rename("stoc.tmp",numef);
}
}

void Cautare() // Cautarea unui produs dupa un sir de caractere ce apare in denumire
{
char *s,nume[50];
int gasit=0;
struct tstoc *st;
ifstream fis;

s=new char[sizeof(struct tstoc)];
st=(struct tstoc*)s;
fis.open(numef,ios::binary);
if (fis.bad())
{
cerr<<endl<<"Eroare! Nu am putut deschide fisierul '"<<numef<<"'.";
getch();
exit(0);
}
93
cout<<endl<<"Dati sirul de caractere ce apare in numele produsului cautat: ";
cin>>nume;
while (!fis.eof())
{
fis.read(s,sizeof(struct tstoc));
if (fis.good())
{
if (strstr(st->den_prod,nume) != NULL)
{
gasit++;
cout<<endl<<"Cod produs: "<<st->cod_prod<<endl;
cout<<"Denumire: "<<st->den_prod<<endl;
cout<<"Cantitate: "<<st->cant<<endl;
cout<<"Pret: "<<st->pret<<endl;
cout<<"Valoare: "<<st->pret*st->cant<<endl;
getch();
}
}
}
if (!gasit)
{
cerr<<endl<<"Nu am gasit nici un produs!"<<endl;
getch();
}
else
{
cout<<endl<<"Am gasit "<<gasit<<" produse!"<<endl;
getch();
}
fis.close();
delete [] s;
}

void AfisStoc() // Afisarea tuturor produselor din stoc
{
char *s;
int gasit=0;
struct tstoc *st;
ifstream fis;

s=new char[sizeof(struct tstoc)];
st=(struct tstoc*)s;
fis.open(numef,ios::binary);
if (fis.bad())
{
cerr<<endl<<"Eroare! Nu am putut deschide fisierul '"<<numef<<"'.";
getch(); exit(0);
}
cout<<"Stocul este alcatuit din:"<<endl<<endl;
while (!fis.eof())
{
fis.read(s,sizeof(struct tstoc));
94
if (fis.good())
{
gasit++;
cout<<endl<<"Cod produs: "<<st->cod_prod<<endl;
cout<<"Denumire: "<<st->den_prod<<endl;
cout<<"Cantitate: "<<st->cant<<endl;
cout<<"Pret: "<<st->pret<<endl;
cout<<"Valoare: "<<st->pret*st->cant<<endl;
getch();
}
}
if (!gasit)
{
cerr<<endl<<"Nu exista nici un produs in stoc!"<<endl;
getch();
}
else
{
cout<<endl<<"Am afisat "<<gasit<<" produse!"<<endl;
getch();
}
fis.close();
delete [] s;
}

void ValStoc() // Afisarea valorii totale a stocului
{
char *s;
double total=0;
struct tstoc *st;
ifstream fis;

s=new char[sizeof(struct tstoc)];
st=(struct tstoc*)s;
fis.open(numef,ios::binary);
if (fis.bad())
{
cerr<<endl<<"Eroare! Nu am putut deschide fisierul '"<<numef<<"'.";
getch();
exit(0);
}
while (!fis.eof())
{
fis.read(s,sizeof(struct tstoc));
if (fis.good()) total+=st->cant*st->pret;
}
fis.close();
cout<<"Valoarea totala a stocului este: "<<total<<endl;
getch();
delete [] s;
}

95
void main()
{
char c;
do
{
cout<<"GESTIONAREA STOCULUI UNEI FIRME"<<endl<<endl;
cout<<" 1) Vanzare / cumparare marfa"<<endl;
cout<<" 2) Adaugare produs in stoc"<<endl;
cout<<" 3) Afisare produs dupa cod"<<endl;
cout<<" 4) Modificare pret"<<endl;
cout<<" 5) Stergere produs din stoc"<<endl;
cout<<" 6) Cautare produs dupa nume"<<endl;
cout<<" 7) Afisare stoc"<<endl;
cout<<" 8) Valoare totala stoc"<<endl;
cout<<endl<<"Esc - Exit"<<endl<<endl;
c=getch();
switch (c)
{
case '1': VanzCump(); break;
case '2': Adaugare(); break;
case '3': AfisProd(); break;
case '4': ModifPret(); break;
case '5': Stergere(); break;
case '6': Cautare(); break;
case '7': AfisStoc(); break;
case '8': ValStoc(); break;
}
}
while (c!=27);
}


Observaie:
Fluxurile C++ pentru fiiere lucreaz numai cu caractere i iruri de caractere. De aceea,
cnd am folosit funciile read, respectiv write pentru citirea, respectiv scrierea unei variabile de
tipul struct tstoc, am fost nevoii s facem conversii de la ir de caractere la tipul adres ctre un
tip struct tstoc. De fapt, n variabilele s (de tip adres ctre un ir de caractere) i st (pointer ctre
tipul struct tstoc) s-a reinut aceeai adres de memorie. Am citit i scris irul de caractere de
lungime sizeof(struct tstoc), ir de caractere aflat la aceeai adres ctre care pointeaz i st. Cu
alte cuvinte, scrierea i citirea datelor la adresa st s-au fcut prin intermediul variabilei s.


Rezumat

Am studiat clasele specializate pe lucru cu fiiere: fstreambase, ifstream, ofstream i
fstream din ierarhia ios. Am vzut cum se deschide un fiier n C++, cum se prelucreaz i cum
se nchide folosind facilittile ierarhiei ios. Am putut urmri n finalul prezentrii cteva exemple
ilustrative la modul de lucru cu fiiere din C++.



96
2.5. Prelucrarea string-urilor n C++

Obiective

Ne propunem s studiem clasele din ierarhia ios specializate pe prelucrarea string-urilor
(iruri de caractere NULL terminate): istrstream, ostrstream i strstream.

n ierarhia de fluxuri ios exist clase scrise pentru a prelucra string-uri (iruri de caractere
ncheiate cu \0) ntr-o manier orientat pe obiecte.
Clasele pentru lucrul cu string-uri sunt definite n fiierul antet strstrea.h.
Buffer-ele pentru string-uri sunt obiecte ale clasei strstreambuf, care este o clas derivat
din streambuf.
Clasa strstreambase specializeaz clasa ios pentru lucrul cu string-uri, specificndu-se
faptul c se va lucra cu un buffer de tip strstreambuf.
Clasa strstreambase are 2 constructori:

strstreambase();
strstreambase(const char* buf, int n, char* start);

Primul constructor creaz un obiect al clasei strstreambase, fr a specifica ns irul de
caractere cu care se va lucra. Legtura se va face dinamic cu un ir de caractere prima dat cnd
obiectul va fi utilizat.
Al doilea constructor creaz un obiect de strstreambase, obiect ce va folosi irul de
caractere aflat la adresa buf, care are lungimea n, al crui poziie de pornire este start.
Funcia membr rdbuf clasei strstreambase va returna adresa buffer-ului (obiect al clasei
strstreambuf), cu care lucreaz fluxul de tip string:

strstreambuf* rdbuf();

Din clasele istream i strstreambase este derivat clasa istrstream, care este
specializat (dup cum i spune numele i clasele din care este derivat) pentru a lucra cu fluxuri
de intrare de tip string:


Clasa istrstream are doi constructori:

istrstream(const char* s);
istrstream(const char* s, int n);

Primul constructor creaz un obiect al clasei istrstream i specific faptul c acesta va
lucra cu string-ul s.
Al doilea constructor, n plus fa de primul, limiteaz la n numrul de caractere din irul
s cu care se va lucra. Cu alte cuvinte, string-ul s nu va putea fi mai lung de n caractere.
istrstream
istream strstreambuf
97
Din clasele ostream i strstreambase este derivat clasa ostrstream, care este
specializat pentru a lucra cu fluxuri de ieire de tip string (string-ului nu i se vor putea aplica
dect operaii de scriere).


Clasa ostrstream are doi constructori:

ostrstream();
ostrstream(char* s, int n, int poz=ios::out);

Primul constructor creaz un obiect de tipul ostrstream, obiect care va lucra cu un ir de
caractere alocat dinamic.
Al doilea constructor creaz un obiect al clasei ostrstream i specific faptul c acesta va
lucra cu irul de caractere s de lungime maxim n. Poziionarea n string-ul s se face implicit la
nceputul acestuia. Dac ultimul parametru este specificat ca avnd valoarea ios::app sau
ios::ate, atunci poziionarea n string-ul s se va face pe ultima poziie a acestuia, adic pe poziia
pe care se afla caracterul \0 (delimitatorul de sfrit de string).
Pe lng cei doi constructori, clasa ostrstream mai are dou funcii membre:

1) int pcount() returneaz numrul de caractere reinute n buffer.
2) char* str() returneaz adresa ctre irul de caractere cu care lucreaz fluxul.

Din clasele iostream, istrstream i ostrstream este derivat clasa strstream, care este o
clas de fluxuri ce lucreaz cu string-uri ce suport operaii atat de intrare, ct i de ieire:

Clasa strstream are doi constructori:

strstream();
strstream(char* s, int n, int poz=ios::out);

Primul constructor creaz un obiect de tipul strstream, obiect care va lucra cu un ir de
caractere alocat dinamic.
Al doilea constructor creaz un obiect al clasei strstream i specific faptul c acesta va
lucra cu irul de caractere s de lungime maxima n. Poziionarea n string-ul s se face implicit la
nceputul acestuia. Dac valoarea ultimului parametru este ios::app sau ios::ate, atunci
poziionarea n string-ul s se va face pe ultima poziie a acestuia, adic pe poziia pe care se afla
caracterul \0 (delimitatorul de sfrit de string).
Pe lng cei doi constructori, n clasa strstream exist i funcia membr str(), care
returneaz adresa ctre irul de caractere cu care lucreaz fluxul.
ostrstream
ostream strstreambuf
strstream
istrstream ostrstream iostream
98
Cnd se dorete a se lucra cu fluxuri de tip string se instaniaz una dintre clasele:
istream, ostream i strstream. Prelucrarea string-ului se face utiliznd metodele din clasele
superioare: ios, istream, ostream, strstreambase.
Spre exemplificare, prezentm urmtorul program care efectueaz operaii aritmetice cu
numere reale (citirea expresiei matematice se face dintr-un string):
# include <stdio.h>
# include <conio.h>
# include <math.h>
# include <iostream.h>
# include <strstrea.h>

void main(void)
{
char s[100],c=0,op;
float nr1,nr2;
istrstream *si;

do
{
cout<<" Calcule matematice (operatiile: +, -, *, / si ^)"<<endl;
cout<<"-------------------------------------------------)"<<endl;
cout<<endl<<"<numar_real> <operator> <numar_real>: ";
gets(s);
cout<<endl;
si=new istrstream(s); // construire obiect
*si>>nr1>>op>>nr2; // extragere real, caracter, real din string-ul s
if (!si->bad()) // verificare daca extragerea a fost cu succes
switch (op)
{
case '+':
cout<<nr1<<op<<nr2<<"="<<(nr1+nr2);
break;
case '-':
cout<<nr1<<op<<nr2<<"="<<(nr1-nr2);
break;
case '*':
cout<<nr1<<op<<nr2<<"="<<(nr1*nr2);
break;
case '/':
if (nr2) cout<<nr1<<op<<nr2<<"="<<(nr1/nr2);
else cout<<"Eroare! Impartire prin 0.";
break;
case '^': // ridicare la putere
if (nr1>0) cout<<nr1<<op<<nr2<<"="<<pow(nr1,nr2);
else cerr<<"Eroare! Primul numar negativ.";
break;
default:
cerr<<"Eroare! Operator necunoscut";
}
else cerr<<"Eroare! Utilizare incorecta.";
cout<<endl<<endl<<"Apasati Esc pentru a parasi programul,";
cout<<" orice alta tasta pentru a continua.";
99
c=getch();
delete si; // distrugere obiect
}
while (c!=27);
}

Rezumat

Pentru prelucrarea string-urilor n C++ instaniem una din clasele: istrstream, ostrstream
sau strstream. Practic, un ir de caractere NULL terminat se mbrac ntr-un obiect pentru a fi
prelucrat, urmnd ca n final el s poat fi extras din obiect cu metoda str() care returneaz
adresa irului.

2.6. Clasa complex din C++

Obiective

Ne propunem s studiem n final o alt clas interesant care se instaleaz odat cu
mediul de programare C++. Este vorba de o clas scris pentru numere complexe. Aceast clas
ne ofer un mare numr de operatori i funcii pentru numere complexe. Practic, aproape tot ce
exist pentru numere reale (operatori i funciile din fiierul antet math.h) este aplicabil i
numerelor complexe.

C++ ofer o clas de lucru cu un numr complex. n aceast clas sunt suprancrcai o
serie de operatori. De asemenea exist o mulime de metode prietene clasei pentru numere
complexe.
n continuare vom prezenta clasa complex aa cum exist ea n Borland C++. Pentru a
lucra cu ea trebuie inclus fiierul antet complex.h.
Clasa complex conine dou date de tip double. Este vorba de re, care reine partea real a
numarului complex i de im, partea imaginar a numarului complex. Aceste date sunt private.
Clasa complex are 2 constructori:

complex();
complex(double Re, double Im=0);

Primul constructor creaz un obiect fr a iniializa partea real i cea imaginar a
numarului complex.
Al doilea constructor iniializeaz partea real i pe cea imaginar cu cele dou valori
primite ca argumente. Pentru al doilea argument exist valoarea implicit 0. Evident, n cazul n
care valoarea pentru partea imaginar este omis, la construcia obiectului complex ea se va
iniializa cu 0 (vezi capitolul dedicat valorilor implicite pentru parametrii funciilor n C++).
Exist o mulime de funcii (matematice) definite ca fiind prietene clasei complex:

1) double real(complex &z) returneaz partea real a numrului complex z.
2) double imag(complex &z) returneaz partea imaginar a numrului z.
3) double conj(complex &z) returneaz conjugatul numrului complex z.
4) double norm(complex &z) returneaz norma numrului complex z, adic:

2 2
im re +

100
5) double arg(complex &z) returneaz argumentul numrului complex z (msura unui
unghi n radiani n intervalul [0, 2t) ).
6) complex polar(double r, double u=0); creaz un obiect de tip complex pornind de la
norma i argumentul acestuia. Numrul complex care se creaz este rcos(u)+rsin(u)i. Se
returneaz obiectul creat.
7) double abs(complex &z) returneaz modulul numrului complex z, adic re
2
+im
2

(ptratul normei).
8) complex acos(complex &z) returneaz arccosinus din numrul complex z.
9) complex asin(complex &z) returneaz arcsinus din numrul complex z.
10) complex atan(complex &z) returneaz arctangent din numrul complex z.
11) complex cos(complex &z) returneaz cosinus din numrul complex z.
12) complex cosh(complex &z) returneaz cosinus hiperbolic din z.
13) complex exp(complex &z) returneaz e
z
.
14) complex log(complex &z) returneaz logaritm natural din numrul z.
15) complex log10(complex &z) returneaz logaritm zecimal (n baza 10) din z.
16) complex pow(double r, complex &z) ridic numrul real r la puterea z.
17) complex pow(complex &z, double r) ridic numrul complex z la puterea r.
18) complex pow(complex &z1, complex &z2) ridic z1 la puterea z2.
19) complex sin(complex &z) returneaz sinus din numrul complex z.
20) complex sinh(complex &z) returneaz sinus hiperbolic din z.
21) complex sqrt(complex &z) returneaz radical din numrul complex z.
22) complex tan(complex &z) returneaz tangent din numrul complex z.
23) complex tanh(complex &z) returneaz tangent hiperbolic din z.

n clasa complex sunt suprancrcai operatorii aritmetici: +, -, *, / de cte 3 ori.
Astfel ei funcioneaz ntre dou numere complexe, ntre un double i un complex i ntre un
complex i un double.
Exist definii operatorii de tip atribuire combinat cu un operator aritmetic: +=, -=,
*=, /=. Aceti operatori sunt definii sub dou forme: ntre dou numere complexe i ntre un
complex i un double.
Dou numere complexe se pot compara cu ajutorul operatorilor == i !=.
Operatorii unari pentru semn (+, respectiv ) sunt de asemenea definii n clasa complex
(aceti operatori permit scrierea +z, respectiv -z).
Extragerea, respectiv introducerea unui numr complex dintr-un / ntr-un flux se poate
face cu ajutorul operatorilor <<, respectiv >>, care sunt definii ca fiind externi clasei complex, ei
nu sunt nici mcar prieteni clasei. Citirea i afiarea numrului complex se face sub forma unei
perechi de numere reale (re, im) (partea real i partea imaginar desprite prin virgul, totul
ntre paranteze rotunde). De exemplu, perechea (2.5, 7) reprezint numrul complex 2.5+7i.
Propunem n continuare o aplicaie de tip calculator pentru numere complexe pentru a
exemplifica modul de lucru cu numere complexe:

# include <stdio.h>
# include <conio.h>
# include <complex.h>
# include <iostream.h>
# include <strstrea.h>

void main(void)
{
char s[100],c=0,op;
complex nr1,nr2; // doua numere complexe (obiecte ale clasei complex)
101
istrstream *si;

do
{
cout<<" Calcule cu numere complexe (+, -, *, / si ^)"<<endl;
cout<<"---------------------------------------------)"<<endl;
cout<<endl<<"<numar_complex> <operator> <numar_complex>: ";
gets(s);
cout<<endl;
si=new istrstream(s);
*si>>nr1>>op>>nr2;
if (si->good())
switch (op)
{
case '+':
cout<<nr1<<op<<nr2<<"="<<(nr1+nr2);
break;
case '-':
cout<<nr1<<op<<nr2<<"="<<(nr1-nr2);
break;
case '*':
cout<<nr1<<op<<nr2<<"="<<(nr1*nr2);
break;
case '/':
if (abs(nr2)) cout<<nr1<<op<<nr2<<"="<<(nr1/nr2);
else cout<<"Eroare! Impartire prin 0.";
break;
case '^': // ridicare la putere
cout<<nr1<<op<<nr2<<"="<<pow(nr1,nr2);
break;
default:
cerr<<"Eroare! Operator necunoscut.";
}
else cerr<<"Eroare! Utilizare incorecta.";
cout<<endl<<endl<<"Apasati Esc pentru a parasi programul,";
cout<<" orice alta tasta pentru a continua.";
c=getch();
delete si;
}
while (c!=27);
}

Dac rulm programul de mai sus i introducem de la tastatur: (2.5,7)+(1.31,-2.2), pe
ecran se va afia: (2.5, 7)+(1.32, -2.3)=(3.82, 4.7).

Rezumat

n finalul fiscuiei noastre despre programarea orientat pe obiecte din C++ am prezentat
clasa complex scris pentru lucrul cu numere complexe. Nu prea avem membri n clasa complex
ci mai mult funcii prietene pentru a apropia mai mult modul de lucru cu numere complexe de cel
cu numere reale din C.
102



Indicaii i rspunsuri

Cap. 1.5. i 1.6.

Problemele 1. i 2. Se vor folosi maniopulatorii setprecision, setw i setiosflags, ultimul
cu indicatorii de formatare: ios::left (pentru texte), respectiv ios::right (pentru valori numerice),
ios::fixed i ios::showpoint.(pentru numere reale).

Cap. 1.7.

Problemele 1., 2. i 3. Este de preferat (este mai uor) s se adapteze prima metod de
alocare dinamic a memorie pentru o matrice la aceste probleme (cea cu m+1 alocri).

Cap. 1.8.5.

Problema 1. Un numr natural n mai mare ca doi (n>2) este prim dac nu se mparte la 2
i la nici un numr ntreg impar ntre 3 i parte ntreag din radical din n. Atenie! 0 i 1 nu sunt
prime.

Cap. 1.9.

Se va folosi metoda prezentat mai sus pentru reuniune: sortarea unuia dintre vectori i
cutarea rapid n vectorul sortat.

Cap. 1.10.

Posibile excepii ce pot fi tratate sunt: scoaterea unui element care nu aparine unei
multimi i reuniunea a dou mulimi care conduce la o mulime cu mai mult de 1000 de
elemente.

Cap. 2.2. (la sfritul subcapitolului 2.2.19.)

Problema 3. Vezi exemplul de la capitolul dedicat supraincrcrii operatorilor
Problema 4. Produsul vectorial a doi vectori tridimensionali este:


z y x
z y x
b b b
a a a
k j i
b a =

Problema 5. Calcularea sumei se poate face mai rapid grupnd termenii astfel:

A
t
+ (A
t
)
2
+ + (A
t
)
n
= A
t
+ A
t
(A
t
+ A
t
(A
t
+ ... + A
t
A
t
)).

Problema 6. Pentru cel mai mare divizor comun se va folosi algoritmul lui Euclid, iar cel
mai mic multiplu comun se poate gsi cu formula:
103

.
) , .( . . . .
) , .( . . . .
Q P c d m m c
Q P
Q P c m m m c

=

Problema 8. Se aplic dreptei d o translaie i o rotaie aa nct dreapta s se suprapun
peste axa Ox, se gsete simetricul fa de Ox, i n final se aplic inversele rotaiei i a translaiei
iniiale. Se obine c simetricul unui punct P fa de dreapta d este dat de compunerea
urmtoarelor transformri elementare:


Yp Xp Ox Yp Xp
T R S R T
, , o o
, unde o este msura unghiului dintre axa Ox i
dreapta d. Avem:

+
=
+
=
2 2
2 2
) sin(
) cos(
b a
b
b a
a
o
o
.
ANEXA 1 - Folosirea mouse-ului sub DOS

Mediul de programare Borland C/C++ pentru DOS nu ofer faciliti directe de utilizare a
mouse-ului. Propunem n continuare cteva funcii pentru utilizarea mouse-lui att n modul text
ct i n modul grafic.
Scriei urmtorul cod ntr-un fiier cu numele mouse.h. Includei acest fiier de fiecare
dat cnd avei nevoie s scriei programe n care se folosete mouse-ul..

# include <dos.h>
# include <stdio.h>

typedef struct graphtype
{
char screenmask[16];
char cursormask[16];
int xactive,yactive;
}graphshapetype;

# define screenmask
{ \
0xFEFF,0xFEFF,0xFEFF,0xFEFF,0xFEFF,0xFEFF,0xFEFF,0x0001,\
0xFEFF,0xFEFF,0xFEFF,0xFEFF,0xFEFF,0xFEFF,0xFEFF,0xFeFF \
};

# define cursormask
{ \
0x0100,0x0100,0x0100,0x0100,0x0100,0x0100,0x0100,0xFFFE,\
0x0100,0x0100,0x0100,0x0100,0x0100,0x0100,0x0100,0x0000 \
};

# define waitdoubleclick 300;

typedef struct ppe
{
104
char ofs,seg;
}ptrtype;

struct REGPACK r;

void clicknumber(int *buttons,int *clicks,int *x,int *y);
void defgraphlocator(graphshapetype shape);
void defsensitivity(int deltax,int deltay);
void deftextlocator(int styleflag,char scrmask,char cursmask);
void defxrange(int xmin,int xmax);
void defyrange(int ymin,int ymax);
void getbuttonpress(int *button,int *n,int *x,int *y);
void getbuttonrelease(int *button,int *n,int *x,int *y);
void getmotion(int *deltax,int *deltay);
void getmouse(int *button,int *x,int *y);
void hidelocator();
void resetmouse(int *foundmouse,int *buttons);
void setdoublespeed(int speed);
void setmouse(int x,int y);
void showlocator();
int foundmouse();

void resetmouse(int *foundmouse,int *buttons) // activeaza mouse-ul
{
r.r_ax = 0;
intr(0x33,&r);
*buttons=r.r_bx;
*foundmouse=(! r.r_ax==0);
}

void showlocator() // face sa apara cursorul mouse-lui
{
r.r_ax=1;
intr(0x33,&r);
}

void hidelocator() // ascunde cursorul mouse-lui
{
r.r_ax=2;
intr(0x33,&r);
}

void getmouse(int *button,int *x,int *y) // returneaza pozitia mouse-lui
{ // si combinatia de butoane apasate
r.r_ax=3;
intr(0x33,&r);
*button=r.r_bx;
*x=r.r_cx;
*y=r.r_dx;
}

void setmouse(int x,int y) // pozitioneaza mouse-ul pe ecran la coordonatele (x,y)
105
{
r.r_ax=4;
r.r_cx=x;
r.r_dx=y;
intr(0x33,&r);
}

void getbuttonpress(int *button,int *n,int *x,int *y)
{
r.r_ax=5;
r.r_bx=*button;
intr(0x33,&r);
*button=r.r_ax;
*n=r.r_bx;
*x=r.r_cx;
*y=r.r_dx;
}

void getbuttonrelease(int *button,int *n,int *x,int *y) // returneaza butoanele apasate
{
r.r_ax=6;
r.r_bx=*button;
intr(0x33,&r);
*button=r.r_ax;
*n=r.r_bx;
*x=r.r_cx;
*y=r.r_dx;
}

void clicknumber(int *buttons,int *clicks,int *x,int *y) // returneaza nr. de click-uri
{
getmouse(buttons,x,y);
if (*buttons==1)
{
delay(300);
*buttons=0;
getbuttonpress(buttons,clicks,x,y) ;
}
else *clicks=0;
}

void defxrange(int xmin,int xmax) // defineste limitele inferioare si
{ // superioare pe orizontala ecranului
r.r_ax=7;
r.r_cx=xmin;
r.r_dx=xmax;
intr(0x33,&r);
}

void defyrange(int ymin,int ymax) // defineste limitele inferioare si
{ // superioare pe verticala ecranului
r.r_ax=8;
106
r.r_cx=ymin;
r.r_dx=ymax;
intr(0x33,&r);
}

void defgraphlocator() // defineste cursorul n modul grafic
{
r.r_ax=9;
r.r_bx=1;//activ x
r.r_cx=1;//activ y
r.r_dx=0xfe;
r.r_es=0x01;
intr(0x33,&r);
}

void deftextlocator(int styleflag,char scrmask,char cursmask) // defineste cursorul
{ // n modul text
r.r_ax=10;
if (styleflag) r.r_bx=0; else r.r_bx=1;
r.r_cx=scrmask;
r.r_dx=cursmask;
intr(0x33,&r);
}

void getmotion(int *deltax,int *deltay) // returneaza pasul de miscare
{ // pe orizontala si pe verticala
r.r_ax=11;
intr(0x33,&r);
*deltax=r.r_cx;
*deltay=r.r_dx;
}

void defsensitivity(int deltax,int deltay) // defineste sensibilitatea la miscare
{ // pe orizontala si pe verticala
r.r_ax=15;
r.r_cx=deltax;
r.r_dx=deltay;
intr(0x33,&r);
}

void setdoublespeed(int speed)
{
r.r_ax=19;
r.r_dx=speed;
intr(0x33,&r);
}

Ca aplicaie la utilizarea mouse-ului n modul grafic propunem desenarea de cercuri,
ptrate i elipse la apsarea butoanelor stnga, dreapta, respectiv stnga mpreun cu dreapta
(simultan):

# include <conio.h>
107
# include <string.h>
# include <graphics.h>
# include "mouse.h" // includere fisier cu functiile pentru mouse de mai sus

# define r 40 // dimensiune figuri (cercurri si patrate)

void main(void)
{
char s[10];
int gd=DETECT,gm,buton,x,y,dx,dy;
initgraph(&gd,&gm,"");
showlocator(); // face sa apara cursorul mouse-lui
do
{
getmouse(&buton,&x,&y); // returnare buton si pozitie mouse
getmotion(&dx,&dy); // returnare valori deplasare mouse
if (dx || dy) // verificare daca s-a miscat mouse-ul
{
setfillstyle(SOLID_FILL,RED);
setcolor(WHITE);
bar(0,0,56,10);
sprintf(s,"%3d/%3d",x,y);
outtextxy(1,2,s); // afisare pozitie cursor mouse
}
switch (buton)
{
case 1: // click buton stanga mouse
setcolor(YELLOW);
circle(x,y,r);
break;
case 2: // click buton dreapta mouse
setcolor(LIGHTCYAN);
rectangle(x-r,y-r,x+r,y+r);
break;
case 2: // click butoane stanga+dreapta mouse
setcolor(LIGHTGREEN);
ellipse(x,y,0,360,r,2*r);
break; }
}
while (!kbhit());
getch(); closegraph();
}

Aplicaia pe care o propunem pentru utilizarea mouse-ului n modul text este afiarea
caracterelor x i o la apsarea butoanelor stnga, respectiv dreapta:

# include <conio.h>
# include <stdio.h>
# include "mouse.h" // includere fisier cu functiile pentru mouse de mai sus

void main(void)
{
108
int buton,x,y,dx,dy;
textbackground(0); clrscr();
showlocator(); // face sa apara cursorul mouse-lui
do
{
getmouse(&buton,&x,&y); // returnare buton si pozitie mouse
getmotion(&dx,&dy); // returnare valori deplasare mouse
if (dx || dy) // verificare daca s-a miscat mouse-ul
{
textcolor(WHITE);
textbackground(RED);
gotoxy(1,1);
cprintf("%3d/%3d",x,y); // afisare pozitie cursor mouse
}
switch (buton)
{
case 1: // click buton stanga mouse
textbackground(0);
textcolor(YELLOW);
gotoxy(x/8+1,y/8+1);
cprintf("o");
break;
case 2: // click buton dreapta mouse
textbackground(0);
textcolor(LIGHTCYAN);
gotoxy(x/8+1,y/8+1);
cprintf("x");
break;
}
}
while (!kbhit()); // cand se apasa buton de la tastatura se paraseste programul
getch();
}


Observaie: Poziia mouse-lui n modul text este dat de ctre funcia getmouse tot n puncte
(ca i n modul grafic). Rezoluia ecranului n modul text obinuit co80 este 640x200. De aceea,
pentru a afla poziia mouse-lui n coordonate text, trebuie ca la poziia n puncte mprit la 8 s
se adauge 1. Astfel, obinem coordonatele text ale cursorului mouse-lui (X,Y) = (x/8+1,y/8+1).
Evident obinem c Xe{1, 2,,80} i Ye{1, 2,, 25}, pornind de la coordonatele n puncte
(x,y) returnate de funcia getmouse, unde x e {0, 8, 16, , 632} i ye{0, 8, 16, , 192}.

Scriei un program C n care se citesc coordonatele vrfurilor unui poligon. Translatai i
rotii poligonul pe ecran cu ajutorul mouse-lui.



ANEXA 2 - Urmrirea execuiei unui program. Rularea pas cu pas.


109
Pentru a vedea efectiv traseul de execuie i modul n care se i modific variabilele
valorile ntr-un program, putem rula pas cu pas. Acest lucru se face n Borland C/C++ cu ajutorul
butoanelor F7 sau F8, iar n Visual C++ cu F11, combinaii de taste care au ca efect rularea liniei
curente i trecerea la linia urmtoare de execuie.
Execuia programului pn se ajunge la o anumit linie se face apsnd pe linia
respectiv butonul F4 n Borland C/C++ i respectiv Ctrl+F10 n Visual C++.

n Borland C/C++ pentru a fi posibil urmrirea execuia unui program, n meniul
Options, la Debugger, trebuie selectat On n Source Debugging ! n lipsa acestei setri, dac se
ncearc execuia pas cu pas, se afieaz mesajul de atenionare WARNING: No debug info.
Run anyway?. Este bine ca la Display Swapping (n fereastra Source Debugging) s se selecteze
opiunea Always, altfel fiind posibil alterarea afirii mediului de programare. Reafiarea
mediului de programare se poate face cu Repaint desktop din meniul . Este bine de tiut c
informaiile legate de urmrirea execuiei sunt scrise n codul executabil al programului ceea ce
duce la o ncrcare inutil a memoriei cnd se lanseaz n execuie aplicaia. Aa c programul,
dup ce a fost depanat i este terminat, este indicat s fie compilat i link-editat cu debugger-ul
dezactivat.
Pentru ca execuia programului s se ntrerup cnd se ajunge pe o anumit linie (break),
se apas pe linia respectiv Ctrl+F8 n Borland C/C++ i F9 n Visual C++. Linia va fi marcat
(de obicei cu rou). Pentru a anula un break se apas tot Ctrl+F8, respectiv F9 pe linia
respectiv.
Execuia pas cu pas a unui program poate fi oprit apsnd Ctrl+F2 n Borland C/C++ i
F7 n Visual C++.
Dac se dorete continuarea execuiei programului fr Debbuger, se poate apsa
Ctrl+F9 n Borland C/C++ i F5 n Visual C++.
n orice moment, n Borland C/C++ de sub DOS rezultatele afiate pe ecran pot fi
vizualizate cu ajutorul combinaiei de taste Alt+F5.
n Borland C/C++ valorile pe care le iau anumite variabile sau expresii pe parcursul
execuiei programului pot fi urmrite n fereastra Watch, pe care o putem deschide din meniul
Window. Adugarea unei variabile sau a unei expresii n Watch se face apsnd Ctrl+F7, sau
Insert n fereastra Watch. Dac se apas Enter pe o expresie sau variabil din fereastra Watch,
aceasta poate fi modificat.
n Visual C++ valoarea pe care o are o variabil pe parcursul urmririi execuiei unui
program poate fi aflat mutnd cursorul pe acea variabil. n timpul urmririi execuiei
programului putem vedea rezultatul unei expresii apsnd Shift+F9, dup ce acea expresie este
introdus.










BIBLIOGRAFIE

1. A. Deaconu, Programare avansat n C i C++, Editura Univ. Transilvania, Braov, 2003.
110

2. J. Bates, T. Tompkins, Utilizare Visual C++ 6, Editura Teora, 2000.

3. Nabajyoti Barkakati, Borland C++ 4. Ghidul programatorului, Editura Teora, 1997.

4. B. Stroustrup, The C++ Programming Language, a doua ediie, Addison-Wesley Publishing
Company, Reading, MA, 1991..

5. T. Faison, Borland C++ 3.1 Object-Oriented Programming, ediia a doua, Sams Publishing,
Carmel, IN, 1992.

6. R. Lafore, Turbo C++ - Getting Started, Borland International Inc., 1990.

7. R. Lafore, Turbo C++ - User Guide, Borland International Inc., 1990.

8. R. Lafore, Turbo C++ - Programmers Guide, Borland International Inc., 1990.

9. O. Catrina, I. Cojocaru, Turbo C++, ed. Teora, 1993.

10. M. A. Ellis, B. Stroustrup, The Annotatted C++ Referense Manual, Addison-Wesley
Publishing Company, Reading, MA, 1990.

11. S. C. Dewhurst, K. T. Stark, Programming in C++, Prentice Hall, Englewood Cliffs, NJ,
1989.

12. E. Keith Gorlen, M. Sanford, P. S. Plexico, Data Abstraction and Object-Oriented-
Programming in C++, J. Wiley & Sons, Chichester, West Sussex, Anglia, 1990.

13. B. S. Lippman, C++ Primer, ediia a doua, Addison-Wesley, Reading, MA, 1991.

14. C. Spircu, I. Lopatan, Programarea Orientat spre Obiecte, ed. Teora.

15. M. Mullin, Object-Oriented Program Design with Examples n C++, Addison-Wesley,
Reading, MA, 1991.

16. I. Pohl, C++ for C Programmers, The Benjamin/Cummings Publishing Company. Redwood
City, CA, 1989.

17. T. Swan, Learning C++, Sams Publishing, Carmel, IN, 1992.

18. K. Weiskamp, B. Flaming, The Complete C++ Primer, Academic Press, Inc., San Diego,
CA, 1990.

19. J. D. Smith, Reusability & Software Construction: C & C++, John Wiley & Sons, Inc., New
York, 1990.

20. G. Booch, Object-Oriented Design with Applications, The Benjamin/Cummings Publishing
Company. Redwood City, CA, 1991.

21. B. Meyer, Object-Oriented Software Construction, Pretice Hall International (U.K.) Ltd.
Hertfordshire, Marea Britanie, 1988.
111

22. L. Pinson, R. S. Wiener, Applications of Object-Oriented Programming, Addison-Wesley
Publishing Company, Reading, MA, 1990.

23. J. Rambaugh, M. Blaha, W. Premerlani, F. Eddy, W. Lorensen, Object-Oriented Modelling
and Design, Prentice Hall, Englewood-Cliffs, NJ, 1991.

24. L. A. Winblad, S. D. Ewards, D. R. King, Object-Oriented Software, Addison-Wesley
Publishing Company, Reading, MA, 1990.

25. R. Wirfs-Brock, B. Wilkerson, L. Wiener, Designing Object-Oriented Software, Prentice
Hall, Englewood Cliffs, NJ, 1990.

26. D. Claude, Programmer en Turbo C++, Eyrolles, Paris, 1991.