Sunteți pe pagina 1din 154

Universitatea “Transilvania” Comment [DB1]:

Facultatea de Matematică şi Informatică


Catedra de Informatică

Note de curs despre…

Programarea obiect orientată în C++


(incluzând bazele programării în C şi
aplicaţii)

Dorin Bocu
2002-2003
Î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 de la specializarea informatică 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

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

3
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ă 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.

4
 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 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)

5
{
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.

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.

6
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

În această secţiune sunt definite


Secţiunea de definire a tipurilor şi constante sau tipuri de date necesare în
constantelor globale program

Antet program principal


În această secţiune se fac toate
Secţiunea declarativă declaraţiile de variabile ale programului

În această secţiune se amplasează


Secţiunea executabilă 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 să rezolve anumite probleme. Această
nouă abordare o vom dezbate treptat în secţiunile care vor urma.

7
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.

8
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.

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

9
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 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.

10
Î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.

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()
{

11
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 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;

12
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:”);
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++.

13
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.

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);

14
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 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ă.

15
Variabile globale statice
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.

16
Constante C
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*/

Constante de tip şir

17
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 “:=”. Membrul stâng al atribuirii trebuie să
fie o variabilă sau un pointer, nu o funcţie sau o constantă.

18
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;

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:

19
<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++;
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

20
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.

De ordinul cel mai înalt !


>, >=, <, <=
==, !=
&&
De ordinul cel mai coborât ||
Tabelul 8 Relaţia de precedenţă între operatorii relaţionali şi logici

21
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>

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”.

22
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:

 Se evaluiază <Expresie_1>;
 Dacă <Expresie_1> este adevărată se evaluiază <Expresie_2> şi rezultatul
evaluării ei i se atribuie expresiei globale;
 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.

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


 permit funcţiilor C să modifice parametrii de apelare;
 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.

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.

23
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)


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>)

24
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++
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

25
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.

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

26
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.

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.

27
<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!!!");
}

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");

28
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();
}
}

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>;
:

29
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>
}

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.

30
#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.
-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:

31
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;
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…

32
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


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]);
}

33
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.

#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:

34
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");
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()

35
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.

36
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().

37
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);
}
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>

38
#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.

#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:

39
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.

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()
{

40
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;
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

41
%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.

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

42
Î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.

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

43
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.

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);

44
cprintf("Apasati o tasta pentru a sterge ecranul...");
getch();

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

//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)

45
{
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;
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

46
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.

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 :

47
float sir[10];

putem crea un pointer la primul element al matricei astfel:

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];

48
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]);
}
//Apelare functie cu parametru matrice transmisa ca pointer static
sespdp(matr);

49
}

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);
}
}
}
char prelopt()

50
{
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++)
{
gotoxy(20,10);
cprintf("Elementul [ %d ]=",i);
cscanf("%f",&sir1[i]);
}

51
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);
};
}

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:

52
#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.

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>;

53
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.
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;

54
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
}
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

55
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 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();

56
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ă.

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>);

57
} <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;
};
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;

58
} 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()
{
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>

59
typedef struct
{
unsigned bit :1;
unsigned :7;
unsigned :7;
unsigned bits:1;
} TOctet;
TOctet Octet;
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();
}

60
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.

61
II Programarea C++

62
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

63
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.
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.

64
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


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.

65
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.

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>

66
<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).

#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();
};

67
// 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 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
{

68
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


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;

69
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ă.
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.

70
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;}
}

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>

71
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
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.

72
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.

 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();

73
};
};

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;
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 .

74
Î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:

<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:

<Ponter>=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>

75
#define SIZE 100

// 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()

76
{
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 clasă derivată oferă operaţiile efective pe care le

77
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
void ExecPolim(CB *p);

void main()
{

78
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


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.

79
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();
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)
{

80
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


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 clasaclass Clasa2;class Clasa1{ //<ocupat> daca o metoda a clasei a scris pe
ecran
//<liber> in caz contrar
int stare;

81
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;
}

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

82
{
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();
};
}
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

83
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.
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;
};

84
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;
};

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 ->:";

85
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.
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.

86
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

87
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:

 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.

88
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 cerr Ecran
Tabelul 15. Fluxurile predefinite C++

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

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

89
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 folosit
cin>>setfill(‘ ‘); 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
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++

90
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()
{
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++

91
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 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);

92
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.

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;

93
}
in.close();
}

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


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);
}

94
//Citeste din sursa si scrie in destinatie
char c;
while(ins.get(c) && outs) outs.put(c);
}
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();
while(!in.eof())

95
{
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 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.

96
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.

3 Programare generică în C++

97
Î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;
}

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]>

98
Nume_şablon
definiţie_şablon

Sintaxa la utilizare este:

Nume şablon<Expresie_1[, …,Expresie_n]>;


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ă).
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;

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.);

99
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()
{
clrscr();
int i=0;

//***************************************
//Citire persoane de la tastatura
do
{
gotoxy(20,12);
cout<<"Numele persoanei:";clreol();
cin>>pers[i].np;
gotoxy(20,13);

100
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;
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;
};
};
};

101
}
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ă.

Î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:

102
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;
};

//***************************************
//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];
};

//***************************************

103
//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>
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;

104
};
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;
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()

105
{
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();

//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();
}

106
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.

107
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
}
Î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…";
};

108
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;
};

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ă.

109
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";
};

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
}

110
:
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…";
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)

111
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
}

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";
};

112
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)
{
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 )

113
{
//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.

#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;
}

114
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
{
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)

115
{
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
{
cout<<Introduceti numaratorul (0 pentru stop):"<<endl;
cin i;
cout<<Introduceti numitorul :"<<endl;
cin j;
div(i,j);
} while (i!=0);
};

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

117
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;

118
case '2': sort();
break;
}
}
while (op!='3');
}

//Preluare optiuni program...


char prelopt()

{
char c;
do
{
textcolor(RED);
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]);

119
}
};

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

//Sortare elemnete sir


do
{
sortat=1;
for(i=0;i<dims-1;i++)
{
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();
};

120
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);

121
}

//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;
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");

122
}
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);
printf("Coordonate ecran eronate!!!");
getch();
}
}

123
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)
{

124
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)
{
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();
};

125
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)

126
{
int y;
element temp;
y=n/2-1;
while (y>=0)
{
walk_down(list,y,n);
--y;
}
y=n;
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();
}

127
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;

128
case '4': exit(0);
}
}
while (2>1);
}

//Implementare functie detstatcar()


void detstatcar()
{
char c;
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
{

129
gotoxy(1,24);
textcolor(RED+BLINK);
cprintf("Selectati mai intai fisierul...");
getch();
textcolor(WHITE);
}
}

//Implementare functie selnumef()


char *selnumef()
{
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();

130
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();
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;

131
do
op=getch();
while (strchr(optiuni,op)==NULL);
return op;
}

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);

132
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;
}

//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;

133
for(i=y;*p;i++)
{
*v++=*p++;
*v++=atrib;
}
}

//Afisarea antetului tabelei de coduri ASCII


void antet()
{
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
{

134
lin=1;
col++;
}
switch(col)
{
case 1:
{
cc=startcol1;
break;
}
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);

135
return parer;
}

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 <

136
int operator<(string & x);

//Concatenarea a doua siruri


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;

137
psir=new char[lsir];
strcpy(psir,sir);
}

//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;

138
};

//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);

139
return a;
};

140
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
void main()
{
char op;
sfs=0;

141
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);
ftestin.read((unsigned char*) &inr,sizeof(t_inr_fis));
if(!ftestin.eof())
{
clrscr();

142
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();
};
};

//Implementare crefis()
void crefis()
{

143
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));
gotoxy(20,12);cout<<"Mai aveti de introdus(D,N):";
ras=getch();
}
while (!(toupper(ras)=='N'));

144
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();
}
}

//Implementare selnumef()
char *selnumef()
{

145
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;
do
op=getch();
while (strchr(optiuni,op)==NULL);
return op;

146
};

BIBLIOGRAFIE MINIMALĂ

147
[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.

Subiecte de meditaţie importante pentru cei care


studiază disciplina Programare Obiect Orientată din
perspectivă C++
148
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.

Uniuni.

PARADIGMA POO

Concepte în programarea obiect orientată.

149
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++.

Scurte consideraţii relativ la “problema evaluării” la


disciplina POO

 Evaluarea studenţilor la POO se va face cu ajutorul a două lucrări de


verificare; prima lucrare urmăreşte verificarea cunoştinţelor referitoare la bazele

150
programării în C, cea de-a doua lucrare urmărind verificarea cunoştinţelor
referitoare la programarea în spirit obiect orientat utilizând suportul C++.
Conţinutul lucrărilor va fi prezentat şi discutat în cadrul tutorialelor planificate.

 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 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,

151
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.

LUCRARE DE VERIFICARE PE PARCURS NR. 1


Programare Obiect Orientată
Semestrul I/2002-2003

152
Subiect teoretic
Suportul C++ pentru programarea generică

Subiect aplicativ

Să se scrie codul C++ care simulează comportarea unui browser de texte


cod ASCII.
Funţiile minimale ale browser-ului:
-Selectarea unui fişier text de lucru.
-Crearea unui fişier text.
-“Răsfoirea” unui fişier text.
-Căutarea unui cuvânt specificat în fişierul text selectat.
-Alte funcţii, la alegere.

Conf. Dr. Dorin Bocu

LUCRARE DE VERIFICARE PE PARCURS NR. 2


Programare Obiect Orientată
Semestrul I/2002-2003

153
Subiect teoretic
Fluxuri C/C++. Lucrul cu fişiere în C++.

Subiect aplicativ

Să se scrie codul C++ care simulează lucrul cu şiruri de caractere,


cunoscut fiind faptul că în C/C++ nu există suport natural pentru lucrul cu
şiruri de caractere.
Astfel, se vor defini mecanisme obiect orientate cu ajutorul cărora să
putem avea acces la operaţii asupra şirurilor de caractere, obişnuite în alte
limbaje de programare: atribuirea unui şir de caractere către o variabilă de
un tip convenabil (redefinire operator =), concatenarea a două şiruri de
caractere (redefinire operator +), extragerea unui subşir de caractere
dintr-un şir de caractere (redefinire operator -), recuperarea lungimii unui
şir de caractere, etc.

Conf. Dr. Dorin Bocu

154