Sunteți pe pagina 1din 158

Universitatea “Transilvania”

Facultatea de Matematică şi Informatică


Catedra de Informatică

Note de curs despre…

Programarea obiect orientată în


C++
(incluzând şi bazele programării
în C)

Dorin BoKu
2001-2002
În loc de introducere
După iniţierea în programare, cu sprijinul altor cursuri,
paralele cu programarea sau complementare în cel mai bun
înţeles al cuvântului, iată-ne bătând, cu înţelepciune
sporită, la poarta unei lumi în care vom vedea aceleaşi
concepte într-o mişcare care respectă rigorile unui joc nou,
mult mai interesant din punct de vedere al împătimiţilor în
ale programării. Avem de lămurit şi adâncit două teme de
meditaţie mari şi importante pentru formarea oricărui
informatician :

ÖParadigma programării obiect orientate


ÖUniversul C++ ale cărui origini vin dinspre C şi
dinspre începuturile dintotdeauna ale programării.

După experienţele cursurilor introductive în programare, în


prezentul curs voi deplasa accentul de la “cât mai mult
despre…” la “esenţialul despre…”, ceea ce va aduce
beneficii tuturor, sper.
Pentru titularul de curs este un prilej (nu tocmai comod) de
a scoate în evidenţă cunoştinţele şi abilităţile
indispensabile unei călătorii la capătul căreia, cei care nu
au abandonat, primesc certificatul simbolic de “Oameni
care au încercat limitele unui limbaj de cotitură pentru orice
programator”. Care este rezultatul unei astfel de încercări?
Depinde, ca întotdeauna, de curiozitatea şi complexul de
motive care îi însoţesc pe participanţii la călătorie.
Multe dintre tainele programării în C/C++ vor trebui aflate în
momentul în care, optând pentru meseria de specialist în
ingineria softului, veţi avea motive, timp şi condiţii să o
faceţi.
Studentul care citeşte acest suport de curs trebuie să
înţeleagă un lucru elementar: fără determinare clară nu se
poate face nimic cu temeinicie; dincolo de determinare se
află foarte multă muncă, înţelept drămuită între
numeroasele borne care aşteaptă să fie trecute, cu o notă
care să certifice un anumit coeficient de inteligenţă.

Autorul

2
I BAZELE C++. LIMBAJUL C

3
1 Privire de ansamblu asupra limbajului C
1.1 Despre originile limbajului C
Legenda spune că C a fost inventat şi implementat de Dennis Ritchie pe un
calculator DEC PDP-11, care utiliza sistemul de operare UNIX. Virtual, C este
rezultatul unui proces de dezvoltare complex, la care putem spune că a participat
întreaga comunitate preocupată de ridicarea performanţelor în munca de
programare. De fapt, strămoşi direcţi ai limbajului C pot fi considerate limbajele
BCPL (creat de Martin Richards) şi limbajul B (inventat de Ken Thompson). În
anii ’70, limbajul B a influenţat în cel mai înalt grad specificarea limbajului C. Mult
timp, standardul de facto pentru C a fost versiunea ce însoţea sistemul de
operare UNIX. Acest standard a fost descris pentru prima dată în cartea The C
Programming Language, scrisă de Brian Kernigan şi Dennis Ritchie, apărută în
1978 la editura Prentice Hall. Odată cu creşterea popularităţii calculatoarelor
personale, au fost create numeroase implementări de C, ceea ce a generat o
problemă nouă: compatibilitatea implementărilor. Pentru rezolvarea acestei
probleme, în vara anului 1983, a fost înfiinţat un comitet pentru crearea unui
standard ANSI (American National Standard Institute) care avea ca sarcină
specificarea definitivă a limbajului C. Standardul ANSI C a fost adoptat în
decembrie 1989, primele copii devenind disponibile la începutul lui 1990. La ora
actuală, toate compilatoarele C/C++ se aliniază la standardul ANSI C. Totodată
standardul ANSI C este o bază pentru propunerea de standard ANSI C++(Bazat
pe limbajul C, C++ adaugă extinderi care permit programarea obiect orientată).
Evident, vom reveni asupra topicii C++.

1.2 Locul limbajului C în lumea limbajelor de programare


Specialiştii consideră C un limbaj de nivel mediu. Această apreciere nu
presupune neapărat că C este mai puţin performant decât limbajele de nivel
înalt, precum BASIC sau PASCAL. De asemenea, nu trebuie să concluzionăm
că C are dificultăţile unui limbaj de asamblare. C este considerat un limbaj de
nivel mediu deoarece combină cele mai bune facilităţi ale unui limbaj de
nivel înalt cu posibilităţile de control şi flexibilitatea unui limbaj de
asamblare.
Astfel, ca limbaj de nivel mediu C permite lucrul cu biţi, octeţi şi adrese –
elemente de bază pentru funcţionarea calculatorului. Cu toate acestea, codul C
rămâne în foarte mare măsură portabil.

 Despre un sistem soft spunem că este portabil dacă în momentul în


care este scris pe o anumită maşină, sub un anumit mediu de operare,
poate fi uşor adaptat pe altă maşină, eventual cu alt sistem de operare.

Tot ca nişte trăsături specifice limbajului C semnalăm următoarele:


ÖChiar dacă C are cinci tipuri de date de bază, nu este un limbaj centrat
pe tipuri de date aşa cum este, de exemplu, Pascal. Prin această afirmaţie
sugerăm faptul că în C lucrul cu diferite instanţe ale tipurilor de date de bază

4
beneficiază de un cadru mult mai liberal (controalele compilatorului nu mai sunt
atât de severe ca în Pascal).
ÖSpre deosebire de un limbaj de nivel înalt, C nu face aproape nici un
control în timpul executării unui program. Responsabilitatea de a evita
producerea erorilor şi de a le dibui şi corecta, dacă le-a ocazionat, este a
programatorului.
ÖC permite lucrul direct cu biţi, octeţi cuvinte şi pointeri ceea ce îl face
potrivit pentru programare la nivel de sistem.

 Numim programare la nivel de sistem activitatea de scriere a codului


pentru anumite componente ale sistemului de operare sau care sunt
accesorii ale sistemelor de operare.

ÖC are doar 32 cuvinte-cheie (27 din standardul Kernigan & Ritchie şi


cinci adăugate de comitetul de standardizare ANSI C). Limbajele de nivel înalt
depăşesc cu mult oferta limbajului C. Faptul că are atât de puţine cuvinte cheie
ar trebui să fie benefic pentru procesul de învăţare a limbajului. Ceea ce se
învaţă greu în C este “democraţia” prea mare în comparaţie, de exemplu, cu
stilul autoritar în care compilatorul Pascal asistă scrierea de programe
corecte.

1.3 C este limbaj structurat


Deoarece nu oferă suport pentru implementarea autentică a conceptului de
structură – în – blocuri, C este numit, simplu, limbaj structurat. C nu este limbaj
<structurat în blocuri> deoarece nu permite definirea de funcţii în interiorul altor
funcţii, situaţie absolut normală şi cu efecte interesante asupra modularizării
codului Pascal, de exemplu.
Caracteristica esenţială a limbajelor structurate este compartimentarea
codului şi a datelor, prin care se înţelege capacitatea unui limbaj de a separa şi
ascunde de restul programului toate informaţiile şi instrucţiunile necesare
efectuării unei sarcini.
O modalitate de realizare a compartimentării este utilizarea de subrutine care
folosesc variabile locale. Utilizând variabile locale putem scrie modulele astfel
încât ceea ce se întâmplă în interiorul lor să nu aibă efecte în alte secţiuni ale
programului. C permite compartimentarea în acest spirit, utilizând potenţialul
noţiunii de funcţie.

 Utilizarea excesivă a variabilelor globale, în orice limbaj de programare


structurat, permite greşelilor să se strecoare în program, sub forma
efectelor secundare neprevăzute.

Evident, un limbaj de programare structurat descurajează sau interzice utilizarea


instrucţiunii goto.
Un alt mod de a structura şi de a compartimenta codul, în C, este utilizarea
blocurilor de cod. Un bloc de cod este un grup de instrucţiuni alăturate logic,
tratate ca un singur element. Blocurile de cod permit reprezentarea multor

5
algoritmi cu limpezime, eleganţă şi eficienţă. În C un bloc de cod poate fi creat
incluzând între acolade un grup de instrucţiuni.

Dacă în Pascal am avea:


.
if I<10
then
begin
gotoxy(12,I);
write(‘Valoarea curentă a lui I:’,I);
end;
.
atunci în mod echivalent, în C avem:
.
if (I<10)
{
gotoxy(12,I);
cout<<”Valoarea curentă a lui I:”<<I;
}
.
Blocul de cod este delimitat de acolade.
L-am evidenţiat folosind efectul de umbrire (shading) a textului.

1.4 Relaţia limbajului C cu programatorul


Specialiştii în limbaje de programare afirmă ceea ce, de fapt, s-a avut în vedere
la specificarea unor limbaje: acestea nu sunt pentru programatori. Limbaje
precum COBOL sau BASIC au fost specificate nu pentru a îndulci soarta
programatorilor sau pentru a mări siguranţa în exploatare a codului creat, ci
pentru a lărgi accesul celor mulţi la înţelegerea programelor sau chiar la scrierea
de programe pentru a rezolva probleme simple.
Din contră C (ca şi C++) a fost creat, influenţat şi testat de către adevăraţii
programatori. Rezultatul final este că C oferă programatorului exact ceea ce îşi
doreşte: restricţii puţine, structurare, blocuri de cod, funcţii de sine stătătoare şi
un set compact de cuvinte cheie. De asemenea faptul că poate fi utilizat în locul
limbajelor de asamblare îi sporeşte limbajului C popularitatea printre
programatori.

1.5 Componentele de bază ale unui program C


În Tabelul 1 prezentăm cuvintele-cheie, care, utilizate conform regulilor sintactice
specifice, permit scrierea de programe în C. Obişnuinţa de a scrie programe în
alte limbaje de programare poate fi de un real folos în C. Astfel, multe din
abilităţile cultivate ca autori de programe Pascal sunt transpuse, cu unele
modificări de sintaxă sau semantică şi în C. Diferite compilatoare de C pot
beneficia de extinderi sintactice care permit programelor să beneficieze de
avantajele unor medii de operare particulare.

6
Auto double Int struct
Break else Long switch
Case enum Register typedef
Char extern Return union
Const float Short unsigned
Continue for Signed void
Default goto Sizeof volatile
Do if Static while
Tabelul 1 Cele 32 cuvinte-cheie definite de standardul ANSI C

Din cele 32 cuvinte-cheie prezentate în Tabelul 1, cele prezentate cu caractere


bold au fost adăugate de comitetul ANSI C, celelalte corespunzând standardului
C original.

 Toate cuvintele-cheie în C se scriu cu litere mici . De asemenea, este


important să ştim că în C literele mari diferă de cele mici. Astfel că, în timp
ce for este cuvânt-cheie, FOR nu este cuvânt-cheie.

Toate programele C constau din una sau mai multe funcţii a căror
sintaxă şi semantică o vom prezenta în secţiunile următoare.

În sfârşit, componentele unui program C simplu se grupează în următoarele


secţiuni:

Directive preprocesor În această secţiune se includ


bibliotecile de care are nevoie
programul

Secţiunea de definire a tipurilor şi În această secţiune sunt definite


constantelor globale constante sau tipuri de date necesare
în program

Antet program principal

Secţiunea declarativă În această secţiune se fac toate


declaraţiile de variabile ale programului

Secţiunea executabilă În această secţiune se amplasează


versiunea cod C a algoritmului care
rezolvă problema

Un programator Pascal recunoaşte în acest şablon de organizare a codului unui


program C o parte din “filozofia” scrierii codului Pascal dar şi o serie de elemente
inedite care îl atenţionează asupra faptului că limbajul C este o lume care
propune o nouă abordare a unei probleme mai vechi :învăţarea calculatoarelor

7
să rezolve anumite probleme. Această nouă abordare o vom dezbate treptat în
secţiunile care vor urma.

8
2 Expresii în C
În această secţiune vom trece în revistă propunerile limbajului C în ceea ce
proveşte posibilitatea de a utiliza expresii. Toate limbajele de programare
acordă o atenţie specială noţiunii de expresie deoarece, în ultimă analiză, orice
transformare a datelor de intrare ale unui program este realizată prin intermediul
uneia sau a mai multor expresii. Aşa cum vom descoperi pe parcurs, C este un
limbaj mult mai flexibil decât alte limbaje în materie de definire şi utilizare a
expresiilor. Ceea ce este foarte limpede în acest moment este faptul că
expresiile sunt realizate cu ajutorul elementelor atomice numite date şi
operatori. Datele pot fi reprezentate cu ajutorul constantelor sau prin intermediul
variabilelor. Ajungem, evident, la problema tipurilor de date suportate de C.

2.1 Cinci tipuri de date de bază în C


În C există cinci tipuri de date de bază: caracter (char), întreg (int), virgulă
mobilă (float), virgulă mobilă dublă precizie (double) şi fără nici o valoare (void).
Toate celelalte tipuri de date din C se bazează pe cele cinci tipuri de bază.
Dimensiunea reprezentării şi domeniul valoric corespunzător pentru tipurile de
date de bază pot să difere, în funcţie de tipul procesorului şi de modul de
implementare a limbajului C. În toate cazurile, însă, un caracter se reprezintă
pe un octet. Chiar dacă de multe ori un întreg se reprezintă pe doi octeţi, nu se
poate conta pe această observaţie dacă dorim ca programele scrise să fie
portabile. De fapt, însuşi standardul ANSI C, stipulează doar domeniul de
cuprindere minimal al fiecărui tip de date, nu şi mărimea sa în octeţi.
Formatul exact al valorilor în virgulă mobilă depinde de modul lor de introducere.
Întregii corespund, în general, mărimii normale a unui cuvânt pe calculatorul
respectiv.
Valorile de tip char sunt, în general, utilizate pentru a memora valori definite de
setul de caractere ASCII. Valorile care ies din acest domeniu sunt tratate în mod
diferit de compilatoare.
Domeniul valoric pentru float şi double va depinde de metoda folosită pentru a
reprezenta numere în virgulă mobilă. Standardul ANSI C indică domeniul valoric
pentru virgulă mobilă de la 1E-37 pâna la 1E+37. Evident, vor exista deosebiri
atât în ceea ce priveşte ordinul de mărime cât şi în ceea ce priveşte precizia între
cele două specii de virgulă mobilă. Numărul minim de cifre din punct de vedere al
preciziei este precizat în Tabelul 2.

Tipul void declară explicit că o funcţie nu returnează nici o valoare sau


crează pointeri generici.

9
Tip de dată Dimensiune reprezenatre Domeniul valoric minimal
în biţi
char 8 -127..127
unsigned char 8 0..255
signed char 8 -127..127
int 16 -32767..32767
unsigned int 16 0..65535
signed int 16 Similar cu int
short int 16 Similar int
unsigned short int 16 0..65535
signed short int 16 Similar cu short int
long int 32 -2.147.483.647..2.147.483.647
signed long int 32 Similar cu long int
unsigned long int 32 0..4.294.967.295
float 32 6 zecimale exacte
double 64 10 zecimale exacte
long double 80 10 zecimale exacte

Tabelul 2 Tipurile de date definite prin standardul ANSI C

2.2 Modificarea tipurilor de bază


Exceptând tipul void, tipurile de bază pot fi precedate de diverşi specificatori de
conversie. Un specificator de conversie se utilizează pentru a modifica tipul de
bază în ideea de adaptare mai precisă la diferite situaţii. După cum rezultă şi din
Tabelul 2, lista specificatorilor de conversie este:
signed
unsigned
long
short
În mod evident, diferenţa între întregii cu semn şi întregii fără semn constă în
modul în care se interpretează bitul de pe poziţia cea mai semnificativă. Dacă, de
exemplu, indicaţi un întreg cu semn, compilatorul generează cod care presupune
că bitul cel mai semnificativ va fi utilizat ca bit de semn (0 dacă numărul este
pozitiv, 1 dacă numărul este negativ). Să mai menţionăm faptul că, în general,
pentru reprezentarea întregilor negativi se foloseşte codul complementar
(complementul faţă de 2).
Detalii referitoare la caracteristicile codului complementar se pot obţine
consultând lucrări de specialitate în care se dezbate problema codificării datelor
numerice în sistemele de calcul. În acest sens se poate consulta lucrarea [4]
care face o introducere accesibilă în problematica mai sus menţionată.

2.3 Nume de identificatori în C


În C/C++ numele variabilelor, funcţiilor, etichetelor şi ale altor diverse obiecte
definite de către utilizator sunt numite identificatori. Aceşti identificatori pot să
aibă unul sau mai multe caractere. Primul caracter trebuie să fie obligatoriu o

10
literă sau o liniuţă de subliniere; următoarele pot fi litere, cifre sau liniuţa de
subliniere.
Exemple de identificatori:

Corecţi Incorecţi
număr_de pagini 1_valoare_returnată
_limita1 ok!
ok_ switch..conversie

Standardul ANSI C stipulează că identificatorii pot avea orice lungime. Totuşi,


nu toate caracterele sunt obligatoriu semnificative. Dacă identificatorul este
implicat într-un proces de editare de legături externe, vor conta cel mult şase
caractere. Aceşti identificatori, denumiţi nume externe, includ numele funcţiilor şi
ale variabilelor globale care aparţin mai multor fişiere. Dacă fişierul nu este
utilizat într-un proces de editare de legaturi externe, atunci vor fi semnificative cel
mult 31 de caractere. Acest tip de identificator este denumit nume intern şi
include, de exemplu, nume de variabile locale. De remarcat faptul ca în C++ nu
există limite ale lungimii unui identificator şi toate caracterele sunt semnificative.
Această diferenţă începe să conteze în momentul în care doriţi să convertiţi un
program din C în C++.
Pare evident, dar facem precizarea că un identificator C nu poate fi identic cu
un cuvânt-cheie şi nu trebuie să aibă acelaşi nume ca o funcţie din
biblioteca C sau C++.

2.4 Variabile C
Aşa cum s-a aflat şi în alte împrejurări, o variabilă este numele unei locaţii de
memorie utilizată pentru a păstra o valoare care poate fi modificată de
program.
Înainte de a fi utilizate în program, variabilele trebuie declarate. Declararea unei
variabile în C are forma:

<Tip> <Listă_de_variabile>;

<Tip> trebuie să fie un tip de dată valid (predefinit sau definit de utilizator)
precedat, eventual, de un specificator de conversie.
<Listă_de_variabile> poate consta dintr-un nume de identificator sau mai multe
nume de identificatori separate prin virgulă.
Exemple de declaraţii de variabile:

int nr_pag;
char opt,ch;
unsigned i, j;

Variabilele se pot declara în trei moduri: în interiorul funcţiilor, în cadrul definiţiei


parametrilor funcţiei şi în afara oricărei funcţii. Prin urmare, este vorba despre
variabile locale, parametri formali şi variabile globale.

11
2.5 Variabile locale în C
Variabilele declarate în interiorul unei funcţii sunt numite variabile locale. O
parte din literatura C/C++ numeşte aceste variabile automatice. Ţinând cont de
asemănarea evidentă cu variabilele locale din Pascal, de exemplu, vom folosi
termenul de variabilă locală.
Afirmaţia cea mai importantă referitor la variabilele locale este următoarea:

“Variabilele locale sunt accesibile doar instrucţiunilor care sunt în interiorul


blocului în care sunt declarate variabilele”.

Alt amănunt important şi specific limbajului C este faptul că:

“Variabilele locale există cât timp se execută blocul de cod în care sunt
declarate”.

Altfel spus, o variabilă locală este creată la începerea execuţiei blocului său şi
este distrusă la încheierea execuţiei acestui bloc.
Blocul de cod cel mai uzual în care se declară variabile este funcţia.
Deoarece noţiunea de funcţie este centrală pentru fizionomia unui program C,
prezentăm, în continuare, o viziune asupra unui program C care include şi
noţiunea de funcţie.

Directive preprocesor
Declaraţii globale
Declaraţie funcţie_1
:
Declaraţie funcţie_n
int main()
{
Declaraţii de date ale programului principal
Instrucţiuni program principal
Orice program C conţine obligatoriu o funcţie al cărei nume este main
}
Implementare funcţie_1
:
Implementare funcţie_n

Totodată să precizăm faptul că sintaxa de declarare a unei funcţii (prototip în


C++), conform standardului ANSI C este :

<Tip returnat> <Nume funcţie> (<Lista de parametri formali>)


Toată această construcţie sintactică se mai numeşte şi <Antet funcţie>.
În C clasic declararea unei funcţii permitea doar specificarea tipului returnat,
problema eventualilor parametri aşteptaţi de funcţie având o rezolvare aparte, pe

12
care nu mai insistăm deoarece standardul ANSI C ca şi standardul ANSI C++
descurajează, respectiv interzice utilizarea căii clasice.
În C++ declararea unei funcţii se face apelând la prototipuri.
În sfârşit, implementarea unei funcţii înseamnă o construcţie sintactică de tipul:

<Antet funcţie>
{
<Declaratii de date>
<Instructiuni executabile>
}

Referindu-ne la standardul ANSI C/ANSI C++ putem spune că:


<Nume funcţie> este un identificator valid.
<Tip returnat> specifică tipul de dată asociat cu numele funcţiei.
<Lista de parametri formali> constă din o serie de perechi de denumiri, separate
între ele prin virgulă, unde prima denumire specifică un tip de dată iar cea de-a
doua este numele parametrului formal.
Parametrii formali care apar în antetul unei funcţii, în principiu, trebuie să
corespundă ca tip cu parametrii actuali care apar la apelul unei funcţii.Când un
parametru actual trimis unei funcţii nu se potriveşte exact, ca tip, cu parametrul
formal corespunzător, compilatorul încearcă o conversie convenabilă. De
exemplu, o dată de tip int trimisă unei funcţii care aşteaptă o dată de tip float
este convertită la tipul float. Prezentăm, în continuare, câteva exemple.

void f1(void)
{
int x;
x=12;
}
void f2(void)
{
int x;
x=-128;
}

Variabila x este declarată atât în f1 cât şi în f2. Variabila x din f1 nu are nici o
influenţă asupra variabilei x din f2 deoarece fiecare variabilă este cunoscută
doar în blocul de cod gazdă. Din obişnuinţă şi din respect faţă de tradiţie,
majoritatea programatorilor declară variabilele folosite de o funcţie imediat după
acolada deschisă a funcţiei şi înainte de orice instrucţiune executabilă. De fapt,
se pot declara variabile în orice bloc de cod, obţinându-se chiar efecte benefice
pentru calitatea codului rezultat. Astfel putem avea:

void fexemplu(void)
{
int i;
printf(”Introduceti un numar:”);

13
scanf(“%d”,&i);
if (i>0)
{
char nume [40];
printf(”Numele Dvs.:”);
gets(nume);
}
}

Din exemplul de mai sus reţinem doar faptul că, în situaţia în care numărul
preluat de la tastatură (cu ajutorul rutinei de uz general scanf ) este mai mare
decât zero, atunci devine activ blocul de cod în care se declară variabila nume.
Această variabilă este distrusă la ieşirea din acest bloc de cod.
Prin urmare, un avantaj potenţial: variabilei nume i se alocă memorie numai
dacă se activează blocul de cod, ceea ce înseamnă economie de memorie.
Este absolut evident faptul că se declară într-un bloc de cod una sau mai multe
variabile strict necesare în acest bloc de cod; în acest mod prevenim, într-o
oarecare măsură, apariţia unor efecte secundare. Mai semnalăm o diferenţă
importantă între modul de declarare a variabilelor locale în C faţă de C++.
În C trebuie declarate toate variabilele locale la începutul blocului în care
intenţionăm să le definim, înainte de orice instrucţiune executabilă. În C++ nu
mai există această restricţie. Astfel că secvenţa de cod C:

void f(void)
{
int i;
i=10;
int j;
j=20;
}

este considerată greşită de un compilator C şi perfect acceptabilă de către un


compilator C++.
Deoarece variabilele locale sunt create şi distruse la fiecare intrare, respectiv
ieşire din blocul în care au fost declarate, conţinutul lor se pierde odată cu
părăsirea blocului. Acest lucru este important să se ştie când apelăm o funcţie.
La apelarea ei sunt create variabilele locale iar la încheierea ei acestea sunt
distruse, deci variabilele locale nu păstrează valorile lor între apelări. Putem
determina compilatorul să păstreze aceste valori utilizând specificatorul de
stocare static asupra căruia vom reveni.

2.6 Parametrii formali


Dacă o funcţie urmează să folosească argumente, ea trebuie să declare
variabilele pe care le acceptă ca valori ale argumentelor. Aceste variabile sunt
denumite parametri formali ai funcţiei. Variabilele în cauză se comportă ca
oricare altă variabilă locală din acea funcţie.

14
2.7 Variabile globale
Variabilele globale, spre deosebire de cele locale, sunt cunoscute în tot
programul şi pot fi utilizate de către orice zonă a codului. Totodată, ele îşi
păstrază valoarea tot timpul execuţiei programului. Variabilele globale se
crează prin declarare în afara oricărei funcţii. Orice expresie are acces la ele,
indiferent de tipul blocului de cod în care se află expresia. De semnalat faptul că
o variabilă globală este vizibilă în orice bloc de cod al programului cât timp în
respectivul bloc de cod nu a fost declarată o variabilă cu acelaşi nume, de
acelaşi tip. În caz afirmativ, în blocul de cod este prioritară referirea la variabila
locală.
Stocarea variabilelor globale este făcută de compilator într-o zonă de memorie,
special alocată. Variabilele globale sunt utile atunci când mai multe funcţii ale
aceluiaşi program folosesc aceleaşi date. Utilizarea excesivă a variabilelor
globale ar trebui evitată deoarece:
ÖOcupă memoria pe tot timpul execuţiei programului, nu doar când sunt
necesare;
ÖDacă o funcţie utilizează o variabilă globală în locul uneia locale, atunci
funcţia îşi pierde din generalitate, bazându-se pe ceva exterior logicii ei;
ÖVariabile globale multe înseamnă efecte secundare necunoscute şi
nedorite.

2.8 Modelatori de acces


În C există doi modelatori de acces (const şi volatile) care introduc restricţii
suplimentare asupra modului de apelare sau modificare a variabilelor. Aceşti
modelatori trebuie să preceadă specificatorul de tip şi numele tipului de dată la
care se referă.
ÖVariabilele de tip const nu pot fi modificate de programele în care sunt
declarate. Acestor variabile li se permite, totuşi, să primească o valoare iniţială.
Compilatorul poate să plaseze variabilele de acest tip în ROM (Read Only
Memory). Evident, aceste variabilele de tip const pot fi utilizate în construcţia
unor expresii. Tehnic vorbind, modelatorul const poate fi utilizat şi pentru a
proteja obiectele trimise ca argumente unei funcţii, de modificări în interiorul
acelei funcţii. Multe funcţii din biblioteca standard a limbajului utilizează const în
declaraţiile lor de parametri. Ca un exemplu, funcţia strlen() are următorul
prototip:

size_t strlen( const char * s);

ÖModelatorul volatile informează compilatorul că valoarea unei variabile


poate fi modificată pe căi nedeclarate explicit de program. Aceasta înseamnă că
este posibil următorul scenariu: ”Adresa unei variabile este transmisă unei rutine
rezidente în memoria sistemului, această rutină fiind abilitată să modifice
valoarea variabilei”. Evident, pentru programatori acesta este un mecanism
extrem de practic şi interesant. Modelatorul volatile poate fi combinat cu
modelatorul const făcându-se plauzibil scenariul: “Valoarea unei variabile poate

15
fi modificată de condiţii externe, fiind protejată de modificări accidentale în
programul în care este declarată”.

2.9 Specificatori de clase de stocare


Limbajul C admite patru specificatori de clase de stocare:
extern
static
register
auto
Aceşti specificatori indică compilatorului modul în care trebuie să stocheze
variabilele la care se referă. Cadrul sintactic general de utilizare a specificatorilor
de clase de stocare este:

<Specificator_de_stocare> <Tip> <Nume_variabilă>;

extern
Deoarece C/C++ permit secţiunilor separate ale unui program să fie compilate
independent şi să li se editeze legăturile împreună, trebuie să existe o modalitate
de a comunica tuturor fişierelor variabilele globale necesare programului.
Scenariul căruia trebuie să îi facem faţă este următorul: “Cum putem informa
toate fişierele care compun un program C despre variabilele globale utilizate?”.
Soluţia este următoarea : Se declară variabilele globale într-un fişier, care este
de preferat să conţină şi funcţia principală a programului, aceleaşi variabile
declarându-se şi în toate fişierele care folosesc variabilele globale, însoţite de
specificatorul extern.

static
Variabilele de tip static sunt variabile permanente în interiorul funcţiei sau
fişierului în care se găsesc. Spre deosebire de variabilele globale, ele nu sunt
cunoscute în afara funcţiei sau fişierului, dar îşi păstrează valoarea între două
apelări. Această caracteristică este folositoare la scrierea de funcţii generice şi
de bibliotecă , utilizabile de alte programe. Specificatorul static are efecte diferite
asupra variabilelor locale şi globale.

Variabile locale statice


Aplicat unei variabile locale, specificatorul static informează compilatorul de
dorinţa de a i se asocia variabilei un loc de stocare permanentă, similar celui
asociat unei variabile globale. Diferenţa esenţială între variabilele locale statice
şi o variabilă globală se referă la faptul că variabila locală statică rămâne
cunoscută doar blocului în care a fost declarată. Altfel spus, o variabilă
locală statică este o variabilă care îşi păstrează valoarea între apelurile funcţiei în
care a fost declarată.

Variabile globale statice

16
Aplicând specificatorul static unei variabile globale, cerem compilatorului să
creeze o variabilă globală care este cunoscută doar în fişierul în care a fost
declarată. Aceasta înseamnă că, deşi variabila este globală, rutine din alte
fişiere nu au acces la ea şi nu îi pot modifica conţinutul.

register
Specificatorul de stocare register se aplică, prin tradiţie, doar variabilelor de tip
int şi char. Totuşi standardul ANSI C îi dă definiţia astfel încât poate fi folosit
pentru orice tip de variabilă. Iniţial, register cerea compilatorului să păstreze
valoarea unei variabile într-un registru din CPU, nu în memoria RAM, unde
variabilele sunt stocate, de regulă. Scopul unei astfel de cereri: spor de viteză în
timpul operaţiilor asupra variabilei. Actualmente, definiţia specificatorului register
a fost mult extinsă, acesta putând fi aplicat oricărui tip de variabilă. Standardul
ANSI C stipulează, simplu, că register este o indicaţie dată compilatorului că
obiectul vizat va fi utilizat preferenţial din punct de vedere al vitezei.
Specificatorul se poate aplica doar variabilelor locale şi parametrilor formali. Prin
urmare, nu sunt permise variabile globale de tip register.

auto
Specificatorul auto este, practic, nefolosit, fiind reclamat doar de motive de
compatibilitate între codul C şi codul C++.

2.10 Iniţializarea variabilelor


Forma generală a iniţializării unei variabile respectă sintaxa:

<Tip> <Nume_variabilă> = <Constanta>;

Exemple:

char ch=’A’;
int media=0;
float bilant=0;

Variabilele globale şi cele statice locale sunt iniţializate doar la începutul


execuţiei programului. Variabilele locale (cu excepţia celor de tip static, sunt
iniţializate de fiecare dată când este întâlnit blocul în care sunt declarate.

Variabilele locale care nu sunt iniţializate au valori necunoscute înainte de


prima atribuire. Variabilele globale şi locale de tip static neiniţializate sunt
iniţializate din oficiu cu zero.

Evident, trebuie să discutăm în continuare despre problema reprezentării


constantelor în programele C.

Constante C

17
Constantele se referă la valori fixe pe care programul nu poate să le modifice.
Constantele pot fi de oricare din tipurile fundamentale de date. Modul în care se
reprezintă în program fiecare constantă depinde de tipul său.
Constantele de tip caracter sunt incluse între ghilimele simple. ‘A’ şi ‘%’ sunt
exemple de constante de tip caracter. C defineşte şi caractere multioctet, în
mediile care nu utilizează limba engleză. O situaţie asemănătoare se întâlneşte
şi în Delphi.
Constantele de tip întreg sunt specificate ca numere fără parte fracţionară. De
exemplu, 3 şi -15 sunt exemple de constante întregi.
Constantele în virgulă mobilă cer punctul zecimal urmat de partea zecimală a
numărului. 0.75 este un exemplu de constantă care apelează la virgula mobilă.
Limbajul C permite şi notaţia ştiinţifică, cunoscută şi în alte limbaje.
De asemenea, C stabileşte pentru o constantă numerică cel mai scurt tip de date
compatibil care o poate păstra. Astfel că, -10 este implicit un int, 60.000 este
unsigned int iar 120.000 este un long int. Tipul constantei numerice poate fi
specificat şi explicit ca în exemplele din Tabelul 3.

Tip de data Exemplu de constantă


int 1, 234, 2000, -243
long int 100000, 23000L, -45L
short int 9, -2000
unsigned int 10000U, 900U, 45000
float 120.15F, 3.75e-5F
double 120.15, 22122122, -0.9876432
long double 1000.25L
Tabelul 3 Exemple de constante numerice

După cum se vede, constantele în virgulă mobilă sunt asimilate implicit tipului
double.

Constante hexazecimale şi octale


Deoarece există numeroase situaţii în care avem nevoie de referiri la sistemele
de numeraţie octal şi hexazecimal, C permite utilizarea constantelor octale şi
hexazecimale astfel:

ÖO constantă octală începe cu 0 (zero).


Exemplu:

int octala=017; /*In zecimal 15 */

ÖO constantă hexazecimală începe cu 0x.


Exemplu:

int hexa =0x100; /*In zecimal 256*/

18
Constante de tip şir
C admite, cum era şi firesc şi constanta de tip şir de caractere. O constantă şir
este o succesiune de caractere delimitată de ghilimele. A nu se confunda şirurile
de caractere cu caracterele. ‘a’ şi “a” sunt constante de tipuri diferite( disticţie
care, în Pascal, nu este operată sintactic, vorbind.

Constante de tip backslash caracter


Încadrarea constantelor de tip caracter între apostrofuri funcţionează pentru
majoritatea caracterelor afişabile. Însă, altele, puţine la număr, sunt imposibil de
introdus de la tastatură (constanta BEL, de exemplu). În acest scop C introduce
constante speciale, de tip backslash caracter. C admite mai multe coduri
backslash, cum rezultă şi din Tabelul 4. Pentru a se asigura portabilitatea
programelor sunt indicate codurile backslash în locul codurilor lor ASCII.

Codul Semnificaţia
\b Backspace
\f form feed (=Salt la pagină nouă)
\n CR+LF
\r CR
\t tab orizontal
\’’ Ghilimele
\’ Apostrof
\0 Null
\\ Backslash
\v tabulare verticală
\a Alertă
\N constantă în octal; N este constanta
\xN constantă în hexazecimal; N este constanta
Tabelul 4 Coduri backslash în C
2.11 Operatori în C
C dispune de foarte mulţi operatori. De fapt, C acordă acestora o importanţă mult
mai mare în comparaţie cu alte limbaje. C defineşte, în esenţă, patru clase de
operatori: aritmetici, relaţionali, logici şi de acţiune la nivel de bit. Pentru
anumite sarcini, C are operatori suplimentari.

Operatorul de atribuire
Operatorul de atribuire poate fi folosit, în C, în cadrul oricărei expresii valide,
lucru care nu este permis în majoritatea limbajelor de programare (inclusiv
Pascal), care tratează operatorul de atribuire ca pe un caz de instrucţiune
specială. Sintaxa de aplicare a operatorului de atribuire în C este:

<Nume_variabilă> = <Expresie>;

unde <Expresie> poate fi o constantă sau o construcţie legală de


complexitatea cerută în context. Atenţie, se foloseşte “=” în loc de “:=”.

19
Membrul stâng al atribuirii trebuie să fie o variabilă sau un pointer, nu o funcţie
sau o constantă.

Conversii de tip la atribuire


Când variabilele de un anumit tip sunt amestecate cu variabile de alt tip, au loc
conversii de tip. Într-o instrucţiune de atribuire, regula de conversie este simplă:
valoarea din membrul drept (al expresiei) al instrucţiunii de atribuire este
convertită la tipul din membrul stâng. Pentru o mai bună înţelegere a
“fenomenului”, fie exemplul de mai jos.

int nri;
char car;
float nrr;

void functie(void)
{
car=nri; /* Linia 1 */
nri=nrr; /* Linia 2 */
nrr=car; /* Linia 3 */
nrr=nri; /* Linia 4 */
}

În Linia 1, în variabila car ajung primii 8 biţi cei mai puţin semnificativi ai
variabilei nri. Dacă nri este cuprins între 0 şi 255, car şi nri vor avea valori
identice ş.a.m.d.. Presupunând că sistemul de calcul are cuvântul de 16 biţi,
Tabelul 5 prezintă posibilele pierderi de informaţie care pot să apară la
conversia de tip în atribuiri.

Tipul destinaţiei Tipul expresiei Posibile pierderi de informaţie


signed char char Dacă valoarea este > 127, destinaţia
este negativă.
char short int Cei mai semnificativi opt biţi
char int Cei mai semnificativi opt biţi
char long int Cei mai semnificativi 24 biţi
int long int Cei mai semnificativi 16 biţi
int float Partea zecimală şi, posibil, mai mult
float double Precizie, rezultat, rotunjit
double long double Precizie, rezultat, rotunjit
Tabelul 5 Tipuri de conversie frecvent utilizate

Atribuiri multiple
C permite atribuirea aceleeaşi valori mai multor variabile prin utilizarea atribuirii
multiple într-o singură instrucţiune de atribuire. De exemplu instrucţiunea:

x = y = z = 0;

20
permite setarea la 0 a variabilelor x,y,z. Programatorii profesionişti folosesc
masiv atribuirea multiplă pentru a obţine cod performant.
Să mai adăugăm o observaţie interesantă pentru un programator profesionist. O
atribuire de tipul:
<Variabilă>=<Variabilă>+<Expresie> (1)
este echivalentă semantic cu sintaxa:
<Variabilă>+=<Expresie> (2)
Deşi s-ar putea să vină “peste mână” unor programatori, aceştia trebuie să ştie
că pentru compilator sintaxa (2) este de preferat sintaxei (1) din punct de vedere
al calităţii codului generat.
Aşadar,
x=x+1
este totuna cu:
x+=1.

Operatori aritmetici
Tabelul 6 prezintă operatorii aritmetici din C.

Operator Acţiune
- Scădere, de asemenea şi minus unar
+ Adunare
* Înmulţire
/ Împărţire
% Modul
-- Decrementare
++ Incrementare
Tabelul 6 Operatori aritmetici în C

Dintre operatorii prezentaţi în Tabelul 6, operatorii ++ şi -- sunt specifici limbajului


C. Operatorul ++ adună 1 la variabila asociată. Operatorul -- scade 1 din
variabila asociată. Astfel că:
x=x+1;
este sinonim cu:
++x;
iar
x=x-1;
este sinonim cu:
x--;
Ambii operatori pot să fie plasaţi atât înainte cât şi după variabila operată. Prin
urmare:
x=x+1;
poate fi scris:
++x ;
sau
x++;

21
Există, totuşi, o diferenţă între forma cu prefix şi forma cu sufix, dacă operatorii
sunt utilizaţi într-o expresie. Atunci când operatorii preced variabila, C
efectuează operaţiile corespunzătoare lor, înainte de a evalua expresia.
Atunci când operatorii succed variabila, C efectuiază operaţiile corespunzătoare
lor, după evaluarea expresiei. Majoritatea compilatoarelor C/C++ produc rapid
un cod obiect eficient pentru operaţiile de incrementare şi decrementare (cod
mai bun decât cel obţinut prin utilizarea atribuirii clasice echivalente). Pe acest
considerent, recomandarea de a utiliza aceşti operatori de câte ori este posibil
este firească. În sfârşit, ordinea de precedenţă a operatorilor aritmetici este:

De ordinul cel mai înalt ++, --


- (minus unar)
*, /, %
De ordinul cel mai coborât +, -

Operatori relaţionali şi logici


Deoarece operatorii relaţionali şi logici lucrează de multe ori împreună, îi
prezentăm împreună. Semnificaţia celor două tipuri de operatori este cunoscută
de la alte limbaje. Operatorii relaţionali permit “compararea” (=punerea în relaţie)
a operanzilor. Rezultatele comparării pot fi combinate după rigorile algebrei
Boole cu ajutorul operatorilor logici.
Atât operatorii relaţionali cât şi cei logici au o precedenţă mai scăzută decât
operatorii aritmetici. Deci, o expresie precum 8 > 1+5 este evaluată ca şi cum ar
fi fost scrisă 8 > (1+5). În Tabelul 7 prezentăm opertorii relationali şi logici folosiţi
în C/C++.

Operatori relaţionali
Operator Acţiune
> Mai mare decât
>= Mai mare sau egal
< Mai mic decât
<= Mai mic sau egal
== Egal
!= Diferit
Operatori logici
Operator Acţiune
&& AND (ŞI)
|| OR (SAU)
! NOT (NEGAT)
Tabelul 7 Operatorii relaţionali şi logici în C.

Tabelul 8 prezintă priorităţile în relaţia dintre operatorii relaţionali şi logici.

22
De ordinul cel mai înalt !
>, >=, <, <=
==, !=
&&
De ordinul cel mai coborât ||
Tabelul 8 Relaţia de precedenţă între operatorii relaţionali şi logici
Evident, parantezele pot fi utilizate pentru a modifica ordinea firească de
evaluare a unei expresii relaţionale şi/sau logice.

Operatori de acţiune pe biţi


Spre deosebire de multe alte limbaje, C admite un complement deplin cu
operatorii de acţiune pe biţi. Deoarece C a fost proiectat să ia locul limbajului de
asamblare pentru majoritatea sarcinilor, el trebuie să fie capabil să asigure multe
operaţii care pot fi executate în asamblor, inclusiv asupra biţilor. Operaţiile
asupra biţilor se referă la testare, iniţializare sau deplasare a biţilor existenţi într-
un octet sau într-un cuvânt care corespund tipurilor de date char şi int şi
variantelor acestora din standardul C. Operatorii de acţiune asupra biţilor nu pot
fi utilizaţi asupra tipurilor float, double, long double,void. În Tabelul 9
prezentăm operatorii C pentru biţi.

Operator Acţiune
& AND
| OR
^ OR exclusiv (XOR)
~ Complement faţă de 1 (NOT)
>> Deplasare la dreapta
<< Deplasare la stânga
Tabelul 9 Operatorii de acţiune pentru biţi

Înţelegerea transformărilor efective care au loc la utilizarea operatorilor AND, OR


şi XOR este legată de analiza în spiritul legilor logicii Boole a unei expresii de
tipul:

<Operand_1> <Operator><Operand_2>

Acţiunea operatorului este aplicată bit cu bit operanzilor, rezultând o configuraţie


de biţi asupra cărora se pot continua alte prelucrări.

Operatorii de deplasare a biţilor >> şi << deplasează toţi biţii dintr-o variabilă la
dreapta sau la stânga. Sintaxa asociată este:

<Variabilă> >> <Număr_de_poziţii_de_shiftat>


sau
<Variabilă> << <Număr_de_poziţii_de_shiftat>

23
Sensul operaţiei de shiftare este cel precizat la un curs de Bazele logice ale
sistemelor de calcul.

Precizări referitoare la operatorii suplimentari pot fi găsite în [1].

Operatorul ?
C propune un operator unar foarte puternic şi util care poate înlocui anumite
instrucţiuni de forma “dacă – atunci – altfel”.

Operatorul ? apare în expresii având forma generală:

<Expresie_1> ? <Expreasie_2> : < Expresie_3>


unde:
-<Expresie_1>, <Expresie_2>, <Expresie_3> sunt expresii, cu precizarea
că <Expresie_1> este o expresie condiţională;
-semantica construcţiei sintactice de mai sus este următoarea:

z Se evaluiază <Expresie_1>;
z Dacă <Expresie_1> este adevărată se evaluiază <Expresie_2> şi
rezultatul evaluării ei i se atribuie expresiei globale;
z Dacă <Expresie_1> este falsă. atunci se evaluiază <Expresie_3> şi
rezultatul evaluiării acesteia este atribuit expresiei globale.
Această semantică este evidenţiată şi de exemplul de mai jos de utilizare
a operatorului ?.

X=24;
X=X<24 ? 1:X+1;

Acest cod este echivalent cu codul:

X=24;
if (X<24) X=1
else X=X+1;

Operatorii & şi *
Un pointer este adresa din memorie a unei variabile. O variabilă de tip pointer
este declarată explicit pentru a reţine un pointer către un obiect de un tip
specificat. Cunoaşterea adresei unei variabile poate fi de mare utilitate în
anumite situaţii. În C pointerii au trei funcţii principale.

z asigură o cale rapidă de acces la elementele unei matrici;


z permit funcţiilor C să modifice parametrii de apelare;
z permit realizarea structurilor dinamice de date.
Pentru simplificarea lucrului cu pointeri au fost introduşi operatorii & şi *.
Operatorul & este un operator unar care permite recuperarea adresei din
memorie a unui element C precum şi declararea de variabile sinonime.

24
O atribuire de tipul :

adr=&Numar_Elemente;

este utilă pentru a obţine în variabila adr adresa din memorie a variabilei
Numar_Elemente. Această adresă este adresa locaţiei din memorie începând de
la care se păstrează conţinutul variabilei Numar_Elemente.
Al doilea operator pentru pointeri este * , oarecum complementar operatorului
&. Şi operatorul * este unar, returnând valoarea din variabila localizată la
adresa specificată. Astfel , după execuţia secvenţei:

adr=&Numar_Elemente;
nr=*adr ;

variabila nr va conţine aceeaşi valoare cu variabila Numar_Elemente,


presupunând că cele două variabile sunt compatibile ca tip.
Evident, există, din punct de vedere al utilizatorului, primejdia de a utiliza confuz
aceşti doi operatori, din moment ce * mai înseamnă şi “înmulţire” iar & mai
înseamnă şi “şi logic” la nivel de biţi.
Variabilele care păstrează pointeri trebuie declarate ca atare. Sintaxa de
declarare a unei variabile pointer este:

<Tip de bază> * <Nume_Variabila>;

Exemplu

#include <stdio.h>
void main(void)
{
int destinatar,sursa;
int *m;
sursa=10;
m=&sursa;
destinatar=*m;
printf (“%d”, destinatar);
}

Operatorii & şi * sunt utilizaţi pentru a introduce valoarea 10 în variabila


destinatar .

Operatorul sizeof (cu acţiune în timpul compilării)

25
sizeof este un operator unar utilizat în timpul compilării care returnează lungimea
în octeţi a variabilei sau a specificatorului de tip dintre parantezele asociate de
sintaxă operatorului. Sintaxa de utilizare a operatorului:

sizeof ( <Nume_Variabila>)

sau

sizeof (<Specificator_ de_ tip>)

De remarcat faptul că în cazul unei variabile este permisă şi sintaxa:

sizeof <Nume_Variabila>

Acest operator este util în procesul de realizare a aplicaţiilor portabile.

Operatorii . (punct) şi -> (săgeată)


Sunt utilizaţi pentru a realiza referirea la elementele individuale ale structurilor şi
uniunilor din C. Aşa cum se va vedea, structurile şi uniunile sunt tipuri de date
agregate la care se poate avea acces sub un singur nume.
Operatorul punct este utilizat atunci când se lucrează cu structuri sau uniuni
efective. Operatorul săgeată este folosit împreună cu un pointer la o structură
sau la o uniune. Astfel, anticipând puţin, dacă avem codul:

struct tangajat
{
char nume[80];
int varsta;
float salariu;
} angajat;
struct tangajat *pangajat=&angajat;

are sens următorul cod:

angajat . salariu:=1100000;

Această atribuire este echivalentă cu atribuirea:

pangajat->salariu=1100000;

De remarcat faptul că în C există multe alte facilităţi puse la dispoziţia


programatorilor, precum: conversia automată în expresii, forţarea tipurilor
expresiilor utilizând modelatorii, utilizarea expresiilor prescurtate.
2.11 Comentarea programelor C/C++

26
Un comentariu este o notă explicativă pentru programatori, ignorată complet de
compilator În C comentariile sunt delimitate de perechile de caractere /* …*/.
Compilatorul C ignoră orice text aflat între aceşti delimitatori.

Exemple de comentarii:

/* Un comentariu se poate întinde pe mai multe linii


dacă aceasta este dorinţa programatorului*/

sau

clrscr() /* Stergere ecran în mod text */


Acest tip de comentariu nu poate fi imbricat.

Compilatoarele C++ acceptă şi comentarii care încep cu secvenţa //, dar se pot
întinde pe o singură linie, astfel:

clrscr() // Stergere ecran în mod text.

27
3 Reprezentarea structurilor de prelucrare în C/C++
Limbajul C nu propune o revoluţie de fond în ceea ce priveşte problema
reprezentării structurilor de prelucrare. Vine, însă, cu o serie de inovaţii
interesante, în ceea ce priveşte flexibilitatea şi varietatea propunerilor de
reprezentare. Nu spunem nici o noutate amintind că reprezentarea structurilor
de prelucrare se bazează pe combinarea, dacă se poate, profesională, a
instrucţiunilor executabile ale limbajului. Potrivit standardelor ANSI C şi ANSI
C++ instrucţiunile sunt împărţite în următoarele grupe:
ÖInstrucţiuni de selecţie
ÖInstrucţiuni iterative
ÖInstrucţiuni de salt
ÖInstrucţiuni de etichetare
ÖInstrucţiuni bloc
Instrucţiunile de selecţie cuprind enunţurile if şi switch.
În categoria instrucţiunilor iterative intră enunţurile while, for şi do-while.
Instrucţiunile de salt desemnează în C instrucţiunile break, continue, goto şi
return.
Instrucţiunile de etichetare includ enunţurile case şi default (implicate, esenţial,
în sintaxa şi semantica instrucţiunii switch şi etichetele (discutate relativ la
instrucţiunea goto. Problematica instrucţiunilor expresie am discutat-o, pe larg,
în secţiunea precedentă. De asemenea, blocul de cod este, deja, o noţiune cu
care ne-am familiarizat. De altfel, aşa cum în Pascal, secvenţa de cod cuprinsă
între begin şi end era numită şi instrucţiune compusă, standardul ANSI C++
propune, aceeaşi denumire, ca denumire alternativă pentru blocul de cod.

Să atragem atenţia asupra faptului că C++ adaugă elemente sintactice


specifice pentru tratarea excepţiilor de către programator. Nu toate
compilatoarele implementează, însă, acest suport important pentru a realiza
aplicaţii robuste şi fiabile în C++.

3.1 Ce se înţelege prin adevărat şi fals în C?


Mai multe instrucţiuni din C se bazează pe expresii condiţionale pentru a
reprezenta un anumit tip de prelucrare. La fel ca în Pascal, sintaxa acestor
instrucţiuni C face supoziţia că alegerea unei traiectorii de prelucrare depinde de
rezultatul evaluării acestor expresii condiţionale. Teoretic, valorile admisibile
pentru o expresie condiţională sunt adevărat şi fals. Punctul de vedere al
limbajului C cu privire la adevăr şi fals este următorul:
ÖUn rezultat al evaluării egal cu zero este interpretat ca fals.
ÖUn rezultat nenul al evaluării este interpretat ca adevărat.
Această schimbare de optică în ceea ce priveşte adevărul şi falsul este de bun
augur pentru scrierea de cod flexibil şi eficient.

 Propunerea de standard ANSI C++ defineşte un tip de dată boolean numit


bool (care poate să aibă doar valorile adevărat şi fals. Ceea ce nu înseamnă,
însă, că nu se păstrează punctul de vedere al limbajui C relativ la adevăr şi fals.

28
3.2 Instrucţiuni de selecţie în C
C admite două tipuri de instrucţiuni de selecţie: if şi switch.

if
Sintaxa generală a instrucţiunii if este următoarea:

if (<Expresie>) <Instrucţiune 1>;


else <Instrucţiune 2>;

<Instrucţiune 1> şi <Instrucţiune 2> desemnează o singură instrucţiune, un bloc


de instrucţiuni sau nici o instrucţiune. Clauza else este opţională.
Dacă <Expresie> este evaluată ca adevărat (adică rezultatul evaluării este orice
valoare diferită de zero), atunci este executată <Instrucţiune 1>; altfel, se
execută <Instrucţiune 2>. Evident, nu este obligatoriu să fie prezentă clauza
else.
<Expresie> trebuie să returneze o valoare scalară (un întreg, un caracter, un
pointer sau un număr real în virgulă mobilă). Un număr real în virgulă mobilă se
utilizează “cu reţinere” pentru a controla o instrucţiune de selecţie deoarece
încetineşte semnificativ execuţia programului. Aceasta deoarece sunt necesare
mai multe instrucţiuni pentru a efectua o operaţie în virgulă mobilă decât pentru a
executa operaţii la nivel de caracter sau cu întregi.
De semnalat faptul că, în situaţia în care una din instrucţiunile asociate condiţiilor
true sau false ale unui if este bloc de instrucţiuni atunci sintaxa corectă este:

if (<Expresie>) {…}
else {…};

Modul de utilizare a instrucţiunii if poate fi urmărit şi în exemplul de mai jos.


#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
void main()
{
int magic; /* Numar generat aleator */
int ghicit; /* Numar banuit */
magic=rand();
clrscr();
printf("Ghiceste numarul generat aleator:\n");
printf("Numarul generat aleator este cuprins intre 0 si %d",RAND_MAX);
scanf("%d",&ghicit);
if (ghicit==magic)
printf("Ati nimerit numarul generat!!!");
else
printf("Nu ati nimerit numarul generat!!!");
}

29
Instrucţiunea if poate fi imbricată. În caz de imbricare, clauza else se referă
întotdeauna la cea mai apropiată instrucţiune if situată în amonte, în acelaşi bloc
cu else şi neasociat încă cu un else, ca în exemplul:

#include <stdio.h>
#include <conio.h>
#include <string.h>;
void main()
{
float A,B,X;
clrscr();
gotoxy(20,12);
printf("____________________________________");
gotoxy(20,13);
printf(" Rezolvarea unei ecuatii de gradul I");
gotoxy(20,14);
printf(" A=");
gotoxy(20,15);
printf(" B=");
gotoxy(20,17);
printf("____________________________________");
gotoxy(20+strlen(" A="),14);
scanf("%f",&A);
gotoxy(20+strlen(" B="),15);
scanf("%f",&B);
gotoxy(20,17);
printf("_____________________________________");
if (A==0)
if (B==0)
{
gotoxy(20,16);
printf("Ecuatie nedetrminata!!!");
getch();
}
else
{
gotoxy(20,16);
printf("Ecuatie imposibila!!!"); getch();
}
else
{
X=B/A;
gotoxy(20,16);
printf("Solutia ecuatiei este:%f",X); getch();
}
}

30
Destul de frecvent instrucţiunea if este utilizată şi în construcţia numită
“scara if-else-if” având sintaxa generală:

if (<Expresie_1>)
<Instrucţiune_1>;
else if (<Expresie_2>)
<Instrucţiune_2>;
else if (<Expresie_3>)
<Instrucţiune_3>;
:
:
else if (<Expresie_k>)
<Instrucţiune_k>;
:
Reamintim că, în anumite situaţii, putem utiliza operatorul ? pentru a înlocui
instrucţiunea if-else de forma:

if (<Expresie>)
<Expresie_1>;
else
<Expresie_2>;

cu o construcţie de forma:

<Expresie>?<Expresi_1>:<Expresie_2>;

switch
C permite reprezentarea structurilor alternative cu mai multe ramuri utilizând în
acest scop instrucţiune switch având sintaxa:

switch (<Expresie>) {
case <Constanta_1>:
<Secvenţa_de_instrucţiuni_1>
break;
case <Constanta_2>:
<Secvenţa_de_instrucţiuni_2>
break;
:
case <Constanta_k>:
<Secvenţa_de_instrucţiuni_k>
break;
default
<secvenţa_de_instrucţiuni>
}

31
Semantica instrucţiunii este următoarea: se compară valoarea expresiei
<Expresie> cu valorile constantelor specificate în instrucţiunile case. Când se
întâlneşte o coincidenţă, se execută secvenţa de instrucţiuni asociată acelui
case până la instrucţiunea break sau până când se ajunge la finalul instrucţiunii
switch. Instrucţiunea default se execută dacă nu este întâlnită nici o
coincidenţă. Clauza default este opţională şi dacă nu este prezentă, atunci când
nu avem nici o coincidenţă nu se execută nici o acţiune.
Standardul ANSI C stipulează că switch poate să aibă cel mult 257 de clauze
case. Standardul propus de ANSI C++ recomandă să se poată introduce cel mult
16.384 clauze case. În practică, din motive de eficienţă, se urmăreşte limitarea
numărului de clauze case într-o instrucţiune switch.
Instrucţiunea case nu poate fi utilizată decât în contextul instrucţiunii switch.
Instrucţiunea break este o instrucţiune de salt în C, fiind utilizată pentru a
determina ieşirea forţată din anumite tipuri de structuri (switch, for, do-while).
Ilustrăm modul de utilizare a instrucţiunii switch prin exemplul de mai jos.

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
void main()
{
int x;
char Ras;
clrscr();
x=random(2);
switch(x+2){
case 3:
{
gotoxy(20,10);
printf("Switch atins...");
break;
}
default:
{
gotoxy(20,10);
printf("Switch ocolit...");
}
}
Ras=getch();
}

Evident şi instrucţiunea switch poate fi imbricată.


De asemenea, facem următoarele precizări relativ la instrucţiunea switch:
-În acelaşi switch nu pot exista două constante case cu valori identice.
Două instrucţiuni switch, imbricate, pot să aibă aceeaşi constantă case.

32
-Dacă în instrucţiunea switch sunt utilizate constante de tip caracter, ele
sunt automat convertite în întregi.

3.3 Instrucţiuni iterative


În C, ca şi în alte limbaje de programare evoluate, există enunţuri de limbaj
pentru a reprezenta:
Östructuri repetitive cu număr cunoscut de paşi (for);
Östructuri repetitive cu număr necunoscut de paşi, anterior condiţionate
(while);
Östructuri repetitive cu număr necunoscut de paşi, posterior condiţionate
(do-while);
Structurile repetitive se mai numesc şi bucle.

Bucla for (pentru structuri repetitive cu număr cunoscut de paşi…)


Conceptul general de <buclă for> se reflectă într-o formă sau alta în orice limbaj
de programare de nivel înalt. În C, acest concept are o implementare de o
flexibilitate şi o putere neaşteptate. Sintaxa generală a instrucţiunii for este:

for (<Iniţializare>;<Condiţie>;<Increment>) <Instrucţiune>;

O astfel de sintaxă permite multe variante constructive ale buclei for. În general
vorbind, însă:
-<Iniţializare> este o instrucţiune de atribuire utilizată pentru a iniţializa
variabila de control a buclei;
-<Condiţie> este o expresie relaţională care determină condiţia de ieşire
din buclă;
-<Increment> defineşte modul în care se modifică variabila de control a
buclei de fiecare dată când aceasta se repetă.
De remarcat faptul că cele trei secţiuni trebuie separate prin punct şi virgulă.
Aşadar, bucla for se execută cât timp <Condiţie> este adevărată. Dacă
<Condiţie> devine falsă, atunci execuţia programului continuă cu instrucţiunea
care urmează construcţiei for, dacă aceasta există.
Maniera clasică de utilizare a buclei for în C, o prezentăm în exemplul de mai
jos.

#include <stdio.h>
#include <iostream.h>
#include <conio.h>
long int fact( int n);
void main()
{
clrscr();
gotoxy(20,12);
int nr;
cout<<"Introduceti numarul:";
cin>>nr;

33
gotoxy(20,14);
cout<<"Factorial("<<nr<<")="<<fact(nr);
getch();
}
long int fact(int n)
{
long int f;
int i;
f=1;
/*-------------------------------------------------------------------
/* Bucla for clasică cu o singură variabilă de control
/*-------------------------------------------------------------------
for (i=2;i<=n;i++)
f=f*i;
return f;
}
Exemplul de cod C prezentat mai sus permite calculul factorialului pentru un
număr natural dat. Exemplul arată, anticipând, modul de utilizare a conceptului
de funcţie în C++.

Câteva variaţiuni pe tema buclei for…


Sintaxa C permite variante dintre cele mai neaştepate de definire a unor bucle
for. Rigiditatea buclei for din Pascal este înlocuită în C cu o sintaxă care emană
mai multă putere, flexibilitate şi aplicabilitate în situaţii specifice de programare.
Una dintre cele mai folosite variaţiuni foloseşte operatorul virgulă, pentru a
permite ca bucla să fie controlată de două sau mai multe variabile. De exemplu,
variabilele x şi y controlează următoarea buclă şi amândouă sunt iniţializate în
interiorul instrucţiunii for.
:
for (x=0,y=0;x+y<10;++x)
{
y+=1; //Această scriere reste echivalentă cu y=y+1;
printf(“Y= %d”,y);
}
:
Prezentăm, mai jos, un exemplu practic de utilizare a unei bucle for controlată de
două variabile: afişarea unui şir de caractere începând de la ambele capete,
mergând către mijlocul şirului. Se observă prezenţa fişierului header <dos.h> în
care se află prototipul funcţiei delay().

#include <conio.h>
#include <stdio.h>
#include <string.h>
#include <dos.h>;

//Semnatura functie converg

34
void converg(int linie, char *mesaj);

//Functia principala
void main()
{
clrscr();
gotoxy(20,12);
converg(12,"Acesta este un test pentru functia converg().");
}

//Implementare functie converg


void converg(int linie, char *mesaj)
{
int i,j,ls,cs;
ls=(80-strlen(mesaj))/2;
cs=ls+strlen(mesaj)-1;

for (i=ls,j=cs;i<=j;i++,j--)
{
gotoxy(i,linie);printf("%c",mesaj[i-ls]);
delay(50); //prototipul in fisierul antet <dos.h>
gotoxy(j,linie);printf("%c",mesaj[strlen(mesaj)-1-cs+j]);
}
getch();
}

Ca un caz particular prezentăm şi sintaxa pentru o buclă infinită cu for…

for ( ; ; )

Bucla while (pentru structuri repetitive cu număr necunoscut de paşi


condiţionate anterior…)
A doua buclă disponibilă în C este bucla while. Forma sa generală este:

while (<Condiţie>) <Instrucţiune>;

unde <Instrucţiune> este o instrucţiune vidă, o instrucţiune sau un bloc de


instrucţiuni.. Condiţia poate să fie orice expresie, fiind adevărată pentru orice
valoare nenulă. Bucla se reia cât timp condiţia este adevărată. Când condiţia
devine falsă, controlul programului trece la linia de cod următoare buclei, dacă
aceasta există.
Prezentăm un exemplu de cod în care bucla while este utilizată pentru a simula,
eventual, execuţia unei secvenţe de instrucţiuni până când doreşte liberul arbitru
al utilizatorului programului.

35
#include <stdio.h>
#include <conio.h>
#include <ctype.h> //Contine prototipul functiei toupper
void main()
{
int sw;
char ras;
sw=1;
/* Bucla while în acţiune…
while(sw)
{
gotoxy(20,12);
printf("Continuam(D,N):");
ras=getch();
if (toupper(ras)=='N')
sw=0;
}
}

Bucla do-while (repetitive cu număr necunoscut de paşi… condiţionate


posterior…)
Spre deosebire de buclele for şi while, care testează condiţia din buclă la
începutul execuţiei lor, bucla do-while o verifică la sfârşit. Aceasta înseamnă că
bucla do-while se execută cel puţin odată. Forma sa generală este:

do {
<Instrucţiune>;
} while <Condiţie>;

Bucla do-while se repetă până când <Condiţie> devine falsă. Dăm, mai jos, un
exemplu practic de utilizare a buclei do-while (afişarea/ selectarea opţiunilor unui
program C).
:
char prelopt()
{
int c;
clrscr();
gotoxy(20,9);
printf( "Optiunile programului...");
gotoxy(20,11);
printf("1-Preluare elemente vector");
gotoxy(20,12);
printf("2-Determinare suma");
gotoxy(20,13);
printf("3-Terminare program");

36
gotoxy(20,15);
printf("Optiunea Dvs.:");
/* Utilizare buclă do-while…
do
c=getch();
while ((c!='1')&&(c!='2')&&(c!='3'));
return c;
};
:
Alte instrucţiuni C…
Aşa cum rezultă şi din exemplele prezentate, în C mai sunt intens folosite
următoarele instrucţiuni: return, exit, break , continue şi goto.

Instrucţiunea return este utilizată pentru întoarcerea dintr-o funcţie. Este


considerată ca instrucţiune de salt deoarece determină execuţia programului să
revină la prima instrucţiune după funcţia apelată în care apare return. Forma
generală a instrucţiunii return este:

return [<Expresie>]

Această sintaxă arată că dacă <Expresie> este prezentă atunci rezultatul


evaluării expresiei este valoarea returnată de funcţie. O funcţie void nu trebuie
să returneze nimic, deci return poate să apară fără <Expresie>. Într-o funcţie
return poate să apară de câte ori este necesar. În C++ o funcţie care nu este
void trebuie să returneze o valoare.

Funcţia exit()

Este utilizată pentru ieşirea imediată dintr-un program, returnând, eventual un


cod de retur. Sintaxa de apel este:

exit (<Cod_de_retur>);

<Cod de retur> poate fi interpretat de procesul care a apelat programul, de


regulă, sistemul de operare. Exit acţionează ca un break generalizat.

Instrucţiunea break

Are două utilizări. Poate fi folosită, după cum am văzut deja pentru a încheia un
case dintr-o instrucţiune switch, sau pentru a determina încheierea imediată a
unei bucle.

Instrucţiunea continue
Forţează trecerea la următoarea iteraţie a unei bucle, determinând ignorarea
restului codului iteraţiei în care se află.
Nu facem menţiuni speciale referitor la instrucţiunea goto.

37
38
4 Operaţii I/O relativ la perifericele standard
Ne propunem în acest modul să prezentăm funcţiile C care fac parte din
sistemul I/O referitor la efectuarea operaţiilor de introducere a datelor de la
tastatură şi afişare a acestora pe ecranul monitorului.

4.1 Operaţii la nivel de caracter


Cele mai simple funcţii C pentru lucrul la nivel de caracter relativ la perifericele
standard au următoarele prototipuri:

int getch (void); { Fişierul antet depozitar :conio.h}


int getche (void); { Fişierul antet depozitar :conio.h}
int putchar (int car); {Fişierul antet depozitar :stdio.h}

Funcţia getch() este utilizată pentru a citi un caracter de la tastatură fără ecou pe
ecranul monitorului , analog mecanismului readkey din Pascal.

Funcţia getche() este utilizată pentru a citi un caracter de la tastatură cu ecou pe


ecranul monitorului.

Funcţia putchar() se foloseşte pentru a scrie un caracter pe ecranul monitorului


în poziţia curentă a cursorului.

După cum sugerează prototipul, getch() returnează un întreg. Totuşi, funcţia


poate returna această valoare unei variabile de tip caracter deoarece caracterul
este conţinut în octetul de ordin inferior.
Aceeaşi observaţie este valabilă şi pentru funcţia getche().
Deşi din alt punct de vedere, şi funcţia putchar() poate fi apelată cu argument de
tip caracter deoarece ieşirea pe ecran se referă, oricum, doar la octetul de ordin
inferior.
În cazul apariţiei unei erori, putchar() returnează EOF. Funcţia macro EOF, este
definită în stdio.h şi, în general este egală cu –1. Exemplul pe care îl prezentăm
permite citirea de caractere de la tastatură şi, dacă sunt litere mici, convertirea
lor la litere mari. Citirea se termină în momentul în care se tastează caracterul
ESC al cărui cod backslash este \033.

#include <stdio.h>
#include <conio.h>
#include <ctype.h>
void main()
{
char car;
do
{
car=getche();
car=toupper(car);
putchar(car);

39
}
while (car!='\033');
}

Prototipul funcţiei toupper() se află (aşa cum am mai spus-o) în fişierul antet
ctype.h. Se observă că citirea se face cu ecou pe ecranul monitorului. Este cazul
să spunem că programatorul în C/C++ are de făcut o serie de descoperiri
folositoare în ceea ce priveşte oferta numeroaselor fişiere antet livrate odată cu
compilatorul. Prezentarea tuturor acestor funcţii, fie şi numai prina antet, nu este
de loc o treabă uşoară, din punct de vedere al volumului. De aceea. în acest
suport de curs adresăm cititorului invitaţia de a descoperi, cu ajutorul help-ului on
line şi a unor cărţi care nu fac economie de spaţiu, potenţialul structurat în
fişierele antet.

4.2 Operaţii la nivel de şir de caractere


Pentru operaţii la nivel de şir de caractere C pune la dispoziţie funcţiile având
următoarele prototipuri:

char *gets(char *sir);


int puts(const char *sir);

Funcţia gets() citeşte un şir de caractere de la tastatură şi îl plasează la adresa


indicată de argumentul său.
Funcţia puts() scrie pe ecran argumentul său, urmat de o linie nouă.
Prototipurile acestor două funcţii se găsesc în stdio.h.
Exemplul care urmează arată modul de utilizare al funcţiilor gets() şi puts() când
argumentul lui gets() este o variabilă obişnuită.

#include <stdio.h>
#include <conio.h>
void main()
{
char s[30];
clrscr();
gotoxy(20,10);
printf("Introduceti un sir de caractere:");
gets(s);
gotoxy(20,11);
printf("Sirul introdus :");
puts(s);
getch();
clrscr();
}

În exemplul de mai jos se arată modul de utilizare al funcţiilor gets() şi puts()


când argumentul lui gets() este o variabilă pointer la un şir de caractere.

40
#include <stdio.h>
#include <conio.h>
void main()
{
char s;
char *p;
clrscr();
gotoxy(20,10);
p=&s
printf("Introduceti un sir de caractere:");
gets(p);
gotoxy(20,11);
printf("Sirul introdus :");
puts(p);
getch();
clrscr();
}

Operaţii I/O cu şiruri de caractere se pot efectua şi cu funcţiile scanf() şi printf(),


mult mai versatile decât gets() şi puts() , după cum vom vedea în continuare.

4.3 Funcţii I/O relativ la consolă formatate


Funcţiile printf() şi scanf() efectuează operaţii I/O formatate, adică pot scrie şi,
respectiv, citi date în diverse formate specificate de programator. Funcţia printf()
afişează date formatate la consolă. Funcţia scanf(), complementul funcţiei
printf() citeşte date formatate de la tastatură. Ambele funcţii pot lucra cu toate
tipurile de date existente în C. Prototipurile celor două funcţii sunt:

int scanf( const char *format [, address, ...]); {Fişierul antet gazdă :stdio.h}
int printf(const char *format [, argument, ..]); {Fişierul antet gazdă :stdio.h}

printf()
Returnează, cu destinaţia ecran, numărul de caractere scrise sau, dacă apare o
eroare, o valoare negativă. Parametrul format se compune din două tipuri de
simboluri. Primul tip îl formează caracterele care vor fi afişate pe ecran. Al doilea
tip se referă la specificatorii de format cu ajutorul cărora se stabileşte modul în
care sunt afişate argumentele care urmează. Un specificator de format începe cu
un semn % şi este urmat de un cod de format. Trebuie să existe acelaşi număr
de argumente ca şi acela al specificatorilor de format şi, totodată, specificatorii
de format şi argumentele se asociază în ordinea de la stânga la dreapta.

41
Codurile de format disponibile pentru printf() sunt:

Cod Format
%c Caracter
%d Numere întregi în baza 10, cu semn
%I Numere întregi în baza 10, cu semn
%e Notaţie ştiinţifică (cu litera e)
%E Notaţie ştiinţifică (cu litera E)
%f Număr zecimal în virgulă mobilă
%g Foloseşte %e sau %f , anume, care din ele este mai mic
%G Foloseşte %E sau %f , anume, care din ele este mai mic
%o Număr în octal, fără semn
%s Şir de caractere
%u Numere întregi zecimale fără semn
%x Numere hexazecimale fără semn (cu litere mici)
%X Numere hexazecimale, fără semn, cu litere mari
%p Afişează un pointer
%n Argumentul asociat este un pointer de tip întreg în care a fost
plasat numărul de caractere scrise până atunci.
%% Afişează un semn %

Pentru a înţelege modul deosebit în care acţionează codul %n urmăriţi exemplul


de mai jos.

#include <stdio.h>
#include <conio.h>
void main()
{
int numara;
printf("Acesta%n este un test...\n",&numara);
printf("%d",numara);
getch();
}

De remarcat faptul că specificatorii de format acceptă modelatori de format care


modifică uşor semnificţia lor. De exemplu, se poate specifica un minim de
caractere permise la afişare, numărul de cifre zecimale şi alinierea la stânga.
Modelatorul de format se află între semnul % şi codul pentru format. Codul de
mai jos poate forma o idee asupra utilităţii modelatorilor de format.

#include <stdio.h>
#include <conio.h>
void main()
{
int numara;

42
clrscr();
printf("Acesta%n este un test...\n",&numara);
printf("Aliniere la dreapta.......\n");
printf("%10d\n",numara);
printf("Aliniere la stanga .......\n");
printf("%-d\n",numara);
printf("Completare cu zerouri.....\n");
printf("%010d\n",numara);
getch();
}

scanf()
Este o rutină de uz general pentru intrări de la consolă. Ea poate să citească
toate tipurile de date încorporate şi să facă automat conversia numerelor în
format intern corect. Se aseamănă mult cu complementara ei printf(). Ca funcţie,
scanf() returnează numărul de elemente cărora li s-a atribuit cu succes o
valoare. Dacă apare o eroare scanf() returnează EOF. Argumentul format
determină modul în care vor fi citite valorile în variabilele din lista de argumente.
Codurile de format disponibile pentru scanf() sunt prezentate în tabelul de mai
jos.

Cod Format
%c Citeşte un singur caracter
%d Citeşte un număr întreg în baza 10
%I Citeşte un număr întreg în baza 10
%e Citeşte un număr în virgulă mobilă
%E Citeşte un număr în virgulă mobilă
%f Citeşte un număr în virgulă mobilă
%g Citeşte un număr în virgulă mobilă
%o Citeşte un număr în octal
%s Citeşte un şir de caractere
%u Numere întregi zecimale fără semn
%x Citeşte un număr în hexazecimal
%p Citeşte un pointer
%n Argumentul asociat este un pointer de tip întreg în care a fost
plasat numărul de caractere citite până atunci.
%u Citeşte un întreg fără semn
%[] Caută un set de caractere
Tabelul 10. O parte din codurile pentru formatarea operaţiilor I/I relativ la
periferice standard

Primul exemplu de cod prezentat mai jos ilustrează ideea de scanset posibilă la
utilizarea funcţiei scanf(). Definirea unui scanset înseamnă, de fapt că citirea
corespunzătoare unei variabile este validată cât timp caracterele citite se
potrivesc celor definite în scanset.

43
Al doilea exemplu este conceput în ideea că valoarea variabilei n2 va fi citită
numai după introducerea de la tastatură a două caractere virgulă. Acest
mecanism permite, printre altele, definirea ca separator la introducerea datelor a
altui caracter decât <CR>.

#include <stdio.h>
#include <conio.h>
void main()
{
char sir[3];
clrscr();

scanf("%[TOC]",&sir); //Citire cu scanset


printf("\n%s",sir);
getch();
}

#include <stdio.h>
#include <conio.h>
void main()
{
int n1,n2;
clrscr();
scanf("%d,,%d",&n1,&n2);
printf("%d\n",n1);
printf("%d\n",n2);
getch();
}

Atenţie la necesitatea ca variabila în care se citeşte să fie un pointer!

Alte facilităţi pentru lucrul cu consola


În categoria “alte facilităţi pentru lucrul cu consola” includem, cu prioritate,
funcţiile clrscr(), clreol(), gotoxy(col,lin) ale căror valori de întrebuinţare sunt
identice celor din Borland Pascal. Chiar şi sintaxa funcţiei gotoxy(col,lin) este
concordantă în ceea ce priveşte ordinea parametrilor după care se face
poziţionarea cursorului.
Aşadar:

clrscr() – se utilizează pentru şteregerea ferestrei text curente; fereastra


text curentă implicită depinde de modul text în care se lucrează. Caracteristicile
modurilor text recunoscute de C sunt prezentate în tabelul de mai jos.

44
Constanta Valoare Mod Text Caracterisrici
LASTMODE -1 Precedentul mod text
BW40 0 Black and white 40 coloane
C40 1 Color 40 coloane
BW80 2 Black and white 80 coloane
C80 3 Color 80 coloane
MONO 7 Monochrome 80 coloane
C4350 64 EGA and VGA 50 linii
Tabelul 11. Moduri video text standard

Aceste moduri text pot fi selectate cu ajutorul funcţiei textmode(), al cărei antet
se află tot în conio.h şi face parte tot din arsenalul C de lucru cu ecranul în mod
text. Tot în mod text, putem controla culorile textului şi ale fondului (background-
ul). Sintaxa funcţiilor cu care realizăm controlul culorilor este:

textcolor(<Culoare>);
textbackground(<Culoare>);

Parametrul <Culoare> poate lua una din valorile prezentate în tabelul de mai jos:

Constanta Valoare Fond Text


BLACK 0 Da Da
BLUE 1 Da Da
GREEN 2 Da Da
CYAN 3 Da Da
RED 4 Da Da
MAGENTA 5 Da Da
BROWN 6 Da Da
LIGHTGRAY 7 Da Da
DARKGRAY 8 Da Da
LIGHTBLUE 9 Nu Da
LIGHTGREEN 10 Nu Da
LIGHTCYAN 11 Nu Da
LIGHTRED 12 Nu Da
LIGHTMAGENTA 13 Nu Da
YYELLOW 14 Nu Da
WHITE 15 Nu Da
BLINK 128 Nu ***
Tabelul 12. Valori admise pentru culoare text si fond. Moduri video text standard

*** Codul BLINK se adaugă la culoarea textului pentru a obţine efectul de


blinking.

45
clreol() – se utilizează pentru ştergerea liniei curente începând cu coloana
pe care se află cursorul şi până la sfârşitul liniei.
gotoxy(col,lin) – permite poziţionarea cursorului pe linia şi coloana
specificate.
De asemenea, pot fi de interes, în anumite situaţii, următoarele funcţii:

Öwindow(css,lss,cdj,ldj);
Defineşte coordonatele ferestrei text active.

Öwherex();
Returnează coloana curentă a cursorului.

Öwherey();
Returnează linia curentă a cursorului.

Ögettext() şi puttext()
Permit citirea/scrierea memoriei video în mod text, ca în exemplul de mai
jos.

#include <conio.h>

//Buffer pentru salvat conţinut memorie-video


char buffer[4096];
int main(void)
{
int i;

// Comutare în modul text C4350


textmode( C4350);
clrscr();
for (i = 0; i <= 20; i++)
cprintf("Linia %d\r\n", i);

//Salvare conţinut memorie video în variabila buffer


gettext(1, 1, 80, 24, buffer);
gotoxy(1, 25);
cprintf("Apasati o tasta pentru a sterge ecranul...");
getch();

//Ştergere ecran
clrscr();
gotoxy(1, 25);
cprintf(" Apasati o tasta pentru a restaura ecranul...");
getch();

46
//Refacere conţinut memorie video, utilizând datele salvate în variabila buffer
puttext(1, 1, 80, 24, buffer);

gotoxy(1, 25);
cprintf("Apasati o tasta pentru a termina...");
getch();
return 0;
}

Ca o aplicaţie la cele prezentate până în acest moment în legătură cu


programarea în C, prezentăm codul care încapsulează câteva noi facilităţi de
lucru în mod text, sub forma unui fişier antet, care poate fi utilizat în orice
program C, dacă este prezentă directiva de compilare:

#Include “facilcrt.h”

Numele fişierului antet este, evident, facilcrt.h.

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

//Activare video-invers
void avideo()
{
textcolor(BLACK);
textbackground(WHITE);
}

//dezactivare video-invers
void dvideo()
{
textcolor(WHITE);
textbackground(BLACK);
}

//Afisare centrata text in interiorul unei linii


void acentext(int ls,int ld,int linia,char *sir)
{
int sw;
int col;
sw=(ls>=1) && (ls<=79) && (ld<=80) && (ld>=2) && (ls<ld);
sw=sw && ((ld-ls+1)>=strlen(sir));
if (sw)
{
col=ls+(ld-ls+1-strlen(sir))/2;

47
gotoxy(col,linia);
cprintf(sir);
}
}

//Construire fereastra cu rama dreptunghiulara de dimensiuni specificate


void makewin2(int ass,int oss,int adj,int odj)
{
short int i;
int sw;
sw=(ass>0) && (ass<81) && (adj>0) && (adj<81) && (ass<=adj);
sw=sw && (oss>0) && (oss<25) && (odj>0) && (odj<25) && (oss<=odj);
if(sw)
{
for(i=ass;i<=adj;i++)
{
gotoxy(i,oss-1);cprintf("\315");
gotoxy(i,odj+1);cprintf("\315");
}
for(i=oss;i<=odj;i++)
{
gotoxy(ass-1,i);cprintf("\272");
gotoxy(adj+1,i);cprintf("\272");
}
gotoxy(ass-1,oss-1);cprintf("\311");
gotoxy(adj+1,oss-1);cprintf("\273");
gotoxy(ass-1,odj+1);cprintf("\310");
gotoxy(adj+1,odj+1);cprintf("\274");
}
else
{
gotoxy(1,24);
printf("Coordonate ecran eronate!!!");
getch();
}
}

Ca un comentariu la exemplul de cod C prezentat mai sus şi la alte exemple


prezentate deja, facem precizarea că directiva de includere #include este soluţia
C pentru o modularizare a codului, orientată pe clasificarea tipurilor de
capabilităţi de prelucrare în fişiere speciale numite fişiere antet. Fişierele antet
sunt incluse în codurile sursă ale programelor noastre de câte ori avem nevoie
de o capabilitate al cărei prototip se află în acele fişiere antet.

48
5 Matrice şi şiruri
O matrice este o colecţie de variabile de acelaşi tip, apelate cu acelaşi nume.
Accesul la un anumit element al matricei se face cu ajutorul unui indice. În C
toate matricile constau în locaţii de memorie contigue. Cel mai mic indice
corespunde primului element iar cel mai mare ultimului element. Matricele pot
avea una sau mai multe dimensiuni. Ca şi în alte limbaje, cea mai simplă matrice
este şirul. În C şirul este o matrice de caractere terminate cu un caracter NULL.
Această caracteristică oferă limbajului C mai multă putere şi eficienţă decât
posedă alte limbaje.
Semnalăm, de asemenea, faptul că în C, există o strânsă legătură între matrice
şi pointeri.

5.1 Matrice cu o singură dimensiune


Forma generală în declararea unei matrice cu o singură dimensiune este:

<Tip> <Nume_variabilă>[<Dimensiune>];

<Tip> declară tipul de bază al matricei, care este tipul fiecărui element al
său.
<Dimensiune> indică numărul maximal de elemente pe care le poate
conţine matricea.

 De reţinut faptul că, în C, toate matricele au ca indice pentru primul


element pe 0.

Cantitatea de memorie necesară pentru înregistrarea unei matrice este direct


proporţională cu tipul şi mărimea sa. Pentru o matrice unidimensională, mărimea
totală în octeţi este calculată astfel:

Total_octeţi = sizeof(<Tip>)*<Dimensiune>

 C nu controlează limitele unei matrice. Puteţi depăşi ambele margini ale


unei matrice şi scrie în alte variabile sau peste codul programului. Riscurile
şi responsabilităţile sunt de partea programatorilor.

5.2 Crearea unui pointer la o matrice


Un pointer la primul element al unei matrice se creează simplu, specificând
numele matricei, fără nici un indice.
De exemplu, având :

float sir[10];

putem crea un pointer la primul element al matricei astfel:

49
float *p;
float sir[10];
p=sir;

5.3 Şiruri de caractere


De departe, cea mai utilizată matrice unidimensională este şirul de caractere.
Reamintim faptul că în C un şir de caractere este definit ca o matrice de
caractere care se termină cu un caracter NULL. În convenţie backslash un NULL
se reprezintă prin ‘\0’ şi are valoarea 0. Din acest motiv, matricele de tip caracter
se declară cu un caracter mai mult decât lungimea celui mai mare şir pe care îl
vor conţine. Deşi C nu are date de tip şir permite constante şir. O constantă şir
este o succesiune de caractere închise între ghilimele. Nu este necesar să
introduceţi manual caracterul NULL la sfârşitul constantelor şir, compilatorul face
acest lucru automat.
C admite, totodată, o gamă largă de funcţii de manipulare a şirurilor. Cele mai
des utilizate sunt prezentate în tabelul de mai jos.

Nume funcţie Utilitate


strcpy(s1,s2) Copiază s2 în s1
strcat(s1,s2) Concatenează s2 la sfârşitul s1
strlen(s1) Returnează lungimea lui s1
strcmp(s1,s2) Returnează 0 dacă s1 şi s2 sunt identice; un număr
mai mic decât 0 dacă s1<s2 în sens lexicografic; un
număr mai mare decât 0 dacă s1>s2 în sens
lexicografic.
strchr(s1,ch) Returnează un pointer la prima apariţie a caracterului
ch în s1.
strstr(s1,s2) Returnează un pointer la prima apariţie a lui s2 în s1.
Tabelul 13. Funcţii C pentru lucrul cu şiruri de caractere păstrate în fişierul antet
string.h
 Atenţie! strcmp() returnează fals dacă şirurile sunt egale.

5.4 Matrici bi şi multidimensionale


C admite matrice multidimensionale. Cea mai simplă formă de matrice
multidimensională este cea cu două dimensiuni. De fapt, o matrice
bidimensională este o matrice de matrice unidimensionale. Pentru a declara o
matrice bidimensională utilizăm sintaxa:

<Tip> <Nume_variabilă>[<Dimensiune_1>][<Dimensiune_2>];

De exemplu, o matrice bidimensională de întregi, de mărime10/20 se declară


astfel:

int matr[10][20];

50
Adresarea elementelor matricei se bazează pe premiza că, după fiecare
dimensiune, indexarea începe de la 0. De asemenea, facem precizarea că,
atunci când o matrice bidimensională este utilizată ca un argument pentru o
funcţie, se transmite doar un pointer către primul element al matricei. Însă,
parametrul care primeşte o matrice bidimensională trebuie să definească cel
puţin numărul de coloane, necesare compilatorului pentru a indexa corect
matricea. De urmărit exemplul de mai jos de cod C care permite calculul sumei
elementelor strict pozitive de pe diagonala principala a unei matrice patratice .

#include<stdio.h>
#include<conio.h>

// Declarare matrice bidimensionala


int matr[10][10];
int dimm;

//Functia care primeste ca argument o matrice . Declara numarul de coloane.


void sespdp( int m[ ][10])
{
int k,suma;
suma=0;
for (k=0;k<dimm;k++)
{
if (m[k][k]>0)
suma=suma+m[k][k];
}
clrscr();
gotoxy(20,12);
cprintf("Suma elementelor strict pozitive ddp: %d",suma);
getch();
}
//Functia principala
void main()
{
int i,j,el;
clrscr();
gotoxy(20,10);cprintf("Nr. de linii :");
scanf("%d",&dimm);
clrscr();
for(i=0;i<dimm;i++)
for(j=0;j<dimm;j++)
{
gotoxy(20,12);
cprintf("Elementul[ %d , %d ]=",i,j);
scanf("%d",& matr[i][j]);
}

51
//Apelare functie cu parametru matrice transmisa ca pointer static
sespdp(matr);
}

Evident, în cazul unei matrice multidimensionale, forma generală de declarare


precum şi modul de utilizare sunt deductibile din cazul bidimensional.
Sintaxa pentru declararea unei matrice multidimensionale este:

<Tip> <Nume_variabilă>[<Dim_1>][<Dim_2>]…[Dim_n];

În încheierea paragrafului referitor la matrice prezentăm o aplicaţie în care,


arătăm, totodată, primii paşi spre modularizarea şi interfaţarea unui program C.

Este vorba de un program C care cercetează ortogonalitatea a doi vectori


reali. Se utilizează încă odată fişierul antet facilcrt.h .

#include<stdio.h>
#include<conio.h>
#include "facilcrt.h"
#include<stdlib.h>
//Directiva #define permite specificarea unor macrouri
#define lmaxs 100
#define optiuni "1234"
//Solutie modularizata C pentru problema determinrii
//ortogonalitatii a doi vectori reali
char prelopt();
void prelvec();
int ortogon();
float sir1[lmaxs],sir2[lmaxs];
int dims;
void main()
{
for(; ;)
{
switch (prelopt())
{
case '1':
{ prelvec();
break;
}
case '2':
{ ortogon();
break;
};
case '3': exit(0);
}

52
}
}
char prelopt()
{
char opt;
clrscr();
gotoxy(20,9);
cprintf("Optiunile programului....");
gotoxy(20,10);
cprintf("1- Preluare vectori");
gotoxy(20,11);
cprintf("2- Determinare ortogonalitate....");
gotoxy(20,12);
cprintf("3- Terminare program");
makewin2(20,9,55,12);
gotoxy(20,15);
cprintf("Optiunea Dvs.:");
makewin2(20,15,55,15);
do
{
gotoxy(20+strlen("Optiunea Dvs.:"),15);
opt=getch();
}
while (strchr(optiuni,opt)==NULL);
return opt;
}

void prelvec()
{
int i,col;
clrscr();
gotoxy(20,9);
cprintf("Dimensiune vectori:");
makewin2(20,9,20+strlen("Dimensiune vectori:"),9);
gotoxy(22+strlen("Dimensiune vectori:"),9);
col=strlen("Dimensiune vectori:");
makewin2(22+col,9,30+col,9);
gotoxy(23+col,9);
cscanf("%d",&dims);

clrscr();
gotoxy(20,9);
cprintf("Preluarea componentelor primului vector...");
makewin2(20,9,20+strlen("Preluarea componentelor primului vector..."),12);
for (i=0;i<dims;i++)
{

53
gotoxy(20,10);
cprintf("Elementul [ %d ]=",i);
cscanf("%f",&sir1[i]);
}
clrscr();
gotoxy(20,9);
cprintf("Preluarea componentelor celui de-al doilea vector...");
makewin2(20,9,20+strlen("Preluarea componentelor celui de-al doilea
vector..."),12);
for (i=0;i<dims;i++)
{
gotoxy(20,10);
cprintf("Elementul [ %d ]=",i);
cscanf("%f",&sir2[i]);
}
}

int ortogon()
{
float suma;
int i;
suma=0;
for (i=0;i<dims;i++)
suma=suma+sir1[i]*sir2[i];
clrscr();
if (suma)
{
gotoxy(20,10);
textcolor(RED+BLINK);
cprintf("Vectorii nu sunt ortogonali!!");
makewin2(20,10,21+strlen("Vectorii nu sunt ortogonali!!"),10);
getch();
textcolor(WHITE);
}
else
{
gotoxy(20,10);
textcolor(GREEN+BLINK);
cprintf("Vectorii sunt ortogonali!!");
makewin2(20,10,21+strlen("Vectorii sunt ortogonali!!"),10);
getch();
textcolor(WHITE);
};
}

54
Se cuvine să mai fac o paranteză referitoare la insistenţa cu care apar în
programele C/C++ construcţii sintactice care încep cu #, precum #include,
#define. Aceste construcţii fac parte din categoria instrucţiunilor care se
adresează compilatorului şi se mai numesc şi directive pentru preprocesor. deşi
nu fac parte din limbajul C/C++ aceste directive lărgesc sfera de acţiune a
programelor C/C++. Am văzut deja care este utilitatea directivei preprocesor
#include. Să spunem, totodată, faptul că, directiva pentru preprocesor #define
permite definirea unor macrouri astfel:

#define <Nume_macrou> <Secvenţă_de_caracter>

Oriunde în program apare <Nume_macrou> compilatorul îl înlocuieşte cu


<Secvenţă_de_caractere>. Adică avem la dispoziţie un instrument foarte util la
parametrizarea codului. Să adăugăm că #define poate permite şi definirea de
funcţii macro, situaţie în care numele macroului poate avea şi argumente.
Exemplu:

#define ABS(a) (a)<0 ? –(a) : (a)


:
printf(“abs de –1 si 1”%d %d,ABS(-1), ABS(1));

La compilarea acestei secvenţe, a care apare în definirea funcţiei macro va fi


înlocuit cu valorile –1 şi 1.
Utilizarea unei funcţii macro măreşte viteza de execuţie a codului.
Merită cercetate cu atenţie şi trucurile care pot fi realizate cu ajutorul directivei
#define. Semnalăm că, aşa cum se întâmpla şi în Pascal, în C există o serie de
directive pentru compilare condiţională a codului sursă. Din această categorie fac
parte #if, #endif, #else #ifdef, #ifndef, etc.

55
6 Pointeri
Un pointer este o variabilă care poate conţine o adresă de memorie. Această
adresă este localizarea în memorie a unui alt obiect (de regulă o altă variabilă).
De exemplu, dacă o variabilă conţine adresa alteia, despre prima se spune că
este un pointer la cea de-a doua. O variabilă de tip pointer se declară în C astfel:

<Tip> * <Nume>;

unde <Tip> este tipul de bază al pointerului iar <Nume> este numele variabilei
pointer. Tipul de bază al pointerului defineşte tipul de variabilă către care indică
pointerul. Practic, orice tip de pointer poate să indice orice în memorie.
Problema este, însă, că aritmetica pointerilor este integral raportată la tipul
de bază al pointerului.

Operatori pentru pointeri


Există doi operatori speciali pentru pointeri: * şi &. & este un operator unar care
returnează adresa din memorie a operandului său. Operatorul * returnează
valoarea înregistrată la adresa care îl urmează. Evident că operatorul & se aplică
oricărui tip de variabilă dar operatorul * cere după el, neapărat, o variabilă
pointer.

Expresii cu pointeri
În general, expresiile care implică pointeri se conformează aceloraşi reguli ca şi
celelalte expresii. Există, totuşi, o serie de particularităţi pe care le vom evidenţia
în acest paragraf în ceea ce priveşte aritmetica pointerilor.
Astfel:
ÖAtribuirile sunt permise între pointeri concordanţi ca tip.
ÖPointerilor li se poate aplica operatorul ++ (incrementare) ca mai jos
:
int *p;
int mat[10];
:
p=&mat[0];
:
p++; // Operatorul de incrementare aplicat pointerului p
p- -;
:
În urma aplicării operatorului de incrementare pointerului p, care conţinea adresa
primului element al tabloului unidimensional mat, offset-ul acestuia este
incrementat cu 2, adică atât cât este lungimea în octeţi a tipului de bază al
pointerului p.
Ö Pointerilor li se poate aplica operatorul - - (decrementare) .
În urma aplicării operatorului de decrementare pointerului p, care conţinea
adresa celui de-al doilea element al tabloului unidimensional mat, offset-ul
acestuia este decrementat cu 2, adică atât cât este lungimea în octeţi a tipului de
bază al pointerului p.

56
ÖDe asemenea, putem aduna sau scădea întregi la, sau din pointeri. De
exemplu:
:
p=p+10;
:
face ca p să indice al 10-lea element de acelaşi tip cu tipul de bază al lui p,
relativ la elementul curent. În exemplul de mai jos ilustrăm utilitatea aritmeticii
pointerilor în contextul lucrului cu matrice.

#include<stdio.h>
#include<conio.h>
int matr[10][10]; //matrice de intregi bidimensionala
int *p; // pointer la intregi
int dimm;
void main()
{
int i,j,el;
clrscr();
gotoxy(20,10);cprintf("Dimensiune matrice :");
scanf("%d",&dimm);
clrscr();
for(i=0;i<dimm;i++)
for(j=0;j<dimm;j++)
{
gotoxy(20,12);
cprintf("Elementul[ %d , %d ]=",i,j);
scanf("%d",&el);
matr[i][j]=el;
};
p=&matr[0][0]; //p refera primul element al matricei
for (i=0;i<dimm;i++)
cprintf("\r\n Ref_poin=%d",*(p+i)); //modificare offset p in expresie
getch();
p=&matr[0][0];
for (i=0;i<dimm;i++)
{
cprintf("\r\n Ref_poin=%d",*(p));
p=p+1; //modificare offset p prin incrementare
}
getch();

p=&matr[0][0];
for (i=0;i<dimm;i++)
{
cprintf("\r\n Ref_poin=%d",*(p));
p++; //aplicare operator de incrementare

57
}
getch();
}

ÖPointerii pot fi comparati în cadrul expresiilor relaţionale. Daţi pointerii p


şi q, este perfect valabilă instrucţiunea:

if (p<q) cprintf (“p indica o memorie de adresa mai mica decat q\n”);

Evident, în C se poate vorbi de matrice de pointeri, de indirectare multiplă şi,


evident, de alocarea dinamică a memoriei aferente unor pointeri.

Funcţii de alocare dinamică în C


Pointerii oferă suportul necesar pentru sistemul puternic de alocare dinamică a
memoriei în C. Alocarea dinamică este caracteristica prin care un program poate
obţine memorie în timpul execuţiei. După cum se ştie, variabilelor globale li se
alocă memorie în timpul compilării. Variabilele locale folosesc memoria de tip
stivă. În mod cert, nici variabilele globale nici cele locale nu pot fi adăugate în
timpul execuţiei programului. Există situaţii în care necesarul real de memorie
este cunoscut de-abia în timpul execuţiei programului.

 Chiar dacă C++ acceptă pe deplin sistemul de alocare dinamică al lui C,


el îşi defineşte propriul sistem, care conţine mai multe îmbunătăţiri faţă de
cele din C.

Memoria alocată de funcţiile de alocare dinamică din C este obţinută din HEAP-
zona de memorie liberă situată între zona permanentă a memoriei programului şi
stivă.
Nucleul sistemului de alocare din C constă din funcţiile malloc() şi free().
Aceste “instrumente” de alocare lucrează în pereche, folosind zona de memorie
liberă pentru a stabili şi a păstra o listă cu memoria disponibilă.
Funcţia malloc() alocă memorie, având următorul prototip:

void *malloc(size_t numar_de_octeţi);

În acest prototip, numar_de_octeţi este numărul de octeţi din memorie pe care


dorim să-l alocăm programului. Funcţia malloc() returnează un pointer de tipul
void, ceea ce înseamnă că îl puteţi atribui oricărui tip de pointer.

Prototipul funcţiei free este:

void free(void *p);

Funcţia free returnează în sistem memoria alocată anterior pointerului p. Este


esenţial să nu se apeleze free cu un argument impropriu; deoarece acest fapt
poate aduce prejudicii gestiunii de către sistem a memoriei libere. Exemplul de
mai jos arată modul efectiv de utilizare a funcţiilor malloc şi free, precum şi

58
modul de utilizare a funcţiei memcpy, în situaţia în care se doreşte copierea
conţinutului memoriei de la o anumită adresă la altă adresă. De remarcat,
totodată, faptul că bibliotecile C au numeroase alte funcţii pentru a satisface
cerinţele de alocare/ manipulare dinamică a memoriei în programele utilizator.

#include<stdio.h>
#include<conio.h>
#include<stdlib.h>
#include<string.h>
void main()
{
char c[20];
int *p;

//pointer generic
void *x;
int nr=10000;
p=&nr;
clrscr();
gotoxy(20,10);

//Alocare dinamica memorie incepand


//de la o adresa returnata in x
x=(int*) malloc(sizeof(int));

//Copiere 2 octeti din zona referita de p in zona referita de x


memcpy(x,p,sizeof(int));
printf("%d",*(int*)x);
getch();

//Utilizarea pointerului x pentru a referi un sir de caractere


gotoxy(20,11);
strcpy(c,"Sir de caractere…");
x=(char*) malloc(21); //alocare cu conversie de tip
memcpy(x,c,21);
puts((char*)x);
getch();
free(x);
}

Programatorul Pascal recunoaşte în suportul oferit de malloc() şi free() soluţia


pentru problema alocării de memorie la nivel de octet, ceea ce înseamnă
responsabilitatea programatorului de a realiza conversiile necesare în procesul
de utilizare a memoriei astfel alocate, ca în programul de mai sus. C++ are
propria ofertă pentru alocarea dinamică de memorie din perspectivă structurată.

59
7 Structuri
O structură este, pragmatic vorbind, un grup de variabile reunite sub acelaşi
nume, ceea ce permite un mod convenabil de manipulare a unor date care au
afinităţi semantice între ele. O declarare de structură formează un şablon care
poate fi folosit pentru a crea structuri efective. În C, variabilele care fac parte din
structură se numesc membri ai structurii. Uzual, membrii structurii se mai
numesc şi elemente sau câmpuri. Aşa cum vom vedea mai jos, o structură
poate încapsula şi metode de prelucrare a datelor, dacă programatorul doreşte
să abstractizeze tipuri de date cu suport struct.
Sintaxa generală pentru declararea unei structuri este:

struct <Nume_generic>
{
<Tip> <Nume_membru_1>;
<Tip> <Nume_membru_2>;
:
<Tip> <Nume_membru_n>;
:
<Tip returnat> <Nume functie>(<Lista de parametri>);

} <Variabila_1>,[…<Variabila_k>];
În cazul în care în definiţia structurii este încapsulată şi o metodă atunci la
implementare se foloseşte sintaxa:

<Tip returnat> <Structură>::<Nume functie>(<Lista de parametri>);


{
//Corp funcţie
};

Pentru mai multă claritate se poate urmări exemplul de mai jos.

#include<stdio.h>
#include<conio.h>

typedef struct
{
char matricol[6];
char nume[30];
float media;
void setstud(char matr[],char num[],float med);
} TStud;
void TStud::setstud(char matr[],char num[],float med)
{
strcpy(matricol,matr);
strcpy(nume,num);
media=med;

60
};
void main()
{
clrscr();
TStud stud;
stud.setstud("12","Mihai Guramare",10);
printf("Matricol :%s\n",stud.matricol);
printf("Nume :%s\n",stud.nume);
printf("Media :%f",stud.media);
getch();
}

După cum se vede deja, numele variabilei structurate, urmat de un punct şi


numele membrului permite adresarea acelui membru. Adică, în general vorbind:

<Variabila_structurată> . <Nume_membru>

În codul de mai jos se arată modul concret de declarare a unei structuri şi de


asociere a acestei declaraţii cu un nume de tip prin intermediul cuvântului cheie
typedef.

#include<conio.h>
#include<iostream.h>
#include<string.h>
void main()
{
typedef struct TPers{
char nume[30];
float salariu;
} TPers;
TPers pers;
strcpy(pers.nume,"Test typedef + struct….");
pers.salariu=1200000;
clrscr();
cout<<pers.nume<<"\r\n"<<pers.salariu;
getch();
}

În mod evident, structurile pot fi asociate şi cu pointerii


prin declaraţii asemănătoare celor din codul alternativ de
mai jos.

#include<conio.h>
#include<iostream.h>
#include<string.h>
void main()
{

61
typedef struct TPers{
char nume[30];
float salariu;
} TPers;
TPers *pers;

…alocare memorie pentru pers

strcpy(pers->nume,"Test relatie structura - pointer");


pers->salariu=1200000;
clrscr();
cout<<pers->nume<<"\r\n"<<pers->salariu;
getch();
}

O altă întrebuinţare a structurilor o reprezintă posibilitatea de a defini câmpuri de


biţi. Câmpurile de biţi sunt mecanisme cu ajutorul cărora programatorul poate
avea acces la conţinutul variabilelor până la nivel de bit.

Sintaxa pentru definirea unui camp de biti este:

typedef struct
{
tip1 nume1:lungime1;
tip2 nume2:lungime2;
...
tipn numen:lungimen;
} TOctet;

Tipurile pot lua una din valorile int, unsigned, signed, pentru majoritatea
compilatoarelor.

Câmpul de biţi cu lungimea 1 este obligatoriu unsigned. A se vedea şi


exemplul de mai jos.

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

typedef struct
{
unsigned bit :1;
unsigned :7;
unsigned :7;
unsigned bits:1;
} TOctet;
TOctet Octet;

62
int nr=1;
int *pointer;

void main()
{
int i;
pointer=&nr;
memcpy(&Octet,pointer,sizeof(int));
clrscr();
printf("Bitul 0 : %i\n",Octet.bit);
printf("Bitul de semn: %i\n",Octet.bits);
getch();
}

63
8 Uniuni
O uniune este o locaţie de memorie care este partajată în momente diferite între
două sau mai multe variabile diferite.
Sintaxa generala şi modul de utilizare pot fi deduse din exemplul de mai jos.

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

typedef union
{
int codsal;
float salar;
} TUSal;

void main()
{
clrscr();
TUSal vunion;
vunion.codsal=10;
printf("Cod confidential salariu:%i\n",vunion.codsal);
vunion.salar=1000.50;
printf("Salariu :%f",vunion.salar);
getch();
}

În acest exemplu variabilele codsal şi salar, de tipuri diferire, partajează


aceeaşi locaţie de memorie, dar, important de ştiut, în momente diferire ale
execuţiei unui programului, după cum se observă, de altfel şi din exemplu.

Încheiem aici partea intitulată BAZELE C++. LIMBAJUL C, cu menţiunea că


există nenumărate alte aspecte ale programării în C care necesită timp şi
răbdare pentru a înţelege toate consecinţele stilului C de programare, înţelegere
folositoare şi în abordarea C++. Dintre aceste aspecte semnalez: fluxurile C,
şabloanele C, suprascrierea funcţiilor, etc. Pentru toate acestea există, însă
suport C++ mult mai adecvat şi mai comod de multe ori pentru programarea cu
adevărat în spirit obiect orientat.

64
II Programarea C++

65
1 Introducere
Programarea orientată pe obiecte (POO) este expresia, în materie de codificare
a proiectelor, a paradigmei care presupune modelarea orientată pe obiecte a
sistemelor soft.

POO este o paradigmă care câştigă tot mai mulţi aderenţi datorită calităţilor
pe care le au produsele şi sistemele soft realizate în spiritul conceptelor şi
principiilor promovate de aceasta. Rezultantă a unor direcţii diverse de
cercetare şi experimentare (programare structurată, programare modulară,
programare orientată pe structuri abstracte, reprezentarea cunoştinţelor în
sisteme expert, etc.) POO, aplicată corect poate rezolva, parţial sau
integral, multe din problemele obsedante ale ingineriei softului, în genere:
reutilizarea codului, extinderea/modificarea cu minim de efort a sistemelor
soft, ascunderea detaliilor de implementare faţă de anumite categorii de
utilizatori ai sistemelor soft. În acest mod platforma POO poate ajuta
programatorii şi companiile de soft să realizeze produse şi sisteme soft
performante, în timp util şi la un preţ scăzut.

1. 1 Concepte POO
Orice demers de modelare orientată pe obiecte apelează la o serie de concepte
specifice paradigmei POO.
Astfel, pentru modelarea unui sistem soft se operează frecvent cu:
1. Concepte ale teoriei generale a sistemelor (sistem, subsistem,
descompunere, agregare, structură, etc.)
2. Concepte care provin din arsenalul conceptual al modelării în genere
a sistemelor şi produselor soft (modul, modularizare, interfaţă, tip de dată,
structură de date, ascunderea informaţiei, etc.).
Tuturor acestora li se adaugă principiile cu ajutorul cărora aceste concepte devin
operaţionale.
Paradigma POO a intrat în competiţia pentru modernizarea şi îmbunătăţirea
reală a procesului de realizare a unui sistem soft cu un set propriu de concepte şi
principii.
Prezentăm mai întâi conceptele cheie ale paradigmei POO.

Conceptul de clasă, prin care se desemnează o colecţie de obiecte (de natură


materială sau spirituală) care au în comun faptul că pot fi caracterizate similar
din punct de vedere informaţional şi comportamental.

Este evident faptul că identificarea unei clase este în mod normal, rezultatul unui
demers cognitiv care presupune caracterizarea unui obiect prin însuşirile lui
(informaţionale şi comportamentale) care îi definesc apartenenţa la o anumită
clasă de obiecte. Aşadar, conceptul de clasă adună laolaltă datele şi metodele
de prelucrare a acestora.

În esenţă definirea unei clase se bazează pe analiza, clasificarea şi


abstractizarea însuşirilor obiectelor de un anumit tip.

66
Acesta este un exerciţiu de îndemânare a cărui rezolvare o poate învăţa oricine
care încearcă, are răbdare cu el însuşi şi citeşte cum procedează iniţiaţii când
întâlnesc astfel de exerciţii. Aşa cum va reieşi din implementarea conceptului de
clasă în C++, de exemplu, conceptul de clasă este o abstracţie care
pregăteşte un anumit tip de background pentru descrierea soluţiei unei
probleme date. Fără a intra prea mult în problemă să amintim, totuşi, că
modelând obiect orientat soluţiile problemelor noastre ne asigurăm o serie
de avantaje imediate şi de perspectivă în ceea ce priveşte gestiunea relaţiei
dintre domeniul problemei şi domeniul soluţiei în ingineria sistemelor soft.

Conceptul de obiect (instanţă a unei clase) este deja un concept cu valoare


operaţională, prin care se desemnează un obiect concret al unei clase
definitoare, caracterizat prin valori specifice ale atributelor informaţionale.
Dacă definirea unei clase pune, în primul rând, probleme de natură conceptuală,
manipularea unui obiect se bazează pe abilităţile specifice limbajului referitoare
la alocarea de memorie pentru obiect (static sau dinamic), controlul stării
obiectului (= valorile atributelor informaţionale) cu ajutorul metodelor proprii, etc.
Proprietăţile unui obiect sunt, în mare parte, proprietăţile unei clase. Calitatea
unui obiect depinde de calitatea clasei definitoare. Există două perspective din
care putem aprecia calităţile unui obiect: perspectiva utilizator de obiect şi
perspectiva specificator de clasă definitoare. Utilizatorul de obiect este
interesat, practic, de calitatea interfeţei obiectului, care permite o apreciere şi
asupra potenţialului informaţional şi comportamental al obiectului respectiv.
Munca unui specificator de clasă definitoare poate fi apreciată după potenţialul
informaţional şi comportamental al clasei, accesibil prin intermediul unei interfeţe
care implementează principiul “cine doreşte date despre starea unui obiect al
unei clase trebuie să se mulţumească cu deschiderea oferită în acest sens de
interfaţa obiectului în cauză”. Trădarea acestui principiu este o dovadă de
neprofesionalism în abordarea obiect orientată a unei probleme.
Aşa se ajunge că, pentru un programator, un obiect contează prin: identitate,
interfaţă(partajată în comun cu alte obiecte de acelaşi tip prin raportare la o
clasă definitoare) şi stare.

Identitatea unui obiect desemnează procedeul prin care se asigură, în sistem,


unicitatea unui obiect. Cele mai multe limbaje rezolvă această problemă prin
asocierea instanţei unei clase (=obiect) cu o anumită adresă de memorie.

Interfaţa unui obiect desemnează mulţimea metodelor unei clase cu ajutorul


cărora obiectele clase comunică cu ambianţa în care acestea există.
Comunicarea între obiecte se realizează, dogmatic vorbind, prin mesaje.

Conceptul de stare a unui obiect prin care se desemnează valorile atributelor


informaţionale ale obiectului.
Conceptul de mesaj prin care se înţelege un semnal emis de către un obiect
emiţător către un obiect receptor, din iniţiativa obiectului emiţător.

67
Este evident faptul că obiectul care emite mesajul trebuie să cunoască protocolul
de comunicaţie al obiectului receptor.
De asemenea, se subânţelege faptul că mesajul trimis de obiectul emiţător va
provoca o reacţie(= un răspuns) din partea obiectului receptor.

Conceptul de metodă prin intermediul căruia se dă o expresie procedeelor care


definesc comportamentul şi, direct sau indirect, protocolul de comunicaţie al unei
clase.
Pentru mai multă exactitate, este bine să se înţeleagă faptul că "metoda" este o
denumire generică pentru un procedeu de prelucrare care face parte din
protocolul de comunicare al unei clase. Metoda poate fi implementată în C, de
exemplu, ca funcţie, constructor sau destructor. Asupra semnificaţiei noţiunilor de
constructor şi destructor vom mai reveni .

1. 2 Principiile POO
Noutatea POO, ca paradigmă este ilustrată şi de principiile pe care le
promovează pentru a completa potenţialul oferit de concepte. Promovarea
sistematică a principiilor pe care le prezentăm în continuare promite, dar
nu garantează, realizarea unor produse, sisteme sau platforme soft
remarcabile din punct de vedere al performanţelor şi al efortului de
întreţinere.

1.2.1 Principiul încapsulării


Înţelegerea acestui principiu presupune două nivele de abordare.

@ Ca metodă de concepţie, încapsularea se referă la capacitatea de a separa


aspectele externe ale unui obiect (interfaţa), accesibile altor obiecte, de
aspectele implementaţionale, interne obiectului, care sunt ascunse faţă de
celelalte obiecte. Utilizatorul unui obiect poate accesa doar anumite metode ale
acestuia, numite publice, în timp ce atributele şi celelalte metode îi rămân
inaccesibile (acestea se numesc private).
Încapsularea este foarte importantă atunci când dorim să schimbăm
implementarea anumitor metode (cu scopul de a optimiza un algoritm sau de a
elimina posibile erori).
Încapsularea ne va împiedica să modificăm toate caracteristicile obiectului iar
aplicaţiile care utilizează obiectul nu vor avea de suferit deoarece protocolul de
comunicaţie al obiectului moştenit de la interfaţa clasei (rezultatul încapsulării
ca metodă de concepţie) nu s-a schimbat.

@Ca implementare, la nivelul unui limbaj de programare, încapsularea este


asigurată de exigenţele sintactice specifice.

1.2.2 Principiul moştenirii

68
Acest principiu este de mare utilitate în transmiterea sistematică a similarităţilor
de la o clasă la alta. Principiul poate fi aplicat cu succes doar în conjuncţie cu
operatorii conceptuali complementari:generalizarea şi specializarea.
Generalizarea apare în relaţia dintre o clasă şi una sau mai multe versiuni mai
rafinate ale acesteia. Este, în limbajul de specialitate, relaţia dintre clasa de
bază (sau superclasă) şi clasele derivate (sau subclase).
Atributele şi operaţiile comune sunt grupate în superclasă şi se spune că sunt
moştenite de subclase.
Se poate spune că moştenirea se referă la mecanismul de a transmite atribute şi
operaţii de-a lungul unei relaţii de generalizare. Evident, simpla moştenire a
proprietăţilor unei clase nu rezolvă nici o problemă din punct de vedere
conceptual. Utilitatea principiului moştenirii este reliefată în condiţiile aplicării
operatorului de specializare, prin care subclasa rafinează (specializează)
superclasa.
Implementarea moştenirii nu creează probleme deosebite de înţelegere şi
utilizare.

1.2.3 Principiul polimorfismului


Prin aplicarea principiului moştenirii se creează condiţii pentru specializarea unei
superclase. Specializarea poate fi realizată prin adăugarea de noi operaţii celor
moştenite sau prin redefinirea unora dintre operaţiile moştenite. Specializarea
orientată pe redefinirea operaţiilor se află la baza implementării principiului
polimorfismului.
Ca rezultat final aplicarea principiului polimorfismului creează posibilitatea ca un
mesaj (în structura căruia intervine un nume de operaţie) să genereze
răspunsuri diferite, în funcţie de contextul în care este formulat mesajul.
O astfel de posibilitate este cu atât mai valoroasă cu cât gestiunea
contextului în care se formulează mesajul este asumată de către sistem.
Ca mecanism, aplicarea principiului presupune instituirea unui protocol pentru
evidenţa legăturilor dintre clase şi operaţiile aferente, susceptibile de redefinire.

69
2 Implementarea C++ a POO
2.1 Definirea claselor
Pentru a putea lucra cu obiecte în C++ trebuie, în prealabil, să definim forma lor
generală folosind în acest scop cuvântul cheie class. O clasă este similară,
sintactic vorbind, cu o structură. Forma generală a unei declaraţii de clasă care
nu moşteneşte nici o altă clasă este următoarea:

class <Nume_clasa> [:<Lista_claselor_de_bază>]


{
<Date şi funcţii particulare>
<Specificator de acces>:
<Date si functii>
<Specificator de acces>:
<Date si functii>
:
:
<Specificator de acces>:
<Date si functii>
} [<Lista de obiecte>];

<Lista de obiecte> este opţională. Dacă există, ea declară obiecte din


acea clasă.
<Specificator de acces> este unul din cuvintele cheie:
-public
-private
-protected.
Implicit, funcţiile şi datele declarate într-o clasă sunt proprii acelei clase, doar
membrii săi având acces la ele.
<Lista_claselor_de_bază> indică, opţional, clasele de la care se pot
moşteni atribute informaţionale şi comportamentale.

 Pentru datele şi funcţiile care fac parte din definiţia unei clase se
obişnuiesc şi denumirile de variabile membre, respectiv, funcţii membre
sau, pur şi simplu, membri.

Folosind specificatorul de acces public, permitem funcţiilor sau datelor membre


să fie accesibile altor secţiuni ale programului nostru. Odată utilizat un
specificator, efectul său durează până când se întâlneşte alt specificator de
acces sau se ajunge la sfârşitul declaraţiei clasei. Pentru a reveni la modul privat
de declarare a membrilor, se foloseşte specificatorul de acces private.
Specificatorul protected are implicaţii asupra vizibilităţii membrilor unei clase în
cazul în care se face şi moştenire. Prezentăm, în continuare, un exemplu de
definiţie de clasă, care încapsulează date şi operaţii minimale referitoare la un
salariat al unei firme( nume, matricol, salariu).

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

// Definitie clasa fara declarare de variabile obiect.


class Persoana
{
public:
char nume[30];
int matricol;
float salariu;
void setfields(char n[30], int m, float s);
void afis();
};

// Implementare functie setfields


void Persoana:: setfields(char *n, int m, float s)
{
strcpy(nume,n);
matricol=m;
salariu=s;
}

// Implementare functie afis


void Persoana:: afis()
{
clrscr();
cout<<nume<<"\n"<<salariu;
getch();
}

//Implementare functie principala


void main()
{

// Declarare variabila obiect


Persoana pers;

pers.setfields("Radu Vasile",1,10000);
pers.afis();
}

În exemplul prezentat am folosit specificatorul de acces public pentru a permite


funcţiei principale, care a declarat variabila obiect pers, accesul la membrii clasei
definitoare a variabilei pers. În programarea obiect orientată adevărată în C++,
efectul de ascundere a membrilor unei clase este intens utilizat, interzicând

71
anumitor categorii de utilizatori accesul la membrii clasei, definind pentru
comunicaţia cu alte clase şi categorii de utilizatori ceea ce se numeşte interfaţa
clasei.
Evident, se poate face o oarecare analogie între clasă şi structură; analogia
cade, însă, în momentul în care se pune problema manevrabilităţii instanţelor
celor două tipuri de concepte sau atunci când este vorba de moştenire şi
polimorfism. Un exemplu ceva mai apropiat de realitatea preocupărilor unui
programator C++ poate fi programul care implementează o stivă de întregi,
prezentat în continuare.

#include <iostream.h>
#include <conio.h>
#define SIZE 100

// Definirea clasei stack


class stack
{
int st[SIZE];
int top;
public:
void init();
void push(int i);
int pop();
};

//Implementare functie init


void stack::init()
{
top=0;
}

//Implementare functie push


void stack::push(int i)
{
if (top==SIZE)
{
gotoxy(20,24);
cout<<"Stiva este plina!!!";
getch();
return;
};
st[top]=i;
top++;
}

//Implementare functie pop

72
int stack::pop()
{
if(top==0)
{
gotoxy(20,24);
cout<<"Depasire inferioara stiva!!!";
getch();
return 0;
};
top--;
return st[top];
}

//Functia principala
void main()
{
int i;
stack st1,st2;
st1.init();
st2.init();
for (i=0;i<=9;i++)
{
st1.push(i);
st2.push(9-i);
}
clrscr();
for (i=0;i<=9;i++)
{
gotoxy(35,wherey()+1);
cout<<st1.pop()<<"*****";
cout<<st2.pop()<<endl;
}
getch();
}

2.2 Clase derivate. Constructori. Destructori. Obiecte C++


Modelarea obiect orientată a soluţiei unei probleme de oarecare complexitate
pune, inevitabil şi problema transmiterii similarităţilor între clase care formează o
ierarhie sau o reţea. Suportul C++ pentru rezolvarea acestei probleme îl
reprezintă posibilitatea ca o clasă să fie derivată din una sau mai multe clase,
ceea ce înseamnă posibilitatea de a abstractiza soluţia cu scopul de a reutiliza
cod şi de a adapta codul uşor la cerinţe noi dacă este cazul. Am văzut mai sus
sintaxa pentru moştenire. Să adăugăm, în plus la cele spuse mai sus, că în
<Lista_claselor_de_bază> clasele definitoare apar precedate, eventual, de un
modificator de protecţie şi sunt separate între ele prin virgulă.

73
Modificatorii de protecţie utilizaţi în <Lista_claselor_de_bază> definesc protecţia
în clasa derivată a elementelor moştenite. Prezentăm sub formă tabelară accesul
la elementele moştenite de clasa derivată în funcţie de protecţia fiecărui element
şi de modificatorul de protecţie asociat în <Lista_claselor_de_bază>.

Tipul de acces al Modificatorul de protecţie Accesul în clasa


elementului în clasa asociat clasei de bază la derivată la element
de bază definirea clasei
private private interzis
protected private private
public private private
private public interzis
protected public protected
public public public
Tabelul 14. Problematica accesului la membrii unei clase derivate

Concluzionând, dacă în clasa de bază accesul la element este private atunci în


clasa derivată accesul este interzis. În schimb, clasa derivată are acces la
elementele clasei de bază aflate sub incidenţa accesului protected/public.
Se mai poate observa, totodată, că dacă la definirea clasei derivate se utilizează
modificatorul de protecţie private, atunci elementele protejate în clasa de bază
prin protected sau public devin protejate private în clasa derivată; deci
inaccesibile unor clase care s-ar deriva eventual din clasa derivată.

Exemplu

#include <iostream.h>
class CB
{
protected:
int i,j;
//Pentru un program care utilizează această clasă
//sau pentru altă clasă care nu se află în relaţie
//de derivare cu CB I,j sunt private din punct de
//vedere al accesului. i,j sunt accesibile unei clase derivate.
public:
void setij(int a, int b) {i=a;j=b}
void dispij() { cout<<i<<” “<<j<<endl;}
}

class CD:public CB
{
int k;
public:
void setk(){k=I*j;}
void dispk(){cout<<k<<endl;}

74
}

void main()
{
CD ob;
ob.setij(2,3); //OK!
ob.dispij(); //OK
ob.setk();
ob.dispk ();
}

Moştenirea protected a clasei de bază


Este posibil să se moştenească o clasă de bază ca protected. Când se
procedează astfel, toţi membrii public şi protected ai clasei de bază devin
membri protected ai clasei derivate.

Exemplu

#include <iostream.h>
class CB
{
protected:
int i,j;
//Pentru un program care utilizează această clasă
//sau pentru altă clasă care nu se află în relaţie
//de derivare cu CB i, j sunt private din punct de
//vedere al accesului. i,j sunt accesibile unei clase derivate.
public:
void setij(int a, int b) {i=a;j=b}
void dispij() { cout<<i<<” “<<j<<endl;}
}

class CD:protected CB
{
int k;
public:
void setk(){setij(10,20);k=I*j;}
void disptot(){cout<<k<<” “;dispij();}
}

void main()
{
CD ob;
ob.setij(2,3); //ilegal!
ob.setk(); //OK!, membru public în CD

75
ob.setk();
ob.disptot (); //OK!
ob.dispij(); //ilegal! Membru protected în CD
}

Constructori şi destructori
Este un fapt obişnuit necesitatea ca unele elemente ale unui obiect să fie
iniţializate. Pentru a degreva programatorul de o asemenea sarcină de rutină,
compilatorul C++ generează cod care permite obiectelor să se iniţializeze
singure. Această iniţializare automată este efectuată prin intermediul unei funcţii
membru speciale a clasei definitoare numită constructor.

Constructorul este o funcţie care are acelasi nume cu clasa.


Un constructor al unui obiect este apelat automat la crearea obiectului.
Un constructor al unui obiect este apelat o singură dată pentru obiecte
globale sau pentru cele locale de tip static.
Constructorul este o funcţie fără tip ceea ce nu reclamă, totuşi cuvântul
cheie void în locul tipului.
Pentru obiecte locale constructorul este apelat de fiecare dată când este
întâlnită declararea acestuia.

Complementul constructorului este destructorul. De multe ori un obiect trebuie


să efectueze anumite acţiuni când este distrus.
Este evident faptul că obiectele locale sunt distruse la părăsirea blocului în care
apar iar obiectele globale la terminarea programului.
Când este distrus un obiect, este apelat destructorul clasei definitoare.

Destructorul are acelaşi nume cu constructorul, dar precedat de un


caracter ~.
O clasă are un singur destructor şi acest destructor nu poate avea
parametri formali.
Atât constructorul cât şi destructorul, în C++ nu pot să returneze valori.

De semnalat faptul că în situaţia în care programatorul nu specifică un


constructor explicit la definirea unei clase, la crearea unei instanţe a clasei se
foloseşte constructorul implicit ataşat de compilator fiecărei clase.
Constructorul implicit nu are parametri formali, ceea ce are drept consecinţă
faptul că nu este permisă iniţializarea la declarare a datelor membre ale
obiectelor.
De asemenea, constructorul implicit nu este generat în cazul în care clasa are
ataşat un alt constructor fără parametri.
Programatorul poate înzestra clasa cu o proprie funcţie constructor. În acest
scop trebuie ţinut cont de faptul că o metodă constructor are întotdeauna numele
clasei din care face parte.
Programatorul poate înzestra clasa şi cu parametri formali ceea ce permite şi o
formulă elegantă de iniţializare a datelor membre ale obiectelor clasei respective.

76
 Foarte important mi se pare să semnalez şi existenţa unui constructor
special, numit constructor de copiere. Acesta are rolul de a atribui datele unui
obiect altuia. Mai mult, poate fi apelat chiar la definirea obiectelor. Dacă
programatorul nu defineşte propriul constructor de copiere, compilatorul adaugă
automat un astfel de constructor. În exemplul de mai jos se pot urmări elemente
de sintaxă şi semantică care trebuie cunoscute când se lucrează cu constructori
în diferite ipostaze.

#include<stdio.h>
#include<conio.h>

//Definire clasă
class intreg
{
public:
int a;
//Constructor parametrizat
intreg(int v)
{
printf("Constructor parametrizat\n");
getch();
a=v;
}
//Constructor de copiere
intreg (intreg& v)
{
a=v.a;
printf("Constructor de copiere\n");
getch();
}
void dispa()
{
printf("a=%i\n",a);
getch();
};
};

void main()
{
clrscr();
//Declarare obiect static cu iniţializare;
intreg x=100;
x.dispa();
//Declarare obiect static cu iniţializare prin copiere
intreg y=x;

77
y.dispa();
}

Evident, o clasă poate avea mai mulţi constructori, ceea ce este tot în beneficiul
programatorilor.

Crearea obiectelor în C++


Obiectele pot fi create static sau dinamic.
Aşa cum s-a văzut şi în exemplul de mai sus sintaxa pentru varianta statică este:

<Clasa> <Obiect>[(<Lista de valori>)];


sau
<Clasa> <Obiect>[=<Valoare>];
Sintaxa ne arată că odată cu crearea instanţei se poate face şi iniţializarea
datelor membre ale obiectului cu ajutorul constructorilor parametrizaţi.

Pentru a înţelege alocarea dinamică a memoriei pentru obiecte trebuie să facem


o scurtă prezentare a problematicii alocării dinamice structurate a memoriei în
C++.

Operatori de alocare dinamică a memoriei în C++


În C, alocarea dinamică a memoriei este realizată cu ajutorul funcţiior malloc() şi
free().
Din motive de compatibilitate şi nu numai, aceste funcţii sunt valabile şi în C++.
Totodată, C++ are un sistem alternativ de alocare dinamică bazat pe operatorii
new şi delete.
Sintaxa generală pentru new şi delete este:

<Pointer>=new <Tip>;
delete <Pointer> ;

<Pointer> este o variabilă pointer, compatibilă ca tip cu <Tip>. Aşadar, <Pointer>


poate păstra adresa către zona de memorie în care încap date având tipul <Tip>.
De subliniat că operatorul delete trebuie folosit doar cu un pointer valid, alocat
deja prin utilizarea operatorului new. În caz contrar, rezultatele sunt imprevizibile.
Faţă de malloc()şi free(), operatorii new şi delete prezintă câteva avantaje:
@ new alocă automat memorie suficientă pentru a păstra obiectele de
tipul specificat. Nu mai este necesară folosirea operatorului sizeof.
@ new returnează automat un pointer de tipul specificat. Nu mai este
necesar să folosim modelatorii de tip ca în cazul funcţiei malloc().
@ Atât new cât şi delete pot fi supraîncărcaţi, ceea ce vă permite crearea
unui sistem propriu de alocare dinamică a memoriei. Despre supraîncărcare
vom discuta în paragrafele 2.4 şi 2.7 .
În plus, operatorul new mai are şi alte capabilităţi:
@Permite iniţializarea memoriei alocate unui tip de bază cu o valoare dată,
utilizând sintaxa:

78
<Pointer>=new <Tip> (<Valoare_iniţială>);
ca în exemplul:

int *p;
p=new int(10);

@Operatorul new permite alocarea de memorie pentru matrici, utilizând


sintaxa:

<Pointer>=new <Tip> [Marime];

Memoria alocată unei matrici de operatorul new se eliberează de către delete


apelat cu sintaxa:

delete [ ] <Pointer>

 Matricile nu pot fi iniţializate în timpul alocării.

Suntem în măsură să prezentăm soluţia C++ pentru problema alocării dinamice a


memoriei pentru obiecte.
Obiectelor li se poate aloca memorie dinamic folosind operatorul new. Când
procedaţi astfel se creează un obiect şi se returnează un pointer către el.
Obiectul creat dinamic se comportă ca oricare altul. Când este creat este apelată
şi funcţia constructor. Când este eliberat se execută şi funcţia destructor.
Sintaxa generală:

<Pointer_la_obiect>=new <Clasa>;

Dacă <Clasa> are constructor parametrizat atunci se poate folosi sintaxa:

<Pointer_la_obiect>=new <Clasa>(<Lista_valori>);

pentru a realiza şi iniţializarea obiectului.

În sfârşit, în cazul matricilor de obiecte, cărora li se poate aloca memorie


dinamic, trebuie să ne asigurăm că în cazul în care există funcţii constructor, una
va fi fără parametri. În caz contrar, compilatorul dă eroare de sintaxă.

Modalitatea concretă de utilizare a conceptului de constructor, precum şi a


conceptului complementar, destructorul, poate fi desprinsă şi din exemplul de
mai jos care reia modelarea obiect orientată a unei stive de întregi.

#include <iostream.h>
#include <conio.h>
#define SIZE 100

79
// Definirea clasei stack
class stack
{
int st[SIZE];
int top;
public:
stack(); //constructor
~stack(); //destructor
void push(int i);
int pop();
};

//Implementare constructor
stack::stack()
{
top=0;
gotoxy(20,24);
cout<<"Stiva este initializata";
getch();
clrscr();
}
stack::~stack()
{
gotoxy(20,24);
cout<<"Stiva este distrusa!!";
getch();
clrscr();
}

//Implementare functie push


void stack::push(int i)
{
if (top==SIZE)
{
gotoxy(20,24);
cout<<"Stiva este plina!!!";
getch();
return;
};
st[top]=i;
top++;
}

//Implementare functie pop


int stack::pop()

80
{
if(top==0)
{
gotoxy(20,24);
cout<<"Depasire inferioara stiva!!!";
getch();
return 0;
};
top--;
return st[top];
}
//Functia principala
void main()
{
int i;
stack st1,st2;
for (i=0;i<=9;i++)
{
st1.push(i);
st2.push(9-i);
}
clrscr();
for (i=0;i<=9;i++)
{
gotoxy(35,wherey()+1);
cout<<st1.pop()<<"*****";
cout<<st2.pop()<<endl;
}
getch();
}

2.3 Funcţii virtuale şi polimorfism


C++ asigură suport pentru polimorfism atât în timpul compilării cât şi pe timpul
execuţiei unui program. Polimorfismul în timpul compilării este legat de
posibilitatea de a supraîncărca funcţiile şi operatorii, problemă pe care o vom
discuta în paragrafele 2.4 şi 2.7.
Polimorfismul în timpul execuţiei este obţinut combinând principiile moştenirii
cu mecanismul funcţiilor virtuale.

Funcţii virtuale
O funcţie virtuală este o funcţie declarată virtual în clasa de bază şi redefinită
într-un lanţ de derivare asociat respectivei clase de bază. O funcţie virtuală
defineşte o clasă generală de acţiuni. O redefinire a ei introduce o metodă
specifică. În esenţă, o funcţie virtuală declarată în clasa de bază acţionează ca
un substitut pentru păstrarea elementelor care specifică o clasă generală de
acţiuni, stabilind elementele de interfaţă. Redefinirea unei funcţii virtuale într-o

81
clasă derivată oferă operaţiile efective pe care le execută funcţia. Utilizate static,
funcţiile virtuale se comportă ca oricare altă funcţie membru a clasei .
Capabilităţile funcţiilor virtuale ies în evidenţă atunci când sunt apelate în context
dinamic.

Exemplu
#include <iostream.h>
#include <conio.h>
//Clasa de baza care are doua functii membri publici
//o functie ordinara
//cealalta functie virtuala
class CB
{
public:
void f()
{
cout<<"CB::f()"<<endl;
getch();
}
virtual void g() //funcţie virtuală
{
cout<<"CB::g()"<<endl;
getch();
}
};

//Clasa derivata din clasa de baza


class CD:public CB
{
public:
void f()
{
cout<<"CD::f()"<<endl;
getch();
}
virtual void g()
{
cout<<"CD::g()"<<endl;
getch();
}
};

//Functie care permite utilizarea polimorfismului


//Parametrul functiei este un pointer la un obiect de tip CB
//De retinut ca un parametru de tip CD este compatibil
//cu tipul lui p

82
void ExecPolim(CB *p);

void main()
{
CB *pb=new CB;
CD *pd=new CD;
clrscr();
pb->f();
pb->g();
pd->f();
pd->g();
clrscr();
cout<<"Apel polimorfic in context CB"<<endl;
ExecPolim(pb);
cout<<"Apel polimorfic in context CD"<<endl;
ExecPolim(pd);
delete pb;
delete pd;
};

void ExecPolim(CB *p)


{
p->f();
p->g();
}

La prima vedere redefinirea unei funcţii virtuale într-o clasă derivată pare similară
cu supraîncărcarea unei funcţii. Nu este aşa, deoarece există mai multe
diferenţe:
@Prototipul pentru o funcţie virtuală redefinită trebuie să coincidă cu
prototipul specificat în clasa de bază.
@Funcţiile virtuale nu pot să fie membri de tip static ai clasei din care fac
parte şi nu pot fi nici friend
Funcţiile obişnuite suportă supraîncărcarea; funcţiile virtuale suportă
suprascrierea.
În cele ce urmează facem o serie de precizări relativ la clauza virtual.

Clauza virtual este moştenită


Când o funcţie virtuală este moştenită se moşteneşte şi natura sa virtuală. Astfel
că, în situaţia în care o clasă derivată a moştenit o funcţie virtuală şi este folosită
drept clasă de bază pentru o altă clasă derivată, funcţia virtuală poate fi în
continuare suprascrisă. Altfel spus, o funcţie rămâne virtuală indiferent de câte
ori este moştenită.

Funcţiile virtuale sunt ierarhizate

83
Deoarece în C++ moştenirea este ierarhizată este normal ca funcţiile virtuale să
fie, de asemenea, ierarhizate. Acestea înseamnă că, atunci când o clasă
derivată nu suprascrie o funcţie virtuală, este utilizată prima redefinire găsită în
ordinea inversă derivării.

Să mai spunem că există situaţii în care programatorul vrea să se


asigure că toate clasele derivate suprascriu o funcţie virtuală sau
programatorul defineşte o clasă de bază astfel încât să nu permită definirea
unei funcţii virtuale în această clasă. Pentru aceste două situaţii există
funcţiile virtuale pure.

Sintaxa unei funcţii virtuale pure este:

virtual <Tip> <Nume_funcţie> [(<Lista)_de_parametri>)]=0;

O clasă care conţine cel puţin o funcţie virtuală pură se numeşte clasă
abstractă. O clasă abstractă nu poate fi utilizată pentru crearea de obiecte dar
poate fi utilizată pentru a crea pointeri şi referinţe spre astfel de clase permiţând
astfel manifestările polimorfice în timpul execuţiei.

2.4 Funcţii supraîncărcate


O modalitate C++ de realizare a polimorfismului o reprezintă supraîncărcarea
funcţiilor (overloading). În C++, două sau mai multe funcţii pot să aibă acelaşi
nume cât timp declaraţiile lor de parametri sunt diferite. În această situaţie se
spune că funcţiile cu acelaşi nume sunt supraîncărcate iar procesul este
numit supraîncărcarea funcţiilor.
Când supraîncărcaţi o funcţie trebuie să respectaţi următoarele cerinţe:
@Listele de parametri ale diferitelor versiuni ale unei funcţii trebuie să
difere;
@Versiunile pot diferi şi din punct de vedere al tipului returnat; acest lucru
nu este, însă, obligatoriu. Odată specificate versiunile unei funcţii respectând
restricţiile de mai sus, compilatorul este în măsură să aleagă metoda adecvată
unui apel specific.
Avantajele supraîncărcării, nu neapărat în context obiect orientat, pot fi desprinse
şi din analiza exemplului de cod C++ de mai jos.

#include <iostream.h>
#include <conio.h>
//functia abs este definita in trei moduri diferite
int abs(int i);
double abs(double d);
long abs(long l);

void main()
{
clrscr();

84
cout<<abs(-10);
cout<<abs(-10.0);
cout<<abs(-10L);
getch();

}
int abs(int i)
{
gotoxy(20,10);cout<<"Varianta int !!! :";
return i<0 ?-i:i;
}
double abs(double d)
{
gotoxy(20,12);cout<<"Varianta double!!! :";
return d<0 ?-d:d;
}

long abs(long l)
{
gotoxy(20,14);cout<<"Varianta long !!! :";
return l<0 ?-l:l;
}

Acest program implementează şi utilizează trei funcţii cu acelaşi nume (abs())


dar diferite prin proprietăţile listelor de parametri formali. Fiecare returnează un
mesaj specific şi valoarea absolută a argumentului. Numele abs() reprezintă
acţiunea generică care urmează să fie efectuată, compilatorul fiind acela care
alege metoda potrivită tipului de parametru actual asociat cu numele funcţiei
generice. Este uşor de bănuit puternicul impact al supraîncărcării în cazul
programării generice.

2.5 Funcţii inline


Aceste funcţii sunt similare macrocomenzilor adresate preprocesoarelor,
compilatorul înlocuind fiecare apel de funcţie inline cu corpul acesteia. Funcţiile
inline sunt destinate să sprijine implementarea eficientă a tehnicilor OOP în C++.
Deoarece abordarea OOP necesită utilizarea extensivă a funcţiilor membre,
desele apeluri de funcţii ar putea duce la scăderea performanţelor programelor.
Pentru funcţii al căror cod este redus se poate utiliza specificatorul inline pentru a
evita acest inconvenient. Similaritatea macrocomenzi-funcţii inline este doar
aparentă. Spre deosebire de macrocomenzi, compilatorul consideră funcţiile
inline ca funcţii adevărate.
O funcţie membru inline se declară astfel:

inline <Tip><Nume_Funcţie>([<Lista de parametri>]);

2.6 Funcţii prietene

85
Este posibil să se permită unei funcţii care nu este membru al unei clase să aibă
acces la membrii privaţi ai clasei declarând-o ca funcţie prietenă a clasei (friend).
Aşadar, o funcţie prietenă are acces la membrii private şi protected ai clasei
căreia îi este prietenă. Sintaxa pentru declararea unei funcţii prietene este:

friend <Tip> >Nume_funcţie>[(<Lista_parametri>)]


Declararea funcţiei friend se face în interiorul clasei dar implementarea nu este
legată de definirea clasei.
A se vedea şi exemplul de mai jos pentru mai multe detalii.

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

#define liber 0
#define ocupat 1

//Declarare anticipata clasa


class Clasa2;
class Clasa1
{
//<ocupat> daca o metoda a clasei a scris pe ecran
//<liber> in caz contrar
int stare;
public:
void setare_stare(int val);
friend int ecran_liber(Clasa1 a,Clasa2 b);
};

class Clasa2
{
//<ocupat> daca o metoda a clasei a scris pe ecran
//<liber> in caz contrar
int stare;
public:
void setare_stare(int val);
friend int ecran_liber(Clasa1 a,Clasa2 b);
};

void Clasa1::setare_stare(int val)


{
stare=val;
}

void Clasa2::setare_stare(int val)


{
stare=val;
}

86
int ecran_liber(Clasa1 a, Clasa2 b)
{
if (a.stare ||b.stare)
return 0;
else
return 1;
}

void main()
{
Clasa1 x;
Clasa2 y;
x.setare_stare(ocupat);
y.setare_stare(liber);
if (ecran_liber(x,y))
{
clrscr();
gotoxy(20,12);
cout<<"Ecranul este liber...";
getch();
}
else
{
clrscr();
gotoxy(20,12);
cout<<"Ecranul este ocupat...";
getch();
};
x.setare_stare(liber);
y.setare_stare(liber);
if (ecran_liber(x,y))
{
clrscr();
gotoxy(20,12);
cout<<"Ecranul este liber...";
getch();
}
else
{
clrscr();
gotoxy(20,12);
cout<<"Ecranul este ocupat...";
getch();
};
}

87
2.7 Supraîncărcarea operatorilor
Polimorfismul este realizat în C++ şi prin supraîncărcarea operatorilor. După
cum s-a văzut în numeroase exemple, în C++ se pot folosi operatorii >> şi <<
pentru a efectua operaţii I/O relativ la consolă. Aceşti operatori pot efectua
aceste operaţii suplimentare (ştiut fiind faptul că pot funcţiona şi ca operatorii de
shiftare la nivel de biţi) deoarece operaţiile sunt supraîncărcate în fişierul antet
IOSTREAM.H. Când un operator este supraîncărcat, el capătă o semnificaţie
suplimentară relativ la o anumită clasă fără să-şi piardă vreunul din înţelesurile
iniţiale. Majoritatea operatorilor din C++ pot fi supraîncărcaţi, stabilind
semnificaţia lor relativ la o anumită clasă.
Limbajul C++ permite supraîncărcarea numai a operatorilor existenţi în limbaj.
Dintre aceştia nu pot fi supraîncărcaţi operatorii: . :: ? : .
Să mai precizăm faptul că, prin supraîncărcarea operatorilor nu se poate
schimba n-aritatea, prioritatea sau asociativitatea operatorilor, acestea fiind
elemente predefinite pentru tipuri predefinite şi deci ele se vor menţine şi pentru
tipuri abstracte.
Prin n-aritate înţelegem că operatorul este unar sau binar.
Supraîncărcarea operatorilor se realizează cu ajutorul unor funcţii membre sau
prietene speciale. Specificul acestora se află în numele lor. El se compune din
cuvântul cheie operator şi unul sau mai multe caractere care definesc operatorul
care se supraîncarcă. Între cuvăntul cheie operator şi caracterele care definesc
operatorul care se supraîncarcă se află cel puţin un spaţiu alb. Felul în care sunt
scrise funcţiile operator diferă pentru cele de tip membru de cele de tip friend.

2.7.1 Crearea unei funcţii operator membru


Funcţiile operator membru au sintaxa de implementare:

<Tip_returnat> <Nume_clasă>::operator # (<Lista_argumente>)


{

//Operaţii specifice

Deseori funcţiile operator returnează un obiect din clasa asupra căreia


operează, dar <Tip_returnat> poate fi orice tip valid.

# este o notaţie pentru numele operatorului aşa cum va fi folosit in program după
redefinire. Deci, dacă supraîncărcăm operatorul = atunci sintaxa prototipului
funcţiei membru va fi:

<Tip_returnat> operator =(<Lista_argumente>);

Funcţiile operator membre au un singur parametru sau nici unul. În cazul în care
au un parametru, acesta se referă la operandul din dreapta al operatorului.
Celălalt operand ajunge la operator prin intermediul pointerului special this.

88
Astfel stând lucrurile, trebuie să avem grijă de eventualitatea ca operandul din
stânga să fie o constantă, ceea ce înseamnă ca nu mai avem context de apel
pentru operatorul redefinit.
Inivităm cititorul să urmărească exemplul de mai jos.

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

//Clasa complex contine functia operator +


//ca membru
//operatorul + este extins la multimea numerelor complexe
class complex
{
float x,y;
public:
complex(){};
complex(float a,float b)
{
static i;
i++;
clrscr();
cout<<"Lucreaza constructorul...Obiectul->:"<<i;
getch();
x=a;
y=b;
};
void disp_nc();

//prototipul operatorului +
complex operator+(complex &op2);
};

void complex::disp_nc()
{
cout<<x<<"+i*"<<y;
};

//Implementare operator +
//Aceasta sintaxa transforma apelul <ob1+ob2>
//in +(ob2) ob1 fiind accesibil prin pointerul
//special <this>
complex complex::operator+(complex &op2)
{
complex temp;
temp.x=op2.x+x;
temp.y=op2.y+y;
return temp;

89
};

void main()
{
complex tamp1,tamp2;
complex *pod1,*pod2;
complex ob1(10,10),ob2(11,11);
clrscr();
gotoxy(20,10);cout<<"Primul numar complex ->:";
ob1.disp_nc();
getch();
gotoxy(20,11);cout<<"Al doilea numar complex->:";
ob2.disp_nc();
getch();
ob1=ob1+ob2;
gotoxy(20,13);cout<<"Suma numerelor complexe->:";
ob1.disp_nc();
getch();

pod1=new complex(200,200);
pod2=new complex(300,300);
tamp1=*pod1;

clrscr();
gotoxy(20,10);cout<<"Al treilea numar complex ->:";
tamp1.disp_nc();

tamp2=*pod2;
gotoxy(20,11);cout<<"Al patrulea numar complex ->:";
tamp2.disp_nc();

gotoxy(20,14);cout<<"Suma numerelor complexe->:";


tamp1=tamp1+tamp2;
tamp1.disp_nc();
getch();
}

2.7.2 Supraîncărcarea operatorilor folosind o funcţie friend


Se poate supraîncărca un operator relativ la o clasă folosind o funcţie friend.
Deoarece un prieten nu este membru al clasei, nu are disponibil un pointer de tip
this. De aceea, unei funcţii supraîncărcate de tip friend operator I se vor
transmite explicit operanzii. Deci, dacă supraîncărcăm un operator unar vom
avea un parametru, dacă supraîncărcăm unul binar vom avea doi parametri.
Dacă supraîncărcăm un operator binar, operandul din stânga este pasat în
primul parametruiar cel din stânga în al doilea parametru.

90
Invităm cititorul să urmărească utilizarea funcţiilor friend la supraîncărcare în
Exerciţiul 6 de la Capitolul aplicativ al prezentului suport de curs.

2.8 Forma generală a unui program în C++


Cu toate că stilurile individuale diferă, majoritatea programelor în C++ au
următoarea formă generală:

#include…
declaraţii clase de bază
declaraţii clase derivate
prototipuri de funcţii nemembre
main()
{
:
}
definiţii de funcţii ne-membre

De remarcat, totodată, faptul că, în majoritatea proiectelor mari, toate


declaraţiile de clase se pun într-un fişier antet şi sunt incluse în fiecare modul.

91
3 Bazele sistemului I/O în C++
Pe lângă faptul că permite sistemul de I/O din C, C++ defineşte propriul său
sistem I/O orientat pe obiecte. Ca şi sistemul de I/O din C, cel din C++ este
complet integrat. Aceasta înseamnă că diferitele tipuri de operaţii I/O sunt doar
perspective diferite ale aceluiaşi mecanism. Această perspectivă integratoare
asupra operaţiilor I/O are la bază, atât în C cât şi în C++, conceptul de flux
(stream).

3.1 Fluxuri în C şi C++


Sistemul de fişiere din C şi C++ este proiectat să lucreze cu o mare varietate de
echipamente, care include terminale, drivere de disc, drivere de unitate de
bandă, etc.

Chiar dacă echipamentele diferă, sistemul de fişiere din C şi C++ le


transformă într-un instrument logic numit flux (stream).

Toate fluxurile se comportă la fel. Deoarece fluxurile sunt independente de


echipamente, o funcţie care poate să scrie într-un fişier de pe hard poate fi
folosită cu aceeaşi sintaxă pentru a scrie la alt dispozitiv. Sistemul de fişiere
C/C++ recunoaşte două tipuri de fluxuri:text şi binar.

Fluxuri de tip text


Un flux de tip text este o secvenţă de caractere. Standardul ANSI C permite (dar
nu impune) ca un flux de tip text să fie organizat în linii terminate cu un caracter
de linie nouă. Totuşi, caracterul de linie nouă din ultima linie este opţional,
utilizarea sa fiind determinată de modul de implementare a compilatorului
(Majoritatea compilatoarelor de C/C++ nu încheie fluxul de tip text cu un caracter
de linie nouă. Să mai semnalăm faptul că, într-un flux de tip text pot să apară
anumite transformări cerute de mediul de operare gazdă (De exemplu, un
caracter de linie nouă poate fi înlocuit cu perechea început de rând-linie nouă.
Acesta este motivul pentru care nu există o relaţie biunivocă între caracterele
care sunt scrise sau citite şi cele de la echipamentul extern.

Fluxuri binare
Un flux binar este o secvenţă de octeţi într-o corespondenţă biunivocă cu cei de
la echipamentul extern.

Fişiere
În C/C++ un fişier poate să fie: un fişier de pe disc, tastatura, ecranul monitorului,
imprimanta,etc. Un flux se asociază cu un anumit fişier efectuând o operaţie de
deschidere. Odată deschis fişierul, este posibil schimbul de date între el şi
programul utilizator care l-a deschis.
De observat faptul, trivial pentru cunoscători, că nu toate fişierele au aceleaşi
posibilităţi. De exemplu, un fişier de pe disc poate să admită un acces aleator la
datele stocate în el, în timp ce imprimanta nu o poate face. Astfel că, putem
concluziona, pentru claritate:

92
 Pentru sistemul I/O din C/C++ toate fluxurile sunt la fel dar nu şi
fişierele.

Dacă fişierul admite cereri de poziţionare, deschiderea fişierului iniţializează


pointerul de fişier la o valoare care indică începutul fişierului. Pe măsură ce se
fac operaţii de citire/scriere, pointerul de fişier este incrementat corespunzător
naturii operaţiei.
Un fişier se disociază de un flux în urma operaţiei de închidere. Dacă este închis
un fişier deschis în operaţii de scriere, conţinutul fluxului asociat este scris la
dispozitivul extern (acest proces se numeşte flushing=golire a fluxului ).
Toate fişierele se închid automat când programul se termină normal. În caz de
blocaj sau dacă programul se termină ca urmare a apelului funcţiei abort()
fişierele nu se închid.
Cu menţiunea că în fişierul antet stdio.h se găsesc structurile de control de tip
FILE, indispensabile pentru lucrul cu fişiere în C, prezentăm, în continuare
contextul C++ referitor la sistemul I/O.

3.2 Clasele de bază pentru fluxuri în C++


C++ asigură suportul pentru sistemul său de I/O în fişierul antet IOSTREAM.H. În
acest fişier antet sunt definite două ierarhii de clase care admit operaţii de I/O.
Clasa cu nivelul de abstractizare cel mai înalt se numeşte streambuf şi asigură
operaţiile de bază de intrare/ieşire. Ca programatori, nu folosiţi streambuf direct
decât dacă veţi deriva propriile clase pentru efectuarea operaţiilor I/O. A doua
ierarhie porneşte cu clasa ios, care acceptă operaţii I/O formatate. Din ea sunt
derivate clasele istream, ostream şi iostream. Aceste clase sunt folosite pentru
a crea fluxuri capabile să citească, să scrie, respectiv să citească/ să scrie date
din/ la echipamentele externe. Clasa ios conţine o serie de alte ramuri relativ la
lucrul cu fişiere pe care nu ne propunem să le studiem în cadrul acestui curs.

Fluxuri predefinite în C++


Când îşi începe execuţia un program C++, se deschid automat patru fluxuri
predefinite, pe care le prezentăm în tabelul de mai jos.

Flux Semnificaţie Echipament implicit


cin Intrare standard Tastatura
cout Ieşire standard Ecran
cerr Ieşire standard pentru eroare Ecran
clog Versiune cu memorie tampon pentru Ecran
cerr
Tabelul 15. Fluxurile predefinite C++

Fluxurile cin, cout, cerr corespund fluxurilor stdin, stdout, stderr din C. Implicit,
fluxurile standard sunt folosite pentru a comunica cu consola. Însă, în mediile
care admit redirecţionarea I/O, fluxurile standard pot fi redirecţionate spre alte
echipamente sau fişiere.

93
I/O formatate în C++
Sistemul de I/O din C++ vă permite să formataţi operaţiile I/O, aşa cum se
întâmpla şi în cazul utilizării funcţiilor C pentru operaţii I/O, precum: printf,
cprintf, scanf,etc. De exemplu, se poate specifica mărimea unui câmp, baza
unui număr, numărul de cifre după punctul zecimal,etc. Operatorii din C++
utilizaţi pentru introducerea informaţiilor de formatare sunt >> şi <<.
Există două căi înrudite, dar conceptual diferite, prin care se pot formata datele.
În primul rând, putem avea acces direct la diferiţi membri ai clasei ios. În al
doilea rând, putem folosi funcţii speciale numite manipulatori, care pot fi incluse
în expresii de I/O.
Prezentăm, în continuare modul de utilizare a manipulatorilor de formate, datorită
accesibilităţii mai mari a acestora.
Manipulatorii standard sunt prezentaţi în tabelul de mai jos.

Manipulator Exemplu de folosire Efect


dec cout<<dec<<intvar; Converteşte întregi în cifre
zecimale; corespunde
formatului %d din C
endl cout<<endl Trimite o nouă linie în
ostream şi descarcă
bufferul
ends cout<<ends Inserează un caracter nul
într-un flux
flush cout<<flush Descarcă bufferul fluxului
ostream
hex cout<<hex<<intvar; Conversie hexazecimală
cin>>hex>>intvar corespunzătoare
formatului %x din ANSI C
oct cout<<oct<<intvar; Conversie octală (formatul
cin>>oct>>intvar; %o din C)
resetiosflags(long cout<<resetioflags(ios::dec); Reiniţializează biţii de
f) formatare specificaţi de
argumentul întreg de tip
long
setbase(int baza) cout<<setbase(10); Stabileşte baza de
cin>>setbase(8); conversie la argumentul
întreg (trebuie să fie
0,8,10 sau 16). Valoarea 0
este baza implicită.
setfill(int ch) cout<<setfill(‘.’); Stabileşte caracterul
cin>>setfill(‘ ‘); folosit pentru completarea
câmpurilor de mărime
specificată
setiosflags(long f) cout<<setiosflags(ios::dec); Stabileşte biţii de
cin>> setiosflags(ios::hex); formatare specificaţi de

94
argumentul întreg de tip
long
setprecision(int p) cout<<setprecision(6); Stabileşte precizia
cin>>setprecision(10); conversiei în virgulă
mobilă la numărul
specificat de zecimale
setw(int w) cout<<setw(6)<<var; Stabileşte mărimea unui
cin>>setw(24)>>buf câmp la numărul specificat
de caractere
ws cin ws; Elimină spaţiile libere din
fluxul de intrare
Tabelul 16. Manipulatori de formatare a operaţiilor I/O în C++

Toţi aceşti manipulatori au prototipul în fişierul antet iomanip.h.

Pentru a utiliza manipulatorii setiosflags şi resetiosflags trebuie cunoscuţi


indicatorii de formatare din tabelul de mai jos.

Nume indicator Ce efect are utilizarea indicatorului


ios :: skipws Elimină spaţiile goale din intrare
ios :: left Aliniază ieşirea la stânga în interiorul lăţimii câmpului
ios :: right Aliniază ieşirea la dreapta
ios :: scientific Foloseşte notaţia ştiinţifică pentru numerele în virgulă
mobilă.
ios :: fixed Foloseşte notaţia zecimală pentru numere în virgulă mobilă
ios :: dec Foloseşte notaţia zecimală pentru întregi
ios :: hex Foloseşte notaţia hexazecimală pentru întregi
ios :: oct Foloseşte notaţia octală pentru întregi
ios :: uppercase Foloseşte litere mari pentru ieşire
ios :: showbase Indică baza sistemului de numeraţie în cadrul ieşirii (prefixul
0x pentru hexazecimal şi prefixul 0 pentru octal
ios :: showpoint Include un punct zecimal pentru ieşiri în virgulă mobilă
ios :: showpos Include şi semnul + la afişarea valorilor pozitive
ios :: unitbuf Goleşte toate fluxurile după inserarea caracterlor într-un flux
Tabelul 16. Indicatori de formatare a operaţiilor I/O în C++

Notaţia ios :: <Nume_indicator> este folosită pentru a identifica indicatorul ca


pe un membru al clasei ios.
Exemplificăm cele spuse mai sus prin codul C++ de mai jos.

#include<iostream.h>
#include<iomanip.h>
#include<conio.h>
#include<stdio.h>
void main()
{

95
double nr;
clrscr();
gotoxy(10,6);
nr=7./3.;
gotoxy(5,6);
cout<<"Afisare numar in virgula mobila/ format implicit...";
gotoxy(10,7);
cout<<nr;

gotoxy(5,9);
cout<<"Afisare numar in virgula mobila/ cu precizia specificata...";
gotoxy(10,10);
cout<<setprecision(10)<<nr;
gotoxy(5,12);
cout<<"Afisare numar in virgula mobila/ format virgula fixa...";
gotoxy(10,13);
cout.setf(ios::fixed);
cout<<setprecision(10)<<nr;

gotoxy(5,15);
cout<<"Afisare numar in virgula mobila/ format virgula fixa...";
gotoxy(10,16);
cout.setf(ios::scientific);
cout<<setprecision(10)<<nr;

gotoxy(5,18);
cout<<"Afisare numar in virgula mobila/ format virgula fixa...";
gotoxy(10,19);
cout.setf(ios::scientific|ios::showpos);
cout<<setprecision(10)<<nr;

getch();
}

Fişiere utilizator în C++


Chiar dacă abordarea operaţiilor I/O din C++ formează un sistem integrat,
operaţiile cu fişiere (altele decât cele predefinite), sunt suficient de specializate
pentru a fi necesar să la discutăm separat.
Pentru a efectua operaţii I/O cu fişiere conform paradigmei C++, trebuie să
includeţi în programul Dvs. fişierul antet FSTREAM.H. Acesta defineşte mai
multe clase, printre care ifstream, ofstream şi fstream. Aceste clase sunt
derivate din istream şi, respectiv, din ostream la care ne-am referit şi mai sus.

ÖDeschiderea şi închiderea unui fişier


Un fişier se deschide în C++ legându-l de un flux. Înainte de a putea să
deschideţi un fişier, trebuie, pentru început, să aveţi un flux. Există trei tipuri de

96
fluxuri: de intrare, de ieşire şi de intrare/ieşire. Pentru a crea un flux de intrare,
trebuie să-l declaraţi ca fiind din clasa ifstream. Pentru a crea un flux de ieşire,
trebuie să-l declaraţi ca fiind din clasa ofstream. Fluxurile care efectuiază atât
operaţii de intrare cât şi operaţii de ieşire, trebuie declarate ca fiind din clasa
fstream. Odată declarat fluxul, o modalitate de a-i asocia un fişier extern o
reprezintă utilizarea funcţiei open() având prototipul:

void open(const char *nume_fisier , int mod, int acces=filebuf::openprot);

nume_fisier este un nume extern de fişier care poate include şi


specificarea căii de acces.
Valoarea parametrului mod determină modul de deschidere a fişierului.
Parametrul mod poate avea una sau mai multe din valorile prezentate în tabelul
de mai jos.

Nume mod Operaţie


ios::app Adaugă date în fişier
ios::ate Când se deschide pentru prima dată, operează
poziţionarea în fişier la sfârşitul fişierului (ate înseamnă la
sfârşit)
ios::binary Deschide fişierul în mod binar, inhibând interpretarea
caracterelor <CR> <LF>
ios::in Deschide fişierul pentru citire
ios::nocreate Nu efectuează deschiderea fişierului dacă acesta nu există
deja
ios::noreplace Dacă fişierul există, încercarea de a-l deschide pentru ieşire
eşuează, cu excepţia cazului în care ios::app sau ios::ate
sunt operate
ios::out Deschide fişierul pentru scriere
ios:trunc Trunchiază fişierul dacă el există deja
Tabelul 17. Valorile parametrului care stabileşte modul de deschidere a
unui fişier

Puteţi specifica mai mult de un mod de lucru pentru un fişier, folosind operatorul
pe biţi SAU cu modurile respective. De exemplu, pentru deschiderea unui fişier
pentru ieşire şi poziţionarea pointerului la sfârşitul lui se folosesc modurile
ios::out şi ios::ate astfel:

ofstream oflux(“o_fisier”,ios::out | ios::ate);

ceea ce ne arată al doilea procedeu de deschidere a unui fişier, utilizând


constructorul clasei ofstream sau, de ce nu, ifstream, dacă este cazul.
Pentru a închide un fişier, folosiţi funcţia membru close(). Această funcţie nu
preia nici un parametru şi nu returnează nici o valoare. De analizat utilizarea
funcţiei close() în exemplele care vor urma.

97
ÖScrierea şi citirea fişierelor text
Sunt două operaţii foarte uşoare, realizate apelând la operatorii >> şi << într-un
mod asemănător operaţiilor referitoare la consola sistemului, cu deosebirea că în
loc să folosiţi cin şi cout apelaţi la un flux legat de un fişier . Codul de mai jos
arată cum poate fi afişat pe ecranul monitorului conţinutul unui fişier text.

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

//Functia principala a programului citeste linia de comanda a programului


void main(int argc,char *argv[])
{

// Linia de comanda a programului trebuie sa contina doi parametri


if (argc!=2)
{
cerr<<"Mod de utilizare : lisfis <nume fisier CPP>\n";
exit(0);
}

// Deschidere fisier text de nume specificat in argv[1]


ifstream in(argv[1],ios::in);
if (!in)
{
cerr<<"Fisierul nu poate fi deschis!";
exit(0);
}

char c;
clrscr();
while (in.get(c))
{
if (wherey()>20)
{
gotoxy(20,24);
cout<<"Press any key to continue...";
getch();
clrscr();
}
cout<<c;
}
in.close();
}

ÖI/O în fişiere de tip binar

98
Există două modalităţi de a scrie şi citi date binare într-un fişier. Prima
modalitate se referă la utilizarea funcţiilor get() şi put(). Aceste funcţii sunt
orientate pe octeţi, ceea ce înseamnă că get() va citi un octet de date iar put() va
scrie un octet de date.
Funcţia get() are mai multe forme; o prezentăm, în continuare, împreună cu
omoloaga ei put(), pe cea mai des folosită:

istream &get(char &ch);


ostream &put(char ch);

Funcţia get() citeşte un singur caracter din streamul asociat şi memorează


valoarea sa în ch. De asemenea, se mai observă că funcţia returnează o
referinţă către flux. Funcţia put() scrie ch în flux şi returnează fluxului o referinţă.
Ca un exemplu de utilizare a funcţiilor get() şi put() prezentăm codul de mai jos,
care realizează copierea unui fişier în altul. Dacă numele executabilului asociat
acestui cod este copyf, atunci sintaxa de lansare în execuţie a programului este:

copyf <fisier_sursa> <fisier_destinatie>

#include<stdlib.h>
#include <fstream.h>
void main(int argc, char *argv)
{
//Verific daca sunt suficiente argumente
if(argc<3)
{
cerr<<"Mod de utilizare : filecopy <fisier sursa> <fisier destinatie>\n";
exit(0);
}
//Deschide fisierul de intrare si il conecteaza la fluxul ins
ifstream ins(argv[1]);
if(!ins)
{
cerr<<"Nu pot deschide fisierul sursa:"<<argv[1];
exit(1);
}
//Deschide fisierul de iesire si il conecteaza la fluxul outs
ofstream outs(argv[2]);
if(!outs)
{
cerr<<"Nu pot deschide fisierul sursa:"<<argv[2];
exit(1);
}
//Citeste din sursa si scrie in destinatie
char c;
while(ins.get(c) && outs) outs.put(c);

99
}
A doua modalitate de a citi şi scrie blocuri de date în binar este folosirea funcţiilor
din C++ read() şi write(). Prototipurile lor sunt:

istream &read(unsigned char *buf, int numar);


ostream &write(const unsigned char *buf, int numar);

Funcţia read() citeşte numar octeţi din fluxul asociat şi îl pune în buffer-ul indicat
de buf . Funcţia write() scrie în fluxul asociat numar octeţi citiţi din buffer-ul spre
care indică buf.

ÖDetectarea EOF
Puteţi să detectaţi sfârşitul fişierului folosind funcţia membru eof() ,care are acest
prototip:

int eof();

Ea returnează o valoare nenulă când a fost atins sfârşitul fişierului; altfel,


returnează zero. Utilizarea funcţiei eof() şi alte elemente legate de lucrul cu
fişiere, prezentăm în codul de mai jos, care realizează afişarea conţinutului unui
fişier atât în hexazecimal cât şi în cod ASCII, atunci când codul asociat este
printabil.

#include <fstream.h>
#include <ctype.h>
#include <iomanip.h>
#include <stdlib.h>
#include<conio.h>
void main(int argc,char *argv[])
{
if (argc!=2)
{
cerr<<"Mod de utilizare : lishex <nume fisier CPP>\n";
exit(0);
}
ifstream in(argv[1],ios::in|ios::binary);
if (!in)
{
cerr<<"Fisierul nu poate fi deschis!";
exit(0);
}
register int i,j;
int count=0;
char c[16];
cout.setf(ios::uppercase);
clrscr();

100
while(!in.eof())
{
for(i=0;i<16 && !in.eof();i++)
{
in.get(c[i]);
}
if (i<16) i--;
for(j=0;j<i;j++)
cout<<setw(3)<<hex<<(int) c[j];
for(;j<16;j++)
cout<<"\t";
for(j=0;j<i;j++)
if(isprint(c[j])) cout<<c[j];
else cout<<".";
cout<<endl;
count++;
if(count==16)
{
count=0;
cout<<"Press ENTER to continue!";
getch();
clrscr();
cout<<endl;
}
}
in.close();
}

ÖAccesul aleator în fişiere


În sistemul de I/O din C++, accesul aleator se efectuează folosind funcţiile
seekg() şi seekp(). Formele lor cele mai uzuale sunt:

istream &seekg(streamoff offset, seek_dir origine);


ostream &seekp(streamoff offset, seek_dir origine);

streamoff este un tip definit în IOSTREAM.H, capabil să conţină cea mai mare
valoare validă pe care o poate avea offset, iar seek_dir este o enumerare care
are aceste valori:

ios::beg
ios::cur
ios::end

Sistemul de I/O din C++ operează cu doi pointeri asociaţi unui fişier. Unul este
pointerul de get , care specifică unde va apărea următoarea operaţie de intrare
în fişier. Celălalt este pointerul de put şi specifică unde va avea loc următoarea

101
operaţie de ieşire. După fiecare operaţie de intrare sau ieşire, pointerul
corespunzător este avansat automat, secvenţial. Dar, folosirea funcţiilor seekg()
şi seekp() permite un acces nesecvenţial la datele din fişier.
seekg() şi seekp() deplasează pointerul de înregistrare cu offset octeţi faţă de
origine.

Cu menţiunea că lucrul cu fişiere în C++ are nenumărate alte faţete pentru a


căror prezentare nu dispunem de timpul şi spaţiul necesar, încheiem această
scurtă excursie în problematica fişierelor. În fine, pentru curioşi facem şi
precizarea că, însuşi bătrânul C are propria filozofie, extrem de puternică, în
ceea ce priveşte lucrul cu fişiere.

102
3 Programare generică în C++
În programarea profesională apar nenumărate situaţii în care reutilizarea codului
presupune o soluţie de un anumit tip pentru o problemă dată. Situaţia la care ne
referim în această secţiune este, potenţial vorbind, următoarea: Ce putem face
pentru a comprima codul sursă în situaţia în care structuri de date, diferite
ca tip, suportă prelucrări similare.
Soluţia acestei probleme de stil de programare o reprezintă programarea
generică. Exprimându-ne în termenii limbajului C, o funcţie generică defineşte
un set general de operaţii care vor fi aplicate unor tipuri de date diferite.
Ca un exemplu, o soluţie generică pentru modelarea unei stive este un pretext
ideal pentru precizarea ideilor principale ale programării generice.
Altfel spus, dacă dorim o stivă, în care, de la caz la caz, să putem păstra numere
întregi, numere reale sau şiruri de caractere (deci tipuri de date diferite),
operaţiile fiind aceleaşi ( push() şi pop() ), este clar că ne aflăm în situaţia în
care avem nevoie de suport pentru scrierea de cod cu proprietăţi generice.
Dacă în Pascal programarea generică se baza pe tipuri procedurale şi
programarea la nivel de octet, în C++ există suport evoluat pentru programare
generică, sub forma şabloanelor. Cu un şablon, în C++ se poate crea o funcţie
generică sau o clasă generică.

ÖFuncţii TEMPLATE
O funcţie template este o funcţie şablon, având unul sau mai mulţi parametri
formali de un tip generic. În funcţie de nevoile de utilizare a acestei funcţii,
compilatorul generează funcţii propriu-zise, înlocuind tipul generic cu un tip
concret. Tipul concret poate fi orice tip fundamental, derivat sau clasă predefinită.
Considerăm un exemplu. Fie funcţia max(x,y) care returnează valoarea maximă
a argumentelor sale. Tipul variabilelor x şi y trebuie, obligatoriu, specificat în
momentul compilării. Soluţia clasică constă în redefinirea (over-loading) funcţiei
max pentru fiecare tip al argumentelor x şi y (de observat şi cele spuse la
paragraful 2.2 relativ la funcţii supraîncărcate în C++).
Trebuie, aşadar, să definim mai multe versiuni ale funcţiei max.

int max(int x, int y)


{
return (x>y) ? x : y;
}

float max(float x, float y)


{
return (x>y) ? x : y;
}

103
Mecanismul template permite definirea o singură dată a şablonului de funcţii,
după care se generează automat funcţiile propriu-zise în concordanţă cu
necesităţile de utilizare, dar ,evident, în faza de compilare.

Sintaxa la specificare este:

template <class Nume_tip_generic_1 [,…class Nume_tip_generic_n]>


Nume_şablon
definiţie_şablon

De precizat următoarele:
@Caracterele < şi > fac parte din sintaxa obligatorie.
@Lista de parametri formali ai unei funcţii şablon trebuie să utilizeze toate
tipurile de date generice.
@În cazul funcţiilor template nu se fac conversii
@Funcţia care are acelaşi nume şi acelaşi număr de parametri cu o funcţie
şablon se numeşte caz exceptat (Supraîncărcarea explicită este prioritară).

Sintaxa la utilizare este:

Nume şablon (Expresie_1[, …,Expresie_n]);

Prezentăm, în continuare, definiţia funcţiei şablon max , urmată de o secvenţă


client de utilizare.

template <class T>


T max(T x, T y)
{
return (x>y) ? x : y;
}

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

//Definire sablon functie


template<class T>
T max(T x,T y)
{
return(x>y) ? x:y;
}
void main()
{
int i,j;
float k,l;

104
clrscr();
i=10;
j=2;
k=13;
l=-7;

//Exemple de utilizare sablon


gotoxy(20,10);cout<<"Apel max cu parametri variabili de tip float..."<<max(k,l);
gotoxy(20,12);cout<<"Apel max cu parametri variabili de tip int ..."<<max(i,j);
gotoxy(20,13);cout<<"Apel max cu parametri valoare …float ..."<<max(13.,-7.);
getch();
}

Prezentăm, totodată, un exemplu de funcţie generică pentru compararea unor


date după valoarea unei chei încapsulate în aceste date.

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

//Definirea unei <functii generice> pentru compararea


//unor date dupa valoarea unei chei incapsulate
//in aceste date
template <class T>
int comp(T i1,T i2)
{
if (i1.key<i2.key) return -1;
if (i1.key==i2.key) return 0;
if (i1.key>i2.key) return 1;
};

//Structura aleasa pentru exemplificare


//cheia generica incapsulata este campul <key>
struct tpers
{
char np[30];
int key;
};

//Instantiere <struct tpers>


struct tpers tam,pers[50];

void main()
{

105
clrscr();
int i=0;

//***************************************
//Citire persoane de la tastatura
do
{
gotoxy(20,12);
cout<<"Numele persoanei:";clreol();
cin>>pers[i].np;
gotoxy(20,13);
cout<<"Matricola:";clreol();
cin>>pers[i].key;
gotoxy(20,14);
cout<<"Mai aveti(D,N):";
i++;
}

//***************************************
//Listare persoane pe ecranul monitorului
//in ordinea citirii de la tastatura
while(toupper(getch())!='N');
clrscr();
cout<<"Listare persoane in ordinea citirii de la tastatura..."<<endl;
cout<<"_______________________________________________"<<endl;
for(int j=0;j<i;j++)
{
if (wherey()>10)
{
cout<<"______________________________________________"<<endl;
cout<<"Pentru continuare apasati o tasta..."<<endl;
getch();
clrscr();
cout<<"Listare persoane in ordinea citirii de la tastatura..."<<endl;
cout<<"_______________________________________________"<<endl;
};
cout.width(30);cout.setf(ios::left);
cout<<pers[j].np<<" "<<pers[j].key<<endl;
};
getch();
//*************************************
//Sortare persoane
int sortat;
do
{
sortat=1;

106
for(int j=0;j<i-1;j++)
{
switch(comp(pers[j],pers[j+1]))
{
case 1: {
tam=pers[j];
pers[j]=pers[j+1];
pers[j+1]=tam;
sortat=0;
};
};
};
}
while(!sortat);

//****************************************
//Listare persoane dupa sortare in ordinea
//crescatoare a matricolelor
clrscr();
cout<<"Listare persoane dupa sortare........................."<<endl;
cout<<"____________________________________________"<<endl;
for(int k=0;k<i;k++)
{
if (wherey()>10)
{
cout<<"_____________________________________________"<<endl;
cout<<"Pentru continuare apasati o tasta..."<<endl;
getch();
clrscr();
cout<<"Listare persoane dupa sortare........................."<<endl;
cout<<"______________________________________________"<<endl;
};
cout.width(30);cout.setf(ios::left);
cout<<pers[k].np<<" "<<pers[k].key<<endl;
};
getch();
}

ÖClase TEMPLATE
O clasă template defineşte un şablon pe baza căruia se pot genera clase
propriu-zise. Din acest motiv, o clasă template se mai numeşte şi clasă generică
, clasă generator sau metaclasă.

Astfel că, o clasă template devine o clasă de clase, reprezentând cel mai
înalt nivel de abstractizare admis de programarea obiect orientată.

107
În cadrul clasei şablon se pot declara atribute informaţionale de un tip ambiguu,
care sunt particularizate în cadrul clasei generată pe baza şablonului. Evident, şi
în acest caz, generarea se face în faza de compilare în concordanţă cu cerinţele
clientului.
Sintaxa la specificarea clasei:

template <class T>


class nume_clasă
{
:
};

Sintaxa la implementarea funcţiilor membre ale unei clase template:

template <class T> Tip returnat nume_clasa <T>::nume_funcţie(…)


{
:
};

Pentru mai multă claritate prezentăm şi exemplul de mai jos.

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

const int SIZE = 10;

//***************************************
//Definire clasa matrice generica
template <class ATip>
class genmat
{
ATip a[SIZE];
public:
genmat();
ATip &operator [ ](int i); //Supraîncărcare operator [ ]
};

//***************************************
//Implementare constructor clasa generica
template <class ATip> genmat<ATip>::genmat()
{
register int i;
for(i=0;i<SIZE;i++)
a[i]=i;
};

108
//***************************************
//Implementare supraincarcare operator [ ]
template <class ATip> ATip &genmat<ATip>::operator[ ](int i)
{
if(i<0 ||i>SIZE-1)
{
cerr<<"Valoare indice eronata...";
getch();
exit(1);
};
return a[i];
};

//***************************************
//Functia principala
void main()
{
genmat<int> intob;
genmat<double> doubob;
int i;
clrscr();
cout<<"Matrice de intregi..."<<endl;
for(i=0;i<SIZE;i++)
intob[i]=i;
for(i=0;i<SIZE;i++)
cout<<intob[i]<<endl;
getch();
clrscr();
cout<<"Matrice de reali dubla precizie..."<<endl;
for(i=0;i<SIZE;i++)
doubob[i]=(double)i/3;
for(i=0;i<SIZE;i++)
cout<<doubob[i]<<endl;
getch();
clrscr();
intob[100]=100;
};

Tot pentru exemplificare să considerăm şi o situaţie deosebit de simplă. Ni se


cere să construim o clasă template corespunzătoare conceptului de stivă, din
care, ulterior, să se poată concretiza clase care simulează stiva pentru tipuri de
date diferite.

//Clasa sablon CSTack


template <Class T>

109
class Cstack
{
T * v; //pointer la varful stivei
T * p; //pointer la pozitia curenta din stiva
int dim; //dimensiunea stivei
public:
Cstack(int sz)
{
v=p=new T [dim=sz]; //alocare dinamica de memorie pentru p şi v
}
~Cstack()
{
delete [ ] v;
}
void push(T a)
{
*p++=a;
};
T pop()
{
return *-p
}
}

După ce o astfel de clasă template a fost declarată şi definită, se poate trece


deja la instanţierea de obiecte. Singura deosebire faţă de folosirea unei clase
obişnuite constă în faptul că trebuie specificat tipul concret care înlocuieşte tipul
generic T. Ca un exemplu, să instanţiem un obiect stivă (CStack) , în care încap
maximum 100 elemente de tip char.

CStack<char> sc(100);

În acest caz, compilatorul generează clasa ce rezultă prin înlocuirea lui T cu


char, după care instanţiază obiectul sc. Constructorul acestuia primeşte ca
argument valoarea 100. Pentru mai multe detalii urmăriţi exemplul de mai jos.

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

// Definirea clasei stack. Se vede că instanţele ei nu sunt protejate faţă de


//excepţii.
//Despre excepţii urmează să discutăm…
template <class T>
class CStack
{
T *v;

110
T *top;
int dims;
public:
CStack( int sz) //constructor
{
v=top=new T[dims=sz];
}

~CStack() //destructor
{
delete [ ] v;
}

void push(T a) //Functia de inserare in stiva


{
*top++=a; //Notatie prescurtata echvalenta cu *top=a; top++
}

T pop()
{
return *--top; //Notatie prescurtata echibvalenta cu - -top; return *top
}
};

//Functia principala
void main()
{
int i;
//Primul exemplu de instantiere a stivei generice ;numere întregi
CStack<int> st1(20);

//Încarcare stiva
for (i=0;i<=9;i++)
{
st1.push(i);
}

//Vizualizare continut stiva


clrscr();
for (i=0;i<=9;i++)
{
gotoxy(35, wherey()+1);
cout<<st1.pop()<<"*****";
}
getch();

111
//Al doilea exemplu de instantiere a stivei generice; caractere, începand cu “A”
CStack<char> st2(20);

//Încarcare stiva
for (i=65;i<75;i++)
{
st2.push((char) i);
}

//Vizualizare continut stiva


clrscr();
for (i=0;i<10;i++)
{
gotoxy(35, wherey()+1);
cout<<st2.pop()<<"*****";
}
getch();
}

112
4 Tratarea excepţiilor
Programatorii adevăraţi trebuie să ia în calcul şi posibilitatea de a crea programe
robuste, care fac faţă atât cerinţelor specificate dar nerafinate suficient, cât şi
cerinţelor nespecificate dar formulate de utilizator, din diverse motive.
Programele care au aceste calităţi se numesc robuste.
În programarea clasică soluţia acestei probleme se putea numi, destul de exact
spus, programare defensivă. Seamănă puţin cu conducerea preventivă din
şoferie dacă ne gândim că programând defensiv, în fond punem răul înainte,
deci nu ne bazăm pe cumsecădenia şi buna pregătire a utilizatorului.
Pentru a face faţă cerinţelor legate de problema tratării excepţiilor (aşa se
numesc în jargon profesional erorile care apar în timpul execuţiei programelor)
anumite limbaje de programare oferă suport adecvat. Aş include aici limbaje
precum Delphi, C++, Java, Visual C.
Nu toate compilatoarele de C++ oferă suport, dar standardul ANSI C++ cere
acest lucru în mod explicit. Compilatoarele din familia Borland incepând cu
versiunea 4.0 oferă acest suport.
Esenţialul din punctul de vedere al programatorului C++ este ca el să-şi formeze
abilitatea de a scrie în jurul aplicaţiilor cod C++ care îndeplineşte funcţia de
handler de excepţii.
Suportul sintactic C++ pentru tratarea excepţiilor se rezumă la trei cuvinte cheie,
a căror semantică preliminară o prezentăm în Tabelul xxxxx.

Cuvântul cheie Semnificaţie


try Delimitează o porţiune de cod în care se instituie
controlul sistemului asupra excepţiilor în timpul rulării.
throw Lansează o excepţie de un anumit tip
catch Captează o excepţie lansată
Tabelul 18 Cuvintele cheie ale limbajului C++ referitoare la tratarea excepţiilor

Forma de bază a tratării excepţiilor


Aşadar, atunci când programele dumneavoastră efectuează prelucrarea
excepţiilor, trebuie să includeţi în cadrul unui bloc try instrucţiunile pe care doriţi
să le monitorizaţi în vederea unei excepţii. Dacă execuţia unei instrucţiuni se
termină cu o eroare, trebuie să lansaţi o eroare corespunzătoare acţiunii funcţiei
în care se află instrucţiunea. Programul plasează instrucţiunea throw în cadrul
blocului try-catch. Forma generalizată a blocului care captează şi tratează
erorile este:

try{
//blocul try
//if(eroare) throw valoare_excepţie;
}
catch (Tip_excepţie Nume_variabilă ){
//Prelucrarea excepţiei
}

113
În cadrul acestei forme generalizate, valoarea valoare_excepţie lansată trebuie
să corespundă tipului Tip_excepţie .

Scrierea unui handler de excepţii simplu


Pentru a înţelege mai bine semantica unui handler de excepţii, studiaţi
programul de mai jos.

#include <iostream.h>
void main()
{
cout<<"Start"<<endl;
try
{
cout<<"In interiorul blocului try…"<<endl;
throw 100;
cout<<"Nu se va executa…";
}
catch (int i)
{
cout<<"Am captat o excepţie --valoarea este:";
cout<<i <<endl;
}
cout<<"Sfarsit…";
};

Programul de mai sus implementează un bloc try-catch simplu. În loc să se


aştepte ca programul să eşueze datorită unei erori, se utilizează instrucţiunea
throw pentru lansarea erorii prezumtive. După ce blocul try lansează eroarea,
blocul catch o captează şi prelucrează valoarea transmisă de instrucţiunea
throw. Este evident şi din acest exemplu că mecanismul try-throw-catch oferă
suport pentru rezolvarea problemei tratării excepţiilor dar nu rezolvă de la sine
această problemă. Altfel spus, tratarea corectă a excepţiilor unui program
este o problemă de atitudine ca proiectant şi ca programator.

Lansarea excepţiilor cu o funcţie din cadrul blocului try


Atunci când programele apelează funcţii din cadrul blocurilor try , C++ va
transmite excepţia apărută într-o astfel de funcţie în afara funcţiei dacă nu există
un bloc try în interiorul funcţiei.
Exemplul de mai jos arată cum se petrec lucrurile într-o astfel de situaţie.

#include <iostream>
void XHandler(int test)
{
cout<<"Inauntrul functiei XHandler, test are
valoarea:"<<test<<endl;
if(test) throw test;

114
};

void main()
{
cout<<"Start:"<<endl;
try
{
cout<<"Inauntrul blocului try…"<<endl;
XHandler(1);
XHandler(2);
XHandler(0);
}
catch(int i)
{
cout<<"Am captat o exceptie. Valoarea este:";
cout<<i<<endl;
};
cout<<"Sfarsit";
};

Plasarea unui bloc try intr-o funcţie


Am văzut cum apare un bloc try în funcţia principală a unui program. C++
permite blocuri try şi în alte funcţii ale unui program diferite de funcţia principală.
Atunci când se plasează un bloc try într-o funcţie C++ reiniţializează blocul de
fiecare dată când intraţi se intră în acea funcţie. Programul următor ilustrează
cele spuse.

#include <iostream.h>
void XHandler(int test)
{
try
{
if(test) throw test;
}
catch(int i)
{
cout<<"Am captat exceptia nr.: "<<i<<endl;
}
};
void main()
{
cout<<"Start: "<<endl;
XHandler(1);
XHandler(2);
XHandler(0);
XHandler(3);
cout<< "Sfarsit";

115
};

Un comentariu pe marginea celor prezentate până acum ar fi următorul: o


instrucţiune catch se execută numai dacă programul lansează o excepţie în
cadrul blocului try situat imediat înainte. În caz că o astfel de excepţie nu se
lansează blocul catch va fi ignorat.

Utilizarea mai multor instrucţiuni catch cu un singur


bloc try
Pe măsură ce tratările excepţiilor devin tot mai complexe, uneori este necesar şi
posibil ca un singur bloc try să lanseze excepţii de mai multe tipuri. În cadrul
programelor dumneavoastră puteţi construi un handler de excepţii astfel încât să
accepte captarea mai multor excepţii. Într-o astfel de situaţie sintaxa generală
este:

try
{
//instrucţiuni
}
catch (tip1)
{
//tratare excepţie 1
}
catch(tip2)
{
//tratare excepţie 2
}
:
catch(tipn)
{
//tratare excepţie n
}

Cu acest amendament sintactic deducem că instrucţiunile catch pot capta orice


tip returnat, nu numai tipurile de bază acceptate de C++. Acest "fenomen" este
ilustrat în codul de mai jos.

#include <iostream.h>
void XHandler(int test)
{
try
{
if(test==0)
throw test;
if(test==1)
throw "Sir de caractere…";

116
if(test==2)
throw 121.25;
}
catch(int i)
{
cout<<"Am captat exceptia #:"<<i<<endl;
}
catch(char *sir)
{
cout<<"Am captat exceptia de tip sir de
caractere:"<<sir<<endl;
}
catch(double d)
{
cout<<"Am captat exceptia #:"<<d<<endl;
}
};

void main()
{
XHandler(0);
XHandler(1);
XHandler(2);
cout<<"Sfarsit";
};

Blocuri catch generice (utilizarea operatorului puncte de


suspensie)
Programele scrise de dumneavoastră pot capta excepţii din cadrul mai multor
blocuri try (de exemplu un bloc try care incapsuleaza mai multe functii care
lanseaza exceptii diferite din blocuri try diferite sau să utilizeze mai multe
instrucţiuni catch într-un singur bloc try. C++ permite, de asemenea, utilizarea
operatorului puncte de suspensie (…) pentru a capta orice tip de eroare care
apare într-un singur bloc try. Sintaxa care permite captarea tuturor erorilor care
apar într-un bloc try este prezentată mai jos.

try
{
//Instructiuni
}
catch(…)
{
//tratarea exceptiei
}

117
Pentru exemplificare propun codul de mai jos.

#include <iostream.h>
void XHandler(int test)
{
try
{
if(test==0)
throw test;
if(test==1)
throw 'a';
if(test==2)
throw 121.25;
}
catch(…)
{
cout<<"Am captat o exceptie"<<endl;
}
};

void main()
{
cout<<"Start:"<<endl;
XHandler(0);
XHandler(1);
XHandler(2);
cout<<"Sfarsit";
};

Evident, prelucrările din cadrul blocului catch generic trebuie să fie independente
de tipul erorii.
Mecanismul captării excepţiilor explicite poate fi combinat cu mecanismul
excepţiilor generice ca în exemplul de mai jos.

#include <iostream.h>
void XHandler(int test)
{
try
{
if(test==0)
throw test;
if(test==1)
throw 'a';
if(test==2)
throw 121.25;
}
catch(int i)

118
{
cout<<"Am captat o exceptie de tip intreg…"<<endl;
}
catch(…)
{
cout<<"Am captat o exceptie generica"<<endl;
}
};

void main()
{
cout<<"Start:"<<endl;
XHandler(0);
XHandler(1);
XHandler(2);
cout<<"Sfarsit";
};

Restrictionarea exceptiilor
Pe măsură ce programele dumneavoastră devin mai complexe, ele vor apela
frecvent funcţii din cadrul unui bloc try. Atunci când programele dumneavoastră
apelează funcţii dintr-un bloc try, puteţi restricţiona tipurile de excepţii pe care
funcţia apelată le poate lansa. De asemenea puteţi preveni lansarea oricărei
excepţii dintr-o anumită funcţie.

Sintaxa pentru restricţionare este:

tip_returnat nume_functie(lista_arg) throw(lista_tipuri )


{
//Cod functie
}

Sintaxa care inhibă lansare oricărei excepţii este:

tip_returnat nume_functie(lista_arg) throw()


{
//Cod functie
}

Este bine să subliniem că atunci când declaraţi o funcţie cu clauza throw ea


poate să lanseze doar acele tipuri precizate în listă. Dacă funcţia lansează orice
al tip programul este abortat.
Un exemplu în continuare.

119
#include <iostream.h>
void XHandler(int test) throw(int, char, double)
{
if(test==0)
throw test;
if(test==1)
throw 'a';
if(test==2)
throw 121.25;
}

void main()
{
cout<<"Start:"<<endl;
try
{
XHandler(0);
}
catch(int i)
{
cout<<"Am captat un intreg…"<<endl;
}
catch(char c)
{
cout<<"Am captat un caracter…"<<endl;
}
catch(double d)
{
cout<<"Am captat un double…"<<endl;
}
cout<<"Sfarsit";
};

Relansarea unei excepţii


În anumite situaţii poate fi necesar să se relanseze o excepţie din interiorul unui
handler de excepţii. Dacă relansaţi o excepţie, C++ o va transmite unui bloc try
exterior dacă acesta există. Cea mai probabilă situaţie în care puteţi opta pentru
această variantă este atunci când doriţi să trataţi o excepţie în cadrul a două
programe handler distincte. Pentru mai multă claritate urmăriţi exemplul de mai
jos.

#include <iostream.h>
void XHandler(void)
{
try
{

120
throw "Salve…";
}
catch(char *)
{
cout<<"Am captat char* in XHandler… "<<endl;
throw;
}
void main()
{
cout<<"Start…"<<endl;
try
{
XHandler();
}
catch(char *)
{
cout<<"Am captat char * in main…"<<endl;
}
cout<<"Sfarsit…";
};

Mod de utilizare a excepţiilor


Toate elementele prezentate au încercat să demonstreze că C++ are o atitudine
activă faţă de problema tratării excepţiilor. Suportul oferit de C++ îl ajută pe
programator să definească un comportament al programului când se produc
evenimente anormale sau neaşteptate. O idee mai pragmatică de utilizare a
suportului C++ în situaţii efective o puteţi desprinde din exemplul de mai jos.

#include <iostream.h>
void div (double a, double b)
{
try
{
if(!b) throw b;
cout<<"a/b="<<a/b<<endl;
}
catch(double b)
{
cout<<"Nu se poate imparti la zero…"<<endl;
}
}

void main()
{
double i,j;
do

121
{
cout<<Introduceti numaratorul (0 pentru stop):"<<endl;
cin i;
cout<<Introduceti numitorul :"<<endl;
cin j;
div(i,j);
} while (i!=0);
};

122
III Câteva aplicaţii care “au în spate”
C sau C++…

123
1 Sortarea elementelor unui şir de numere reale prin metoda bulelor.
Program pretext pentru modularizare. Se utilizează fişierul antet facilcrt.h
prezentat în continuarea acestei secţiuni.

#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include"facilcrt.h"
#include<string.h>

#define _lms 100


#define _optiune "123"

void prelsir();
char prelopt();
void sort();

int dims;
float x[ _lms];

//Functia principala..
void main()
{
char op;
do
{
op=prelopt();
switch (op)
{
case '1': prelsir();
break;
case '2': sort();
break;
}
}
while (op!='3');
}

//Preluare optiuni program...


char prelopt()

{
char c;
do
{
textcolor(RED);

124
textbackground(CYAN);
clrscr();
gotoxy(20,8) ;cprintf("Optiunile programului sunt:");
gotoxy(20,9) ;cprintf("1-Preluare elemente sir");
gotoxy(20,10);cprintf("2-Sortare/afisare sir");
gotoxy(20,11);cprintf("3-Terminare program");
makewin2(20,8,60,11);
makewin2(20,14,60,14);
gotoxy(20,14);cprintf("Optiunea Dvs.:");
c=getch();
}
while (strchr(_optiune,c)==NULL);
return c;
};

//Preluare elemente sir...


void prelsir()
{
int i;
clrscr();
makewin2(20,5,60,5);
gotoxy(20,5);
printf("Dimensiune sir...");
gotoxy(20+strlen("Dimensiune sir..."),5);
scanf("%d",&dims);
makewin2(20,8,60,7+dims);
gotoxy(20,8);
for (i=0;i<dims;i++)
{
gotoxy(20,wherey());
printf("Element[%d]=",i);
scanf("%f",&x[i]);
}
};

//Determinare maxim...
void sort()
{
float aux;
int i,sortat;

//Sortare elemnete sir


do
{
sortat=1;
for(i=0;i<dims-1;i++)
{

125
if (x[i]>x[i+1])
{
aux=x[i];
x[i]=x[i+1];
x[i+1]=aux;
sortat=0;
}
}
}
while(!sortat);

//Afisare sir sortat


clrscr();
makewin2(20,5,60,5);
gotoxy(20,5);
printf("Sirul sortat este...");
makewin2(20,8,60,7+dims);
gotoxy(20,7);
for (i=0;i<dims;i++)
{
gotoxy(20,wherey()+1);
printf("Element[%d]= %6.2f",i,x[i]);
}
getch();
};

126
2 Fşier antet care încapsulează câteva facilităţi de lucru în mod text, utile
pentru realizarea unor interfeţe agreabile ale programelor cu utilizatorii.
Numit în exemplul precedent prin facilcrt.h.

#include <conio.h>
#include <stdio.h>
#include <string.h>
#define css 1

//Afisare text dinspre capete...


void converg(int end,int linie,char *mesaj)
{
int i,j,ls,cs;
ls=(end-strlen(mesaj))/2;
cs=ls+strlen(mesaj)-1;

for (i=ls,j=cs;i<=j;i++,j--)
{
gotoxy(i,linie);
printf("%c",mesaj[i-ls]);
delay(50); //prototipul in fisierul antet <dos.h>
gotoxy(j,linie);
printf("%c",mesaj[strlen(mesaj)-1-cs+j]);
}
getch();
}

//Activare video-invers
void avideo()
{
textcolor(BLACK);
textbackground(WHITE);
}

//dezactivare video-invers
void dvideo()
{
textcolor(WHITE);
textbackground(BLACK);
}

//Afisare centrata text in interiorul unei linii


void acentext(int ls, int ld, int linia, char *sir)
{
int sw;
int col;
sw=(ls>=1) && (ls<=79) && (ld<=80) && (ld>=2) && (ls<ld);

127
sw=sw && ((ld-ls+1)>=strlen(sir));
if (sw)
{
col=ls+(ld-ls+1-strlen(sir))/2;
gotoxy(col,linia);
cprintf(sir);
}
}

//Construire fereastra cu rama dreptunghiulara de


//dimensiuni specificate
void makewin2(int ass, int oss, int adj, int odj)
{
short int i;
int sw;
sw=(ass>0) && (ass<81) && (adj>0) && (adj<81) &&
(ass<=adj);
sw=sw && (oss>0) && (oss<25) && (odj>0) && (odj<25) &&
(oss<=odj);
if(sw)
{
for(i=ass;i<=adj;i++)
{
gotoxy(i,oss-1);
printf("\315");
gotoxy(i,odj+1);
printf("\315");
}
for(i=oss;i<=odj;i++)
{
gotoxy(ass-1,i);
printf("\272");
gotoxy(adj+1,i);
printf("\272");
}
gotoxy(ass-1,oss-1);
printf("\311");
gotoxy(adj+1,oss-1);
printf("\273");
gotoxy(ass-1,odj+1);
printf("\310");
gotoxy(adj+1,odj+1);
printf("\274");
}
else
{
gotoxy(1,24);

128
printf("Coordonate ecran eronate!!!");
getch();
}
}

129
3 Aplicaţie pretext pentru lucru cu tablouri, folosind pointeri.
Mai precis, codul prezentat mai jos rezolvă următoarele probleme:
-alocarea dinamică a memoriei pentru un tablou bidimensional cu
număr de linii şi coloane precizate;
-citirea elementelor unui tablou bidimensional;
-transpunerea unei matrice;
-afişarea elementelor unei matrice;

#include <conio.h>
#include<iostream.h>
int i,j;

//Alocare dinamica a memoriei pentru o matrice de intregi


//Numarul de linii si coloane sunt precizate prin lista de
//parametri

void almemmat(int **p,int nl,int nc)


{
for(i=0;i<nl;i++)
p[i]=new int[nc];
};

//Citirea elementelor matricei de intregi de la tastatura


void citmat(int **p,int nl,int nc)
{
for(i=0;i<nl;i++)
for(j=0;j<nc;j++)
{
gotoxy(10,12);
cout<<"Elementul["<<i<<","<<j<<"]=";
cin>>p[i][j];
};
};

//Afisarea matricei de intregi


void afismat(int **p,int nl,int nc)
{
for(i=0;i<nl;i++)
{
for(j=0;j<nc;j++)
cout<<p[i][j]<<" | ";
cout<<endl;
};
}

//Transpunerea matricei
void transpun(int **p,int **q,int nl,int nc)

130
{
for(i=0;i<nl;i++)
for(j=0;j<nc;j++)
q[j][i]=p[i][j];
};

//Functia principala
void main()
{
int **pmat;
int **q;
int nl=6,nc=2;
clrscr();
almemmat(pmat,nl,nc);
almemmat(q,nc,nl);
citmat(pmat,nl,nc);
transpun(pmat,q,nl,nc);
afismat(q,nc,nl);
getch();
};

131
4 Sortarea unui şir de numere întregi prin metoda HEAP_SORT. Se
utilizează reprezentarea liniară a unui arbore binar.

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

const int MAX_LIST_SIZE=100;


typedef int element;
typedef element list_type[MAX_LIST_SIZE];
typedef int boolean;

void walk_down(list_type list, int j, int n)


{
int i,k;
element ref;
boolean found_spot=0;
i=j;
ref=list[i];
k=2*i+1;
while((k<n) && !found_spot)
{
if(k<n-1)
if(list[k+1]>list[k])
++k;
if (list[k]>ref)
{
list[i]=list[k];
i=k;
k=2*i+1;
}
else
found_spot=1;
}
list[i]=ref;
}

void heap_sort(list_type list, int n)


{
int y;
element temp;
y=n/2-1;
while (y>=0)
{
walk_down(list,y,n);
--y;
}
y=n;

132
while(y>0)
{
temp=list[0];
list[0]=list[y-1];
list[y-1]=temp;
--y;
walk_down(list,0,y);
}
}

void main()
{
list_type list;
int i,nrel;
clrscr();
gotoxy(20,10);
cout<<"Numar de elemente in lista:";
cin>>nrel;
clrscr();
for(i=0;i<nrel;i++)
{
gotoxy(20,11);
clreol();
cout<<"Elementul[ "<<i<<" ]=";
cin>>list[i];
}
heap_sort(list,nrel);
clrscr();
for(i=0;i<nrel;i++)
cout<<list[i]<<endl;
getch();
}

133
5 Rasfoirea unui fişier text şi contorizarea apariţiilor unui caracter într-un
fisier text utilizând fluxuri binare.

#include <fstream.h>
#include <stdlib.h>
#include "facilcrt.h"

char optiuni[]="1234";
char *nf;
int sfs;

//Semnatura functie prelopt()


char prelopt();

//Semnatura functie selnumef()


char *selnumef();

//Semnatura functie rasfft()


void rasfft(char *n);

//Semnatura functie detstatcar()


void detstatcar();

//Implementare functie principala


void main()
{
sfs=0;
do
{
switch(prelopt())
{
case '1': nf=selnumef();
break;
case '2': rasfft(nf);
break;
case '3': detstatcar();
break;
case '4': exit(0);
}
}
while (2>1);
}

//Implementare functie detstatcar()


void detstatcar()
{
char c;

134
int cont;
clrscr();
gotoxy(20,10);
cout<<"Caracterul:";
makewin2(20,10,60,11);
gotoxy(20+strlen("Caracterul:"),10);
cin>>c;
if(sfs)
{
ifstream in(nf,ios::in | ios::binary);
if(!in)
{
sfs=0;
gotoxy(1,24);
textcolor(RED+BLINK);
cprintf("Fisierul indicat nu poate fi deschis...");
getch();
sfs=0;
textcolor(WHITE);
}
else
{
char ch;
cont=0;
while(in)
{
in.get(ch);
if(ch==c)
cont++;
}
gotoxy(20,11);
cout<<"Numar de aparitii:"<<cont;
getch();
}
}
else
{
gotoxy(1,24);
textcolor(RED+BLINK);
cprintf("Selectati mai intai fisierul...");
getch();
textcolor(WHITE);
}
}

//Implementare functie selnumef()


char *selnumef()

135
{
char *n;
sfs=1;
clrscr();
gotoxy(20,10);
cout<<"Numele fisierului de rasfoit:";
makewin2(15,10,65,10);
do
{
strcpy(n,"");
gotoxy(20+strlen("Numele fisierului de rasfoit:"),10);
gets(n);
}
while(strlen(n)==0);
return n;
}

//Implementare functie rasfft()


void rasfft(char *n)
{
if(sfs)
{
//Deschid fluxul binar in in citire
ifstream in(n, ios::in | ios::binary);
if(!in)
{
sfs=0;
gotoxy(1,24);
textcolor(RED+BLINK);
cprintf("Fisierul indicat nu poate fi deschis...");
getch();
sfs=0;
textcolor(WHITE);
}
else
{
clrscr();
char ch;
while(in)
{
//Citesc un caracter din flux
in.get(ch);
if (wherey()>20)
{
gotoxy(1,24);
cout<<"Pentru continuare apasati o tasta...";
getch();

136
clrscr();
}
cout<<ch;
}
getch();
}
}
else
{
gotoxy(1,24);
textcolor(RED+BLINK);
cprintf("Selectati mai intai fisierul...");
getch();
textcolor(WHITE);
}
}

//Implementare functie prelopt()


char prelopt()
{
clrscr();
gotoxy(20,8);
cout<<"Optiunile programului...";
makewin2(20,8,60,8);
gotoxy(20,11);
cout<<"1-Selectare fisier text";
gotoxy(20,12);
cout<<"2-Rasfoire fisier text";
gotoxy(20,13);
cout<<"3-Contorizare aparitii caracter specificat";
gotoxy(20,14);
cout<<"4-Terminare program";
gotoxy(20,15);
cout<<" Alegerea Dvs.:";
makewin2(20,11,60,15);
gotoxy(20+strlen(" Alegerea Dvs.:"),15);
char op;
do
op=getch();
while (strchr(optiuni,op)==NULL);
return op;
}

137
6 Modelarea C a unor facilităţi de lucru cu memoria video în mod text.
Exemplul de cod prezentat mai jos ilustrează, în alt context, utilitatea
pointerilor, precum şi o serie de elemente tehnice referitoare la
scrierea/citirea în/din memoria video, atunci când se lucrează în mod text.

#ifndef _stdio_h
#include<stdio.h>
#define _stdio_h
#endif

#ifndef _conio_h
#include<conio.h>
#define _conio_h
#endif

#ifndef _string_h
#include<string.h>
#define _string_h
#endif

#define startcol1 16
#define startcol2 36
#define startcol3 56

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

//Variabila in care se pastreaza adresa memoriei video


char far *mem_vid;

int afcent(int lin,char s[80]);


void codasc();
void antet();
int mod_video();
void setvptr();
void scrie_car(int x,int y,char ch,int atrib);
void scrie_sir(int x,int y,char *p,int atrib);

//Functia returneaza modul video corespunzator


//echipamentului pe care se ruleaza programul
int mod_video()
{
union REGS r;
r.h.ah=15;
return int86(0x10,&r,&r)&255;
}

138
//Functia seteaza adresa memoriei video in functie de
//modul video curent
void setvptr()
{
int vmod;
vmod=mod_video();
if((vmod!=2)&&(vmod!=3)&&(vmod!=7))
{
cout<<"Video trebuie sa fie in modul text cu 80 de
coloane.";
exit(1);
}
if(vmod==7)
mem_vid=(char far *) 0xB0000000;
else mem_vid=(char far *)0xB8000000;
}

//Functia permite scrierea unui caracter direct


//in memoria video pe linie si coloana specificate
void scrie_car(int x,int y,char ch,int atrib)
{
char far *v;
v=mem_vid;
v+=(y*160)+(x-1)*2;
*v++=ch;
*v=atrib;
}

//Functia permite scrierea unui sir de caractere


//in memoria video pe linie si coloana specificata
void scrie_sir(int x,int y,char *p,int atrib)
{
register int i;
char far *v;
v=mem_vid;
v+=(y*160)+(x-1)*2;
for(i=y;*p;i++)
{
*v++=*p++;
*v++=atrib;
}
}

//Afisarea antetului tabelei de coduri ASCII


void antet()
{

139
gotoxy(1,1);
for(int i=0;i<78;i++)
printf("\xf");
gotoxy(16,2);printf("Cod Caracter");
gotoxy(36,2);printf("Cod Caracter");
gotoxy(56,2);printf("Cod Caracter\n");
for(i=0;i<78;i++)
printf("\xf");
}

//Afisare coduri ASCII pe trei coloane


void codasc()
{
int nrcan=2;
int i,j,cc;
int lin=1;
int col=1;
clrscr();
antet();
for(i=0;i<256;i++)
{
if(lin>19)
if(col==3)
{
gotoxy(1,24);
gotoxy(1,23);
for(j=0;j<78;j++)
printf("\xf");
printf("\nESC-pentru terminare\\Alta tasta pentru
continuare...");
if(getch()=='\033') return;
col=lin=1;
clrscr();
antet();
}
else
{
lin=1;
col++;
}
switch(col)
{
case 1:
{
cc=startcol1;
break;
}

140
case 2:
{
cc=startcol2;
break;
}
case 3:
{
cc=startcol3;
break;
};
}
lin++;
gotoxy(cc,nrcan+lin);printf("%i",i);
gotoxy(cc+7,nrcan+lin);printf("%c",(char)i);
};
gotoxy(1,23);
for(j=0;j<78;j++)
printf("\xf");
gotoxy(1,24);
printf("Pentru terminare apasati o tasta....");
getch();
}

//Afisare centrata text pe o linie specificata


int afcent(int lin,char s[80])
{
int col;
int parer=0;
if((lin<1)||(lin>24))
parer++;
if(!strlen(s))
parer=parer+2;
if(parer)
return parer;
col=(80-strlen(s))/2;
gotoxy(col,lin);
printf("%s",s);
return parer;
}

141
7 Modelarea C++ a operaţiilor cu şiruri de caractere. Problemă pretext
pentru aplicarea corectă a principiului încapsulării, pentru utilizarea în
funcţie de cerinţe a constructorilor în specificarea comportamentului unei
clase, pentru redefinirea unor operatori uzuali în lucru cu şiruri de
caractere (concatenarea polimorfică, adresarea unui caracter al tabloului
de caractere, etc.).

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

//*****************************************************
//Clasa modeleaza lucrul cu siruri de caractere in C++
//class string
{
int lsir; //lungimea sirului
char *psir; //tablou de caractere alocat dinamic
public:

//Constructor clasic; stringul contine sirul vid


string(int l=1);

//Constructor de copiere a unui obiect deja creat


string( string& x);

//Constructor de copiere care utilizeaza un sir


//obisnuit
string(char sir[]);

//Redefinire operator atribuire pentru copiere obiect


void operator=(string x);

//Redefinire operator atribuire pentru copiere sir


void operator=(char sir[]);

//Redefinire operator de testare egalitate intre doua


//siruri
int operator==(string & x);

//Redefinire operator de comparare >


int operator>(string & x);

//Redefinire operator de comparare <


int operator<(string & x);

//Concatenarea a doua siruri

142
string operator+(string & x);

//Redefinire operator + ca functie prietena


friend string operator+(string &x, char s[]);

//Redefinire operator + ca functie prietena


friend string operator+(char s[],string &x);

//Destructor clasa
~string();

//Returnare lungime sir ca functie prietena


friend int length(string &x);

//Extragere caracter din sir


char & operator[](int i);

//Extragere sir din obiect ca functie prietena


friend char * sir(string& x);
};

//*****************************************************
//Implementare functii membre

//Implementare constructor creare sir vid


string::string(int)
{
lsir=1;
psir=new char[1];
*psir=0;
}

//Implementare constructor de copiere obiect deja creat


string::string( string & x)
{
lsir=x.lsir;
psir=new char[lsir];
strcpy(psir,x.psir);
}
//****************************************************
//Implementare constructor de copiere sir obisnuit
string::string(char sir[])
{
lsir=strlen(sir)+1;
psir=new char[lsir];
strcpy(psir,sir);
}

143
//Implementare redefinire operator atribuire obiect
void string::operator=(string x)
{
delete psir;
lsir=x.lsir;
psir=new char[lsir];
strcpy(psir,x.psir);
}

//Implementare redefinire operator atribuire pentru copiere


//sir
void string::operator=(char sir[])
{
delete psir;
lsir=strlen(sir)+1;
psir=new char[lsir];
strcpy(psir,sir);
}

//Implementare redefinire operator de testare egalitate


//siruri
int string::operator==(string & x)
{
if(!strcmp(psir,x.psir))
return 1;
else
return 0;
}

//Implementare redefinire operator de comparare >


int string::operator>(string & x)
{
if (strcmp(psir,x.psir)>0)
return 1;
else
return 0;
}

//Implementare redefinire operator de comparare <


int string::operator<(string & x)
{
if (strcmp(psir,x.psir)<0)
return 1;
else
return 0;
};

144
//Implementare concatenare a doua siruri
string string::operator+(string & x)
{
string s;
strcat(s.psir,psir); strcat(s.psir,x.psir);
s.lsir=lsir+x.lsir-1;
return s;
}

//Implementare destructor
string::~string()
{
delete psir;
}

//Implementare redefinire operator []


char &string::operator[](int i)
{
return psir[i-1];
}

//implementare extragere sir ca functie prietena


char * sir(string &x)
{
return x.psir;
};

//Implementare redefinire operator de concatenare


//Varianta obiect+sir
string operator+(string &x, char s[])
{
string a;
strcat(a.psir,x.psir);
strcat(a.psir,s);
a.lsir=x.lsir+strlen(s);
return a;
};

//Implementare redefinire operator de concatenare


//Varianta sir+obiect
string operator+(char s[],string &x)
{
string a;
strcat(a.psir,s); strcat(a.psir,x.psir);
a.lsir=x.lsir+strlen(s);
return a;

145
};

146
8 Aplicatie care ilustreaza lucrul cu fisiere cu tip in C++
Din nou o problemă pretext, care se referă la actualizarea unui fişier în care
se păstrează întrebările unui test şi răspunsurile corecte, presupunând că o
întrebare are mai multe alternative de răspuns.

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

char optiuni[]="1234567";
char *nf;
int sfs,nri;

//Tipul inregistrare
typedef struct struct_test
{
int nr_intrebare;
int alt_corecta;
} t_inr_fis;

//Variabila inregistrare
t_inr_fis inr;

//Functia permite preluarea optiunilor programului


char prelopt();

//Functia permite selectarea numelui fisierului de lucru


char *selnumef();

//Functia permite crearea fisierului


void crefis();

//Functia permite adaugarea de inregistrari in fisier


void adinfis();

//Functia permite steregerea unei inregistrari din fisier


void steinfis(int nrint);

//Functia permite vizualizarea inregistrarilor fisierului


void vizfis();

void selin(int ni);


//Implementare functie principala

147
void main()
{
char op;
sfs=0;
do
{
op=prelopt();
switch(op)
{
case '1': nf=selnumef();
break;
case '2': crefis();
break;
case '3': adinfis();
break;
case '4': {
clrscr();
gotoxy(10,12);
cout<<"Numar intrebare inregistrare de
sters:";
cin>>nri;
steinfis(nri);
break;
};
case '5': vizfis();
break;
case '6': {
clrscr();
gotoxy(10,12);
cout<<"Numar inregistrare de selectat:";
cin>>nri;
selin(nri);
break;
};
case '7': break;
}
}
while (op!='7');
};

void selin(int ni)


{
if (sfs)
{
ifstream ftestin;
ftestin.open(nf,ios::in|ios::binary);
ftestin.seekg((ni-1)*sizeof(t_inr_fis),ios::beg);

148
ftestin.read((unsigned char*) &inr,sizeof(t_inr_fis));
if(!ftestin.eof())
{
clrscr();
gotoxy(10,12);
cout<<"Nr. intrebare :";
cout<<inr.nr_intrebare;
gotoxy(10,13);
cout<<"Alternativa corecta:";
cout<<inr.alt_corecta;
getch();
ftestin.close();
};
}
else
{
gotoxy(1,24);
cout<<"Selectati sau creati mai intai un fisier!....";
getch();
}
};

void steinfis(int nrint)


{
ifstream ftestin;
ofstream fmanout;
if(!sfs)
{
cerr<<"Fisier neselectat....";
getch();
}
else
{
fmanout.open("man",ios::out|ios::binary);
clrscr();
ftestin.open(nf,ios::in|ios::binary);
while (!ftestin.eof())
{
ftestin.read((unsigned char*)&inr,sizeof(t_inr_fis));
if ((inr.nr_intrebare!=nrint)&&(!ftestin.eof()))
fmanout.write((unsigned char*) &inr,
sizeof(t_inr_fis));
};
fmanout.close();ftestin.close();
};
};

149
//Implementare crefis()
void crefis()
{
ofstream ftestout;
clrscr();
gotoxy(20,10);
cout<<"Nume fisier de creat:";
cin>>nf;
ftestout.open(nf,ios::out|ios::binary);
sfs=1;
char ras;
do
{
clrscr();
gotoxy(20,10);cout<<"Numar intrebare:";
cin>>inr.nr_intrebare;
gotoxy(20,11);cout<<"Raspuns corect :";
cin>>inr.alt_corecta;
ftestout.write((unsigned char *) &inr,
sizeof(t_inr_fis));
gotoxy(20,12);cout<<"Mai aveti de introdus(D,N):";
ras=getch();
}
while (!(toupper(ras)=='N'));
ftestout.close();
}

//Implementare adinfis()
void adinfis()
{
if (sfs)
{
ofstream ftestout;
clrscr();
gotoxy(20,10);
ftestout.open(nf,ios::app|ios::binary);
char ras;
do
{
clrscr();
gotoxy(20,10);cout<<"Numar intrebare:";
cin>>inr.nr_intrebare;
gotoxy(20,11);cout<<"Raspuns corect :";
cin>>inr.alt_corecta;
ftestout.write((unsigned char *) &inr,
sizeof(t_inr_fis));

150
gotoxy(20,12);cout<<"Mai aveti de introdus(D,N):";
ras=getch();
}
while (!(toupper(ras)=='N'));
ftestout.close();
}
else
{
gotoxy(1,24);
cout<<"Selectati sau creati mai intai un fisier!....";
getch();
}
}

//Implementare vizfis()
void vizfis()
{
if (sfs)
{
ifstream ftestin;
ftestin.open(nf,ios::in|ios::binary);
while (!ftestin.eof())
{
clrscr();
ftestin.read((unsigned char*)&inr,sizeof(t_inr_fis));
if(!ftestin.eof())
{
gotoxy(10,12);
cout<<"Nr. intrebare :";
cout<<inr.nr_intrebare;
gotoxy(10,13);
cout<<"Alternativa corecta:";
cout<<inr.alt_corecta;
getch();
};
};
ftestin.close();
}
else
{
gotoxy(1,24);
cout<<"Selectati sau creati mai intai un fisier!....";
getch();
}
}

151
//Implementare selnumef()
char *selnumef()
{
char *n;
sfs=1;
clrscr();
gotoxy(20,10);
cout<<"Numele fisierului de lucru:";
do
{
strcpy(n,"");
gotoxy(20+strlen("Numele fisierului de lucru:"),10);
gets(n);
}
while(strlen(n)==0);
return n;
}

//Implementare prelopt()
char prelopt()
{
clrscr();
gotoxy(20,8);
cout<<"Optiunile programului...";
gotoxy(20,9);
cout<<"__________________________________________";
gotoxy(20,11);
cout<<"1-Selectare fisier structura test";
gotoxy(20,12);
cout<<"2-Creare fisier structura test";
gotoxy(20,13);
cout<<"3-Adaugare date in fisierul structura test";
gotoxy(20,14);
cout<<"4-Stergerea unei inregistrari din fisier";
gotoxy(20,15);
cout<<"5-Vizualizare date fisier structura";
gotoxy(20,16);
cout<<"6-Afisare inregistrare de numar specificat";
gotoxy(20,17);
cout<<"7-Terminare program";
gotoxy(20,19);
cout<<"__________________________________________";
gotoxy(20,20);
cout<<" Alegerea Dvs.:";
gotoxy(20+strlen(" Alegerea Dvs.:"),20);
char op;

152
do
op=getch();
while (strchr(optiuni,op)==NULL);
return op;
};

153
BIBLIOGRAFIE MINIMALĂ
[1]. N. Barkakati Borland C++ 4, Ghidul programatorului,
Editura Teora, 1997
[2]. K., Jamsa, L. Klander Totul despre C şi C++, Editura Teora,
2000
[3]. I. Muşlea C++ pentru avansaţi, Editura
Microinformatica, 1994
[4]. L. Negrescu Limbajele C şi C++ pentru începători,
Limbajul C++, volumul 2, Editura Albastră,
Cluj-Napoca, 2000
[5]. L. Negrescu Limbajele C şi C++ pentru începători,
Limbajul C, volumul 1, Editura Albastră,
Cluj-Napoca, 2000
[6]. D. M. Popovici, C++. Tehnologia orientată pe obiecte.
I. M. Popovici, Aplicaţii,
I. Tănase Teora, 1996

[7]. Herbert Schildt C++. Manual complet,


Teora, 1998
[8]. B.Silaghi Din tainele programării în C++, Editura
Albastră, 1996

Lista cărţilor din care se poate învăţa câte ceva despre C++ si lumea
POO este deschisă, la fel de deschisă ca mintea oricărui student care nu
“şi-a greşit culoarul” pe care aleargă pentru a deveni un profesionist.

154
Subiecte pentru examenul la disciplina
Programare Obiect Orientată, Anul II INFO

FUNDAMENTE

ÖLocul limbajului C/C++ printre celelalte limbaje de programare.


Elemente caracteristice.

ÖTipuri de date fundamentale. Modificarea tipurilor de date


fundamentale.

ÖVariabile în C/C++. Variabile locale. Variabile globale.


Modelatori de acces. Specificatori de clase de stocare.

ÖOperatori în C/C++.

ÖReprezentarea structurilor de prelucrare în C/C++. Structuri


secvenţiale.

ÖReprezentarea structurilor de prelucrare în C/C++. Structuri


alternative.

ÖReprezentarea structurilor de prelucrare în C/C++. Structuri


repetitive.

ÖTablouri C/C++.

ÖPointeri. Relaţia pointeri-matrice. Alocarea dinamică a


memoriei.

ÖStructuri de date în programarea C/C++. Înregistrări şi câmpuri


de biţi.

155
ÖUniuni.

PARADIGMA POO

ÖConcepte în programarea obiect orientată.

ÖPrincipii în programarea obiect orientată.

ÖImplementarea conceptelor şi principiilor OO în C++.

ÖConstructori, destructori, crearea obiectelor în C++.

ÖMetodele virtuale şi polimorfismul în programarea OO în C++.

PROGRAMARE AVANSATĂ ÎN C/C++

ÖSupraîncărcarea funcţiior în programarea C/C++.

ÖSupraîncărcarea operatorilor în programarea C++.

ÖFuncţii inline. Funcţii prietene.

ÖFluxuri C/C++.

ÖProgramarea generică în C/C++. Funcţii generice.

ÖProgramarea generică în C/C++. Clase generice.

ÖProgramarea excepţiilor în C/C++.

156
Scurte consideraţii relativ la “problema evaluării” la
disciplina POO

 Exemplu de formulare a subiectelor pentru


examenul scris la disciplina POO II INFO

ÖSupraîncărcarea operatorilor în programarea C++.

ÖCe este o clasă abstractă?

ÖDefiniţi noţiunea de flux.

ÖTratarea excepţiilor în C++.

ÖDefiniţi o clasă care încapsulează proprietăţile necesare pentru


a simula operaţii cu şiruri de caractere. Implementaţi trei metode,
dintre care una să fie inline.

 De citit, cel puţin, cu răbdare


Câteva cuvinte despre felul în care vede autorul prezentului suport
de curs întâlnirea cu studentul în timpul sesiunii.
Indiferent de forma de evaluare, întâlnirea student- profesor, în
sesiune, ar trebui să fie reciproc avantajoasă. Profesorul are de
câştigat multe, dacă face efortul de a vedea în student nu doar un
subiect al examenului, ci un partener care s-a hotărât să se
confrunte cu el însuşi. Studentul trebuie să aibă înţelepciunea de a
vedea în profesor, în cel mai rău caz, un sfătuitor care nu-şi
cunoaşte limitele. Autorul prezentului suport de curs îşi cunoaşte
limitele şi încearcă permanent să şi le depăşească. Acest exerciţiu
de autodepăşire şi-l poate asuma şi studentul, cu încredinţarea că o

157
face pentru a confirma superioritatea omului în raport cu alte
vieţuitoare şi nu numai.
Pentru a mă face mai bine înţeles, în cele ce urmează voi “arunca”
câteva idei care ar vrea să sugereze modul în care, ca profesor,
înţeleg să evaluez lucrarea unui student.

...Lucrarea unui student este o “mică operă” care vorbeşte despre


limitele autorului ei în momentul în care acesta a realizat-o.
Limitele acestei opere nu se referă, exclusiv, la ortografie, ci şi la:
• Modul în care este structurat “discursul” de răspuns la
fiecare subiect în parte.
• Calitatea viziunii punctuale a studentului, pentru fiecare
subiect în parte.
• Calitatea viziunii sistemice a studentului; conexiunile
între subiect şi restul universului nu fac decât să arate,
odată în plus, inteligenţa acestuia.

Aşadar, ideea este că, pentru un profesror normal, fiecare student


este un potenţial creator de “bijuterii” în materie de cugetare pe
teme impuse, condiţie necesară pentru a realiza în viitor “bijuterii”
în materie de cugetare şi de altă natură, pe teme liber alese sau
impuse de viitoarea profesie.

158

S-ar putea să vă placă și