c sau c++…

158
Universitatea “Transilvania” Facultatea de Matematică şi Informatică Catedra de Informatică Note de curs despre… Programarea obiect orientată în C++ (incluzând şi bazele programării în C) Dorin BoKu 2001-2002

Upload: hoangbao

Post on 29-Jan-2017

222 views

Category:

Documents


3 download

TRANSCRIPT

Page 1: C sau C++…

Universitatea “Transilvania” Facultatea de Matematică şi Informatică Catedra de Informatică

Note de curs despre…

Programarea obiect orientată în C++

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

Dorin BoKu 2001-2002

Page 2: C sau C++…

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

Paradigma programării obiect orientate Universul C++ ale cărui origini vin dinspre C şi

dinspre începuturile dintotdeauna ale programării.

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

Autorul

2

Page 3: C sau C++…

I BAZELE C++. LIMBAJUL C

3

Page 4: C sau 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 de dezvoltare complex, la care putem spune că a participat întreaga comunitate preocupată de ridicarea performanţelor în munca de programare. De fapt, strămoşi direcţi ai limbajului C pot fi considerate limbajele BCPL (creat de Martin Richards) şi limbajul B (inventat de Ken Thompson). În anii ’70, limbajul B a influenţat în cel mai înalt grad specificarea limbajului C. Mult timp, standardul de facto pentru C a fost versiunea ce însoţea sistemul de operare UNIX. Acest standard a fost descris pentru prima dată în cartea The C Programming Language, scrisă de Brian Kernigan şi Dennis Ritchie, apărută în 1978 la editura Prentice Hall. Odată cu creşterea popularităţii calculatoarelor personale, au fost create numeroase implementări de C, ceea ce a generat o problemă nouă: compatibilitatea implementărilor. Pentru rezolvarea acestei probleme, în vara anului 1983, a fost înfiinţat un comitet pentru crearea unui standard ANSI (American National Standard Institute) care avea ca sarcină specificarea definitivă a limbajului C. Standardul ANSI C a fost adoptat în decembrie 1989, primele copii devenind disponibile la începutul lui 1990. La ora actuală, toate compilatoarele C/C++ se aliniază la standardul ANSI C. Totodată standardul ANSI C este o bază pentru propunerea de standard ANSI C++(Bazat pe limbajul C, C++ adaugă extinderi care permit programarea obiect orientată). Evident, vom reveni asupra topicii C++. 1.2 Locul limbajului C în lumea limbajelor de programare Specialiştii consideră C un limbaj de nivel mediu. Această apreciere nu presupune neapărat că C este mai puţin performant decât limbajele de nivel înalt, precum BASIC sau PASCAL. De asemenea, nu trebuie să concluzionăm că C are dificultăţile unui limbaj de asamblare. C este considerat un limbaj de nivel mediu deoarece combină cele mai bune facilităţi ale unui limbaj de nivel înalt cu posibilităţile de control şi flexibilitatea unui limbaj de asamblare. Astfel, ca limbaj de nivel mediu C permite lucrul cu biţi, octeţi şi adrese – elemente de bază pentru funcţionarea calculatorului. Cu toate acestea, codul C rămâne în foarte mare măsură portabil. Despre un sistem soft spunem că este portabil dacă în momentul în care este scris pe o anumită maşină, sub un anumit mediu de operare, poate fi uşor adaptat pe altă maşină, eventual cu alt sistem de operare. Tot ca nişte trăsături specifice limbajului C semnalăm următoarele:

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

4

Page 5: C sau C++…

beneficiază de un cadru mult mai liberal (controalele compilatorului nu mai sunt atât de severe ca în Pascal).

Spre deosebire de un limbaj de nivel înalt, C nu face aproape nici un control în timpul executării unui program. Responsabilitatea de a evita producerea erorilor şi de a le dibui şi corecta, dacă le-a ocazionat, este a programatorului.

C permite lucrul direct cu biţi, octeţi cuvinte şi pointeri ceea ce îl face potrivit pentru programare la nivel de sistem.

Numim programare la nivel de sistem activitatea de scriere a codului pentru anumite componente ale sistemului de operare sau care sunt accesorii ale sistemelor de operare.

C are doar 32 cuvinte-cheie (27 din standardul Kernigan & Ritchie şi cinci adăugate de comitetul de standardizare ANSI C). Limbajele de nivel înalt depăşesc cu mult oferta limbajului C. Faptul că are atât de puţine cuvinte cheie ar trebui să fie benefic pentru procesul de învăţare a limbajului. Ceea ce se învaţă greu în C este “democraţia” prea mare în comparaţie, de exemplu, cu stilul autoritar în care compilatorul Pascal asistă scrierea de programe corecte. 1.3 C este limbaj structurat Deoarece nu oferă suport pentru implementarea autentică a conceptului de structură – în – blocuri, C este numit, simplu, limbaj structurat. C nu este limbaj <structurat în blocuri> deoarece nu permite definirea de funcţii în interiorul altor funcţii, situaţie absolut normală şi cu efecte interesante asupra modularizării codului Pascal, de exemplu. Caracteristica esenţială a limbajelor structurate este compartimentarea codului şi a datelor, prin care se înţelege capacitatea unui limbaj de a separa şi ascunde de restul programului toate informaţiile şi instrucţiunile necesare efectuării unei sarcini. O modalitate de realizare a compartimentării este utilizarea de subrutine care folosesc variabile locale. Utilizând variabile locale putem scrie modulele astfel încât ceea ce se întâmplă în interiorul lor să nu aibă efecte în alte secţiuni ale programului. C permite compartimentarea în acest spirit, utilizând potenţialul noţiunii de funcţie.

Utilizarea excesivă a variabilelor globale, în orice limbaj de programare structurat, permite greşelilor să se strecoare în program, sub forma efectelor secundare neprevăzute. Evident, un limbaj de programare structurat descurajează sau interzice utilizarea instrucţiunii goto. Un alt mod de a structura şi de a compartimenta codul, în C, este utilizarea blocurilor de cod. Un bloc de cod este un grup de instrucţiuni alăturate logic, tratate ca un singur element. Blocurile de cod permit reprezentarea multor

5

Page 6: C sau C++…

algoritmi cu limpezime, eleganţă şi eficienţă. În C un bloc de cod poate fi creat incluzând între acolade un grup de instrucţiuni. Dacă în Pascal am avea: . if I<10 then begin gotoxy(12,I);

write(‘Valoarea curentă a lui I:’,I); end;

. atunci în mod echivalent, în C avem: . if (I<10) { gotoxy(12,I); cout<<”Valoarea curentă a lui I:”<<I; } . Blocul de cod este delimitat de acolade. L-am evidenţiat folosind efectul de umbrire (shading) a textului. 1.4 Relaţia limbajului C cu programatorul Specialiştii în limbaje de programare afirmă ceea ce, de fapt, s-a avut în vedere la specificarea unor limbaje: acestea nu sunt pentru programatori. Limbaje precum COBOL sau BASIC au fost specificate nu pentru a îndulci soarta programatorilor sau pentru a mări siguranţa în exploatare a codului creat, ci pentru a lărgi accesul celor mulţi la înţelegerea programelor sau chiar la scrierea de programe pentru a rezolva probleme simple. Din contră C (ca şi C++) a fost creat, influenţat şi testat de către adevăraţii programatori. Rezultatul final este că C oferă programatorului exact ceea ce îşi doreşte: restricţii puţine, structurare, blocuri de cod, funcţii de sine stătătoare şi un set compact de cuvinte cheie. De asemenea faptul că poate fi utilizat în locul limbajelor de asamblare îi sporeşte limbajului C popularitatea printre programatori. 1.5 Componentele de bază ale unui program C În Tabelul 1 prezentăm cuvintele-cheie, care, utilizate conform regulilor sintactice specifice, permit scrierea de programe în C. Obişnuinţa de a scrie programe în alte limbaje de programare poate fi de un real folos în C. Astfel, multe din abilităţile cultivate ca autori de programe Pascal sunt transpuse, cu unele modificări de sintaxă sau semantică şi în C. Diferite compilatoare de C pot beneficia de extinderi sintactice care permit programelor să beneficieze de avantajele unor medii de operare particulare.

6

Page 7: C sau C++…

Auto Break Case Char Const Continue Default Do

double else enum extern float for goto if

Int Long Register Return Short Signed Sizeof Static

struct switch typedef union unsigned void volatile while

Tabelul 1 Cele 32 cuvinte-cheie definite de standardul ANSI C Din cele 32 cuvinte-cheie prezentate în Tabelul 1, cele prezentate cu caractere bold au fost adăugate de comitetul ANSI C, celelalte corespunzând standardului C original.

Toate cuvintele-cheie în C se scriu cu litere mici . De asemenea, este important să ştim că în C literele mari diferă de cele mici. Astfel că, în timp ce for este cuvânt-cheie, FOR nu este cuvânt-cheie.

Toate programele C constau din una sau mai multe funcţii a căror sintaxă şi semantică o vom prezenta în secţiunile următoare. În sfârşit, componentele unui program C simplu se grupează în următoarele secţiuni: Directive preprocesor Secţiunea de definire a tipurilor şi constantelor globale Antet program principal Secţiunea declarativă Secţiunea executabilă

În această secţiune se includ bibliotecile de care are nevoie programul În această secţiune sunt definite constante sau tipuri de date necesare în program În această secţiune se fac toate declaraţiile de variabile ale programului În această secţiune se amplasează versiunea cod C a algoritmului care rezolvă problema

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

7

Page 8: C sau C++…

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

8

Page 9: C sau C++…

2 Expresii în C În această secţiune vom trece în revistă propunerile limbajului C în ceea ce proveşte posibilitatea de a utiliza expresii. Toate limbajele de programare acordă o atenţie specială noţiunii de expresie deoarece, în ultimă analiză, orice transformare a datelor de intrare ale unui program este realizată prin intermediul uneia sau a mai multor expresii. Aşa cum vom descoperi pe parcurs, C este un limbaj mult mai flexibil decât alte limbaje în materie de definire şi utilizare a expresiilor. Ceea ce este foarte limpede în acest moment este faptul că expresiile sunt realizate cu ajutorul elementelor atomice numite date şi operatori. Datele pot fi reprezentate cu ajutorul constantelor sau prin intermediul variabilelor. Ajungem, evident, la problema tipurilor de date suportate de C. 2.1 Cinci tipuri de date de bază în C În C există cinci tipuri de date de bază: caracter (char), întreg (int), virgulă mobilă (float), virgulă mobilă dublă precizie (double) şi fără nici o valoare (void). Toate celelalte tipuri de date din C se bazează pe cele cinci tipuri de bază. Dimensiunea reprezentării şi domeniul valoric corespunzător pentru tipurile de date de bază pot să difere, în funcţie de tipul procesorului şi de modul de implementare a limbajului C. În toate cazurile, însă, un caracter se reprezintă pe un octet. Chiar dacă de multe ori un întreg se reprezintă pe doi octeţi, nu se poate conta pe această observaţie dacă dorim ca programele scrise să fie portabile. De fapt, însuşi standardul ANSI C, stipulează doar domeniul de cuprindere minimal al fiecărui tip de date, nu şi mărimea sa în octeţi. Formatul exact al valorilor în virgulă mobilă depinde de modul lor de introducere. Întregii corespund, în general, mărimii normale a unui cuvânt pe calculatorul respectiv. Valorile de tip char sunt, în general, utilizate pentru a memora valori definite de setul de caractere ASCII. Valorile care ies din acest domeniu sunt tratate în mod diferit de compilatoare. Domeniul valoric pentru float şi double va depinde de metoda folosită pentru a reprezenta numere în virgulă mobilă. Standardul ANSI C indică domeniul valoric pentru virgulă mobilă de la 1E-37 pâna la 1E+37. Evident, vor exista deosebiri atât în ceea ce priveşte ordinul de mărime cât şi în ceea ce priveşte precizia între cele două specii de virgulă mobilă. Numărul minim de cifre din punct de vedere al preciziei este precizat în Tabelul 2.

Tipul void declară explicit că o funcţie nu returnează nici o valoare sau crează pointeri generici.

9

Page 10: C sau C++…

Tip de dată Dimensiune reprezenatre în biţi

Domeniul valoric minimal

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.647signed long int 32 Similar cu long int unsigned long int 32 0..4.294.967.295 float 32 6 zecimale exacte double 64 10 zecimale exacte long double 80 10 zecimale exacte

Tabelul 2 Tipurile de date definite prin standardul ANSI C

2.2 Modificarea tipurilor de bază Exceptând tipul void, tipurile de bază pot fi precedate de diverşi specificatori de conversie. Un specificator de conversie se utilizează pentru a modifica tipul de bază în ideea de adaptare mai precisă la diferite situaţii. După cum rezultă şi din Tabelul 2, lista specificatorilor de conversie este: signed unsigned long short În mod evident, diferenţa între întregii cu semn şi întregii fără semn constă în modul în care se interpretează bitul de pe poziţia cea mai semnificativă. Dacă, de exemplu, indicaţi un întreg cu semn, compilatorul generează cod care presupune că bitul cel mai semnificativ va fi utilizat ca bit de semn (0 dacă numărul este pozitiv, 1 dacă numărul este negativ). Să mai menţionăm faptul că, în general, pentru reprezentarea întregilor negativi se foloseşte codul complementar (complementul faţă de 2). Detalii referitoare la caracteristicile codului complementar se pot obţine consultând lucrări de specialitate în care se dezbate problema codificării datelor numerice în sistemele de calcul. În acest sens se poate consulta lucrarea [4] care face o introducere accesibilă în problematica mai sus menţionată. 2.3 Nume de identificatori în C În C/C++ numele variabilelor, funcţiilor, etichetelor şi ale altor diverse obiecte definite de către utilizator sunt numite identificatori. Aceşti identificatori pot să aibă unul sau mai multe caractere. Primul caracter trebuie să fie obligatoriu o

10

Page 11: C sau C++…

literă sau o liniuţă de subliniere; următoarele pot fi litere, cifre sau liniuţa de subliniere. Exemple de identificatori: Corecţi Incorecţi număr_de pagini 1_valoare_returnată _limita1 ok! ok_ switch..conversie Standardul ANSI C stipulează că identificatorii pot avea orice lungime. Totuşi, nu toate caracterele sunt obligatoriu semnificative. Dacă identificatorul este implicat într-un proces de editare de legături externe, vor conta cel mult şase caractere. Aceşti identificatori, denumiţi nume externe, includ numele funcţiilor şi ale variabilelor globale care aparţin mai multor fişiere. Dacă fişierul nu este utilizat într-un proces de editare de legaturi externe, atunci vor fi semnificative cel mult 31 de caractere. Acest tip de identificator este denumit nume intern şi include, de exemplu, nume de variabile locale. De remarcat faptul ca în C++ nu există limite ale lungimii unui identificator şi toate caracterele sunt semnificative. Această diferenţă începe să conteze în momentul în care doriţi să convertiţi un program din C în C++. Pare evident, dar facem precizarea că un identificator C nu poate fi identic cu un cuvânt-cheie şi nu trebuie să aibă acelaşi nume ca o funcţie din biblioteca C sau C++. 2.4 Variabile C Aşa cum s-a aflat şi în alte împrejurări, o variabilă este numele unei locaţii de memorie utilizată pentru a păstra o valoare care poate fi modificată de program. Înainte de a fi utilizate în program, variabilele trebuie declarate. Declararea unei variabile în C are forma: <Tip> <Listă_de_variabile>; <Tip> trebuie să fie un tip de dată valid (predefinit sau definit de utilizator) precedat, eventual, de un specificator de conversie. <Listă_de_variabile> poate consta dintr-un nume de identificator sau mai multe nume de identificatori separate prin virgulă. Exemple de declaraţii de variabile: int nr_pag; char opt,ch; unsigned i, j; Variabilele se pot declara în trei moduri: în interiorul funcţiilor, în cadrul definiţiei parametrilor funcţiei şi în afara oricărei funcţii. Prin urmare, este vorba despre variabile locale, parametri formali şi variabile globale.

11

Page 12: C sau C++…

2.5 Variabile locale în C Variabilele declarate în interiorul unei funcţii sunt numite variabile locale. O parte din literatura C/C++ numeşte aceste variabile automatice. Ţinând cont de asemănarea evidentă cu variabilele locale din Pascal, de exemplu, vom folosi termenul de variabilă locală. Afirmaţia cea mai importantă referitor la variabilele locale este următoarea: “Variabilele locale sunt accesibile doar instrucţiunilor care sunt în interiorul blocului în care sunt declarate variabilele”. Alt amănunt important şi specific limbajului C este faptul că: “Variabilele locale există cât timp se execută blocul de cod în care sunt declarate”. Altfel spus, o variabilă locală este creată la începerea execuţiei blocului său şi este distrusă la încheierea execuţiei acestui bloc. Blocul de cod cel mai uzual în care se declară variabile este funcţia. Deoarece noţiunea de funcţie este centrală pentru fizionomia unui program C, prezentăm, în continuare, o viziune asupra unui program C care include şi noţiunea de funcţie. Directive preprocesor Declaraţii globale Declaraţie funcţie_1 : Declaraţie funcţie_n int main() { Declaraţii de date ale programului principal Instrucţiuni program principal Orice program C conţine obligatoriu o funcţie al cărei nume este main } Implementare funcţie_1 : Implementare funcţie_n Totodată să precizăm faptul că sintaxa de declarare a unei funcţii (prototip în C++), conform standardului ANSI C este : <Tip returnat> <Nume funcţie> (<Lista de parametri formali>) Toată această construcţie sintactică se mai numeşte şi <Antet funcţie>. În C clasic declararea unei funcţii permitea doar specificarea tipului returnat, problema eventualilor parametri aşteptaţi de funcţie având o rezolvare aparte, pe

12

Page 13: C sau C++…

care nu mai insistăm deoarece standardul ANSI C ca şi standardul ANSI C++ descurajează, respectiv interzice utilizarea căii clasice. În C++ declararea unei funcţii se face apelând la prototipuri. În sfârşit, implementarea unei funcţii înseamnă o construcţie sintactică de tipul: <Antet funcţie> { <Declaratii de date> <Instructiuni executabile> } Referindu-ne la standardul ANSI C/ANSI C++ putem spune că: <Nume funcţie> este un identificator valid. <Tip returnat> specifică tipul de dată asociat cu numele funcţiei. <Lista de parametri formali> constă din o serie de perechi de denumiri, separate între ele prin virgulă, unde prima denumire specifică un tip de dată iar cea de-a doua este numele parametrului formal. Parametrii formali care apar în antetul unei funcţii, în principiu, trebuie să corespundă ca tip cu parametrii actuali care apar la apelul unei funcţii.Când un parametru actual trimis unei funcţii nu se potriveşte exact, ca tip, cu parametrul formal corespunzător, compilatorul încearcă o conversie convenabilă. De exemplu, o dată de tip int trimisă unei funcţii care aşteaptă o dată de tip float este convertită la tipul float. Prezentăm, în continuare, câteva exemple.

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

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

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

13

Page 14: C sau C++…

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

Din exemplul de mai sus reţinem doar faptul că, în situaţia în care numărul preluat de la tastatură (cu ajutorul rutinei de uz general scanf ) este mai mare decât zero, atunci devine activ blocul de cod în care se declară variabila nume. Această variabilă este distrusă la ieşirea din acest bloc de cod. Prin urmare, un avantaj potenţial: variabilei nume i se alocă memorie numai dacă se activează blocul de cod, ceea ce înseamnă economie de memorie. Este absolut evident faptul că se declară într-un bloc de cod una sau mai multe variabile strict necesare în acest bloc de cod; în acest mod prevenim, într-o oarecare măsură, apariţia unor efecte secundare. Mai semnalăm o diferenţă importantă între modul de declarare a variabilelor locale în C faţă de C++. În C trebuie declarate toate variabilele locale la începutul blocului în care intenţionăm să le definim, înainte de orice instrucţiune executabilă. În C++ nu mai există această restricţie. Astfel că secvenţa de cod C: void f(void) { int i; i=10; int j; j=20; } este considerată greşită de un compilator C şi perfect acceptabilă de către un compilator C++. Deoarece variabilele locale sunt create şi distruse la fiecare intrare, respectiv ieşire din blocul în care au fost declarate, conţinutul lor se pierde odată cu părăsirea blocului. Acest lucru este important să se ştie când apelăm o funcţie. La apelarea ei sunt create variabilele locale iar la încheierea ei acestea sunt distruse, deci variabilele locale nu păstrează valorile lor între apelări. Putem determina compilatorul să păstreze aceste valori utilizând specificatorul de stocare static asupra căruia vom reveni. 2.6 Parametrii formali Dacă o funcţie urmează să folosească argumente, ea trebuie să declare variabilele pe care le acceptă ca valori ale argumentelor. Aceste variabile sunt denumite parametri formali ai funcţiei. Variabilele în cauză se comportă ca oricare altă variabilă locală din acea funcţie.

14

Page 15: C sau C++…

2.7 Variabile globale Variabilele globale, spre deosebire de cele locale, sunt cunoscute în tot programul şi pot fi utilizate de către orice zonă a codului. Totodată, ele îşi păstrază valoarea tot timpul execuţiei programului. Variabilele globale se crează prin declarare în afara oricărei funcţii. Orice expresie are acces la ele, indiferent de tipul blocului de cod în care se află expresia. De semnalat faptul că o variabilă globală este vizibilă în orice bloc de cod al programului cât timp în respectivul bloc de cod nu a fost declarată o variabilă cu acelaşi nume, de acelaşi tip. În caz afirmativ, în blocul de cod este prioritară referirea la variabila locală. Stocarea variabilelor globale este făcută de compilator într-o zonă de memorie, special alocată. Variabilele globale sunt utile atunci când mai multe funcţii ale aceluiaşi program folosesc aceleaşi date. Utilizarea excesivă a variabilelor globale ar trebui evitată deoarece: Ocupă memoria pe tot timpul execuţiei programului, nu doar când sunt necesare; Dacă o funcţie utilizează o variabilă globală în locul uneia locale, atunci funcţia îşi pierde din generalitate, bazându-se pe ceva exterior logicii ei; Variabile globale multe înseamnă efecte secundare necunoscute şi nedorite. 2.8 Modelatori de acces În C există doi modelatori de acces (const şi volatile) care introduc restricţii suplimentare asupra modului de apelare sau modificare a variabilelor. Aceşti modelatori trebuie să preceadă specificatorul de tip şi numele tipului de dată la care se referă. Variabilele de tip const nu pot fi modificate de programele în care sunt declarate. Acestor variabile li se permite, totuşi, să primească o valoare iniţială. Compilatorul poate să plaseze variabilele de acest tip în ROM (Read Only Memory). Evident, aceste variabilele de tip const pot fi utilizate în construcţia unor expresii. Tehnic vorbind, modelatorul const poate fi utilizat şi pentru a proteja obiectele trimise ca argumente unei funcţii, de modificări în interiorul acelei funcţii. Multe funcţii din biblioteca standard a limbajului utilizează const în declaraţiile lor de parametri. Ca un exemplu, funcţia strlen() are următorul prototip: size_t strlen( const char * s); Modelatorul volatile informează compilatorul că valoarea unei variabile poate fi modificată pe căi nedeclarate explicit de program. Aceasta înseamnă că este posibil următorul scenariu: ”Adresa unei variabile este transmisă unei rutine rezidente în memoria sistemului, această rutină fiind abilitată să modifice valoarea variabilei”. Evident, pentru programatori acesta este un mecanism extrem de practic şi interesant. Modelatorul volatile poate fi combinat cu modelatorul const făcându-se plauzibil scenariul: “Valoarea unei variabile poate

15

Page 16: C sau C++…

fi modificată de condiţii externe, fiind protejată de modificări accidentale în programul în care este declarată”. 2.9 Specificatori de clase de stocare Limbajul C admite patru specificatori de clase de stocare: extern static register auto Aceşti specificatori indică compilatorului modul în care trebuie să stocheze variabilele la care se referă. Cadrul sintactic general de utilizare a specificatorilor de clase de stocare este: <Specificator_de_stocare> <Tip> <Nume_variabilă>; extern Deoarece C/C++ permit secţiunilor separate ale unui program să fie compilate independent şi să li se editeze legăturile împreună, trebuie să existe o modalitate de a comunica tuturor fişierelor variabilele globale necesare programului. Scenariul căruia trebuie să îi facem faţă este următorul: “Cum putem informa toate fişierele care compun un program C despre variabilele globale utilizate?”. Soluţia este următoarea : Se declară variabilele globale într-un fişier, care este de preferat să conţină şi funcţia principală a programului, aceleaşi variabile declarându-se şi în toate fişierele care folosesc variabilele globale, însoţite de specificatorul extern. static Variabilele de tip static sunt variabile permanente în interiorul funcţiei sau fişierului în care se găsesc. Spre deosebire de variabilele globale, ele nu sunt cunoscute în afara funcţiei sau fişierului, dar îşi păstrează valoarea între două apelări. Această caracteristică este folositoare la scrierea de funcţii generice şi de bibliotecă , utilizabile de alte programe. Specificatorul static are efecte diferite asupra variabilelor locale şi globale. Variabile locale statice Aplicat unei variabile locale, specificatorul static informează compilatorul de dorinţa de a i se asocia variabilei un loc de stocare permanentă, similar celui asociat unei variabile globale. Diferenţa esenţială între variabilele locale statice şi o variabilă globală se referă la faptul că variabila locală statică rămâne cunoscută doar blocului în care a fost declarată. Altfel spus, o variabilă locală statică este o variabilă care îşi păstrează valoarea între apelurile funcţiei în care a fost declarată. Variabile globale statice

16

Page 17: C sau C++…

Aplicând specificatorul static unei variabile globale, cerem compilatorului să creeze o variabilă globală care este cunoscută doar în fişierul în care a fost declarată. Aceasta înseamnă că, deşi variabila este globală, rutine din alte fişiere nu au acces la ea şi nu îi pot modifica conţinutul. register Specificatorul de stocare register se aplică, prin tradiţie, doar variabilelor de tip int şi char. Totuşi standardul ANSI C îi dă definiţia astfel încât poate fi folosit pentru orice tip de variabilă. Iniţial, register cerea compilatorului să păstreze valoarea unei variabile într-un registru din CPU, nu în memoria RAM, unde variabilele sunt stocate, de regulă. Scopul unei astfel de cereri: spor de viteză în timpul operaţiilor asupra variabilei. Actualmente, definiţia specificatorului register a fost mult extinsă, acesta putând fi aplicat oricărui tip de variabilă. Standardul ANSI C stipulează, simplu, că register este o indicaţie dată compilatorului că obiectul vizat va fi utilizat preferenţial din punct de vedere al vitezei. Specificatorul se poate aplica doar variabilelor locale şi parametrilor formali. Prin urmare, nu sunt permise variabile globale de tip register. auto Specificatorul auto este, practic, nefolosit, fiind reclamat doar de motive de compatibilitate între codul C şi codul C++. 2.10 Iniţializarea variabilelor Forma generală a iniţializării unei variabile respectă sintaxa: <Tip> <Nume_variabilă> = <Constanta>; Exemple:

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

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

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

Variabilele locale care nu sunt iniţializate au valori necunoscute înainte de prima atribuire. Variabilele globale şi locale de tip static neiniţializate sunt iniţializate din oficiu cu zero. Evident, trebuie să discutăm în continuare despre problema reprezentării constantelor în programele C. Constante C

17

Page 18: C sau 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*/

18

Page 19: C sau C++…

Constante de tip şir C admite, cum era şi firesc şi constanta de tip şir de caractere. O constantă şir este o succesiune de caractere delimitată de ghilimele. A nu se confunda şirurile de caractere cu caracterele. ‘a’ şi “a” sunt constante de tipuri diferite( disticţie care, în Pascal, nu este operată sintactic, vorbind. Constante de tip backslash caracter Încadrarea constantelor de tip caracter între apostrofuri funcţionează pentru majoritatea caracterelor afişabile. Însă, altele, puţine la număr, sunt imposibil de introdus de la tastatură (constanta BEL, de exemplu). În acest scop C introduce constante speciale, de tip backslash caracter. C admite mai multe coduri backslash, cum rezultă şi din Tabelul 4. Pentru a se asigura portabilitatea programelor sunt indicate codurile backslash în locul codurilor lor ASCII. Codul Semnificaţia \b Backspace \f form feed (=Salt la pagină nouă) \n CR+LF \r CR \t tab orizontal \’’ Ghilimele \’ Apostrof \0 Null \\ Backslash \v tabulare verticală \a Alertă \N constantă în octal; N este constanta \xN constantă în hexazecimal; N este constanta

Tabelul 4 Coduri backslash în C 2.11 Operatori în C C dispune de foarte mulţi operatori. De fapt, C acordă acestora o importanţă mult mai mare în comparaţie cu alte limbaje. C defineşte, în esenţă, patru clase de operatori: aritmetici, relaţionali, logici şi de acţiune la nivel de bit. Pentru anumite sarcini, C are operatori suplimentari. Operatorul de atribuire Operatorul de atribuire poate fi folosit, în C, în cadrul oricărei expresii valide, lucru care nu este permis în majoritatea limbajelor de programare (inclusiv Pascal), care tratează operatorul de atribuire ca pe un caz de instrucţiune specială. Sintaxa de aplicare a operatorului de atribuire în C este: <Nume_variabilă> = <Expresie>; unde <Expresie> poate fi o constantă sau o construcţie legală de complexitatea cerută în context. Atenţie, se foloseşte “=” în loc de “:=”.

19

Page 20: C sau C++…

Membrul stâng al atribuirii trebuie să fie o variabilă sau un pointer, nu o funcţie sau o constantă. Conversii de tip la atribuire Când variabilele de un anumit tip sunt amestecate cu variabile de alt tip, au loc conversii de tip. Într-o instrucţiune de atribuire, regula de conversie este simplă: valoarea din membrul drept (al expresiei) al instrucţiunii de atribuire este convertită la tipul din membrul stâng. Pentru o mai bună înţelegere a “fenomenului”, fie exemplul de mai jos. int nri; char car; float nrr; void functie(void) { car=nri; /* Linia 1 */ nri=nrr; /* Linia 2 */ nrr=car; /* Linia 3 */ nrr=nri; /* Linia 4 */ } În Linia 1, în variabila car ajung primii 8 biţi cei mai puţin semnificativi ai variabilei nri. Dacă nri este cuprins între 0 şi 255, car şi nri vor avea valori identice ş.a.m.d.. Presupunând că sistemul de calcul are cuvântul de 16 biţi, Tabelul 5 prezintă posibilele pierderi de informaţie care pot să apară la conversia de tip în atribuiri. Tipul destinaţiei Tipul expresiei Posibile pierderi de informaţie signed char char Dacă valoarea este > 127, destinaţia

este negativă. char short int Cei mai semnificativi opt biţi char int Cei mai semnificativi opt biţi char long int Cei mai semnificativi 24 biţi int long int Cei mai semnificativi 16 biţi int float Partea zecimală şi, posibil, mai mult float double Precizie, rezultat, rotunjit double long double Precizie, rezultat, rotunjit

Tabelul 5 Tipuri de conversie frecvent utilizate

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

20

Page 21: C sau C++…

permite setarea la 0 a variabilelor x,y,z. Programatorii profesionişti folosesc masiv atribuirea multiplă pentru a obţine cod performant. Să mai adăugăm o observaţie interesantă pentru un programator profesionist. O atribuire de tipul: <Variabilă>=<Variabilă>+<Expresie> (1) este echivalentă semantic cu sintaxa: <Variabilă>+=<Expresie> (2) Deşi s-ar putea să vină “peste mână” unor programatori, aceştia trebuie să ştie că pentru compilator sintaxa (2) este de preferat sintaxei (1) din punct de vedere al calităţii codului generat. Aşadar, x=x+1 este totuna cu: x+=1. Operatori aritmetici Tabelul 6 prezintă operatorii aritmetici din C. Operator Acţiune - Scădere, de asemenea şi minus unar + Adunare * Înmulţire / Împărţire % Modul -- Decrementare ++ Incrementare

Tabelul 6 Operatori aritmetici în C

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

x++;

21

Page 22: C sau C++…

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

- (minus unar) *, /, %

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

Operatori relaţionali Operator Acţiune > Mai mare decât >= Mai mare sau egal < Mai mic decât <= Mai mic sau egal == Egal != Diferit

Operatori logici Operator Acţiune && AND (ŞI) || OR (SAU) ! NOT (NEGAT)

Tabelul 7 Operatorii relaţionali şi logici în C.

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

22

Page 23: C sau C++…

De ordinul cel mai înalt ! >, >=, <, <= ==, != &&

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

Evident, parantezele pot fi utilizate pentru a modifica ordinea firească de evaluare a unei expresii relaţionale şi/sau logice. Operatori de acţiune pe biţi Spre deosebire de multe alte limbaje, C admite un complement deplin cu operatorii de acţiune pe biţi. Deoarece C a fost proiectat să ia locul limbajului de asamblare pentru majoritatea sarcinilor, el trebuie să fie capabil să asigure multe operaţii care pot fi executate în asamblor, inclusiv asupra biţilor. Operaţiile asupra biţilor se referă la testare, iniţializare sau deplasare a biţilor existenţi într-un octet sau într-un cuvânt care corespund tipurilor de date char şi int şi variantelor acestora din standardul C. Operatorii de acţiune asupra biţilor nu pot fi utilizaţi asupra tipurilor float, double, long double,void. În Tabelul 9 prezentăm operatorii C pentru biţi. Operator Acţiune & AND | OR ^ OR exclusiv (XOR) ~ Complement faţă de 1 (NOT) >> Deplasare la dreapta << Deplasare la stânga

Tabelul 9 Operatorii de acţiune pentru biţi

Înţelegerea transformărilor efective care au loc la utilizarea operatorilor AND, OR şi XOR este legată de analiza în spiritul legilor logicii Boole a unei expresii de tipul: <Operand_1> <Operator><Operand_2> Acţiunea operatorului este aplicată bit cu bit operanzilor, rezultând o configuraţie de biţi asupra cărora se pot continua alte prelucrări. Operatorii de deplasare a biţilor >> şi << deplasează toţi biţii dintr-o variabilă la dreapta sau la stânga. Sintaxa asociată este: <Variabilă> >> <Număr_de_poziţii_de_shiftat> sau <Variabilă> << <Număr_de_poziţii_de_shiftat>

23

Page 24: C sau C++…

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

♦ Precizări referitoare la operatorii suplimentari pot fi găsite în [1]. Operatorul ? C propune un operator unar foarte puternic şi util care poate înlocui anumite instrucţiuni de forma “dacă – atunci – altfel”. Operatorul ? apare în expresii având forma generală:

<Expresie_1> ? <Expreasie_2> : < Expresie_3> unde:

-<Expresie_1>, <Expresie_2>, <Expresie_3> sunt expresii, cu precizarea că <Expresie_1> este o expresie condiţională;

-semantica construcţiei sintactice de mai sus este următoarea:

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.

24

Page 25: C sau C++…

O atribuire de tipul : adr=&Numar_Elemente; este utilă pentru a obţine în variabila adr adresa din memorie a variabilei Numar_Elemente. Această adresă este adresa locaţiei din memorie începând de la care se păstrează conţinutul variabilei Numar_Elemente. Al doilea operator pentru pointeri este * , oarecum complementar operatorului &. Şi operatorul * este unar, returnând valoarea din variabila localizată la adresa specificată. Astfel , după execuţia secvenţei: adr=&Numar_Elemente; nr=*adr ; variabila nr va conţine aceeaşi valoare cu variabila Numar_Elemente, presupunând că cele două variabile sunt compatibile ca tip. Evident, există, din punct de vedere al utilizatorului, primejdia de a utiliza confuz aceşti doi operatori, din moment ce * mai înseamnă şi “înmulţire” iar & mai înseamnă şi “şi logic” la nivel de biţi. Variabilele care păstrează pointeri trebuie declarate ca atare. Sintaxa de declarare a unei variabile pointer este: <Tip de bază> * <Nume_Variabila>; Exemplu #include <stdio.h> void main(void) { int destinatar,sursa; int *m; sursa=10; m=&sursa; destinatar=*m; printf (“%d”, destinatar); } Operatorii & şi * sunt utilizaţi pentru a introduce valoarea 10 în variabila destinatar . Operatorul sizeof (cu acţiune în timpul compilării)

25

Page 26: C sau C++…

sizeof este un operator unar utilizat în timpul compilării care returnează lungimea în octeţi a variabilei sau a specificatorului de tip dintre parantezele asociate de sintaxă operatorului. Sintaxa de utilizare a operatorului: sizeof ( <Nume_Variabila>) sau sizeof (<Specificator_ de_ tip>) De remarcat faptul că în cazul unei variabile este permisă şi sintaxa: sizeof <Nume_Variabila> Acest operator este util în procesul de realizare a aplicaţiilor portabile. Operatorii . (punct) şi -> (săgeată) Sunt utilizaţi pentru a realiza referirea la elementele individuale ale structurilor şi uniunilor din C. Aşa cum se va vedea, structurile şi uniunile sunt tipuri de date agregate la care se poate avea acces sub un singur nume. Operatorul punct este utilizat atunci când se lucrează cu structuri sau uniuni efective. Operatorul săgeată este folosit împreună cu un pointer la o structură sau la o uniune. Astfel, anticipând puţin, dacă avem codul:

struct tangajat { char nume[80]; int varsta;

float salariu; } angajat; struct tangajat *pangajat=&angajat;

are sens următorul cod:

angajat . salariu:=1100000; Această atribuire este echivalentă cu atribuirea:

pangajat->salariu=1100000; De remarcat faptul că în C există multe alte facilităţi puse la dispoziţia programatorilor, precum: conversia automată în expresii, forţarea tipurilor expresiilor utilizând modelatorii, utilizarea expresiilor prescurtate. 2.11 Comentarea programelor C/C++

26

Page 27: C sau 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 clrscr() /* Stergere ecran în mod text */ Acest tip de comentariu nu poate fi imbricat. Compilatoarele C++ acceptă şi comentarii care încep cu secvenţa //, dar se pot întinde pe o singură linie, astfel: clrscr() // Stergere ecran în mod text.

27

Page 28: C sau C++…

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

Să atragem atenţia asupra faptului că C++ adaugă elemente sintactice specifice pentru tratarea excepţiilor de către programator. Nu toate compilatoarele implementează, însă, acest suport important pentru a realiza aplicaţii robuste şi fiabile în C++. 3.1 Ce se înţelege prin adevărat şi fals în C? Mai multe instrucţiuni din C se bazează pe expresii condiţionale pentru a reprezenta un anumit tip de prelucrare. La fel ca în Pascal, sintaxa acestor instrucţiuni C face supoziţia că alegerea unei traiectorii de prelucrare depinde de rezultatul evaluării acestor expresii condiţionale. Teoretic, valorile admisibile pentru o expresie condiţională sunt adevărat şi fals. Punctul de vedere al limbajului C cu privire la adevăr şi fals este următorul: Un rezultat al evaluării egal cu zero este interpretat ca fals. Un rezultat nenul al evaluării este interpretat ca adevărat. Această schimbare de optică în ceea ce priveşte adevărul şi falsul este de bun augur pentru scrierea de cod flexibil şi eficient.

Propunerea de standard ANSI C++ defineşte un tip de dată boolean numit bool (care poate să aibă doar valorile adevărat şi fals. Ceea ce nu înseamnă, însă, că nu se păstrează punctul de vedere al limbajui C relativ la adevăr şi fals.

28

Page 29: C sau C++…

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

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

int magic; /* Numar generat aleator */ int ghicit; /* Numar banuit */ magic=rand(); clrscr(); printf("Ghiceste numarul generat aleator:\n"); printf("Numarul generat aleator este cuprins intre 0 si %d",RAND_MAX); scanf("%d",&ghicit); if (ghicit==magic) printf("Ati nimerit numarul generat!!!"); else printf("Nu ati nimerit numarul generat!!!");

}

29

Page 30: C sau C++…

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

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

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

}

30

Page 31: C sau C++…

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

if (<Expresie_1>) <Instrucţiune_1>;

else if (<Expresie_2>) <Instrucţiune_2>;

else if (<Expresie_3>) <Instrucţiune_3>;

: : else if (<Expresie_k>)

<Instrucţiune_k>; :

Reamintim că, în anumite situaţii, putem utiliza operatorul ? pentru a înlocui instrucţiunea if-else de forma: if (<Expresie>) <Expresie_1>; else <Expresie_2>; cu o construcţie de forma: <Expresie>?<Expresi_1>:<Expresie_2>; switch C permite reprezentarea structurilor alternative cu mai multe ramuri utilizând în acest scop instrucţiune switch având sintaxa: switch (<Expresie>) { case <Constanta_1>: <Secvenţa_de_instrucţiuni_1> break; case <Constanta_2>: <Secvenţa_de_instrucţiuni_2> break; : case <Constanta_k>: <Secvenţa_de_instrucţiuni_k> break; default <secvenţa_de_instrucţiuni> }

31

Page 32: C sau C++…

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

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

int x; char Ras;

clrscr(); x=random(2); switch(x+2){ case 3: { gotoxy(20,10); printf("Switch atins..."); break; } default: { gotoxy(20,10); printf("Switch ocolit..."); } } Ras=getch();

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

32

Page 33: C sau C++…

-Dacă în instrucţiunea switch sunt utilizate constante de tip caracter, ele sunt automat convertite în întregi. 3.3 Instrucţiuni iterative În C, ca şi în alte limbaje de programare evoluate, există enunţuri de limbaj pentru a reprezenta: structuri repetitive cu număr cunoscut de paşi (for); structuri repetitive cu număr necunoscut de paşi, anterior condiţionate (while); structuri repetitive cu număr necunoscut de paşi, posterior condiţionate (do-while); Structurile repetitive se mai numesc şi bucle. Bucla for (pentru structuri repetitive cu număr cunoscut de paşi…) Conceptul general de <buclă for> se reflectă într-o formă sau alta în orice limbaj de programare de nivel înalt. În C, acest concept are o implementare de o flexibilitate şi o putere neaşteptate. Sintaxa generală a instrucţiunii for este: for (<Iniţializare>;<Condiţie>;<Increment>) <Instrucţiune>; O astfel de sintaxă permite multe variante constructive ale buclei for. În general vorbind, însă: -<Iniţializare> este o instrucţiune de atribuire utilizată pentru a iniţializa variabila de control a buclei; -<Condiţie> este o expresie relaţională care determină condiţia de ieşire din buclă; -<Increment> defineşte modul în care se modifică variabila de control a buclei de fiecare dată când aceasta se repetă. De remarcat faptul că cele trei secţiuni trebuie separate prin punct şi virgulă. Aşadar, bucla for se execută cât timp <Condiţie> este adevărată. Dacă <Condiţie> devine falsă, atunci execuţia programului continuă cu instrucţiunea care urmează construcţiei for, dacă aceasta există. Maniera clasică de utilizare a buclei for în C, o prezentăm în exemplul de mai jos.

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

33

Page 34: C sau C++…

gotoxy(20,14); cout<<"Factorial("<<nr<<")="<<fact(nr); getch(); } long int fact(int n) { long int f; int i; f=1; /*------------------------------------------------------------------- /* Bucla for clasică cu o singură variabilă de control /*------------------------------------------------------------------- for (i=2;i<=n;i++) f=f*i; return f; }

Exemplul de cod C prezentat mai sus permite calculul factorialului pentru un număr natural dat. Exemplul arată, anticipând, modul de utilizare a conceptului de funcţie în C++. Câteva variaţiuni pe tema buclei for… Sintaxa C permite variante dintre cele mai neaştepate de definire a unor bucle for. Rigiditatea buclei for din Pascal este înlocuită în C cu o sintaxă care emană mai multă putere, flexibilitate şi aplicabilitate în situaţii specifice de programare. Una dintre cele mai folosite variaţiuni foloseşte operatorul virgulă, pentru a permite ca bucla să fie controlată de două sau mai multe variabile. De exemplu, variabilele x şi y controlează următoarea buclă şi amândouă sunt iniţializate în interiorul instrucţiunii for. :

for (x=0,y=0;x+y<10;++x) { y+=1; //Această scriere reste echivalentă cu y=y+1; printf(“Y= %d”,y); } : Prezentăm, mai jos, un exemplu practic de utilizare a unei bucle for controlată de două variabile: afişarea unui şir de caractere începând de la ambele capete, mergând către mijlocul şirului. Se observă prezenţa fişierului header <dos.h> în care se află prototipul funcţiei delay(). #include <conio.h> #include <stdio.h> #include <string.h> #include <dos.h>; //Semnatura functie converg

34

Page 35: C sau C++…

void converg(int linie, char *mesaj); //Functia principala void main() { clrscr(); gotoxy(20,12); converg(12,"Acesta este un test pentru functia converg()."); } //Implementare functie converg void converg(int linie, char *mesaj) { int i,j,ls,cs; ls=(80-strlen(mesaj))/2; cs=ls+strlen(mesaj)-1; for (i=ls,j=cs;i<=j;i++,j--) { gotoxy(i,linie);printf("%c",mesaj[i-ls]); delay(50); //prototipul in fisierul antet <dos.h> gotoxy(j,linie);printf("%c",mesaj[strlen(mesaj)-1-cs+j]); } getch(); } Ca un caz particular prezentăm şi sintaxa pentru o buclă infinită cu for…

for ( ; ; ) Bucla while (pentru structuri repetitive cu număr necunoscut de paşi condiţionate anterior…) A doua buclă disponibilă în C este bucla while. Forma sa generală este: while (<Condiţie>) <Instrucţiune>; unde <Instrucţiune> este o instrucţiune vidă, o instrucţiune sau un bloc de instrucţiuni.. Condiţia poate să fie orice expresie, fiind adevărată pentru orice valoare nenulă. Bucla se reia cât timp condiţia este adevărată. Când condiţia devine falsă, controlul programului trece la linia de cod următoare buclei, dacă aceasta există. Prezentăm un exemplu de cod în care bucla while este utilizată pentru a simula, eventual, execuţia unei secvenţe de instrucţiuni până când doreşte liberul arbitru al utilizatorului programului.

35

Page 36: C sau C++…

#include <stdio.h> #include <conio.h> #include <ctype.h> //Contine prototipul functiei toupper void main() { int sw; char ras; sw=1;

/* Bucla while în acţiune… while(sw) { gotoxy(20,12); printf("Continuam(D,N):"); ras=getch(); if (toupper(ras)=='N') sw=0; } }

Bucla do-while (repetitive cu număr necunoscut de paşi… condiţionate posterior…) Spre deosebire de buclele for şi while, care testează condiţia din buclă la începutul execuţiei lor, bucla do-while o verifică la sfârşit. Aceasta înseamnă că bucla do-while se execută cel puţin odată. Forma sa generală este: do { <Instrucţiune>; } while <Condiţie>; Bucla do-while se repetă până când <Condiţie> devine falsă. Dăm, mai jos, un exemplu practic de utilizare a buclei do-while (afişarea/ selectarea opţiunilor unui program C).

: char prelopt() { int c; clrscr(); gotoxy(20,9); printf( "Optiunile programului..."); gotoxy(20,11); printf("1-Preluare elemente vector"); gotoxy(20,12); printf("2-Determinare suma"); gotoxy(20,13); printf("3-Terminare program");

36

Page 37: C sau C++…

gotoxy(20,15); printf("Optiunea Dvs.:"); /* Utilizare buclă do-while…

do c=getch(); while ((c!='1')&&(c!='2')&&(c!='3')); return c; };

: Alte instrucţiuni C… Aşa cum rezultă şi din exemplele prezentate, în C mai sunt intens folosite următoarele instrucţiuni: return, exit, break , continue şi goto. Instrucţiunea return este utilizată pentru întoarcerea dintr-o funcţie. Este considerată ca instrucţiune de salt deoarece determină execuţia programului să revină la prima instrucţiune după funcţia apelată în care apare return. Forma generală a instrucţiunii return este:

return [<Expresie>]

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

Funcţia exit()

Este utilizată pentru ieşirea imediată dintr-un program, returnând, eventual un cod de retur. Sintaxa de apel este:

exit (<Cod_de_retur>);

<Cod de retur> poate fi interpretat de procesul care a apelat programul, de regulă, sistemul de operare. Exit acţionează ca un break generalizat.

Instrucţiunea break

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

Instrucţiunea continue

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

37

Page 38: C sau C++…

38

Page 39: C sau C++…

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

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

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

Funcţia getch() este utilizată pentru a citi un caracter de la tastatură fără ecou pe ecranul monitorului , analog mecanismului readkey din Pascal. Funcţia getche() este utilizată pentru a citi un caracter de la tastatură cu ecou pe ecranul monitorului. Funcţia putchar() se foloseşte pentru a scrie un caracter pe ecranul monitorului în poziţia curentă a cursorului. După cum sugerează prototipul, getch() returnează un întreg. Totuşi, funcţia poate returna această valoare unei variabile de tip caracter deoarece caracterul este conţinut în octetul de ordin inferior. Aceeaşi observaţie este valabilă şi pentru funcţia getche(). Deşi din alt punct de vedere, şi funcţia putchar() poate fi apelată cu argument de tip caracter deoarece ieşirea pe ecran se referă, oricum, doar la octetul de ordin inferior. În cazul apariţiei unei erori, putchar() returnează EOF. Funcţia macro EOF, este definită în stdio.h şi, în general este egală cu –1. Exemplul pe care îl prezentăm permite citirea de caractere de la tastatură şi, dacă sunt litere mici, convertirea lor la litere mari. Citirea se termină în momentul în care se tastează caracterul ESC al cărui cod backslash este \033. #include <stdio.h> #include <conio.h> #include <ctype.h> void main() { char car; do

{ car=getche(); car=toupper(car); putchar(car);

39

Page 40: C sau C++…

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

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

char *gets(char *sir); int puts(const char *sir);

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

#include <stdio.h> #include <conio.h> void main() { char s[30]; clrscr(); gotoxy(20,10); printf("Introduceti un sir de caractere:"); gets(s); gotoxy(20,11); printf("Sirul introdus :"); puts(s); getch(); clrscr(); } În exemplul de mai jos se arată modul de utilizare al funcţiilor gets() şi puts() când argumentul lui gets() este o variabilă pointer la un şir de caractere.

40

Page 41: C sau C++…

#include <stdio.h> #include <conio.h> void main() { char s; char *p; clrscr(); gotoxy(20,10); p=&s printf("Introduceti un sir de caractere:"); gets(p); gotoxy(20,11); printf("Sirul introdus :"); puts(p); getch(); clrscr(); } Operaţii I/O cu şiruri de caractere se pot efectua şi cu funcţiile scanf() şi printf(), mult mai versatile decât gets() şi puts() , după cum vom vedea în continuare. 4.3 Funcţii I/O relativ la consolă formatate Funcţiile printf() şi scanf() efectuează operaţii I/O formatate, adică pot scrie şi, respectiv, citi date în diverse formate specificate de programator. Funcţia printf() afişează date formatate la consolă. Funcţia scanf(), complementul funcţiei printf() citeşte date formatate de la tastatură. Ambele funcţii pot lucra cu toate tipurile de date existente în C. Prototipurile celor două funcţii sunt:

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

printf()

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

41

Page 42: C sau C++…

Codurile de format disponibile pentru printf() sunt:

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

plasat numărul de caractere scrise până atunci. %% Afişează un semn %

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

#include <stdio.h> #include <conio.h> void main() { int numara; printf("Acesta%n este un test...\n",&numara); printf("%d",numara); getch(); } De remarcat faptul că specificatorii de format acceptă modelatori de format care modifică uşor semnificţia lor. De exemplu, se poate specifica un minim de caractere permise la afişare, numărul de cifre zecimale şi alinierea la stânga. Modelatorul de format se află între semnul % şi codul pentru format. Codul de mai jos poate forma o idee asupra utilităţii modelatorilor de format. #include <stdio.h> #include <conio.h> void main() { int numara;

42

Page 43: C sau C++…

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

scanf() Este o rutină de uz general pentru intrări de la consolă. Ea poate să citească toate tipurile de date încorporate şi să facă automat conversia numerelor în format intern corect. Se aseamănă mult cu complementara ei printf(). Ca funcţie, scanf() returnează numărul de elemente cărora li s-a atribuit cu succes o valoare. Dacă apare o eroare scanf() returnează EOF. Argumentul format determină modul în care vor fi citite valorile în variabilele din lista de argumente. Codurile de format disponibile pentru scanf() sunt prezentate în tabelul de mai jos. Cod Format %c Citeşte un singur caracter %d Citeşte un număr întreg în baza 10 %I Citeşte un număr întreg în baza 10 %e Citeşte un număr în virgulă mobilă %E Citeşte un număr în virgulă mobilă %f Citeşte un număr în virgulă mobilă %g Citeşte un număr în virgulă mobilă %o Citeşte un număr în octal %s Citeşte un şir de caractere %u Numere întregi zecimale fără semn %x Citeşte un număr în hexazecimal %p Citeşte un pointer %n Argumentul asociat este un pointer de tip întreg în care a fost

plasat numărul de caractere citite până atunci. %u Citeşte un întreg fără semn %[] Caută un set de caractere

Tabelul 10. O parte din codurile pentru formatarea operaţiilor I/I relativ la periferice standard

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

43

Page 44: C sau C++…

Al doilea exemplu este conceput în ideea că valoarea variabilei n2 va fi citită numai după introducerea de la tastatură a două caractere virgulă. Acest mecanism permite, printre altele, definirea ca separator la introducerea datelor a altui caracter decât <CR>. #include <stdio.h> #include <conio.h> void main() { char sir[3]; clrscr(); scanf("%[TOC]",&sir); //Citire cu scanset printf("\n%s",sir); getch(); } #include <stdio.h> #include <conio.h> void main() { int n1,n2; clrscr(); scanf("%d,,%d",&n1,&n2); printf("%d\n",n1); printf("%d\n",n2); getch(); } Atenţie la necesitatea ca variabila în care se citeşte să fie un pointer! Alte facilităţi pentru lucrul cu consola În categoria “alte facilităţi pentru lucrul cu consola” includem, cu prioritate, funcţiile clrscr(), clreol(), gotoxy(col,lin) ale căror valori de întrebuinţare sunt identice celor din Borland Pascal. Chiar şi sintaxa funcţiei gotoxy(col,lin) este concordantă în ceea ce priveşte ordinea parametrilor după care se face poziţionarea cursorului. Aşadar: clrscr() – se utilizează pentru şteregerea ferestrei text curente; fereastra text curentă implicită depinde de modul text în care se lucrează. Caracteristicile modurilor text recunoscute de C sunt prezentate în tabelul de mai jos.

44

Page 45: C sau C++…

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

Tabelul 11. Moduri video text standard

Aceste moduri text pot fi selectate cu ajutorul funcţiei textmode(), al cărei antet se află tot în conio.h şi face parte tot din arsenalul C de lucru cu ecranul în mod text. Tot în mod text, putem controla culorile textului şi ale fondului (background-ul). Sintaxa funcţiilor cu care realizăm controlul culorilor este: textcolor(<Culoare>); textbackground(<Culoare>); Parametrul <Culoare> poate lua una din valorile prezentate în tabelul de mai jos: Constanta Valoare Fond Text BLACK 0 Da Da BLUE 1 Da Da GREEN 2 Da Da CYAN 3 Da Da RED 4 Da Da MAGENTA 5 Da Da BROWN 6 Da Da LIGHTGRAY 7 Da Da DARKGRAY 8 Da Da LIGHTBLUE 9 Nu Da LIGHTGREEN 10 Nu Da LIGHTCYAN 11 Nu Da LIGHTRED 12 Nu Da LIGHTMAGENTA 13 Nu Da YYELLOW 14 Nu Da WHITE 15 Nu Da BLINK 128 Nu ***

Tabelul 12. Valori admise pentru culoare text si fond. Moduri video text standard

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

45

Page 46: C sau C++…

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

wherex(); Returnează coloana curentă a cursorului. wherey(); Returnează linia curentă a cursorului. gettext() şi puttext() Permit citirea/scrierea memoriei video în mod text, ca în exemplul de mai jos. #include <conio.h> //Buffer pentru salvat conţinut memorie-video char buffer[4096]; int main(void) { int i; // Comutare în modul text C4350 textmode( C4350); clrscr(); for (i = 0; i <= 20; i++) cprintf("Linia %d\r\n", i); //Salvare conţinut memorie video în variabila buffer gettext(1, 1, 80, 24, buffer); gotoxy(1, 25); cprintf("Apasati o tasta pentru a sterge ecranul..."); getch(); //Ştergere ecran clrscr(); gotoxy(1, 25); cprintf(" Apasati o tasta pentru a restaura ecranul..."); getch();

46

Page 47: C sau C++…

//Refacere conţinut memorie video, utilizând datele salvate în variabila buffer puttext(1, 1, 80, 24, buffer); gotoxy(1, 25); cprintf("Apasati o tasta pentru a termina..."); getch(); return 0; } Ca o aplicaţie la cele prezentate până în acest moment în legătură cu programarea în C, prezentăm codul care încapsulează câteva noi facilităţi de lucru în mod text, sub forma unui fişier antet, care poate fi utilizat în orice program C, dacă este prezentă directiva de compilare: #Include “facilcrt.h” Numele fişierului antet este, evident, facilcrt.h. #include <conio.h> #include <stdio.h> #include <string.h> //Activare video-invers void avideo() { textcolor(BLACK); textbackground(WHITE); } //dezactivare video-invers void dvideo() { textcolor(WHITE); textbackground(BLACK); } //Afisare centrata text in interiorul unei linii void acentext(int ls,int ld,int linia,char *sir) { int sw; int col; sw=(ls>=1) && (ls<=79) && (ld<=80) && (ld>=2) && (ls<ld); sw=sw && ((ld-ls+1)>=strlen(sir)); if (sw) { col=ls+(ld-ls+1-strlen(sir))/2;

47

Page 48: C sau C++…

gotoxy(col,linia); cprintf(sir); } } //Construire fereastra cu rama dreptunghiulara de dimensiuni specificate void makewin2(int ass,int oss,int adj,int odj) { short int i; int sw; sw=(ass>0) && (ass<81) && (adj>0) && (adj<81) && (ass<=adj); sw=sw && (oss>0) && (oss<25) && (odj>0) && (odj<25) && (oss<=odj); if(sw) { for(i=ass;i<=adj;i++) { gotoxy(i,oss-1);cprintf("\315"); gotoxy(i,odj+1);cprintf("\315"); } for(i=oss;i<=odj;i++) { gotoxy(ass-1,i);cprintf("\272"); gotoxy(adj+1,i);cprintf("\272"); } gotoxy(ass-1,oss-1);cprintf("\311"); gotoxy(adj+1,oss-1);cprintf("\273"); gotoxy(ass-1,odj+1);cprintf("\310"); gotoxy(adj+1,odj+1);cprintf("\274"); } else { gotoxy(1,24); printf("Coordonate ecran eronate!!!"); getch(); } } Ca un comentariu la exemplul de cod C prezentat mai sus şi la alte exemple prezentate deja, facem precizarea că directiva de includere #include este soluţia C pentru o modularizare a codului, orientată pe clasificarea tipurilor de capabilităţi de prelucrare în fişiere speciale numite fişiere antet. Fişierele antet sunt incluse în codurile sursă ale programelor noastre de câte ori avem nevoie de o capabilitate al cărei prototip se află în acele fişiere antet.

48

Page 49: C sau C++…

5 Matrice şi şiruri O matrice este o colecţie de variabile de acelaşi tip, apelate cu acelaşi nume. Accesul la un anumit element al matricei se face cu ajutorul unui indice. În C toate matricile constau în locaţii de memorie contigue. Cel mai mic indice corespunde primului element iar cel mai mare ultimului element. Matricele pot avea una sau mai multe dimensiuni. Ca şi în alte limbaje, cea mai simplă matrice este şirul. În C şirul este o matrice de caractere terminate cu un caracter NULL. Această caracteristică oferă limbajului C mai multă putere şi eficienţă decât posedă alte limbaje. Semnalăm, de asemenea, faptul că în C, există o strânsă legătură între matrice şi pointeri. 5.1 Matrice cu o singură dimensiune Forma generală în declararea unei matrice cu o singură dimensiune este: <Tip> <Nume_variabilă>[<Dimensiune>]; <Tip> declară tipul de bază al matricei, care este tipul fiecărui element al său. <Dimensiune> indică numărul maximal de elemente pe care le poate conţine matricea.

De reţinut faptul că, în C, toate matricele au ca indice pentru primul element pe 0. Cantitatea de memorie necesară pentru înregistrarea unei matrice este direct proporţională cu tipul şi mărimea sa. Pentru o matrice unidimensională, mărimea totală în octeţi este calculată astfel: Total_octeţi = sizeof(<Tip>)*<Dimensiune>

C nu controlează limitele unei matrice. Puteţi depăşi ambele margini ale unei matrice şi scrie în alte variabile sau peste codul programului. Riscurile şi responsabilităţile sunt de partea programatorilor. 5.2 Crearea unui pointer la o matrice Un pointer la primul element al unei matrice se creează simplu, specificând numele matricei, fără nici un indice. De exemplu, având : float sir[10]; putem crea un pointer la primul element al matricei astfel:

49

Page 50: C sau C++…

float *p; float sir[10]; p=sir; 5.3 Şiruri de caractere De departe, cea mai utilizată matrice unidimensională este şirul de caractere. Reamintim faptul că în C un şir de caractere este definit ca o matrice de caractere care se termină cu un caracter NULL. În convenţie backslash un NULL se reprezintă prin ‘\0’ şi are valoarea 0. Din acest motiv, matricele de tip caracter se declară cu un caracter mai mult decât lungimea celui mai mare şir pe care îl vor conţine. Deşi C nu are date de tip şir permite constante şir. O constantă şir este o succesiune de caractere închise între ghilimele. Nu este necesar să introduceţi manual caracterul NULL la sfârşitul constantelor şir, compilatorul face acest lucru automat. C admite, totodată, o gamă largă de funcţii de manipulare a şirurilor. Cele mai des utilizate sunt prezentate în tabelul de mai jos. Nume funcţie Utilitate strcpy(s1,s2) Copiază s2 în s1 strcat(s1,s2) Concatenează s2 la sfârşitul s1 strlen(s1) Returnează lungimea lui s1 strcmp(s1,s2) Returnează 0 dacă s1 şi s2 sunt identice; un număr

mai mic decât 0 dacă s1<s2 în sens lexicografic; un număr mai mare decât 0 dacă s1>s2 în sens lexicografic.

strchr(s1,ch) Returnează un pointer la prima apariţie a caracterului ch în s1.

strstr(s1,s2) Returnează un pointer la prima apariţie a lui s2 în s1. Tabelul 13. Funcţii C pentru lucrul cu şiruri de caractere păstrate în fişierul antet

string.h Atenţie! strcmp() returnează fals dacă şirurile sunt egale.

5.4 Matrici bi şi multidimensionale C admite matrice multidimensionale. Cea mai simplă formă de matrice multidimensională este cea cu două dimensiuni. De fapt, o matrice bidimensională este o matrice de matrice unidimensionale. Pentru a declara o matrice bidimensională utilizăm sintaxa: <Tip> <Nume_variabilă>[<Dimensiune_1>][<Dimensiune_2>]; De exemplu, o matrice bidimensională de întregi, de mărime10/20 se declară astfel: int matr[10][20];

50

Page 51: C sau C++…

Adresarea elementelor matricei se bazează pe premiza că, după fiecare dimensiune, indexarea începe de la 0. De asemenea, facem precizarea că, atunci când o matrice bidimensională este utilizată ca un argument pentru o funcţie, se transmite doar un pointer către primul element al matricei. Însă, parametrul care primeşte o matrice bidimensională trebuie să definească cel puţin numărul de coloane, necesare compilatorului pentru a indexa corect matricea. De urmărit exemplul de mai jos de cod C care permite calculul sumei elementelor strict pozitive de pe diagonala principala a unei matrice patratice . #include<stdio.h> #include<conio.h> // Declarare matrice bidimensionala int matr[10][10]; int dimm; //Functia care primeste ca argument o matrice . Declara numarul de coloane. void sespdp( int m[ ][10]) { int k,suma; suma=0; for (k=0;k<dimm;k++) { if (m[k][k]>0) suma=suma+m[k][k]; } clrscr(); gotoxy(20,12); cprintf("Suma elementelor strict pozitive ddp: %d",suma); getch(); } //Functia principala void main() { int i,j,el; clrscr(); gotoxy(20,10);cprintf("Nr. de linii :"); scanf("%d",&dimm); clrscr(); for(i=0;i<dimm;i++) for(j=0;j<dimm;j++) { gotoxy(20,12); cprintf("Elementul[ %d , %d ]=",i,j); scanf("%d",& matr[i][j]); }

51

Page 52: C sau C++…

//Apelare functie cu parametru matrice transmisa ca pointer static sespdp(matr); } Evident, în cazul unei matrice multidimensionale, forma generală de declarare precum şi modul de utilizare sunt deductibile din cazul bidimensional. Sintaxa pentru declararea unei matrice multidimensionale este: <Tip> <Nume_variabilă>[<Dim_1>][<Dim_2>]…[Dim_n]; În încheierea paragrafului referitor la matrice prezentăm o aplicaţie în care, arătăm, totodată, primii paşi spre modularizarea şi interfaţarea unui program C. Este vorba de un program C care cercetează ortogonalitatea a doi vectori reali. Se utilizează încă odată fişierul antet facilcrt.h . #include<stdio.h> #include<conio.h> #include "facilcrt.h" #include<stdlib.h> //Directiva #define permite specificarea unor macrouri #define lmaxs 100 #define optiuni "1234" //Solutie modularizata C pentru problema determinrii //ortogonalitatii a doi vectori reali char prelopt(); void prelvec(); int ortogon(); float sir1[lmaxs],sir2[lmaxs]; int dims; void main() { for(; ;) { switch (prelopt()) { case '1': { prelvec(); break; } case '2': { ortogon(); break; }; case '3': exit(0); }

52

Page 53: C sau C++…

} } char prelopt() { char opt; clrscr(); gotoxy(20,9); cprintf("Optiunile programului...."); gotoxy(20,10); cprintf("1- Preluare vectori"); gotoxy(20,11); cprintf("2- Determinare ortogonalitate...."); gotoxy(20,12); cprintf("3- Terminare program"); makewin2(20,9,55,12); gotoxy(20,15); cprintf("Optiunea Dvs.:"); makewin2(20,15,55,15); do { gotoxy(20+strlen("Optiunea Dvs.:"),15); opt=getch(); } while (strchr(optiuni,opt)==NULL); return opt; } void prelvec() { int i,col; clrscr(); gotoxy(20,9); cprintf("Dimensiune vectori:"); makewin2(20,9,20+strlen("Dimensiune vectori:"),9); gotoxy(22+strlen("Dimensiune vectori:"),9); col=strlen("Dimensiune vectori:"); makewin2(22+col,9,30+col,9); gotoxy(23+col,9); cscanf("%d",&dims); clrscr(); gotoxy(20,9); cprintf("Preluarea componentelor primului vector..."); makewin2(20,9,20+strlen("Preluarea componentelor primului vector..."),12); for (i=0;i<dims;i++) {

53

Page 54: C sau C++…

gotoxy(20,10); cprintf("Elementul [ %d ]=",i); cscanf("%f",&sir1[i]); } clrscr(); gotoxy(20,9); cprintf("Preluarea componentelor celui de-al doilea vector..."); makewin2(20,9,20+strlen("Preluarea componentelor celui de-al doilea vector..."),12); for (i=0;i<dims;i++) { gotoxy(20,10); cprintf("Elementul [ %d ]=",i); cscanf("%f",&sir2[i]); } } int ortogon() { float suma; int i; suma=0; for (i=0;i<dims;i++) suma=suma+sir1[i]*sir2[i]; clrscr(); if (suma) { gotoxy(20,10); textcolor(RED+BLINK); cprintf("Vectorii nu sunt ortogonali!!"); makewin2(20,10,21+strlen("Vectorii nu sunt ortogonali!!"),10); getch(); textcolor(WHITE); } else { gotoxy(20,10); textcolor(GREEN+BLINK); cprintf("Vectorii sunt ortogonali!!"); makewin2(20,10,21+strlen("Vectorii sunt ortogonali!!"),10); getch(); textcolor(WHITE); }; }

54

Page 55: C sau C++…

Se cuvine să mai fac o paranteză referitoare la insistenţa cu care apar în programele C/C++ construcţii sintactice care încep cu #, precum #include, #define. Aceste construcţii fac parte din categoria instrucţiunilor care se adresează compilatorului şi se mai numesc şi directive pentru preprocesor. deşi nu fac parte din limbajul C/C++ aceste directive lărgesc sfera de acţiune a programelor C/C++. Am văzut deja care este utilitatea directivei preprocesor #include. Să spunem, totodată, faptul că, directiva pentru preprocesor #define permite definirea unor macrouri astfel: #define <Nume_macrou> <Secvenţă_de_caracter> Oriunde în program apare <Nume_macrou> compilatorul îl înlocuieşte cu <Secvenţă_de_caractere>. Adică avem la dispoziţie un instrument foarte util la parametrizarea codului. Să adăugăm că #define poate permite şi definirea de funcţii macro, situaţie în care numele macroului poate avea şi argumente. Exemplu: #define ABS(a) (a)<0 ? –(a) : (a) : printf(“abs de –1 si 1”%d %d,ABS(-1), ABS(1)); La compilarea acestei secvenţe, a care apare în definirea funcţiei macro va fi înlocuit cu valorile –1 şi 1. Utilizarea unei funcţii macro măreşte viteza de execuţie a codului. Merită cercetate cu atenţie şi trucurile care pot fi realizate cu ajutorul directivei #define. Semnalăm că, aşa cum se întâmpla şi în Pascal, în C există o serie de directive pentru compilare condiţională a codului sursă. Din această categorie fac parte #if, #endif, #else #ifdef, #ifndef, etc.

55

Page 56: C sau C++…

6 Pointeri Un pointer este o variabilă care poate conţine o adresă de memorie. Această adresă este localizarea în memorie a unui alt obiect (de regulă o altă variabilă). De exemplu, dacă o variabilă conţine adresa alteia, despre prima se spune că este un pointer la cea de-a doua. O variabilă de tip pointer se declară în C astfel: <Tip> * <Nume>; unde <Tip> este tipul de bază al pointerului iar <Nume> este numele variabilei pointer. Tipul de bază al pointerului defineşte tipul de variabilă către care indică pointerul. Practic, orice tip de pointer poate să indice orice în memorie. Problema este, însă, că aritmetica pointerilor este integral raportată la tipul de bază al pointerului. Operatori pentru pointeri Există doi operatori speciali pentru pointeri: * şi &. & este un operator unar care returnează adresa din memorie a operandului său. Operatorul * returnează valoarea înregistrată la adresa care îl urmează. Evident că operatorul & se aplică oricărui tip de variabilă dar operatorul * cere după el, neapărat, o variabilă pointer. Expresii cu pointeri În general, expresiile care implică pointeri se conformează aceloraşi reguli ca şi celelalte expresii. Există, totuşi, o serie de particularităţi pe care le vom evidenţia în acest paragraf în ceea ce priveşte aritmetica pointerilor. Astfel:

Atribuirile sunt permise între pointeri concordanţi ca tip. Pointerilor li se poate aplica operatorul ++ (incrementare) ca mai jos

: int *p; int mat[10]; : p=&mat[0]; : p++; // Operatorul de incrementare aplicat pointerului p p- -; :

În urma aplicării operatorului de incrementare pointerului p, care conţinea adresa primului element al tabloului unidimensional mat, offset-ul acestuia este incrementat cu 2, adică atât cât este lungimea în octeţi a tipului de bază al pointerului p.

Pointerilor li se poate aplica operatorul - - (decrementare) . În urma aplicării operatorului de decrementare pointerului p, care conţinea adresa celui de-al doilea element al tabloului unidimensional mat, offset-ul acestuia este decrementat cu 2, adică atât cât este lungimea în octeţi a tipului de bază al pointerului p.

56

Page 57: C sau C++…

De asemenea, putem aduna sau scădea întregi la, sau din pointeri. De exemplu:

: p=p+10; :

face ca p să indice al 10-lea element de acelaşi tip cu tipul de bază al lui p, relativ la elementul curent. În exemplul de mai jos ilustrăm utilitatea aritmeticii pointerilor în contextul lucrului cu matrice. #include<stdio.h> #include<conio.h> int matr[10][10]; //matrice de intregi bidimensionala int *p; // pointer la intregi int dimm; void main() { int i,j,el; clrscr(); gotoxy(20,10);cprintf("Dimensiune matrice :"); scanf("%d",&dimm); clrscr(); for(i=0;i<dimm;i++) for(j=0;j<dimm;j++) { gotoxy(20,12); cprintf("Elementul[ %d , %d ]=",i,j); scanf("%d",&el); matr[i][j]=el; }; p=&matr[0][0]; //p refera primul element al matricei for (i=0;i<dimm;i++) cprintf("\r\n Ref_poin=%d",*(p+i)); //modificare offset p in expresie getch(); p=&matr[0][0]; for (i=0;i<dimm;i++) { cprintf("\r\n Ref_poin=%d",*(p)); p=p+1; //modificare offset p prin incrementare } getch(); p=&matr[0][0]; for (i=0;i<dimm;i++) { cprintf("\r\n Ref_poin=%d",*(p)); p++; //aplicare operator de incrementare

57

Page 58: C sau C++…

} getch(); } Pointerii pot fi comparati în cadrul expresiilor relaţionale. Daţi pointerii p şi q, este perfect valabilă instrucţiunea: if (p<q) cprintf (“p indica o memorie de adresa mai mica decat q\n”); Evident, în C se poate vorbi de matrice de pointeri, de indirectare multiplă şi, evident, de alocarea dinamică a memoriei aferente unor pointeri. Funcţii de alocare dinamică în C Pointerii oferă suportul necesar pentru sistemul puternic de alocare dinamică a memoriei în C. Alocarea dinamică este caracteristica prin care un program poate obţine memorie în timpul execuţiei. După cum se ştie, variabilelor globale li se alocă memorie în timpul compilării. Variabilele locale folosesc memoria de tip stivă. În mod cert, nici variabilele globale nici cele locale nu pot fi adăugate în timpul execuţiei programului. Există situaţii în care necesarul real de memorie este cunoscut de-abia în timpul execuţiei programului.

Chiar dacă C++ acceptă pe deplin sistemul de alocare dinamică al lui C, el îşi defineşte propriul sistem, care conţine mai multe îmbunătăţiri faţă de cele din C.

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

void *malloc(size_t numar_de_octeţi);

În acest prototip, numar_de_octeţi este numărul de octeţi din memorie pe care dorim să-l alocăm programului. Funcţia malloc() returnează un pointer de tipul void, ceea ce înseamnă că îl puteţi atribui oricărui tip de pointer.

Prototipul funcţiei free este: void free(void *p); Funcţia free returnează în sistem memoria alocată anterior pointerului p. Este esenţial să nu se apeleze free cu un argument impropriu; deoarece acest fapt poate aduce prejudicii gestiunii de către sistem a memoriei libere. Exemplul de mai jos arată modul efectiv de utilizare a funcţiilor malloc şi free, precum şi

58

Page 59: C sau C++…

modul de utilizare a funcţiei memcpy, în situaţia în care se doreşte copierea conţinutului memoriei de la o anumită adresă la altă adresă. De remarcat, totodată, faptul că bibliotecile C au numeroase alte funcţii pentru a satisface cerinţele de alocare/ manipulare dinamică a memoriei în programele utilizator. #include<stdio.h> #include<conio.h> #include<stdlib.h> #include<string.h> void main() { char c[20]; int *p; //pointer generic void *x; int nr=10000; p=&nr; clrscr(); gotoxy(20,10); //Alocare dinamica memorie incepand //de la o adresa returnata in x x=(int*) malloc(sizeof(int)); //Copiere 2 octeti din zona referita de p in zona referita de x memcpy(x,p,sizeof(int)); printf("%d",*(int*)x); getch(); //Utilizarea pointerului x pentru a referi un sir de caractere gotoxy(20,11); strcpy(c,"Sir de caractere…"); x=(char*) malloc(21); //alocare cu conversie de tip memcpy(x,c,21); puts((char*)x); getch(); free(x); }

Programatorul Pascal recunoaşte în suportul oferit de malloc() şi free() soluţia pentru problema alocării de memorie la nivel de octet, ceea ce înseamnă responsabilitatea programatorului de a realiza conversiile necesare în procesul de utilizare a memoriei astfel alocate, ca în programul de mai sus. C++ are propria ofertă pentru alocarea dinamică de memorie din perspectivă structurată.

59

Page 60: C sau C++…

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

{ <Tip> <Nume_membru_1>; <Tip> <Nume_membru_2>; :

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

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

<Tip returnat> <Structură>::<Nume functie>(<Lista de parametri>); { //Corp funcţie }; Pentru mai multă claritate se poate urmări exemplul de mai jos. #include<stdio.h> #include<conio.h> typedef struct { char matricol[6]; char nume[30]; float media; void setstud(char matr[],char num[],float med); } TStud; void TStud::setstud(char matr[],char num[],float med) { strcpy(matricol,matr); strcpy(nume,num); media=med;

60

Page 61: C sau C++…

}; void main() { clrscr(); TStud stud; stud.setstud("12","Mihai Guramare",10); printf("Matricol :%s\n",stud.matricol); printf("Nume :%s\n",stud.nume); printf("Media :%f",stud.media); getch(); } După cum se vede deja, numele variabilei structurate, urmat de un punct şi numele membrului permite adresarea acelui membru. Adică, în general vorbind: <Variabila_structurată> . <Nume_membru> În codul de mai jos se arată modul concret de declarare a unei structuri şi de asociere a acestei declaraţii cu un nume de tip prin intermediul cuvântului cheie typedef. #include<conio.h> #include<iostream.h> #include<string.h> void main() { typedef struct TPers{ char nume[30]; float salariu; } TPers; TPers pers; strcpy(pers.nume,"Test typedef + struct…."); pers.salariu=1200000; clrscr(); cout<<pers.nume<<"\r\n"<<pers.salariu; getch(); } În mod evident, structurile pot fi asociate şi cu pointerii prin declaraţii asemănătoare celor din codul alternativ de mai jos. #include<conio.h> #include<iostream.h> #include<string.h> void main() {

61

Page 62: C sau C++…

typedef struct TPers{ char nume[30]; float salariu; } TPers; TPers *pers; …alocare memorie pentru pers strcpy(pers->nume,"Test relatie structura - pointer"); pers->salariu=1200000; clrscr(); cout<<pers->nume<<"\r\n"<<pers->salariu; getch(); } O altă întrebuinţare a structurilor o reprezintă posibilitatea de a defini câmpuri de biţi. Câmpurile de biţi sunt mecanisme cu ajutorul cărora programatorul poate avea acces la conţinutul variabilelor până la nivel de bit. Sintaxa pentru definirea unui camp de biti este: typedef struct { tip1 nume1:lungime1; tip2 nume2:lungime2; ... tipn numen:lungimen; } TOctet; Tipurile pot lua una din valorile int, unsigned, signed, pentru majoritatea compilatoarelor.

Câmpul de biţi cu lungimea 1 este obligatoriu unsigned. A se vedea şi exemplul de mai jos. #include<stdio.h> #include<conio.h> #include<string.h> typedef struct { unsigned bit :1; unsigned :7; unsigned :7; unsigned bits:1; } TOctet; TOctet Octet;

62

Page 63: C sau C++…

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

63

Page 64: C sau C++…

8 Uniuni O uniune este o locaţie de memorie care este partajată în momente diferite între două sau mai multe variabile diferite. Sintaxa generala şi modul de utilizare pot fi deduse din exemplul de mai jos. #include<stdio.h> #include<conio.h> #include<string.h> typedef union { int codsal; float salar; } TUSal; void main() { clrscr(); TUSal vunion; vunion.codsal=10; printf("Cod confidential salariu:%i\n",vunion.codsal); vunion.salar=1000.50; printf("Salariu :%f",vunion.salar); getch(); } În acest exemplu variabilele codsal şi salar, de tipuri diferire, partajează aceeaşi locaţie de memorie, dar, important de ştiut, în momente diferire ale execuţiei unui programului, după cum se observă, de altfel şi din exemplu. Încheiem aici partea intitulată BAZELE C++. LIMBAJUL C, cu menţiunea că există nenumărate alte aspecte ale programării în C care necesită timp şi răbdare pentru a înţelege toate consecinţele stilului C de programare, înţelegere folositoare şi în abordarea C++. Dintre aceste aspecte semnalez: fluxurile C, şabloanele C, suprascrierea funcţiilor, etc. Pentru toate acestea există, însă suport C++ mult mai adecvat şi mai comod de multe ori pentru programarea cu adevărat în spirit obiect orientat.

64

Page 65: C sau C++…

II Programarea C++

65

Page 66: C sau C++…

1 Introducere Programarea orientată pe obiecte (POO) este expresia, în materie de codificare a proiectelor, a paradigmei care presupune modelarea orientată pe obiecte a sistemelor soft. POO este o paradigmă care câştigă tot mai mulţi aderenţi datorită calităţilor pe care le au produsele şi sistemele soft realizate în spiritul conceptelor şi principiilor promovate de aceasta. Rezultantă a unor direcţii diverse de cercetare şi experimentare (programare structurată, programare modulară, programare orientată pe structuri abstracte, reprezentarea cunoştinţelor în sisteme expert, etc.) POO, aplicată corect poate rezolva, parţial sau integral, multe din problemele obsedante ale ingineriei softului, în genere: reutilizarea codului, extinderea/modificarea cu minim de efort a sistemelor soft, ascunderea detaliilor de implementare faţă de anumite categorii de utilizatori ai sistemelor soft. În acest mod platforma POO poate ajuta programatorii şi companiile de soft să realizeze produse şi sisteme soft performante, în timp util şi la un preţ scăzut. 1. 1 Concepte POO Orice demers de modelare orientată pe obiecte apelează la o serie de concepte specifice paradigmei POO. Astfel, pentru modelarea unui sistem soft se operează frecvent cu: 1. Concepte ale teoriei generale a sistemelor (sistem, subsistem, descompunere, agregare, structură, etc.) 2. Concepte care provin din arsenalul conceptual al modelării în genere a sistemelor şi produselor soft (modul, modularizare, interfaţă, tip de dată, structură de date, ascunderea informaţiei, etc.). Tuturor acestora li se adaugă principiile cu ajutorul cărora aceste concepte devin operaţionale. Paradigma POO a intrat în competiţia pentru modernizarea şi îmbunătăţirea reală a procesului de realizare a unui sistem soft cu un set propriu de concepte şi principii. Prezentăm mai întâi conceptele cheie ale paradigmei POO. Conceptul de clasă, prin care se desemnează o colecţie de obiecte (de natură materială sau spirituală) care au în comun faptul că pot fi caracterizate similar din punct de vedere informaţional şi comportamental. Este evident faptul că identificarea unei clase este în mod normal, rezultatul unui demers cognitiv care presupune caracterizarea unui obiect prin însuşirile lui (informaţionale şi comportamentale) care îi definesc apartenenţa la o anumită clasă de obiecte. Aşadar, conceptul de clasă adună laolaltă datele şi metodele de prelucrare a acestora. În esenţă definirea unei clase se bazează pe analiza, clasificarea şi abstractizarea însuşirilor obiectelor de un anumit tip.

66

Page 67: C sau C++…

Acesta este un exerciţiu de îndemânare a cărui rezolvare o poate învăţa oricine care încearcă, are răbdare cu el însuşi şi citeşte cum procedează iniţiaţii când întâlnesc astfel de exerciţii. Aşa cum va reieşi din implementarea conceptului de clasă în C++, de exemplu, conceptul de clasă este o abstracţie care pregăteşte un anumit tip de background pentru descrierea soluţiei unei probleme date. Fără a intra prea mult în problemă să amintim, totuşi, că modelând obiect orientat soluţiile problemelor noastre ne asigurăm o serie de avantaje imediate şi de perspectivă în ceea ce priveşte gestiunea relaţiei dintre domeniul problemei şi domeniul soluţiei în ingineria sistemelor soft. Conceptul de obiect (instanţă a unei clase) este deja un concept cu valoare operaţională, prin care se desemnează un obiect concret al unei clase definitoare, caracterizat prin valori specifice ale atributelor informaţionale. Dacă definirea unei clase pune, în primul rând, probleme de natură conceptuală, manipularea unui obiect se bazează pe abilităţile specifice limbajului referitoare la alocarea de memorie pentru obiect (static sau dinamic), controlul stării obiectului (= valorile atributelor informaţionale) cu ajutorul metodelor proprii, etc. Proprietăţile unui obiect sunt, în mare parte, proprietăţile unei clase. Calitatea unui obiect depinde de calitatea clasei definitoare. Există două perspective din care putem aprecia calităţile unui obiect: perspectiva utilizator de obiect şi perspectiva specificator de clasă definitoare. Utilizatorul de obiect este interesat, practic, de calitatea interfeţei obiectului, care permite o apreciere şi asupra potenţialului informaţional şi comportamental al obiectului respectiv. Munca unui specificator de clasă definitoare poate fi apreciată după potenţialul informaţional şi comportamental al clasei, accesibil prin intermediul unei interfeţe care implementează principiul “cine doreşte date despre starea unui obiect al unei clase trebuie să se mulţumească cu deschiderea oferită în acest sens de interfaţa obiectului în cauză”. Trădarea acestui principiu este o dovadă de neprofesionalism în abordarea obiect orientată a unei probleme. Aşa se ajunge că, pentru un programator, un obiect contează prin: identitate, interfaţă(partajată în comun cu alte obiecte de acelaşi tip prin raportare la o clasă definitoare) şi stare. Identitatea unui obiect desemnează procedeul prin care se asigură, în sistem, unicitatea unui obiect. Cele mai multe limbaje rezolvă această problemă prin asocierea instanţei unei clase (=obiect) cu o anumită adresă de memorie. Interfaţa unui obiect desemnează mulţimea metodelor unei clase cu ajutorul cărora obiectele clase comunică cu ambianţa în care acestea există. Comunicarea între obiecte se realizează, dogmatic vorbind, prin mesaje. Conceptul de stare a unui obiect prin care se desemnează valorile atributelor informaţionale ale obiectului. Conceptul de mesaj prin care se înţelege un semnal emis de către un obiect emiţător către un obiect receptor, din iniţiativa obiectului emiţător.

67

Page 68: C sau C++…

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

Conceptul de metodă prin intermediul căruia se dă o expresie procedeelor care definesc comportamentul şi, direct sau indirect, protocolul de comunicaţie al unei clase. Pentru mai multă exactitate, este bine să se înţeleagă faptul că "metoda" este o denumire generică pentru un procedeu de prelucrare care face parte din protocolul de comunicare al unei clase. Metoda poate fi implementată în C, de exemplu, ca funcţie, constructor sau destructor. Asupra semnificaţiei noţiunilor de constructor şi destructor vom mai reveni . 1. 2 Principiile POO Noutatea POO, ca paradigmă este ilustrată şi de principiile pe care le promovează pentru a completa potenţialul oferit de concepte. Promovarea sistematică a principiilor pe care le prezentăm în continuare promite, dar nu garantează, realizarea unor produse, sisteme sau platforme soft remarcabile din punct de vedere al performanţelor şi al efortului de întreţinere. 1.2.1 Principiul încapsulării Înţelegerea acestui principiu presupune două nivele de abordare.

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

Ca implementare, la nivelul unui limbaj de programare, încapsularea este asigurată de exigenţele sintactice specifice.

1.2.2 Principiul moştenirii

68

Page 69: C sau C++…

Acest principiu este de mare utilitate în transmiterea sistematică a similarităţilor de la o clasă la alta. Principiul poate fi aplicat cu succes doar în conjuncţie cu operatorii conceptuali complementari:generalizarea şi specializarea. Generalizarea apare în relaţia dintre o clasă şi una sau mai multe versiuni mai rafinate ale acesteia. Este, în limbajul de specialitate, relaţia dintre clasa de bază (sau superclasă) şi clasele derivate (sau subclase). Atributele şi operaţiile comune sunt grupate în superclasă şi se spune că sunt moştenite de subclase. Se poate spune că moştenirea se referă la mecanismul de a transmite atribute şi operaţii de-a lungul unei relaţii de generalizare. Evident, simpla moştenire a proprietăţilor unei clase nu rezolvă nici o problemă din punct de vedere conceptual. Utilitatea principiului moştenirii este reliefată în condiţiile aplicării operatorului de specializare, prin care subclasa rafinează (specializează) superclasa. Implementarea moştenirii nu creează probleme deosebite de înţelegere şi utilizare. 1.2.3 Principiul polimorfismului Prin aplicarea principiului moştenirii se creează condiţii pentru specializarea unei superclase. Specializarea poate fi realizată prin adăugarea de noi operaţii celor moştenite sau prin redefinirea unora dintre operaţiile moştenite. Specializarea orientată pe redefinirea operaţiilor se află la baza implementării principiului polimorfismului. Ca rezultat final aplicarea principiului polimorfismului creează posibilitatea ca un mesaj (în structura căruia intervine un nume de operaţie) să genereze răspunsuri diferite, în funcţie de contextul în care este formulat mesajul. O astfel de posibilitate este cu atât mai valoroasă cu cât gestiunea contextului în care se formulează mesajul este asumată de către sistem. Ca mecanism, aplicarea principiului presupune instituirea unui protocol pentru evidenţa legăturilor dintre clase şi operaţiile aferente, susceptibile de redefinire.

69

Page 70: C sau C++…

2 Implementarea C++ a POO 2.1 Definirea claselor Pentru a putea lucra cu obiecte în C++ trebuie, în prealabil, să definim forma lor generală folosind în acest scop cuvântul cheie class. O clasă este similară, sintactic vorbind, cu o structură. Forma generală a unei declaraţii de clasă care nu moşteneşte nici o altă clasă este următoarea: class <Nume_clasa> [:<Lista_claselor_de_bază>] { <Date şi funcţii particulare> <Specificator de acces>: <Date si functii> <Specificator de acces>: <Date si functii> : : <Specificator de acces>: <Date si functii> } [<Lista de obiecte>]; <Lista de obiecte> este opţională. Dacă există, ea declară obiecte din acea clasă. <Specificator de acces> este unul din cuvintele cheie: -public -private -protected. Implicit, funcţiile şi datele declarate într-o clasă sunt proprii acelei clase, doar membrii săi având acces la ele.

<Lista_claselor_de_bază> indică, opţional, clasele de la care se pot moşteni atribute informaţionale şi comportamentale.

Pentru datele şi funcţiile care fac parte din definiţia unei clase se

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

70

Page 71: C sau C++…

#include <conio.h> #include <string.h> #include <iostream.h> // Definitie clasa fara declarare de variabile obiect. class Persoana { public: char nume[30]; int matricol; float salariu; void setfields(char n[30], int m, float s); void afis(); }; // Implementare functie setfields void Persoana:: setfields(char *n, int m, float s) { strcpy(nume,n); matricol=m; salariu=s; } // Implementare functie afis void Persoana:: afis() { clrscr(); cout<<nume<<"\n"<<salariu; getch(); } //Implementare functie principala void main() { // Declarare variabila obiect Persoana pers; pers.setfields("Radu Vasile",1,10000); pers.afis(); } În exemplul prezentat am folosit specificatorul de acces public pentru a permite funcţiei principale, care a declarat variabila obiect pers, accesul la membrii clasei definitoare a variabilei pers. În programarea obiect orientată adevărată în C++, efectul de ascundere a membrilor unei clase este intens utilizat, interzicând

71

Page 72: C sau C++…

anumitor categorii de utilizatori accesul la membrii clasei, definind pentru comunicaţia cu alte clase şi categorii de utilizatori ceea ce se numeşte interfaţa clasei. Evident, se poate face o oarecare analogie între clasă şi structură; analogia cade, însă, în momentul în care se pune problema manevrabilităţii instanţelor celor două tipuri de concepte sau atunci când este vorba de moştenire şi polimorfism. Un exemplu ceva mai apropiat de realitatea preocupărilor unui programator C++ poate fi programul care implementează o stivă de întregi, prezentat în continuare. #include <iostream.h> #include <conio.h> #define SIZE 100 // Definirea clasei stack class stack { int st[SIZE]; int top; public: void init(); void push(int i); int pop(); }; //Implementare functie init void stack::init() { top=0; } //Implementare functie push void stack::push(int i) { if (top==SIZE) { gotoxy(20,24); cout<<"Stiva este plina!!!"; getch(); return; }; st[top]=i; top++; } //Implementare functie pop

72

radu
program stiva C++
Page 73: C sau C++…

int stack::pop() { if(top==0) { gotoxy(20,24); cout<<"Depasire inferioara stiva!!!"; getch(); return 0; }; top--; return st[top]; } //Functia principala void main() { int i; stack st1,st2; st1.init(); st2.init(); for (i=0;i<=9;i++) { st1.push(i); st2.push(9-i); } clrscr(); for (i=0;i<=9;i++) { gotoxy(35,wherey()+1); cout<<st1.pop()<<"*****"; cout<<st2.pop()<<endl; } getch(); } 2.2 Clase derivate. Constructori. Destructori. Obiecte C++ Modelarea obiect orientată a soluţiei unei probleme de oarecare complexitate pune, inevitabil şi problema transmiterii similarităţilor între clase care formează o ierarhie sau o reţea. Suportul C++ pentru rezolvarea acestei probleme îl reprezintă posibilitatea ca o clasă să fie derivată din una sau mai multe clase, ceea ce înseamnă posibilitatea de a abstractiza soluţia cu scopul de a reutiliza cod şi de a adapta codul uşor la cerinţe noi dacă este cazul. Am văzut mai sus sintaxa pentru moştenire. Să adăugăm, în plus la cele spuse mai sus, că în <Lista_claselor_de_bază> clasele definitoare apar precedate, eventual, de un modificator de protecţie şi sunt separate între ele prin virgulă.

73

Page 74: C sau C++…

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 elementului în clasa de bază

Modificatorul de protecţie asociat clasei de bază la definirea clasei

Accesul în clasa derivată la element

private private interzis protected private private public private private private public interzis protected public protected public public public

Tabelul 14. Problematica accesului la membrii unei clase derivate

Concluzionând, dacă în clasa de bază accesul la element este private atunci în clasa derivată accesul este interzis. În schimb, clasa derivată are acces la elementele clasei de bază aflate sub incidenţa accesului protected/public. Se mai poate observa, totodată, că dacă la definirea clasei derivate se utilizează modificatorul de protecţie private, atunci elementele protejate în clasa de bază prin protected sau public devin protejate private în clasa derivată; deci inaccesibile unor clase care s-ar deriva eventual din clasa derivată. Exemplu #include <iostream.h> class CB { protected: int i,j; //Pentru un program care utilizează această clasă //sau pentru altă clasă care nu se află în relaţie //de derivare cu CB I,j sunt private din punct de //vedere al accesului. i,j sunt accesibile unei clase derivate. public: void setij(int a, int b) {i=a;j=b} void dispij() { cout<<i<<” “<<j<<endl;} } class CD:public CB { int k; public: void setk(){k=I*j;} void dispk(){cout<<k<<endl;}

74

Page 75: C sau C++…

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

Moştenirea protected a clasei de bază Este posibil să se moştenească o clasă de bază ca protected. Când se procedează astfel, toţi membrii public şi protected ai clasei de bază devin membri protected ai clasei derivate. Exemplu #include <iostream.h> class CB { protected: int i,j; //Pentru un program care utilizează această clasă //sau pentru altă clasă care nu se află în relaţie //de derivare cu CB i, j sunt private din punct de //vedere al accesului. i,j sunt accesibile unei clase derivate. public: void setij(int a, int b) {i=a;j=b} void dispij() { cout<<i<<” “<<j<<endl;} } class CD:protected CB { int k; public: void setk(){setij(10,20);k=I*j;} void disptot(){cout<<k<<” “;dispij();} } void main() { CD ob; ob.setij(2,3); //ilegal! ob.setk(); //OK!, membru public în CD

75

Page 76: C sau C++…

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

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

Constructorul este o funcţie care are acelasi nume cu clasa. Un constructor al unui obiect este apelat automat la crearea obiectului. Un constructor al unui obiect este apelat o singură dată pentru obiecte globale sau pentru cele locale de tip static. Constructorul este o funcţie fără tip ceea ce nu reclamă, totuşi cuvântul cheie void în locul tipului. Pentru obiecte locale constructorul este apelat de fiecare dată când este întâlnită declararea acestuia. Complementul constructorului este destructorul. De multe ori un obiect trebuie să efectueze anumite acţiuni când este distrus. Este evident faptul că obiectele locale sunt distruse la părăsirea blocului în care apar iar obiectele globale la terminarea programului. Când este distrus un obiect, este apelat destructorul clasei definitoare.

Destructorul are acelaşi nume cu constructorul, dar precedat de un caracter ~. O clasă are un singur destructor şi acest destructor nu poate avea parametri formali. Atât constructorul cât şi destructorul, în C++ nu pot să returneze valori. De semnalat faptul că în situaţia în care programatorul nu specifică un constructor explicit la definirea unei clase, la crearea unei instanţe a clasei se foloseşte constructorul implicit ataşat de compilator fiecărei clase. Constructorul implicit nu are parametri formali, ceea ce are drept consecinţă faptul că nu este permisă iniţializarea la declarare a datelor membre ale obiectelor. De asemenea, constructorul implicit nu este generat în cazul în care clasa are ataşat un alt constructor fără parametri. Programatorul poate înzestra clasa cu o proprie funcţie constructor. În acest scop trebuie ţinut cont de faptul că o metodă constructor are întotdeauna numele clasei din care face parte. Programatorul poate înzestra clasa şi cu parametri formali ceea ce permite şi o formulă elegantă de iniţializare a datelor membre ale obiectelor clasei respective.

76

Page 77: C sau C++…

Foarte important mi se pare să semnalez şi existenţa unui constructor

special, numit constructor de copiere. Acesta are rolul de a atribui datele unui obiect altuia. Mai mult, poate fi apelat chiar la definirea obiectelor. Dacă programatorul nu defineşte propriul constructor de copiere, compilatorul adaugă automat un astfel de constructor. În exemplul de mai jos se pot urmări elemente de sintaxă şi semantică care trebuie cunoscute când se lucrează cu constructori în diferite ipostaze. #include<stdio.h> #include<conio.h> //Definire clasă class intreg { public: int a; //Constructor parametrizat intreg(int v) { printf("Constructor parametrizat\n"); getch(); a=v; } //Constructor de copiere intreg (intreg& v) { a=v.a; printf("Constructor de copiere\n"); getch(); } void dispa() { printf("a=%i\n",a); getch(); }; }; void main() { clrscr(); //Declarare obiect static cu iniţializare; intreg x=100; x.dispa(); //Declarare obiect static cu iniţializare prin copiere intreg y=x;

77

radu
constructor de copiere
Page 78: C sau C++…

y.dispa(); } Evident, o clasă poate avea mai mulţi constructori, ceea ce este tot în beneficiul programatorilor. Crearea obiectelor în C++ Obiectele pot fi create static sau dinamic. Aşa cum s-a văzut şi în exemplul de mai sus sintaxa pentru varianta statică este: <Clasa> <Obiect>[(<Lista de valori>)]; sau <Clasa> <Obiect>[=<Valoare>]; Sintaxa ne arată că odată cu crearea instanţei se poate face şi iniţializarea datelor membre ale obiectului cu ajutorul constructorilor parametrizaţi. Pentru a înţelege alocarea dinamică a memoriei pentru obiecte trebuie să facem o scurtă prezentare a problematicii alocării dinamice structurate a memoriei în C++. Operatori de alocare dinamică a memoriei în C++ În C, alocarea dinamică a memoriei este realizată cu ajutorul funcţiior malloc() şi free(). Din motive de compatibilitate şi nu numai, aceste funcţii sunt valabile şi în C++. Totodată, C++ are un sistem alternativ de alocare dinamică bazat pe operatorii new şi delete. Sintaxa generală pentru new şi delete este: <Pointer>=new <Tip>;

delete <Pointer> ;

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

78

radu
Operatori de alocare dinamică a memoriei în C++
Page 79: C sau C++…

<Pointer>=new <Tip> (<Valoare_iniţială>); ca în exemplul: int *p; p=new int(10); Operatorul new permite alocarea de memorie pentru matrici, utilizând sintaxa: <Pointer>=new <Tip> [Marime]; Memoria alocată unei matrici de operatorul new se eliberează de către delete apelat cu sintaxa: delete [ ] <Pointer>

Matricile nu pot fi iniţializate în timpul alocării. Suntem în măsură să prezentăm soluţia C++ pentru problema alocării dinamice a memoriei pentru obiecte. Obiectelor li se poate aloca memorie dinamic folosind operatorul new. Când procedaţi astfel se creează un obiect şi se returnează un pointer către el. Obiectul creat dinamic se comportă ca oricare altul. Când este creat este apelată şi funcţia constructor. Când este eliberat se execută şi funcţia destructor. Sintaxa generală: <Pointer_la_obiect>=new <Clasa>; Dacă <Clasa> are constructor parametrizat atunci se poate folosi sintaxa: <Pointer_la_obiect>=new <Clasa>(<Lista_valori>); pentru a realiza şi iniţializarea obiectului. În sfârşit, în cazul matricilor de obiecte, cărora li se poate aloca memorie dinamic, trebuie să ne asigurăm că în cazul în care există funcţii constructor, una va fi fără parametri. În caz contrar, compilatorul dă eroare de sintaxă. Modalitatea concretă de utilizare a conceptului de constructor, precum şi a conceptului complementar, destructorul, poate fi desprinsă şi din exemplul de mai jos care reia modelarea obiect orientată a unei stive de întregi. #include <iostream.h> #include <conio.h> #define SIZE 100

79

radu
utilizare constructor si destructor pentru stiva
Page 80: C sau C++…

// Definirea clasei stack class stack { int st[SIZE]; int top; public: stack(); //constructor ~stack(); //destructor void push(int i); int pop(); }; //Implementare constructor stack::stack() { top=0; gotoxy(20,24); cout<<"Stiva este initializata"; getch(); clrscr(); } stack::~stack() { gotoxy(20,24); cout<<"Stiva este distrusa!!"; getch(); clrscr(); } //Implementare functie push void stack::push(int i) { if (top==SIZE) { gotoxy(20,24); cout<<"Stiva este plina!!!"; getch(); return; }; st[top]=i; top++; } //Implementare functie pop int stack::pop()

80

Page 81: C sau C++…

{ if(top==0) { gotoxy(20,24); cout<<"Depasire inferioara stiva!!!"; getch(); return 0; }; top--; return st[top]; } //Functia principala void main() { int i; stack st1,st2; for (i=0;i<=9;i++) { st1.push(i); st2.push(9-i); } clrscr(); for (i=0;i<=9;i++) { gotoxy(35,wherey()+1); cout<<st1.pop()<<"*****"; cout<<st2.pop()<<endl; } getch(); } 2.3 Funcţii virtuale şi polimorfism C++ asigură suport pentru polimorfism atât în timpul compilării cât şi pe timpul execuţiei unui program. Polimorfismul în timpul compilării este legat de posibilitatea de a supraîncărca funcţiile şi operatorii, problemă pe care o vom discuta în paragrafele 2.4 şi 2.7. Polimorfismul în timpul execuţiei este obţinut combinând principiile moştenirii cu mecanismul funcţiilor virtuale. Funcţii virtuale O funcţie virtuală este o funcţie declarată virtual în clasa de bază şi redefinită într-un lanţ de derivare asociat respectivei clase de bază. O funcţie virtuală defineşte o clasă generală de acţiuni. O redefinire a ei introduce o metodă specifică. În esenţă, o funcţie virtuală declarată în clasa de bază acţionează ca un substitut pentru păstrarea elementelor care specifică o clasă generală de acţiuni, stabilind elementele de interfaţă. Redefinirea unei funcţii virtuale într-o

81

radu
Funcţii virtuale
Page 82: C sau C++…

clasă derivată oferă operaţiile efective pe care le execută funcţia. Utilizate static, funcţiile virtuale se comportă ca oricare altă funcţie membru a clasei . Capabilităţile funcţiilor virtuale ies în evidenţă atunci când sunt apelate în context dinamic. Exemplu #include <iostream.h> #include <conio.h> //Clasa de baza care are doua functii membri publici //o functie ordinara //cealalta functie virtuala class CB { public: void f() { cout<<"CB::f()"<<endl; getch(); } virtual void g() //funcţie virtuală { cout<<"CB::g()"<<endl; getch(); } }; //Clasa derivata din clasa de baza class CD:public CB { public: void f() { cout<<"CD::f()"<<endl; getch(); } virtual void g() { cout<<"CD::g()"<<endl; getch(); } }; //Functie care permite utilizarea polimorfismului //Parametrul functiei este un pointer la un obiect de tip CB //De retinut ca un parametru de tip CD este compatibil //cu tipul lui p

82

Page 83: C sau C++…

void ExecPolim(CB *p); void main() { CB *pb=new CB; CD *pd=new CD; clrscr(); pb->f(); pb->g(); pd->f(); pd->g(); clrscr(); cout<<"Apel polimorfic in context CB"<<endl; ExecPolim(pb); cout<<"Apel polimorfic in context CD"<<endl; ExecPolim(pd); delete pb; delete pd; }; void ExecPolim(CB *p) { p->f(); p->g(); } La prima vedere redefinirea unei funcţii virtuale într-o clasă derivată pare similară cu supraîncărcarea unei funcţii. Nu este aşa, deoarece există mai multe diferenţe: Prototipul pentru o funcţie virtuală redefinită trebuie să coincidă cu prototipul specificat în clasa de bază. Funcţiile virtuale nu pot să fie membri de tip static ai clasei din care fac parte şi nu pot fi nici friend Funcţiile obişnuite suportă supraîncărcarea; funcţiile virtuale suportă suprascrierea. În cele ce urmează facem o serie de precizări relativ la clauza virtual. Clauza virtual este moştenită Când o funcţie virtuală este moştenită se moşteneşte şi natura sa virtuală. Astfel că, în situaţia în care o clasă derivată a moştenit o funcţie virtuală şi este folosită drept clasă de bază pentru o altă clasă derivată, funcţia virtuală poate fi în continuare suprascrisă. Altfel spus, o funcţie rămâne virtuală indiferent de câte ori este moştenită. Funcţiile virtuale sunt ierarhizate

83

Page 84: C sau C++…

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

Să mai spunem că există situaţii în care programatorul vrea să se asigure că toate clasele derivate suprascriu o funcţie virtuală sau programatorul defineşte o clasă de bază astfel încât să nu permită definirea unei funcţii virtuale în această clasă. Pentru aceste două situaţii există funcţiile virtuale pure. Sintaxa unei funcţii virtuale pure este: virtual <Tip> <Nume_funcţie> [(<Lista)_de_parametri>)]=0; O clasă care conţine cel puţin o funcţie virtuală pură se numeşte clasă abstractă. O clasă abstractă nu poate fi utilizată pentru crearea de obiecte dar poate fi utilizată pentru a crea pointeri şi referinţe spre astfel de clase permiţând astfel manifestările polimorfice în timpul execuţiei. 2.4 Funcţii supraîncărcate O modalitate C++ de realizare a polimorfismului o reprezintă supraîncărcarea funcţiilor (overloading). În C++, două sau mai multe funcţii pot să aibă acelaşi nume cât timp declaraţiile lor de parametri sunt diferite. În această situaţie se spune că funcţiile cu acelaşi nume sunt supraîncărcate iar procesul este numit supraîncărcarea funcţiilor. Când supraîncărcaţi o funcţie trebuie să respectaţi următoarele cerinţe: Listele de parametri ale diferitelor versiuni ale unei funcţii trebuie să difere; Versiunile pot diferi şi din punct de vedere al tipului returnat; acest lucru nu este, însă, obligatoriu. Odată specificate versiunile unei funcţii respectând restricţiile de mai sus, compilatorul este în măsură să aleagă metoda adecvată unui apel specific. Avantajele supraîncărcării, nu neapărat în context obiect orientat, pot fi desprinse şi din analiza exemplului de cod C++ de mai jos. #include <iostream.h> #include <conio.h> //functia abs este definita in trei moduri diferite int abs(int i); double abs(double d); long abs(long l); void main() { clrscr();

84

radu
2.4 Funcţii supraîncărcate
Page 85: C sau C++…

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

Acest program implementează şi utilizează trei funcţii cu acelaşi nume (abs()) dar diferite prin proprietăţile listelor de parametri formali. Fiecare returnează un mesaj specific şi valoarea absolută a argumentului. Numele abs() reprezintă acţiunea generică care urmează să fie efectuată, compilatorul fiind acela care alege metoda potrivită tipului de parametru actual asociat cu numele funcţiei generice. Este uşor de bănuit puternicul impact al supraîncărcării în cazul programării generice. 2.5 Funcţii inline Aceste funcţii sunt similare macrocomenzilor adresate preprocesoarelor, compilatorul înlocuind fiecare apel de funcţie inline cu corpul acesteia. Funcţiile inline sunt destinate să sprijine implementarea eficientă a tehnicilor OOP în C++. Deoarece abordarea OOP necesită utilizarea extensivă a funcţiilor membre, desele apeluri de funcţii ar putea duce la scăderea performanţelor programelor. Pentru funcţii al căror cod este redus se poate utiliza specificatorul inline pentru a evita acest inconvenient. Similaritatea macrocomenzi-funcţii inline este doar aparentă. Spre deosebire de macrocomenzi, compilatorul consideră funcţiile inline ca funcţii adevărate. O funcţie membru inline se declară astfel: inline <Tip><Nume_Funcţie>([<Lista de parametri>]); 2.6 Funcţii prietene

85

radu
2.5 Funcţii inline
radu
2.6 Funcţii prietene
Page 86: C sau C++…

Este posibil să se permită unei funcţii care nu este membru al unei clase să aibă acces la membrii privaţi ai clasei declarând-o ca funcţie prietenă a clasei (friend). Aşadar, o funcţie prietenă are acces la membrii private şi protected ai clasei căreia îi este prietenă. Sintaxa pentru declararea unei funcţii prietene este: friend <Tip> >Nume_funcţie>[(<Lista_parametri>)] Declararea funcţiei friend se face în interiorul clasei dar implementarea nu este legată de definirea clasei. A se vedea şi exemplul de mai jos pentru mai multe detalii. #include<conio.h> #include <iostream.h> #define liber 0 #define ocupat 1 //Declarare anticipata clasa class Clasa2; class Clasa1 { //<ocupat> daca o metoda a clasei a scris pe ecran //<liber> in caz contrar int stare; public: void setare_stare(int val); friend int ecran_liber(Clasa1 a,Clasa2 b); }; class Clasa2 { //<ocupat> daca o metoda a clasei a scris pe ecran //<liber> in caz contrar int stare; public: void setare_stare(int val); friend int ecran_liber(Clasa1 a,Clasa2 b); }; void Clasa1::setare_stare(int val) { stare=val; } void Clasa2::setare_stare(int val) { stare=val; }

86

Page 87: C sau C++…

int ecran_liber(Clasa1 a, Clasa2 b) { if (a.stare ||b.stare) return 0; else return 1; } void main() { Clasa1 x; Clasa2 y; x.setare_stare(ocupat); y.setare_stare(liber); if (ecran_liber(x,y)) { clrscr(); gotoxy(20,12); cout<<"Ecranul este liber..."; getch(); } else { clrscr(); gotoxy(20,12); cout<<"Ecranul este ocupat..."; getch(); }; x.setare_stare(liber); y.setare_stare(liber); if (ecran_liber(x,y)) { clrscr(); gotoxy(20,12); cout<<"Ecranul este liber..."; getch(); } else { clrscr(); gotoxy(20,12); cout<<"Ecranul este ocupat..."; getch(); }; }

87

Page 88: C sau C++…

2.7 Supraîncărcarea operatorilor Polimorfismul este realizat în C++ şi prin supraîncărcarea operatorilor. După cum s-a văzut în numeroase exemple, în C++ se pot folosi operatorii >> şi << pentru a efectua operaţii I/O relativ la consolă. Aceşti operatori pot efectua aceste operaţii suplimentare (ştiut fiind faptul că pot funcţiona şi ca operatorii de shiftare la nivel de biţi) deoarece operaţiile sunt supraîncărcate în fişierul antet IOSTREAM.H. Când un operator este supraîncărcat, el capătă o semnificaţie suplimentară relativ la o anumită clasă fără să-şi piardă vreunul din înţelesurile iniţiale. Majoritatea operatorilor din C++ pot fi supraîncărcaţi, stabilind semnificaţia lor relativ la o anumită clasă. Limbajul C++ permite supraîncărcarea numai a operatorilor existenţi în limbaj. Dintre aceştia nu pot fi supraîncărcaţi operatorii: . :: ? : . Să mai precizăm faptul că, prin supraîncărcarea operatorilor nu se poate schimba n-aritatea, prioritatea sau asociativitatea operatorilor, acestea fiind elemente predefinite pentru tipuri predefinite şi deci ele se vor menţine şi pentru tipuri abstracte. Prin n-aritate înţelegem că operatorul este unar sau binar. Supraîncărcarea operatorilor se realizează cu ajutorul unor funcţii membre sau prietene speciale. Specificul acestora se află în numele lor. El se compune din cuvântul cheie operator şi unul sau mai multe caractere care definesc operatorul care se supraîncarcă. Între cuvăntul cheie operator şi caracterele care definesc operatorul care se supraîncarcă se află cel puţin un spaţiu alb. Felul în care sunt scrise funcţiile operator diferă pentru cele de tip membru de cele de tip friend. 2.7.1 Crearea unei funcţii operator membru Funcţiile operator membru au sintaxa de implementare: <Tip_returnat> <Nume_clasă>::operator # (<Lista_argumente>) { //Operaţii specifice } Deseori funcţiile operator returnează un obiect din clasa asupra căreia operează, dar <Tip_returnat> poate fi orice tip valid. # este o notaţie pentru numele operatorului aşa cum va fi folosit in program după redefinire. Deci, dacă supraîncărcăm operatorul = atunci sintaxa prototipului funcţiei membru va fi: <Tip_returnat> operator =(<Lista_argumente>); Funcţiile operator membre au un singur parametru sau nici unul. În cazul în care au un parametru, acesta se referă la operandul din dreapta al operatorului. Celălalt operand ajunge la operator prin intermediul pointerului special this.

88

radu
2.7 Supraîncărcarea operatorilor
Page 89: C sau C++…

Astfel stând lucrurile, trebuie să avem grijă de eventualitatea ca operandul din stânga să fie o constantă, ceea ce înseamnă ca nu mai avem context de apel pentru operatorul redefinit. Inivităm cititorul să urmărească exemplul de mai jos. #include<conio.h> #include<iostream.h> //Clasa complex contine functia operator + //ca membru //operatorul + este extins la multimea numerelor complexe class complex { float x,y; public: complex(){}; complex(float a,float b) { static i; i++; clrscr(); cout<<"Lucreaza constructorul...Obiectul->:"<<i; getch(); x=a; y=b; }; void disp_nc(); //prototipul operatorului + complex operator+(complex &op2); }; void complex::disp_nc() { cout<<x<<"+i*"<<y; }; //Implementare operator + //Aceasta sintaxa transforma apelul <ob1+ob2> //in +(ob2) ob1 fiind accesibil prin pointerul //special <this> complex complex::operator+(complex &op2) { complex temp; temp.x=op2.x+x; temp.y=op2.y+y; return temp;

89

Page 90: C sau C++…

}; void main() { complex tamp1,tamp2; complex *pod1,*pod2; complex ob1(10,10),ob2(11,11); clrscr(); gotoxy(20,10);cout<<"Primul numar complex ->:"; ob1.disp_nc(); getch(); gotoxy(20,11);cout<<"Al doilea numar complex->:"; ob2.disp_nc(); getch(); ob1=ob1+ob2; gotoxy(20,13);cout<<"Suma numerelor complexe->:"; ob1.disp_nc(); getch(); pod1=new complex(200,200); pod2=new complex(300,300); tamp1=*pod1; clrscr(); gotoxy(20,10);cout<<"Al treilea numar complex ->:"; tamp1.disp_nc(); tamp2=*pod2; gotoxy(20,11);cout<<"Al patrulea numar complex ->:"; tamp2.disp_nc(); gotoxy(20,14);cout<<"Suma numerelor complexe->:"; tamp1=tamp1+tamp2; tamp1.disp_nc(); getch(); } 2.7.2 Supraîncărcarea operatorilor folosind o funcţie friend Se poate supraîncărca un operator relativ la o clasă folosind o funcţie friend. Deoarece un prieten nu este membru al clasei, nu are disponibil un pointer de tip this. De aceea, unei funcţii supraîncărcate de tip friend operator I se vor transmite explicit operanzii. Deci, dacă supraîncărcăm un operator unar vom avea un parametru, dacă supraîncărcăm unul binar vom avea doi parametri. Dacă supraîncărcăm un operator binar, operandul din stânga este pasat în primul parametruiar cel din stânga în al doilea parametru.

90

Page 91: C sau C++…

Invităm cititorul să urmărească utilizarea funcţiilor friend la supraîncărcare în Exerciţiul 6 de la Capitolul aplicativ al prezentului suport de curs. 2.8 Forma generală a unui program în C++ Cu toate că stilurile individuale diferă, majoritatea programelor în C++ au următoarea formă generală:

#include… declaraţii clase de bază declaraţii clase derivate prototipuri de funcţii nemembre main() { : } definiţii de funcţii ne-membre De remarcat, totodată, faptul că, în majoritatea proiectelor mari, toate

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

91

Page 92: C sau C++…

3 Bazele sistemului I/O în C++ Pe lângă faptul că permite sistemul de I/O din C, C++ defineşte propriul său sistem I/O orientat pe obiecte. Ca şi sistemul de I/O din C, cel din C++ este complet integrat. Aceasta înseamnă că diferitele tipuri de operaţii I/O sunt doar perspective diferite ale aceluiaşi mecanism. Această perspectivă integratoare asupra operaţiilor I/O are la bază, atât în C cât şi în C++, conceptul de flux (stream). 3.1 Fluxuri în C şi C++ Sistemul de fişiere din C şi C++ este proiectat să lucreze cu o mare varietate de echipamente, care include terminale, drivere de disc, drivere de unitate de bandă, etc.

Chiar dacă echipamentele diferă, sistemul de fişiere din C şi C++ le transformă într-un instrument logic numit flux (stream). Toate fluxurile se comportă la fel. Deoarece fluxurile sunt independente de echipamente, o funcţie care poate să scrie într-un fişier de pe hard poate fi folosită cu aceeaşi sintaxă pentru a scrie la alt dispozitiv. Sistemul de fişiere C/C++ recunoaşte două tipuri de fluxuri:text şi binar. Fluxuri de tip text Un flux de tip text este o secvenţă de caractere. Standardul ANSI C permite (dar nu impune) ca un flux de tip text să fie organizat în linii terminate cu un caracter de linie nouă. Totuşi, caracterul de linie nouă din ultima linie este opţional, utilizarea sa fiind determinată de modul de implementare a compilatorului (Majoritatea compilatoarelor de C/C++ nu încheie fluxul de tip text cu un caracter de linie nouă. Să mai semnalăm faptul că, într-un flux de tip text pot să apară anumite transformări cerute de mediul de operare gazdă (De exemplu, un caracter de linie nouă poate fi înlocuit cu perechea început de rând-linie nouă. Acesta este motivul pentru care nu există o relaţie biunivocă între caracterele care sunt scrise sau citite şi cele de la echipamentul extern. Fluxuri binare Un flux binar este o secvenţă de octeţi într-o corespondenţă biunivocă cu cei de la echipamentul extern. Fişiere În C/C++ un fişier poate să fie: un fişier de pe disc, tastatura, ecranul monitorului, imprimanta,etc. Un flux se asociază cu un anumit fişier efectuând o operaţie de deschidere. Odată deschis fişierul, este posibil schimbul de date între el şi programul utilizator care l-a deschis. De observat faptul, trivial pentru cunoscători, că nu toate fişierele au aceleaşi posibilităţi. De exemplu, un fişier de pe disc poate să admită un acces aleator la datele stocate în el, în timp ce imprimanta nu o poate face. Astfel că, putem concluziona, pentru claritate:

92

Page 93: C sau C++…

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

fişierele. Dacă fişierul admite cereri de poziţionare, deschiderea fişierului iniţializează pointerul de fişier la o valoare care indică începutul fişierului. Pe măsură ce se fac operaţii de citire/scriere, pointerul de fişier este incrementat corespunzător naturii operaţiei. Un fişier se disociază de un flux în urma operaţiei de închidere. Dacă este închis un fişier deschis în operaţii de scriere, conţinutul fluxului asociat este scris la dispozitivul extern (acest proces se numeşte flushing=golire a fluxului ). Toate fişierele se închid automat când programul se termină normal. În caz de blocaj sau dacă programul se termină ca urmare a apelului funcţiei abort() fişierele nu se închid. Cu menţiunea că în fişierul antet stdio.h se găsesc structurile de control de tip FILE, indispensabile pentru lucrul cu fişiere în C, prezentăm, în continuare contextul C++ referitor la sistemul I/O. 3.2 Clasele de bază pentru fluxuri în C++ C++ asigură suportul pentru sistemul său de I/O în fişierul antet IOSTREAM.H. În acest fişier antet sunt definite două ierarhii de clase care admit operaţii de I/O. Clasa cu nivelul de abstractizare cel mai înalt se numeşte streambuf şi asigură operaţiile de bază de intrare/ieşire. Ca programatori, nu folosiţi streambuf direct decât dacă veţi deriva propriile clase pentru efectuarea operaţiilor I/O. A doua ierarhie porneşte cu clasa ios, care acceptă operaţii I/O formatate. Din ea sunt derivate clasele istream, ostream şi iostream. Aceste clase sunt folosite pentru a crea fluxuri capabile să citească, să scrie, respectiv să citească/ să scrie date din/ la echipamentele externe. Clasa ios conţine o serie de alte ramuri relativ la lucrul cu fişiere pe care nu ne propunem să le studiem în cadrul acestui curs. Fluxuri predefinite în C++ Când îşi începe execuţia un program C++, se deschid automat patru fluxuri predefinite, pe care le prezentăm în tabelul de mai jos. Flux Semnificaţie Echipament implicit cin Intrare standard Tastatura cout Ieşire standard Ecran cerr Ieşire standard pentru eroare Ecran clog Versiune cu memorie tampon pentru

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.

93

Page 94: C sau C++…

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; cin>>hex>>intvar

Conversie hexazecimală corespunzătoare formatului %x din ANSI C

oct cout<<oct<<intvar; cin>>oct>>intvar;

Conversie octală (formatul %o din C)

resetiosflags(long f)

cout<<resetioflags(ios::dec); Reiniţializează biţii de formatare specificaţi de argumentul întreg de tip long

setbase(int baza) cout<<setbase(10); cin>>setbase(8);

Stabileşte baza de conversie la argumentul întreg (trebuie să fie 0,8,10 sau 16). Valoarea 0 este baza implicită.

setfill(int ch) cout<<setfill(‘.’); cin>>setfill(‘ ‘);

Stabileşte caracterul folosit pentru completarea câmpurilor de mărime specificată

setiosflags(long f) cout<<setiosflags(ios::dec); cin>> setiosflags(ios::hex);

Stabileşte biţii de formatare specificaţi de

94

Page 95: C sau C++…

argumentul întreg de tip long

setprecision(int p) cout<<setprecision(6); cin>>setprecision(10);

Stabileşte precizia conversiei în virgulă mobilă la numărul specificat de zecimale

setw(int w) cout<<setw(6)<<var; cin>>setw(24)>>buf

Stabileşte mărimea unui câmp la numărul specificat de caractere

ws cin ws; Elimină spaţiile libere din fluxul de intrare

Tabelul 16. Manipulatori de formatare a operaţiilor I/O în C++

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

Pentru a utiliza manipulatorii setiosflags şi resetiosflags trebuie cunoscuţi indicatorii de formatare din tabelul de mai jos.

Nume indicator Ce efect are utilizarea indicatorului ios :: skipws Elimină spaţiile goale din intrare ios :: left Aliniază ieşirea la stânga în interiorul lăţimii câmpului ios :: right Aliniază ieşirea la dreapta ios :: scientific Foloseşte notaţia ştiinţifică pentru numerele în virgulă

mobilă. ios :: fixed Foloseşte notaţia zecimală pentru numere în virgulă mobilă ios :: dec Foloseşte notaţia zecimală pentru întregi ios :: hex Foloseşte notaţia hexazecimală pentru întregi ios :: oct Foloseşte notaţia octală pentru întregi ios :: uppercase Foloseşte litere mari pentru ieşire ios :: showbase Indică baza sistemului de numeraţie în cadrul ieşirii (prefixul

0x pentru hexazecimal şi prefixul 0 pentru octal ios :: showpoint Include un punct zecimal pentru ieşiri în virgulă mobilă ios :: showpos Include şi semnul + la afişarea valorilor pozitive ios :: unitbuf Goleşte toate fluxurile după inserarea caracterlor într-un flux

Tabelul 16. Indicatori de formatare a operaţiilor I/O în C++

Notaţia ios :: <Nume_indicator> este folosită pentru a identifica indicatorul ca pe un membru al clasei ios. Exemplificăm cele spuse mai sus prin codul C++ de mai jos.

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

95

Page 96: C sau C++…

double nr; clrscr(); gotoxy(10,6); nr=7./3.; gotoxy(5,6); cout<<"Afisare numar in virgula mobila/ format implicit..."; gotoxy(10,7); cout<<nr; gotoxy(5,9); cout<<"Afisare numar in virgula mobila/ cu precizia specificata..."; gotoxy(10,10); cout<<setprecision(10)<<nr; gotoxy(5,12); cout<<"Afisare numar in virgula mobila/ format virgula fixa..."; gotoxy(10,13); cout.setf(ios::fixed); cout<<setprecision(10)<<nr; gotoxy(5,15); cout<<"Afisare numar in virgula mobila/ format virgula fixa..."; gotoxy(10,16); cout.setf(ios::scientific); cout<<setprecision(10)<<nr; gotoxy(5,18); cout<<"Afisare numar in virgula mobila/ format virgula fixa..."; gotoxy(10,19); cout.setf(ios::scientific|ios::showpos); cout<<setprecision(10)<<nr; getch(); }

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

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

96

Page 97: C sau C++…

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

nume_fisier este un nume extern de fişier care poate include şi specificarea căii de acces. Valoarea parametrului mod determină modul de deschidere a fişierului. Parametrul mod poate avea una sau mai multe din valorile prezentate în tabelul de mai jos. Nume mod Operaţie ios::app Adaugă date în fişier ios::ate Când se deschide pentru prima dată, operează

poziţionarea în fişier la sfârşitul fişierului (ate înseamnă la sfârşit)

ios::binary Deschide fişierul în mod binar, inhibând interpretarea caracterelor <CR> <LF>

ios::in Deschide fişierul pentru citire ios::nocreate Nu efectuează deschiderea fişierului dacă acesta nu există

deja ios::noreplace Dacă fişierul există, încercarea de a-l deschide pentru ieşire

eşuează, cu excepţia cazului în care ios::app sau ios::ate sunt operate

ios::out Deschide fişierul pentru scriere ios:trunc Trunchiază fişierul dacă el există deja

Tabelul 17. Valorile parametrului care stabileşte modul de deschidere a unui fişier

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

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

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

97

Page 98: C sau C++…

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

#include <fstream.h> #include <stdlib.h> #include<conio.h> //Functia principala a programului citeste linia de comanda a programului void main(int argc,char *argv[]) { // Linia de comanda a programului trebuie sa contina doi parametri if (argc!=2) { cerr<<"Mod de utilizare : lisfis <nume fisier CPP>\n"; exit(0); } // Deschidere fisier text de nume specificat in argv[1] ifstream in(argv[1],ios::in); if (!in) { cerr<<"Fisierul nu poate fi deschis!"; exit(0); } char c; clrscr(); while (in.get(c)) { if (wherey()>20) { gotoxy(20,24); cout<<"Press any key to continue..."; getch(); clrscr(); } cout<<c; } in.close(); } I/O în fişiere de tip binar

98

Page 99: C sau C++…

Există două modalităţi de a scrie şi citi date binare într-un fişier. Prima modalitate se referă la utilizarea funcţiilor get() şi put(). Aceste funcţii sunt orientate pe octeţi, ceea ce înseamnă că get() va citi un octet de date iar put() va scrie un octet de date. Funcţia get() are mai multe forme; o prezentăm, în continuare, împreună cu omoloaga ei put(), pe cea mai des folosită: istream &get(char &ch); ostream &put(char ch); Funcţia get() citeşte un singur caracter din streamul asociat şi memorează valoarea sa în ch. De asemenea, se mai observă că funcţia returnează o referinţă către flux. Funcţia put() scrie ch în flux şi returnează fluxului o referinţă. Ca un exemplu de utilizare a funcţiilor get() şi put() prezentăm codul de mai jos, care realizează copierea unui fişier în altul. Dacă numele executabilului asociat acestui cod este copyf, atunci sintaxa de lansare în execuţie a programului este: copyf <fisier_sursa> <fisier_destinatie> #include<stdlib.h> #include <fstream.h> void main(int argc, char *argv) { //Verific daca sunt suficiente argumente if(argc<3) { cerr<<"Mod de utilizare : filecopy <fisier sursa> <fisier destinatie>\n"; exit(0); } //Deschide fisierul de intrare si il conecteaza la fluxul ins ifstream ins(argv[1]); if(!ins) { cerr<<"Nu pot deschide fisierul sursa:"<<argv[1]; exit(1); } //Deschide fisierul de iesire si il conecteaza la fluxul outs ofstream outs(argv[2]); if(!outs) { cerr<<"Nu pot deschide fisierul sursa:"<<argv[2]; exit(1); } //Citeste din sursa si scrie in destinatie char c; while(ins.get(c) && outs) outs.put(c);

99

Page 100: C sau 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();

100

Page 101: C sau C++…

while(!in.eof()) { for(i=0;i<16 && !in.eof();i++) { in.get(c[i]); } if (i<16) i--; for(j=0;j<i;j++) cout<<setw(3)<<hex<<(int) c[j]; for(;j<16;j++) cout<<"\t"; for(j=0;j<i;j++) if(isprint(c[j])) cout<<c[j]; else cout<<"."; cout<<endl; count++; if(count==16) { count=0; cout<<"Press ENTER to continue!"; getch(); clrscr(); cout<<endl; } } in.close(); } Accesul aleator în fişiere În sistemul de I/O din C++, accesul aleator se efectuează folosind funcţiile seekg() şi seekp(). Formele lor cele mai uzuale sunt: istream &seekg(streamoff offset, seek_dir origine); ostream &seekp(streamoff offset, seek_dir origine); streamoff este un tip definit în IOSTREAM.H, capabil să conţină cea mai mare valoare validă pe care o poate avea offset, iar seek_dir este o enumerare care are aceste valori: ios::beg ios::cur ios::end Sistemul de I/O din C++ operează cu doi pointeri asociaţi unui fişier. Unul este pointerul de get , care specifică unde va apărea următoarea operaţie de intrare în fişier. Celălalt este pointerul de put şi specifică unde va avea loc următoarea

101

Page 102: C sau C++…

operaţie de ieşire. După fiecare operaţie de intrare sau ieşire, pointerul corespunzător este avansat automat, secvenţial. Dar, folosirea funcţiilor seekg() şi seekp() permite un acces nesecvenţial la datele din fişier. seekg() şi seekp() deplasează pointerul de înregistrare cu offset octeţi faţă de origine. Cu menţiunea că lucrul cu fişiere în C++ are nenumărate alte faţete pentru a căror prezentare nu dispunem de timpul şi spaţiul necesar, încheiem această scurtă excursie în problematica fişierelor. În fine, pentru curioşi facem şi precizarea că, însuşi bătrânul C are propria filozofie, extrem de puternică, în ceea ce priveşte lucrul cu fişiere.

102

Page 103: C sau C++…

3 Programare generică în C++ În programarea profesională apar nenumărate situaţii în care reutilizarea codului presupune o soluţie de un anumit tip pentru o problemă dată. Situaţia la care ne referim în această secţiune este, potenţial vorbind, următoarea: Ce putem face pentru a comprima codul sursă în situaţia în care structuri de date, diferite ca tip, suportă prelucrări similare. Soluţia acestei probleme de stil de programare o reprezintă programarea generică. Exprimându-ne în termenii limbajului C, o funcţie generică defineşte un set general de operaţii care vor fi aplicate unor tipuri de date diferite. Ca un exemplu, o soluţie generică pentru modelarea unei stive este un pretext ideal pentru precizarea ideilor principale ale programării generice. Altfel spus, dacă dorim o stivă, în care, de la caz la caz, să putem păstra numere întregi, numere reale sau şiruri de caractere (deci tipuri de date diferite), operaţiile fiind aceleaşi ( push() şi pop() ), este clar că ne aflăm în situaţia în care avem nevoie de suport pentru scrierea de cod cu proprietăţi generice. Dacă în Pascal programarea generică se baza pe tipuri procedurale şi programarea la nivel de octet, în C++ există suport evoluat pentru programare generică, sub forma şabloanelor. Cu un şablon, în C++ se poate crea o funcţie generică sau o clasă generică. Funcţii TEMPLATE O funcţie template este o funcţie şablon, având unul sau mai mulţi parametri formali de un tip generic. În funcţie de nevoile de utilizare a acestei funcţii, compilatorul generează funcţii propriu-zise, înlocuind tipul generic cu un tip concret. Tipul concret poate fi orice tip fundamental, derivat sau clasă predefinită. Considerăm un exemplu. Fie funcţia max(x,y) care returnează valoarea maximă a argumentelor sale. Tipul variabilelor x şi y trebuie, obligatoriu, specificat în momentul compilării. Soluţia clasică constă în redefinirea (over-loading) funcţiei max pentru fiecare tip al argumentelor x şi y (de observat şi cele spuse la paragraful 2.2 relativ la funcţii supraîncărcate în C++). Trebuie, aşadar, să definim mai multe versiuni ale funcţiei max. int max(int x, int y) { return (x>y) ? x : y; } float max(float x, float y) { return (x>y) ? x : y; }

103

Page 104: C sau C++…

Mecanismul template permite definirea o singură dată a şablonului de funcţii, după care se generează automat funcţiile propriu-zise în concordanţă cu necesităţile de utilizare, dar ,evident, în faza de compilare. Sintaxa la specificare este: template <class Nume_tip_generic_1 [,…class Nume_tip_generic_n]> Nume_şablon definiţie_şablon De precizat următoarele: Caracterele < şi > fac parte din sintaxa obligatorie. Lista de parametri formali ai unei funcţii şablon trebuie să utilizeze toate tipurile de date generice.

În cazul funcţiilor template nu se fac conversii Funcţia care are acelaşi nume şi acelaşi număr de parametri cu o funcţie

şablon se numeşte caz exceptat (Supraîncărcarea explicită este prioritară). Sintaxa la utilizare este: Nume şablon (Expresie_1[, …,Expresie_n]); Prezentăm, în continuare, definiţia funcţiei şablon max , urmată de o secvenţă client de utilizare.

template <class T>

T max(T x, T y) { return (x>y) ? x : y; } #include <conio.h> #include<iostream.h> //Definire sablon functie template<class T> T max(T x,T y) { return(x>y) ? x:y; } void main() { int i,j; float k,l;

104

Page 105: C sau C++…

clrscr(); i=10; j=2; k=13; l=-7; //Exemple de utilizare sablon gotoxy(20,10);cout<<"Apel max cu parametri variabili de tip float..."<<max(k,l); gotoxy(20,12);cout<<"Apel max cu parametri variabili de tip int ..."<<max(i,j); gotoxy(20,13);cout<<"Apel max cu parametri valoare …float ..."<<max(13.,-7.); getch(); } Prezentăm, totodată, un exemplu de funcţie generică pentru compararea unor date după valoarea unei chei încapsulate în aceste date. #include <conio.h> #include <stdio.h> #include <string.h> #include <iostream.h> #include <ctype.h> //Definirea unei <functii generice> pentru compararea //unor date dupa valoarea unei chei incapsulate //in aceste date template <class T> int comp(T i1,T i2) { if (i1.key<i2.key) return -1; if (i1.key==i2.key) return 0; if (i1.key>i2.key) return 1; }; //Structura aleasa pentru exemplificare //cheia generica incapsulata este campul <key> struct tpers { char np[30]; int key; }; //Instantiere <struct tpers> struct tpers tam,pers[50]; void main() {

105

Page 106: C sau C++…

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

106

Page 107: C sau C++…

for(int j=0;j<i-1;j++) { switch(comp(pers[j],pers[j+1])) { case 1: { tam=pers[j]; pers[j]=pers[j+1]; pers[j+1]=tam; sortat=0; }; }; }; } while(!sortat); //**************************************** //Listare persoane dupa sortare in ordinea //crescatoare a matricolelor clrscr(); cout<<"Listare persoane dupa sortare........................."<<endl; cout<<"____________________________________________"<<endl; for(int k=0;k<i;k++) { if (wherey()>10) { cout<<"_____________________________________________"<<endl; cout<<"Pentru continuare apasati o tasta..."<<endl; getch(); clrscr(); cout<<"Listare persoane dupa sortare........................."<<endl; cout<<"______________________________________________"<<endl; }; cout.width(30);cout.setf(ios::left); cout<<pers[k].np<<" "<<pers[k].key<<endl; }; getch(); } Clase TEMPLATE O clasă template defineşte un şablon pe baza căruia se pot genera clase propriu-zise. Din acest motiv, o clasă template se mai numeşte şi clasă generică , clasă generator sau metaclasă.

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

107

Page 108: C sau C++…

În cadrul clasei şablon se pot declara atribute informaţionale de un tip ambiguu, care sunt particularizate în cadrul clasei generată pe baza şablonului. Evident, şi în acest caz, generarea se face în faza de compilare în concordanţă cu cerinţele clientului. Sintaxa la specificarea clasei: template <class T> class nume_clasă { : }; Sintaxa la implementarea funcţiilor membre ale unei clase template: template <class T> Tip returnat nume_clasa <T>::nume_funcţie(…) { : }; Pentru mai multă claritate prezentăm şi exemplul de mai jos. #include <iostream.h> #include <stdlib.h> #include <conio.h> const int SIZE = 10; //*************************************** //Definire clasa matrice generica template <class ATip> class genmat { ATip a[SIZE]; public: genmat(); ATip &operator [ ](int i); //Supraîncărcare operator [ ] }; //*************************************** //Implementare constructor clasa generica template <class ATip> genmat<ATip>::genmat() { register int i; for(i=0;i<SIZE;i++) a[i]=i; };

108

Page 109: C sau C++…

//*************************************** //Implementare supraincarcare operator [ ] template <class ATip> ATip &genmat<ATip>::operator[ ](int i) { if(i<0 ||i>SIZE-1) { cerr<<"Valoare indice eronata..."; getch(); exit(1); }; return a[i]; }; //*************************************** //Functia principala void main() { genmat<int> intob; genmat<double> doubob; int i; clrscr(); cout<<"Matrice de intregi..."<<endl; for(i=0;i<SIZE;i++) intob[i]=i; for(i=0;i<SIZE;i++) cout<<intob[i]<<endl; getch(); clrscr(); cout<<"Matrice de reali dubla precizie..."<<endl; for(i=0;i<SIZE;i++) doubob[i]=(double)i/3; for(i=0;i<SIZE;i++) cout<<doubob[i]<<endl; getch(); clrscr(); intob[100]=100; }; Tot pentru exemplificare să considerăm şi o situaţie deosebit de simplă. Ni se cere să construim o clasă template corespunzătoare conceptului de stivă, din care, ulterior, să se poată concretiza clase care simulează stiva pentru tipuri de date diferite. //Clasa sablon CSTack template <Class T>

109

Page 110: C sau C++…

class Cstack { T * v; //pointer la varful stivei T * p; //pointer la pozitia curenta din stiva int dim; //dimensiunea stivei public: Cstack(int sz) { v=p=new T [dim=sz]; //alocare dinamica de memorie pentru p şi v } ~Cstack() { delete [ ] v; } void push(T a) { *p++=a; }; T pop() { return *-p } } După ce o astfel de clasă template a fost declarată şi definită, se poate trece deja la instanţierea de obiecte. Singura deosebire faţă de folosirea unei clase obişnuite constă în faptul că trebuie specificat tipul concret care înlocuieşte tipul generic T. Ca un exemplu, să instanţiem un obiect stivă (CStack) , în care încap maximum 100 elemente de tip char. CStack<char> sc(100); În acest caz, compilatorul generează clasa ce rezultă prin înlocuirea lui T cu char, după care instanţiază obiectul sc. Constructorul acestuia primeşte ca argument valoarea 100. Pentru mai multe detalii urmăriţi exemplul de mai jos. #include <iostream.h> #include <conio.h> // Definirea clasei stack. Se vede că instanţele ei nu sunt protejate faţă de //excepţii. //Despre excepţii urmează să discutăm… template <class T> class CStack { T *v;

110

Page 111: C sau C++…

T *top; int dims; public: CStack( int sz) //constructor { v=top=new T[dims=sz]; } ~CStack() //destructor { delete [ ] v; } void push(T a) //Functia de inserare in stiva { *top++=a; //Notatie prescurtata echvalenta cu *top=a; top++ } T pop() { return *--top; //Notatie prescurtata echibvalenta cu - -top; return *top } }; //Functia principala void main() { int i; //Primul exemplu de instantiere a stivei generice ;numere întregi CStack<int> st1(20); //Încarcare stiva for (i=0;i<=9;i++) { st1.push(i); } //Vizualizare continut stiva clrscr(); for (i=0;i<=9;i++) { gotoxy(35, wherey()+1); cout<<st1.pop()<<"*****"; } getch();

111

Page 112: C sau C++…

//Al doilea exemplu de instantiere a stivei generice; caractere, începand cu “A” CStack<char> st2(20); //Încarcare stiva for (i=65;i<75;i++) { st2.push((char) i); } //Vizualizare continut stiva clrscr(); for (i=0;i<10;i++) { gotoxy(35, wherey()+1); cout<<st2.pop()<<"*****"; } getch(); }

112

Page 113: C sau C++…

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

controlul sistemului asupra excepţiilor în timpul rulării. throw Lansează o excepţie de un anumit tip catch Captează o excepţie lansată

Tabelul 18 Cuvintele cheie ale limbajului C++ referitoare la tratarea excepţiilor Forma de bază a tratării excepţiilor Aşadar, atunci când programele dumneavoastră efectuează prelucrarea excepţiilor, trebuie să includeţi în cadrul unui bloc try instrucţiunile pe care doriţi să le monitorizaţi în vederea unei excepţii. Dacă execuţia unei instrucţiuni se termină cu o eroare, trebuie să lansaţi o eroare corespunzătoare acţiunii funcţiei în care se află instrucţiunea. Programul plasează instrucţiunea throw în cadrul blocului try-catch. Forma generalizată a blocului care captează şi tratează erorile este: try{ //blocul try //if(eroare) throw valoare_excepţie; } catch (Tip_excepţie Nume_variabilă ){ //Prelucrarea excepţiei }

113

Page 114: C sau C++…

În cadrul acestei forme generalizate, valoarea valoare_excepţie lansată trebuie să corespundă tipului Tip_excepţie . Scrierea unui handler de excepţii simplu Pentru a înţelege mai bine semantica unui handler de excepţii, studiaţi programul de mai jos. #include <iostream.h> void main() { cout<<"Start"<<endl; try { cout<<"In interiorul blocului try…"<<endl; throw 100; cout<<"Nu se va executa…"; } catch (int i) { cout<<"Am captat o excepţie --valoarea este:"; cout<<i <<endl; } cout<<"Sfarsit…"; }; Programul de mai sus implementează un bloc try-catch simplu. În loc să se aştepte ca programul să eşueze datorită unei erori, se utilizează instrucţiunea throw pentru lansarea erorii prezumtive. După ce blocul try lansează eroarea, blocul catch o captează şi prelucrează valoarea transmisă de instrucţiunea throw. Este evident şi din acest exemplu că mecanismul try-throw-catch oferă suport pentru rezolvarea problemei tratării excepţiilor dar nu rezolvă de la sine această problemă. Altfel spus, tratarea corectă a excepţiilor unui program este o problemă de atitudine ca proiectant şi ca programator. Lansarea excepţiilor cu o funcţie din cadrul blocului try Atunci când programele apelează funcţii din cadrul blocurilor try , C++ va transmite excepţia apărută într-o astfel de funcţie în afara funcţiei dacă nu există un bloc try în interiorul funcţiei. Exemplul de mai jos arată cum se petrec lucrurile într-o astfel de situaţie. #include <iostream> void XHandler(int test) { cout<<"Inauntrul functiei XHandler, test are valoarea:"<<test<<endl; if(test) throw test;

114

Page 115: C sau C++…

}; void main() { cout<<"Start:"<<endl; try { cout<<"Inauntrul blocului try…"<<endl; XHandler(1); XHandler(2); XHandler(0); } catch(int i) { cout<<"Am captat o exceptie. Valoarea este:"; cout<<i<<endl; }; cout<<"Sfarsit"; }; Plasarea unui bloc try intr-o funcţie Am văzut cum apare un bloc try în funcţia principală a unui program. C++ permite blocuri try şi în alte funcţii ale unui program diferite de funcţia principală. Atunci când se plasează un bloc try într-o funcţie C++ reiniţializează blocul de fiecare dată când intraţi se intră în acea funcţie. Programul următor ilustrează cele spuse. #include <iostream.h> void XHandler(int test) { try { if(test) throw test; } catch(int i) { cout<<"Am captat exceptia nr.: "<<i<<endl; } }; void main() { cout<<"Start: "<<endl; XHandler(1); XHandler(2); XHandler(0); XHandler(3); cout<< "Sfarsit";

115

Page 116: C sau C++…

}; Un comentariu pe marginea celor prezentate până acum ar fi următorul: o instrucţiune catch se execută numai dacă programul lansează o excepţie în cadrul blocului try situat imediat înainte. În caz că o astfel de excepţie nu se lansează blocul catch va fi ignorat. Utilizarea mai multor instrucţiuni catch cu un singur bloc try Pe măsură ce tratările excepţiilor devin tot mai complexe, uneori este necesar şi posibil ca un singur bloc try să lanseze excepţii de mai multe tipuri. În cadrul programelor dumneavoastră puteţi construi un handler de excepţii astfel încât să accepte captarea mai multor excepţii. Într-o astfel de situaţie sintaxa generală este: try { //instrucţiuni } catch (tip1) { //tratare excepţie 1 } catch(tip2) { //tratare excepţie 2 } : catch(tipn) { //tratare excepţie n } Cu acest amendament sintactic deducem că instrucţiunile catch pot capta orice tip returnat, nu numai tipurile de bază acceptate de C++. Acest "fenomen" este ilustrat în codul de mai jos. #include <iostream.h> void XHandler(int test) { try { if(test==0) throw test; if(test==1) throw "Sir de caractere…";

116

Page 117: C sau C++…

if(test==2) throw 121.25; } catch(int i) { cout<<"Am captat exceptia #:"<<i<<endl; } catch(char *sir) { cout<<"Am captat exceptia de tip sir de caractere:"<<sir<<endl; } catch(double d) { cout<<"Am captat exceptia #:"<<d<<endl; } }; void main() { XHandler(0); XHandler(1); XHandler(2); cout<<"Sfarsit"; }; Blocuri catch generice (utilizarea operatorului puncte de suspensie) Programele scrise de dumneavoastră pot capta excepţii din cadrul mai multor blocuri try (de exemplu un bloc try care incapsuleaza mai multe functii care lanseaza exceptii diferite din blocuri try diferite sau să utilizeze mai multe instrucţiuni catch într-un singur bloc try. C++ permite, de asemenea, utilizarea operatorului puncte de suspensie (…) pentru a capta orice tip de eroare care apare într-un singur bloc try. Sintaxa care permite captarea tuturor erorilor care apar într-un bloc try este prezentată mai jos. try { //Instructiuni } catch(…) { //tratarea exceptiei }

117

Page 118: C sau C++…

Pentru exemplificare propun codul de mai jos. #include <iostream.h> void XHandler(int test) { try { if(test==0) throw test; if(test==1) throw 'a'; if(test==2) throw 121.25; } catch(…) { cout<<"Am captat o exceptie"<<endl; } }; void main() { cout<<"Start:"<<endl; XHandler(0); XHandler(1); XHandler(2); cout<<"Sfarsit"; }; Evident, prelucrările din cadrul blocului catch generic trebuie să fie independente de tipul erorii. Mecanismul captării excepţiilor explicite poate fi combinat cu mecanismul excepţiilor generice ca în exemplul de mai jos. #include <iostream.h> void XHandler(int test) { try { if(test==0) throw test; if(test==1) throw 'a'; if(test==2) throw 121.25; } catch(int i)

118

Page 119: C sau C++…

{ cout<<"Am captat o exceptie de tip intreg…"<<endl; } catch(…) { cout<<"Am captat o exceptie generica"<<endl; } }; void main() { cout<<"Start:"<<endl; XHandler(0); XHandler(1); XHandler(2); cout<<"Sfarsit"; }; Restrictionarea exceptiilor Pe măsură ce programele dumneavoastră devin mai complexe, ele vor apela frecvent funcţii din cadrul unui bloc try. Atunci când programele dumneavoastră apelează funcţii dintr-un bloc try, puteţi restricţiona tipurile de excepţii pe care funcţia apelată le poate lansa. De asemenea puteţi preveni lansarea oricărei excepţii dintr-o anumită funcţie. Sintaxa pentru restricţionare este: tip_returnat nume_functie(lista_arg) throw(lista_tipuri ) { //Cod functie } Sintaxa care inhibă lansare oricărei excepţii este: tip_returnat nume_functie(lista_arg) throw() { //Cod functie } Este bine să subliniem că atunci când declaraţi o funcţie cu clauza throw ea poate să lanseze doar acele tipuri precizate în listă. Dacă funcţia lansează orice al tip programul este abortat. Un exemplu în continuare.

119

Page 120: C sau C++…

#include <iostream.h> void XHandler(int test) throw(int, char, double) { if(test==0) throw test; if(test==1) throw 'a'; if(test==2) throw 121.25; } void main() { cout<<"Start:"<<endl; try { XHandler(0); } catch(int i) { cout<<"Am captat un intreg…"<<endl; } catch(char c) { cout<<"Am captat un caracter…"<<endl; } catch(double d) { cout<<"Am captat un double…"<<endl; } cout<<"Sfarsit"; }; Relansarea unei excepţii În anumite situaţii poate fi necesar să se relanseze o excepţie din interiorul unui handler de excepţii. Dacă relansaţi o excepţie, C++ o va transmite unui bloc try exterior dacă acesta există. Cea mai probabilă situaţie în care puteţi opta pentru această variantă este atunci când doriţi să trataţi o excepţie în cadrul a două programe handler distincte. Pentru mai multă claritate urmăriţi exemplul de mai jos. #include <iostream.h> void XHandler(void) { try {

120

Page 121: C sau C++…

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

Mod de utilizare a excepţiilor Toate elementele prezentate au încercat să demonstreze că C++ are o atitudine activă faţă de problema tratării excepţiilor. Suportul oferit de C++ îl ajută pe programator să definească un comportament al programului când se produc evenimente anormale sau neaşteptate. O idee mai pragmatică de utilizare a suportului C++ în situaţii efective o puteţi desprinde din exemplul de mai jos. #include <iostream.h> void div (double a, double b) { try { if(!b) throw b; cout<<"a/b="<<a/b<<endl; } catch(double b) { cout<<"Nu se poate imparti la zero…"<<endl; } } void main() { double i,j; do

121

Page 122: C sau C++…

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

122

Page 123: C sau C++…

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

123

Page 124: C sau C++…

1 Sortarea elementelor unui şir de numere reale prin metoda bulelor. Program pretext pentru modularizare. Se utilizează fişierul antet facilcrt.h prezentat în continuarea acestei secţiuni. #include <stdio.h> #include <conio.h> #include <stdlib.h> #include"facilcrt.h" #include<string.h> #define _lms 100 #define _optiune "123" void prelsir(); char prelopt(); void sort(); int dims; float x[ _lms]; //Functia principala.. void main() { char op; do { op=prelopt(); switch (op) { case '1': prelsir(); break; case '2': sort(); break; } } while (op!='3'); } //Preluare optiuni program... char prelopt() { char c; do { textcolor(RED);

124

Page 125: C sau C++…

textbackground(CYAN); clrscr(); gotoxy(20,8) ;cprintf("Optiunile programului sunt:"); gotoxy(20,9) ;cprintf("1-Preluare elemente sir"); gotoxy(20,10);cprintf("2-Sortare/afisare sir"); gotoxy(20,11);cprintf("3-Terminare program"); makewin2(20,8,60,11); makewin2(20,14,60,14); gotoxy(20,14);cprintf("Optiunea Dvs.:"); c=getch(); } while (strchr(_optiune,c)==NULL); return c; }; //Preluare elemente sir... void prelsir() { int i; clrscr(); makewin2(20,5,60,5); gotoxy(20,5); printf("Dimensiune sir..."); gotoxy(20+strlen("Dimensiune sir..."),5); scanf("%d",&dims); makewin2(20,8,60,7+dims); gotoxy(20,8); for (i=0;i<dims;i++) { gotoxy(20,wherey()); printf("Element[%d]=",i); scanf("%f",&x[i]); } }; //Determinare maxim... void sort() { float aux; int i,sortat; //Sortare elemnete sir do { sortat=1; for(i=0;i<dims-1;i++) {

125

Page 126: C sau C++…

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

126

Page 127: C sau C++…

2 Fşier antet care încapsulează câteva facilităţi de lucru în mod text, utile pentru realizarea unor interfeţe agreabile ale programelor cu utilizatorii. Numit în exemplul precedent prin facilcrt.h. #include <conio.h> #include <stdio.h> #include <string.h> #define css 1 //Afisare text dinspre capete... void converg(int end,int linie,char *mesaj) { int i,j,ls,cs; ls=(end-strlen(mesaj))/2; cs=ls+strlen(mesaj)-1; for (i=ls,j=cs;i<=j;i++,j--) { gotoxy(i,linie);

printf("%c",mesaj[i-ls]); delay(50); //prototipul in fisierul antet <dos.h> gotoxy(j,linie);

printf("%c",mesaj[strlen(mesaj)-1-cs+j]); } getch(); } //Activare video-invers void avideo() { textcolor(BLACK); textbackground(WHITE); } //dezactivare video-invers void dvideo() { textcolor(WHITE); textbackground(BLACK); } //Afisare centrata text in interiorul unei linii void acentext(int ls, int ld, int linia, char *sir) { int sw; int col; sw=(ls>=1) && (ls<=79) && (ld<=80) && (ld>=2) && (ls<ld);

127

Page 128: C sau C++…

sw=sw && ((ld-ls+1)>=strlen(sir)); if (sw) { col=ls+(ld-ls+1-strlen(sir))/2; gotoxy(col,linia); cprintf(sir); } } //Construire fereastra cu rama dreptunghiulara de //dimensiuni specificate void makewin2(int ass, int oss, int adj, int odj) { short int i; int sw; sw=(ass>0) && (ass<81) && (adj>0) && (adj<81) && (ass<=adj); sw=sw && (oss>0) && (oss<25) && (odj>0) && (odj<25) && (oss<=odj); if(sw) { for(i=ass;i<=adj;i++) { gotoxy(i,oss-1);

printf("\315"); gotoxy(i,odj+1);

printf("\315"); } for(i=oss;i<=odj;i++) { gotoxy(ass-1,i);

printf("\272"); gotoxy(adj+1,i);

printf("\272"); } gotoxy(ass-1,oss-1); printf("\311"); gotoxy(adj+1,oss-1); printf("\273"); gotoxy(ass-1,odj+1); printf("\310"); gotoxy(adj+1,odj+1); printf("\274"); } else { gotoxy(1,24);

128

Page 129: C sau C++…

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

129

Page 130: C sau C++…

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

-citirea elementelor unui tablou bidimensional; -transpunerea unei matrice; -afişarea elementelor unei matrice;

#include <conio.h> #include<iostream.h> int i,j; //Alocare dinamica a memoriei pentru o matrice de intregi //Numarul de linii si coloane sunt precizate prin lista de //parametri void almemmat(int **p,int nl,int nc) { for(i=0;i<nl;i++) p[i]=new int[nc]; }; //Citirea elementelor matricei de intregi de la tastatura void citmat(int **p,int nl,int nc) { for(i=0;i<nl;i++) for(j=0;j<nc;j++) { gotoxy(10,12); cout<<"Elementul["<<i<<","<<j<<"]="; cin>>p[i][j]; }; }; //Afisarea matricei de intregi void afismat(int **p,int nl,int nc) { for(i=0;i<nl;i++) { for(j=0;j<nc;j++) cout<<p[i][j]<<" | "; cout<<endl; }; } //Transpunerea matricei void transpun(int **p,int **q,int nl,int nc)

130

Page 131: C sau C++…

{ for(i=0;i<nl;i++) for(j=0;j<nc;j++) q[j][i]=p[i][j]; }; //Functia principala void main() { int **pmat; int **q; int nl=6,nc=2; clrscr(); almemmat(pmat,nl,nc); almemmat(q,nc,nl); citmat(pmat,nl,nc); transpun(pmat,q,nl,nc); afismat(q,nc,nl); getch(); };

131

Page 132: C sau C++…

4 Sortarea unui şir de numere întregi prin metoda HEAP_SORT. Se utilizează reprezentarea liniară a unui arbore binar. #include<iostream.h> #include<conio.h> const int MAX_LIST_SIZE=100; typedef int element; typedef element list_type[MAX_LIST_SIZE]; typedef int boolean; void walk_down(list_type list, int j, int n) { int i,k; element ref; boolean found_spot=0; i=j; ref=list[i]; k=2*i+1; while((k<n) && !found_spot) { if(k<n-1) if(list[k+1]>list[k]) ++k; if (list[k]>ref) { list[i]=list[k]; i=k; k=2*i+1; } else found_spot=1; } list[i]=ref; } void heap_sort(list_type list, int n) { int y; element temp; y=n/2-1; while (y>=0) { walk_down(list,y,n); --y; } y=n;

132

Page 133: C sau C++…

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

133

Page 134: C sau C++…

5 Rasfoirea unui fişier text şi contorizarea apariţiilor unui caracter într-un fisier text utilizând fluxuri binare. #include <fstream.h> #include <stdlib.h> #include "facilcrt.h" char optiuni[]="1234"; char *nf; int sfs; //Semnatura functie prelopt() char prelopt(); //Semnatura functie selnumef() char *selnumef(); //Semnatura functie rasfft() void rasfft(char *n); //Semnatura functie detstatcar() void detstatcar(); //Implementare functie principala void main() { sfs=0; do { switch(prelopt()) { case '1': nf=selnumef(); break; case '2': rasfft(nf); break; case '3': detstatcar(); break; case '4': exit(0); } } while (2>1); } //Implementare functie detstatcar() void detstatcar() { char c;

134

Page 135: C sau 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 { gotoxy(1,24); textcolor(RED+BLINK); cprintf("Selectati mai intai fisierul..."); getch(); textcolor(WHITE); } } //Implementare functie selnumef() char *selnumef()

135

Page 136: C sau C++…

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

136

Page 137: C sau C++…

clrscr(); } cout<<ch; } getch(); } } else { gotoxy(1,24); textcolor(RED+BLINK); cprintf("Selectati mai intai fisierul..."); getch(); textcolor(WHITE); } } //Implementare functie prelopt() char prelopt() { clrscr(); gotoxy(20,8); cout<<"Optiunile programului..."; makewin2(20,8,60,8); gotoxy(20,11); cout<<"1-Selectare fisier text"; gotoxy(20,12); cout<<"2-Rasfoire fisier text"; gotoxy(20,13); cout<<"3-Contorizare aparitii caracter specificat"; gotoxy(20,14); cout<<"4-Terminare program"; gotoxy(20,15); cout<<" Alegerea Dvs.:"; makewin2(20,11,60,15); gotoxy(20+strlen(" Alegerea Dvs.:"),15); char op; do op=getch(); while (strchr(optiuni,op)==NULL); return op; }

137

Page 138: C sau C++…

6 Modelarea C a unor facilităţi de lucru cu memoria video în mod text. Exemplul de cod prezentat mai jos ilustrează, în alt context, utilitatea pointerilor, precum şi o serie de elemente tehnice referitoare la scrierea/citirea în/din memoria video, atunci când se lucrează în mod text. #ifndef _stdio_h #include<stdio.h> #define _stdio_h #endif #ifndef _conio_h #include<conio.h> #define _conio_h #endif #ifndef _string_h #include<string.h> #define _string_h #endif #define startcol1 16 #define startcol2 36 #define startcol3 56 #include<dos.h> #include<iostream.h> #include<stdlib.h> //Variabila in care se pastreaza adresa memoriei video char far *mem_vid; int afcent(int lin,char s[80]); void codasc(); void antet(); int mod_video(); void setvptr(); void scrie_car(int x,int y,char ch,int atrib); void scrie_sir(int x,int y,char *p,int atrib); //Functia returneaza modul video corespunzator //echipamentului pe care se ruleaza programul int mod_video() { union REGS r; r.h.ah=15; return int86(0x10,&r,&r)&255; }

138

Page 139: C sau C++…

//Functia seteaza adresa memoriei video in functie de //modul video curent void setvptr() { int vmod; vmod=mod_video(); if((vmod!=2)&&(vmod!=3)&&(vmod!=7)) { cout<<"Video trebuie sa fie in modul text cu 80 de coloane."; exit(1); } if(vmod==7) mem_vid=(char far *) 0xB0000000; else mem_vid=(char far *)0xB8000000; } //Functia permite scrierea unui caracter direct //in memoria video pe linie si coloana specificate void scrie_car(int x,int y,char ch,int atrib) { char far *v; v=mem_vid; v+=(y*160)+(x-1)*2; *v++=ch; *v=atrib; } //Functia permite scrierea unui sir de caractere //in memoria video pe linie si coloana specificata void scrie_sir(int x,int y,char *p,int atrib) { register int i; char far *v; v=mem_vid; v+=(y*160)+(x-1)*2; for(i=y;*p;i++) { *v++=*p++; *v++=atrib; } } //Afisarea antetului tabelei de coduri ASCII void antet() {

139

Page 140: C sau C++…

gotoxy(1,1); for(int i=0;i<78;i++) printf("\xf"); gotoxy(16,2);printf("Cod Caracter"); gotoxy(36,2);printf("Cod Caracter"); gotoxy(56,2);printf("Cod Caracter\n"); for(i=0;i<78;i++) printf("\xf"); } //Afisare coduri ASCII pe trei coloane void codasc() { int nrcan=2; int i,j,cc; int lin=1; int col=1; clrscr(); antet(); for(i=0;i<256;i++) { if(lin>19) if(col==3) { gotoxy(1,24); gotoxy(1,23); for(j=0;j<78;j++) printf("\xf"); printf("\nESC-pentru terminare\\Alta tasta pentru

continuare..."); if(getch()=='\033') return; col=lin=1; clrscr(); antet(); } else { lin=1; col++; } switch(col) { case 1: { cc=startcol1; break; }

140

Page 141: C sau C++…

case 2: { cc=startcol2; break; } case 3: { cc=startcol3; break; }; } lin++; gotoxy(cc,nrcan+lin);printf("%i",i); gotoxy(cc+7,nrcan+lin);printf("%c",(char)i); }; gotoxy(1,23); for(j=0;j<78;j++) printf("\xf"); gotoxy(1,24); printf("Pentru terminare apasati o tasta...."); getch(); } //Afisare centrata text pe o linie specificata int afcent(int lin,char s[80]) { int col; int parer=0; if((lin<1)||(lin>24)) parer++; if(!strlen(s)) parer=parer+2; if(parer) return parer; col=(80-strlen(s))/2; gotoxy(col,lin); printf("%s",s); return parer; }

141

Page 142: C sau C++…

7 Modelarea C++ a operaţiilor cu şiruri de caractere. Problemă pretext pentru aplicarea corectă a principiului încapsulării, pentru utilizarea în funcţie de cerinţe a constructorilor în specificarea comportamentului unei clase, pentru redefinirea unor operatori uzuali în lucru cu şiruri de caractere (concatenarea polimorfică, adresarea unui caracter al tabloului de caractere, etc.). #include <stdio.h> #include<string.h> //***************************************************** //Clasa modeleaza lucrul cu siruri de caractere in C++ //class string { int lsir; //lungimea sirului char *psir; //tablou de caractere alocat dinamic public: //Constructor clasic; stringul contine sirul vid string(int l=1); //Constructor de copiere a unui obiect deja creat string( string& x);

//Constructor de copiere care utilizeaza un sir //obisnuit

string(char sir[]); //Redefinire operator atribuire pentru copiere obiect void operator=(string x); //Redefinire operator atribuire pentru copiere sir void operator=(char sir[]); //Redefinire operator de testare egalitate intre doua //siruri int operator==(string & x); //Redefinire operator de comparare > int operator>(string & x); //Redefinire operator de comparare < int operator<(string & x); //Concatenarea a doua siruri

142

Page 143: C sau C++…

string operator+(string & x); //Redefinire operator + ca functie prietena friend string operator+(string &x, char s[]); //Redefinire operator + ca functie prietena friend string operator+(char s[],string &x); //Destructor clasa ~string(); //Returnare lungime sir ca functie prietena friend int length(string &x); //Extragere caracter din sir char & operator[](int i); //Extragere sir din obiect ca functie prietena friend char * sir(string& x); }; //***************************************************** //Implementare functii membre //Implementare constructor creare sir vid string::string(int) { lsir=1; psir=new char[1]; *psir=0; } //Implementare constructor de copiere obiect deja creat string::string( string & x) { lsir=x.lsir; psir=new char[lsir]; strcpy(psir,x.psir); } //**************************************************** //Implementare constructor de copiere sir obisnuit string::string(char sir[]) { lsir=strlen(sir)+1; psir=new char[lsir]; strcpy(psir,sir); }

143

Page 144: C sau C++…

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

//Implementare redefinire operator de comparare >

int string::operator==(string & x) { if(!strcmp(psir,x.psir)) return 1; else return 0; }

int string::operator>(string & x) { if (strcmp(psir,x.psir)>0) return 1; else return 0; }

//Implementare redefinire operator de comparare < int string::operator<(string & x) { if (strcmp(psir,x.psir)<0) return 1; else return 0; };

144

Page 145: C sau C++…

//Implementare concatenare a doua siruri string string::operator+(string & x) { string s; strcat(s.psir,psir); strcat(s.psir,x.psir); s.lsir=lsir+x.lsir-1; return s; } //Implementare destructor string::~string() { delete psir; } //Implementare redefinire operator [] char &string::operator[](int i) { return psir[i-1]; } //implementare extragere sir ca functie prietena char * sir(string &x) { return x.psir; }; //Implementare redefinire operator de concatenare //Varianta obiect+sir string operator+(string &x, char s[]) { string a; strcat(a.psir,x.psir); strcat(a.psir,s); a.lsir=x.lsir+strlen(s); return a; }; //Implementare redefinire operator de concatenare //Varianta sir+obiect string operator+(char s[],string &x) { string a; strcat(a.psir,s); strcat(a.psir,x.psir); a.lsir=x.lsir+strlen(s); return a;

145

Page 146: C sau C++…

};

146

Page 147: C sau C++…

8 Aplicatie care ilustreaza lucrul cu fisiere cu tip in C++ Din nou o problemă pretext, care se referă la actualizarea unui fişier în care se păstrează întrebările unui test şi răspunsurile corecte, presupunând că o întrebare are mai multe alternative de răspuns. #include <conio.h> #include <stdio.h> #include <iostream.h> #include <ctype.h> #include<stdlib.h> #include <string.h> #include <fstream.h> char optiuni[]="1234567"; char *nf; int sfs,nri; //Tipul inregistrare typedef struct struct_test { int nr_intrebare; int alt_corecta; } t_inr_fis; //Variabila inregistrare t_inr_fis inr; //Functia permite preluarea optiunilor programului char prelopt(); //Functia permite selectarea numelui fisierului de lucru char *selnumef(); //Functia permite crearea fisierului void crefis(); //Functia permite adaugarea de inregistrari in fisier void adinfis(); //Functia permite steregerea unei inregistrari din fisier void steinfis(int nrint); //Functia permite vizualizarea inregistrarilor fisierului void vizfis(); void selin(int ni); //Implementare functie principala

147

Page 148: C sau C++…

void main() { char op; sfs=0; do { op=prelopt(); switch(op) { case '1': nf=selnumef(); break; case '2': crefis(); break; case '3': adinfis(); break; case '4': { clrscr(); gotoxy(10,12); cout<<"Numar intrebare inregistrare de

sters:"; cin>>nri; steinfis(nri); break; }; case '5': vizfis(); break; case '6': { clrscr(); gotoxy(10,12); cout<<"Numar inregistrare de selectat:"; cin>>nri; selin(nri); break; }; case '7': break; } } while (op!='7');

void selin(int ni)

};

{ if (sfs) { ifstream ftestin; ftestin.open(nf,ios::in|ios::binary); ftestin.seekg((ni-1)*sizeof(t_inr_fis),ios::beg);

148

Page 149: C sau C++…

ftestin.read((unsigned char*) &inr,sizeof(t_inr_fis)); if(!ftestin.eof()) { clrscr();

cout<<"Nr. intrebare :"; cout<<inr.nr_intrebare; gotoxy(10,13); cout<<"Alternativa corecta:"; cout<<inr.alt_corecta; getch(); ftestin.close(); }; } else { gotoxy(1,24);

cerr<<"Fisier neselectat....";

}

while (!ftestin.eof())

gotoxy(10,12);

cout<<"Selectati sau creati mai intai un fisier!...."; getch(); } }; void steinfis(int nrint) { ifstream ftestin; ofstream fmanout; if(!sfs) {

getch();

else { fmanout.open("man",ios::out|ios::binary); clrscr(); ftestin.open(nf,ios::in|ios::binary);

{ ftestin.read((unsigned char*)&inr,sizeof(t_inr_fis)); if ((inr.nr_intrebare!=nrint)&&(!ftestin.eof())) fmanout.write((unsigned char*) &inr,

sizeof(t_inr_fis)); }; fmanout.close();ftestin.close(); }; };

149

Page 150: C sau C++…

gotoxy(20,10);

//Implementare crefis() void crefis() { ofstream ftestout; clrscr();

cout<<"Nume fisier de creat:"; cin>>nf; ftestout.open(nf,ios::out|ios::binary); sfs=1; char ras; do { clrscr(); gotoxy(20,10);cout<<"Numar intrebare:"; cin>>inr.nr_intrebare; gotoxy(20,11);cout<<"Raspuns corect :"; cin>>inr.alt_corecta; ftestout.write((unsigned char *) &inr,

sizeof(t_inr_fis)); gotoxy(20,12);cout<<"Mai aveti de introdus(D,N):"; ras=getch(); } while (!(toupper(ras)=='N')); ftestout.close(); } //Implementare adinfis() void adinfis() { if (sfs) { ofstream ftestout; clrscr(); gotoxy(20,10); ftestout.open(nf,ios::app|ios::binary); char ras; do { clrscr(); gotoxy(20,10);cout<<"Numar intrebare:"; cin>>inr.nr_intrebare; gotoxy(20,11);cout<<"Raspuns corect :"; cin>>inr.alt_corecta; ftestout.write((unsigned char *) &inr, sizeof(t_inr_fis));

150

Page 151: C sau C++…

gotoxy(20,12);cout<<"Mai aveti de introdus(D,N):"; ras=getch(); } while (!(toupper(ras)=='N')); ftestout.close(); } else { gotoxy(1,24); cout<<"Selectati sau creati mai intai un fisier!...."; getch(); } } //Implementare vizfis() void vizfis() { if (sfs) { ifstream ftestin; ftestin.open(nf,ios::in|ios::binary); while (!ftestin.eof()) { clrscr(); ftestin.read((unsigned char*)&inr,sizeof(t_inr_fis)); if(!ftestin.eof()) { gotoxy(10,12); cout<<"Nr. intrebare :"; cout<<inr.nr_intrebare; gotoxy(10,13); cout<<"Alternativa corecta:"; cout<<inr.alt_corecta; getch(); }; }; ftestin.close(); } else { gotoxy(1,24); cout<<"Selectati sau creati mai intai un fisier!...."; getch(); } }

151

Page 152: C sau C++…

//Implementare selnumef() char *selnumef() { char *n; sfs=1; clrscr(); gotoxy(20,10); cout<<"Numele fisierului de lucru:"; do { strcpy(n,""); gotoxy(20+strlen("Numele fisierului de lucru:"),10); gets(n); } while(strlen(n)==0); return n; } //Implementare prelopt() char prelopt() { clrscr(); gotoxy(20,8); cout<<"Optiunile programului..."; gotoxy(20,9); cout<<"__________________________________________"; gotoxy(20,11); cout<<"1-Selectare fisier structura test"; gotoxy(20,12); cout<<"2-Creare fisier structura test"; gotoxy(20,13); cout<<"3-Adaugare date in fisierul structura test"; gotoxy(20,14); cout<<"4-Stergerea unei inregistrari din fisier"; gotoxy(20,15); cout<<"5-Vizualizare date fisier structura"; gotoxy(20,16); cout<<"6-Afisare inregistrare de numar specificat"; gotoxy(20,17); cout<<"7-Terminare program"; gotoxy(20,19); cout<<"__________________________________________"; gotoxy(20,20); cout<<" Alegerea Dvs.:"; gotoxy(20+strlen(" Alegerea Dvs.:"),20); char op;

152

Page 153: C sau C++…

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

153

Page 154: C sau C++…

BIBLIOGRAFIE MINIMALĂ [1]. N. Barkakati Borland C++ 4, Ghidul programatorului,

Editura Teora, 1997 [2]. K., Jamsa, L. Klander Totul despre C şi C++, Editura Teora,

2000 [3]. I. Muşlea C++ pentru avansaţi, Editura

Microinformatica, 1994 [4]. L. Negrescu Limbajele C şi C++ pentru începători,

Limbajul C++, volumul 2, Editura Albastră, Cluj-Napoca, 2000

[5]. L. Negrescu Limbajele C şi C++ pentru începători, Limbajul C, volumul 1, Editura Albastră, Cluj-Napoca, 2000

[6]. D. M. Popovici, I. M. Popovici, I. Tănase

C++. Tehnologia orientată pe obiecte. Aplicaţii, Teora, 1996

[7]. Herbert Schildt C++. Manual complet, Teora, 1998

[8]. B.Silaghi Din tainele programării în C++, Editura Albastră, 1996

Lista cărţilor din care se poate învăţa câte ceva despre C++ si lumea POO este deschisă, la fel de deschisă ca mintea oricărui student care nu “şi-a greşit culoarul” pe care aleargă pentru a deveni un profesionist.

154

Page 155: C sau C++…

Subiecte pentru examenul la disciplina Programare Obiect Orientată, Anul II INFO

FUNDAMENTE

Locul limbajului C/C++ printre celelalte limbaje de programare.

Elemente caracteristice.

Tipuri de date fundamentale. Modificarea tipurilor de date fundamentale.

Variabile în C/C++. Variabile locale. Variabile globale. Modelatori de acces. Specificatori de clase de stocare.

Operatori în C/C++.

Reprezentarea structurilor de prelucrare în C/C++. Structuri secvenţiale.

Reprezentarea structurilor de prelucrare în C/C++. Structuri alternative.

Reprezentarea structurilor de prelucrare în C/C++. Structuri repetitive.

Tablouri C/C++.

Pointeri. Relaţia pointeri-matrice. Alocarea dinamică a memoriei.

Structuri de date în programarea C/C++. Înregistrări şi câmpuri de biţi.

155

Page 156: C sau C++…

Uniuni.

PARADIGMA POO

Concepte în programarea obiect orientată.

Principii în programarea obiect orientată.

Implementarea conceptelor şi principiilor OO în C++.

Constructori, destructori, crearea obiectelor în C++.

Metodele virtuale şi polimorfismul în programarea OO în C++.

PROGRAMARE AVANSATĂ ÎN C/C++

Supraîncărcarea funcţiior în programarea C/C++.

Supraîncărcarea operatorilor în programarea C++.

Funcţii inline. Funcţii prietene.

Fluxuri C/C++.

Programarea generică în C/C++. Funcţii generice.

Programarea generică în C/C++. Clase generice.

Programarea excepţiilor în C/C++.

156

Page 157: C sau C++…

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

Exemplu de formulare a subiectelor pentru examenul scris la disciplina POO II INFO

Supraîncărcarea operatorilor în programarea C++.

Ce este o clasă abstractă?

Definiţi noţiunea de flux.

Tratarea excepţiilor în C++.

Definiţi o clasă care încapsulează proprietăţile necesare pentru a simula operaţii cu şiruri de caractere. Implementaţi trei metode, dintre care una să fie inline.

De citit, cel puţin, cu răbdare Câteva cuvinte despre felul în care vede autorul prezentului suport de curs întâlnirea cu studentul în timpul sesiunii. Indiferent de forma de evaluare, întâlnirea student- profesor, în sesiune, ar trebui să fie reciproc avantajoasă. Profesorul are de câştigat multe, dacă face efortul de a vedea în student nu doar un subiect al examenului, ci un partener care s-a hotărât să se confrunte cu el însuşi. Studentul trebuie să aibă înţelepciunea de a vedea în profesor, în cel mai rău caz, un sfătuitor care nu-şi cunoaşte limitele. Autorul prezentului suport de curs îşi cunoaşte limitele şi încearcă permanent să şi le depăşească. Acest exerciţiu de autodepăşire şi-l poate asuma şi studentul, cu încredinţarea că o

157

Page 158: C sau C++…

158

face pentru a confirma superioritatea omului în raport cu alte vieţuitoare şi nu numai. Pentru a mă face mai bine înţeles, în cele ce urmează voi “arunca” câteva idei care ar vrea să sugereze modul în care, ca profesor, înţeleg să evaluez lucrarea unui student. ...Lucrarea unui student este o “mică operă” care vorbeşte despre limitele autorului ei în momentul în care acesta a realizat-o. Limitele acestei opere nu se referă, exclusiv, la ortografie, ci şi la:

• Modul în care este structurat “discursul” de răspuns la fiecare subiect în parte.

• Calitatea viziunii punctuale a studentului, pentru fiecare subiect în parte.

• Calitatea viziunii sistemice a studentului; conexiunile între subiect şi restul universului nu fac decât să arate, odată în plus, inteligenţa acestuia.

Aşadar, ideea este că, pentru un profesror normal, fiecare student este un potenţial creator de “bijuterii” în materie de cugetare pe teme impuse, condiţie necesară pentru a realiza în viitor “bijuterii” în materie de cugetare şi de altă natură, pe teme liber alese sau impuse de viitoarea profesie.