Medii De Programare Pentru Gestiunea Bazelor De Date Access

  • Uploaded by: Cătălin
  • 0
  • 0
  • April 2020
  • PDF

This document was uploaded by user and they confirmed that they have the permission to share it. If you are author or own the copyright of this book, please report to us by using this DMCA report form. Report DMCA


Overview

Download & View Medii De Programare Pentru Gestiunea Bazelor De Date Access as PDF for free.

More details

  • Words: 78,437
  • Pages: 230
Cuprins

CUPRINS PARTEA I CAPITOLUL 1 Noţiuni introductive 1.1. Structura generală a unui sistem de calcul . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2. Algoritmi . . . . . . . . . . . . . . . . . . . . . . . . 1.2.1. Noţiuni generale . . . . . . . . . . . . . . 1.2.2. Definiţii şi caracteristici . . . . . . . . 1.2.3. Reprezentarea algorimilor . . . . . . . 1.2.3.1. Reprezentarea prin scheme logice . . . . . . . . . 1.2.3.2. Reprezentarea prin pseudocod . . . . . . . . . . . . 1.3. Teoria rezolvării problemelor . . . . . . . . Întrebări şi exerciţii . . . . . . . . . . . . . . . . . . . . CAPITOLUL 2 Date, operatori şi expresii 2.1. Limbajele C şi C++ . . . . . . . . . . . . . . . . 2.2. Programe în limbajul C/C++ . . . . . . . . . 2.3. Preprocesorul . . . . . . . . . . . . . . . . . . . . . 2.4. Elemente de bază ale limbajului . . . . . . 2.4.1. Vocabularul . . . . . . . . . . . . . . . . . . 2.4.2. Unităţile lexicale . . . . . . . . . . . . . . 2.5. Date în limbajul C/C++ . . . . . . . . . . . . . 2.5.1. Tipuri de date . . . . . . . . . . . . . . . . 2.5.2. Constante . . . . . . . . . . . . . . . . . . . 2.5.2.1. Constante întregi . . . . . . . 2.5.2.2. Constante numerice, reale. 2.5.2.3. Constante caracter . . . . . . 2.5.2.4. Constante şir de caractere 2.5.3. Variabile . . . . . . . . . . . . . . . . . . . . 2.5.3.1. Declararea variabilelor . . 2.5.3.2. Iniţializarea variabilelor în declaraţii . . . . . . . . . . . . . . 2.5.3.3. Operaţii de intrare/ieşire . 2.6. Operatori şi expresii . . . . . . . . . . . . . . . . 2.6.1. Operatori . . . . . . . . . . . . . . . . . . . . 2.6.2. Expresii . . . . . . . . . . . . . . . . . . . . . 2.6.3. Conversii de tip . . . . . . . . . . . . . . . Întrebări şi exerciţii . . . . . . . . . . . . . . . . . . . . CAPITOLUL 3 Implementarea structurilor de control 3.1. Implementarea structurii secvenţiale . . .

3.2. Implementarea structurii de decizie . . . 3.3. Implementarea structurilor repetitive (ciclice) . . . . . . . . . . . . . . . . . . . . . . . . . 3.3.1. Implementarea structurilor ciclice cu test iniţial . . . . . . . . . . 3.3.2. Implementarea structurilor ciclice cu test final . . . . . . . . . . . 3.4. Facilităţi de întrerupere a unei secvenţe Întrebări şi exerciţii . . . . . . . . . . . . . . . . . . .

9 11 11 12 12 13 14 16 18

CAPITOLUL 4 Tablouri 4.1. Declararea tablourilor . . . . . . . . . . . . . . . 4.2. Tablouri unidimensionale . . . . . . . . . . . . 4.3. Tablouri bidimensionale . . . . . . . . . . . . . 4.4. Şiruri de caractere . . . . . . . . . . . . . . . . . . Întrebări şi exerciţii . . . . . . . . . . . . . . . . . . . .

19 19 21 22 22 22 23 23 25

CAPITOLUL 5 Pointeri 5.1.Variabile pointer . . . . . . . . . . . . . . . . . . . 5.1.1. Declararea variabilelor pointer . . . 5.1.2. Iniţializarea variabilelor pointer . . 5.1.3. Pointeri generici . . . . . . . . . . . . . 5.2. Operaţii cu pointeri . . . . . . . . . . . . . . . . . 5.3. Pointeri şi tablouri . . . . . . . . . . . . . . . . . . 5.3.1. Pointeri şi şiruri de caractere . . . . 5.3.2.Pointeri şi tablouri bidimensionale. 5.4. Tablouri de pointeri . . . . . . . . . . . . . . . . 5.5. Pointeri la pointeri . . . . . . . . . . . . . . . . . 5.6. Modificatorul const în declararea pointerilor . . . . . . . . . . . . . . . . . . . . . . . . Întrebări şi exerciţii . . . . . . . . . . . . . . . . . . . .

25 25 27 28 29 29 29 30 31 31 39 39 39

CAPITOLUL 6 Funcţii 6.1. Structura unei funcţii . . . . . . . . . . . . . . . 6.2. Apelul şi prototipul unei funcţii . . . . . . . 6.3. Transferul parametrilor unei funcţii . . . . 6.3.1. Transferul parametrilor prin valoare . . . . . . . . . . . . . . . . . . . . . 6.3.2. Transferul prin pointeri . . . . . . . . 6.3.2.1. Funcţii care returnează pointeri . . . . . . . . . . . . . . 6.3.3. Transferul prin referinţă . . . . . . .

41

5

42 45 45 46 50 51 53 53 55 57 60 61 61 62 63 63 64 64 66 67 68 69 69 71 72 74 75 75 77 77

Cuprins

PARTEA a II a 6.3.4. Transferul parametrilor către funcţia main . . . . . . . . . . . . . . . . 81 6.4. Tablouri ca parametri . . . . . . . . . . . . . . . 81 6.5. Funcţii cu parametri impliciţi . . . . . . . . . 84 6.6. Funcţii cu număr variabil de parametri . 84 6.7. Funcţii predefinite . . . . . . . . . . . . . . . . . 85 6.7.1. Funcţii matematice . . . . . . . . . . . . 85 6.7.2. Funcţii de clasificare (testare) a caracterelor . . . . . . . . . . . . . . . . . 85 6.7.3. Funcţii de conversie a caracterelor . . . . . . . . . . . . . . . . . 87 6.7.4. Funcţii de conversie din şir în număr . . . . . . . . . . . . . . . . . . . . . . 87 6.7.5. Funcţii de terminare a unui proces (program) . . . . . . . . . . . . . . . . . . . 87 6.7.6. Funcţii de intrare/ieşire . . . . . . . . 88 6.8. Clase de memorare . . . . . . . . . . . . . . . . . 90 6.9. Moduri de alocare a memoriei . . . . . . . 92 6.10.Funcţii recursive . . . . . . . . . . . . . . . . . . 95 6.11.Pointeri către funcţii . . . . . . . . . . . . . . . 100 Întrebări şi exerciţii . . . . . . . . . . . . . . . . . . . . 101 CAPITOLUL 7 Tipuri de date definite de utilizator 7.1. Tipuri definite de utilizator . . . . . . . . . . 7.2. Structuri . . . . . . . . . . . . . . . . . . . . . . . . . 7.3. Câmpuri de biţi . . . . . . . . . . . . . . . . . . . . 7.4. Declaraţii typedef . . . . . . . . . . . . . . . . . . 7.5. Uniuni . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.6. Enumerări . . . . . . . . . . . . . . . . . . . . . . . . Întrebări şi exerciţii . . . . . . . . . . . . . . . . . . . . CAPITOLUL 8 Fişiere 8.1. Caracteristicile generale ale fişierelor. . . 8.2. Deschiderea unui fişier . . . . . . . . . . . . . . 8.3. Închiderea unui fişier . . . . . . . . . . . . . . . 8.4. Prelucrarea fişierelor text . . . . . . . . . . . . 8.4.1. Prelucrarea la nivel de caracter . . 8.4.2. Prelucrarea la nivel de cuvânt. . . . 8.4.3. Prelucrarea la nivel de şir de caractere . . . . . . . . . . . . . . . . . . . 8.4.4. Intrări/ieşiri formatate . . . . . . . . . 8.5. Intrări/ieşiri binare . . . . . . . . . . . . . . . . . 8.6. Poziţionarea într-un fişier . . . . . . . . . . . . 8.7. Funcţii utilitare pentru lucrul cu fişiere. . 8.8. Alte operaţii cu fişiere . . . . . . . . . . . . . . Întrebări şi exerciţii . . . . . . . . . . . . . . . . . . . .

CAPITOLUL 9 Concepte de bază ale programării orientate obiect 9.1. Introducere . . . . . . . . . . . . . . . . . . . . . . . 9.2. Abstractizarea datelor . . . . . . . . . . . . . . 9.3. Moştenirea . . . . . . . . . . . . . . . . . . . . . . . 9.3.1. Moştenirea unică . . . . . . . . . . . . . 9.3.1. Moştenirea multiplă . . . . . . . . . . . 9.4. Încapsularea informaţiei . . . . . . . . . . . . . 9.5. Legarea dinamică (târzie) . . . . . . . . . . . . 9.6. Alte aspecte . . . . . . . . . . . . . . . . . . . . . . CAPITOLUL 10 Clase şi obiecte 10.1. Definiţia claselor şi accesul la Membri . . . . . . . . . . . . . . . . . . . . . . . . . 10.1.1. Legătura clasă-structurăUniune . . . . . . . . . . . . . . . . . . . 10.1.2. Declararea claselor . . . . . . . . . 10.1.3. Obiecte . . . . . . . . . . . . . . . . . . 10.1.4. Membrii unei clase . . . . . . . . . 10.1.5. Pointerul this . . . . . . . . . . . . . . 10.1.6. Domeniul unui nume, vizibilitate şi timp de viaţă . . . 10.2. Funcţii inline . . . . . . . . . . . . . . . . . . . . 10.3. Constructori şi destructori . . . . . . . . . . 10.3.1. Iniţializarea datelor . . . . . . . . . 10.3.2. Constructori . . . . . . . . . . . . . . 10.3.1.1. Constructori cu liste de iniţializare. . 10.3.1.2. Constructori de copiere . . . . . . . . . . 10.3.3. Destructori . . . . . . . . . . . . . . . 10.3.4. Tablouri de obiecte . . . . . . . . . 10.4. Funcţii prietene (friend) . . . . . . . . . . . . Întrebări şi exerciţii . . . . . . . . . . . . . . . . . . . .

103 103 106 107 107 108 111 113 114 116 116 116 117

CAPITOLUL 11 Supraîncărcarea operatorilor 11.1. Moduri de supraîncărcare a operatorilor . . . . . . . . . . . . . . . . . . . . . 11.1.1. Supraîncărcarea prin funcţii membre . . . . . . . . . . . . . . . . . . 11.1.2. Supraîncărcarea prin funcţii prietene . . . . . . . . . . . . . . . . . . 11.2. Restricţii la supraîncărcarea operatorilor . . . . . . . . . . . . . . . . . . . . . 11.3. Supraîncărcarea operatorilor unari . . . 11.4. Membrii constanţi ai unei clase . . . . .

118 119 119 120 121 122 126

6

129 129 130 130 130 131 132 132

133 133 133 136 136 139 139 140 142 142 143 144 145 145 150 151 151

153 153 154 155 156 156

Cuprins

11.5. Supraîncărcarea operatorilor insertor şi extractor . . . . . . . . . . . . . . . 11.6. Supraîncărcarea operatorului de atribuire = . . . . . . . . . . . . . . . . . . . . . . . 11.7. Supraîncărcarea operatorului de indexare [ ] . . . . . . . . . . . . . . . . . . . . . . 11.8. Supraîncărcarea operatorilor new şi delete. . . . . . . . . . . . . . . . . . . . . . . . . . 11.9. Supraîncărcarea operatorului ( ) . . . . . 11.10. Supraîncărcarea operatorului -> . . . . . 11.11. Conversii . . . . . . . . . . . . . . . . . . . . . . 11.11.1. Conversii din tip predefinit1 în tip predefinit2. 11.11.2. Conversii din tip predefinit în clasă . . . . . . . . . . . . . . . . . 11.11.3. Conversii din clasă în tip predefinit . . . . . . . . . . . . . . . 11.11.4. Conversii din clasă1 în clasă2 . . . . . . . . . . . . . . . . . . Întrebări şi exerciţii . . . . . . . . . . . . . . . . . . . .

12.4. Moştenirea simplă . . . . . . . . . . . . . . . 12.5. Moştenirea multiplă . . . . . . . . . . . . . . 12.6. Redefinirea membrilor unei clase de bază în clasa derivată . . . . . . . . . . . . . 12.7. Metode virtuale . . . . . . . . . . . . . . . . . Întrebări şi exerciţii . . . . . . . . . . . . . . . . . . .

156 157 158 164 166 166 172

CAPITOLUL 13 Intrări/ieşiri 13.1. Principiile de bază ale sistemului de I/O din limbajul C++ . . . . . . . . . . . . . 13.2. Testarea şi modificarea stării unui flux . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.3. Formatarea unui flux . . . . . . . . . . . . . 13.3.1. Formatarea prin manipulatori . . . . . . . . . . . . . 13.3.1.2. Manipulatori fără parametri . . . . . . . . 13.3.1.2. Manipulatori cu parametri . . . . . . . . 13.3.2. Formatarea prin metode . . . . . 13.4. Metodele clasei istream . . . . . . . . . . . 13.5. Metodele clasei ostream . . . . . . . . . . 13.6. Manipulatori creaţi de utilizator . . . . 13.7. Fluxuri pentru fişiere . . . . . . . . . . . . . 13.8. Fişiere binare . . . . . . . . . . . . . . . . . . . Întrebări şi exerciţii . . . . . . . . . . . . . . . . . . .

172 172 173 173 176

CAPITOLUL 12 Crearea ierarhiilor de clase 12.1. Mecanismul moştenirii . . . . . . . . . . . . . 176 12.2. Modul de declarare a claselor derivate. 177 12.3. Constructorii claselor derivate . . . . . . . 178

7

179 186 187 191 195

197 199 201 202 202 202 203 204 205 206 207 210

INTRODUCERE

Limbajele C şi C++ sunt limbaje de programare de nivel înalt. Limbajul C a apărut în anii 1970 şi a fost creat de Dennis Ritchie în laboratoarele AT&T Bell. Limbajul C face parte din familia de limbaje concepute pe principiile programării structurate, la care ideea centrală este ”structurează pentru a stăpâni o aplicaţie”. Popularitatea limbajului a crescut rapid datorită eleganţei şi a multiplelor posibilităţi oferite programatorului. Limbajul C++ apare la începutul anilor ’80 şi îl are ca autor pe Bjarne Stroustrup. El este o variantă de limbaj C îmbunătăţit, mai riguroasă şi mai puternică, completată cu construcţiile necesare aplicării principiilor programării orientate pe obiecte (POO). Limbajul C++ păstrează toate elementele limbajului C, beneficiind de eficienţa şi flexibilitatea acestuia. Limbajul C++ este un superset al limbajului C. Incompatibilităţile sunt minore, de aceea, modulele C pot fi încorporate în proiecte C++ cu un efort minim. Lucrarea cuprinde două părţi. Prima parte se adresează programatorilor începători. Ea prezintă elementele de bază şi construcţiile limbajului C, completate cu extensiile limbajului C++. Acestea permit rezolvarea problemelor prin metoda programării structurate. Partea a doua se adresează cunoscătorilor limbajului C dornici să-şi modernizeze stilul şi concepţia abordării proiectelor informatice. Elementele prezentate sunt specifice limbajului C++ şi permit stiluri de programare impracticabile în C: programarea prin abstractizarea datelor şi programarea orientată obiect. Citându-l chiar pe Bjarne Stroustrup, ”C++ este un limbaj de programare general, conceput astfel încât să-i facă pe programatorii serioşi să programeze într-o manieră cât mai plăcută”. Şi cum cea mai bună metodă de învăţare este practica, prezentarea aspectelor teoretice este însoţită de multe exemple şi probleme rezolvate. Deasemenea, întrebările teoretice şi problemele propuse spre rezolvare, de la sfârşitul fiecărui capitol, permit cititorului să-şi verifice cunoştinţele dobândite. Un aspect foarte important îl constituie implementarea şi testarea pe calculator a exemplelor şi a problemelor rezolvate sau propuse, găsirea unor soluţii proprii. Sperăm ca acest material să constituie un sprijin real pentru cei care doresc să pătrundă în tainele limbajelor C/C++, cât şi un punct de plecare în activitatea de programare. Dorim să mulţumim şi pe acestă cale domnului prof. Severin BUMBARU şi colegilor pentru observaţiile şi sfaturile care au condus la forma actuală a cărţii. Sugestiile cititorilor sunt aşteptate la adresele: [email protected], [email protected]. AUTORII

CAPITOLUL 1

Noţiuni introductive

1

NOŢIUNI INTRODUCTIVE 1.1. Structura generală a unui sistem de calcul 1.2. Algoritmi 1.2.1. Noţiuni generale

1.2.2. Definiţii şi caracteristici 1.2.3. Reprezentarea algorimilor 1.3. Teoria rezolvării problemelor

1.1. STRUCTURA GENERALĂ A UNUI SISTEM DE CALCUL Calculatorul reprezintă un sistem electronic (ansamblu de dispozitive şi circuite diverse) complex care prelucrează datele introduse într-o formă prestabilită, efectuează diverse operaţii asupra acestora şi furnizează rezultatele obţinute (figura 1.1.).

Date de intrare (datele iniţiale ale problemei)

PROGRAM (şir de acţiuni , prelucrări, algoritm) Date de ieşire (rezultatele obţinute)

Figura 1.1. Calculatorul - sistem automat de prelucrare a datelor Principalele avantaje ale folosirii calculatorului constau în:  viteza mare de efectuare a operaţiilor;  capacitatea extinsă de prelucrare şi memorare a informaţiei. Deşi construcţia unui calculator - determinată de tehnologia existentă la un moment dat, de domeniul de aplicaţie, de costul echipamentului şi de performanţele cerute - a evoluat rapid în ultimii ani, sistemele de calcul, indiferent de model, serie sau generaţie, au o serie de caracteristici comune. Cunoaşterea acestor caracteristici uşurează procesul de înţelegere şi învăţare a modului de funcţionare şi de utilizare a calculatorului. În orice sistem de calcul vom găsi două părţi distincte şi la fel de importante: hardware-ul şi software-ul.  Hardware-ul este reprezentat de totalitatea echipamentelor şi dispozitivelor fizice;  Software-ul este reprezentat prin totalitatea programelor care ajută utilizatorul în rezolvarea problemelor sale (figura 1.2.). Software-ul are două componente principale:  Sistemul de operare (de exploatare) care coordonează întreaga activitate a echipamentului de calcul. Sistemul de operare intră în funcţiune la pornirea calculatorului şi asigură, în principal, trei funcţii:  Gestiunea echitabilă şi eficientă a resurselor din cadrul sistemului de calcul;  Realizarea interfeţei cu utilizatorul;  Furnizarea suportului pentru dezvoltarea şi execuţia aplicaţiilor. Exemple de sisteme de operare: RSX11, CP/M, MS-DOS, LINUX, WINDOWS NT, UNIX.  Sistemul de aplicaţii (de programare): medii de programare, editoare de texte, compilatoare, programe aplicative din diverse domenii (economic, ştiinţific, financiar, divertisment). Componentele unui sistem de calcul pot fi grupate în unităţi cu funcţii complexe, dar bine precizate, numite SISTEM OPERARE unităţi funcţionale. Modelul din figura 1.3. face o prezentare simplificată a structurii unui calculator, facilitând înţelegerea unor noţiuni şi concepte de bază privind funcţionarea şi utilizarea acestuia. Denumirea fiecărei unităţi indică funcţia ei, iar săgeţile - modul de transfer al informaţiei. UTILIZATOR HARDWARE

SOFTWARE DE APLICAŢIE9 SOFTWARE

Figura 1.2. Echipamentul de calcul ca un sistem hardware-software

CAPITOLUL 1

Noţiuni introductive

Vom utiliza în continuare termenii de citire pentru operaţia de introducere (de intrare) de la tastatură a datelor iniţiale ale unei probleme, şi scriere pentru operaţia de afişare (de ieşire) a rezultatelor obţinute. În cazul în care utilizatorul doreşte să rezolve o problemă cu ajutorul calculatorului, informaţia de intrare (furnizată calculatorului de către utilizator) va consta din datele iniţiale ale problemei de rezolvat şi dintr-un program (numit program sursă). În programul sursă utilizatorul implementează (traduce) într-un limbaj de programare un algoritm (acţiunile executate asupra datelor de intrare pentru a obţine rezultatele). Această informaţie de intrare este prezentată într-o forma externă, accesibilă omului (numere, text, grafică) şi va fi transformată de către calculator într-o forma internă, binară. Unitatea de intrare (cu funcţia de citire) realizează această conversie a informaţiei din format extern în cel intern. Din punct de vedere logic, fluxul (informaţia) de intrare este un şir de caractere, din exterior către memoria calculatorului. Din punct de vedere fizic, unitatea de intrare standard este tastatura calculatorului. Tot ca unităţi de intrare, pot fi enumerate: mouse-ul, joystick-ul, scanner-ul (pentru introducerea informaţiilor grafice). Unitatea de ieşire (cu funcţia de scriere, afişare) realizează conversia inversă, din formatul intern în cel extern, accesibil omului. Din punct de vedere fizic, unitatea de ieşire standard este monitorul calculatorului. Ca unităţi de ieşire într-un sistem de calcul, mai putem enumera: imprimanta, plotter-ul, etc. Informaţia este înregistrată în memorie. Memoria internă (memoria RAM - Random Acces Memory) se prezintă ca o succesiune de octeţi (octet sau byte sau locaţie de memorie). Un octet are 8 biţi. Bit-ul reprezintă unitatea elementară de informaţie şi poate avea una din valorile: 0 sau 1. Capacitatea unei memorii este dată de numărul de locaţii pe care aceasta le conţine şi se măsoară în multiplii de 1024 (2 10 ). De exemplu, 1 Mbyte=1024Kbytes; 1Kbyte=1024bytes. Numărul de ordine al unui octet în memorie se poate specifica printr-un cod, numit adresă. Ordinea în care sunt adresate locaţiile de memorie nu este impusă, memoria fiind un dispozitiv cu acces aleator la informaţie. În memorie se înregistrează două categorii de informaţii:  Date - informaţii de prelucrat;  Programe - conţin descrierea (implementarea într-un limbaj de programare) a acţiunilor care vor fi executate asupra datelor, în vederea prelucrării acestora. În memoria internă este păstrată doar informaţia prelucrată la un moment dat. Memoria internă are capacitate redusă; accesul la informaţia pastrată în aceasta este extrem de rapid, iar datele nu sunt păstrate după terminarea prelucrării (au un caracter temporar). Unitatea centrală prelucrează datele din memoria internă şi coordonează activitatea tuturor componentelor fizice ale unui sistem de calcul. Ea înglobează:  Microprocesorul- circuit integrat complex cu următoarele componente de bază:  Unitatea de execuţie (realizează operaţii logice şi matematice);  Unitatea de interfaţă a magistralei (transferă datele la/de la microprocesor). 10

CAPITOLUL 1 

Noţiuni introductive

Coprocesorul matematic – circuit integrat destinat realizării cu viteză sporită a operaţiilor cu numere reale.

Unitate de intrare (flux de intrare - istream în C++)

Memorie internă

Unitate de ieşire (flux de ieşire - ostream în C++)

Unitate centrală

Memorie externă Figura 1.3. Unităţile funcţionale ale unui sistem de calcul În funcţie de numărul de biţi transferaţi simultan pe magistrala de date, microprocesoarele pot fi clasificate astfel: microprocesoare pe 8 biţi (Z80, 8080); microprocesoare pe 16 biţi (8086, 8088, 80286) cu coprocesoarele corespunzătoare (8087, 80287); familii de procesoare pe 32 biţi (80386DX, 80486, PENTIUM) cu coprocesoarele corespunzătoare (începând de la 486, coprocesoare sunt încorporate microprocesoarelor). Memoria externă este reprezentată, fizic, prin unităţile de discuri (discuri dure-hard disk, discuri flexibilefloppy disk, discuri de pe care informaţia poate fi doar citită-CDROM, DVDROM, etc). Spre deosebire de memoria internă, memoria externă are capacitate mult mai mare, datele înregistrate au caracter permanent, în dezavantajul timpului de acces la informaţie.

1.2. ALGORITMI 1.2.1. NOŢIUNI GENERALE Algoritmul este conceptul fundamental al informaticii. Orice echipament de calcul poate fi considerat o maşină algoritmică. Într-o definiţie aproximativă algoritmul este un set de paşi care defineşte modul în care poate fi dusă la îndeplinire o anumită sarcină. Exemplu de algoritm: algoritmul de interpretare a unei bucăţi muzicale (descris în partitură). Pentru ca o maşină de calcul să poată rezolva o anumită problemă, programatorul trebuie mai întâi să stabilească un algoritm care să conducă la efectuarea la sarcinii respective. Exemplu: Algoritmul lui Euclid pentru determinarea celui mai mare divizor comun (cmmdc) a 2 numere întregi pozitive. Date de intrare: cele 2 numere întregi Date de iesire: cmmdc 1. Se notează cu A şi B- cea mai mare, respectiv cea mai mică, dintre datele de intrare 2. Se împarte A la B şi se notează cu R restul împărţirii 3. a. Dacă R diferit de 0, se atribuie lui A valoarea lui B şi lui B valoarea lui R. Se revine la pasul 2. b. Dacă R este 0, atunci cmmdc este B. Probleme legate de algoritmi Descoperirea unui algoritm care să rezolve o problemă echivalează în esenţă cu descoperirea unei soluţii a problemei. După descoperirea algoritmului, pasul următor este ca algoritmul respectiv să fie reprezentat într-o formă în care să poată fi comunicat unei maşini de calcul. Algoritmul trebuie transcris din forma conceptuală într-un set clar de instrucţiuni. Aceste instrucţiuni trebuie reprezentate într-un mod lipsit de ambiguitate. În acest domeniu, studiile se bazează pe cunoştinţele privitoare la gramatică şi limbaj şi au dus la o mare varietate de scheme de reprezentare a algoritmilor (numite limbaje de programare), bazate pe diverse abordări ale procesului de programare (numite paradigme de programare). 11

CAPITOLUL 1

Noţiuni introductive

Căutarea unor algoritmi pentru rezolvarea unor probleme din ce în ce mai complexe a avut ca urmare apariţia unor întrebări legate de limitele proceselor algoritmice, cum ar fi:  Ce probleme pot fi rezolvate prin intermediul proceselor algoritmice?  Cum trebuie procedat pentru descoperirea algoritmilor?  Cum pot fi îmbunătăţite tehnicile de reprezentare şi comunicare a algoritmilor?  Cum pot fi aplicate cunoştinţele dobândite în vederea obţinerii unor maşini algoritmice mai performante?  Cum pot fi analizate şi comparate caracteristicile diverşilor algoritmi?

1.2.2. DEFINIŢII ŞI CARACTERISTICI Definiţii: Algoritmul unei prelucrări constă într-o secvenţă de primitive care descrie prelucrarea. Algoritmul este un set ordonat de paşi executabili, descrişi fără echivoc, care definesc un proces finit. Proprietăţile fundamentale ale algoritmilor:  Caracterul finit: orice algoritm bine proiectat se termină într-un număr finit de paşi;  Caracterul unic şi universal: orice algoritm trebuie să rezolve toate problemele dintr-o clasă de probleme;  Realizabilitatea: orice algoritm trebuie să poată fi codificat într-un limbaj de programare;  Caracterul discret: fiecare acţiune se execută la un moment dat de timp;  Caracterul determinist: ordinea acţiunilor în execuţie este determinată în mod unic de rezultatele obţinute la fiecare moment de timp. Nerespectarea acestor caracteristici generale conduce la obţinerea de algoritmi neperformanţi, posibil infiniţi sau nerealizabili.

1.2.3. REPREZENTAREA ALGORITMILOR Reprezentarea (descrierea) unui algoritm nu se poate face în absenţa unui limbaj comun celor care vor să îl înţeleagă. De aceea s-a stabilit o mulţime bine definită de primitive (blocuri elementare care stau la baza reprezentării algoritmilor). Fiecare primitivă se caracterizează prin sintaxă şi semantică. Sintaxa se referă la reprezentarea simbolică a primitivei; semantica se referă la semnificaţia primitivei. Exemplu de primitivă: aerdin punct de vedere sintactic este un cuvânt format din trei simboluri (litere); din punct de vedere semantic este o substanţă gazoasă care înconjoară globul pământesc. Algoritmii se reprezintă prin:  scheme logice;  pseudocod.

1.2.3.1. Reprezentarea algoritmilor prin scheme logice Primitivele utilizate în schemele logice sunt simboluri grafice, cu funcţiuni (reprezentând procese de calcul) bine precizate. Aceste simboluri sunt unite prin arce orientate care indică ordinea de execuţie a proceselor de calcul. Categorii de simboluri: 

Simboluri de început şi sfârşit

START 

Simbolul paralelogram CITEŞTE a, b

STOP

Simbolul START desemnează începutul unui program sau al unui subprogram. Simbolul STOP desemnează sfârşitul unui program sau al unui subprogram. Prezenţa lor este obligatorie.

AFIŞEAZĂ a, b 12

Semnifică procese (operaţii) de intrare/ieşire (citirea sau scrierea)

CAPITOLUL 1



Noţiuni introductive

Simbolul dreptunghi Semnifică o atribuire (modificarea valorii unei date).

a ←34 

Simbolul romb

Condiţie îndeplinită ?

NU

ACŢIUNE2

Simbolul romb este utilizat pentru decizii (figura 1.4.). Se testează îndeplinirea condiţiei din blocul de decizie. Dacă această condiţie este îndeplinită, se execută ACŢIUNE1. Dacă nu, se execută ACŢIUNE2. La un moment dat, se execută sau ACŢIUNE1, sau ACŢIUNE2.

DA

ACŢIUNE1

Figura 1.4. Structura de decizie Cu ajutorul acestor simboluri grafice se poate reprezenta orice algoritm. Repetarea unei secvenţe se realizează prin combinarea simbolurilor de decizie şi de atribuire. Structurile repetitive obţinute pot fi: cu test iniţial sau cu test final. Structuri repetitive cu test initial Se evaluează condiţia de test (figura 1.5.). Dacă aceasta este îndeplinită, se execută ACŢIUNE1. Se revine apoi şi se testează iar condiţia. Dacă este îndeplinită, se execută (se DA Condiţie repetă) ACŢIUNE1, ş.a.m.d. Abia în momentul NU îndeplinită în care condiţia nu mai este îndeplinită, se trece ? la execuţia ACŢIUNE2. Astfel, cât timp condiţia este îndeplinită, se ACŢIUNE2 ACŢIUNE1 repetă ACŢIUNE1. În cazul în care, la prima testare a condiţiei, aceasta nu este îndeplinită, se execută ACŢIUNE2. Astfel, este posibil ca să onuanumită fie executată niciodată. 1.5. în Structură test iniţial ExisăFigura şi situaţii care se repetitivă ştie de la cu început de câte oriACŢIUNE1 se va repeta acţiune. În aceste cazuri se foloseşte tot o structură de control repetitivă cu test iniţial. Se utilizează un contor (numeric) pentru a ţine o evidenţă a numărului de execuţii ale acţiunii. De câte ori se execută acţiunea, contorul este incrementat. contorvaloare_iniţială NU

valoare_contor<= valoare_finală

Se atribuie contorului valoarea iniţială (figura 1.6.). Cât timp condiţia (valoarea contorului este mai mică sau egală cu valoarea finală) este îndeplinită, se repetă:

DA

ACŢIUNE

incrementare contor (se adună 1 la valoarea anterioară a contorului).

ACŢIUNE valoare_contor valoare_contor + 1

Figura 1.6. Structură repetitivă cu test iniţial, cu număr cunoscut de paşi 13

CAPITOLUL 1

Noţiuni introductive

Structură repetitivă cu test final:

Se execută mai întâi ACŢIUNE1. Se testează apoi condiţia (figura 1.7.). Se repetă ACŢIUNE1 cât timp condiţia este îndeplinită. În acest caz, corpul ciclului (ACŢIUNE1) este executat cel puţin o dată.

ACŢIUNE 1

Condiţie îndeplinită ?

DA

NU ACŢIUNE 2

Figura 1.7. Structură repetitivă cu test final

1.2.3.2. Reprezentarea algoritmilor prin pseudocod Pseudocodul este inspirat din limbajele de programare, nefiind însă atât de formalizat ca acestea. Pseudocodul reprezintă o punte de legătură între limbajul natural şi limbajele de programare. Nu există un standard pentru regulile lexicale. Limbajul pseudocod permite comunicarea între oameni, şi nu comunicarea om-maşina (precum limbajele de programare). Pseudocodul utilizează cuvinte cheie (scrise cu majuscule subliniate) cu următoarele semnificaţii: Sfârşit algoritm: SFÂRŞIT Început algoritm: ÎNCEPUT Citire (introducere) date: CITEŞTE lista Scriere (afişare) date: SCRIE lista Atribuire: <Structura de decizie (alternativă): DACĂ condiţie ATUNCI acţiune1 ALTFEL acţiune2 Structuri repetitive cu test iniţial: CÂT TIMP condiţie REPETĂ acţiune PENTRU contor=val_iniţ LA val_fin [PAS] REPETĂ acţiune; Structuri repetitive cu test final: REPETĂ acţiune CÂT TIMP condiţie sau: REPETĂ acţiune PÂNĂ CÂND condiţie ALGORITM aflare_arie_drept Pe lângă cuvintele cheie, în reprezentarea algoritmilor în pseudocod pot apare şi propoziţii nestandard a caror INCEPUT detaliere va fi realizată ulterior. START CITEŞTE În cazul în care se realizează un algoritm modularizat, pot apare cuvintele cheie: L,l aria <- L*l SUBALGORITM nume (lista_intrări) AFIŞEAZA aria CHEAMĂ nume (lista_valori_efective_de_intrare) SFARŞIT

CITEŞTE L, l

Exemple: Se vor reprezinta în continuare algoritmii de rezolvare pentru câteva probleme simple (pentru primele 2 probleme se va exemplifica şi modul de implementare a acestor algoritmi în limbajul C++). #include Implementare: <- L *l 1. Se citesc 2aria valori numerice reale, care reprezintă dimensiunile (lungimea şi lăţimea unui dreptunghi). Să se void main( ) calculeze şi să se afişeze aria dreptunghiului. { double L, l; AFIŞEAZĂ aria 14 STOP

cout<<"Lungime="; cin>>L; cout<<"Laţime="; cin>>l; double aria = L * l; cout << "Aria="<< aria; }

CAPITOLUL 1

Noţiuni introductive

2. Se citesc 2 valori reale. Să se afiseze valoarea maximului dintre cele 2 numere. ALGORITM max_2_nr INCEPUT CITESTE a, b DACA a >= b ATUNCI max<-a ALTFEL max<-b AFISEAZA max SFARŞIT Implementare în limbajul C++:

ALGORITM max_2_nr ÎNCEPUT CITEŞTE a, b DACA a >= b ATUNCI AFISEAZA a ALTFEL AFISEAZA b SFARŞIT

Sau:

#include void main( ) { float a, b; cout<<"a=";cin>>a; cout<<"b="; cin>>b; if (a >= b) cout<<"Maximul este:"< void main( ) { float a, b, max; cout<<"a="; cin>>a; cout<<"b="; cin>>b; if (a >= b) max = a; else max = b; cout<<"Maximul este:"<<max;}

numere citite, să se afişeze maximul. Algoritm care utilizează structură repetitivă cu test iniţial: ALGORITM max_perechi1 INCEPUT CITESTE a,b CAT TIMP(a#0sau b#0)REPETA INCEPUT DACA (a>=b) ATUNCI AFISEAZA a ALTFEL AFISEAZA b CITESTE a,b SFARSIT SFARSIT

ALGORITM max_perechi2 INCEPUT a ← 3 CAT TIMP (a#0 sau b#0) REPETA INCEPUT CITESTE a, b DACA (a>=b) ATUNCI AFISEAZA a ALTFEL AFISEAZA b SFARSIT SFARSIT

Algoritm care utilizează structură repetitivă cu test final: ALGORITM max_perechi3 INCEPUT REPETA INCEPUT CITEŞTE a,b DACA (a>=b) ATUNCI AFIŞEAZA a ALTFEL AFIŞEAZA 15 SFARŞIT CAT TIMP (a#0 sau b#0) SFARŞIT

b

CAPITOLUL 1

Noţiuni introductive

1.3. TEORIA REZOLVĂRII PROBLEMELOR Creşterea complexităţii problemelor supuse rezolvării automate (cu ajutorul calculatorului) a determinat ca activitatea de programare să devină, de fapt, un complex de activităţi. Pentru rezolvarea unei probleme trebuie parcurse următoarele etape:  Analiza problemei (înţelegerea problemei şi specificarea cerinţelor acesteia). Se stabileste ce trebuie să facă aplicaţia, şi nu cum. Se stabilesc datele de intrare (identificarea mediului iniţial) şi se stabilesc obiectivele (identificarea mediului final, a rezultatelor);  Proiectarea (conceperea unei metode de rezolvare a problemei printr-o metodă algoritmică);  Implementarea (codificarea algoritmului ales într-un limbaj de programare);  Testarea aplicaţiei obţinute (verificarea corectitudinii programului);  Exploatarea şi întreţinerea (mentenanţa, activitatea de modificare a aplicaţiei la cererea beneficiarului sau în urma unor deficienţe constatate pe parcursul utilizării aplicaţiei). În acest context, activitatea de programare a devenit o activitate organizată, definindu-se metode formale de dezvoltare a fiecărei etape. Etapele descrise anterior alcătuiesc ciclul de viaţă al unui produs software şi constituie obiectul de studiu al disciplinei numite ingineria sistemulor de programe (software engineering). Teoreticienii ingineriei programării consideră că rezolvarea unei probleme se poate face pe 3 direcţii:  Rezolvarea orientată pe algoritm (pe acţiune), în care organizarea datelor este neesenţială;  Rezolvarea orientată pe date, acţiunile fiind determinate doar de organizarea datelor;  Rezolvarea orientată obiect, care combină tendinţele primelor două abordări. Abordarea aleasă determină modelarea problemei de rezolvat. Dintre metodele de proiectare orientate pe algoritm amintim: metoda programării structurate şi metoda rafinării succesive. Ambele au ca punct de plecare metoda de proiectare top-down, considerată ca fiind o metodă clasică de formalizare a procesului de dezvoltare a unui produs software. La baza metodei top-down stă descompunerea funcţională a problemei P, adica găsirea unui număr de subprobleme P 1 , P 2 , ... P n , cu următoarele proprietăţi:



Fiecare subproblemă P i (1<=i<=n) poate fi rezolvată independent. Dacă nu constituie o problemă elementară, poate fi, la randul ei, descompusă; Fiecare subproblemă P i este mai simplă decât problema P;



Soluţia problemei P se obţine prin reuniunea soluţiilor subproblemelor P i ;



Procesul de descompunere se opreşte în momentul în care toate subproblemele P i obţinute sunt elementare, deci pot fi implementate;



Comunicarea între aceste subprobleme se realizează prin intermediul parametrilor. Implementarea metodei top-down într-un limbaj de programare se face cu ajutorul modulelor de program (funcţii sau proceduri în limbajul Pascal, funcţii în limbajul C). P P

P1

P

P2

P

P 16

Figura 1.8. Descompunerea funcţională

Descompunerea funcţională a unui program P constă în identificarea funcţiilor (task-urilor, sarcinilor) principale ale programului (P, P, P), fiecare dintre aceste funcţii reprezentând un subprogram (figura 1.8.). Problemele de pe acelaşi nivel i sunt independente unele faţă de altele.

CAPITOLUL 1

Noţiuni introductive

1.3.1. Etapele rezolvării unei probleme cu ajutorul calculatorului Să detaliem în continuare etapa de implementare. După analiza problemei şi stabilirea algoritmului, acesta trebuie tradus (implementat) într-un limbaj de programare. 

Srierea (editarea) programului sursă. Programele sursă sunt fişiere text care conţin instrucţiuni (cu sintactica şi semantica proprii limbajului utilizat). Programul (fişierul) sursă este creat cu ajutorul unui editor de texte şi va fi salvat pe disc (programele sursă C primesc, de obicei, extensia .c, iar cele C++, extensia .cpp). Pentru a putea fi executat, programul sursă trebuie compilat şi linkeditat.



Compilarea Procesul de compilare este realizat cu ajutorul compilatorului, care translatează codul sursă în cod obiect (cod maşină), pentru ca programul să poată fi înţeles de calculator. În cazul limbajului C, în prima fază a compilării este invocat preprocesorul. Acesta recunoaşte şi analizează mai întâi o serie de instrucţiuni speciale, numite directive procesor. Verifică apoi codul sursă pentru a constata dacă acesta respectă sintaxa şi semantica limbajului. Dacă există erori, acestea sunt semnalate utilizatorului. Utilizatorul trebuie să corecteze erorile (modificând programul sursă). Abia apoi codul sursă este translatat în cod de asamblare, iar în final, în cod maşină, binar, propriu calculatorului. Acest cod binar este numit cod obiect şi de obicei este memorat într-un alt fişier, numit fişier obiect. Fişierul obiect va avea, de obicei, acelaşi nume cu fişierul sursă şi extensia .obj.



Linkeditarea Dupa ce programul sursă a fost translatat în program obiect, el este va fi supus operaţiei de linkeditare. Scopul fazei de linkeditare este acela de a obţine o formă finală a programului, în vederea execuţiei acestuia. Linkeditorul “leagă” modulele obiect, rezolvă referinţele către funcţiile externe şi rutinele din biblioteci şi produce cod executabil, memorat într-un alt fisier, numit fişier executabil (acelaşi nume, extensia .exe)



Execuţia Lansarea în execuţie constă în încărcarea programului executabil în memorie şi startarea execuţiei sale.

(Preprocesor) Compilator

Cod sursă

Cod obiect

Linkeditor

Cod executabil

Figura 1.9. Etapele necesare obţinerii fişierului executabil Observaţii: 1. Mediile de programare integrate (BORLANDC, TURBOC) înglobează editorul, compilatorul, linkeditorul şi depanatorul (utilizat în situaţiile în care apar erori la execuţie); 2. Dacă nu se utilizează un mediu integrat, programatorul va apela în mod explicit (în linie de comandă) un editor de texte, compilatorul, linkeditorul. Lansarea în execuţie se va face tot din linie de comandă. 3. Extensiile specificate pentru fişierele sursă, obiect şi executabile sunt

ÎNTREBĂRI ŞI EXERCIŢII Chestiuni teoretice

17

CAPITOLUL 1

Noţiuni introductive

1. Enumeraţi unităţile funcţionale componente ale unui sistem de calcul. 2. Care sunt diferenţele între soft-ul de aplicaţie şi sistemul de operare? 3. Care este deosebirea între algoritm şi program?

4. Care sunt proprietăţile fundamentale ale algoritmilor? 5. Care sunt modalităţile de reprezentare a algoritmilor?

Chestiuni practice 1. Reprezentaţi algoritmul lui Euclid (pentru calculul celui mai mare divizor comun a 2 numere întregi) prin schema logică. 2. Proiectaţi un algoritm care să rezolve o ecuaţie de gradul I (de forma ax + b = 0), unde a,b sunt numere reale. Discuţie după coeficienţi. 3. Proiectaţi un algoritm care să rezolve o ecuaţie de gradul II (de forma ax 2 + bx + c = 0), unde a,b,c sunt numere reale. Discuţie după coeficienţi. 4. Proiectaţi un algoritm care să testeze dacă un număr întreg dat este număr prim. 5. Proiectaţi un algoritm care să afişeze toţi divizorii unui număr întreg introdus de la tastatură. 6. Proiectaţi un algoritm care să afişeze toţi divizorii primi ai unui număr întreg introdus de la tastatură. 7. Proiectaţi un algoritm care calculează factorialul unui număr natural dat. (Prin definiţie 0!=1)

18

CAPITOLUL 2

Date, operatori şi expresii

DATE, OPERATORI ŞI EXPRESII 2.1. Limbajele C şi C++ 2.2. Programe în limbajul C/C++ 2.3. Preprocesorul 2.4. Elemente de bază ale limbajului 2.4.1. Vocabularul 2.4.2. Unităţile lexicale 2.5. Date în limbajul C/C++

2

2.5.1. Tipuri de date 2.5.2. Constante 2.5.3. Variabile 2.6. Operatori şi expresii 2.6.1. Operatori 2.6.2. Expresii 2.7. Conversii de tip

2.1. LIMBAJELE C ŞI C++ Aşa cum comunicarea dintre două persoane se realizează prin intermediul limbajului natural, comunicarea dintre om şi calculator este mijlocită de un limbaj de programare. Limbajele C şi C++ sunt limbaje de programare de nivel înalt. Limbajul C a apărut în anii 1970 şi a fost creat de Dennis Ritchie în laboratoarele AT&T Bell. Limbajul C face parte din familia de limbaje concepute pe principiile programării structurate, la care ideea centrală este ”structurează pentru a stăpâni o aplicaţie”. Popularitatea limbajului a crescut rapid datorită eleganţei şi a multiplelor posibilităţi oferite programatorului (puterea şi flexibilitatea unui limbaj de asamblare); ca urmare, au apărut numeroase alte implementări. De aceea, în anii ’80 se impune necesitatea standardizării acestui limbaj. În perioada 1983-1990, un comitet desemnat de ANSI (American National Standards Institute) a elaborat un compilator ANSI C, care permite scrierea unor programe care pot fi portate fără modificări, pe orice sistem. Limbajul C++ apare la începutul anilor ’80 şi îl are ca autor pe Bjarne Stroustrup. El este o variantă de limbaj C îmbunătăţit, mai riguroasă şi mai puternică, completată cu construcţiile necesare aplicării principiilor programării orientate pe obiecte (POO). Limbajul C++ păstrează toate elementele limbajului C, beneficiind de eficienţa şi flexibilitatea acestuia. Limbajul C++ este un superset al limbajului C. Incompatibilităţile sunt minore, de aceea, modulele C pot fi încorporate în proiecte C++ cu un efort minim.

2.2. PROGRAME ÎN LIMBAJUL C/C++ Un program scris în limbajul C (sau C++) este compus din unul sau mai multe fişiere sursă. Un fişier sursă este un fişier text care conţine codul sursă (în limbajul C) al unui program. Fiecare fişier sursă conţine una sau mai multe funcţii şi eventual, referinţe către unul sau mai multe fişiere header (figura 2.1.). Funcţia principală a unui program este numită main. Execuţia programului începe cu execuţia acestei funcţii, care poate apela, la rândul ei, alte funcţii. Toate funcţiile folosite în program trebuie descrise în fişierele sursă (cele scrise de către programator), în fişiere header (funcţiile predefinite, existente în limbaj), sau în biblioteci de funcţii. Un fişier header este un fişier aflat în sistem sau creat de către programator, care conţine declaraţii şi definiţii de funcţii şi variabile. Acţiunile din fiecare funcţie sunt codificate prin instrucţiuni (figura 2.2.a.). Există mai multe tipuri de instrucţiuni, care vor fi discutate în capitolul următor. O instrucţiune este orice expresie validă (de obicei, o asignare sau un apel de funcţie), urmată de simbolul ;. În figura 2.2.b. este dat un exemplu de instrucţiune

19

CAPITOLUL 2

Date, operatori şi expresii

simplă. Uneori, ca instrucţiune poate apare instrucţiunea nulă (doar ;), sau instrucţiunea compusă (privită ca o succesiune de instrucţiuni simple, încadrate între acoladele delimitatoare {}. Program

Fişiere header

Fişier sursă

Funcţii

Biblioteci C++

main

Funcţii din bibliotecă

Figura 2.1. Structura unui program în limbajul C O expresie este o structură corectă sintactic, formată din operanzi şi operatori (figura 2.2.c.). FUNCŢII Instrucţiunea1 Instrucţiunea2 Instrucţiunea3

INSTRUCŢIUNI

EXPRESII Operanzi

Expresie;

. . . .

2.2.a.

Operatori 2.2.c.

2.2.b.

Figura 2.2. Funcţie, instrucţiune, expresie Pentru a înţelege mai bine noţiunile prezentate, să considerăm un exemplu foarte simplu. Programul următor afişează pe ecran un mesaj (mesajul Primul meu program). Informaţia de prelucrat (de intrare) este însuşi mesajul (o constantă şir), iar prelucrarea ei constă în afişarea pe ecran. Exemplu: #include // linia 1 void main() // linia 2 - antetul funcţiei main { /* linia 3 - începutul corpului funcţiei, a unei intrucţiuni compuse */ cout<<”Primul meu program in limbajul C++\n”; // linia 5 } // linia6-sfârşitul corpului funcţiei Prima linie este o directivă preprocesor (indicată de simbolul #) care determină includerea în fişierul sursă a fişierului header cu numele iostream.h. Acest header permite realizarea afişării pe monitor. Programul conţine o singură funcţie, funcţia principală, numită main, al cărui antet (linia 2) indică: - tipul valorii returnate de funcţie (void, ceea ce înseamnă că funcţia nu returnează nici o valoare) - numele funcţiei (main) 20

CAPITOLUL 2

Date, operatori şi expresii

- lista argumentelor primite de funcţie, încadrată de cele 2 paranteze rotunde. Funcţiile comunică între ele prin argumente. Aceste argumente reprezintă datele de intrare ale funcţiei. În cazul nostru, nu avem nici un argument în acea listă, deci puteam să scriem antetul funcţiei şi astfel: void main(void)

Ceea ce urmează după simbolul //, până la sfărşitul liniei, este un comentariu, care va fi ignorat de către compilator. Comentariul poate conţine un text explicativ; informaţii lămuritoare la anumite aspecte ale problemei sau observaţii. Dacă vrem să folosim un comentariu care cuprinde mai multe linii, vom delimita începutul acestuia indicat prin simbolulurile /*, iar sfârşitul - prin */ (vezi liniile 3, 4). Introducerea comentariilor în programele sursă uşurează înţelegerea acestora. În general, se recomandă introducerea unor comentarii după antetul unei funcţiei, pentru a preciza prelucrările efectuate în funcţie, anumite limite impuse datelor de intrare, etc. Începutul şi sfârşitul corpului funcţiei main sunt indicate de cele două acoalade { (linia3) şi }(linia 6). Corpul funcţiei (linia 5) este format dintr-o singură instrucţiune, care implementează o operaţie de scriere. Cuvantul cout este un cuvânt predefinit al limbajului C++ - console output - care desemnează dispozitivul logic de iesire; simbolul << este operatorul de transfer a informaţiei. Folosite astfel, se deschide un canal de comunicaţie a datelor către dispozitivul de ieşire, în cazul acesta, monitorul. După operator se specifică informaţiile care vor fi afişate (în acest exemplu, un şir de caractere constant). Faptul că este un şir constant de caractere este indicat de ghilimelele care îl încadrează. Pe ecran va fi afişat fiecare caracter din acest şir, cu excepţia grupului \n. Deşi grupul este format din două caractere, acesta va fi interpretat ca un singur caracter - numit caracter escape - care determină poziţionarea cursorului la începutul următoarei linii. O secvenţă escape (cum este \n) furnizează un mecanism general şi extensibil pentru reprezentarea caracterelor invizibile sau greu de obţinut. La sfârşitul instrucţiunii care implementează operaţia de scriere, apare ; .

2.3. PREPROCESORUL Aşa cum am menţionat în capitolul 1.3., în faza de compilare a fişierului sursă este invocat întâi preprocesorul. Acesta tratează directivele speciale - numite directive preprocesor - pe care le găseşte în fişierul sursă. Directivele preprocesor sunt identificate prin simbolul #, care trebuie să fie primul caracter, diferit de spaţiu, dintr-o linie. Directivele preprocesor sunt utilizate la includerea fişierelor header, la definirea numelor constantelor simbolice, la definirea macro-urilor, sau la realizarea altor funcţii (de exemplu, compilarea condiţionată), aşa cum ilustrează exemplele următoare: 

Includerea fişierelor header în codul sursă: Exemplul1: #include <stdio.h>

Când procesorul întâlneşte această linie, datorită simbolului #, o recunoaşte ca fiind o directivă preprocesor, localizează fişierul header indicat (parantezele unghiulare < > indică faptul că este vorba de un fişier header sistem). Exemplul 2: #include "headerul_meu.h"

Numele fişierului header inclus între ghilimele, indică faptul că headerul_meu.h este un fişier header creat de utilizator. Preprocesorul va căuta să localizeze acest fişier în directorul curent de lucru al utilizatorului. În cazul în care fişierul header nu se află în directorul curent, se va indica şi calea către acesta. Exemplul 3: #include "c:\\bc\\head\\headerul_meu.h"

În acest exemplu, pentru interpretarea corectă a caracterului backslash \, a fost necesară "dublarea" acestuia, din motive pe care le vom prezenta în paragraful 2.5.2.4. 

Asignarea de nume simbolice constantelor: Exemplu: #define TRUE #define FALSE

1 0

21

CAPITOLUL 2

Date, operatori şi expresii

Tratarea acestor directive preprocesor are ca efect asignarea (atribuirea) valorii întregi 1 numelui (constantei simbolice) TRUE, şi a valorii 0 numelui simbolic FALSE. Ca urmare, înaintea compilării propriu-zise, în programul sursă, apariţiile numelor TRUE şi FALSE vor fi înlocuite cu valorile 1, respectiv 0. 

Macrodefiniţii: Directiva #define este folosită şi în macrodefiniţii. Macrodefiniţiile permit folosirea unor nume simbolice pentru expresiile indicate în directivă. Exemplu: #define NEGATIV(x)

-(x)

Între numele macrodefiniţiei şi paranteza stângă ( NEGATIV(…) ) nu sunt permise spaţii albe. La întalnirea în programul sursă a macrodefiniţiei NEGATIV, preprocesorul subtituie argumentul acesteia cu expresia (negativarea argumentului). Macrodefiniţia din exemplu poate fi folosită în programul sursă astfel: NEGATIV(a+b). Când preprocesorul întâlneşte numele expresiei, subtituie literalii din paranteză, a+b, cu argumentul din macrodefiniţie, x, obţinându-se -(a+b). Dacă macrodefiniţia ar fi fost de forma: #define NEGATIV(x) NEGATIV(a+b) ar fi fost tratată ca -a+b.

-x

2.4. ELEMENTE DE BAZĂ ALE LIMBAJULUI 2.4.1. VOCABULARUL În scrierea programelor în limbajul C/C++ pot fi folosite doar anumite simboluri care alcătuiesc alfabetul limbajului. Acesta cuprinde:  Literele mari sau mici de la A la Z (a-z);  Caracterul subliniere ( _ underscore), folosit, de obicei, ca element de legătura între cuvintele compuse;  Cifrele zecimale (0-9);  Simboluri speciale:  Caractere:  operatori (Exemple: +, *, !=);  delimitatori (Exemple: blank (spaţiu), tab \t, newline \n, cu rolul de a separa cuvintele);  Grupuri (perechi de caractere). Grupurile de caractere, numire adesea separatori, pot fi:  ( ) - Încadrează lista de argumente ale unei funcţii sau sunt folosite în expresii pentru schimbarea ordinii de efectuare a operaţiilor (în ultimul caz, fiind operator);  { } - Încadrează instrucţiunile compuse;  // - Indică începutul unui comentariu care se poate întinde până la sfârşitul liniei;  /* */ - Indică începutul şi sfârşitul unui comentariu care poate cuprinde mai multe linii;  " " - Încadrează o constantă şir (un şir de caractere);  ' ' - Încadrează o constantă caracter (un caracter imprimabil sau o secvenţă escape).

2.4.2. UNITĂŢILE LEXICALE Unităţile lexicale (cuvintele) limbajului C/C++ reprezintă grupuri de caractere cu o semnificaţie de sine stătătoare. Acestea sunt:  Identificatori;  Cuvinte cheie ale limbajului; Identificatorii reprezintă numele unor date (constante sau variabile), sau ale unor funcţii. Identificatorul este format dintr-un şir de litere, cifre sau caracterul de subliniere (underscore), trebuie să înceapă cu o literă sau cu caracterul de subliniere şi să fie sugestivi. 22

CAPITOLUL 2

Date, operatori şi expresii

Exemple: viteză, greutate_netă, Viteza, Viteza1, GreutateNetă Identificatorii pot conţine litere mici sau mari, dar limbajul C++ este senzitiv la majuscule şi minuscule (case-sensitive). Astfel, identificatorii viteza şi Viteza sunt diferiţi. Nu pot fi folosiţi ca identificatori cuvintele cheie. Identificatorii pot fi standard (ca de exemplu numele unor funcţii predefinite: scanf, clear, etc.) sau aleşi de utilizator. Cuvintele cheie sunt cuvinte ale limbajului, împrumutate din limba engleză, cărora programatorul nu le poate da o altă utilizare. Cuvintele cheie se scriu cu litere mici şi pot reprezenta:  Tipuri de date (Exemple: int, char, double);  Clase de memorare (Exemple: extern, static, register);  Instrucţiuni (Exemple: if, for, while);  Operatori (Exemplu: sizeof). Sensul cuvintelor cheie va fi explicat pe masură ce vor fi prezentate construcţiile în care acestea apar.

2.5. DATE ÎN LIMBAJUL C/C++ Aşa cum s-a văzut în capitolul 1, un program realizează o prelucrare de informaţie. Termenul de prelucrare trebuie să fie considerat într-un sens foarte general (de exemplu, în programul prezentat în paragraful 2.2., prelucrarea se referea la un text şi consta în afişarea lui). În program datele apar fie sub forma unor constante (valori cunoscute anticipat, care nu se modifică), fie sub forma de variabile. Constantele şi variabilele sunt obiectele informaţionale de bază manipulate într-un program. Fiecare categorie de date este caracterizată de atributele:  Nume;  Valoare;  Tip;  Clasa de memorare. De primele trei tipuri de atribute ne vom ocupa în continuare, urmând ca de atributul clasă de memorare să ne ocupăm în paragraful 6.8. Numele unei date Numele unei date este un identificator şi, ca urmare, trebuie să respecte regulile specifice identificatorilor. Deasemenea, numărul de caractere care intră în compunerea unui identificator este nelimitat, însă, implicit, numai primele 32 de caractere sunt luate în considerare. Aceasta înseamnă că doi identificatori care au primele 32 de caractere identice, diferenţiindu-se prin caracterul 33, vor fi consideraţi identici.

2.5.1. TIPURI DE DATE Tipul unei date constă într-o mulţime de valori pentru care s-a adoptat un anumit mod de reprezentare în memoria calculatorului şi o mulţime de operatori care pot fi aplicaţi acestor valori. Tipul unei date determină lungimea zonei de memorie ocupată de acea dată. În general, lungimea zonei de memorare este dependentă de calculatorul pe care s-a implementat compilatorul. Tabelul 2.1. prezintă lungimea zonei de memorie ocupată de fiecare tip de dată pentru compilatoarele sub MS-DOS şi UNIX/LINUX. Tipurile de bază sunt:  char un singur octet (1 byte=8 biţi), capabil să conţină codul unui caracter din setul local de caractere;  int număr întreg, reflectă în mod tipic mărimea naturală din calculatorul utilizat;  float număr real, în virgulă mobilă, simplă precizie;  double număr real, în virgulă mobilă, dublă precizie. În completare există un număr de calificatori, care se pot aplica tipurilor de bază char, int, float sau double: short, long, signed şi unsigned. Astfel, se obţin tipurile derivate de date. Short şi long se referă la mărimea diferită a întregilor, iar datele de tip unsigned int sunt întotdeauna pozitive. S-a 23

CAPITOLUL 2

Date, operatori şi expresii

intenţionat ca short şi long să furnizeze diferite lungimi de întregi, int reflectând mărimea cea mai "naturală" pentru un anumit calculator. Fiecare compilator este liber să interpreteze short şi long în mod adecvat propriului hardware; în nici un caz, însă, short nu este mai lung decât long. Toţi aceşti calificatori pot aplicaţi tipului int. Calificatorii signed (cel implicit) şi unsigned se aplică tipului char. Calificatorul long se aplică tipului double. Dacă într-o declaraţie se omite tipul de bază, implicit, acesta va fi int. Tabelul 2.1. Tip

char unsigned char signed char int long (long int) long long int short int unsigned int unsigned long int float double long double

Lungimea zonei de memorie ocupate (în biţi) MSUNIX DOS LINUX 8 8

Descriere

8 8 16 32

8 8 32 64

Valoarea unui singur caracter; poate fi întâlnit în expresii cu extensie de semn Aceeaşi ca la char, fară extensie de semn Aceeaşi ca la char, cu extensie de semn obligatorie Valoare întreagă Valoare întreagă cu precizie mare

32 16 16 32

64 32 32 64

Valoare întreagă cu precizie mare Valoare întreagă cu precizie mică Valoare întreagă, fără semn Valoare întreagă, fără semn

32 64 80

32 64 128

Valoare numerică cu zecimale, simplă precizie (6 ) Valoare numerică cu zecimale, dublă precizie (10 ) Valoare numerică cu zecimale, dublă precizie

Să considerăm, de exmplu, tipul int, folosit pentru date întregi (pozitive sau negative). Evident că mulţimea valorilor pentru acest tip va fi, de fapt, o submulţime finită de numere întregi. Dacă pentru memorarea unei date de tip int se folosesc 2 octeţi de memorie, atunci valoarea maximă pentru aceasta va fi 2 15 - 1 (32767), iar valoarea minimă va fi -

1 × 2 16 - 1, deci 2

1 × 2 16 , deci -2 15 (-32768). Încercarea de a calcula o expresie 2

de tip int a cărei valoare se situează în afara acestui domeniu va conduce la o eroare de execuţie. Mulţimea valorilor pentru o dată de tip unsigned int (întreg fără semn) va fi formată din numerele întregi situate în intervalul [0, 2 16 - 1]. În header-ul sunt definite constantele simbolice (cum ar fi: MAXINT, MAXSHORT, MAXLONG, MINDOUBLE, MINFLOAT, etc.) care au ca valoare limitele inferioară şi superioară ale intervalului de valori pentru tipurile de date enumerate. (de exemplu MAXINT reprezintă valoarea întregului maxim care se poate memora, etc. ) Fără a detalia foarte mult modul de reprezentare a datelor reale (de tip float sau double), vom sublinia faptul că, pentru acestea, este importantă şi precizia de reprezentare. Deoarece calculatorul poate reprezenta doar o submulţime finită de valori reale, în anumite cazuri, pot apare erori importante. Numerele reale pot fi scrise sub forma: N = mantisa × baza exponent unde:baza reprezintă baza sistemului de numeraţie; mantisa (coeficientul) este un număr fracţionar normalizat ( în faţa virgulei se află 0, iar prima cifră de după virgulă este diferită de zero); exponentul este un număr întreg. Deoarece forma internă de reprezentare este binară, baza=2. În memorie vor fi reprezentate doar mantisa şi exponentul. Numărul de cifre de după virgulă determină precizia de exprimare a numărului. Ce alte cuvinte, pe un calculator cu o precizie de 6 cifre semnificative, două valori reale care diferă la a 7-a

24

CAPITOLUL 2

Date, operatori şi expresii

cifră zecimală, vor avea aceeaşi reprezentare. Pentru datele de tip float, precizia de reprezentare este 6; pentru cele de tip double, precizia este 14, iar pentru cele de tip long double, precizia este 20. Lungimea zonei de memorie ocupate de o dată de un anumit tip (pe câţi octeţi este memorată data) poate fi aflată cu ajutorul operatorului sizeof. Exemplu: cout<<"Un int este memorat pe "<<sizeof(int)<<"octeti.\n";

Instrucţiunea are ca efect afişarea pe monitor a mesajului: Un int este memorat pe 2 octeţi.

2.5.2. CONSTANTE O constantă este un literal (o formă externă de reprezentare) numeric, caracter sau şir de caractere. Numele şi valoarea unei constante sunt identice. Valoarea unei constante nu poate fi schimbată în timpul execuţiei programului în care a fost utilizată. Tipul şi valoarea ei sunt determinate în mod automat, de către compilator, pe baza caracterelor care compun literalul.

2.5.2.1. Constante întregi Constantele întregi sunt literali numerici (compuşi din cifre), fără punct zecimal.  Constante întregi în baza 10, 8 sau 16  Constante întregi în baza 10 Exemple: 45 -78



// constante întregi decimale (în baza 10), tip int Constante întregi octale Dacă în faţa numărului apare cifra zero (0), acest lucru indică faptul că acea constantă este de tipul int, in baza opt (constantă octală). Exemple: 056 077



// constante întregi octale, tip int Constante întregi hexagesimale Dacă în faţa numărului apar caracterele zero (0) şi x (sau X), acest lucru indică faptul că acea constantă este de tipul int, în baza 16 (constantă hexagesimală). Amintim că în baza 16 cifrele sunt: 0-9, A (sau a) cu valoare 10, B (sau b) cu valoare 11, C (sau c) cu valoare 12, D (sau d) cu valoare 13, E (sau e) cu valoare 14, F (sau f) cu valoare 15. Exemple: 0x45 0x3A 0Xbc



// constante întregi hexagesimale, tip int Constante întregi, de tipuri derivate  Dacă secvenţa de cifre este urmată de L sau l, tipul constantei este long int. Exemple: 145677L 897655l



// tip decimal long int Dacă secvenţa de cifre este urmată de U sau u, tipul constantei este unsigned int. Exemple: 65555u



Dacă secvenţa de cifre este urmată de U (u) şi L (l), tipul constantei este unsigned long int. Exemple: 7899UL //tip decimal unsigned long int

2.5.2.2. Constante numerice, reale 

Dacă o constantă numerică conţine punctul zecimal, ea este de tipul double. Exemplu: 3.1459 //tip double 25

CAPITOLUL 2  



Date, operatori şi expresii

Dacă numărul este urmat de F sau f, constante este de tip float. Dacă numărul este urmat de L sau l, este de tip long double. Exemplu: 0.45f //tip float 9.788L //tip long double Constante reale în format ştiinţific Numărul poate fi urmat de caracterul e sau E şi de un număr întreg, cu sau fără semn. În acest caz, constanta este în notaţie ştiinţifică. În această formă externă de reprezentare, numărul din faţa literei E reprezintă mantisa, iar numărul întreg care urmează caracterului E reprezintă exponentul. In forma externă de reprezentare, baza de numeraţie este 10, deci valoarea constantei va fi dată de mantisa × 10 exp onent . Exemplu: 1.5e-2 //tip double, în notaţie ştiinţifică, valoare 1.5 × 10 − 2

Exerciţiu: Să se scrie următorul program şi să se urmărească rezultatele execuţiei acestuia. #include #include #define PI 3.14359 int main() { cout<<"Tipul int memorat pe: "<<sizeof(int)<<" octeti\n"; cout<<"Tipul int memorat pe: "<<sizeof(23)<<" octeti\n"; //23-const. zecimala int cout<<"Int maxim="<<MAXINT<<’\n’;

//const. simbolice MAXINT, MAXLONG, etc. - definite in cout<<"Const. octala 077 are val decimala:"<<077<<’\n; cout<<"Const. hexagesimala d3 are val decimala:"<<0xd3<<’\n’; cout<<"Tipul unsigned int memorat pe:"<<sizeof(unsigned int)<<" octeti\n"; cout<<"Tipul unsigned int memorat pe: "<<sizeof(23U)<<" octeti\n"; cout<<"Tipul unsigned int memorat pe: "<<sizeof(23u)<<" octeti\n"; cout<<"Tipul long int memorat pe: "<<sizeof(long int)<<" octeti\n"; cout<<"Tipul long int memorat pe: "<<sizeof(23L)<<" octeti\n"; cout<<"Tipul long int memorat pe: "<<sizeof(23l)<<" octeti\n";

//23L sau 23l-const. decimala long int cout<<"Long int maxim="<<MAXLONG<<’\n’; cout<<"Tipul unsigned long memorat pe:"; cout<<sizeof(unsigned long int)<<" octeti\n"; cout<<"Tipul unsigned long memorat pe: "<<sizeof(23UL)<<" octeti\n"; cout<<"Tipul unsigned long memorat pe: "<<sizeof(23ul)<<" octeti\n";

//23UL sau 23ul-const. decimala unsigned long int cout<<"Tipul long long int memorat pe: "; cout<<sizeof(long long int)<<" octeti\n"; cout<<"Tipul long long int memorat pe: "<<sizeof(d)<<" octeti\n"; cout<<"Tipul short int memorat pe: "<<sizeof(short int)<<" octeti\n"; cout<<"Short int maxim="<<MAXSHORT<<’\n’; cout<<"Tipul float memorat pe: "<<sizeof(float)<<" octeti\n"; cout<<"Tipul float memorat pe: "<<sizeof(23.7f)<<" octeti\n";

//23.7f-const. decimala float cout<<"Float cout<<"Float cout<<"Tipul cout<<"Tipul

maxim="<<MAXFLOAT<<’\n’; minim="<<MINFLOAT<<’\n’; double memorat pe: "<<sizeof(double)<<" octeti\n"; double memorat pe: "<<sizeof(23.7)<<" octeti\n";

//23.7-const. decimala double cout<<"Const. decim. doubla in notatie stiintifica:"<<23.7e-5<<’\n’; cout<<”Const. PI este:”<
//23.7L-const. decimala long double 26

CAPITOLUL 2 Date, operatori şi expresii cout<<"Cifra A din HEXA are val.:"<<0xA<<"\n"; cout<<"Cifra B din HEXA are val.:"<<0XB<<"\n"; cout<<"Cifra C din HEXA are val.:"<<0xc<<"\n"; cout<<" Cifra D din HEXA are val.:"<<0xD<<"\n"; cout<<" Cifra E din HEXA are val.:"<<0XE<<"\n"; cout<<" Cifra F din HEXA are val.:"<<0xf<<"\n"; cout<<"Val. const. hexa 0x7ac1e este: "<<0x7ac1e<<'\n'; cout<<"Val. const. octale 171 este: "<<0171<<'\n'; cout<<"O const. octala se memoreaza pe "<<sizeof(011)<<" octeti\n"; cout<<"O const.oct.long se mem pe ";cout<<sizeof(011L)<<" octeti\n";}

2.5.2.3. Constante caracter Constantele caracter sunt încadrate între apostroafe. Exemplu: //tip char O constantă caracter are ca valoare codul ASCII al caracterului pe care îl reprezintă. Acest set de caractere are următoarele proprietăţi:  Fiecărui caracter îi corespunde o valoare întreagă distinctă (ordinală);  Valorile ordinale ale literelor mari sunt ordonate şi consecutive ('A' are codul ASCII 65, 'B' - codul 66, 'C' - codul 67, etc.);  Valorile ordinale ale literelor mici sunt ordonate şi consecutive ('a' are codul ASCII 97, 'b' - codul 98, 'c' codul 99, etc.);  Valorile ordinale ale cifrelor sunt ordonate şi consecutive ('0' are codul ASCII 48, '1' - codul 49, '2' codul 50, etc.). 'a'



Constante caracter corespunzătoare caracterelor imprimabile O constantă caracter corespunzătoare unui caracter imprimabil se reprezintă prin caracterul respectiv inclus între apostroafe. Exemplu: Constantă caracter Valoare ‘A’ ‘a’ ‘0’ ‘*’

65 97 48 42

Excepţii de la regula de mai sus le constituie caracterele imprimabile apostrof (') şi backslash (\). Caracterul backslash se reprezintă: '\\'. Caracterul apostrof se reprezintă: '\''. 

Constante caracter corespunzătoare caracterelor neimprimabile Pentru caracterele neimprimabile, se folosesc secvenţe escape. O secvenţă escape furnizează un mecanism general şi extensibil pentru reprezentarea caracterelor invizibile sau greu de obţinut. În tabelul 2.2. sunt prezentate câteva caractere escape utilizate frecvent. Tabelul 2.2. Constantă caracter ‘\n’ ‘\t’ ‘\r’ ‘\f’ ‘\a’

Valoare (Cod ASCII)

Denumirea caracterului

10 9 13 12 7

LF HT CR FF BEL

Utilizare

rând nou (Line Feed) tabulator orizontal poziţionează cursorul în coloana 1 din rândul curent salt de pagină la imprimantă (Form Feed) activare sunet O constantă caracter pentru o secvenţă escape poate apare însă, şi sub o formă în care se indică codul ASCII, în octal, al caracterului dorit: ’\ddd’ unde d este o cifră octală. Exemple: ’\11’ (pentru ’\t’) 27

CAPITOLUL 2

Date, operatori şi expresii

reprezintă constanta caracter backspace, cu codul 9 în baza 10, deci codul 11 în baza 8. ’\15’ (pentru ’\r’) reprezintă constanta caracter CR, cu codul 13 în baza 10, deci codul 11 în baza 8. Exerciţiu: Să se scrie următorul program şi să se urmărească rezultatele execuţiei acestuia. #include void main(void) { cout<<"Un caracter este memorat pe "<<sizeof(char)<<" octet\n"; cout<<"Caracterul escape \\n este memorat pe "; cout<<sizeof('\n')<<" octet\n"; cout<<"Caracterul escape '\\n\' este memorat pe "<<sizeof('\n'); cout<<" octet\n"; cout<<"Caracterul '9' este memorat pe "<<sizeof('9')<<" octet\n"; cout<<'B';cout<<' ';cout<<'c';cout<<'\t'; cout<<'\t';cout<<'9';cout<<'\b';cout<<'\a'; cout<<'L';cout<<'\v';cout<<'L'; cout<<'\'';cout<<'\t';cout<<'\"';cout<<'\\';cout<<'\n'; cout<<'\a';cout<<'\7'; }

2.5.2.4. Constante şir de caractere Constanta şir este o succesiune de zero sau mai multe caractere, încadrate de ghilimele. În componenţa unui şir de caractere, poate intra orice caracter, deci şi caracterele escape. Lungimea unui şir este practic nelimitată. Dacă se doreşte continuarea unui şir pe rândul următor, se foloseşte caracterul backslash. Caracterele componente ale unui şir sunt memorate într-o zonă continuă de memorie (la adrese succesive). Pentru fiecare caracter se memorează codul ASCII al acestuia. După ultimul caracter al şirului, compilatorul plasează automat caracterul NULL (\0), caracter care reprezintă marcatorul sfârşitului de şir. Numărul de octeţi pe care este memorat un şir va fi, deci, mai mare cu 1 decât numărul de caractere din şir. Exemple: ”Acesta este un şir de caractere” //constantă şir memorată pe 32 octeţi ”Şir de caractere continuat\” pe rândul următor!” //constantă şir memorată pe 45 octeţi ”Şir \t cu secvenţe escape\n” //constantă şir memorată pe 26 octeţi ’\n’ //constantă caracter memorată pe un octet ”\n” //constanta şir memorată pe 2 octeţi (codul caracterului escape şi terminatorul de şir) ”a\a4” /*Şir memorat pe 4 octeţi:

”\\ASCII\\”

”1\175a”

Pe primul octet: codul ASCII al caracterului a Pe al doilea octet: codul ASCII al caracterului escape \a Pe al treilea octet: codul ASCII al caracterului 4 Pe al patrulea octet: terminatorul de şir NULL, cod ASCII 0 */ /*Şir memorat pe 8 octeţi: Pe primul octet: codul ASCII al caracterului backslah Pe al doilea octet: codul ASCII al caracterului A Pe al treilea octet: codul ASCII al caracterului S Pe al patrulea octet: codul ASCII al caracterului S Pe al 6-lea octet: codul ASCII al caracterului I Pe al 7-lea octet: codul ASCII al caracterului I Pe al 8-lea octet: codul ASCII al caracterului backslah Pe al 9-ea octet: terminatorul de şir NULL, de cod ASCII 0 */ /*Şir memorat pe 4 octeţi: Primul octet: Codul ASCII al caracterul 1 Al 2-lea octet: codul ASCII 125 (175 in octal) al caracterului } Al 3-lea octet: codul ASCII al caracterului a 28

CAPITOLUL 2

Date, operatori şi expresii

Al 4-lea octet: codul ASCII 0 pentru terminatorul şirului */ Exerciţiu: Să se scrie următorul program şi să se urmărească rezultatele execuţiei acestuia. #include void main() { cout<<"Şirul \"Ab9d\" este memorat pe:"<<sizeof("Ab9d")<<" octeţi\n"; cout<<"Şirul \"Abcd\\t\" este memorat pe:"<<sizeof("Abcd\t")<<" octeţi\n"; cout<<"Şirul \"\n\" este memorat pe "<<sizeof("\n")<<" octeţi\n"; cout<<"Şirul \"\\n\" este memorat pe "<<sizeof("\n")<<" octeţi\n"; cout<<"Şirul \"ABCDE\" se memorează pe "<<sizeof("ABCDE")<<" octeţi\n";}

2.5.3. VARIABILE Spre deosebire de constante, variabilele sunt date (obiecte informaţionale) ale căror valori se pot modifica în timpul execuţiei programului. Şi variabilele sunt caracterizate de atributele nume, tip, valoare şi clasă de memorare. Variabilele sunt nume simbolice utilizate pentru memorarea valorilor introduse pentru datele de intrare sau a rezultatelor. Dacă la o constantă ne puteam referi folosind caracterele componente, la o variabilă ne vom referi prin numele ei. Numele unei variabile ne permite accesul la valoarea ei, sau schimbarea valorii sale, dacă este necesar acest lucru. Numele unei variabile este un identificator ales de programator. Ca urmare, trebuie respectate regulile enumerate în secţiunea identificatori. Dacă o dată nu are legături cu alte date (de exemplu, relaţia de ordine), vom spune că este o dată izolată. O dată izolată este o variabilă simplă. Dacă datele se grupează într-un anumit mod (în tablouri - vectori, matrici - sau structuri), variabilele sunt compuse (structurate). În cazul constantelor, în funcţie de componenţa literalului, compilatorul stabilea, automat, tipul constantei. În cazul variabilelor este necesară specificarea tipului fiecăreia, la declararea acesteia. Toate variabilele care vor fi folosite în program, trebuie declarate înainte de utilizare.

2.5.3.1. Declararea variabilelor Modul general de declarare a variabilelor este: tip_variabile listă_nume_variabile; Se specifică tipul variabilei(lor) şi o listă formată din unul sau mai mulţi identificatori ai variabilelor de tipul respectiv. Într-un program în limbajul C++, declaraţiile de variabile pot apare în orice loc în programul sursă. La declararea variabilelor, se rezervă în memorie un număr de octeţi corespunzător tipului variabilei, urmând ca ulterior, în acea zonă de memorie, să fie depusă (memorată, înregistrată) o anumită valoare. Exemple: int i, j;/*declararea var. simple i, j, de tip int. Se rezervă pentru i şi j câte 16 biţi (2octeţi)*/ char c; /* declararea variabilei simple c, de tip char. Se rezervă un octet. */ float lungime; /* declararea variabilei simple lungime; se rezervă 4 octeţi */

2.5.3.2. Iniţializarea variabilelor în declaraţii În momentul declarării unei variabile, acesteia i se poate da (asigna, atribui) o anumită valoare. În acest caz, în memorie se rezervă numărul de locaţii corespunzător tipului variabilei respective, iar valoarea va fi depusă (memorată) în acele locaţii. Forma unei declaraţii de variabile cu atribuire este: tip_variabilă nume_variabilă=expresie; Se evaluează expresia, iar rezultatul acesteia este asignat variabilei specificate. Exemple: char backslash=’\\’; //declararea şi iniţializarea variabilei simple backslash int a=7*9+2; /* declararea variabilei simple a, de tip int şi iniţializarea ei cu valoarea 65*/ float radiani, pi=3.14;/*declararea variabilei radiani;declararea şi iniţializarea var. pi*/ 29

CAPITOLUL 2 short int z=3; char d=’\011’; char LinieNoua=’\n’; double x=9.8, y=0;

Date, operatori şi expresii

//declararea şi iniţializarea variabilei simple z

Compilatorul C++ furnizează mecanisme care permit programatorului să influenţeze codul generat la compilare, prin aşa-numiţii calificatori. Aceştia sunt:  const;  volatile. Calificatorul const asociat unei variabile, nu va permite modificarea ulterioară a valorii acesteia, prin program (printr-o atribuire). Calificatorul volatile (cel implicit) are efect invers calificatorului const. Dacă după calificator nu este specificat tipul datei, acesta este considerat tipul implicit, adică int. Exemple: const float b=8.8; volatile char terminator;terminator=’@’;terminator=’*’; //permis b=4/5; //nepermisa modificarea valorii variabilei b const w; volatile g; //w, g de tip int, implicit

2.5.3.3.

Operaţii de intrare/ieşire

Limbajele C/C++ nu posedă instrucţiuni de intrare/ieşire, deci de citire/scriere (ca limbajul PASCAL, de exemplu). În limbajul C aceste operaţii se realizează cu ajutorul unor funcţii (de exemplu, printf şi scanf), iar în limbajul C++ prin supraîncărcarea operatorilor (definirea unor noi proprietăţi ale unor operatori existenţi, fără ca proprietăţile anterioare să dispară), mai precis a operatorilor >> şi << . Vom folosi în continuare abordarea limbajului C++, fiind, în momentul de faţă, mai simplă. În limbajul C++ sunt predefinite următoarele dispozitive logice de intrare/ieşire: cin - console input - dispozitivul de intrare (tastatura); cout - console output - dispozitivul de ieşire (monitorul). Aşa cum se va vedea în capitolul 9, cin şi cout sunt, de fapt, obiecte (predefinite). Transferul informaţiei se realizează cu operatorul >> pentru intrare şi operatorul << pentru ieşire. Utilizarea dispozitivelor de intrare/ieşire cu operatorii corespunzători determină deschiderea unui canal de comunicaţie a datelor către dispozitivul respectiv. După operator se specifică informaţiile care vor fi citite sau afişate. Exemple: cout << var; /* afişează valoarea variabilei var pe monitor*/ cin >> var; /* citeşte valoarea variabilei var de la tasatatură */ Sunt posibile operarţii multiple, de tipul: Exemple: cout << var1 << var2 << var3; cin >> var1 >> var2 >> var3;

În acest caz, se efectuează succesiv, de la stânga la dreapta, scrierea, respectiv citirea valorilor variabilelor var1, var2 şi var3. Operatorul >> se numeşte operator extractor (extrage valori din fluxul datelor de intrare, conform tipului acestora), iar operatorul << se numeşte operator insertor (inserează valori în fluxul datelor de ieşire, conform tipului acestora). Tipurile de date citite de la tastatură pot fi toate tipurile numerice, caracter sau şir de caractere. Tipurile de date transferate către ieşire pot fi: toate tipurile numerice, caracter sau şir de caractere. Operanzii operatorului extractor (>>) pot fi doar nume de variabile. Operanzii operatorului insertor (<<) pot fi nume de variabile (caz în care se afişează valoarea variabilei), constante sau expresii. Utilizarea dispozitivelor şi operatorilor de intrare/ieşire în C++ impune includerea fişierului iostream.h. Exemple: char c;

30

CAPITOLUL 2

Date, operatori şi expresii

cout<<"Astept un caracter:"; //afişarea constantei şir de caractere, deci a mesajului cin>>c; //citirea valorii variabilei c, de tip caracter int a, b, e; double d; cin>>a>>b>>e>>d; //citirea valorilor variabilelor a, b, e, d de tip int, int, int, double cout<<"a="<
2.6. OPERATORI ŞI EXPRESII Datele (constante sau variabile) legate prin operatori, formează expresii (figura 2.4). Operatorii care pot fi aplicaţi datelor (operanzilor) depind de tipul operanzilor, datorită faptului că tipul unei date constă într-o mulţime de valori pentru care s-a adoptat un anumit mod de reprezentare în memoria calculatorului şi o mulţime de operatori care pot fi aplicaţi acestor valori. Operatorii pot fi:  unari (necesită un singur operand);  binari (necesită doi operanzi);  ternari (trei operanzi). O expresie este o combinaţie corectă din punct de vedere sintactic, formată din operanzi şi operatori. Expresiile, ca şi operanzii, au tip şi valoare. 2.6.1. OPERATORI Operatorul unar adresă &, aplicat identificatorului unei variabile, furnizează adresa la care este memorată aceasta. Poate fi aplicat oricărui tip de date şi se mai numeşte operator de referenţiere. Exemplu: 

int a; cout<<"Adresa la care este memorata variabila a este:"<<&a;

Operatorul de atribuire (de asignare) este un operator binar care se aplică tuturor tipurilor de variabile. Este folosit sub formele următoare: nume_variabilă=expresie; sau: expresie1=expresie2; Se evaluează expresia din membrul drept, iar valoarea acesteia este atribuită variabilei din membrul stâng. Dacă tipurile membrilor stâng şi drept diferă, se pot realiza anumite conversii, prezentate în paragraful 2.7. Exemplu: 

float x; int a,b; x=9.18; a=b=10; int s; s=a+20*5; s=x+2;

//rezultat: s=110 //rezultat s=11, deoarece s este int.

Aşa cum se observă în linia a 2-a din exemplul precedent, operatorul de atribuire poate fi utilizat de mai multe ori în aceeaşi expresie. Asociativitatea operatorului are loc de la dreapta la stânga. Astfel, mai întâi b=10, apoi a=b. Exerciţiu: Să se scrie următorul program şi să se urmărească rezultatele execuţiei acestuia. #include void main() { float x,y=4.25; char car=’A’; int a,b,c; cout<<”Val. lui y este:”<
//Afişare: Val. lui y este:4.25 x=y; cout<<”Val. lui x este:”<<x<<’\n’; //Afişare: Val. lui x este:4.25 a=x;cout<<”Val.lui a este:”<
CAPITOLUL 2

Date, operatori şi expresii

// citire val. pentru c //Afişare: Val. lui c este:4

cout<<”Introduceţi val. lui c:”; cin rel="nofollow">>c; cout<<”Val. lui c este:”<
Operatorul poate fi aplicat tipurilor de date întregi, reale, caracter, şi chiar şiruri de caractere, aşa cum vom vedea în capitolele următoare (exemplu: char şir [10]=”a5dfgthklj”). 

Operatori aritmetici unari: Operator Semnificaţie Minus unar ++ Operator de incrementare (adună 1 la valoarea operandului) -Operator de decrementare (scade 1 din valoarea operandului)

Exemple -a a++ sau ++a a-sau --a

Operatorul - unar schimbă semnul operandului. Exemplu: 

int a,b; cout<<”a=”<<-a<<’\n’; cout<<”b=”<
b=-a;

Operatorul - unar poate fi aplicat datelor întregi, reale, caracter. Operatorii de incrementare şi decrementare pot fi aplicaţi datelor numerice sau caracter. Ambii operatori pot fi folosiţi în formă prefixată, înaintea operandului, (++a, respectiv --a) sau postfixată, după operand (a++, respectiv a--). Operatorul de decrementare -- care poate fi folosit în formă prefixată (--a) sau postfixată (a--). 

Utilizarea acestor operatori în expresii, în formă prefixată sau postfixată, determină evaluarea acestora în moduri diferite, astfel: y=++x

este echivalent cu:

y=x++

este echivalent cu:

y=--x

este echivalent cu:

y=x--

este echivalent cu:

x=x+1; y=x; y=x; x=x+1; x=x-1; y=x; y=x; x=x-1;

Exerciţiu: Să se scrie următorul program şi să se urmărească rezultatele execuţiei acestuia. #include void main() { int a=9; cout<<”a++=”<
32

//Afişare: a++=9 //Afişare: a=10 //Revenire in situatia anterioara //Afişare: ++a=10 //Afişare: a=10 //Afişare: a--=9 //Afişare: a=8 //Revenire in situaţia anterioara //Afişare: --a=8 //Afişare: a=8 //Afişare: z=1 //Afişare: x=4 //Afişare: z=2

CAPITOLUL 2

Date, operatori şi expresii

//Afişare: x=4

cout<<"x=”<<x<<’\n’; } 

Operatori aritmetici binari: Operator Semnificaţie Exemple + Adunarea celor doi operanzi a+b Scăderea celor doi operanzi a-b * Înmulţirea celor doi operanzi a*b / Împărţirea celor doi operanzi a/b % Operatorul modulo (operatorul rest) a%b (furnizează restul împărţirii operatorului stâng la operatorul drept).

Operatorul modulo se aplică numai operanzilor întregi (de tip int sau char). Ceilalţi operatori aritmetici binari pot fi aplicaţi datelor întregi sau reale. Dacă într-o expresie cu 2 operanzi şi un operator binar aritmetic, ambii operanzi sunt întregi, rezultatul expresiei va fi tot un număr întreg. De exemplu, la evaluarea expresiei 9/2, ambii operanzi fiind întregi, rezultatul furnizat este numărul întreg 4. Operatorii prezentaţi respectă o serie de reguli de precedenţă (prioritate) şi asociativitate, care determină precis modul în care va fi evaluată expresia în care aceştia apar. În tabelul 2.3 sunt prezentaţi operatorii anteriori, în ordinea descrescătoare a priorităţii. Precedenţa operatorilor poate fi schimbată cu ajutorul parantezelor. Tabelul 2.3. Clasă de operatori Unari Multiplicativi Aditivi Atribuire

Operatori - (unar) ++ * / % + =

--

Asociativitate de la dreapta la stânga de la stânga la dreapta de la stânga la dreapta de la dreapta la stânga

Exerciţiu: Să se scrie următorul program şi să se urmărească rezultatele execuţiei acestuia. #include void main() { int rezult, a=20,b=2,c=25,d=4; rezult=a-b; cout<<”a-b=”<
Operatori aritmetici binari compuşi Operator Semnificaţie += a=a+b -= a=a+b *= a=a*b /= a=a/b %= a=a%b Aceşti operatori se obţin prin combinarea operatorilor folosiţi sub forma următoare: expresie1 operator= expresie2;

// Afişare: a-b=18 // Afişare: a+b=22 // Afişare: c*b=50 // Afişare: a/d=5 // Afişare: c%b=1 // Afişare: c/b*d=48 // Afişare: -b+a=18 // Afişare: -(b+a)=-22 // Afişare: b+c*d=102 // Afişare: (b+c)*d=108



33

Exemple a+=b a-=b a*=b a/=b a%=b aritmetici binari cu operatorul de atribuire şi sunt

CAPITOLUL 2

Date, operatori şi expresii

Rezultatul obţinut este acelaşi cu rezultatul obţinut prin: expresie1 = expresie1 operator expresie2; Toţi aceşti operatorii modifică valoarea operandului stâng prin adunarea, scăderea, înmulţirea sau împărţirea acestuia prin valoarea operandului drept. Construcţia x+=1 generează acelaşi rezultat ca expresia x=x+1. Observaţiile referitoare la operatorii aritmetici binari sunt valabile şi pentru operatorii aritmetici binari compuşi. Operatorii aritmetici binari compuşi au aceeaşi prioritate şi asociativitate ca şi operatorul de atribuire. Exerciţiu: Să se scrie următorul program şi să se urmărească rezultatele execuţiei acestuia. #include void main() { int a,b; float c=9.3; a=3; b=8; cout<<”a=”<
//Afişare //Afişare //Afişare //Afişare //Afişare //Afisare

a=3 a=11 a=-5 a=24 a=0 a=3

Operatori relaţionali binari Operator Semnificaţie Exemple == Egal cu a==b != Diferit de a!=b < Mai mic decât a Mai mare decât a>b >= Mai mare sau egal a>=b Primii doi operatori mai sunt numiţi operatori de egalitate. Operatorii relaţionali servesc la compararea valorilor celor doi operanzi şi nu modifică valorile operanzilor. Rezultatul unei expresii în care apare unul din operatorii relaţionali binari este întreg şi are valoarea zero (0) dacă relaţia este falsă, sau valoarea unu (1) (sau diferită de 0 în cazul compilatoarelor sub UNIX), dacă relaţia este adevărată. Aceşti operatorii pot fi aplicaţi datelor de tip întreg, real sau char. Regulile de precedenţă şi asociativitate ale acestor operatori sunt prezentate în tabelul 2.4. 

Tabelul 2.4. Clasă de operatori Unari Multiplicativi Aditivi Atribuire Relaţionali De egalitate Atribuire şi aritmetici binari

Operatori - (unar) ++ -* / % + = < <= > >= == != = *= /= %= += -=

Asociativitate de la dreapta la stânga de la stânga la dreapta de la stânga la dreapta de la dreapta la stânga de la stânga la dreapta de la stânga la dreapta de la dreapta la stânga

Observaţie: Deosebirea dintre operatorii == (relaţional, de egalitate) şi = (de atribuire) constă în faptul că primul nu modifică valoarea nici unuia dintre operanzii săi, pe când cel de-al doilea modifică valoarea operandului stâng (vezi exemplul următor) Exerciţiu: Să se scrie următorul program şi să se urmărească rezultatele execuţiei acestuia. #include void main() { int a=1, b=20, lim=100; int rezult; rezult=a
CAPITOLUL 2 cout<<”a
Date, operatori şi expresii

// Afişare: a
//operatorul realţional <= are prioritate mai mare decât cel de atribuire cout<<”a<=b=”<
// Afisare: ab; cout<<”a>b=”<=lim;

cout<<”a+10>=lim=”<
/* Operatorul + are prioritate mai mare decât operatorul >= . Afişare: a+10>=lim=0 */ rezult=a+(10>=lim);

cout<<”a+(10>=lim)=”<
/* Schimbarea prioritatii operatorilor prin folosirea parantezelor; Afişare: a+(10>=lim)=1 */ rezult=a==b; cout<<”a==b=”<b>10;cout<<”b=”<b>10=”<b)>10 Afişare: 5>b>10=0 }

Operatori logici pe cuvânt Operator Semnificaţie Exemple ! Not (negaţie logică) !(a==b) && And (conjuncţie, şi logic) (a>b) && (b>c) || Or (disjuncţie, sau logic) (a>b) || (b>c) Aceşti operatori pot fi aplicaţi datelor de tip întreg, real sau caracter. Evaluarea unei expresii în care intervin operatorii logici se face conform tabelului 2.5. 

Tabelul 2.5. x y !x x&&y x||y adevărat (1) adevărat (1) fals (0) adevărat (1) adevărat (1) adevărat (1) fals (0) fals (0) fals (0) adevărat (1) fals (0) adevărat (1) adevărat (1) fals (0) adevărat (1) fals (0) fals (0) adevărat (1) fals (0) fals (0) Expresia !expresie are valoarea 0 (fals) dacă expresia-operand are o valoare diferită de zero şi valoarea unu (adevărat) dacă expresia-operand are valoarea zero. Expresia expresie1||expresie2 are valoarea diferită de 0 (true) dacă FIE expresie1, FIE expresie2 au valori diferite de zero. Expresia expresie1 && expresie2 are valoarea diferită de 0 (true) dacă AMBELE expresiioperand ( expresie1 şi expresie2) au valori diferite de zero. Exerciţiu: Să se scrie următorul program şi să se urmărească rezultatele execuţiei acestuia. #include void main() { int a=0, b=10, c=100, d=200; int rezult; rezult=a&&b; cout<<”a&&b=”<b) || (b>c);cout<<”(a>b) || (b>c)=”<
//Afişare (a>b) || (b>c) =1(sau valoare nenula) rezult=!(cd)=0 rezult=(a-b)&&1;cout<<”(a-b)&&1=”<
//Afişare (a-b)&&1 =1(sau valoare nenula) 35

CAPITOLUL 2

Date, operatori şi expresii

rezult=d||b&&a;cout<<”d||b&&a=”<
Tabelul 2.6. Clasă de operatori Unari Multiplicativi Aditivi Atribuire relaţionali de egalitate logici logici atribuire şi aritmetici binari

Operatori ! - (unar) ++ -* / % + = < <= > >= == != && || = *= /= %= += -=

Asociativitate de la dreapta la stânga de la stânga la dreapta de la stânga la dreapta de la dreapta la stânga de la stânga la dreapta de la stânga la dreapta de la stânga la dreapta de la stânga la dreapta de la dreapta la stânga

Exerciţiu: Să se scrie un program care citeşte un număr real şi afişează 1 dacă numărul citit aparţine unui interval ale cărui limite sunt introduse tot de la tastatură, sau 0 în caz contrar. #include void main() { double lmin, lmax, nr;cout<<"Numar=";cin>>nr; cout<<”Limita inferioară a intervalului:”; cin>>lmin; cout<<”Limita superioară a intervalului:”; cin>>lmax; cout<<(nr>=lmin && nr<=lmax); } 

Operatori logici pe bit Operator Semnificaţie ~ Negaţie (cod complementar faţă de unu) & AND (Conjuncţie, şi logic pe bit | OR (Disjuncţie, sau logic pe bit) ^ XOR (Sau exclusiv logic pe bit) << Deplasare stânga >> Deplasare dreapta

Exemple ~a a & 0377 a | 0377 a^b 0377 << 2 0377 >> 2

Aceşti operatori nu se aplică numerelor reale, ci numai datelor de tip întreg sau caracter. Primul operator este unar, ceilalţi binari. Operatorii acţionează la nivel de bit, la nivelul reprezentării interne (în binar), conform tabelelului 2.7. Tabelul 2.7. x 1 1 0 0

y 1 0 1 0

x&y 1 0 0 0

x|y 1 1 1 0

x^y 0 1 1 0

~x 0 0 1 1

Operatorul ~ are aceeaşi prioritate ca şi ceilalţi operatori unari. El furnizează complementul faţă de unu al unui întreg, adică va schimba fiecare bit de pe 1 în zero şi invers. Operatorii de deplasare pe bit (<< şi >>) efectuează deplasarea la stânga sau la dreapta a operandului stâng, cu numărul de biţi indicaţi de operandul drept. Astfel, x<<2 deplasează biţii din x la stânga, cu două poziţii, introducând zero pe poziţiile rămase vacante. Exemple: int a=3; //Reprezentare internă a lui a (pe 2 octeţi): 0000000000000011 int b=5; //Reprezentare internă a lui b (pe 2 octeţi): 0000000000000101 int rez=~a; cout<<"~"<
//Complementul faţă de unu este: 1111111111111100 (în octal: 0177777774 (!a= - 4) 36

CAPITOLUL 2

Date, operatori şi expresii

rez=a & b; cout<
//a&b=0000000000000001 =1 rez=a^b; cout<
//a ^b = 0000000000000110 rez=a|b; cout<
//3|5= 7

//a | b = 0000000000000111 rez=a<<2; cout<
//3<<2=16=2*2 3

//a<<2= 0000000001100000 rez=5 rel="nofollow">>2; cout<>"<<2<<'='<
//5>>2=1=5/2 2

//b>>2= 0000000000000001 Operatorul binar ^ îşi găseşte o utilizare tipică în expresii ca: x&^077, care maschează ultimii 6 biţi ai lui x pe zero. Operatorul & este adesea utilizat în expresii ca x&0177, unde setează toţi biţii pe zero, cu excepţia celor de ordin inferior din x. Operatorul | este utilizat în expresii ca: x&MASK , unde setează pe unu biţii care în x şi masca MASK sunt setaţi pe unu. Operatorii logici pe bit & şi | sunt diferiţi de operatorii logici && şi || (pe cuvânt). Deplasarea la stânga a unei date cu n poziţii este echivalentă cu înmulţirea valorii acesteia cu 2 n . Deplasarea la dreapta a unei date fără semn cu n poziţii este echivalentă cu împărţirea valorii acesteia cu 2 n . Combinând operatorii logici pe bit cu operatorul de atribuire, se obţin operatorii: &=, ^=, |=, <<=, >>=. Operatorul condiţional Este un operator ternar (necesită 3 operanzi), utilizat în construcţii de forma: expresie1?expresie2:expresie3 

Se evaluează expresia1. Dacă aceasta are o valoare diferită de zero, atunci tipul şi valoarea întregii expresii vor fi aceleaşi cu tipul şi valoarea expresiei2. Altfel (dacă expresie1 are valoarea zero), tipul şi valoarea întregii expresii vor fi aceleaşi cu tipul şi valoarea expresiei3. Deci operatorul condiţional este folosit pentru a atribui întregii expresii tipul şi valoarea expresiei2 sau a expresiei3, în funcţie de o anumită condiţie. Acest lucru este echivalent cu: Dacă expresie1 diferită de zero Atunci evaluează expresie2 Altfel evaluează expresie3 Exemplu: int semn=(x<0)?-1:1 Dacă x<0, atunci semn=-1, altfel semn=1.

Operatorul virgulă Este utilizat în construcţii de forma: expresie1 , expresie2 Operatorul virgulă forţează evaluarea unei expresii de la stânga la dreapta. Tipul şi valoarea întregii expresii este dată de tipul şi valoarea expresiei2. Operatorul virgulă este folosit în instrucţiunea for. Operatorul virgulă are cea mai mică prioritate. Exemplu: 

int x, c, y; cout<<”Astept val. ptr. y:”; cin>>y; x=(c=y, c<=5); /* c va primi valoarea lui y (citită); se verifică dacă c este mai mic sau x++, y--;

egal cu 5. Daca nu, x=0; daca da, x=1 sau x=valoare diferită de zero)*/ //întâi este incrementat x, apoi este decrementat y

Operatorul sizeof() Este un operator unar, care are ca rezultat numărul de octeţi pe care este memorată o dată de un anumit tip. Operandul este un tip sau o dată (constantă sau variabilă) de un anumit tip. Exemple: 

37

CAPITOLUL 2

Date, operatori şi expresii

cout<<sizeof(int); // afişează numărul de octeţi pe care este memorat un întreg (2) cout<<sizeof(”ab6*”);// afişează 5, nr. de octeţi pe care este memorată constanta şir ”ab6*”

Operatorul (tip) Este un operator unar care apare în construcţii numite ”cast” şi converteşte tipul operandului său la tipul specificat între paranteze. Exemple: int a; (float) a; // converteşte operandul a (care era de tip întreg) în float 

În afara operatorilor prezentaţi, există şi alţii, pe care îi vom enumera în continuare. Despre aceşti operatori vom discuta în capitolele viitoare, când cunoştinţele acumulate vor permite acest lucru. Operatorul unar * Este operator unar, numit şi operator de deferenţiere. Se aplică unei expresii de tip pointer şi este folosit pentru a accesa conţinutul unei zone de memorie spre care pointează operatorul. Operatorii & (adresă) şi * sunt complementari. Exemplu: Expresia *a este înlocuită cu valoarea de la adresa conţinută în variabila pointer a. 

Operatorii paranteză Parantezele rotunde ( ) se utilizează în expresii, pentru schimbarea ordinii de efectuare a operaţiilor, sau la apelul funcţiilor. La apelul funcţiilor, parantezele rotunde încadrează lista parametrilor efectivi. Din acest motiv, parantezele rotunde sunt numite şi operatori de apel de funcţie. Exemplu: 

double sum(double a, double b);

/*declar. funcţiei sum, care primeşte 2 argumente reale(double) şi returnează o valoare tip double */ void main() { . . . double a=sum(89.9, 56.6); //apelul funcţiei sum, cu parametri efectivi 89.9 şi 56.6 int s0=6; double s1=(s0+9)/a; //folosirea parantezelor în expresii . . . } 

Operatorii de indexare Operatorii de indexare sunt parantezele pătrate []. Acestea includ expresii întregi care reprezintă indici ai unui tablou.



Operatori de acces la membri structurilor Operatorii ::, ., ->, .* şi ->* permit accesul la componentele unei structuri. Ei vor fi studiaţi în capitolul 7.

În tabelul 2.8. sunt prezentaţi toţi operatorii, grupaţi pe categorii, cu priorităţile lor şi regulile de asociativitate. Operatorii dintr-o categorie au aceeaşi prioritate. Tabelul 2.8. Nr. Clasă de operatori 1. Primari 2. Unari 3. 4. 5. 6. 7.

Multiplicativi Aditivi Deplasare pe bit Relaţionali De egalitate

Operatori () [] . -> :: ! ~ ++ -- sizeof (tip) -(unar) *(deferenţiere) &(referenţiere) * / % + << >> < <= > >= == !=

38

Asociativitate de la stânga la dreapta de la stânga la dreapta de la stânga la dreapta de la stânga la dreapta de la stânga la dreapta de la stânga la dreapta de la stânga la dreapta

CAPITOLUL 2

8. 9. 10. 11. 12. 13. 14.

Condiţional De atribuire

15.

Virgulă

Date, operatori şi expresii

& (ŞI logic pe bit) ^ (XOR pe bit) | (SAU logic pe bit) && || ?: = += -= *= %= &= ^= |= <<= >>= ,

de la stânga la dreapta de la stânga la dreapta de la stânga la dreapta de la stânga la dreapta de la stânga la dreapta de la dreapta la stânga de la dreapta la stânga de la stânga la dreapta

2.6.2. EXPRESII Prin combinarea operanzilor şi a operatorilor se obţin expresii. Tipul unei expresii este dat de tipul rezultatului obţinut în urma evaluării acesteia. La evaluarea unei expresii se aplică regulile de prioritate şi asociativitate a operatorilor din expresie. Ordinea de aplicare a operatorilor poate fi schimbată prin folosirea parantezelor. La alcătuirea expresiilor, este indicată evitarea expresiilor în care un operand apare de mai multe ori. 2.6.3. CONVERSII DE TIP La evaluarea expresiilor, se realizează conversii ale tipului operanzilor. Conversiile sunt:  Automate;  Cerute de evaluarea expresiilor;  Cerute de programator (prin construcţiile cast), explicite. Conversiile automate sunt realizate de către compilator: char, short int -> int Ele sunt realizate de fiecare dată când într-o expresie apar operanzi de tipul char sau short int. Conversiile cerute de evaluarea expresiilor sunt efectuate în cazurile în care în expresii apar operanzi de tipuri diferite. Înaintea aplicării operatorilor, se realizează conversia unuia sau a ambilor operanzi:  Dacă un operand este de tip long int, celălalt este convertit la acelaşi tip; tipul expresiei este long int.  Dacă un operand este de tipul double, celălalt este convertit la acelaşi tip; tipul expresiei este double.  Dacă un operand este de tipul float, celălalt este convertit la acelaşi tip; tipul expresiei este float. Conversiile explicite (cerute de programator) se realizează cu ajutorul construcţiilor cast. Exemplu: int x=3;

float y;

y=(float)x/2;

Înainte de a se efectua împărţirea celor 2 operanzi, operandul x (întreg) este convertit în număr real simplă precizie. După atribuire, valoarea lui y va fi 1.5. Dacă nu ar fi fost folosit operatorul de conversie în expresia y=x / 2, operanzii x şi 2 fiind întregi, rezultatul împărţirii este întreg, deci y ar fi avut valoarea 1.

ÎNTREBĂRI ŞI EXERCIŢII Chestiuni teoretice Ce reprezintă datele şi care sunt atributele lor? 2. Care sunt diferenţele între constante şi variabile? 3. Cine determină tipul unei constante? 4. Ce sunt identificatorii? 5. Ce sunt directivele preprocesor? 6. Ce reprezinta variabilele? 7. Ce sunt constantele? 8. Enumeraţi tipurile simple de variabile. 9. Câte tipuri de directive preprocesor cunoasteţi? Exemple. 10. Care este modalitatea de a interzice modificarea valorii unei variabile?

11. Ce loc ocupă declararea varibilelor în cadrul unui program sursă scris în limbajul C++? 12. Ce conţin fişierele header? 13. Ce tipuri de variabile se utilizează pentru datele numerice? 14. Care sunt calificatorii folosiţi alături de tipurile de bază pentru obţinerea tipurilor derivate de date? 15. Ce semnifică parantezele unghiulare < > care încadrează numele unui fişier header? 16. Care este diferenţa între constantele 35.2e-1 şi 3.52 ? Dar între "\t" şi '\t'? 17. Ce tip are constanta 6.44 ?

1.

39

CAPITOLUL 2

Date, operatori şi expresii

18. Care este diferenţa între operatorii = şi = = ?

25. Constante reale. 26. Ce operatori ternari cunoasteţi? 27. Operatorul virgulă. 28. Operatorul sizeof. 29. Operatori aritmetici binari compuşi. 30. Operatorul de referenţiere. 31. Operatori relaţionali binari.

19. 20. 21. 22. 23.

Ce reprezintă caracterele "escape"? Constante întregi. Constante caracter. Ce tipuri de conversii cunoaşteţi? Care sunt conversiile realizate în mod automat, de către compilator? 24. Constante şir de caractere. Chestiuni aplicative

1. Să se scrie declaraţiile pentru definirea constantelor simbolice: pi, g (acceleraţia gravitaţională), unghi_drept, dimensiune_MAX. 2. Care va fi rezultatul afişat pe ecran în urma execuţiei următoarelor secvenţe de instrucţiuni:            

3.

double a=9/2; cout<
double a=7/2; cout<b)<<’\n’;

Să se verifice corectitudinea următoarelor secvenţe. Pentru cele incorecte, explicaţi sursa erorilor.          

double a=9.7, b=5.2; int c=(a+6b)<<’\n’; double a=9.8; double b=9.7; cout<
6.

7. 8. 9.

10.

-i - 5 * j rel="nofollow">= k + 1 3 < j < 5 i + j + k == -2 * j x && i || j - 3

Ce operaţie logică şi ce mască trebuie să folosiţi pentru a converti codurile ASCII ale literelor mici în litere mari? Dar pentru conversia inversă? O deplasare la dreapta cu 3 biţi este echivalentă cu o rotaţie la stânga cu câţi biţi? Să se seteze pe 1 toţi biţii dintr-un octet, cu excepţia bitului cel mai semnificativ. Să se scrie un program care citeşte o valoare întreagă. Să se afişeze un mesaj care să indice dacă numărul citit este par sau impar. Să se citeasca două valori întregi. Să se calculeze şi să se afişeze restul împărţirii celor două numere. 40

CAPITOLUL 3 control

Implementarea structurilor de

IMPLEMENTAREA STRUCTURILOR DE CONTROL 3.1. Implementarea structurii secvenţiale 3.2. Implementarea structurii de decizie

3

3.3. Implementarea structurilor repetitive 3.4. Facilităţi de întrerupere a unei secvenţe

Algoritmul proiectat pentru rezolvarea unei anumite probleme trebuie implementat într-un limbaj de programare; prelucrarea datelor se realizează cu ajutorul instrucţiunilor. Instrucţiunea descrie un proces de prelucrare pe care un calculator îl poate executa. O instrucţiune este o construcţie validă (care respectă sintaxa limbajului) urmată de ; . Ordinea în care se execută instrucţiunile unui program defineşte aşa-numita structură de control a programului. Limbajele moderne sunt alcătuite pe principiile programării structurate. Conform lui C. Bohm şi G. Jacobini, orice algoritm poate fi realizat prin combinarea a trei structuri fundamentale:  structura secvenţială;  structura alternativă (de decizie, de selecţie);  structura repetitivă (ciclică).

3.1. IMPLEMENTAREA STRUCTURII SECVENŢIALE Structura secvenţială este o înşiruire de secvenţe de prelucrare (instrucţiuni), plasate una după alta, în ordinea în care se doreşte execuţia acestora. Reprezentarea structurii secvenţiale cu ajutorul schemei logice ( figura 3.1.):

Reprezentarea structurii secvenţiale cu ajutorul pseudocodului:

S1

instr1; instr2; . . . . .

S2

Sn

Figura 3.1. Schema logică pentru structura secvenţială Implementarea structurii secvenţiale se realizează cu ajutorul instrucţiunilor:  Instrucţiunea vidă Sintaxa: ; Instrucţiunea vidă nu are nici un efect. Se utilizează în construcţii în care se cere prezenţa unei instrucţiuni, dar nu se execută nimic (de obicei, în instrucţiunile repetitive). Exemple: int . . int ; for {

a; . . . . j; (;;)

41

CAPITOLUL 3 control . . . . } 

Instrucţiunea expresie Sintaxa: sau:

Implementarea structurilor de

expresie; apel_funcţie;

Exemple: int b, a=9; double c; b=a+9; cout<
Instrucţiunea compusă (instrucţiunea bloc) Sintaxa: { declaratii; instr1; instr2; . . . .

} Într-un bloc se pot declara şi variabile care pot fi accesate doar în corpul blocului. Instrucţiunea bloc este utilizată în locurile în care este necesară prezenţa unei singure instrucţiuni, însă procesul de calcul este mai complex, deci trebuie descris în mai multe secvenţe.

3.2. IMPLEMENTAREA STRUCTURII DE DECIZIE (ALTERNATIVE, DE SELECŢIE) Reprezentarea prin schemă logică şi prin pseudocod a structurilor de decizie şi repetitive sunt descrise în capitolul 1. Se vor prezenta în continure doar instrucţiunile care le implementează. Instrucţiunea if: Sintaxa: if (expresie) instrucţiune1; [ else instrucţiune2; ] 

Ramura else este opţională. La întâlnirea instrucţiunii if, se evaluează expresie (care reprezintă o condiţie) din paranteze. Dacă valoarea expresiei este 1 (condiţia este îndeplinită) se execută instrucţiune1; dacă valoarea expresiei este 0 (condiţia nu este îndeplinită), se execută instrucţiune2. Deci, la un moment dat, se execută doar una dintre cele două instrucţiuni: fie instrucţiune1, fie instrucţiune2. După execuţia instrucţiunii if se trece la execuţia instrucţiunii care urmează acesteia. Observaţii: 1.

Instrucţiune1 şi instrucţiune2 pot fi instrucţiuni compuse (blocuri), sau chiar alte instrucţiuni if

(if-uri imbricate). Deoarece instrucţiunea if testează valoarea numerică a expresiei (condiţiei), este posibilă prescurtarea: if (expresie), în loc de if (expresie != 0). 3. Deoarece ramura else a instrucţiunii if este opţională, în cazul în care aceasta este omisă din secvenţele if-else imbricate, se produce o ambiguitate. De obicei, ramura else se asociază ultimei instrucţiuni if. Exemplu: 2.

if (n rel="nofollow">0) if (a>b) z=a; else z=b; 42

CAPITOLUL 3 Implementarea structurilor de control 4. Pentru claritatea programelor sursă se recomandă alinierea instrucţiunilor prin utilizarea tabulatorului

orizontal. 5. Deseori, apare construcţia: if (expresie1) instrucţiune1; else if (expresie2) instrucţiune2; else if (expresie3) instrucţiune3; . . . . . . . . . else instrucţiune_n;

Aceeaşi construcţie poate fi scrisă şi astfel: if (expresie1) instrucţiune1; else if (expresie2) instrucţiune2; else if (expresie3) instrucţiune3; . . . . .. . . . . . else instrucţiune_n;

Expresiile sunt evaluate în ordine; dacă una dintre expresii are valoarea 1, se execută instrucţiunea corespunzătoare şi se termină întreaga înlănţuire. Ultima parte a lui else furnizează cazul când nici una dintre expresiile 1,2,. . ., n-1 nu are valoarea 1. 6. În cazul în care instrucţiunile din cadrul if-else sunt simple, se poate folosi operatorul condiţional. Exerciţii: 1. Să se citească de la tastatură un număr real. Daca acesta se află în intervalul [-1000, 1000], să se afiseze 1, dacă nu, să se afiseze -1. #include void main() { double nr; cout<<”Astept numar:”; cin>>nr; int afis = (nr>= -1000 && nr <= 1000 ? 1 : -1); cout<= -1000 && nr <= 10000) afis = 1; else afis= -1; cout<
2. Să se calculeze valoarea funcţiei f(x), ştiind că x este un număr real introdus de la tastatură: - 6x + 20 , dacă x ∈ [- ∞ , -7 ] f(x) = x + 30 , dacă x ∈ (-7, 0] , dacă x rel="nofollow">0 x #include Sau: #include <math.h> void main() { double x,f;cout<<”x=”;cin>>x; if (x <= -7) f= -x* 6 +20; else if ( x<=0 ) f= x+30; else f=sqrt(x); cout<<”f=”<
#include #include <math.h> void main() { double x,f;cout<<”x=”;cin>>x; if (x <= 7) f= -x* 6 +20; if (x>=-7 && x<=0 ) f= x+30; if (x>0) f=sqrt(x); cout<<”f=”<
Uneori, construcţia if-else este utilizată pentru a compara valoarea unei variabile cu diferite valori constante, ca în programul următor:

43

CAPITOLUL 3 control

Implementarea structurilor de

3. Se citeşte un caracter reprezentând un operator aritmetic binar simplu. În funcţie de caracterul citit, se afişează numele operaţiei pe care acesta o poate realiza. #include void main() { char oper; cout<<”Introdu operator aritmetic, simplu, binar:”; cin>>oper; if (oper == ’+’) cout<<”Operatorul de adunare!\n”; else if (oper==’-’ ) cout<<”Operatorul de scadere!\n”; else if (oper==’*’ ) cout<<”Operatorul de inmultire!\n”; else if (oper==’/’ ) cout<<”Operatorul de impartire!\n”; else if (oper==’%’ ) cout<<”Operatorul rest!\n”; else cout<<”Operator ilegal!!!\n”; }

Instrucţiunea switch În unele cazuri este necesară o decizie multiplă specială. Instrucţiunea switch permite acest lucru. 

Reprezentare prin schema logică (figura 3.2): Reprezentare prin pseudocod: test_expresie

instrucţiune1 break instrucţiune2

Dacă expresie=expr_const_1 instrucţiune1; [ieşire;] Altfel dacă expresie=expr_const_2 instrucţiune2; [ieşire;]

break

instrucţiune_n

Altfel dacă expresie=expr_const_n-1 instrucţiune_n-1; [ieşire;] Altfel instrucţiune_n;

Figura 3.2. Decizia multiplă Se testează dacă valoarea pentru expresie este una dintre constantele specificate (expr_const_1, expr_const_2, etc.) şi se execută instrucţiunea de pe ramura corespunzătoare. În schema logică test_expresie este una din condiţiile: expresie=expr_const_1, expresie=expr_const_2, etc. Sintaxa: switch (expresie) { case expresie_const_1: instructiune_1; [break;] case expresie_const_2: instructiune_2; [break;] . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . case expresie_const_n-1: instructiune_n-1; [break;]

44

CAPITOLUL 3 control

Implementarea structurilor de

[ default: instructiune_n; ] } Este evaluată expresie (expresie aritmetică), iar valoarea ei este comparată cu valoarea expresiilor constante 1, 2, etc. (expresii constante=expresii care nu conţin variabile). În situaţia în care valoarea expresie este egală cu valoarea expr_const_k, se execută instrucţiunea corespunzătoare acelei ramuri (instrucţiune_k). Dacă se întâlneşte instrucţiunea break, parcurgerea este întreruptă, deci se va trece la execuţia primei instrucţiuni de după switch. Dacă nu este întâlnită instrucţiunea break, parcurgerea continuă. Break-ul cauzează deci, ieşirea imediată din switch. În cazul în care valoarea expresiei nu este găsită printre valorile expresiilor constante, se execută cazul marcat cu eticheta default (când acesta există). Expresiile expresie, expresie_const_1, expresie_const_2,etc., trebuie să fie întregi. În exemplul următor, ele sunt de tip char, dar o dată de tip char este convertită automat în tipul int. Exerciţiu: Să rescriem programul pentru problema 3, utilizând instrucţiunea switch. #include void main() { char oper; cout<<”Introdu operator aritmetic, simplu, binar:”; cin>>oper; switch (oper) { case (’+’): cout<<”Operatorul de adunare!\n”; break; case (’-’): cout<<”Operatorul de scadere!\n”; break; case (’*’): cout<<” Operatorul de inmultire!\n”; break; case (’/’): cout<<”Operatorul de impartire!\n”; break; case (’%’): cout<<”Operatorul rest!\n”; break; default: cout<<”Operator ilegal!\n”; } }

3.3. IMPLEMENTAREA STRUCTURILOR REPETITIVE (CICLICE) Există două categorii de instrucţiuni ciclice: cu test iniţial şi cu test final.

3.3.1. Implementarea structurilor ciclice cu test iniţial Structura ciclică cu test iniţial este implementată prin instrucţiunile while şi for.  Instrucţiunea while Sintaxa: while (expresie) instructiune; La întâlnirea acestei instrucţiuni, se evaluează expresie. Dacă aceasta are valoarea 1 - sau diferită de 0 (condiţie îndeplinită), se execută instrucţiune. Se revine apoi în punctul în care se evaluează din nou

45

CAPITOLUL 3 control

Implementarea structurilor de

valoarea expresiei. Dacă ea este tot 1, se repetă instrucţiune, ş.a.m.d. Astfel, instrucţiunea (corpul ciclului) se repetă atât timp cât expresie are valoarea 1. În momentul în care expresie ia valoarea 0 (condiţie neîndeplinită), se iese din ciclu şi se trece la următoarea instrucţiune de după while. Observaţii: 1. În cazul în care la prima evaluare a expresiei, aceasta are valoarea zero, corpul instrucţiunii while nu va fi executat niciodată. 2. Instrucţiune din corpul ciclului while poate fi compusă (un bloc), sau o altă instrucţiune ciclică. 3. Este de dorit ca instrucţiunea din corpul ciclului while să modifice valoarea expresiei. Dacă nu se realizează acest lucru, corpul instrucţiunii while se repetă de un număr infinit de ori. Exemplu: int a=7; while (a==7) cout<<”Buna ziua!\n”;

// ciclu infinit; se repetă la infinit afişarea mesajului

Instrucţiunea for În majoritatea limbajelor de programare de nivel înalt, instrucţiunea for implementează structura ciclică cu număr cunoscut de paşi (vezi reprezentarea prin schema logică şi pseudocod din capitolul 1). În limbajul C instrucţiunea for poate fi utilizată într-un mod mult mai flexibil. 

Reprezentare prin schema logică (figura 3.3.):

Reprezentare în pseudocod:

evaluare expresie1 (particular iniţializare contor) expresie2

0

1 instrucţiune

evaluare expresie1 CÂT TIMP expresie2 REPETĂ ÎNCEPUT instrucţiune evaluare expresie3 SFÂRŞIT

evaluare expresie3 (particular incrementare contor) Figura 3.3. Structura ciclică cu test iniţial Sintaxa: for (expresie1; expresie2; expresie3) instructiune; Nu este obligatorie prezenţa expresiilor, ci doar a instrucţiunilor vide. Exemplu: for ( ; expresie2; ) sau: for ( ; ; ) instructiune;

instructiune;

3.3.2. Implementarea structurilor ciclice cu test final Instrucţiunea do-while Sintaxa: do instructiune; while(expresie); 

Se execută instrucţiune. Se evaluează apoi expresie. Dacă aceasta are valoarea 1, se execută instrucţiune. Se testează din nou valoarea expresiei. Se repetă instrucţiune cât timp valoarea 46

CAPITOLUL 3 control

Implementarea structurilor de

expresiei este 1 (condiţia este îndeplinită). În cazul instrucţiunii do-while, corpul ciclului se execută cel puţin o dată. Exerciţii: 1. Se citeşte câte un caracter, până la întâlnirea caracterului @. Pentru fiecare caracter citit, să se afişeze un mesaj care să indice dacă s-a citit o literă mare, o literă mică, o cifră sau un alt caracter. Să se afişeze câte litere mari au fost introduse, câte litere mici, câte cifre şi câte alte caractere. Se prezintă trei modalităţi de implementare (cu instrucţiunea while, cu instrucţiunea for şi cu instrucţiunea do-while). #include #include void main() { char c; clrscr(); int lmic=0, lmare=0, lcif=0; int altcar=0; cout<<"Aştept car.:"; cin>>c; while (c!='@'){ if (c>='A' && c<='Z') { cout<<"Lit. mare!\n"; lmare++; } else if (c>='a' && c<='z') { cout<<"Lit. mică!\n"; lmica++; } else if (c>='0' && c<='9') { cout<<"Cifră!\n"; lcif++; } else { cout<<"Alt car.!\n"; altcar++; } cout<<"Aştept car.:";cin>>c; } cout<<"Aţi introdus \n"; cout<
Observaţii legate de implementare Variabila c (tip char) memorează caracterul introdus la un moment dat, de la tastatură. Variabilele întregi lmic, lmare, lcif şi altcar sunt utilizate pe post de contor pentru litere mari, mici, cifre, respectiv alte caractere. Acţiunea care se repetă cât timp caracterul citit este diferit de constanta caracter '@' constă din mai multe acţiuni simple: citirea unui caracter (cu afişarea în prealabil a mesajului "Aştept car.:"; testarea caracterului citit (operatorii relaţionali pot fi aplicaţi datelor de tip char). Ca urmare, acţiunea din corpul instructiunii while a fost implementată printr-o instrucţiune bloc. Tot instrucţiuni bloc au fost utilizate pe fiecare ramură a instrucţiunii if (afişare mesaj referitor la caracter şi incrementare contor).

#include #include void main() { char c;clrscr(); intlmic=0,lmare=0,lcif=0;int altcar=0; cout<<"Aştept caract.:"; cin>>c; for( ; c!='@'; ){

Pentru implementarea aceluiaşi algoritm se poate utiliza instrucţiunea for. În cadrul acesteia, expresie1 şi expresie3 lipsesc, însă prezenţa instrucţiunilor vide este obligatorie.

// corp identic } cout<<"Aţi introdus \n"; cout<
O altă variantă de implementare poate fi următoarea, în care şi iniţializarea variabilelor contor se realizează în cadrul expresiei expresie1. int lmic, lmare, lcif, altcar; for(lmare=0, lmic=0, lcif=0, altcar=0; c!='@'; ){

// corp identic }

47

CAPITOLUL 3 control

Implementarea structurilor de

Variantă de implementare care utilizează instrucţiunea do-while: int lmic=0, lmare=0, lcif=0; int altcar=0; cout<<"Aştept caract.:";cin rel="nofollow">>c; do {

//corp do-while } while (c!='@'); cout<<"Aţi introdus \n";

//. . . 2.

Să se calculeze suma şi produsul primelor n numere naturale, n fiind introdus de la tastatură. Se vor exemplifica modalităţile de implementare cu ajutorul instrucţiunilor do-while, while, şi for. (Se n

observă că: S =

∑k , P = k =1

n

∏ k ). k =1

cout<<"n="; int n; cin>>n; int S=0, P=1, k=1; while (k <= n){ S+=k; P*=k; k++; } cout<<"P="<
cout<<"n="; int n; cin>>n; int S=0, P=1, k=1; do{ S+=k; P*=k; k++; } while (k <= n); cout<<"P="<
Pentru a ilustra multiplele posibilităţi oferite de instrucţiunea for, prezentăm variantele // varianta1

// varianta2

int S=0, P=1, k; for (k=1; k<=n; k++){ S+=k; P*=k; } cout<<"P="<
int S=0, P=1; for (int k=1; k<=n; k++){ S+=k; P*=k; } cout<<"P="<
// varianta3 for (int S=0, P=1, k=1; k<=n; k++){ S+=k; P*=k; } cout<<"P="<
// varianta4 for (int S=0, P=1, k=1; k<=n; S+=k, P*=k, k++) ; cout<<"P="<
3. Să se citească un şir de numere reale, până la întâlnirea numărului 900. Să se afişeze maximul numerelor citite. #include void main() Se presupune că primul element din şirul de {double n; numere are valoarea maximă. Se memorează cout<<"Introdu nr:"; cin>>n; valoarea sa în variabila max. Se parcurge apoi double max=n; şirul, comparându-se valoarea fiecărui element while (n!=900) cu valoarea variabilei max. În cazul în care se { if (n>=max) găseşte un element cu o valoare mai mare decât max=n; a variabilei max, se reţine noua valoare cout<<"Introdu nr:"; (max=n). cin>>n; } cout<<"Max şir este:"<<max<<'\n'; 4. Să} se afişeze literele mari ale alfabetului şi codurile aferente acestora în ordine crescătoare, iar literele

mici şi codurile aferente în ordine descrescătoare. Afişarea se va face cu pauză după fiecare ecran. 48

CAPITOLUL 3 control

Implementarea structurilor de

#include #include #define DIM_PAG 22 //dimensiunea paginii (numarul de randuri de pe o pagina) void main() {clrscr(); cout<<"LITERELE MARI:\n";int nr_lin=0; // nr_lin este contorul de linii de pe

un

ecran for (char LitMare='A'; LitMare<='Z'; LitMare++){ if (nr_lin==DIM_PAG){ cout<<"Apasa o tasta...."; getch(); clrscr(); nr_lin=0;} cout<<"Litera "<
// conversia explicita (int)LitMare permite afisarea codului ASCII al caracterului nr_lin++; } cout<<"LITERELE MICI:\n"; for (char LitMica='z'; LitMica>='a'; LitMica--){ if (nr_lin==DIM_PAG){ cout<<"Apasa o tasta...."; getch(); clrscr(); nr_lin=0;} cout<<"Litera "<
Să se scrie un program care realizează conversia numărului N întreg, din baza 10 într-o altă bază de numeraţie, b<10 (N şi b citite de la tastatură). Conversia unui număr întreg din baza 10 în baza b se realizează prin împărţiri succesive la b şi memorarea resturilor, în ordine inversă. De exemplu: 547:8=68 rest 3; 68:8=8 rest 4; 8:8=1 rest 0; 1:8=0 rest 1 547 10 = 1043 8 #include void main() { int nrcif=0,N,b,rest,Nv,p=1; long Nnou=0; cout<<"\nIntroduceti baza<10, b=";cin>>b; cout<<"Introduceti numarul in baza 10, nr=";cin>>N; Nv=N; while(N!=0) { rest=N%b; N/=b; cout<<"nr="<
6.

Să se calculeze seria următoare cu o eroare mai mică decât EPS (EPS introdus de la tastatură): 1+

xk ∑ k =1 k

, x ∈ [0,1], x citit de la tastatură. Vom aduna la sumă încă un termen cât timp diferenţa dintre suma calculată la pasul curent şi cea calculată la pasul anterior este mai mare sau egală cu EPS. #include #include #include <math.h> void main() { double T,S,S1;long k;k=1;T=1;S=T;double x; cout<<"x="; cin>>x;

// T= termenul general de la pasul curent; S=suma la pasul curent; S1=suma la pasul anterior do { S1=S;k=k+1;T=pow(x,k)/k; //funcţia pow(x, k), aflată în <math.h> calculează x k S=S+T; // cout<
CAPITOLUL 3 control } while ((S-S1)>=EPS); cout<<"Nr termeni="<
Implementarea structurilor de T="<
S="<<S<<'\n'; }

3.4. FACILITĂŢI DE ÎNTRERUPERE A UNEI SECVENŢE Pentru o mai mare flexibilitate (tratarea excepţiilor care pot apare în procesul de prelucrare), în limbajul C se utilizează instrucţiunile break şi continue. Ambele instrucţiuni sunt utilizate în instrucţiunile ciclice. În plus, instrucţiunea break poate fi folosită în instrucţiunea switch. Instrucţiunea break Aşa cum se observă din figura 3.4., utilizată în cadrul instrucţiunilor ciclice, instrucţiunea break "forţează" ieşirea din acestea. Fără a se mai testa valoarea expresiei (condiţia) care determină repetarea corpului instrucţiunii ciclice, se continuă execuţia cu instructiunea care urmează instructiunii ciclice. Astfel, se întrerupe repetarea corpului instrucţiunii ciclice, indiferent de valoarea condiţiei de test. Utilizarea în cadrul instrucţiunii switch: În situaţia în care s-a ajuns la o valoare a unei expresiei constante egală cu cea a expresiei aritmetice, se execută instrucţiunea corespunzătoare acelei ramuri. Dacă se întâlneşte instrucţiunea break, parcurgerea este întreruptă (nu se mai compară valoarea expresiei aritmetice cu următoarele constante), deci se va trece la execuţia primei instrucţiuni de după switch. Dacă nu este întâlnit break, parcurgerea continuă. Instrucţiunea breakl cauzează deci, ieşirea imediată din switch. 

Instrucţiunea continue Întâlnirea instrucţiunii continue (figura 3.4.) determină ignorarea instrucţiunilor care o urmează în corpul instrucţiunii ciclice şi reluarea execuţiei cu testarea valorii expresiei care determină repetarea sau nu a corpului ciclului. 

Exemplu: Să revenim la programul realizat pentru problema 1, care foloseşte instrucţiunea dowhile. Dacă primul caracter citit este chiar caracterul @, se realizează testarea acestuia; ca urmare, se afişează mesajul "Alt car.!" şi se incrementează valoarea contorului altcar. Dacă nu se doreşte ca acest caracter să fie testat şi numărat, în corpul instrucţiunii do while putem face un test suplimentar. int lmic=0,lmare=0,lcif=0,altcar=0;cout<<"Aştept caract.:";cin>>c; do { if (c == '@') break; //ieşire din do while

//corp do-while } while (c!='@'); cout<<"Aţi introdus \n"; //. . . do{

while (expresie1){ instructiune1; instructiune2; if (expresie2) break; else continue; instructiune3;

instructiune1; instructiune2; if (expresie2) break; else continue; instructiune3; } while (expresie1);

}

for (expr1; expr2; expr3)){ instructiune1; instructiune2; if (expresie2) break; else continue; instructiune3;

} Figura 3.4. Modul de utilizare a instrucţiunilor break şi continue 50

CAPITOLUL 3 control

Implementarea structurilor de

ÎNTREBĂRI ŞI EXERCIŢII Chestiuni teoretice 1. Care sunt instrucţiunile care implementează în limbajul C structura condiţională? 2. Care sunt instrucţiunile care implementează în limbajul C structura secvenţială? 3. Care sunt instrucţiunile care implementează în limbajul C structura repetitivă cu test iniţial?

4. Care sunt instrucţiunile care implementează în limbajul C structura repetitivă cu test final? 5. Ce deosebiri sunt între instrucţiunea while şi instrucţiunea do-while? 6. Pornind de la sintaxa instrucţiunii for, stabiliţi echivalenţa între aceasta şi instrucţiunile while şi do-while.

Chestiuni practice 1. 2. 3. 4.

Să se implementeze programele cu exemplele prezentate. Să se scrie programele pentru exerciţiile rezolvate care au fost prezentate. Să se implementeze algoritmii proiectaţi pentru problemele 1-7 din capitolul 1. Să se calculeze aria unui triunghi, cunoscându-se mărimea laturilor sale. Numerele care reprezintă mărimile laturilor vor fi introduse de utilizator. Se va testa mai întâi dacă cele 3 numere reprezentând mărimea laturilor pot forma un triunghi ( a <= b+c, b <= c+d, c <= a+b). 5. Să se rescrie următoarea secvenţă, folosind o singură instrucţiune if. if

(n<0) if (n>=90) if (x!=0) int b= n/x;

6. Să se citească un numar natural n. Să se scrie un program care afişează dacă numărul n citit reprezintă sau nu, un an bisect (anii bisecţi sunt multipli de 4, exceptând multiplii de 100, dar incluzând multiplii de 400). 7. Să se găsească toate numerele de două cifre care satisfac relaţia:

xy =( x +y ) 2 8. Să se citească un şir de numere reale, până la întâlnirea numarului 800 şi să se afişeze valoarea minimă introdusă, suma şi produsul elementelor şirului. 9. Scrieţi un program care să verifice inegalitatea 1/(n+1) < ln[(n+1)/n] < 1/n, unde n este un număr natural pozitiv, introdus de la tastatură. 10. Fie funcţia e x −3 , x ∈ [0, 1) Să se calculeze f(x), x citit de la tastatură. f(x)= sinx+cosx , x ∈ [1, 2) 0,9ln(x+3) , x ∈ [2, 100] 11. Să se scrie un program care calculează şi afişează maximul a 3 numere reale (a, b şi c) citite de la tastatură. 12. Să se scrie un program care calculează şi afişează minimul a 3 numere reale (a, b şi c) citite de la tastatură. 13. Să se citească 2 caractere care reprezintă 2 litere mari. Să se afişeze caracterele citite în ordine alfabetică. 14. Să se citească 3 caractere care reprezintă 3 litere mici. Să se afişeze caracterele citite în ordine alfabetică. 15. Să se scrie un program care citeşte o cifră. În funcţie de valoarea ei, să se facă următorul calcul: dacă cifra este 3, 5 sau 7 să se afişeze pătratul valorii numerice a cifrei; dacă cifra este 2, 4 sau 6 să se afişeze cubul valorii numerice a cifrei; dacă cifra este 0 sau 1 să se afişeze mesajul "Valori mici"; altfel., să se afişeze mesajul "Caz ignorat!". 16. Fie şirul lui Fibonacci, definit astfel: f(0)=0, f(1)=1, f(n)=f(n-1)+f(n-2) în cazul în care n>1. Să se scrie un program care implementează algoritmul de calcul al şirului Fibonacci. 17. Să se calculeze valoarea polinomului Cebîşev de ordin n într-un punct x dat, cunoscând relaţia: T 0 (x)=1, T 1 (x)=x şi T k +1 (x) - 2xT k (x) + T k −1 (x) = 0 18. Să se citească câte 2 numere întregi, până la întâlnirea perechii (0, 0). Pentru fiecare pereche de numere, să se calculeze şi să se afişeze cel mai mare divizor comun. 51

CAPITOLUL 3 control

Implementarea structurilor de

19. Se citesc câte 3 numere reale, până la întâlnirea numerelor 9, 9, 9. Pentru fiecare triplet de numere citit, să se afişeze maximul. 20. Se citeşte câte un caracter până la întâlnirea caracterului @. Să se afişeze numărul literelor mari, numarul literelor mici şi numărul cifrelor citite; care este cea mai mare (lexicografic) literă mare, literă mică şi cifră introdusă. 21. Se citesc câte 2 numere întregi, până la întâlnirea perechii de numere 9, 9. Pentru fiecare pereche de numere citite, să se afişeze cel mai mare divizor comun al acestora. 22. Să se calculeze suma seriei 1 + x 3 /3 - x 5 /5 + x 7 /7 - … cu o eroare mai mică decât epsilon (epsilon citit de la tastatură). Să se afişeze şi numărul de termeni ai sumei. 23. Să se citească un număr întreg format din 4 cifre (abcd). Să se calculeze şi să se afişeze valoarea expresiei reale: 4*a + b/20 -c + 1/d. 24. Să se scrie un program care afişează literele mari ale alfabetului în ordine crescătoare, iar literele mici - în ordine descrescătoare. 25. Să se scrie un program care generează toate numerele perfecte până la o limită dată, LIM. Un număr perfect este egal cu suma divizorilor lui, inclusiv 1 (exemplu: 6=1+2+3). 26. Să se calculeze valoarea sumei urmatoare, cu o eroare EPS mai mică de 0.0001: S=1+(x+1)/ 2! + (x+2)/ 3! + (x+3)/ 3! + ... , unde 0<=x<=1, x citit de la tastatură. 27. Să se genereze toate numerele naturale de 3 cifre pentru care cifra sutelor este egală cu suma cifrelor zecilor şi unităţilor. 28. Să se citească câte un număr întreg, până la întâlnirea numărului 90. Pentru fiecare numar să se afişeze un mesaj care indică dacă numărul este pozitiv sau negativ. Să se afişeze cel mai mic număr din şir. 29. Să se genereze toate numerele naturale de 3 cifre pentru care cifra zecilor este egală cu diferenţa cifrelor sutelor şi unităţilor. 30. Să se calculeze suma: (1 + 2!) / (2 + 3!) - (2+3!) / (3+4!) + (3+4!) / (4+5!) - .....

52

4

CAPITOLUL 4

Tablouri

TABLOURI 4.1. Declararea tablourilor 4.2. Tablouri unidimensionale

4.3. Tablouri bidimensionale 4.4. Şiruri de caractere

4.1. DECLARAREA TABOURILOR Numim tablou o colecţie (grup, mulţime ordonată) de date, de acelaşi tip, situate într-o zonă de memorie continuă (elementele tabloului se află la adrese succesive). Tablourile sunt variabile compuse (structurate), deoarece grupează mai multe elemente. Variabilele tablou au nume, iar tipul tabloului este dat de tipul elementelor sale. Elementele tabloului pot fi referite prin numele tabloului şi indicii (numere întregi) care reprezintă poziţia elementului în cadrul tabloului. În funcţie de numărul indicilor utilizaţi pentru a referi elementele tabloului, putem întâlni tablouri unidimensionale (vectorii) sau multidimensionale (matricile sunt tablouri bidimensionale). Ca şi variabilele simple, variabilele tablou trebuie declarate înainte de utilizare. Modul de declarare: tip nume_tablou[dim_1][dim_2]…[dim_n]; unde:tip reprezintă tipul elementelor tabloului; dim_1,dim_2,...,dim_n sunt numere întregi sau expresii constante întregi (a căror valoare este evaluată la compilare) care reprezintă limitele superioare ale indicilor tabloului. Exemple: //1 int vect[20]; // declararea tabloului vect, de maximum 20 de elemente, de tipul int. // Se rezervă 20*sizeof(int)=20 * 2 = 40 octeţi //2 double p,q,tab[10];

// declararea variabilelor simple p, q şi a vectorului tab, de maximum 10 elemente, tip double //3 #define MAX 10 char tabc[MAX];

/*declararea tabloului tabc, de maximum MAX (10) elemente de tip char*/

//4 double matrice[2][3];

// declararea tabloului matrice (bidimensional), // maximum 2 linii şi maximum 3 coloane, tip double

4.2. TABLOURI UNIDIMENSIONALE Tablourile unidimensionale sunt tablouri cu un singur indice (vectori). Dacă tabloul conţine dim_1 elemente, indicii elementelor au valori întregi din intervalul [0, dim_1-1]. La întâlnirea declaraţiei unei variabile tablou, compilatorul alocă o zonă de memorie continuă (dată de produsul dintre dimensiunea maximă şi numărul de octeţi corespunzător tipului tabloului) pentru păstrarea valorilor elementelor sale. Numele tabloului poate fi utilizat în diferite expresii şi valoarea lui este chiar adresa de început a zonei de memorie care i-a fost alocată. Un element al unui tablou poate fi utilizat ca orice altă variabilă (în exemplul următor, atribuirea de valori elementelor tabloului vector). Se pot efectua operaţii asupra fiecărui element al tabloului, nu asupra întregului tablou. 53

vector CAPITOLUL 4

Tablouri

Exemplu: // Declararea tabloului vector int vector[6];

// Iniţializarea elementelor tabloului vector[0]=100; vector[1]=101; vector[2]=102; vector[3]=103; vector[4]=104; vector[5]=105;

Exemplu: double alpha[5], beta[5], gama[5]; int i=2; alpha[2*i-1] = 5.78; alpha[0]=2*beta[i]+3.5; gama[i]=aplha[i]+beta[i]; //permis gama=alpha+beta; //nepermis

Variabilele tablou pot fi iniţializate în momentul declarării: declaraţie_tablou=listă_valori; Valorile din lista de valori sunt separate prin virgulă, iar întreaga listă este inclusă între acolade: Exemple: //1 int vector[6]={100,101,102,103,104,105};

[0]

[5]

//2 double x=9.8; double a[5]={1.2, 3.5, x, x-1, 7.5};

La declararea unui vector cu iniţializarea elementelor sale, numărul maxim de elemente ale tabloului poate fi omis, caz în care compilatorul determină automat mărimea tabloului, în funcţie de numărul elementelor iniţializate. Exemplu: char tab[]={ ’A’, ’C’, ’D’, ’C’};

[0]

[3]

float data[5]={ 1.2, 2.3, 3.4 };

[0] [4] Adresa elementului de indice i dintr-un tablou unidimensional poate fi calculată astfel: adresa_elementului_i = adresa_de_bază + i ∗ lungime_element Exerciţii: //1 Citirea elementelor unui vector: double a[5]; int i; for (i=0; i<5; i++) 54

Q= CAPITOLUL 4 {

prealabil

Tablouri

citirii

cout<<”a["<<<”]=”;

//afişarea

unui

mesaj

fiecărui element

//citirea valorii elementului de indice i

cin>>a[i]; }

//Sau: double a[20]; int i, n; cout<<”Dim. Max. =”; cin>>n; for (i=0; i>a[i]; }

//2 Afişarea elementelor unui vector:

cout<<”Vectorul introdus este:\n”; for (i=0; i
//3 Afişarea elementelor unui vector în ordine inversă: cout<<”Elementele vectorului în ordine inversă:\n”; for (i=n-1; i rel="nofollow">=0 i++) cout<
∑ a ∗b

p= i = 0

i

i

double p=0; for (i=0; i
4.2. TABLOURI BIDIMENSIONALE Din punct de vedere conceptual, elementele unui tablou bidimensional sunt plasate în spaţiu pe două direcţii. Matricea reprezintă o aplicaţie naturală a tablourilor bidimensionale. În matematică:

Q=

q 11

q 12

q 13

...

q 1n

q 21

q 22

q 23

...

q 2n

.......................... q m1

q m2

q m3

...

Q m× n

q mn

În limbajele C/C++ (indicii de linie şi de coloană pornesc de la 0): q 00

q 01

q 02

...

q 0,n−1

q 10 q 11 q 12 ... q 1,n−1 ............................ q m−1, 0 q m−1,1 q m−1, 2 . . .

Q m× n

q m−1,n −1

Exemplu: double q[3][2];

// declararea matricii q, cu maxim3 linii şi 2 coloane, tip double 55

CAPITOLUL 4

Tablouri

În memorie, elementele unei matrici sunt memorate pe linii: q 00 q 01 q 10 q 11 q 20 q 21 ... Dacă notăm cu k poziţia în memorie a unui element, valoarea lui k = i ∗ m + j (unde m este numărul maxim de linii, i este indicele de linie, j este indicele de coloană). Dacă se doreşte iniţializarea elementelor unei matrici în momentul declarării acesteia, se poate proceda astfel: int mat[4][3] = { {10, -50, 3}, {32, 20, 1}, {-1, 1, -2}, {7, -8, 19} };

Prin această construcţie, elementele matricii mat se iniţializează în modul următor: mat[0][0]=10, mat[0][1]=-50, mat[0][2]=3 mat[1][0]=32, mat[1][1]=20, mat[1][2]=1 mat[2][0]=-1, mat[2][1]=1, mat[2][2]=-2 mat[3][0]=7, mat[3][1]=-8, mat[3][2]=19 La declararea unei matrici şi iniţializarea elementelor sale, se poate omite numărul maxim de linii, în schimb, datorită modului de memorare, trebuie specificat numărul maxim de coloane: int mat[][3] = { {10, -5, 3}, {32, 20, 1}, {-1, 1, -2}, {7, -8, 9} };

Construcţia are acelaşi efect ca precedenta. int mat[][3] = { {1, 1}, { -1}, {3, 2, 1}};

mat reprezintă o matrice 3 ∗ 3, ale cărei elemente se iniţializează astfel: mat[0][0]=1, mat[0][1]=1, mat[1][0]=-1, mat[2][0]=3, mat[2][1]=2, mat[2][2]=1 Elementele mat[0][2], mat[1][1], mat[1][2] nu sunt initalizate. Ele au valoarea zero dacă tabloul este global şi valori iniţiale nedefinite dacă tabloul este automatic. Construcţiile utilizate la iniţializarea tablourilor bidimensionale se extind pentru tablouri multidimensionale, cu mai mult de doi indici. Exemplu: int a[2][2][3]={ { {10, 20}, {1, -1}, {3, 4}}, { {20, 30}, {50, -40}, {11, 12}} };

Exerciţiu: Să se citească de la tastatură elementele unei matrici de maxim 10 linii şi 10 coloane. Să se afişeze matricea citită. #include void main(void) {int A[10][10]; int nr_lin, nr_col; cout<<"Nr. linii:"; cin>>nr_lin; cout<<"Nr. coloane:"; cin>>nr_col;int i, j;

//citirea elementelor unei matrici for (i=0; i. strlen (nume_şir) Returnează un număr întreg ce reprezintă lungimea unui şir de caractere, fără a număra terminatorul de şir. strcmp (şir_1, şir_2) Funcţia compară cele două şiruri date ca argument şi returnează o valoare întreagă egală diferenţa dintre codurile ASCII ale primelor caractere care nu coincid. strcpy (şir_destinaţie, şir_sursă) Funcţia copie şirul sursă în şirul destinaţie. Pentru a fi posibilă copierea, lungimea şirului destinaţie trebuie să fie mai mare sau egală cu cea a şirului sursă, altfel pot 57

CAPITOLUL 4

Tablouri

apare erori grave. strcat (şir_destinaţie, şir_sursă) Funcţia concatenează cele două şiruri: şirul sursă este adăugat la sfârşitul şirului destinaţie. Tabloul care conţine şirul destinaţie trebuie să aibă suficiente elemente. Exemplu: #include #include <string.h> void main() { char sir1[] = ”abcd”, sir2[] = ”abcde”, sir3 = "abcdef”, sir4 = "de”; cout<<strcmp(sir1, sir2)<<’\n’; // afişare: -101

// ’e’ = 101, ’a’ = 97, ’d’ = 100 //’0’ - ’e’ = -101 //afişare: 101 //compararea variabilei sir1 cu constanta

cout<<strcmp(sir2, sir1)<<’\n’; cout<<strcmp(sir1, "")<<’ ';

şir vid

char str1[20]=”hello”; char str2[20]=”goodbye”; char str3[20]; int difer, lungime; cout<<”str1=”<<str1<<” str2=”<<str2<<’\n’; difer=strcmp(str1, str2); if (difer == 0) cout<<”Siruri echivalente!\n”; else if (difer>0) cout<<str1<<” mai mare (lexicografic) decât “<<str2<<’\n’; else cout<<str1<<” mai mic (lexicografic) decât “<<str2<<’\n’; cout<<”str1=”<<str1<<’\n’; cout<<”str3=”<<str3<<’\n’; strcpy (str3, str1); cout<<”str1=”<<str1<<’\n’; cout<<”str3=”<<str3<<’\n’; strcat (str3, str1); cout<<”str1=”<<str1<<’\n’; cout<<”str3=”<<str3<<’\n’; }

Exemplu: Să se citească elementele unui vector cu maxim 100 de elemente reale. a) Să se interschimbe elementele vectorului în modul următor: primul cu ultimul, al doilea cu penultimul, etc. b) Să se ordoneze crescător elementele vectorului. // a) #define FALSE 0 #define TRUE 1 #include void main() { double vect[100];int n;//n-numarul de elemente ale vectorului cout<<"Nr. elemente"; cin>>n; double aux;

// de completat exemplul cu secventa de citire a elementelor vectorului for (int i=0; i
// de completat exemplul cu secventa de afisare a vectorului }

Pentru schimbarea elementelor vectorului s-a folosit variabila auxiliară aux (figura 4.2.). Fără

58

CAPITOLUL 4

Tablouri

această variabilă, la atribuirea vect[i]=vect[n-1-i], valoarea elementului vect[i] s-ar fi pierdut. Trebuie observat, deasemenea, că variabila contor i ia valori între 0 şi n/2 (de exemplu, dacă vectorul are 4 sau 5 elemente sunt necesare 2 interschimbări). b) Pentru ordonarea elementelor vectorului, este prezentat un algoritmi de sortare. Metoda Bubble Sort compară fiecare element al vectorului cu cel vecin, iar dacă este cazul, le schimbă între ele. ALGORITM Bubble_Sort INCEPUT gata ← false CIT TIMP not gata REPETA INCEPUT gata = true PENTRU i=0 LA n-2 REPETA INCEPUT DACA vect[i] > vect[i+1] ATUNCI ` INCEPUT aux=vect[i] vect[i]=vect[i+1] vect[i+1]=aux gata=fals SFARSIT SFARSIT SFARSIT SFARSIT

// implementarea metodei BubbleSort int gata =FALSE;int i; while (!gata){ gata=TRUE; for (i=0; i<=n-2; i++) if (vect[i]>vect[i+1]){ aux=vect[i]; vect[i]=vect[i+1]; vect[i+1]=aux; gata=FALSE;} }

Exemplu: Să se citească elementele matricilor A(MXN), B(NXP) şi C(MXN), unde M<=10, N<=10 şi P<=10. Să se interschimbe liniile matricii A în modul următor: prima cu T

ultima, a doua cu penultima, etc. Să se calculeze şi să se afişeze matricile: AT=A , SUM=A+C, PROD=AXB. Implementarea citirilor şi afişărilor se va completa conform exemplului dat în capitolul 4.2. #include void main() { double a[10][10], b[10][10], c[10][10]; int m,n,p,j; cout<<"m="; cin>>m; cout<<"n="; cin>>n; cout<<"p="; cin>>p; // de completat secvenţa de citire a elementelor matricii a, cu m linii şi n coloane // de completat secvenţa de citire a elementelor matricii b, cu n linii şi p coloane // de completat secvenţa de afişare a matricii a

//interschimbarea liniilor matricii A: for (i=0; i<m/2; i++) for (j=0; j
CAPITOLUL 4

Tablouri

// calculul matricii AT =A

T

double at[10][10]; // at este matricea transpusa for (i=0; i
// calculul matricii SUM=A+C, SUM(MxN): double sum[10][10]; // sum este matricea suma dintre a si c for (i=0; i<m; i++) for (j=0; j
// de completat secvenţa de afişare a matricii sum double prod[10][10]; // prod este matricea produs dintre a si b

for (i=0; i<m; i++) for (j=0; j
Se observă că fiecare element din matricea produs PROD=AXB ( A(MXN), B(NXP) ), n −1

PROD(MXP) este de forma:

prod

i, j

=

∑a k =0

i ,k

* bk , j

, unde i= 0, m − 1 şi j= 0, n − 1 .

ÎNTREBĂRI ŞI EXERCIŢII Chestiuni teoretice

60

CAPITOLUL 4

Tablouri

1. Care este diferenţa dintre şirurile de caractere şi vectorii de caractere? 2. Ce sunt tablourile? 3. De ce tablourile reprezintă date

structurate? 4. Prin ce se referă elementele unui tablou? 5. Cine impune tipul unui tablou?

53

CAPITOLUL 4

Tablouri

Chestiuni aplicative 1. 2. 3. a) b) c) d) e) f) g)

Să se implementeze programele cu exemplele prezentate. Să se scrie programele pentru exerciţiile rezolvate care au fost prezentate. Se citesc de la tastatura elementele unei matrici de caractere (nr. linii=nr. coloane), A(NXN), N<=10. Să se afişeze matricea A; Să se formeze şi să se afişeze cuvântul format din caracterele pe pe diagonala principală a matricii A; Să se calculeze şi să se afişeze numărul de litere mari, litere mici şi cifre din matrice; Să se afişeze cuvântul format din caracterele de pe diagonala secundară; Să se afişeze procentul literelor mari, al literelor mici şi al cifrelor de pe cele 2 diagonale; Să se afişeze caracterele comune aflate pe liniile p şi q (p, q < N, p şi q citite de la tastatură); Să se afişeze în ordine alfabetică, crescătoare, literele mari aflate pe coloanele impare.

4. Se citesc de la tastatură elementele unei matrici cu elemente reale, B (N X N), N<=8. a) Să se afişeze matricea B; b) Să se calculeze şi să se afişeze produsul elementelor de pe coloanele impare; T

2

Să se calculeze şi să se afişeze matricea A, unde: A=(B+ B ) ; d) Să se formeze şi să se afişeze vectorul V, ale cărui elemente sunt elementele pozitive din matricea A; e) Să se calculeze şi să se afişeze sumele şi produsele elementelor matricii A, aflate în triunghiurile haşurate: c)

f) Să se calculeze procentul elementelor pozitive aflate pe diagonala secundară; g) Să se calculeze şi să se afişeze matricea C, unde:

T

C= 2

h) Să se calculeze şi să se afişeze matricea D, unde:

2

3*B + B ; 3

4

D=B +B +B +B ; i) Să se interschimbe coloanele matricii A astfel: prima cu ultima, a doua cu antipenultima, etc. 5. Se citesc de la tastatură elementele unei matrici de numere întregi C (N X N), N<=10. a) Să se afişeze matricea C; b) Să se calculeze şi să se afişeze procentul elementelor impare de pe liniile pare; c)

Să se calculeze şi să se afişeze matricea B, unde:

d) Să se calculeze şi să se afişeze matricea E, unde:

2

B=C ; T

E = (C + C )

2

+ I, unde I este matricea unitate; e) Să se afle elementul minim din matricea C; f) Să se înlocuiască elementul maxim din matricea C cu valoarea val, introdusă de la tastatură; g) Să se afişeze elementele matricii C care sunt numere prime; h) Să se calculeze şi să se afişeze sumele şi produsele elementelor matricii A, aflate în triunghiurile haşurate:

53

CAPITOLUL 5

Pointeri

5

POINTERI 5.1.Variabile pointer 5.1.1. Declararea variabilelor pointer 5.1.2. Iniţializarea variabilelor pointer 5.1.3. Pointeri generici 5.2. Operaţii cu pointeri 5.3. Pointeri şi tablouri

5.3.1. Pointeri şi şiruri de caractere 5.3.2. Pointeri şi tablouri bidimensionale 5.4. Tablouri de pointeri 5.5. Pointeri la pointeri 5.6. Modificatorul const în declararea pointerilor

5.1. VARIABILE POINTER Pointerii sunt variabile care au ca valori sunt adresele altor variabile (obiecte). Variabila este un nume simbolic utilizat pentru un grup de locaţii de memorie. Valoarea memorată într-o variabilă pointer este o adresă. Din punctul de vedere al conţinutului zonei de memorie adresate, se disting următoarele categorii de pointeri:  pointeri de date (obiecte) - conţin adresa unei variabile din memorie;  pointeri generici (numiţi şi pointeri void) - conţin adresa unui obiect oarecare, de tip neprecizat;  pointeri de funcţii (prezentaţi în capitolul 6.11.)- conţin adresa codului executabil al unei funcţii. Variabilă x

Nume variabilă

5

Valoare

1024

Variabilă pointer ptrx

1024

adresă Figura 5.1. Variabile pointer

1028

În figura 5.1, variabila x este memorată la adresa 1024 şi are valoarea 5. Variabila ptrx este memorată la adresa de memorie 1028 şi are valoarea 1024 (adresa variabilei x). Vom spune că ptrx pointează către x, deoarece valoarea variabilei ptrx este chiar adresa de memorie a variabilei x.

5.1.1. DECLARAREA VARIABILELOR POINTER Sintaxa declaraţiei unui pointer de date este: tip ∗ identificator_pointer; Simbolul ∗ precizează că identificator_pointer este numele unei variabile pointer de date, iar tip este tipul obiectelor a căror adresă o va conţine. Exemplu: int u, v, ∗ p, ∗ q; // ∗ p, ∗ q sunt pointeri de date (către int) double a, b, ∗ p1, ∗ q1; // ∗ p1, ∗ q1 sunt pointeri către date de tip double Pentru pointerii generici, se foloseşte declaraţia: void ∗ identificator_pointer; Exemplu: void

∗ m;

Aceasta permite declararea unui pointer generic, care nu are asociat un tip de date precis. Din acest motiv, în cazul unui pointer vid, dimensiunea zonei de memorie adresate şi interpretarea informaţiei nu sunt definite, iar proprietăţile diferă de ale pointerilor de date.

5.1.2. INIŢIALIZAEA VARIABILELOR POINTER 61

CAPITOLUL 5

Pointeri

Există doi operatori unari care permit utilizarea variabilelor pointer:  & - operatorul adresă (de referenţiere) - pentru aflarea adresei din memorie a unei variabile;  ∗ - operatorul de indirectare (de deferenţiere) - care furnizează valoarea din zona de memorie spre care pointează pointerul operand. În exemplul prezentat în figura 5.1, pentru variabila întreagă x, expresia &x furnizează adresa variabilei x. Pentru variabila pointer de obiecte int, numită ptr, expresia ∗ ptr înseamnă conţinutul locaţiei de memorie a cărei adresă este memorată în variabila ptr. Expresia ∗ ptr poate fi folosită atât pentru aflarea valorii obiectului spre care pointează ptr, cât şi pentru modificarea acesteia (printr-o operaţie de atribuire). Exemplu: int x, y,

∗ ptr;

// ptr- variabilă pointer către un int; x,y-variabile predefinite, simple, de tip int x=5; cout<<”Adresa variabilei x este:”<<&x<<’\n’; cout<<”Valoarea lui x:”<<x<<’\n’; ptr=&x; // atribuire: variabila ptr conţine adresa variabilei x cout<<”Variabila pointer ptr are valoarea:”<
// x si ∗ ptr reprezinta acelasi obiect, un intreg cu valoarea 4 x=70; // echivalenta cu ∗ ptr=70; y=x+10; // echivalenta cu y= ∗ ptr+10

În exemplul anterior, atribuirea ptr=&x se execută astfel: operatorul & furnizează adresa lui x; operatorul = atribuie valoarea (care este o adresă) variabilei pointer ptr. Atribuirea y= ∗ ptr se realizează astfel: operatorul ∗ accesează conţinutul locaţiei a cărei adresă este conţinută în variabila ptr; operatorul = atribuie valoarea variabilei y. Declaraţia int ∗ ptr; poate fi, deci, interpretată în două moduri, ambele corecte:  ptr este de tipul int ∗ ( ptr este de tip pointer spre int)  ∗ ptr este de tipul int (conţinutul locaţiei spre care pointează variabila ptr este de tipul int) Construcţia tip ∗ este de tipul pointer către int. Atribuirea x=8;este echivalentă cu ptr=&x; ∗ p=x; Variabilele pointer, alături de operatorii de referenţiere şi de deferenţiere, pot apare în expresii. Exemple:

int x, y, ∗ q; q=&x; // echivalentă cu x=8; ∗ q=8; q=&5; // invalidă - constantele nu au adresă // invalidă - x nu este variabilă pointer ∗ x=9; x=&y; //invalidă: x nu este variabilă pointer, deci nu poate fi folosită cu operatorul de indirectare y= ∗ q + 3; // echivalentă cu y=x+3; q = 0; // setează x pe 0 ∗ // echivalentă cu ( ∗ q)++ sau cu x++ ∗ q += 1; int ∗ r; r = q;

/* copiază conţinutul lui q (adresa lui x) în r, deci r va pointa tot către x (va conţine tot adresa lui x)*/ double w, ∗ r = &w, ∗ r1, ∗ r2; r1= &w; r2=r1; cout<<”r1=”<
5.1.3. POINTERI GENERICI 62

CAPITOLUL 5

Pointeri

La declararea pointerilor generici ( void ∗ nume; ) nu se specifică un tip, deci unui pointer void i se pot atribui adrese de memorie care pot conţine date de diferite tipuri: int, float, char, etc. Aceşti pointeri pot fi folosiţi cu mai multe tipuri de date, de aceea este necesară folosirea conversiilor explicite prin expresii de tip cast, pentru a preciza tipul datei spre care pointează la un moment dat pointerul generic. Exemplu:

void ∗ v1, ∗ v2; int a, b, ∗ q1, ∗ q2; q1 = &a; q2 = q1; v1 = q1; q2 = v1; // eroare: unui pointer cu tip nu i se poate atribui un pointer generic q2 = (int ∗ ) v1; double s, ∗ ps = &s; int c, ∗ l; void ∗ sv; l = (int ∗ ) sv; ps = (double ∗ ) sv; /*Interpretare: adresa la care se găseşte valoarea lui sv este ∗ (char ∗ ) sv = 'a';

interpretată ca fiind adresa zonei de memorie care conţine o data de tip char. */

Pe baza exemplului anterior, se pot face observaţiile: 1. Conversia tipului pointer generic spre un tip concret înseamnă, de fapt, precizarea tipului de pointer pe care îl are valoarea pointerului la care se aplică conversia respectivă. 2. Conversia tipului pointer generic asigură o flexibilitate mai mare în utilizarea pointerilor. 3. Utilizarea în mod abuziv a pointerilor generici poate constitui o sursă de erori.

5.2. OPERAŢII CU POINTERI În afara operaţiei de atribuire (prezentată în paragraful 5.1.2.), asupra variabilelor pointer se pot realiza operaţii de comparare, adunare şi scădere (inclusiv incrementare şi decrementare). 

Compararea valorilor variabilelor pointer Valorile a doi pointeri pot fi comparate, folosind operatorii relaţionali, ca în exemplul: Exemplu:

int ∗ p1, ∗ p2; if (p1=”<<”p2=”<
O operaţie uzuală este compararea unui pointer cu valoarea nulă, pentru a verifica dacă acesta adresează un obiect. Compararea se face cu constanta simbolică NULL (definită în header-ul stdio.h) sau cu valoarea 0. Exemplu: if (!p1) // sau if (p1 != NULL) . . . . . ; // pointer nul else . . . . ; // pointer nenul 

Adunarea sau scăderea Sunt permise operaţii de adunare sau scădere între un pointer de obiecte şi un întreg: Astfel, dacă ptr este un pointer către tipul tip (tip ∗ ptr;), iar n este un întreg, expresiile ptr + n şi ptr - n au ca valoare, valoarea lui ptr la care se adaugă, respectiv, se scade n ∗ sizeof(tip). Un caz particular al adunării sau scăderii dintre un pointer de date şi un întreg (n=1) îl reprezintă incrementarea şi decrementarea unui pointer de date. În expresiile ptr++, respectiv ptr--, valoarea variabilei ptr devine ptr+sizeof(tip), respectiv, ptr-sizeof(tip). Este permisă scăderea a doi pointeri de obiecte de acelaşi tip, rezultatul fiind o valoare întreagă care reprezintă diferenţa de adrese divizată prin dimensiunea tipului de bază. 63

CAPITOLUL 5

Pointeri

Exemplu:

int a, ∗ pa, ∗ pb; cout<<”&a=”<<&a<<’\n’; pa=&a; cout<<”pa=”<<pa<<’\n’; cout<<”pa+2”<<pa+2<<’\n’; pb=pa++; cout<<”pb=”<
5.3. POINTERI ŞI TABLOURI În limbajele C/C++ există o strânsă legătură între pointeri şi tablouri, deoarece numele unui tablou este un pointer (constant!) care are ca valoare adresa primului element din tablou. Diferenţa dintre numele unui tablou şi o variabilă pointer este aceea că unei variabile de tip pointer i se pot atribui valori la execuţie, lucru imposibil pentru numele unui tablou. Acesta are tot timpul, ca valoare, adresa primului său element. De aceea, se spune că numele unui tablou este un pointer constant (valoarea lui nu poate fi schimbată). Numele unui tablou este considerat ca fiind un rvalue (right value-valoare dreapta), deci nu poate apare decât în partea dreaptă a unei expresii de atribuire. Numele unui pointer (în exemplul următor, ∗ ptr) este considerat ca fiind un lvalue (left value-valoare stânga), deci poate fi folosit atât pentru a obţine valoarea obiectului, cât şi pentru a o modifica printr-o operaţie de atribuire. Exemplu: int a[10], a = a + 1; ptr = a ;

∗ ptr;

// a este definit ca &a[0]; a este pointer constant // ilegal // legal: ptr are aceeaşi valoare ca şi a, respectiv adresa elementului a[0] // ptr este variabilă pointer, a este constantă pointer. // echivalent cu x = ∗ ptr; se atribuie lui x valoarea lui a[0]

int x = a[0];

Deoarece numele tabloului a este sinonim pentru adresa elementului de indice zero din tablou, asignarea ptr=&a[0] poate fi înlocuită, ca în exemplul anterior, cu ptr=a.

5.3.1. POINTERI ŞI ŞIRURI DE CARACTERE Aşa cum s-a arătat în capitolul 4, un şir de caractere poate fi memorat într-un vector de caractere. Spre deosebire de celelalte constante, constantele şir de caractere nu au o lungime fixă, deci numărul de octeţi alocaţi la compilare pentru memorarea şirului, variază. Deoarece valoarea variabilelor pointer poate fi schimbată în orice moment, cu multă uşurinţă, este preferabilă utilizarea acestora, în locul tablourilor de caractere (vezi exemplul următor). Exemplu: char sir[10]; sir = ”hello”; psir = ”hello”;

char

∗ psir;

// ilegal // legal Operaţia de indexare a elementelor unui tablou poate fi realizată cu ajutorul variabilelor pointer. Exemplu: int a[10], ptr = a;

∗ ptr; // a este pointer constant; ptr este variabilă pointer

// ptr este adresa lui a[0] ptr+i înseamnă ptr+(i ∗ sizeof(int)), deci: ptr + i ⇔ & a[i] Deoarece numele unui tablou este un pointer (constant), putem concluziona (figura 5.2): a+i ⇔ & a[i] a[i] ⇔ ∗ (a+i) a[0]

a[1]

. . . .

a[9]

a a=&a[0] a+1=&a[1] a=a[0] (a+1)=a[1]

ptr

.64. .

. . .

a+9=&a[9] (a+9)=a[9]

Figura 5.2. Legătura dintre pointeri şi tablouri

CAPITOLUL 5

Pointeri

Exerciţiu: Să se scrie următorul program (care ilustrează legătura dintre pointeri şi vectori) şi să se urmărească rezultatele execuţiei acestuia. #include void main(void) {int a[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; int int ∗ pi2 = &a[0]; int ∗ pi3; cout<<”a=”<
∗ pi1 = a ;

/* x primeşte valoarea locaţiei a carei adresă se află în variabila pointer pi1, deci valoarea lui a[0] */ cout<<”x=”<<x<<’\n’; x= ∗ pi1++; // echivalent cu ∗ (pi1++) x=1 cout<<”x=”<<x<<’\n’; x=( ∗ pi1)++;

/* x=0: întâi atribuirea, apoi incrementarea valorii spre care pointează pi1. În urma incrementării, valoarea lui a[0] devine 1 */ cout<<”x=”<<x<<’\n’; cout<< ∗ pi1<<’\n’;x= ∗ ++pi1; //echivalent cu ∗ (++pi1) cout<<”x=”<<x<<’\n’; x=++( ∗ pi1); cout<<”x=”<<x<<’\n’; pi1=a; pi3=pi1+3; cout<<”pi1=”<
Exerciţiu: Să se scrie următorul program (legătura pointeri-şiruri de caractere) şi să se urmărească rezultatele execuţiei acestuia. #include void main(void) {int a=-5, b=12, ∗ pi=&a; double u=7.13, v=-2.24, ∗ pd=&v; char sir1[]=”sirul 1”, sir2[]=”sirul 2”, ∗ psir=sir1; cout<<”a=”<
// *sir1=s sir1=sirul 1 &sir1=0xffd6

cout<<” ∗ sir2=”<< ∗ sir2<<” sir2=”<<sir2<<” &sir2=”<<&sir2<<’\n’;

// *sir2=s sir2=sirul 2 &sir1=0xffce

cout<<” ∗ psir=”<< ∗ psir<<” psir=”<
// *psir=s psir=sirul 1 &sir1=0xffcc

cout<<”sir1+2=”<<(sir1+2)<<” psir+2=”<<(psir+2)<<’\n’;

// sir1+2=rul 1 psir+2=rul 1 cout<<” ∗ (sir1+2)=”<<

∗ (sir1+2)<<’\n’;

// *(sir1+2)=r valoarea elementului de indice 2

void ∗ pv1, ∗ pv2; pv1=psir; pv2=sir1; cout<<”pv1=”<
CAPITOLUL 5 }

Pointeri

Exerciţiu: Să se scrie un program care citeşte elementele unui vector de întregi, cu maxim 20 elemente şi înlocuieşte elementul maxim din vector cu o valoare introdusă de la tastatură. Se va folosi aritmetica pointerilor. #include void main() { int a[20]; int n, max, indice; cout<<”Nr. elemente:”; cin>>n; for (i=0; i> ∗ (a+i);}

// citirea elementelor vectorului

max= ∗ a; indice=0; for (i=0; i
// aflarea valorii elementului maxim din vector şi a poziţiei acestuia int val; cout<<”Valoare de inlocuire:”; cin >> val; ∗ (a+indice)=val;

// citirea valorii cu care se va înlocui elementul maxim for (i=0; i
// afişarea noului vector /* în acest mod de implementare, în situaţia în care în vector există mai multe elemente a căror valoare este egală cu valoarea elementului maxim, va fi înlocuit doar ultimul dintre acestea (cel de indice maxim).*/ }

5.3.2. POINTERI ŞI TABLOURI MULTIDIMENSIONALE Elementele unui tablou bidimensional sunt păstrate tot într-o zonă continuă de memorie, dar inconvenientul constă în faptul că ne gândim la aceste elemente în termeni de rânduri (linii) şi coloane (figura 5.3). Un tablou bidimensional este tratat ca un tablou unidimensional ale cărui elemente sunt tablouri unidimensionale. int M[4][3]={ {10, 5, -3}, {9, 18, 0}, {32, 20, 1}, {-1, 0, 8} };

Compilatorul tratează atât M, cât şi M[0], ca tablouri de mărimi diferite. Astfel: cout<<”Marime M:”<<sizeof(M)<<’\n’; // 24 = 2octeţi ∗ 12elemente cout<<”Marime M[0]”<<sizeof(M[0])<<’\n’; // 6 = 2octeţi ∗ 3elemente cout<<”Marime M[0][0]”<<sizeof(M[0][0])<<’\n’; // 4 octeţi (sizeof(int)) Matricea M M[0]

10

5

-3

M[1]

9

18

0

M[2]

32

20

1

M[3]

-1

0

8

Matricea M are 4 linii şi 3 coloane. Numele tabloului bidimensional, M, referă întregul tablou; M[0] referă prima linie din tablou; M[0][0] referă primul element al tabloului.

Figura 5.3. Matricea M Aşa cum compilatorul evaluează referinţa către un tablou unidimensional ca un pointer, un tablou bidimensional este referit într-o manieră similară. Numele tabloului bidimensional, M, reprezintă adresa (pointer) către primul element din tabloul bidimensional, acesta fiind prima linie, M[0] (tablou

66

CAPITOLUL 5

Pointeri

unidimensional). M[0] este adresa primului element (M[0][0]) din linie (tablou unidimensional), deci M[0] este un pointer către int: M = M[0] = &M[0][0]. Astfel, M şi M[linie] sunt pointeri constanţi. Putem concluziona:  M este un pointer către un tablou unidimensional (de întregi, în exemplul anterior).  ∗ M este pointer către int (pentru că M[0] este pointer către int), şi ∗ M = ∗ (M + 0) ⇔ M[0].  ∗ ∗ M este întreg; deoarece M[0][0] este int, ∗ ∗ M= ∗ ( ∗ M) ⇔ ∗ (M[0])= ∗ (M[0]+0) ⇔ M[0][0]. Exerciţiu: Să se testeze programul următor, urmărind cu atenţie rezultatele obţinute. #include #include void main() {int a[3][3]={{5,6,7}, {55,66,77}, {555,666,777}}; clrscr(); cout<<"a="<
ÎNTREBĂRI ŞI EXERCIŢII Chestiuni teoretice 1. În ce constă operaţia de incrementare a

6. Ce fel de variabile pot constitui operandul operatorului de deferenţiere? 7. Operatorul de referenţiere. 8. Unui pointer generic i se poate atribui valoarea unui pointer cu tip? 9. Care este legătura între tablouri şi pointeri? 10. De ce numele unui tablou este rvalue?

pointerilor? 2. Tablouri de pointeri. 3. Ce sunt pointerii generici? 4. Ce operaţii se pot realiza asupra variabilelor pointer? 5. De ce numele unui pointer este lvalue? Chestiuni practice

1. Să se implementeze programele cu exemplele prezentate. 2. Să se scrie programele pentru exerciţiile rezolvate care au fost prezentate. 3. Analizaţi următoarele secvenţe de instrucţiuni. Identificaţi secvenţele incorecte (acolo unde este cazul) şi sursele erorilor:   



int a,b,*c; a=7; b=90; c=a; double y, z, *x=&z; z=&y; char x, **p, *q; x = 'A'; q = &x; p = &q; cout<<”x=”<<x<<’\n’; cout<<”**p=”<<**p<<’\n’; cout<<”*q=”<<*q<<’\n’; cout<<”p=”<
CAPITOLUL 5 p = &x[0]; for (i = 0; i < 3; i++) { cout<<”*p=”<<*p<<” p=”<
Pointeri

4. Rescrieţi programele pentru problemele din capitolul 4 (3.a.-3.g., 4.a.-4.i., 5.a.-5.h.), utilizând aritmetica pointerilor.

70

CAPITOLUL 6 Funcţii

FUNCŢII 6.1. Structura unei funcţii 6.2. Apelul şi prototipul unei funcţii 6.3. Transferul parametrilor unei funcţii 6.3.1.Transferul parametrilor prin valoare 6.3.2.Transferul prin pointeri 6.3.3.Transferul prin referinţă 6.3.4.Transferul parametrilor către funcţia main

6

6.4. Tablouri ca parametri 6.5. Funcţii cu parametri impliciţi 6.6. Funcţii cu număr variabil de parametri 6.7. Funcţii predefinite 6.8. Clase de memorare 6.9. Moduri de alocare a memoriei 6.10.Funcţii recursive 6.11.Pointeri către funcţii

6.1. STRUCTURA UNEI FUNCŢII Un program scris în limbajul C/C++ este un ansamblu de funcţii, fiecare dintre acestea efectuând o activitate bine definită. Din punct de vedere conceptual, funcţia reprezintă o aplicaţie definită pe o mulţime D (D=mulţimea, domeniul de definiţie), cu valori în mulţimea C (C=mulţimea de valori, codomeniul), care îndeplineşte condiţia că oricărui element din D îi corespunde un unic element din C. Funcţiile comunică prin argumennte: ele primesc ca parametri (argumente) datele de intrare, efectuează prelucrările descrise în corpul funcţiei asupra acestora şi pot returna o valoare (rezultatul, datele de ieşire). Execuţia programului începe cu funcţia principală, numită main. Funcţiile pot fi descrise în cadrul aceluiaşi fişier, sau în fişiere diferite, care sunt testate şi compilate separat, asamblarea lor realizându-se cu ajutorul linkeditorului de legături. O funcţie este formata din antet si corp: antet_funcţie { corpul_funcţiei } Sau: tip_val_return nume_func (lista_declaraţiilor_param_ formali) { declaraţii_variabile_locale instrucţiuni return valoare } Prima linie reprezintă antetul funcţiei, în care se indică: tipul funcţiei, numele acesteia şi lista declaraţiilor parametrilor formali. La fel ca un operand sau o expresie, o funcţie are un tip, care este dat de tipul valorii returnate de funcţie în funcţia apelantă. Dacă funcţia nu întoarce nici o valoare, în locul tip_vali_return se specifică void. Dacă tip_val_return lipseşte, se consideră, implicit, că acesta este int. Nume_funcţie este un identificator. Lista_declaraţiilor_param_formali (încadrată între paranteze rotunde) constă într-o listă (enumerare) care conţine tipul şi identificatorul fiecărui parametru de intrare, despărţite prin virgulă. Tipul unui parametru poate fi oricare, chiar şi tipul pointer. Dacă lista parametrilor formali este vidă, în antet, după numele funcţiei, apar doar parantezele ( ), sau (void). Corpul funcţiei este un bloc, care implementează algoritmul de calcul folosit de către funcţie. În corpul funcţiei apar (în orice ordine) declaraţii pentru variabilele locale şi instrucţiuni. Dacă funcţia întoarce o valoare, se foloseşte instrucţiunea return valoare. La execuţie, la întâlnirea acestei instrucţiuni, se revine în funcţia apelantă. În limbajul C/C++ se utilizează declaraţii şi definiţii de funcţii. 71

CAPITOLUL 6 Funcţii

Declaraţia conţine antetul funcţiei şi informează compilatorul asupra tipului, numelui funcţiei şi a listei parametrilor formali (în care se poate indica doar tipul parametrilor formali, nu şi numele acestora). Declaraţiile de funcţii se numesc prototipuri, şi sunt constituite din antetul funcţiei, din care pot lipsi numele parametrilor formali. Definiţia conţine antetul funcţiei şi corpul acesteia. Nu este admisă definirea unei funcţii în corpul altei funcţii. O formă învechită a antetului unei funcţii este aceea de a specifica în lista parametrilor formali doar numele acestora, nu şi tipul. Această libertate în omiterea tipului parametrilor constituie o sursă de erori. tipul_valorii_returnate nume_funcţie (lista_parametrilor_ formali) declararea_parametrilor_formali { declaraţii_variabile_locale instrucţiuni return valoare }

6.2. APELUL ŞI PROTOTIPUL FUNCŢIILOR O funcţie poate fi apelată printr-o construcţie urmată de punct şi virgulă, numită instrucţiune de apel, de forma: nume_funcţie (lista_parametrilor_efectivi); Parametrii efectivi trebuie să corespundă cu cei formali ca ordine şi tip. La apel, se atribuie parametrilor formali valorile parametrilor efectivi, după care se execută instrucţiunile din corpul funcţiei. La revenirea din funcţie, controlul este redat funcţiei apelante, şi execuţia continuă cu instrucţiunea următoare instrucţiunii de apel, din funcţia apelantă. O altă posibilitate de a apela o funcţie este aceea în care apelul funcţiei constituie operandul unei expresii. Acest lucru este posibil doar în cazul în care funcţia returnează o valoare, folosită în calculul expresiei. Parametrii declaraţi în antetul unei funcţii sunt numiţi formali, pentru a sublinia faptul că ei nu reprezintă valori concrete, ci numai ţin locul acestora pentru a putea exprima procesul de calcul realizat prin funcţie. Ei se concretizează la execuţie prin apelurile funcţiei. Parametrii folosiţi la apelul unei funcţii sunt parametri reali, efectivi, concreţi, iar valorile lor vor fi atribuite parametrilor formali, la execuţie. Utilizarea parametrilor formali la implementarea funcţiilor şi atribuirea de valori concrete pentru ei, la execuţie, reprezintă un prim nivel de abstractizare în programare. Acest mod de programare se numeşte programare procedurală şi realizează un proces de abstractizare prin parametri. Variabilele declarate în interiorul unei funcţii, cât şi parametrii formali ai acesteia nu pot fi accesaţi decât în interiorul acesteia. Aceste variabile sunt numite variabile locale şi nu pot fi accesate din alte funcţii. Domeniul de vizibilitate a unei variabile este porţiunea de cod la a cărei execuţie variabila respectivă este accesibilă. Deci, domeniul de vizibilitate a unei variabile locale este funcţia în care ea a fost definită (vezi şi paragraful 6.8.). Exemplu: int f1(void) { double a,b; int c; . . . return c; // a, b, c - variabile locale, vizibile doar în corpul funcţiei } void main() { . . . . . . // variabile a şi b nu sunt accesibile în main() }

Dacă în interiorul unei funcţii există instrucţiuni compuse (blocuri) care conţin declaraţii de variabile, aceste variabile nu sunt vizibile în afara blocului. Exemplu: void main() { int a=1, b=2; 72

CAPITOLUL 6 Funcţii cout << "a=”<
// a=1 b=2, c nedeclarat // a=5 b=6 c=9 // a=1 b=6, c nedeclarat

Exerciţiu: Să se scrie următorul program (pentru înţelegerea modului de apel al unei funcţii) şi să se urmărească rezultatele execuţiei acestuia. #include void f_afis(void) { cout<<”Se execută instrucţiunile din corpul funcţiei\n”; double a=3, b=9.4; cout<
Exerciţiu: Să se scrie un program care citeşte două numere şi afişează cele mai mare divizor comun al acestora, folosind o funcţie care îl calculează. #include int cmmdc(int x, int y) { if (x==0 || y==1 || x==1 || y==0) if (x<0) x=-x; if (y<0) y=-y; while (x != 0){ if ( y > x ) {int z=x; x=y; y=z; } x-=y; // sau: x%=y; } return y;}

return 1;

void main() { int n1,n2; cout<<”n1=”;cin>>n1; cout<<”n2=”;cin>>n2; int diviz=cmmdc(n1,n2); cout<<”Cel mai mare divizor comun al nr-lor:”<
/* sau: cout<<”Cel mai mare divizor comun al nr-lor:”<
Exerciţiu: Să se calculeze valoarea lui y, u şi m fiind citite de la tastatură: z=2 ω ( 2 ϕ (u) + 1, m) + ω (2 u 2 - 3, m + 1), unde:

ω (x,n) =

n

∑ sin(ix) cos(2ix) ,

ϕ (x) =

i =1

2 1 + e − x , ω : R x N → R,

#include #include <math.h> double omega(double x, int n) 73

ϕ: R → R

CAPITOLUL 6 Funcţii { double s=0; int i; for (i=1; i<=n; i++) return s; }

s+=sin(i*x)*cos(i*x);

double psi( double x) { return sqrt( 1 + exp (- pow (x, 2)));} void main() {double u, z; int m; cout<<”u=”; cin>>u; cout<<”m=”; cin>>m; z=2*omega(2* psi(u) + 1, m) + omega(2*pow(u,2) - 3, m+1); cout<<”z=”<
În exemplele anterioare, înainte de apelul funcţiilor folosite, acestea au fost definite (antet+corp). Există cazuri în care definirea unei funcţii nu poate fi făcută înaintea apelului acesteia (cazul funcţiilor care se apelează unele pe altele). Să rezolvăm ultimul exerciţiu, folosind declaraţiile funcţiilor omega şi psi, şi nu definiţiile lor. Exerciţiu: #include #include <math.h> double omega(double, int);

// prototipul funcţiei omega - antet din care lipsesc numele parametrilor formali double psi(double); // prototipul funcţiei psi void main() {double u, z; int m; cout<<”u=”; cin>>u; cout<<”m=”; cin>>m; z=2*omega(2* psi(u) + 1, m) + omega(2*pow(u,2) - 3, m+1); cout<<”z=”<
Prototipurile funcţiilor din biblioteci (predefinite) se găsesc în headere. Utilizarea unei astfel de funcţii impune doar includerea în program a headerului asociat, cu ajutorul directivei preprocesor #include. Programatorul îşi poate crea propriile headere, care să conţină declaraţii de funcţii, tipuri globale, macrodefiniţii, etc. Similar cu declaraţia de variabilă, domeniul de valabilitate (vizibilitate) a unei funcţii este:  fişierul sursă, dacă declaraţia funcţiei apare în afara oricărei funcţii (la nivel global);  funcţia sau blocul în care apare declaraţia.

6.3. TRANSFERUL PARAMETRILOR UNEI FUNCŢII Funcţiile comunică între ele prin argumente (parametrii). Există următoarele moduri de transfer (transmitere) a parametrilor către funcţiile apelate:  Transfer prin valoare;  Transfer prin pointeri;  Transfer prin referinţă.

74

CAPITOLUL 6 Funcţii

6.3.1. TRANFERUL PARAMETRILOR PRIN VALOARE În exemplele anterioare, parametrii de la funcţia apelantă la funcţia apelată au fost transmişi prin valoare. De la programul apelant către funcţia apelată, prin apel, se transmit valorile partametrilor efectivi, reali. Aceste valori vor fi atribuite, la apel, parametrilor formali. Deci procedeul de transmitere a parametrilor prin valoare constă în încărcarea valorii parametrilor efectivi în zona de memorie a parametrilor formali (în stivă). La apelul unei funcţii, parametrii reali trebuie să corespundă - ca ordine şi tip - cu cei formali. Exerciţiu: Să se scrie următorul program (care ilustrează legătura dintre pointeri şi vectori) şi să se urmărească rezultatele execuţiei acestuia. void f1(float intr,int nr)// intr, nr - parametri formali data

{ for (int i=0; i
1.5 Copiere valoare 1.5 intr

// apelul funcţiei f1; data, 3 sunt parametri efectivi cout<<”data=”<
Figura 6.1. Transmiterea prin valoare

// data=1.5 (nemodificat) }

Fiecare argument efectiv utilizat la apelul funcţiei este evaluat, iar valoarea este atribuită parametrului formal corespunzător. În interiorul funcţiei, o copie locală a acestei valori va fi memorată în parametrul formal. O modificare a valorii parametrului formal în interiorul funcţiei (printr-o operaţie din corpul funcţiei), nu va modifica valoarea parametrului efectiv, ci doar valoarea parametrului formal, deci a copiei locale a parametrului efectiv (figura 6.1.). Faptul că variabila din programul apelant (parametrul efectiv) şi parametrul formal sunt obiecte distincte, poate constitui un mijloc util de protecţie. Astfel, în funcţia f1, valoarea parametrului formal intr este modificată (alterată) prin instrucţiunea ciclică for. În schimb, valoarea parametrului efectiv (data) din funcţia apelantă, rămâne nemodificată. În cazul transmiterii parametrilor prin valoare, parametrii efectivi pot fi chiar expresii. Acestea sunt evaluate, iar valoarea lor va iniţializa, la apel, parametrii formali. Exemplu: double psi(int a, double b) { if (a > 0) return a*b*2; else return -a+3*b; } void main() { int x=4; double y=12.6, z; z=psi ( 3*x+9, y-5) + 28; cout<<”z=”<
Transferul valorii este însoţit de eventuale conversii de tip. Aceste conversii sunt realizate automat de compilator, în urma verificării apelului de funcţie, pe baza informaţiilor despre funcţie, sau sunt conversii explicite, specificate de programator, prin operatorul ”cast”. Exemplu: float f1(double, int); void main() { int a, b; float g=f1(a, b); float h=f1( (double) a, b); }

// conversie automată: int a -> double a // conversie explicită

Limbajul C este numit limbajul apelului prin valoare, deoarece, de fiecare dată când o funcţie transmite argumente unei funcţii apelate, este transmisă, de fapt, o copie a parametrilor efectivi. În acest mod, dacă valoarea parametrilor formali (iniţializaţi cu valorile parametrilor efectivi) se modifică în interiorul funcţiei apelate, valorile parametrilor efectivi din funcţia apelantă nu vor fi afectate.

75

CAPITOLUL 6 Funcţii

6.3.2. TRANSFERUL PARAMETRILOR PRIN POINTERI În unele cazuri, parametrii transmişi unei funcţii pot fi pointeri (variabile care conţin adrese). În aceste cazuri, parametrii formali ai funcţiei apelate vor fi iniţializaţi cu valorile parametrilor efectivi, deci cu valorile unor adrese. Astfel, funcţia apelată poate modifica conţinutul locaţiilor spre care pointează argumentele (pointerii). Exerciţiu: Să se citească 2 valori întregi şi să se interschimbe cele două valori. Se va folosi o funcţie de interschimbare. #include void schimbă(int *, int *); //prototipul functiei schimba void main() { int x, y, *ptx, *pty; ptx=&x; pty=&y; cout<<”x=”;cin>>x;cout<<”y=”;cin>>y;cout<<”x=”<<x;cout<<”y=”<
Dacă parametrii funcţiei schimbă ar fi fost transmişi prin valoare, această funcţie ar fi interschimbat copiile parametrilor formali, iar în funcţia main modificările asupra parametrilor transmişi nu s-ar fi păstrat. În figura 6.2. sunt prezentate mecanismul de transmitere a parametrilor (prin pointeri) şi modificările efectuate asupra lor de către funcţia schimbă.

x 0x34 ptx

10

30

y

0x5A 30

pty

0x34

0x5A

p1

p2

0x34

0x5A

10

Parametrii formali p1 şi p2, la apelul funcţiei schimbă, primesc valorile parametrilor efectivi ptx şi pty, care reprezintă adresele variabilelor x şi y. Astfel, variabilele pointer p1 şi ptx, respectiv p2 şi pty pointează către x şi y. Modificările asupra valorilor variabilelor x şi y realizate în corpul funcţiei schimbă, se păstrează şi în funcţia main.

Figura 6.2. Transmiterea parametrilor unei funcţii prin pointeri 76

CAPITOLUL 6 Funcţii

Exerciţiu: Să se scrie următorul program şi să se urmărească rezultatele execuţiei acestuia. #include double omega (long *k) { cout<<"k=", k);

// k conţine adr. lui i cout<<”*k=”; cout<
// s = 35000

k

i

// *k = 35001

35001 (apoi 35017)

0x451

În funcţia main

cout<<”s=”<<s<<’\n’; *k+=17; // *k = 35017 cout<<”*k=”<<*k; cout<<’\n’; return s; }

0x451 (adr.lui i) În funcţia omega

w

s

35000

35000

Figura 6.3. Transferul parametrilor prin pointeri void main() { long i = 35001; double w; cout<<"i="<
6.3.2.1. Funcţii care returnează pointeri Valoarea returnată de o funcţie poate fi pointer, aşa cum se observă în exemplul următor: Exemplu: #include double *f (double *w, int k) { // w conţine adr. de început a vectorului a cout<<"w="<<w<<" *w="<<*w<<'\n'; return w+=k;

// w= adr. lui a ;*w = a[0]=10

/*incrementeaza pointerului w cu 2(val. lui k); deci w pointează către elementul de indice 2 din vectorul a*/ } void main() {double a[10]={10,1,2,3,4,5,6,7,8,9}; int i=2; cout<<"Adr. lui a este:"< 7 12 #include void main() { 12 c int b,c; int &br=b; //br referinţă la altă variabilă (b) Figura 6.4. Variabilele referinţă b, br br=7; cout<<"b="<
CAPITOLUL 6 Funcţii {cout<<"\n\n \n MAIN MAIN"; int t=7; double u=12, v=17, *w, z; cout<<"u="<
Exemplul ilustrează următoarele probleme: La apelul funcţiei func, parametrii t şi u sunt transmişi prin valoare, deci valorile lor vor fi atribuite parametrilor formali a şi b. Orice modificare a parametrilor formali a şi b, în funcţia func, nu va avea efect asupra parametrilor efectivi t şi u. Al treilea parametru formal al funcţiei func este transmis prin pointeri, deci c este de tip double * (pointer către un real), sau *c este de tip double. La apelul funcţiei, valoarea pointerului w (adresa lui u : w=&u) este atribuită pointerului c. Deci pointerii w şi c conţin aceeaşi adresă, pointând către un real. Dacă s-ar modifica valoarea spre care pointează c în func (vezi instrucţiunile din comentariu *c=500), această modificare ar fi reflectată şi în funcţia apelantă, deoarece pointerul w are acelaşi conţinut ca şi pointerul c, deci pointează către aceeaşi locaţie de memorie. Parametrul formal d se transmite prin referinţă, deci, în momentul apelului, d şi v devin similare (d şi v sunt memorate la aceeaşi adresă). Modificarea valorii variabilei d în func se reflectă, deci, şi asupra parametrului efectiv din funcţia main. Exerciţiu: Să se scrie următorul program (care ilustrează legătura dintre pointeri şi vectori) şi să se urmărească rezultatele execuţiei acestuia. #include #include <stdio.h> double omega(long &k) {printf("Adr k=%x Val k=%ld\n",&k,k); //Adr k=fff2 Val k=200001 double s=2+k-3;cout<<"s="<<s<<'\n'; //s=200000 k+=17;printf("Adr k=%x Val k=%ld\n",&k,k); //Adr k=fff2 Val k=200018 return s; } void main() {long a=200001; printf("Adr a=%x Val a=%ld\n",&a,a); //Adr a=fff2 Val a=200001 double w=omega(a); cout<<"w="<<w<<'\n'; //s=200000 }

Aşa cum s-a prezentat în paragrafele 2.5.3.2. şi 5.6., modificatorii sunt cuvinte cheie utilizaţi în declaraţii sau definiţii de variabile sau funcţii. Modificatorul de acces const poate apare în:  Declaraţia unei variabile (precede tipul variabilei) restricţionând modificarea valorii datei;  La declararea variabilelor pointeri definind pointeri constanţi către date neconstante, pointeri neconstanţi către date constante şi pointeri constanţi către date constante.  În lista declaraţiilor parametrilor formali ai unei funcţii, conducând la imposibilitatea de a modifica valoarea parametrului respectiv în corpul funcţiei, ca în exemplul următor: Exemplu: #include #include <stdio.h> int func(const int &a) {printf("Adr a=%x Val a=%d\n",&a,a);int b=2*a+1;

//modificarea valorii parametrului a nu este permisă 80

CAPITOLUL 6 Funcţii cout<<"b="<
6.3.4. TRANSFERUL PARAMETRILOR CĂTRE FUNCŢIA main În situaţiile în care se doreşte transmiterea a unor informaţii (opţiuni, date iniţiale, etc) către un program, la lansarea în execuţie a acestuia, este necesară definirea unor parametri către funcţia main. Se utilizează trei parametrii speciali: argc, argv şi env. Trebuie inclus headerul stdarg.h. Prototipul funcţiei main cu parametri în linia de comandă este: main (int argc, char *argv[ ], char *env[ ]) Dacă nu se lucrează cu un mediu de programare integrat, argumentele transmise către funcţia main trebuie editate (specificate) în linia de comandă prin care se lansează în execuţie programul respectiv. Linia de comandă tastată la lansarea în execuţie a programului este formată din grupuri de caractere delimitate de spaţiu sau tab. Fiecare grup este memorat într-un şir de caractere. Dacă se lucrează cu un mediu integrat (de exemplu, BorlandC), selecţia comanzii Arguments… din meniul Run determină afişarea unei casete de dialog în care utilizatorul poate introduce argumentele funcţiei main. 

 

Adresele de început ale acestor şiruri sunt memorate în tabloul de pointeri argv[], în ordinea în care apar în linia de comandă (argv[0] memorează adresa şirului care constituie numele programului, argv[1] - adresa primului argument, etc.). Parametrul întreg argc memorează numărul de elemente din tabloul argv (argc>=1). Parametrul env[] este un tablou de pointeri către şiruri de caractere care pot specifica parametri ai sistemului de operare.

Funcţia main poate returna o valoare întreagă. În acest caz în antetul funcţiei se specifică la tipul valorii returnate int, sau nu se specifică nimic (implicit, tipul este int), iar în corpul funcţiei apare instrucţiunea return valoare_intreagă;. Numărul returnat este transferat sistemului de operare (programul apelant) şi poate fi tratat ca un cod de eroare sau de succes al încheierii execuţiei programului. Exerciţiu: Să se implementeze un program care afişează argumentele transmise către funcţia main. #include #include <stdarg.h> void main(int argc, char *argv[], char *env[]) {cout<<"Nume program:"<<argv[0]<<'\n';//argv[0] contine numele programului if(argc==1) cout<<"Lipsa argumente!\n"; else for (int i=1; i<argc; i++){ cout<<"Argumentul "<<<":"<<argv[i]<<'\n'; }

6.4. TABLOURI CA PARAMETRI În limbajul C, cazul parametrilor tablou constituie o excepţie de la regula transferului parametrilor prin valoare. Numele unui tablou reprezintă, de fapt, adresa tabloului, deci a primului element din tablou. Exerciţiu: Să se afle elementul minim dintr-un vector de maxim 10 elemente. Se vor scrie două funcţii: de citire a elementelor vectorului şi de aflare a elementului minim: #include int min_tab(int a[], int nr_elem) {int elm=a[0]; 81

CAPITOLUL 6 Funcţii for (int ind=0; ind=a[ind]) elm=a[ind]; return elm; } void citireVector(int b[], int nr_el) { for (int ind=0; ind
Aceleeaşi problemă poate fi implementată folosind aritmetica pointerilor: #include void citireVector(int *b, int nr_el) { for (int ind=0; ind
Din exemplele anterioare se poate observa: 1. Prototipul funcţiei min_tab poate fi unul dintre: int min_tab(int a[], int nr_elem); int min_tab(int *a, int nr_elem); 2. Echivalenţe: int *a int a[] ⇔ a[i] ⇔ *(a+i) 3. Apelul funcţiilor: citireVector(a,n); int min=min_tab(a,n); 4. Pentru tablourile unidimensionale, la apel, nu trebuie specificat numărul de elemente. Dimensiunea tabloului trebuie să fie cunoscută în funcţia care îl primeşte ca parametru. De obicei, dimensiunea tabloului se transferă ca parametru separat (nr_elem). Exerciţiu: Să se scrie următorul program şi să se urmărească rezultatele execuţiei acestuia. #include #include <stdio.h> double omega(int j, double x, double t[], double *w) {double s; cout<<"În funcţia omega:"; cout<<"j="<<j<<" t[j]="<

83

CAPITOLUL 6 Funcţii int min_tab(int a[][10], int nr_lin, int nr_col) {int elm=a[0][0]; for (int il=0; il=a[il][ic]) elm=a[il][ic]; return elm; } void afisare(int a[][10], int nr_lin, int nr_col) { for (int i=0; i>M; cout<<"Nr. coloane:"; cin>>N; for (i=0; i<M; i++) for (j=0; j
Valoarea returnată de o funcţie poate să fie transmisă şi prin referinţă, cum ilustrează exemplul următor: Exemplu: #include #include <stdio.h> double &func(double &a, double b) { printf("În funcţie:\n"); printf("Val a=%f Adr a=%x\n", a, &a); //Val a=1.20 Adr a=fffe cout<<"b="<
//Val a=5.40 Adr a=fffe return a; } void main() {double c=1.2;cout<<"***************MAIN****************\n"; printf("Val c=%f Adr c=%x\n",c, &c); //Val c=1.20 Adr c=fffe double d; printf("Adr. d=%x\n", &d); //Adr. d=ffe6 d=func(c,2.2); printf("Val d=%f Adr d=%x\n", d, &d); //Val d=5.40 Adr d=ffe6

}

6.5. FUNCŢII CU PARAMETRI IMPLICIŢI Spre deosebire de limbajul C, în limbajul C++ se pot face iniţializări ale parametrilor formali. Parametrii formali iniţializaţi se numesc parametri impliciţi. De exemplu, antetul funcţiei cmmdc (care calculează şi returnează cel mai mare divizor comun al numerelor întregi primite ca argumente) poate avea aceasta formă: int cmmdc(int x, int y=1); Parametrul formal y este iniţializat cu valoarea 1 şi este parametru implicit. La apelul funcţiilor cu parametri impliciţi, unui parametru implicit, poate să-i corespundă sau nu, un parametru efectiv. Dacă la apel nu îi corespunde un parametru efectiv, atunci parametrul formal va primi valoarea prin care a fost iniţializat (valoarea implicită). Dacă la apel îi corespunde un parametru efectiv, parametrul formal va fi iniţializat cu 84

CAPITOLUL 6 Funcţii

valoarea acestuia, negijându-se astfel valoarea implicită a parametrului formal. În exemplul anterior, la apelul: int div=cmmdc(9); x va lua valoarea 9, iar y va lua valoarea 1 (implicită).

Dacă în lista de parametri formali ai unei funcţii există şi parametri impliciţi şi parametri neiniţializaţi, parametrii impliciţi trebuie să ocupe ultimele poziţii în listă, nefiind permisă intercalarea acestora printre parametrii neiniţializaţi.

6.6. FUNCŢII CU NUMĂR VARIABIL DE PARAMETRI În limbajele C şi C++ se pot defini funcţii cu un număr variabil de parametri. Parametrii care trebuie să fie prezenţi la orice apel al funcţiei se numesc parametri ficşi, ceilalţi se numesc parametri variabili. Parametrii ficşi preced parametrii variabili. Prezenţa parametrilor variabili se indică în antetul funcţiei prin trei puncte care se scriu după ultimul parametru fix al funcţiei. De exemplu, fie antetul funcţiei numite vârf: void vârf (int n, double a, . . . ) Funcţia vârf are doi parametri ficşi (n şi a ) şi parametri variabili, pentru care nu se precizează în prealabil numărul şi tipul; numărul şi tipul parametrilor variabili diferă de la un apel la altul. Funcţiile cu un număr variabil de parametri sunt, de obicei, funcţii de bibliotecă (ex: printf, scanf) şi se definesc folosind nişte macrouri speciale care permit accesul la parametrii variabili şi se găsesc în headerul stdarg.h.

6.7. FUNCŢII PREDEFINITE Orice mediu de programare este prevăzut cu una sau mai multe biblioteci de funcţii predefinite. Orice bibliotecă este formată din:  fişierele header (conţine prototipurile funcţiilor, declaraţiile de variabile);  biblioteca (arhiva) propriu-zisă (conţine definiţii de funcţii). Pentru ca funcţiile predefinite să poată fi utilizate, fişierele header în care se găsesc prototipurile acestora trebuie inclus în funcţia (programul) apelant printr-o directivă preprocesor (exemplu #include <stdio.h>). Deasemenea, utilizatorul îşi poate crea propriile headere proprii. Pentru a putea utiliza funcţiile proprii, el trebuie să includă aceste headere în programul apelant (exemplu #include "my_header.h"). Pentru funcţiile predefinite, au fost create fişiere header orientate pe anumite numite tipuri de aplicaţii. De exemplu, funcţiile matematice se găsesc în headerul <math.h>. Headerul <stdlib.h> care conţine funcţii standard. Headerul defineşte o serie de constante simbolice (exemplu MAXINT, MAXLONG) care reprezintă, în principal, valorile maxime şi minime ale diferitelor tipuri de date.

6.7.1. Funcţii matematice (headerul <math.h>) Funcţii aritmetice Valori absolute int abs(int x); Returnează un întreg care reprezintă valoarea absolută a argumentului. long int labs(long int x); Analog cu funcţia abs, cu deosebirea că argumentul şi valoarea returnată sunt de tip long int. double fabs(double x); Returnează un real care reprezintă valoarea absolută a argumentului real. Funcţii de rotunjire double floor(double x); 85

CAPITOLUL 6 Funcţii

Returnează un real care reprezintă cel mai apropiat număr, fără zecimale, mai mic sau egal cu x (rotunjire prin lipsă). double ceil(double x); Returnează un real care reprezintă cel mai apropiat număr, fără zecimale, mai mare sau egal cu x (rotunjire prin adaos). Funcţii trigonometrice double sin(double x); Returnează valoarea lui sin(x), unde x este dat în radiani. Numărul real returnat se află în intervalul [-1, 1]. double cos(double x); Returnează valoarea lui cos(x), unde x este dat în radiani. Numărul real returnat se află în intervalul [-1, 1]. double tan(double x); Returnează valoarea lui tg(x), unde x este dat în radiani. Funcţii trigonometrice inverse double asin(double x); Returnează valoarea lui arcsin(x), unde x se află în intervalul [-1, 1]. Numărul real returnat (în radiani) se află în intervalul [-pi/2, pi/2]. double acos(double x); Returnează valoarea lui arccos(x), unde x se află în intervalul [-1, 1]. Numărul real returnat se află în intervalul [0, pi]. double atan(double x); Returnează valoarea lui arctg(x), unde x este dat în radiani. Numărul real returnat se află în intervalul [0, pi]. double atan2(double y, double x); Returnează valoarea lui tg(y/x), cu excepţia faptului ca semnele argumentelor x şi y permit stabilirea cadranului şi x poate fi zero. Valoarea returnată se află în intervalul [-pi,pi]. Dacă x şi y sunt coordonatele unui punct în plan, funcţia returnează valoarea unghiului format de dreapta care uneşte originea axelor carteziene cu punctul, faţă de axa absciselor. Funcţia foloseşte, deasemenea, la transformarea coordonatelor cartezine în coordonate polare. Funcţii exponenţiale şi logaritmice double exp(double x); long double exp(long double x); Returnează valoarea e x . double log(double x); Returnează logaritmul natural al argumentului ( ln(x) ). double log10(double x); Returnează logaritmul zecimal al argumentului (lg (x) ). double pow(double baza, double exponent); Returnează un real care reprezintă rezultatul ridicării bazei la exponent ( baza exp onent ). double sqrt(double x); Returnează rădăcina pătrată a argumentului x . double hypot(double x, double y); Funcţia distanţei euclidiene - returnează

x 2 + y 2 , deci lungimea ipotenuzei unui triunghi

dreptunghic, sau distanţa punctului P(x, y) faţă de origine. Funcţii de generare a numerelor aleatoare int rand(void) <stdlib.h> Generează un număr aleator în intervalul [0, RAND_MAX].

6.7.2. Funcţii de clasificare (testare) a caracterelor

86

CAPITOLUL 6 Funcţii

Au prototipul în headerul . Toate aceste funcţii primesc ca argument un caracter şi returnează un număr întreg care este pozitiv dacă argumentul îndeplineşte o anumită condiţie, sau valoarea zero dacă argumentul nu îndeplineşte condiţia. int isalnum(int c); Returnează valoare întreagă pozitivă daca argumentul este literă sau cifră. Echivalentă cu: isalpha(c)||isdigit(c) int isalpha(int c); Testează dacă argumentul este literă mare sau mică. Echivalentă cu isupper(c)|| islower(c). int iscntrl(int c); Testează dacă argumentul este caracter de control (neimprimabil). int isdigit(int c); Testează dacă argumentul este cifră. int isxdigit(int c); Testează dacă argumentul este cifră hexagesimală (0-9, a-f, A-F). int islower(int c); Testează dacă argumentul este literă mică. int isupper(int c); Testează dacă argumentul este literă mare. int ispunct(int c); Testează dacă argumentul este caracter de punctuaţie (caracter imprimabil, dar nu literă sau spaţiu). int isspace(int c); Testează dacă argumentul este spaţiu alb (' ', '\n', '\t', '\v', '\r') int isprint(int c); Testează dacă argumentul este caracter imprimabil, inclusiv blancul.

6.7.3. Funcţii de conversie a caracterelor (prototip în ) int tolower(int c); Funcţia schimbă caracterul primit ca argument din literă mare, în literă mică şi returnează codul ASCII al literei mici. Dacă argumentul nu este literă mare, codul returnat este chiar codul argumentului. int toupper(int c); Funcţia schimbă caracterul primit ca argument din literă mică, în literă mare şi returnează codul acesteia. Dacă argumentul nu este literă mică, codul returnat este chiar codul argumentului.

6.7.4. Funcţii de conversie din şir în număr (de citire a unui număr dintr-un şir) (prototip în <stdlib.h>) long int atol(const char *npr); Funcţia converteşte şirul transmis ca argument (spre care pointează npr) într-un număr cu semn, care este returnat ca o valoare de tipul long int. Şirul poate conţine caracterele '+' sau '-'. Se consideră că numărul este în baza 10 şi funcţia nu semnalizează eventualele erori de depăşire care pot apare la conversia din şir în număr. int atoi(const char *sir); Converteste şirul spre care pointeaza sir într-un număr întreg. double atof(const char *sir); Funcţia converteste şirul transmis ca argument într-un număr real cu semn (returnează valoare de tipul double). În secvenţa de cifre din şir poate apare litera 'e' sau 'E' (exponentul), urmată de caracterul '+' sau '-' şi o altă secvenţă de cifre. Funcţia nu semnalează eventualele erori de depăşire care pot apare.

6.7.5. Funcţii de terminare a unui proces (program) (prototip în <process.h>) void exit(int status); 87

CAPITOLUL 6 Funcţii

Termină execuţia unui program. Codul returnat de terminarea corectă este memorat în constanta simbolică EXIT_SUCCES, iar codul de eroare - în EXIT_FAILURE. void abort(); Termină forţat execuţia unui program. int system(const char *comanda); prototip în <system.h> Permite execuţia unei comenzi DOS, specificate prin şirul de caractere transmis ca parametru.

6.7.6. Funcţii de intrare/ieşire (prototip în <stdio.h>)

Streamurile (fluxurile de date) implicite sunt: stdin (fişierul, dispozitivul standard de intrare), stdout (fişierul, dispozitivul standard de ieşire), stderr (fişier standard pentru erori), stdprn (fişier standard pentru imprimantă) şi stdaux (dispozitivul auxiliar standard). De câte ori este executat un program, streamurile implicite sunt deschise automat de către sistem. În headerul <stdio.h> sunt definite şi constantele NULL (definită ca 0) şi EOF (sfârşit de fişier, definită ca -1, CTRL/Z). int getchar(void); Citeşte un caracter (cu ecou) din fişierul standard de intrare (tastatură). int putchar(int c); Afişează caracterul primit ca argument în fişierul standard de ieşire (monitor). char *gets(char *sir); Citeşte un şir de caractere din fişierul standard de intrare (până la primul blank întâlnit sau linie nouă). Returnează pointerul către şirul citit. int puts(const char *sir); Afişează şirul argument în fişierul standard de ieşire şi adaugă terminatorul de şir. Returnează codul ultimului caracter al şirului (caracterul care precede NULL) sau -1 în caz de eroare. int printf(const char *format, ... ); Funcţia permite scrierea în fişierul standard de ieşire (pe monitor) a datelor, într-un anumit format. Funcţia returnează numărul de octeţi (caractere) afişaţi, sau –1 în cazul unei erori. 1. Parametrul fix al funcţiei conţine:  Succesiuni de caractere afişate ca atare Exemplu: printf("\n Buna ziua!\n\n”); // afişare: Buna ziua!  Specificatori de format care definesc conversiile care vor fi realizate asupra datelor de ieşire, din formatul intern, în cel extren (de afişare). 2. Parametrii variabili ai funcţiei sunt expresii. Valorile obţinute în urma evaluării acestora sunt afişate corespunzător specificatorilor de format care apar în parametrul fix. De obicei, parametrul fix conţine atât specificatori de format, cât şi alte caractere. Numărul şi tipul parametrilor variabili trebuie să corespundă specificatorului de format. Un specificator de format care apare în parametrul fix poate avea următoarea formă: % [-|c|][sir_cifre_eventual_punct_zecimal] una_sau_doua_litere - Implicit, datele se cadrează (aliniază) la dreapta câmpului în care se scriu. Prezenţa caracterului – determină cadrarea la stânga. Şirul de cifre defineşte dimensiunea câmpului în care se scrie data. Dacă scrierea datei necesită un câmp de lungime mai mare, lungimea indicată în specificator este ignorată. Dacă scrierea datei necesită un câmp de lungime mai mică, data se va scrie în câmp, cadrată la dreapta sau la stânga (dacă apare semnul - ), completându-se restul câmpului cu caracterele nesemnificative implicite, adica spaţii. Şirul de cifre aflate dupa punct definesc precizia (numarul de zecimale cu care este afişat un numar real - implicit sunt afişate 6 zecimale). Literele definesc tipul conversiei aplicat datei afişate: c – Afişează un caracter s – Afişează un şir de caractere d– Afişează date întregi; cele negative sunt precedate de semnul -. o – Afişează date de tip int sau unsigned int în octal. x sau X - Afişează date de tip int sau unsigned int în hexagesimal. f–Afişează date de tip float sau double în forma: parte_întreagă.parte_fract e sau E-Afişează date de tip float sau double în forma: 88

CAPITOLUL 6 Funcţii

parte_întreagă.parte_fractionară exponent Exponentul începe cu e sau E şi defineşte o putere a lui zece care înmulţită cu restul numărului dă valoarea reală a acestuia. g sau G–Afişează o dată reală fie ca în cazul specificatorului terminat cu f, fie ca în cazul specificatorului terminat cu e. Criteriul de afisare se alege automat, astfel încât afişarea să ocupe un număr minim de poziţii în câmpul de afişare. l – Precede una din literele d, o, x, X, u. La afişare se fac conversii din tipul long sau unsigned long. L – Precede una din literele f, e, E, g, G. La afişare se fac conversii din tipul long double. int scanf(const char *format, ... ); Funcţia citeşte din fişierul standard de intrare valorile unor variabile şi le depune în memorie, la adresele specificate. Funcţia returnează numărul câmpurilor citite corect. 1. Parametrul fix al funcţiei conţine: Specificatorii de format care definesc conversiile aplicate datelor de intrare, din formatul extern, în cel intren (în care sunt memorate). Specificatorii de format sunt asemanători celor folosiţi de funcţia printf: c, s, d, o, x sau X, u, f, l, L. 2. Parametrii varaibili reprezintă o listă de adrese ale variabilelor care vor fi citite, deci în această listă, numele unei varaibile simple va fi precedată de operatorul adresă &. int sprintf(char *sir_cu_format, const char *format, ... ); Funcţia permite scrierea unor date în şirul transmis ca prim argument, într-un anumit format. Valoarea returnată reprezintă numărul de octeţi (caractere) scrise în şir, sau –1 în cazul unei erori. int sscanf(char *sir_cu_format, const char *format, ... ); Funcţia citeşte valorile unor variabile din şirul transmis ca prim argument şi le depune în memorie, la adresele specificate. Returnează numărul câmpurilor citite corect. Exemplu: Să se scrie următorul program (care ilustrează modalităţile de folosire a funcţiilor predefinite) şi să se urmărească rezultatele execuţiei acestuia. #include #include <stdlib.h> #include <math.h> #include #include <stdio.h> void main() { int x=-34; int a=abs(x); cout<<"a="<
// 101=codul carcterului e cout<<'\n';puts(s1);cout<<'\n'; printf("Afisarea unui mesaj\n"); int intreg=-45; printf("VALOAREA VARIABILEI INTREG ESTE:%d\n", intreg); printf("VALOAREA VARIABILEI INTREG ESTE:%10d\n", intreg); printf("VALOAREA VARIABILEI INTREG ESTE:%-10d\n", intreg); double real=2.45; printf("VALOAREA VARIABILEI real ESTE:%f\n", real); printf("VALOAREA VARIABILEI real ESTE:%10.3f\n", real); printf("VALOAREA VARIABILEI real ESTE:%10.5f\n", real); printf("VALOAREA VARIABILEI real ESTE:%e\n", real); printf("VAL VAR real:%f si\neste mem. la adr.%x\n",real,&real ); printf("astept sir:");scanf("%s",s1); printf("Sirul citit este: %s \n", s1); char sir_f[100]; sprintf(sir_f,"Codul caracterului %c este:%d",c, (int)c); puts(sir_f); }

6.8. CLASE DE MEMORARE Definiţii: Variabilele declarate în afara oricărei funcţii sunt variabilele globale. Variabilele declarate în interiorul unui bloc sunt variabilele locale. Porţiunea de cod în care o variabilă este accesibilă reprezintă scopul (domeniul de vizibilitate) al variabilei respective. Parametrii formali ai unei funcţii sunt variabile locale ale funcţiei respective. Domeniul de vizibilitate al unei variabile locale este blocul în care variabila respectivă este definită. În situaţia în care numele unei variabile globale coincide cu numele unei variabile locale, variabila locală o "maschează" pe cea globală, ca în exemplul următor: în interiorul blocului din funcţia main s-a redefinit variabila a, care este variabilă locală în interiorul blocului. Variabila locală a maschează variablila globală numită tot a. Exemplu: #include <stdio.h rel="nofollow"> void main() { int a,b; a=1; b=2; printf("În afara blocului a=%d b=%d\n", a, b); {int a=5; b=6; printf("În interiorul blocului a=%d b=%d\n",a,b); } printf("În afara blocului a=%d b=%d\n", a, b); }

În cazul variabilelor locale, compilatorul alocă memorie în momentul execuţiei blocului sau funcţiei în care acestea sunt definite. Când execuţia funcţiei sau blocului se termină, se eliberează memoria pentru acestea şi valorile pentru variabilele locale se pierd. Definiţii: Timpul de viaţă a unei variabile locale este durata de execuţie a blocului (sau a funcţiei) în care aceasta este definită. Timpul de viaţă a unei variabile globale este durata de execuţie a programului.

90

CAPITOLUL 6 Funcţii

În exemplul următor, variabila întreagă x este vizibilă atât în funcţia main, cât şi în funcţia func1 (x este variabila globală, fiind definită în exteriorul oricărei funcţii). Variabilele a şi b sunt variabile locale în funcţia main (vizibile doar în main). Variabilele c şi d sunt variabile locale în funcţia func1 (vizibile doar în func1). Varabila y este variabilă externă şi este vizibilă din punctul în care a fost definită, până la sfârşitul fişierului sursă (în acest caz, în funcţia func1). Exemplu: int x; void main() {int a,b;

//- - - - - - - - } int y; void func1(void) {int c,d;

//- - - - - - - }

Clase de memorare O variabilă se caracterizează prin: nume, tip, valoare şi clasă de memorare. Clasa de memorare se specifică la declararea variabilei, prin unul din următoarele cuvinte cheie:  auto;  register;  extern;  static. Clasa de memorare determină timpul de viaţă şi domeniul de vizibilitate (scopul) unei variabile (tabelul 6.1). Exemplu: auto int a; static int x; extern double y; register char c; 

Clasa de memorare auto Dacă o variabilă locală este declarată fără a se indica în mod explicit o clasă de memorare, clasa de memorare considerată implicit este auto. Pentru acea variabilă se alocă memorie automat, la intrarea în blocul sau în funcţia în care ea este declarată. Deci domeniul de vizibilitate al variabilei este blocul sau funcţia în care aceasta a fost definită. Timpul de viaţă este durata de execuţie a blocului sau a funcţiei.



Clasa de memorare register Variabilele din clasa register au acelaşi domeniu de vizibilitate şi timp de viaţă ca şi cele din clasa auto. Deosebirea faţă de variabilele din clasa auto constă în faptul că pentru memorarea variabilelor register, compilatorul utilizează regiştrii interni (ceea ce conduce la creşterea eficienţei). Unei variabile pentru care se specifică drept clasă de memorare register, nu i se poate aplica operatorul de referenţiere.



Clasa de memorare extern O variabilă globală declarată fără specificarea unei clase de memorare, este considerată ca având clasa de memorare extern. Domeniul de vizibilitate este din momentul declarării până la sfârşitul fişierului sursă. Timpul de viaţă este durata execuţiei fişierului. O variabilă din clasa extern este iniţializată automat cu valoarea 0.



Clasa de memorare static Clasa de memorare static are două utilizări distincte:  Variabilele locale statice au ca domeniu de vizibilitate blocul sau funcţia în care sunt definite, iar ca timp de viaţă - durata de execuţie a programului. Se iniţializează automat cu 0.

91

CAPITOLUL 6 Funcţii 

Variabilele globale statice au ca domeniu de vizibilitate punctul în care au fost definite până la sfârşitul fişierului sursă, iar ca timp de viaţă - durata execuţiei programului.

Tabelul 6.1. Clasa de memorare

Variabila

auto (register) extern

locală (internă)

static

globală locală globală locală

nespecificat

globală

Domeniu vizibilitate

Timp de viaţă

Blocul sau funcţia

Durara de execuţie a blocului sau a funcţiei  Din punctul definirii, până la Durara de execuţie a blocului sfârşitul fişierului (ROF) sau a programului  Alte fişiere ROF -"Bloc sau funcţie -"Vezi extern Vezi extern Vezi auto Vezi auto

6.9. MODURI DE ALOCARE A MEMORIEI Alocarea memoriei se poate realiza în următoarele moduri:  alocare statică;  alocare dinamică;  alocare pe stivă.  Se alocă static memorie în următoarele cazuri:  pentru instrucţiunile de control propriu-zise;  pentru variabilele globale şi variabilele locale declarate în mod explicit static.  Se alocă memorie pe stivă pentru variabilele locale.  Se aloca dinamic memorie în mod explicit, cu ajutorul funcţiilor de alocare dinamica, aflate în headerul . Exemplu: int a,b; double x; double f1(int c, double v) {int b; static double z; } double w; int f1(int w) {double a; } void main() {double b, c; int k; b=f1(k,c); }

6.9.1. Alocarea memoriei în mod dinamic Pentru toate tipurile de date (simple sau structurate), la declararea acestora, compilatorul alocă automat un număr de locaţii de memorie (corespunzător tipului datei). Dimensiunea zonei de memorie necesară pentru păstrarea valorilor datelor este fixată înaintea lansării în execuţie a programului. În cazul declarării unui tablou de întregi cu maximum 100 de elemente vor fi alocaţi 100*sizeof(int) locaţii de memorie succesive. În situaţia în care la un moment dat tabloul are doar 20 de elemente, pentru a aloca doar atâta memorie cât este necesară în momentul respectiv, se va aloca memorie în mod dinamic. Este de dorit ca în cazul datelor a căror dimensiune nu este cunoscută a priori sau variază în limite largi, să se utilizeze o altă abordare: alocarea memoriei în mod dinamic. În mod dinamic, memoria nu mai este alocată în 92

CAPITOLUL 6 Funcţii

momentul compilării, ci în momentul execuţiei. Alocarea dinamică elimină necesitatea definirii complete a tuturor cerinţelor de memorie în momentul compilării. În limbajul C, alocarea memoriei în mod dinamic se face cu ajutorul funcţiilor malloc, calloc, realloc; eliberarea zonei de memorie se face cu ajutorul funcţiei free. Funcţiile de alocare/dezalocare a memoriei au prototipurile în header-ele <stdlib.h> şi : void *malloc(size_t nr_octei_de_alocat); Funcţia malloc necesită un singur argument (numărul de octeţi care vor fi alocaţi) şi returnează un pointer generic către zona de memorie alocată (pointerul conţine adresa primului octet al zonei de memorie rezervate). void *calloc(size_t nr_elemente, size_t mărimea_în_octeţi_ a_unui_elem); Funcţia calloc lucrează în mod similar cu malloc; alocă memorie pentru un tablou de nr_elemente, numărul de octeţi pe care este memorat un element este mărimea_în_octeţi_a_unui_elem şi returnează un pointer către zona de memorie alocată. void *realloc(void *ptr, size_t mărime); Funcţia realloc permite modificarea zonei de memorie alocată dinamic cu ajutorul funcţiilor malloc sau calloc. Observaţie: În cazul în care nu se reuşeşte alocarea dinamică a memoriei (memorie insuficientă), funcţiile malloc, calloc şi realloc returnează un pointer null. Deoarece funcţiile malloc, calloc, realloc returnează un pointer generic, rezultatul poate fi atribuit oricărui tip de pointer. La atribuire, este indicat să se utilizeze operatorul de conversie explicită (vezi exemplu). Eliberarea memoriei (alocate dinamic cu una dintre funcţiile malloc, calloc sau realloc) se realizează cu ajutorul funcţiei free. void free(void *ptr); Exemplu: Să se aloce dinamic memorie pentru 20 de valori întregi. int *p; p=(int*)malloc(20*sizeof(int)); //p=(int*)calloc(20, sizeof(int));

Exerciţiu: Să se scrie un program care implementează funcţia numită introd_val. Funcţia trebuie să permită introducerea unui număr de valori reale, pentru care se alocă memorie dinamic. Valorile citite cu ajutorul funcţiei introd_val sunt prelucrate în funcţia main, apoi memoria este eliberată. #include <stdio.h> #include <stdlib.h> #include float *introd_val()

/* pentru a putea realiza eliberarea memoriei în funcţia main, funcţia introd_val trebuie să returneze adresa de început a zonei de memorie alocate dinamic */ {double *p; int nr;printf("Număr valori:"); scanf("%d", nr); if (!(p=(float*)malloc(nr*sizeof(float))) ){ printf("Memorie insuficientă!\n");return NULL; } for (int i=0; i
// prelucrare tablou free(pt); }

Exerciţiu: Să se scrie un program care citeşte numele angajaţilor unei întreprinderi. Numărul angajaţilor este transmis ca argument către funcţia main. Alocarea memoriei pentru cei nr_ang angajaţi, cât şi pentru numele fiecăruia dintre aceştia se va face în mod dinamic. #include <stdlib.h> 93

CAPITOLUL 6 Funcţii #include <stdio.h> #include <stdarg.h> void main(int argc, char *argv[]) {char **ang_ptr; char *nume; int nr_ang, i; if (argc==2){ nr_ang=atoi(argv[1]);/* numărul angajaţilor este transmis ca argument către funcţia

main. El este convertit din şir de caractere în număr */ ang_ptr=(char**)calloc(nr_ang, sizeof(char*)); if ((ang_ptr==0){ printf("Memorie insuficientă!\n");exit(1);} nume=(char*)calloc(30, sizeof(char)); for (i=0; i
În limbajul C++ alocarea dinamică a memoriei şi eliberarea ei se pot realiza cu operatorii new şi delete. Folosirea acestor operatori reprezintă o metodă superioară, adaptată programării orientate obiect. Operatorul new este un operator unar care returnează un pointer la zona de memorie alocată dinamic. În situaţia în care nu există suficientă memorie şi alocarea nu reuşeşte, operatorul new returnează pointerul NULL. Operatorul delete eliberează zona de memorie spre care pointează argumentul său. Sintaxa: tipdata_pointer = new tipdata; tipdata_pointer = new tipdata(val_iniţializare); //pentru iniţializarea datei pentru care se alocă memorie dinamic tipdata_pointer = new tipdata[nr_elem]; //alocarea memoriei pentru un tablou delete tipdata_pointer; delete [nr_elem] tipdata_pointer; tablouri

//eliberarea

memoriei

pentru

Tipdata reprezintă tipul datei (predefinit sau obiect) pentru care se alocă dinamic memorie, iar tipdata_pointer este o variabilă pointer către tipul tipdata.

Pentru a putea afla memoria RAM disponibilă la un moment dat, se poate utiliza funcţia coreleft: unsigned coreleft(void); Exerciţiu: Să se aloce dinamic memorie pentru o dată de tip întreg: int *pint; pint=new int;

//Sau: int &i=*new int; i=100; //i permite referirea la întregul păstrat în zona de memorie alocată dinamic

Exerciţiu: Să se aloce dinamic memorie pentru o dată reală, dublă precizie, iniţializând-o cu valoarea -7.2. 94

CAPITOLUL 6 Funcţii double *p; p=new double(-7.2);

//Sau: double &p=* new double(-7.2);

Exerciţiu: Să se aloce dinamic memorie pentru un vector de m elemente reale. double *vector; vector=new double[m];

Exemplu: Să se urmărească rezultatele execuţiei următorului program, care utilizează funcţia coreleft. #include #include #include void main() { int *a,*b; clrscr(); cout<<"Mem. libera inainte de alocare:"<0; Exemplu: Să se implementeze recursiv funcţia care calculează n!, unde n este introdus de la tastatură: #include int fact(int n) {if (n<0){ cout<<"Argument negativ!\n"; exit(2); } else if (n==0) return 1; else return n*fact(n-1); } 95

CAPITOLUL 6 Funcţii void main() {int nr, f; cout<<"nr="; cin>>nr; f=fact(nr); cout<
Se observă că în corpul funcţiei fact se apelează însăşi funcţia fact. Presupunem că nr=4 (iniţial, funcţia fact este apelată pentru a calcula 4!). Să urmărim diagramele din figurile 6.7. şi 6.8. La apelul funcţiei fact, valoarea parametrului de apel nr (nr=4) iniţializează parametrul formal n. Pe stivă se memorează adresa de revenire în funcţia apelantă (adr1) şi valoarea lui n (n=4) (figura 6.7.a.). Deoarece n>0, se execută intrucţiunea de pe ramura else (return n*fact(n-1)). Funcţia fact se autoapelează direct. Se memorează pe stivă noua adresă de revenire şi noua valoare a parametrului n (n=3) (figura 6.7.b.). La noul reapel al funcţiei fact, se execută din nou intrucţiunea de pe ramura else (return n*fact(n-1)). Se memorează pe stivă adresa de revenire şi noua valoare a parametrului n (n=2) (figura 6.7.c.). La noul reapel al funcţiei fact, se execută din nou intrucţiunea de pe ramura else (return n*fact(n-1)). Se memorează pe stivă adresa de revenire şi noua valoare a parametrului n (n=1) (figura 6.7.d.). La noul reapel al funcţiei fact, se execută din nou intrucţiunea de pe ramura else (return n*fact(n-1)). Se memorează pe stivă adresa de revenire şi noua valoare a parametrului n (n=0) (figura 6.7.e.). În acest moment n=0 şi se revine din funcţie cu valoarea 1 (1*fact(0)=1*1), la configuraţia stivei din figura 6.7.d.) (se curăţă stiva şi se ajunge la configuraţia din figura 6.7.d.). În acest moment n=1 şi se revine cu valoarea 2*fact(1)=2*1=2, se curaţă stiva şi se ajunge la configuraţia stivei din figura 6.7.c. În acest moment n=2 şi se revine cu valoarea 3*fact(2)=3*2=6, se curaţă stiva şi se ajunge la configuraţia stivei din figura 6.7.b. Se curăţă stiva şi se ajunge la configuraţia stivei din figura 6.7.a.. În acest moment n=3 şi se revine cu valoarea 4*fact(3)=4*6=24. O funcţie recursivă poate fi realizată şi iterativ. Modul de implementare trebuie ales în funcţie de problemă. Deşi implementarea recursivă a unui algoritm permite o descriere clară şi compactă, recursivitatea nu conduce la economie de memorie şi nici la execuţia mai rapidă a programelor. În general, se recomandă utilizarea funcţiilor recursive în anumite tehnici de programare, cum ar fi unele metode de căutare (backtracking). adr1 n=4

adr2 n=3 adr1 n=4

(a)

(b)

adr2 n=2 adr2 n=3 adr1 n=4

adr2 n=1 adr2 n=2 adr2 n=3 adr1 n=4

(c)

adr2 n=0 adr2 n=1 adr2 n=2 adr2 n=3 adr1 n=4

(d)

0!=1

24

4 fact 3

6 fact

2

2 fact

1

1 fact

0

(e)

1 fact

Figura 6.7. Configuraţia stivei

Figura 6.8. Parametri funcţiei fact Exerciţiu: Fie şirul lui Fibonacci, definit astfel: f(0)=0, f(1)=1, f(n)=f(n-1)+f(n-2), dacă n>1. Să se scrie un program care implementează algoritmul de calcul al şirului Fibonacci atât recursiv, cât şi iterativ. Să se compare timpul de execuţie în cele două situaţii. #include #include #include #include #include

<stdlib.h> <stdio.h>

long int iterativ_fib(long int n) {if (n==0) return 0; if (n==1) return 1; int i; long int a, b, c; a=0; b=1; for (i=2; i<=n; i++){ c=b; b+=a; a=c;} return b; 96

//varianta de implementare iterativă

CAPITOLUL 6 Funcţii }

//varianta de implementare recursivă

long int recursiv_fib(long int n) {if (n==0) return 0; if (n==1) return 1; long int i1=recursiv_fib(n-1); long int i2=recursiv_fib(n-2); return i1+i2; }

void main() {int n; clrscr(); cout<<MAXLONG<<'\n'; for (n=10; n<=40; n++) { clock_t t1, t2, t3; cout<
În exemplul anterior, pentru măsurarea timpului de execuţie s-a utilizat funcţia clock, al cărei prototip se află în header-ul time.h. Variabilele t1, t2 şi t3 sunt de tipul clock_t, tip definit în acelaşi header. Constanta simbolică CLK_TCK defineşte numărul de bătăi ale ceasului, pe secundă. În general, orice algoritm care poate fi implementat iterativ, poate fi implementat şi recursiv. Timpul de execuţie a unei recursii este semnificativ mai mare decât cel necesar execuţiei iteraţiei echivalente. Exerciţiu: Să se implementeze şi să se testeze un program care: a) Generează aleator şi afişează elementele unui vector ; b) Sortează aceste elemente, crescător, aplicând metodele de sortare BubbleSort, InsertSort, şi QuickSort; c) Să se compare viteza de sortare pentru vectori de diverse dimensiuni (10,30,50,100 elemete). 

Metoda BubbleSort a fost prezentată în capitolul 4.



Metoda QuickSort reprezintă o altă metodă de sortare a elementelor unui vector. Algoritmul este recursiv: se împarte vectorul în două partiţii, faţă de un element pivot (de obicei, elementul din "mijlocul vectorului"). Partiţia stângă începe de la indexul i (la primul apel i=0), iar partiţia dreaptă se termină cu indexul j (la primul apel j=n -1) (figura 6.9.).

0

i

j

pivot

... n-1

....

0

n-1 j

i

Figura 6.9. Sortare prin metoda QuickSort Partiţia stângă este extinsă la dreapta (i incrementat) până când se găseşte un element mai mare decât pivotul; partiţia dreaptă este extinsă la stânga (j decrementat) până când se găseşte un element mai mic decât pivotul. Cele două elemente găsite, vect[i] şi vect[j], sunt interschimbate. Se reia ciclic extinderea partiţiilor până când i şi j se "încrucişează" (i devine mai mare ca j). În final, partiţia stângă va conţine elementele mai mici decât pivotul, iar partiţia dreaptă - elementele mai mari decât pivotul, dar nesortate.

97

CAPITOLUL 6 Funcţii

Algoritmul este reluat prin recursie pentru partiţia stângă (cu limitele între 0 şi j ), apoi pentru partiţia dreaptă (cu limitele între i şi n-1 ). Recursia pentru partea stângă se opreşte atunci când j atinge limita stângă (devine 0), iar recursia pentru partiţia dreaptă se opreşte când i atinge limita dreaptă (devine n-1).

SUBALGORITM QuickSort (vect[ ], stg, drt) ÎNCEPUT SUBALGORITM i ← stg j ← drt DACĂ i < j ATUNCI ÎNCEPUT pivot=vect[(stg+drt)/2] CÂT TIMP i <= j REPETĂ

//la primul apel stg = 0 si drt = n - 1

//extinderea partiţiilor stânga şi dreapta până când i se încrucişează cu j ÎNCEPUT CÂT TIMP ipivot REPETĂ j = j - 1 DACĂ i<=j ATUNCI ÎNCEPUT //interschimbă elementele vect[i] şi vect[j] aux ← vect[i] vect[i] ← vect[j] vect[j] ← aux i ← i+1 j ← j-1 SFÂRŞIT SFÂRŞIT DACĂ j > stg ATUNCI // partiţia stângă s-a extins la maxim, apel qiuckSort pentru ea CHEAMĂ QuickSort(vect, stg, j) DACĂ i < drt ATUNCI // partiţia dreaptă s-a extins la maxim, apel qiuckSort pentru ea CHEAMĂ QuickSort(vect, i, drt) SFÂRŞIT SFÂRŞIT SUBALGORITM

Metoda InsertSort (metoda inserţiei) Metoda identifică cel mai mic element al vectorului şi îl schimbă cu primul element. Se reia procedura pentru vectorul iniţial, fără primul element şi se caută minimul în acest nou vector, etc. 

SUBALGORITM InsertSort (vect[ ], nr_elem) ÎNCEPUT SUBALGORITM CÂT TIMP i< nr_elem REPETĂ ÎNCEPUT pozMin ← cautMinim(vect, i) // se apelează algoritmul cautMinim aux ← vect[i] vect[i] ← vect[pozMin] vect[pozMin] ← aux i ← i+1 SFÂRŞIT SFÂRŞIT SUBALGORITM

Funcţia cautMin(vect[ ], indexIni, nr_elem) caută elementul minim al unui vector, începând de la poziţia indexIni şi returnează poziţia minimului găsit. Mod de implementare (Se va completa programul cu instrucţiunile care obţin şi afişează timpului necesar ordonării prin fiecare metodă. Se vor compara rezultatele pentru un vector de 10, 30, 50, 100 elemente): 98

CAPITOLUL 6 Funcţii #include <stdlib.h> #include <stdio.h> #include #define TRUE 1 #define FALSE 0 void gener(double v[], int n)

//functia de generare aleatoare a elementelor vectorului v, cu n elemente {for (int i=0; i
//functia de afisare a vectorului {for (int i=0; i
//functie de "duplicare "a unui vector; copie vectorul v in vectorul v1 {for (int i=0; i=v[i+1]){ double aux=v[i]; v[i]=v[i+1]; v[i+1]=aux; // printf("Interschimbare element %d cu %d",i,i+1); // afis(v,n); gata=FALSE;} } } int cautMin(double v[], int indexIni, int n)

// cauta elementul minim, incepând de la pozitia indexIni, inclusiv { double min=v[indexIni]; int pozMin=indexIni; for (int i=indexIni; i
99

CAPITOLUL 6 Funcţii while (j>stg && v[j]>pivot) j--; if (i<=j){ aux=v[i];v[i]=v[j];v[j]=aux; //interschimbare elemente i++; j--; } } if (j>stg) if (i
quickSort(v, stg, j); quickSort(v, i, drt);

} } void main() { clock_t ti,tf; int n; //n = nr elemente vector printf("Nr componente vector:"); scanf("%d", &n); double v[200], v1[200], v2[200], v3[200]; gener(v, n); copie_vect(v1,v,n); printf("\nInainte de ordonare: v1="); afis(v1, n); ti=clock(); bubbleSort(v1,n); tf=clock(); printf("\nDupa ordonare : v1=");afis(v1, n); printf("%10.7f", dif_b); printf("\n\n****** INSERT SORT ******\n"); copie_vect(v2,v,n); printf("\nInainte de ordonare INSERT: v2="); afis(v2, n); insertSort(v2,n); printf("\nDupa ordonare INSERT: v2=");afis(v2, n); int st=0; int dr=n-1; copie_vect(v3, v, n); printf("\n\n****** QUICK SORT ******\n"); printf("\nInainte ordonare QUICK: v3="); afis(v3, n); quickSort(v3, st, dr); printf("\nDupa ordonare QUICK: v3="); afis(v3, n); }

6.11. POINTERI CĂTRE FUNCŢII Aşa cum s-a evidenţiat în capitolul 5, exista trei categorii de variabilele pointer:  Pointeri cu tip;  Pointeri generici (void);  Pointeri către funcţii. Pointerii către funcţii sunt variabile pointer care conţin adresa de început a codului executabil al unei funcţii. Pointerii către funcţii permit:  Transferul ca parametru al adresei unei funcţii;  Apelul funcţiei cu ajutorul pointerului. Declaraţia unui pointer către funcţie are următoarea formă: tip_val_intoarse (*nume_point)(lista_declar_param_formali); , unde: nume_point este un pointer de tipul “funcţie cu rezultatul tipul_valorii_întoarse”. În declaraţia anterioară trebuie remarcat rolul parantezelor, pentru a putea face distincţie între declaraţia unei funcţii care întoarce un pointer şi declaraţia unui pointer de funcţie: tip_val_intoarse * nume_point (lista_declar_param_formali); tip_val_intoarse (* nume_point)(lista_declar_param_formali); Exemplu: int f(double u, int v); //prototipul funcţiei f int (*pf)(double, int); //pointer către funcţia f int i, j; double d; pf=f; j=*pf(d, i);

//atribuie adresa codului executabil al funcţiei f pointerului pf //apelul funcţiei f, folosind pf

100

CAPITOLUL 6 Funcţii

Exerciţiu: Să se implementeze un program care calculează o funcţie a cărei valoare este integrala altei funcţii. Pentru calculul integralei se va folosi metoda trapezelor. f(x)

Relaţia pentru calculul integralei prin metoda b

trapezelor pentru

∫ f ( x)dx

este:

a

n −1

I = (f(a)+f(b))/2 +

∑ f ( a + k * h)

a x

k =1

Să se calculeze

b

∫1+ a

0.2 + e

x x . . . . .x x b h

| x| 2

dx,

Figura 6.9. Calculul integralei prin metoda trapezelor

0.3 + ln(1 + x 4 )

cu o eroare mai mică decât eps (valoarea erorii introdusă de la tastatură). #include #include <math.h> #include double functie(double x) {return sqrt(0.1+exp(0.5*fabs(x)))/(1+sqrt(0.3+log(1+pow(x,4))));} double intrap(double a, double b, long int n, double (*f)(double)) {double h,s=0; long k; if (a>=b) return 0; if (n<=0) n=1; h=(b-a)/n; for (k=1; k>p; cout<<"Marg. sup:";cin>>q; cout<<"Eroare:";cin>>eps; j=1; double d1=intrap(p, q, j, functie); do{ j*=2; if (j>MAXLONG || j<0) break; d2=intrap(p, q, j, functie); dif=fabs(d1-d2); d1=d2; cout<<"Nr.intervale "<<j<<" Val.integralei "<eps); cout<<"\n\n-----------------------------------------------\n"; cout<<"Val. integralei: "<
ÎNTREBĂRI ŞI EXERCIŢII Chestiuni teoretice 1. Asemănări între transferul parametrilor unei funcţii prin pointeri şi prin referinţă. 2. Caracteristicile modului de transfer a parametrilor unei funcţii prin pointeri. 3. Caracteristicile variabilelor globale. 4. Caracteristicile variabilelor locale.

5. Care este diferenţa între antetul unei funcţii şi prototipul acesteia? 6. Care sunt modurile de alocare a memoriei? 7. Care sunt modurile de transfer a parametrilor unei funcţii? 8. Care sunt operatorii din C++ care permit alocarea/dezalocarea dinamică a memoriei? 101

CAPITOLUL 6 Funcţii 9. Ce clase de memorare cunoasteţi?

23. Diferenţe între modurile de transfer a parametrilor prin valoare şi prin referinţă. 24. Diferenţe între modurile de transfer a parametrilor unei funcţii prin pointeri şi prin referinţă. 25. Din apelul funcţiei printf se poate omite specificatorul de format? 26. Din ce este formată o funcţie? 27. În ce zonă de memorie se rezervă spaţiu pentru variabilele globale? 28. O funcţie poate fi declarată în corpul altei funcţii? 29. O funcţie poate fi definită în corpul unei alte funcţii? 30. Parametrii formali ai unei funcţii sunt variabile locale sau globale? 31. Transferul parametrilor prin valoare. 32. Ce rol au parametrii formali ai unei funcţii?

10. Ce este domeniul de vizibilitate a unei variabile? 11. Ce este prototipul unei funcţii? 12. Ce este timpul de viaţă a unei variabile? 13. Ce loc ocupă declaraţiile variabilelor locale în corpul unei funcţii? 14. Ce reprezintă antetul unei funcţii? 15. Ce rol are declararea funcţiilor? 16. Ce se indică în specificatorul de format al funcţiei printf ? 17. Ce sunt funcţiile cu număr variabil de parametri? Exemple. 18. Ce sunt funcţiile cu parametri impliciţi? 19. Ce sunt pointerii către funcţii? 20. Ce sunt variabilele referinţă? 21. Cine determină timpul de viaţă şi domeniul de vizibilitate ale unei variabile? 22. Comparaţie între declararea şi definirea funcţiilor.

Chestiuni practice 1. Să se implementeze programele cu exemplele prezentate. 2. Să se scrie programele pentru exerciţiile rezolvate care au fost prezentate. 3. Să se modularizeze programele din capitolul 4 (3.a.-3.g., 4.a.-4.i, 5.a.-5.h.), prin implementarea unor funcţii (funcţii pentru: citirea elementelor unui vector, afişarea vectorului, calculul sumei a doi vectori, calculul produsului scalar a doi vectori, aflarea elementului minim din vector, citire a unei matrici, afişare a matricii, calculul transpusei unei matrici, calculul sumei a două matrici, calculul produsului a două matrici, calculul produsului elementelor din triunghiul haşurat, etc.). 4. Să se rescrie programele care rezolvă exerciţiile din capitolul 3, folosind funcţii (pentru calculul factorialului, aflarea celui mai mare divizor comun, ordonarea lexicografică a caracterelor, etc). Utilizaţi funcţiile de intrare/ieşire printf şi scanf. 5. Să se scrie un program care citeşte câte două numere, până la întâlnirea perechii de numere 0, 0 şi afişează, de fiecare dată, cel mai mare divizor comun al acestora, folosind o funcţie care îl calculează. 6. Se introduce de la tastatura un număr întreg. Să se afişeze toţi divizorii numărului introdus. Se va folosi o funcţie de calcul a celui mai mare divizor comun a 2 numere. 7. Secvenţele următoare sunt corecte din punct de vedere sintactic? Dacă nu, identificaţi sursele erorilor. void a(int x, y) {cout<<"x="<<x<<" y="<
Variabilele de acelaşi tip pot apare ca operanzi ai operatorului de atribuire. În acest caz atribuirile se fac membru cu membru. În exemplul anterior am declarat şi iniţializat variabila a1, de tip angajat. Declarăm şi variabila a2, de acelaşi tip. Dacă dorim ca membrii variabilei a2 să conţină aceleaşi valori ca membrii variabilei a1 (a1 si a2 de tip angajat), putem folosi operatorul de atribuire, ca în exemplul următor: struct angajat a2; a2=a1;

Aşa cum s-a observat din exemplul anterior, structurile pot avea ca membri tablouri (structura angajat are ca membrii tablourile de caractere loc_naştere[20], nume[20], prenume[20]). Deasemenea, variabilele de tip definit prin structură pot fi grupate în tablouri. Exemplu: struct persoana{ char nume[20], prenume[20]; int nr_copii; double salariu; char loc_nastere[20]; }angajati[100]; /* S-au declarat noul tip numit persoana şi variabila numită angajati, care este un vector (cu maxim 100 de elemente), ale cărui elemente sunt de tipul persoana */

//Iniţializarea elementelor vectorului angajaţi[100] for (int i=0; i<100; i++){ cout<<"Intruduceti datele pentru angajatul "<
CAPITOLUL 7

Tipuri de date definite de utilizator cout<<"Numele :"; cin>>angajati[i].nume; cout<<"Prenumele :"; cin>>angajaţi[i].prenume; cout<<"Nr. copii:"; cin>> angajaţi[i].nr_copii; cout<<"Locul naşterii:"; cin>> angajaţi[i].loc_naştere;

}

Limbajul C permite definirea de structuri ale căror membri sunt tot structuri: Exemplu: struct data{ int zi; char luna[11]; int an; }; struct persoana{ char nume[20], prenume[20]; int nr_copii; double salariu; char loc_naştere[20]; struct data data_naşterii; }; struct p1={"Popescu","Vasile",1,4000000,"Galati",{22,"Mai",1978}};

persoana

//Modificarea membrului data_naşterii pentru variabila p1 de tip persoana: p1.data_naşteri.zi=23; strcpy(p1.data_naşteri.luna, "Februarie"); p1.data_nasteri.an=1980;

Dacă se doreşte transmiterea ca parametri ai unor funcţii a datelor de tip definit de utilizator prin structuri, acest lucru se realizează numai cu ajutorul pointerilor spre noul tipi. De exemplu, este necesar ca variabila p1, de tip persoana, să fie prelucrată în funcţia f, În acest caz, funcţia va primi ca parametru un pointer spre tipul persoana. Funcţia va avea prototipul: void f(struct persoana *q);

Apelul funcţiei se realizează astfel: f(&p1); În corpul funcţiei f, accesul la membrii varibilei q, de tip persoana, se realizează astfel: (*q).nume; (*q).prenume; (*q).data_naşterii.an;

, etc. Pentru a simplifica construcţiile anterioare, se foloseste operatorul de selecţie indirectă (->): q->nume; q->prenume; q->data_naşterii.an

, etc.

Structurile sunt utilizate în mod frecvent la definirea unor tipuri de date recursive (în implementarea listelor, arborilor, etc.). Un tip de date este direct recursiv dacă are cel puţin un membru care este de tip pointer spre el însuşi. Exemplu: struct nod{ char nume[100]; int an; struct nod *urmator; };

Exerciţiu: Să se citească informaţiile despre angajaţii unei întreprinderi, folosind o funcţie de citire. Să se afişeze apoi informaţiile despre angajaţi. #include <stdio.h> #include struct persoana{ char nume[20];int varsta;int salariu; }; void cit_pers(struct persoana *ptr_pers) {printf("Nume angajat:"); scanf("%s",ptr_pers->nume); 105

CAPITOLUL 7 Tipuri de date definite de utilizator printf("Varsta angajat:"); scanf("%d", &ptr_pers->varsta); printf("Salariu angajat:"); scanf("%d", &ptr_pers->salariu); } void main() {struct persoana *p; //pointer catre date de tip persoana int nr_ang; clrscr(); printf("Nr. angajati:");scanf("%d", &nr_ang); p=new persoana[nr_ang]; //alocare dinamica a memoriei pentru cei nr_ang angajati for (int i=0; i
şirul specificator de format se continuă pe rândul următor (folosirea caracterului \ pentru continuare).

7.3. CÂMPURI DE BIŢI Limbajul C oferă posibilitatea de prelucrare a datelor la nivel de bit. De multe ori se utilizează date care pot avea doar 2 valori (0 sau 1), cum ar fi datele pentru controlul unor dispozitive periferice, sau datele de valori mici. Declarând aceste date de tip int sau short int, în memorie se rezervă 16 biţi. Alocarea unui număr atât de mare de locaţii de memorie nu este justificată, de aceea, limbajul C oferă posibilitatea declarării unor date pentru care să se aloce un număr specificat de biţi (alocare pe biţi). Definiţie: Un şir de biţi adiacenţi formeaza un câmp de biţi. Câmpurile de biţi se pot declara ca membri ai unei structuri, astfel: struct identificator_tip_struct { tip_elem_1 identificator_elem_1:lungime1; tip_elem_2 identificator_elem_2:lungime2; . . . tip_elem_3 identificator_elem_3:lungime3; } lista_identif_var_struct; Lungime1, lungime2, etc. reprezintă lungimea fiecărui câmp de biţi, rezervat pentru memorarea membrilor.

Câmpurile se alocă de la biţii de ordin inferior ai unui cuvânt (2 octeţi), către cei de ordin superior (figura 7.1). Exemplu: struct { int unsigned int int } x, y;

a: b: c:

. . .

2; 1; 3;

. . .

c

b

a

Figura 7.1. Câmpurile de biţi a, b, c

Câmpurile se referă ca orice membru al unei structuri, prin nume calificate: Exemplu: x.a = -1; x.b = 3; x.c = 4;

Utilizarea câmpurilor de biţi impune următoarele restricţii:  Tipul membrilor poate fi int sau unsigened int. 106

CAPITOLUL 7   

Tipuri de date definite de utilizator

Lungime este o constantă întreagă din intervalul [0, 31]; Un câmp de biţi nu poate fi operandul unui operator de referenţiere. Nu se pot organiza tablouri de câmpuri de biţi.

Datorită restricţiilor pe care le impune folosirea câmpurilor de biţi, cât şi datorită faptului că aplicaţiile care folosesc astfel de structuri de date au o portabilitate extrem de redusă (organizarea memoriei depinzând de sistemul de calcul), se recomandă folosirea câmpurilor de biţi cu precauţie, doar în situaţiile în care se face o economie substanţială de memorie.

7.4. DECLARAŢII DE TIP Limbajul C permite atribuirea unui nume pentru un tip (predefinit sau utilizator) de date. Pentru aceasta se folosesc delcaraţiile de tip. Forma generală a acestora este: typedef tip nume_tip; Nume_tip poate fi folosit la declararea datelor în mod similar cuvintelor cheie pentru tipurile predefinite. Exemplu: //1 typedef int INTREG; INTREG x, y; INTREG z=4;

//2 typedef struct{ double parte_reală; double parte_imaginară; } COMPLEX; COMPLEX x, y;

7.5. UNIUNI Aceeaşi zonă de memorie poate fi utilizată pentru păstrarea unor obiecte (date) de diferite tipuri, prin declararea uniunilor. Uniunile sunt similare cu structurile, singura diferenţă constând în modul de memorare. Declararea uniunilor: union identificator_tip_uniune { lista de declaratii_membrii; } lista_identificatori_variabile; Spaţiul de memorie alocat corespunde tipului membrului de dimensiune maximă. Tipul uniune foloseşte aceeaşi zonă de memorie, care va conţine informaţii organizate în mai multe moduri, corespunzător tipurilor membrilor. Exemplu: union numeric{ int i; float f; double d; } num; num.i = 20; num.f = 5.80; cout<<sizeof(num)<<'\n';

num.i num.f num.d

//8

20

5.80 num

Figura 7.2. Modul de alocare a memoriei pentru variabila num (uniune) - 8 octeţi

Pentru variabile num se rezervă 8 octeţi de memorie, dimensiunea maximă a zonei de memorie alocate membrilor (pentru int s-ar fi rezervat 2 octeţi, pentru float 4, iar pentru double 8). În exemplul anterior, în aceeaşi zonă de memorie se păstrează fie o valoare întreagă (num.i=20), fie o valoare reală, dublă precizie (num.f=5.80).

107

CAPITOLUL 7

Tipuri de date definite de utilizator

Dacă pentru definirea tipului numeric s-ar fi folosit o structură, modul de alocare a memoriei ar fi fost cel din figura 7.3. struct numeric{ int i; float f; double d; } num; num.i = 20; num.f = 5.80; cout<<sizeof(num)<<'\n';

num.d num.f

5.80

num.i

20

num

Figura 7.3. Modul de alocare a memoriei pentru variabila num (structură) - 14 octeţi

//14

7.6. ENUMERĂRI Tipul enumerare asociază fiecărui identificator o consatantă întreagă. Sintaxa declaraţiei: enum identificator_tip_enumerare { identif_elem1 = const1, . . . } lista_identif_variabile; Din declaraţie pot lipsi fie identificator_tip_enumerare, fie lista_identif_variabile. Pentru fiecare element al enumerării, constanta poate fi asociată în mod explicit (ca în declaraţia anterioară), fie implicit. În modul implicit nu se specifică nici o constantă, iar valoarea implicită este 0 pentru primul element, iar pentru restul elementelor, valoarea precedentă incrementată cu 1. Enumerările se folosesc în situaţiile în care variabilele pot avea un număr mic de valori întregi, asociind un nume sugestiv pentru fiecare valoare. Exemplu: //1 enum boolean {FALSE, TRUE}; //definirea tipului boolean cu elementele FALSE si TRUE //declaratie echivalenta cu enum boolean {FALSE=0, TRUE=1}; cout<<"FALSE este "<
//FALSE este 0

//2 typedef enum temperatura {mica=-10, medie=10, mare=80}; //tipul enumerare temperatura, cu elementele mica (de valoare -10), medie (valoare 10), mare

(valoare 80) temperatura t1, t2; t1=medie; cout<<"t1="<
//declararea variabilelor t1, t2 de tip enumerare temperatura //t1=10

Exerciţiu: Să se citească (cu ajutorul unei funcţii de citire) următoarele informaţii despre elevii participanţi la un concurs de admitere: nume, numărul de înscriere şi cele trei note obţinute. Să se afişeze, printr-o funcţie, informaţiile citite. Să se afişeze o listă cu elevii participanţi la concurs, ordonaţi alfabetic, notele şi media obţinută (funcţie de ordonare, funcţie de calculare a mediei). Să se afişeze lista elevilor înscrişi la concurs, în ordinea descrescătoare a mediilor. Sunt prezentate câteva modalităţi de implementare. În aceste variante apar doar funcţia cit_elev (de citire) şi main. S-a definit tipul elev. Se lucrează cu vectori de tip elev. În funcţia cit_elev se validează fiecare notă. Se va observa modul de acces la membri structurii în funcţia cit_elev. Dezavantajul principal al acestui mod de implementare îl constituie risipa de memorie, deoarece în funcţia main se rezervă o zonă de memorie continuă, pentru 100 de elemente de tip elev (100*sizeof(elev)). #include #include typedef struct elev{ char nume[20];int nr_matr;int note[3]; }; //definirea tipului elev void cit_elevi(elev a[], int n) 108

CAPITOLUL 7 Tipuri de date definite de utilizator {for (int i=0; i>a[i].nume; //citirea numelui unui elev cout<<"Nr. insriere:"; cin>>a[i].nr_matr; for (int j=0; j<3; j++){ // citirea notelor obtinute do{ cout<<"Nota :"<<j+1<<" ="; cin>>a[i].note[j]; if (a[i].note[j]<0 || a[i].note[j]>10) //validarea notei cout<<"Nota incorecta!....Repeta!\n"; }while (a[i].note[j]<0 || a[i].note[j]>10); } } } void main() { int nr_elevi; clrscr(); cout<<"Nr. elevi:";cin>>nr_elevi; elev p[100]; //declararea tabloului p, de tip elev cit_elevi(p, nr_elevi); //apel functie }

În varianta următoare, se lucrează cu pointeri către tipul elev, iar memoria este alocată dinamic. typedef struct elev{ char nume[20];int nr_matr;int note[3]; }; //definirea tipului elev void cit_elevi(elev *a, int n) { for (int i=0; i>(a+i)->nume; //sau cin>>(*(a+i)).nume; cout<<"Nr. insriere:"; cin>>(a+i)->nr_matr; for (int j=0; j<3; j++){ do{ cout<<"Nota :"<<j+1<<" ="; cin>>(a+i)->note[j]; if ((a+i)->note[j]<0 || (a+i)->note[j]>10) cout<<"Nota incorecta!....Repeta!\n"; }while ((a+i)->note[j]<0 || (a+i)->note[j]>10); } } } void main() { int nr_elevi; clrscr(); cout<<"Nr. elevi:";cin>>nr_elevi; elev *p; //declararea pointerului p, către tipul elev p=new elev[nr_elevi]; //alocarea dinamică a memoriei, pentru un tablou cu nr_elevi elemente cit_elevi(p, nr_elevi); //apel functie }

Implementarea tuturor funcţiilor: #include <stdio.h> #include <string.h> #define DIM_PAG 24 //dimensiunea paginii de afisare #define FALSE 0 #define TRUE 1 void ord_medii(elev *a, int n) { int gata =FALSE;int i;double med1, med2;elev aux; while (!gata){ gata=TRUE; for (i=0; i<=n-2; i++){ med1=0;med2=0; 109

CAPITOLUL 7

Tipuri de date definite de utilizator for (int j=0; j<3; j++){ med1+=(a+i)->note[j]; med2+=(a+i+1)->note[j];

//calculul mediilor pentru elementele vecine } med1/=3; med2/=3; if (med1<med2){ aux=*(a+i); *(a+i)=*(a+i+1);*(a+i+1)=aux; gata=FALSE; } }

}

} void ord_alf(elev *a, int n) { int gata =FALSE;int i;double med1, med2;elev aux; while (!gata){ gata=TRUE; for (i=0; i<=n-2; i++){ if (strcmp( (a+i)->nume,(a+i+1)->nume) >0){ aux=*(a+i); *(a+i)=*(a+i+1);*(a+i+1)=aux; gata=FALSE;} } } } void cit_elevi(elev *a, int n);

// functie implementata anterior void antet_afis(const char *s) {printf("%s\n", s); } void afis_elev(elev *a, int n, char c) {clrscr(); if (c=='A') antet_afis(" LISTA INSCRISILOR \n"); if (c=='O') antet_afis(" LISTA ALFABETICA \n"); if (c=='R') antet_afis(" LISTA MEDII \n"); printf("Nr.crt.|Nr. Matricol| NUME |Nota1|Nota2|Nota3| MEDIA\ |\n"); printf("----------------------------------------------------------------\ \n"); int lin=3; for (int i=0; inr_matr,(a+i)->nume); double med=0; for (int j=0; j<3; j++){ printf("%-5d|", (a+i)->note[j]); med+=(a+i)->note[j]; } med/=3;printf("%-9.2f|\n", med);lin++; if (lin==(DIM_PAG-1)){ printf(" Apasa o tasta...."); getch(); clrscr(); if (c=='A') antet_afis(" LISTA INSCRISILOR \n"); if (c=='O') antet_afis(" LISTA ALFABETICA \n"); if (c=='R') antet_afis(" LISTA MEDII \n"); printf("Nr.crt.| NUME |Nota1|Nota2|Nota3| MEDIA\ |\n"); printf("-----------------------------------------------------\ \n"); int lin=3; } } 110

CAPITOLUL 7

Tipuri de date definite de utilizator

printf(" Apasa o tasta...."); getch(); } void main() { int nr_elevi; clrscr(); cout<<"Nr. elevi:";cin>>nr_elevi; elev *p; p=new elev[nr_elevi]; cit_elevi(p, nr_elevi); afis_elev(p, nr_elevi, 'A');//afisarea inscrisilor ord_medii(p, nr_elevi); afis_elev(p, nr_elevi, 'R');//afisarea in ordinea mediilor ord_alf(p, nr_elevi); //ordonare alfabetica afis_elev(p, nr_elevi, 'O');//afisarea in ordinea mediilor }

descrescatoare

a

descrescatoare

a

S-au implementet următoarele funcţii: cit_elevi - citeşte informaţiile despre elevii înscrişi. afis_elevi - afişează informaţiile despre elevi. Această funcţie este folosită pentru cele trei afişări (lista înscrişilor, lista alfabetică şi clasamentul în ordinea descrescătoare a mediilor). Afişarea se realizează cu ajutorul funcţiei printf, care permite formatarea datelor afişate. Afişarea se realizează ecran cu ecran (se foloseşte variabila lin care contorizează numărul de linii afişate), cu pauză după fiecare ecran. La începutul fiecărei pagini se afişează titlul listei - corespunzător caracterului transmis ca parametru funcţiei - şi capul de tabel. Deasemenea, pentru fiecare elev înscris se calculează media obţinută (variabila med). ord_medii - ordonează vectorul de elevi (transmis ca parametru, pointer la tipul elev), descrescător, după medii. Se aplică metoda BubbleSort, comparându-se mediile elementelor vecine (med1 reprezintă media elementului de indice i, iar med2 - a celui de indice i+1) ale vectorului. ord_alf - ordonează vectorul de elevi (transmis ca parametru, pointer la tipul elev), crescător, după informaţia conţinută de membrul nume. Pentru compararea numelor se foloseşte funcţia strcmp. Deoarece este foarte probabil ca vectorul înscrişilor să aibă multe elemente, pentru ordonări, ar fi fost mai eficientă metoda QuickSort; s-a folosit BubbleSort pentru a nu complica prea mult problema.

ÎNTREBĂRI ŞI EXERCIŢII Chestiuni teoretice 1. Variabilele tablou şi variabilele de tip definit de utilizator sunt exemple de variabile compuse (reprezintă date structurate). Care este, totuşi, deosebirea dintre ele? 2. Ce posibilităţi de definire a unor noi tipuri de date vă oferă limbajul C/C++? 3. În ce constă diferenţa dintre structuri şi uniuni?

4. Cum se numesc componentele unei structuri? 5. Ce restricţii impune folosirea câmpurilor de biţi? 6. Există vreo restricţie referitoare la tipul membrilor unei structuri? Dacă da, care este aceasta?

Chestiuni practice 1. 2. 3. a)

Să se implementeze programele cu exemplele prezentate. Să se scrie programele pentru exerciţiile rezolvate care au fost prezentate. Realizaţi următoarele modificări la exerciţiul prezentat la sfârşitul capitolului: Completaţi cu o funcţie de calcul şi afişare a mediei notelor tuturor candidaţilor pentru fiecare probă (media tuturor elevilor la proba1, media la proba2, etc). b) Modificaţi lista alfabetică, astfel încât la elevii cu medie peste 5, să apară (alături de medie) mesajul "Promovat", iar la ceilalţi, mesajul "Nepromovat".

111

CAPITOLUL 7

Tipuri de date definite de utilizator

c) Considerând că rezultatelor obţinute sunt utilizate la un concurs de admitere, la care există N locuri (N introdus de la tastatură), şi de faptul că pentru a fi admis media trebuie să fie cel puţin 5, să se afişeze lista admişilor şi lista respinşilor, în ordinea descrescătoare a mediilor, în limita locurilor disponibile. 2. Să se scrie un program care să permită memorarea datelor privitoare la angajaţii unei firme mici: nume angajat, adresă, număr copii, sex, data naşterii, data angajării, calificare, salariul brut. Se vor implementa următoarele funcţii: a) Citirea informaţiilor despre cei N angajaţi (N introdus de la tastatură); b) Căutarea - după nume - a unui angajat şi afişarea informaţiilor despre acesta; c) Modificarea informaţiilor despre un anumit angajat; d) Lista alfabetică a angajaţilor, în care vor apare: nume, adresă, data angajării, calificare, salariu; e) Lista angajaţilor în ordone descrescătoare a vechimii; f) Lista angajatilor cu un anumit numar de copii, C, introdus de la tastatură; g) Lista angajaţilor cu vârsta mai mare decât V (V introdus de la tastatură); h) Salariul minim, salariul mediu şi cel maxim din firmă; i) Lista de salarii, în care vor apare: numele, calificarea, salariul brut şi salariul net. La sfârşitul listei vor apare totalurile pentru salariile brute, impozite, salarii nete. Pentru calculul salariului net se aplică următoarele reguli de impozitare: i.1) I=15% pentru salariul brut (SB)<600000 i.2) I=50000+20% pentru 600000<=SB<1500000 (20% din ceea ce depăşeşte 600000) i.3) I=100000+30% pentru 1500000<=SB<3000000 i.4) I=250000+40% pentru 3000000<=SB<15000000 i.5) I=45% pentru SB>=1500000

112

CAPITOLUL 8

Fişiere

FIŞIERE 8.1. Caracteristicile generale ale fişierelor 8.2. Deschiderea unui fişier 8.3. Închiderea unui fişier 8.4. Prelucrarea fişierelor text 8.4.1. Prelucrarea la nivel de caracter 8.4.2. Prelucrarea la nivel de cuvânt

8

8.4.3. Prelucrarea la nivel de şir de caractere 8.4.4. Intrări/ieşiri formatate 8.5. Intrări/ieşiri binare 8.6. Poziţionarea într-un fişier 8.7. Funcţii utilitare pentru lucrul cu fişiere 8.8. Alte operaţii cu fişiere

8.1. CARACTERISTICILE GENERALE ALE FIŞIERELOR Noţiunea de fişier desemnează o colecţie de informaţii memorată pe un suport permanent (de obicei discuri magnetice), percepută ca un ansamblu, căreia i se asociază un nume (în vederea conservării şi regăsirii ulterioare). Caracteristicile unui fişier (sub sistem de operare MS-DOS) sunt :  Dispozitivul logic de memorare (discul);  Calea (în structura de directoare) unde este memorat fişierul;  Numele şi extensia;  Atributele care determină operaţiile care pot fi efectuate asupra fişierului (de exemplu: R-read-only citire; W-write-only scriere; RW-read-write citire/scriere; H-hidden - nu se permite nici măcar vizualizarea; S-system - fişiere sistem asupra cărora numai sistemul de operare poate realiza operaţii operaţii, etc.). Lucrul cu fişiere în programare oferă următoarele avantaje:  Prelucrarea de unei cantităţi mari de informaţie obţinută din diverse surse cum ar fi execuţia prealabilă a unui alt program;  Stocarea temporară pe suport permanent a informaţiei în timpul execuţiei unui program pentru a evita supraîncărcarea memoriei de lucru;  Prelucrarea aceleeaşi colecţii de informaţii prin mai multe programe. În limbajul C, operaţiile asupra fişierelor se realizează cu ajutorul unor funcţii din biblioteca standard (stdio.h). Transferurile cu dipozitivele periferice (tastatură, monitor, disc, imprimantă, etc.) se fac prin intermediul unor dispozitive logice identice numite stream-uri (fluxuri) şi prin intermediul sistemului de operare. Un flux de date este un fişier sau un dispozitiv fizic tratat printr-un pointer la o structură de tip FILE (din header-ul stdio.h). Când un program este executat, în mod automat, se deschid următoarele fluxuri de date predefinite, dispozitive logice (în stdio.h):  stdin (standard input device) - dispozitivul standard de intrare (tastatura) - ANSII C;  stdout (standard output device) - dispozitivul standard de ieşire (monitorul) - ANSII C;  stderr (standard error output device) - dispozitivul standard de eroare (de obicei un fişier care conţine mesajele de eroare rezultate din execuţia unor funcţii) - ANSII C;  stdaux (standard auxiliary device) - dispozitivul standard auxiliar (de obicei interfaţa serială auxiliară) - specifice MS-DOS;  stdprn (standard printer) - dispozitivul de imprimare - specifice MS-DOS. În abordarea limbajului C (impusă de stdio.h), toate elementele care pot comunica informaţii cu un program sunt percepute - în mod unitar - ca fluxuri de date. Datele introduse de la tastatură formează un fişier de intrare (fişierul standard de intrare). Datele afişate pe monitor formează un fişier de ieşire (fişierul standard de ieşire). Sfârşitul oricărui fişier este indicat printr-un marcaj de sfârşit de fişier (end of file). În cazul fişierului standard de intrare, sfârşitul de fişier se generează prin Ctrl+Z (^Z) (sub MS-DOS) (sau Ctrl+D sub Linux). Acest caracter poate fi detectat prin folosirea constantei simbolice EOF (definită în 113

CAPITOLUL 8

Fişiere

fişierul stdio.h), care are valoarea -1. Această valoare nu rămane valabilă pentru fişierele binare, care pot conţine pe o poziţie oarecare caracterul ’\x1A’. De obicei, schimbul de informaţii dintre programe şi periferice se realizează folosind zone tampon. O zonă tampon păstrează una sau mai multe înregistrări. Prin operaţia de citire, înregistrarea curentă este transferată de pe suportul extern în zona tampon care îi corespunde, programul având apoi acces la elementele înregistrării din zona tampon. În cazul operaţiei de scriere, înregistrarea se construieşte în zona tampon, prin program, fiind apoi transferată pe suportul extern al fişierului. În cazul monitoarelor, înregistrarea se compune din caracterele unui rând. De obicei, o zonă tampon are lungimea multiplu de 512 octeţi. Orice fişier trebuie deschis inainte de a fi prelucrat, iar la terminarea prelucrării lui, trebuie închis. Fluxurile pot fi de tip text sau de tip binar. Fluxurile de tip text împart fişierele în linii separate prin caracterul ’\n’ (newline=linie nouă), putând fi citite ca orice fişier text. Fluxurile de tip binar transferă blocuri de octeţi (fără nici o structură), neputând fi citite direct, ca fişierele text. Prelucrarea fişierelor se poate face la două niveluri:  Nivelul superior de prelucrare a fişierelor în care se utilizează funcţiile specializate în prelucrarea fişierelor.  Nivelul inferior de prelucrare a fişierelor în care se utilizează direct facilităţile oferite de sistemul de operare, deoarece, în final, sarcina manipulării fişierelor revine sistemului de operare. Pentru a avea acces la informaţiile despre fişierele cu care lucrează, sistemul de operare foloseşte câte un descriptor (bloc de control) pentru fiecare fişier. Ca urmare, există două abordări în privinţa lucrului cu fişiere:  abordarea implementată în stdio.h, asociază referinţei la un fişier un stream (flux de date), un pointer către o structură FILE.  abordarea definită în header-ul io.h (input/output header) asociază referinţei la un fişier un aşa-numit handle (în cele ce urmează acesta va fi tradus prin indicator de fişier) care din punct de vedere al tipului de date este in; Scopul lucrului cu fişiere este acela de a prelucra informaţia conţinută. Pentru a putea accesa un fişier va trebui să-l asociem cu unul din cele două modalităţi de manipulare. Acest tip de operaţie se mai numeşte deschidere de fişier. Înainte de a citi sau scrie într-un fişier (neconectat automat programului), fişierul trebuie deschis cu ajutorul funcţiei fopen din biblioteca standard. Funcţia primeşte ca argument numele extern al fişierului, negociază cu sistemul de operare şi retunează un nume (identificator) intern care va fi utilizat ulterior la prelucrarea fişireului. Acest identificator intern este un pointer la o structură care conţine informaţii despre fişier (poziţia curentă în buffer, dacă se citeşte sau se scrie în fişier, etc.). Utilizatorii nu trebuie să cunoască detaliile, singura declaraţie necesară fiind cea pentru pointerul de fişier. Exemplu: FILE *fp; Operaţiile care pot fi realizate asupra fişierelor sunt:  deschiderea unui fişier;  scrierea într-un fişier;  citirea dintr-un fişier;  poziţionarea într-un fişier;  închiderea unui fişier.

8.2. DESCHIDEREA UNUI FIŞIER 

Funcţia fopen Crează un flux de date între fişierul specificat prin numele extern (nume_fişier) şi programul C. Parametrul mod specifică sensul fluxului de date şi modul de interpretare a acestora. Funcţia returnează un pointer spre tipul FILE, iar în caz de eroare - pointerul NULL (prototip în stdio.h). FILE *fopen(const char *nume_fişier, const char *mod); Parametrul mod este o constantă şir de caractere, care poate conţine caracterele cu semnificaţiile:

114

CAPITOLUL 8

Fişiere

r : flux de date de intrare; deschidere pentru citire; w : flux de date de ieşire; deschidere pentru scriere (crează un fişier nou sau suprascrie conţinutul anterior al fişierului existent);  a : flux de date de ieşire cu scriere la sfârşitul fişierului, adăugare, sau crearea fişierului în cazul în care acesta nu există;  + : extinde un flux de intrare sau ieşire la unul de intrare/ieşire; operaţii de scriere şi citire asupra unui fişier deschis în condiţiile r, w sau a.  b : date binare;  t : date text (modul implicit). Exemple: "r+" – deschidere pentru modificare (citire şi scriere); "w+" – deschidere pentru modificare (citire şi scriere); "rb" – citire binară; "wb" – scriere binară; "r+b" – citire/scriere binară.  



Funcţia freopen (stdio.h) Asociază un nou fişier unui flux de date deja existent, închizând legătura cu vechiul fişier şi încercând să deschidă una nouă, cu fişierul specificat. Funcţia returnează pointerul către fluxul de date specificat, sau NULL în caz de eşec (prototip în stdio.h). FILE*freopen(const char*nume_fiş,const char*mod,FILE *flux_date);



Funcţia open Deschide fişierul specificat conform cu restricţiile de acces precizate în apel. Returnează un întreg care este un indicator de fişier sau -1 (în caz de eşec) (prototip în io.h). int open(const char *nume_fişier, int acces [,int mod]); Restricţiile de acces se precizează prin aplicarea operatorului | (disjuncţie logică la nivel de bit) între anumite constante simbolice, definite în fcntl.h, cum sunt : O_RDONLY - citire; O_WRONLY - scriere O_RDWR - citire şi scriere O_CREAT - creare O_APPEND - adăugare la sfârşitul fişierului O_TEXT - interpretare CR-LF O_BINARY - nici o interpretare., Restricţiile de mod de creare se realizează cu ajutorul constantelor: S_IREAD - permisiune de citire din fişier S_IWRITE - permisiune de scriere din fişier, eventual legate prin operatorul “|”.



Funcţia creat Crează un fişier nou sau îl suprascrie în cazul în care deja există. Returnează indicatorul de fişier sau -1 (în caz de eşec). Parametrul un_mod este obţinut în mod analog celui de la funcţia de deschidere (prototip în io.h). int creat(const char *nume_fişier, int un_mod);



Funcţia creatnew Crează un fişier nou, conform modului specificat. Returnează indicatorul fişierului nou creat sau rezultat de eroare (-1), dacă fişierul deja există (prototip în io.h). int creatnew(const char *nume_fişier, int mod);

După cum se observă, informaţia furnizată pentru deschiderea unui fişier este aceeaşi în ambele abordări, diferenţa constând în tipul de date al entitaţii asociate fişierului. Implementarea din io.h oferă un alt tip de control la nivelul comunicării cu echipamentele periferice (furnizat de funcţia ioctrl), asupra căruia nu vom insista, deoarece desfăşurarea acestui tip de control este mai greoaie, dar mai profundă. 115

CAPITOLUL 8

Fişiere

8.3. ÎNCHIDEREA UNUI FIŞIER 

Funcţia fclose int fclose(FILE *pf); Funcţia închide un fişier deschis cu fopen şi eliberează memoria alocată (zona tampon şi structura FILE). Returnează valoarea 0 la închiderea cu succes a fişierului şi -1 în caz de eroare (prototip în stdio.h).



Funcţia fcloseall int fcloseall(void); Închide toate fluxururile de date şi returnează numărul fluxurilor de date închise (prototip în stdio.h).



Funcţia close int close(int indicator); Închide un indicator de fişier şi returnează 0 (în caz de succes) sau -1 în caz de eroare (prototip în io.h). 8.4. PRELUCRAREA FIŞIERELOR TEXT

După deschiderea unui fişier, toate operaţiile asupra fişierului vor fi efectuate cu pointerul său. Operaţiile de citire şi scriere într-un fişier text pot fi:  intrări/ieşiri la nivel de caracter (de octet);  intrări/ieşiri la nivel de cuvânt (2 octeţi);  intrări/ieşiri de şiruri de caractere;  intrări/ieşiri cu formatare. Comunicarea de informaţie de la un fişier către un program este asigurată prin funcţii de citire care transferă o cantitate de octeţi (unitatea de măsură în cazul nostru) din fişier într-o variabilă-program pe care o vom numi buffer, ea însăşi având sensul unei înşiruiri de octeţi prin declaraţia void *buf. Comunicarea de informaţie de la un program către un fişier este asigurată prin funcţii de scriere care transferă o cantitate de octeţi dintr-o variabilă-program de tip buffer în fişier. Fişierele sunt percepute în limbajul C ca fiind, implicit, secvenţiale (informaţia este parcursă succesiv, element cu element). Pentru aceasta, atât fluxurile de date cât şi indicatorii de fişier au asociat un indicator de poziţie curentă în cadrul fişierului. Acesta este iniţializat la 0 în momentul deschiderii, iar operaţiile de citire, respectiv scriere, se referă la succesiunea de octeţi care începe cu poziţia curentă. Operarea asupra fiecărui octet din succesiune determină incrementarea indicatorului de poziţie curentă. 8.4.1. PRELUCRAREA UNUI FIŞIER LA NIVEL DE CARACTER Fişierele pot fi scrise şi citite caracter cu caracter folosind funcţiile putc (pentru scriere) şi getc (citire).  Funcţia putc int putc (int c, FILE *pf); c – este codul ASCII al caracterului care se scrie în fişier; pf – este pointerul spre tipul FILE a cărui valoare a fost returnată de funcţia fopen. Funcţia putc returnează valoarea lui c (valoarea scrisă în caz de succes), sau –1 (EOF) în caz de eroare sau sfârşit de fişier. 

Funcţia getc

int getc (FILE *pf); Funcţia citeşte un caracter dintr-un fişier (pointerul spre tipul FILE transmis ca argument) şi returnează caracterul citit sau EOF la sfârşit de fişier sau eroare.

116

CAPITOLUL 8

Fişiere

Exerciţiu: Să se scrie un program care crează un fişier text în care se vor scrie caracterele introduse de la tastatură (citite din fişierul standard de intrare), până la întâlnirea caracterului ^Z = Ctrl+Z. #include <stdio.h> #include <process.h> void main() { int c, i=0; FILE *pfcar; char mesaj[]="\nIntrodu caractere urmate de Ctrl+Z (Ctrl+D sub Linux):\n"; char eroare[]="\n Eroare deschidere fişier \n"; while(mesaj[i]) putchar(mesaj[i++]); pfcar=fopen("f_car1.txt","w"); // crearea fişierului cu numele extern f_car1.txt if(pfcar==NULL) { i=0; while(eroare[i])putc(eroare[i++],stdout); exit(1); }while((c=getchar())!=EOF) // sau: while ((c=getc(stdin)) != EOF) putc(c,pfcar); // scrierea caracterului în fişier fclose(pfcar); // închiderea fişierului }

Exerciţiu: Să se scrie un program care citeşte un fişier text, caracter cu caracter, şi afişează conţinutul acestuia. #include <stdio.h> #include <process.h> void main() { int c, i=0; FILE *pfcar; char eroare[]="\n Eroare deschidere fişier \n"; pfcar=fopen("f_car1.txt","r"); //deschiderea fişierului numit f_car1.txt în citire if(pfcar==NULL) { i=0; while(eroare[i])putc(eroare[i++],stdout); exit(1); } while((c=getc(pfcar))!=EOF) //citire din fişier, la nivel de caracter putc(c,stdout);

//scrierea caracterului citit în fişierul standard de ieşire (afişare pe monitor) fclose(pfcar); }

8.4.2. PRELUCRAREA UNUI FIŞIER LA NIVEL DE CUVÂNT Funcţiile putw şi getw (putword şi getword) sunt echivalente cu funcţiile putc şi getc, cu diferenţa că unitatea transferată nu este un singur octet (caracter), ci un cuvânt (un int). int getw(FILE *pf); int putc (int w, FILE *pf); Se recomandă utilizarea funcţiei feof pentru a testa întâlnirea sfârşitului de fişier. Exemplu: int tab[100]; FILE *pf;

// . . . deschidere fişier while (!feof(pf)){ for (int i=0; i<100; i++){ if (feof(pf)) break; 117

CAPITOLUL 8

Fişiere tab[i]=getw(pf);

//citire din fişier la nivel de cuvânt şi memorare în vectorul tab // . . . } } printf("Sfarşit de fişier\n");

8.4.3. PRELUCRAREA UNUI FIŞIER LA NIVEL DE ŞIR DE CARACTERE Într-un fişier text, liniile sunt considerate ca linii de text separate de sfârşitul de linie ('\n'), iar în memorie, ele devin şiruri de caractere terminate de caracterul nul ('\0'). Citirea unei linii de text dintr-un fişier se realizează cu ajutorul funcţiei fgets, iar scrierea într-un fişier - cu ajutorul funcţiei fputs. Funcţia fgets este indentică cu funcţia gets, cu deosebirea că funcţia gets citeşte din fişierul standard de intrare (stdin). Funcţia fputs este indentică cu funcţia puts, cu deosebirea funcţia puts scrie în fişierul standard de ieşire (stdout). 

Funcţia fputs

int fputs(const char *s, FILE *pf); Funcţia scrie un şir de caractere într-un fişier şi primeşte ca argumente pointerul spre zona de memorie (buffer-ul) care conţine şirul de caractere (s) şi pointerul spre structura FILE. Funcţia returnează ultimul caracter scris, în caz de succes, sau -1 în caz de eroare. 

Funcţia fgets

char *fgets(char *s, int dim, FILE *pf); Funcţia citeşte maximum dim-1 octeţi (caractere) din fişier, sau până la întâlnirea sfarşitului de linie. Pointerul spre zona în care se face citirea caracterelor este s. Terminatorul null ('\0') este plasat automat la sfârşitul şirului (buffer-lui de memorie). Funcţia returnează un pointer către buffer-ul în care este memorat şirul de caractere, în caz de succes, sau pointerul NULL în cazul eşecului. Exerciţiu: Să se scrie un program care crează un fişier text în care se vor scrie şirurile de caractere introduse de la tastatură. #include <stdio.h> void main() { int n=250; FILE *pfsir; char mesaj[]="\nIntrodu siruri car.urmate de Ctrl+Z(Ctrl+D sub Linux):\n"; char sir[250],*psir; fputs(mesaj,stdout); pfsir=fopen("f_sir.txt","w"); //deschiderea fişierului f_şir.txt pentru scriere psir=fgets(sir,n,stdin); // citirea şirurilor din fişierul standard de intrare while(psir!=NULL) { fputs(sir,pfsir); // scrierea în fişierul text psir=fgets(sir,n,stdin); } fclose(pfsir); }

Exerciţu: Să se scrie un program care citeşte un fişier text, linie cu linie, şi afişează conţinutul acestuia #include <stdio.h> void main() { int n=250; FILE *pfsir; char sir[250],*psir; pfsir=fopen("f_sir.txt","r"); psir=fgets(sir,n,pfsir); while(psir!=NULL) {

118

CAPITOLUL 8

Fişiere

//sau: puts(sir); //afişarea (scrierea în fişierul standard de ieşire) şirului (liniei) citit din fişierul text psir=fgets(sir,n,pfsir); //citirea unei linii de text din fişier fputs(sir,stdout);

} fclose(pfsir);}

8.4.4. INTRĂRI/IEŞIRI FORMATATE Operaţiile de intrare/ieşire formatate permit citirea, respectiv scrierea într-un fişier text, impunând un anumit format. Se utilizează funcţiile fscanf şi fprintf, similare funcţiilor scanf şi printf (care permit citirea/scrierea formatată de la tastatură/monitor).  Funcţia fscanf int fscanf(FILE *pf, const char *format, . . .); Funcţia fprintf int fprintf(FILE *pf, const char *format, . . .); Funcţiile primesc ca parametri ficşi pointerul (pf ) spre tipul FILE (cu valoarea atribuită la apelul funcţiei fopen), şi specificatorul de format (cu structură identică celui prezentat pentru funcţiile printf şi scanf). Funcţiile returnează numărul câmpurilor citite/scrise în fişier, sau -1 (EOF) în cazul detectării sfârşitului fişierului sau al unei erori. 

8.5. INTRĂRI/IEŞIRI BINARE Reamintim că fluxurile de tip binar transferă blocuri de octeţi (fără nici o structură), neputând fi citite direct, ca fişierele text (vezi paragraful 8.1.). Comunicarea de informaţie dintre un program şi un fişier este asigurată prin funcţii de citire/scriere care transferă un număr de octeţi, prin intermediul unui buffer. Funcţiile de citire  Funcţia fread Citeşte date dintr-un flux, sub forma a n blocuri (entităţi), fiecare bloc având dimensiunea dim, într-un buffer (buf). Returnează numărul de blocuri citite efectiv, sau -1 în caz de eroare (prototip în stdio.h). size_t fread(void *buf, size_t dim, size_t n, FILE *flux_date); 

Funcţia read Citeşte dintr-un fişier (precizat prin indicatorul său, indicator) un număr de n octeţi şi îi memorează în bufferul buf. Funcţia returnează numărul de octeţi citiţi efectiv (pentru fişierele deschise în mod text nu se numără simbolurile de sfirşit de linie), sau -1 în caz de eroare (prototip în io.h). int read(int indicator, void *buf, unsigned n);

Funcţiile de scriere Fişierele organizate ca date binare pot fi prelucrate cu ajutorul funcţiilor fread şi fwrite. În acest caz, se consideră că înregistrarea este o colecţie de date structurate numite articole. La o citire se transferă într-o zonă specială, numită zona tampon, un număr de articole care se presupune că au o lungime fixă. 

Funcţia fwrite Scrie informaţia (preluată din buffer, buf este pointerul spre zona tampon care conţine articolele citite) într-un flux de date, sub forma a n entităţi de dimensiune dim. Returnează numărul de entităţi scrise efectiv, sau -1 în caz de eroare (prototip în stdio.h). size_t fwrite(const void *buf, size_t dim, size_t n, FILE *flx_date);



Funcţia write Scrie într-un fişier (desemnat prin indicatorul său, indicator) un număr de n octeţi preluaţi dintr-un buffer (buf este pointerul spre acesta). Returnează numărul de octeţi scrişi efectiv sau -1 în caz de 119

CAPITOLUL 8

Fişiere

eroare (prototip în io.h). int write(int indicator, void *buf, unsigned n); Exerciţu: Să se scrie un program care crează un fişier binar în care se vor introduce numere reale, nenule. #include #include <stdio.h> int main() { FILE *f; double nr; int x; if ((f= fopen("test_nrb.dat", "wb")) == NULL) //deschidere flux binar, scriere { cout<<"\nNu se poate deschide fişierul test_nrb.dat"<<'\n'; return 1; } cout<<"\nIntroduceţi numere(diferite de 0) terminate cu un 0:"<<'\n'; cin>>nr; while(nr!=0) { x=fwrite(&nr, sizeof(nr), 1, f); //scriere în fişier cin>>nr; } fclose(f); return 0; }

Exemplu: Să se scrie un program ce citeşte dintr-un fişier binar numere reale, nenule. #include #include <stdio.h> int main() { FILE *f; double buf; if ((f= fopen("test_nrb.dat", "rb")) == NULL) { cout<<"\nNu se poate deschide fişierul test_nrb.dat"<<'\n'; return 1; } cout<<"\nNumerele nenule citite din fişier sunt:"<<'\n'; while((fread(&buf, sizeof(buf), 1, f))==1)

// funcţia sizeof(buf) care returneaza numarul de octeţi necesari variabilei buf. cout<
";

}

8.6. POZIŢIONAREA ÎNTR-UN FIŞIER Pe lângă mecanismul de poziţionare implicit (asigurat prin operaţiile de citire şi scriere) se pot folosi şi operaţiile de poziţionare explicită. 

Funcţia fseek int fseek(FILE *pf, long deplasament, int referinţa); Funcţia deplasează capul de citire/scriere al discului, în vederea prelucrării înregistrărilor fişierului într-o ordine oarecare. Funcţia setează poziţia curentă în fluxul de date la n octeţi faţă de referinţă): deplasament – defineşte numărul de octeţi peste care se va deplasa capul discului; referinţa – poate avea una din valorile: 0 - începutul fişierului (SEEK_SET); 1 - poziţia curentă a capului (SEEK_CUR); 2 - sfârşitul fişierului (SEEK_END). Funcţia returnează valoarea zero la poziţionarea corectă şi o valoare diferită de zero în caz de eroare (prototip în stdio.h). 120

CAPITOLUL 8

Fişiere



Funcţia lseek int lseek(int indicator, long n, int referinta); Seteaza poziţia curentă de citire/scriere în fişier la n octeţi faţa de referinţă. Returnează valoarea 0 în caz de succes şi diferită de zero în caz de eroare (prototip în io.h).



Funcţia fgetpos int fgetpos(FILE *flux_date, fpos_t *poziţie); Determină poziţia curentă (pointer către o structură, fpos_t, care descrie această poziţie în fluxul de date). Înscrie valoarea indicatorului în variabila indicată de poziţie. Returnează 0 la determinarea cu succes a acestei poziţii sau valoare diferită de zero în caz de eşec. Structura care descrie poziţia poate fi transmisă ca argument funcţiei fsetpos (prototip în stdio.h).



Funcţia fsetpos int fsetpos(FILE *flux_date, const fpos_t *poziţie); Setează poziţia curentă în fluxul de date (atribuie indicatorului valoarea variabilei indicate poziţie), la o valoare obţinută printr apelul funcţiei fgetpos. Returnează valoarea 0 în caz de succes, sau diferită de 0 în caz de eşec (prototip în stdio.h).

Există funcţii pentru modificarea valorii indicatorului de poziţie şi de determinare a poziţiei curente a acestuia.  Funcţia ftell long ftell(FILE *pf); Indică poziţia curentă a capului de citire în fişier. Funcţia returnează o valoare de tip long int care reprezintă poziţia curentă în fluxul de date (deplasamentul în octeţi a poziţiei capului faţă de începutul fişierului) sau -1L în caz de eroare (prototip în stdio.h). 

Funcţia tell

long tell(int indicator); Returnează poziţia curentă a capului de citire/scriere în fişier (exprimată în număr de octeţi faţă de începutul fişierului), sau -1L în caz de eroare (prototip în io.h).



Funcţia rewind void rewind(FILE *flux_date); Poziţionează indicatorul la începutul fluxului de date specificat ca argument (prototip în stdio.h).

8.7. FUNCŢII UTILITARE PENTRU LUCRUL CU FIŞIERE Funcţii de testare a sfârşitului de fişier  Funcţia feof int feof(FILE *flux_date); Returnează o valoare diferită de zero în cazul întâlnirii sfârşitului de fişier sau 0 în celelalte cazuri (prototip în stdio.h). 

Funcţia eof int eof(int indicator); Returnează valoarea 1 dacă poziţia curentă este sfârşitul de fişier, 0 dacă indicatorul este poziţionat în altă parte, sau -1 în caz de eroare (prototip în io.h).

Funcţii de golire a fluxurilor de date  Funcţia fflush int fflush(FILE *flux_date); Goleşte un fluxul de date specificat ca argument. Returnează 0 în caz de succes şi -1 (EOF) în caz de eroare (prototip în stdio.h).

121

CAPITOLUL 8 

Fişiere

Funcţia flushall

int flushall(void); Goleşte toate fluxurile de date existente, pentru cele de scriere efectuând şi scrierea în fişiere. Returnează numărul de fluxuri asupra cărora s-a efectuat operaţia (prototip în stdio.h).

8.8. ALTE OPERAŢII CU FIŞIERE Funcţii care permit operaţii ale sistemului de operare asupra fişierelor  Funcţia remove int remove(const char *nume_fişier); Şterge un fişier. Returnează valoarea 0 pentru operaţie reuşită şi -1 pentru operaţie eşuată (prototip în stdio.h). 

Funcţia rename int rename(const char *nume_vechi, const char *nume_nou); Redenumeşte un fişier. Returnează 0 pentru operaţie reuşita şi -1 în cazul eşecului (prototip în stdio.h).



Funcţia unlink int unlink(const char *nume_fişier); Şterge un fişier. Returnează 0 la operaţie reuşită şi -1 la eşec; dacă fişierul are permisiune read-only, funcţia nu va reuşi operaţia (prototip în io.h, stdio.h).

Funcţii care permit manipularea aceluiaşi fişier prin două indicatoare de fişier independente  Funcţia dup int dup(int indicator); Duplică un indicator de fişier. Returnează noul indicator de fişier pentru operaţie reuşită sau -1 în cazul eşecului (prototip în io.h). 

Funcţia dup2 int dup2(int indicator_vechi, int indicator_nou); Duplică un indicator de fişier la valoarea unui indicator de fişier deja existent. Returnează 0 în caz de succes şi -1 în caz de eşec (prototip în io.h).

Funcţii pentru aflarea sau modificarea dimensiunii în octeţi a fişierelor  Funcţia chsize int chsize(int indicator, long lungime); Modifică dimensiunea unui fişier, conform argumentului lungime. Returnează 0 pentru operaţie reuşită sau -1 în caz de eşec (prototip în stdio.h). 

Funcţia filelength long filelength(int indicator); Returnează lungimea unui fişier (în octeţi) sau -1 în caz de eroare (prototip în io.h).

Funcţii de lucru cu fişiere temporare care oferă facilităţi de lucru cu fişiere temporare prin generarea de nume unice de fişier în zona de lucru.  Funcţia tmpfile FILE *tmpfile(void); Deschide un fişier temporar, ca flux de date, în mod binar (w+b). Returnează pointerul către fişierul deschis în cazul operaţiei reuşite, sau NULL în caz de eşec (prototip în stdio.h). 

Funcţia tmpnam

char *tmpnam(char *sptr); Crează un nume unic pentru fişierul temporar (prototip în stdio.h).

122

CAPITOLUL 8 

Fişiere

Funcţia creattemp int creattemp(char *cale, int attrib); Crează un fişier unic ca nume, cu atributele specificate în argumentul attrib (prin _fmode,O_TEXT sau O_BINARY), în directorul dat în argumentul cale. Returnează indicatorul (handler-ul) către fişierul creat sau -1 (şi setarea errno) în cazul eşecului (prototip în io.h).

Exemplu: Să se creeze un fişier binar, care va conţine informaţiile despre angajaţii unei întreprinderi: nume, marca, salariu. Să se afişeze apoi conţinutul fişierului. #include #include <stdio.h> #include typedef struct { char nume[20];int marca;double salariu; }angajat; union {angajat a;char sbinar[sizeof(angajat)];}buffer; int main() {angajat a; FILE *pf; char cont;char *nume_fis; cout<<"Nume fisier care va fi creat:"; cin>>nume_fis; if ((pf= fopen(nume_fis, "wb")) == NULL) { cout<<"\nEroare creare fişier "<>a.marca; cout<<"Nume : ";cin>>a.nume; cout<<"Salariu :";cin>>a.salariu; buffer.a=a; fwrite(buffer.sbinar,1,sizeof(angajat),pf); cout<<"Continuati introducerea de date (d/n) ?"; cin>>cont; } while(toupper(cont)!='N'); fclose(pf);

//citirea informatiilor if ((pf= fopen(nume_fis, "rb")) == NULL) { cout<<"\nEroare citire fişier "<
Exemplu: Aplicaţie pentru gestiunea materialelor dintr-un depozit. Aplicaţia va avea un meniu principal şi va permite gestiunea următoarelor informaţii: codul materialului (va fi chiar "numărul de ordine"), denumirea acestuia, unitatea de măsură, preţul unitar, cantitatea contractată şi cea recepţionată (vectori cu 4 elemente). Memorarea datelor se va face într-un fişier de date (un fişier binar cu structuri), numit "material.dat". Aplicaţia conţine următoarele funcţii: 1. help() - informare privind opţiunile programului 2. Funcţii pentru fişierele binare, care să suplinească lipsa funcţiilor standard pentru organizarea directă a fişierelor binare: citireb() - citire în acces direct din fişier; 123

CAPITOLUL 8

Fişiere

- scriere în acces direct în fişier; - citirea de la terminal a informaţiilor despre un material; - afişarea informaţiilor despre un material (apelată de list); - determinarea lungimii fişierului existent; - creare fişier. 3. Funcţii pentru adaugarea, modificarea, ştergerea şi listarea de materiale. scrieb() citmat() afismat() lungfisis() crefis()

#include <process.h rel="nofollow"> #include #include <stdio.h> #include typedef struct material { int codm,stoc,cant_c[4],cant_r[4]; char den_mat[20],unit_mas[4]; float preţ; }; material mat; FILE *pf; void crefis(),adaug(),modif(),sterg(),list(),help(); void main() { char opţiune; do //afişarea unui meniu de opţiuni şi selecţia opţiunii { cout<<'\n'<<"Opţiunea Dvs. de lucru este"<<'\n' <<"(c|a|m|s|l|e|h pentru help) : "; cin>>opţiune; switch(opţiune) { case 'c':case 'C':crefis();break; case 'a':case 'A':adaug();break; case 'm':case 'M':modif();break; case 's':case 'S':şterg();break; case 'l':case 'L':list();break; case 'h':case 'H':help();break; case 'e':case 'E': break; default:help(); break; } }while(toupper(opţiune)!='E'); } void help() // afişare informaţii despre utilizarea meniului şi opţiunile acestuia {cout<<"Opţiunile de lucru sunt :"<<'\n'; cout<<" C,c-creare fisier"<<'\n'; cout<<" A,a-adaugare"<<'\n'; cout<<" M,m-modificare"<<'\n'; cout<<" L,l-listare"<<'\n'; cout<<" S,s-ştergere"<<'\n'; cout<<" H,h-help"<<'\n'; cout<<" E,e-exit"<<'\n'; } long int lungfis(FILE *f) // returnează lungimea fişierului {long int posi,posf; posi=ftell(f); fseek(f,0,SEEK_END); posf=ftell(f); fseek(f,posi,SEEK_SET); return posf; } void scrieb(int nr,void *a,FILE *f) //scriere în fişierul binar {long depl=(nr-1)*sizeof(material); fseek(f,depl,SEEK_SET); if(fwrite(a,sizeof(material),1,f)!=1) {cout<<"Eroare de scriere in fişier !"<<'\n'; 124

CAPITOLUL 8 exit(1); } } void citireb(int nr,void *a,FILE *f) //citire din fişierul binar {long depl=(nr-1)*sizeof(material); fseek(f,depl,SEEK_SET); if(fread(a,sizeof(material),1,f)!=1) {cout<<"Eroare de citire din fişier !"<<'\n'; exit(2); } } void afismat(material *a) //afişarea informaţiilor despre un anumit material { int i; if(a->codm) {cout<<"Cod material : "<codm<<'\n'; cout<<"Denumire material: "<den_mat<<'\n'; cout<<"Cantitaţi contractate:"<<'\n'; for(i=0;i<4;i++) cout<<"Contractat "<<<" : "<cant_c[i]<<'\n'; cout<<"Cantitaţi recepţionate:"<<'\n'; for(i=0;i<4;i++) cout<<"Receptionat "<<<" : "<cant_r[i]<<'\n'; cout<<"Stoc : "<stoc<<'\n'; cout<<"Unitate de masura: "<unit_mas<<'\n'; cout<<"Preţ unitar : "<preţ<<'\n'; } else cout<<"Acest articol a fost şters !"<<'\n'; } void citmat(material *a) //citirea informaţiilor despre un anumit material { int i;float temp; cout<<"Introduceti codul materialului (0=End): ";cin>>a->codm; if(a->codm==0) return; cout<<"Introduceţi denumirea materialului : ";cin>>a->den_mat; cout<<"Introduceţi unitatea de măsură : ";cin>>a->unit_mas; cout<<"Introduceţi preţul : ";cin>>temp;a->preţ=temp; cout<<"Introduceţi cantitaţile contractate : "<<'\n'; for(i=0;i<4;i++) {cout<<"Contractat "<>a->cant_c[i]; } cout<<"Introduceţi cantitaţile recepţionate : "<<'\n'; for(i=0;i<4;i++) {cout<<"Receptionat "<>a->cant_r[i]; } } void crefis() //deschidere fisier { if((pf=fopen("material.dat","r"))!=NULL) cout<<"Fişierul exista deja !"<<'\n'; else pf=fopen("material.dat","w"); fclose(pf); } void adaug() //adăugare de noi materiale { int na; pf=fopen("material.dat","a");//deschidere pentru append na=lungfis(pf)/sizeof(material); do {citmat(&mat); if(mat.codm) scrieb(++na,&mat,pf); } while(mat.codm); fclose(pf); }

125

Fişiere

CAPITOLUL 8

Fişiere

void modif() //modificarea informaţiilor despre un material existent { int na; char ch; pf=fopen("material.dat","r+"); do {cout<<"Numarul articolului de modificat este (0=END): ";cin>>na; if(na) {citireb(na,&mat,pf); afismat(&mat); cout<<"Modificaţi articol (D/N) ? :"; do { cin>>ch; ch=toupper(ch); } while(ch!='D' && ch!='N'); if(ch=='D') {citmat(&mat); scrieb(na,&mat,pf); } } }while(na); fclose(pf); } void sterg() //ştergerea din fişier a unui material { int n;long int na; pf=fopen("material.dat","r+"); mat.codm=0; na=lungfis(pf)/sizeof(material); do { do {cout<<"Numarul articolului de şters este (0=END): ";cin>>n; if(n<0||n>na) cout<<"Articol eronat"<<'\n'; }while(!(n>=0 && n<=na)); if(n) scrieb(n,&mat,pf); }while(n); fclose(pf); } void list() //afişare informaţii despre un anumit material { int na; pf=fopen("material.dat","r"); do {cout<<"Numarul articolului de listat este (0=END): ";cin>>na; if(na) {citireb(na,&mat,pf); afismat(&mat); cout<<'\n'; } }while(na); fclose(pf); }

ÎNTREBĂRI ŞI EXERCIŢII Chestiuni practice Scrieţi un program de tipărire a conţinuturilor mai multor fişiere, ale căror nume se transmit ca parametri către funcţia main. Tipărirea se face pe ecran (lungimea paginii = 22) sau la imprimantă (lungimea paginii = 61). Conţinutul fiecărui fişier va începe pe o pagină nouă, cu un titlu care indică numele fişierului. Pentru fiecare fişier, paginile vor fi numerotate (cu ajutorul unui contor de pagini). 2. Scrieţi un program care citeşte un fişier text. Pornind de la conţinutul acestuia, se va crea un alt fişier, prin înlocuirea spaţiilor consecutive cu unul singur. Se vor afişa pe ecran conţinutul fişierului de la care s-a pornit şi conţinutul fişierului obţinut. 1.

126

CAPITOLUL 8

Fişiere

3. Să se consulte conţinutul unui fişier şi să se afişeze următoarele informaţii statistice: numărul de cuvinte din fişier, numărul de caractere, numărul de linii, numărul de date numerice (nu cifre, numere!). 4. Scrieţi un program care să compare conţinutul a două fişiere, şi afişaţi primele linii care diferă şi poziţia caracterelor diferite în aceste linii. 5. Scrieţi un program care citeşte conţinutul unui fişier sursă scris în limbajul C şi afişează în ordine alfabetică fiecare grup al numelor de variabile care au primele n caractere identice (n este citit de la tastatură). 6. Scrieţi un program care consultă un fişier text şi afişează o listă a tuturor cuvintelor din fişier. Pentru fiecare cuvânt se vor afişa şi numerele liniilor în care apare cuvântul. 7. Scrieţi un program care citeşte un text introdus de la tastatură şi afişează cuvintele distincte, în ordinea crescătoare a frecvenţei lor de apariţie. La afişare, fiecare cuvânt va fi precedat de numărul de apariţii. 8. Scrieţi un program care citeşte un text introdus de la tastatură, ordonează alfabetic liniile acestuia şi le afişează. 9. Scrieţi o aplicaţie pentru gestiunea informatiilor despre cărţile existente într-o bibliotecă. Aplicaţia va avea un meniu principal care va permite: a) Memorarea datelor într-un fişier (un fişier binar cu structuri), al cărui nume se introduce de la tastatură. Fişierul va contine informaţiile: nume carte, autor, editura, anul apariţiei, preţ. Pentru fiecare carte, se va genera o cotă (un număr unic care să constituie cheia de căutare). b) Adaugărea de noi cărţi; c) Afişarea informaţiilor despre o anumită carte; d) Căutarea titlurilor după un anumit autor; e) Modificarea informaţiilor existente; f) Lista alfabetică a tuturor autorilor; g) Ştergerea unei cărţi din bibliotecă; h) Ordonarea descrescătoare după anul apariţiei; i) Numele celei mai vechi cărţi din bibliotecă; j) Numele celei mai scumpe cărţi din bibliotecă; k) Numele autorului cu cele mai multe cărţi; l) Valoarea totală a cărţilor din bibliotecă.

127

CAPITOLUL 9

Concepte de bază ale programării orientate obiect

CONCEPTE DE BAZĂ ALE PROGRAMĂRII ORIENTATE OBIECT 9.1. Introducere 9.2. Abstractizarea datelor 9.3. Moştenirea

9

9.4. Încapsularea informaţiei 9.5. Legarea dinamică (târzie) 9.6. Alte aspecte

9.1. INTRODUCERE Termenul "OOP" ("Object Oriented Programming") desemnează disciplina programării obiectuale (orientate-obiect). Această disciplină care are la bază ideea unificării datelor cu modalităţile de prelucrare a acestora şi manevrează entităţi reprezentate sub formă de obiecte (obiect=date+cod de tratare a acestor date). Aşa cum s-a subliniat în capitolul 1.3., rezolvarea unei probleme se poate face pe 3 direcţii:  Rezolvarea orientată pe algoritm (pe acţiune), în care organizarea datelor este neesenţială;  Rezolvarea orientată pe date, acţiunile fiind determinate doar de organizarea datelor;  Rezolvarea orientată obiect, care combină tendinţele primelor două abordări. Programarea obiectuală oferă posibilităţi de modelare a obiectelor, a proprietăţilor şi a relaţiilor dintre ele, dar şi posibilitatea de a descompune o problemă în componentele sale (soft mai mentenabil, adaptabil, reciclabil). Câteva exemple de limbaje de programare orientată obiect: SIMULA(1965), SIMULA-2(1967), Smalltalk, C++, Java (în plus, Java poate fi considerat un limbaj de programare orientată eveniment). Facilităţile oferite de programarea orientată obiect (conform lui Pascou) sunt: 1. abstractizarea datelor; 2. moştenirea; 3. încapsularea (ascunderea) informaţiei; 4. legarea dinamică (“târzie”).

9.2. ABSTRACTIZAREA DATELOR Obiectele sunt componente software care modelează fenomene din lumea reală. În general, un fenomen implică tipuri diferite de obiecte. Obiectele care reprezintă aceeaşi idee sau concept sunt de acelaşi tip şi pot fi grupate în clase (concrete sau abstracte). Clasele implementează tipuri de date (aşa cum s-a subliniat în capitolul 2, un tip de date înseamnă o mulţime de valori pentru care s-a adoptatat un anumit mod de reprezentare şi o muţime de operatori care pot fi aplicaţi acestor valori), deci şi operatorii destinaţi manipulării acestora: Clasă = Date + Operaţii. De exemplu, programatorul îşi poate defini tipul (clasa) matrice şi operatorii care pot fi aplicaţi matricilor (* pentru înmulţirea a două matrici, + pentru adunarea a două matrici, - pentru scăderea a două matrici, etc). Astfel, el poate folosi tipul matrice în mod similar unui tip predefinit: matrice A, B; matrice C=A+B;

Tipul unui obiect (şablon al obiectului) este o clasă. O clasă se caracterizează prin: numele clasei, atribute, funcţii şi relaţii cu alte clase. Instanţa este un obiect dintr-o clasă (A, B, C sunt obiecte, instanţe ale clasei matrice) şi are proprietăţile definite de clasă. Pentru o clasă definită, se pot crea mai multe instanţe ale acesteia. Toate obiectele au o stare şi un comportament. Starea unui obiect se referă la elementele de date conţinute în obiect şi la valorile asociate acestora (datele membre). Comportamentul unui obiect este determinat de care acţiunile pe care obiectul poate să le execute (metodele). 129

CAPITOLUL 9

Concepte de bază ale programării orientate obiect

Atributele specificate în definiţia unei clase descriu valoric proprietăţile obiectelor din clasă, sub diferite aspecte. Cele mai multe limbaje orientate obiect fac următoarea distincţie între atribute:  atribute ale clasei (au aceeaşi valoare pentru toate instanţele clasei);  atribute ale instanţei (variază de la o instanţă la alta, fiecare instanţă având propria copie a atributului). În limbajul C++ atributele se numesc date membre. Toate datele membre sunt atribute instanţă. Atributele de clasă se pot obţine în cazul datelor membre statice (aceeaşi adresă de memorare pentru orice instanţă a clasei). Metode (funcţii membre). La definirea unei clase se definesc şi metodele acesteia (numite şi funcţii membre). Fiecare obiect are acces la un set de funcţii care descriu operaţiile care pot fi executate asupra lui. Metodele pot fi folosite de instanţele clasei respective, dar şi de instanţele altor clase (prin mecanismul moştenirii). Clasa conţine atât structurile de date necesare descrierii unui obiect, cât şi metodele care pot fi aplicate obiectului. Astfel, gradul de abstractizare este, mult mai ridicat, iar programele devin mult mai uşor de înţeles, depanat sau întreţinut. La crearea unui obiect, alocarea memoriei se poate fi face static sau dinamic (cu ajutorul unor funcţii membre speciale, numite constructori). Eliberarea memoriei se realizează cu ajutorul unor funcţii membre speciale, numite destructori, în momentul încheierii existenţei obiectului respectiv.

9.3. MOŞTENIREA Moştenirea este o caracteristică a limbajelor de programare orientate obiect, care permite refolosirea codului şi extinderea funcţionalităţii claselor existente. Între două clase pot exista multe diferenţe, dar şi multe asemănări. Este bine ca informaţia comună unor clase să fie specificată o singură dată (conceptul de clasă/subclasă, superclasă/clasă în OOP). Mecanismul moştenirii permite crearea unei ierarhii de clase şi trecerea de la clasele generale la cele particulare. Procesul implică la început definirea clasei de bază care stabileşte calităţile comune ale tuturor obiectelor ce vor deriva din bază (ierarhic superioară)(figura 9.1.). Prin moştenire, un obiect poate prelua proprietăţile obiectelor din clasa de bază. Clasa A reprezintă clasa de bază (este o generalizare) şi conţine informaţiile comune (disponibile prin moştenire şi subclaselor acesteia). Clasa B reprezintă clasa derivată (este o particularizare, o specializare a clasei A) care extinde funcţionalitatea clasei de bază şi conţine informaţiile specifice. Să presupunem că A reprezintă clasa mamiferelor (cu proprietăţile caracteristice: nasc pui vii, au sânge cald, îşi alăptează puii, etc), iar B reprezintă clasa animalelor domestice. În momentul definirii clasei derivate B, aceasta moşteneşte toate caracteristicile clasei A, rămânând de specificat doar trăsăturile distinctive. În acest caz, A este clasă de bază, iar B clasă derivată (subclasă a clasei A). Sau: B este clasă, iar A este o superclasă a clasei B. Moştenirea poate fi: unică sau multiplă.

A

B

Figura 9.1. Relaţia clasă de bază-clasă derivată

9.3.1. MOŞTENIREA UNICĂ În cazul moştenirii unice, fiecare clasă are doar o superclasă. Există două modalităţi de specializare a unei clase de bază:  introducerea de extra-atribute şi extra-metode în clasa derivată (particulare doar clasei derivate);  redefinirea membrilor în clase derivate (polimorfism).

9.3.2. MOŞTENIREA MULTIPLĂ În situaţia moştenirii multiple, o clasă are mai multe superclase. Astfel, moştenirea clasei va fi multiplă (rezultând o structură de reţea). 130

CAPITOLUL 9

Concepte de bază ale programării orientate obiect A

B

E

A

C

F

B

D

G

C

F

E

H

D

Figura 9.3. Moştenirea multiplă

Figura 9.2. Moştenirea simplă (unică)

Moştenirea multiplă este utilă, dar poate crea ambiguităţi (când pentru acelaşi atribut se moştenesc valori diferite). Există mai multe strategii de rezolvare a conflictului (părintele cel mai apropiat, cel mai depărtat, etc.). Deasemenea, este posibilă o moştenire repetată, în care o clasă ajunge să moştenească de la aceeaşi clasă, pe drumuri diferite în reţea (vezi figura 9.3., în care clasa E moşteneşte de la aceeaşi clasă A, pe drumurile A-B-E, A-C-E) . Aşa cum vedea în capitolele următoare, în aceste situaţii, limbajul C++ oferă programatorului două strategii: 1) clasa E poate avea două copii ale lui A, una pentru fiecare drum; 2) clasa E are o singură copie, iar A este clasă virtuală de bază şi pentru C şi pentru B. Ideea moştenirii multiple poate duce la utilizarea unor clase pentru care nu există instanţe, care să ajute doar la organizarea structurii (reţelei) de moştenire. În plus, limbajul C++ permite un control puternic asupra atributelor şi metodelor care vor fi moştenite.

9.4. ÎNCAPSULAREA (ASCUNDEREA) INFORMAŢIEI Încapsularea (ascunderea) informaţiei reflectă faptul că atributele instanţă şi metodele unui obiect îl definesc doar pe acesta. Vom spune că metodele şi atributele unui obiect sunt “private”, încapsulate în obiect. Interfaţa cu obiectul relevă foarte puţin din ceea ce se petrece în interiorul lui. Obiectul deţine controlul asupra atributelor instanţă, care nu pot fi alterate de către alte obiecte. Excepţia de la această observaţie o reprezintă doar atributele de clasă care nu sunt încapsulate, fiind partajate între toate instanţele clasei. Această tehnică de "plasare" a valorilor în datele membre private ale obiectului, reprezintă un mecanism de ascundere a datelor. În limbajul C++ încapsularea poate fi forţată prin controlul accesului, deoarece toate datele şi funcţiile membre sunt caracterizate printr-un nivel de acces (rezultând astfel o mare flexibilitate). Nivelul de acces la membrii unei clase poate fi (figura 9.4.): public 







protected

private: membrii (date şi metode) la care accesul este private pot fi accesaţi doar prin metodele clasei (nivel acces implicit); protected: aceşti membri pot fi accesaţi prin funcţiile membre ale clasei şi funcţiile membre ale clasei derivate; public: membrii la care accesul este public pot fi accesaţi din orice punct al domeniului de existenţă a clasei respective; friend: aceşti membri pot fi accesaţi prin funcţiile membre ale funcţiei prietene specificate.

Clasa A

private

clasă derivată

Clasa B

Figura 9.4. Accesul la membrii unei clase În limbajul C++, nivelul de acces poate preciza şi tipul de moştenire (capitolul 12).  Publică, unde în clasa derivată nivelul de acces al membrilor este acelaşi ca în clasa de bază;  Privată, unde membrii protected şi public din clasa bază devin private în clasa derivată. 131

CAPITOLUL 9

Concepte de bază ale programării orientate obiect

9.5. LEGAREA DINAMICĂ (“TÂRZIE”) Obiectele unei clase părinte trebuie cunoscute în momentul compilării. Efectul combinat al moştenirii poate determina ca o anumită metodă să fie specializată diferit (prin redefinire), pentru subclase diferite. Polimorfismul reprezintă comportamente diferite ale unei metode în raport cu tipul unui obiect. Selectarea unei metode redefinite poate fi realizată în faza de compilare (legarea iniţială), sau în momentul execuţiei (legare târzie). În limbajul C++, legarea dinamică se poate realiza prin implementarea de:  funcţii virtuale (pot fi redefinite polimorfic);  funcţii virtuale pure (doar declarate, nu definite).

9.6. ALTE ASPECTE Comunicarea între obiecte În limbajele de programare orientate obiect, obiectele comunică între ele prin mesaje, ceea ce conduce la accentuarea conceptului de încapsulare. Un obiect poate “stimula” un altul să activeze (declanşeze) o metodă, trimiţându-i un mesaj. După primirea mesajului, metoda respectivă este apelată cu parametrii furnizaţi, asigurând comportarea corespunzătoare a obiectelor. Metodele sunt invocate prin trimiterea de mesaje. În limbajul C++ funcţiile membre (metodele) sunt accesate în mod similar oricarei funcţii, cu deosebirea că este necesară specificarea obiectului căruia îi corespunde metoda. 

Pseudovariabile Limbajele de programare orientate obiect posedă două variabile (numite pseudo-variabile) care diferă de variabilele normale prin faptul că nu li se pot atribui valori în mod direct, de către programator. În general, pseudovariabilele sunt o formă scurtă pentru “obiectul curent ” şi pentru “clasa părinte a obiectului curent”. În limbajul C++ există doar una din aceste pseudovariabile, numită ”this” (pointer către obiectul curent). 

Metaclasele Metaclasele reprezintă “clase de clase”. O clasă este, de fapt, o instanţă a unei metaclase. Diferenţele dintre clase şi metaclase sunt:  Clasa defineşte caracteristici (atribute şi metode) ale instanţelor de acel tip. Metodele pot fi folosite doar de obiectele clasei, nu şi de însăşi clasa (restricţie).  Metaclasele furnizează un mijloc prin care variabilele clasă pot fi implementate: în unele limbaje OOP, variabilele clasă sunt instanţieri ale unei metaclase. 

Limbajul C++ nu include explicit metaclasele, dar suportă variabilele clasă sub forma datelor statice. Aşa cum funcţiile membre obişnuite sunt încapsulate înăuntrul fiecarei instanţe, pentru o funcţie membru statică a unei clase, se foloseşte o singură copie, partajată de către toate instanţele clasei. O asemenea funcţie nu este asociată unei anumite instanţe. Persistenţa Persistenţa reprezintă timpul de viaţă al unui obiect (între crearea obiectului şi ştergerea sa). Instanţele unei clase au un timp de viaţă dat de execuţia unei metode sau a unui bloc, de crearea sau ştergerea specificată explicit în program sau de durata întregului program. Persistenţa obiectelor este importantă în special în aplicaţiile de baze de date. 

Supraîncarcarea operatorilor. Limbajul C++ furnizează modalităţi de supraîncarcare a operatorilor (overloading): acelaşi operator are semnificaţii diferite, care depind de numărul şi tipul argumentelor. 

132

CAPITOLUL 10

Clase şi obiecte

CLASE ŞI OBIECTE 10.1. Definiţia claselor şi accesul la membrii 10.1.1. Legătura clasă-structură-uniune 10.1.2. Declararea claselor 10.1.3. Obiecte 10.1.4. Membrii unei clase 10.1.5. Pointerul this 10.1.6. Domeniul unui nume, vizibilitate şi timp de viaţă

1

10.2. Funcţii inline 10.3. Constructori şi destructori 10.3.1. Iniţializarea datelor 10.3.2. Constructori 10.3.3. Destructori 10.3.4. Tablouri de obiecte 10.4. Funcţii prietene (friend)

10.1. DEFINIŢIA CLASELOR ŞI ACCESUL LA MEMBRII 10.1.1. LEGĂTURA CLASĂ-STRUCTURĂ-UNIUNE Aşa cum s-a subliniat în capitolul 9, o clasă reprezintă un tip abstract de date, care încapsulează atât elementele de date (datele membre) pentru care s-a adoptat un anumit mod de reprezentare, cât şi operaţiile asupra datelor (funcţiile membre, metode). În limbajul C++, structurile şi uniunile reprezintă cazuri particulare ale claselor, putând avea nu numai date membre, câmpuri de date (vezi capitolul 8), dar şi funcţii membre. Singura diferenţă între structuri şi uniuni constă în faptul că la uniuni, pentru memorarea valorilor datelor membre se foloseşte aceeaşi zonă de memorie. Deosebirea esenţială între structuri şi uniuni - pe de o parte - şi clase - pe cealată parte - constă în modul de acces la membrii: la structuri şi uniuni membrii (datele şi metodele) sunt implicit publici, iar la clase - implicit privaţi (membrii sunt încapsulaţi). Lipsa unor modalităţi de protecţie a datelor, face ca tipurile de date introduse prin structuri sau uniuni să nu poată fi strict controlate în ceea ce priveşte operaţiile executate asupra lor. În cazul claselor, modul de acces la membrii tipului de date (în scopul protejării acestora) poate fi schimbat prin utilizarea modificatorilor de control ai accesului: public, private, protected.

10.1.2. DECLARAREA CLASELOR Modul de declarare a unei clase este similar celui de declarare a structurilor şi a uniunilor: class nume_tip{ modificator_control_acces: lista_membrilor; } lista_variabile; Clasa reprezintă un tip de date (definit de utilizator). Membrii unei clase sunt:  Datele membre - datele declarate în cadrul clasei;  Metodele - funcţiile membre, funcţiile declarate sau definite în cadrul clasei. Se admite că în cadrul declaraţiei de clasă să se specifice doar prototipurile funcţiilor membre, definiţiile putând fi făcute oriunde în fişier, sau în alt fişier. Pentru membrii care apar în lista_membrilor se poate preciza un anumit mod de acces. Modificator_control_acces poate fi public, private sau protected (eventual friend, vezi paragraful 10.4.). Dacă nu se specifică, este considerat cel implicit ( private). Modificatorul de acces public se utilizează pentru membrii care dorim să fie neprotejaţi, ultimii doi modificatori asigurând protecţia membrilor din domeniul de acţiune a lor. Membrii cu acces private pot fi accesaţi numai prin 133

CAPITOLUL 10

Clase şi obiecte

metodele clasei (sau prin funcţiile prietene, capitolul 10.4.). Cei cu acces protected posedă caracteristicile celor privaţi, în plus, putând fi accesaţi şi din clasele derivate. Specificatorii modului de acces pot apare în declararea clasei de mai multe ori, în orice ordine. Domeniul unui modificator de acces ţine din punctul în care apare modificatorul respectiv, până la sfârşitul declaraţiei clasei sau al întâlnirii altui modificator de acces (exemplele 1,2). Observaţiile legate de prezenţa nume_tip sau lista_variabile (din capitolul 8) sunt valabile şi în cazul claselor. Variabilele din lista_variabile sunt de tipul nume_tip şi se numesc instanţe (obiecte) ale clasei. Observaţie: În cazul tipurilor de date definite cu ajutorul structurilor, se pot aplica modificatorii de acces. În cazul tipurilor definite cu ajutorul uniunilor, accesul implicit (public) nu poate fi modificat. Exemplu: class masina{ char *culoare; // dată membru la care accesul este, implicit, private int capacit_cil; // dată membru la care accesul este, implicit, private public: void citire_date();

};

//metodă cu acces public, care permite introducerea datelor despre o instanţă a clasei int ret_capacit(); //metodă cu acces public

Membrii culoare şi capacitate_cil (accesul private) pot fi accesaţi doar prin intermediul metodelor clasei. Exemplu: class persoana{ char *nume; //dată membru privată public: void citire_inf_pers(); //metodă publică private: int varsta; //dată membru privată };

Exerciţiu: Să se definească tipul de date dreptunghi, cu ajutorul unei structuri, a unei uniuni şi a unei clase. Datele membre sunt lungimea şi lăţimea (variabilele Lung, lat). Funcţiile membre sunt: void seteaza_dimen(double, double) - primeşte ca argumente două valori reale şi iniţializează datele membre cu valorile argumentelor. double arata_Lung( ) - returnează valoarea lungimii (a datei membre Lung). double arata_Lat( ) - returnează valoarea lăţimii (a datei membre lat). double calcul_arie( ) - returnează valoarea ariei dreptunghiului. //a) Implementarea tipului dreptunghi cu ajutorul unei structuri. #include struct dreptunghi{ double Lung, lat; void seteaza_dimen(double, double );//prototipul funcţiei seteaza_dimen double arata_Lung() {return Lung;} double arata_Lat() {return lat;} double calcul_arie() {return Lung*lat;} }; void dreptunghi::seteaza_dimen(double L, double l) {Lung=L; lat=l;}

134

CAPITOLUL 10 Clase şi obiecte void main() { dreptunghi a; double l1, l2; cout<<"Lungime="; cin>>l1; cout<<"Latime="; cin>>l2; a.seteaza_dimen(l1, l2); // sau: Lung=l1; lat=l2; cout<<"Dimensiunile dreptunghiului sunt:"<
//b) Implementarea tipului dreptunghi cu ajutorul unei uniuni #include union dreptunghi{ double Lung, lat; void seteaza_dimen(double, double ); double arata_Lung() {return Lung;} double arata_Lat() {return lat;} double calcul_arie(double s) {return s*lat;} }; void dreptunghi::seteaza_dimen(double L, double l) {Lung=L; lat=l;} void main() { dreptunghi a; double l1, l2; cout<<"Lungime="; cin>>l1; cout<<"Latime="; cin>>l2; a.seteaza_dimen(l1, l1); cout<<"Lung. drept:"<
În exerciţiul 1 a, b se defineşte tipul dreptunghi printr-o structură, respectiv o uniune. Tipul conţine atât datele membre, cât şi metodele care implementează operaţiile care pot fi realizate asupra variabilelor de tipul dreptunghi. Metodele arata_Lung, arata_Lat, calcul_arie sunt definite în structură (uniune). Metoda seteaza_dimen este doar declarată în interiorul structurii (uniunii), fiind abia apoi definită. În varianta b (implementarea cu uniune, unde pentru memorarea valorilor datelor membre se utilizează aceeaşi zonă de memorie), pentru a păstra valorile atât pentru lungimea dreptunghiului, cât şi pentru lăţime, metodele au fost modificate. Nespecificând nici un modificator de control al accesului, toţi membrii (date şi metode) sunt implicit publici. De aceea, de exemplu, atribuirea unei valori pentru data membră Lung se putea realiza, la fel de bine, în corpul funcţiei main, astfel: Lung=l1; (în exerciţiul 1a, atribuirea se realizează cu ajutorul metodei seteaza_dimen). //c) Implementarea tipului dreptunghi cu ajutorul unei clase #include class dreptunghi{ double Lung, lat; public: void seteaza_dimen(double, double ); double arata_Lung() {return Lung;} double arata_Lat() {return lat;} double calcul_arie() 135

CAPITOLUL 10

Clase şi obiecte

{return Lung*lat;} }; void dreptunghi::seteaza_dimen(double L, double l) {Lung=L; lat=l;} void main() { dreptunghi a;double l1, l2; cout<<"Lungime="; cin>>l1;cout<<"Latime="; cin>>l2; a.seteaza_dimen(l1, l2);cout<<"Dimensiunile dreptunghiului sunt:"; cout<
În exerciţiul 1c se defineşte tipul de date dreptunghi cu ajutorul unei clase. Nivelul de acees implicit la membrii clasei este private. Dacă pentru metode nu s-ar fi folosit modificatorul de acces public, metodele nu ar fi putut fi folosite în funcţia main.

10.1.3. OBIECTE Un obiect este o dată de tip definit printr-o clasă. În exerciţiul anterior, punctul c, în funcţia main, se declară obiectul (variabila) a de tip dreptunghi. Spunem că obiectul a este o instanţă a clasei dreptunghi. Se pot declara oricâte obiecte (instanţe) ale clasei. Aşa cum se observă din exemplu, declararea obiectelor de un anumit tip are o formă asemănătoare celei pentru datele de tip predefinit: nume_clasa lista_obiecte; Exemple: dreptunghi a; dreptunghi b, c, d;

10.1.4. MEMBRII UNEI CLASE Datele membru se alocă distinct pentru fiecare instanţă (atribute ale instanţei) a clasei (pentru declararea obiectelor a, b, c, d de tip dreprunghi, vezi figura 10.1.). Excepţia de la această regulă o constituie datele membru statice, care există într-un singur exemplar, comun, pentru toate instanţele clasei.

a

Lung

a.Lung

Lung

b.Lung

lat

a.lat

lat

b.lat

Lung

d.Lung

lat

d.lat

b

Lung

c.Lung

lat

c.lat

c

d

Figura 10.1. Alocarea memoriei pentru datele membre nestatice

Metodele figurează într-un singur exemplar, oricâte instanţe ale clasei ar exista. În exemplul anterior, metoda seteaza_dimen este doar declarată în interiorul clasei, fiind abia apoi definită. La definirea funcţiei (void dreptunghi::seteaza_dimen(double L, double l)) s-a folosit operatorul :: (scope resolution operator) care specifică relaţia de apartenenţă a metodei la tipul 136

CAPITOLUL 10

Clase şi obiecte

dreptunghi. Operatorul cuplează nume_clasa::nume_functie_membru şi defineşte domeniul (scopul) în care acea funcţie va fi recunoscută. Prezenţa numelui clasei în faţa funcţiei membru este obligatorie, deoarece, altfel nu s-ar putea face distincţia între metode cu nume identice, care aparţin unor clase diferite. O funcţie membru se apelează totdeauna în strânsă dependenţă cu un obiect din clasa respectivă. Legătura dintre obiect şi funcţia membră se face prin operatorul . sau operatorul - rel="nofollow">, după cum obiectul este desemnat prin nume sau prin pointer (vezi exemplu). În plus, metodele statice pot fi apelate independent de un obiect al clasei, folosind operatorul de rezoluţie (::). Accesul la o metodă presupune o activitate de adresare, transparentă utilizatorului. De fapt, în interiorul obiectului creat se află doar punctatori la clasa din care provin. În interiorul definiţiei clasei se alocă o singură copie a fiecărei funcţie membră şi punctatorii respectiv, prin care obiectul va avea acces la metoda respectivă. Excepţia de la această regulă o constituie metodele virtuale (capitolul 12). Exemplu: Fie clasa dreptunghi din exerciţiul anterior. class dreptunghi{ double Lung, lat; public: void seteaza_dimen(double, double ); double arata_Lung(); double arata_Lat(); double calcul_arie(); };

//…………………………………………………………………………….. void main() { dreptunghi a;

//……………………………………………………………………

}

cout<<"Aria dreptunghiului:"<calcul_arie();

Exerciţiu: Să urmărim exerciţiul următor, care ilustrează problemele legate de membrii statici ai unei clase (figura 10.2.). #include #include class exemplu { int i; // dată membră privată, acces la ea doar prin metode public: static int contor; // dată membră publica, neprotejată (scop didactic) void inc(void) {i++;} Memoria void arata_i() statică {cout<<"i="<<<'\n';} void inc_contor(void) exemplu:: {contor++;} contor void init(void) {i=0;} static void arata_contor() {cout<<"Contor="<
Stivă a1.i a2.i a3.i

//iniţialiazarea datei membru statice

void exemplu::functie (exemplu *pe) {//i+=3; //eroare, nu se cunoaste obiectul care-l poseda pe i pe->i++; //corect, este specificat proprietarul lui i contor++; //variabilă statică, comună tuturor obiectelor } 137

Figura 10.2. Alocarea memoriei pentru datele membru statice şi nestatice

CAPITOLUL 10

Clase şi obiecte

void main() {clrscr(); a1.init(); a2.init(); a3.init(); //a1.i=0, a2.i=0, a3.i=0 a1.arata_i();a2.arata_i();a3.arata_i(); //i=0, i=0, i=0 a1.inc(); a2.inc(); a3.inc(); //a1.i=1, a2.i=1, a3.i=1 a1.arata_i();a2.arata_i();a3.arata_i(); //i=1, i=1, i=1 a1.functie(&a1); //contor=1, i=2 exemplu::functie(&a2); //contor=2, i=2 //functie(); //incorect a1.inc_contor(); //contor=3 exemplu::arata_contor(); a2.inc_contor(); //contor=4 exemplu::arata_contor(); a3.inc_contor(); //contor=5 exemplu::arata_contor(); exemplu::arata_contor(); exemplu::contor+=100; //membru public; contor=105 cout<<"Contor="<<exemplu::contor<<'\n'; //Contor=105 }

Din exemplul anterior, se poate observa că metoda statică funcţie poate fi apelată ca o metodă obişnuită, sau folosind operatorul ::. Pentru data membră statică contor se rezervă o zonă de memorie comună obiectelor a1, a2, a3. Pentru data membră i se realizează o copie pentru fiecare instanţă a clasei. Deasemenea, deoarece data membră contor este statică, nu aparţine unui anume obiect, ea apare prefixată de numele clasei şi operatorul de apartenenţă. Exerciţiu: Să se urmărească următorul exerciţiu, care defineşte tipul ex_mstat, cu o dată membru statică (s) şi metodele statice (set_s, ret_s). #include class ex_mstat{ int a; static double s; public: int ret_a(){return a;} void set_a(int x){a=x;} static double ret_s(){return s;} static void set_s(double x){s=x;} void set1_s(double x){s=x;} double ret1_s(){return s;} };

double ex_mstat::s; /*se rezervă spaţiu în memoria statică pentru data membră statică s, care figurează într-un singur exemplar pentru toate instaţele clasei ex_mstat (figura 10.3.)*/

ex_mstat::s

p

a

q

a

void main() {ex_mstat p,q; Figura 10.3. Alocarea memoriei p.set_a(100); pentru membrii clasei ex_mstat p.set_s(200); q.set_a(300); cout<<"p.a="<>real)) return 0; cout<<"P. imag:";if (!(cin>>imag)) return 0 ; return 1; } void complex::afisare() { if (imag>=0) cout<
CAPITOLUL 10 {if (real==0 && imag==0) return 0.0; if (imag==0) //z=p. reala if (real>0) return 0.0; else return PI; if (real==0) if (imag>0) return PI/2; else return (3*PI)/2; double x=atan(imag/real); if (real<0) return PI+x; if (imag<0) return 2*PI+x; return x;}

Clase şi obiecte

inline void complex::decrpi() { imag--;} double complex::retreal() { return real;} double complex::retimag() { return imag; } void complex::adun_c (complex x1, complex x2) {real=x1.real+x2.real; imag=x1.imag+x2.imag;} void complex::inm_c(complex *x1, complex *x2) {real=x1->real*x2->real-x1->imag*x2->imag; imag=x1->real*x2->imag+x1->imag*x2->real;} void main() {complex z1;z1.citire(); cout<<"z1=";z1.afisare(); complex z2;z2.citire();cout<<"z2=";z2.afisare(); cout<<"Modulul z2="<
În cazul tipurilor de date definite cu ajutorul claselor, care au date membru private, listele de iniţializare nu pot fi utilizate. Pentru a elimina aceste neajunsuri, limbajul C++ oferă mecanismul constructorilor şi al desctructorilor.

10.3.1. CONSTRUCTORI Constructorii sunt metode speciale care folosesc la crearea şi iniţializarea instanţelor unei clase. Constructorii au acelaşi nume ca şi clasa căreia îi aparţin şi sunt apelaţi de fiecare dată când se crează noi instanţe ale clasei. Constructorii asigură iniţializarea corectă a tuturor variabilelor membre ale obiectelor unei clase şi constituie o garanţie a faptului că iniţializarea unui obiect se realizează o singură dată. O clasă poate avea mai mulţi constructori (exemplul 3), care diferă între ei prin numărul şi tipul parametrilor acestora. Acest lucru este posibil deoarece limbajul C++ permite supradefinirea (overloading) funcţiilor. Supraîncarcarea (supradefinirea) reprezintă posibilitatea de a atribui unui nume mai multe semnificaţii, care sunt selectate în funcţie de context. Practic, se pot defini funcţii cu acelaşi nume, dar cu liste de parametri diferite, ca număr şi/sau ca tipuri de parametri. În momentul apelului funcţiei, selectarea funcţiei adecvate se face în urma comparării tipurilor parametrilor efectivi cu tipurile parametrilor formali. De aceea, declararea unor funcţii cu acelaşi nume şi acelaşi set de parametri este ilegală şi este semnalată ca eroare la compilare. La întâlnirea declaraţiei unui obiect, se apelează automat un constructor al clasei respective. La fiecare instanţiere a clasei se alocă memorie pentru datele membre. Deci pentru fiecare obiect declarat se alocă memorie pentru datele membre ale clasei. Excepţie de la această regulă o constituie datele membru statice. Acestea figurează într-un singur exemplar pentru toate instanţele clasei respective. Funcţiile membru există într-un singur exemplar pentru toate instanţele clasei. Ordinea în care sunt apelaţi constructorii corespunde ordinii declarării obiectelor. Proprietăţile constructorilor:  Constructorii au acelaţi nume ca şi numele clasei căreia îi aparţin;  Nu întorc nici o valoare (din corpul lor lipseşte intrucţiunea return; în antetul constructorilor nu se specifică niciodată - la tipul valorii returnate - cuvântul cheie void);  Constructorii unei clase nu pot primi ca parametri instanţe ale clasei respective, ci doar pointeri sau referinţe la instanţele clasei respective;  Apelul constructorului se realizează la declararea unui obiect;  Adresa constructorilor nu este accesibilă utilizatorului;  Constructorii nu pot fi metode virtuale (capitolul 12);  În cazul în care o clasă nu are nici constructor declarat de către programator, compilatorul generează un constructor implicit, fără nici un parametru, cu lista instrucţiunilor vidă. Dacă există un constructor al programatorului, compilatorul nu mai generează constructorul implicit (exemplul 2);  Parametrii unui constructor nu pot fi de tipul definit de clasa al cărei membru este constructorul. Ca orice altă funcţie în limbajul C++, constructorii pot avea parametri impliciţi (vezi capitolul 6.5.), fiind numiţi constructori impliciţi. Varianta constructorului cu parametri impliciţi poate fi adoptată în toate cazurile în care constructorul nu necesită argumente. Dacă toţi parametrii unui constructor sunt impliciţi, apelul constructorului are forma unei simple declaraţii (exemplul 1). Constructorii pot fi apelaţi şi în mod explicit (exemplul 1). În cazul în care dorim să instanţiem obiecte atât iniţializate, cât şi neiniţializate se poate folosi un constructor implicit vid, care se va apela la instanţierea obiectelor neiniţializate (exemplul 3). Exemplul1: Pentru clasa complex s-a definit un constructor cu parametri impliciţi; din acest motiv s-a putut face declaraţia "complex z1;" . În ultima linie a programului, pentru obiectul z4, constructorul este apelat în mod explicit. 143

CAPITOLUL 10 class complex { double real,imag; public: complex(double x=0, double y=0); // Constructor implicit }; complex::complex(double x, double y) {real=x; imag=y; } void main() {complex z1; //z1.real=0, z1.imag=0 complex z2(1); //z2.real=1, z2.imag=0 complex z3(2,4); //z3.real=2, z3.imag=4 complex z4=complex(); //apel explicit al constructorului }

Clase şi obiecte

La apelul explicit al constructorului: complex z4=complex(); Evaluarea expresiei complex() conduce la:  Crearea unui obiect temporar de tip punct (obiect cu o adresă precisă, dar inaccesibil);  Apelul constructorului pentru acest obiect temporar;  Copierea acestui obiect temporar în z4. Exemplul2: Pentru clasa complex există un constructor explicit, compilatorul nu mai creează unul implicit. class complex { double real,imag; public: complex(double x,double y) // funcţie constructor inline { real=x; imag=y;} }; void main() {complex z1(2,3); complex z; // Eroare : nu există constructor implicit }

Exemplul3: Definirea unui constructor implicit vid, care se va apela la instanţierea obiectelor neiniţializate. #include class data{ int zi,luna,an; public: data() { } data(int z,int l,int a) { zi=z;luna=l;an=a; } }; void main() {data d; data d1(12,11,1998); }

// constructor implicit vid // constructor cu parametri

// apelul constructorului vid // apelul constructorului cu parametri

10.3.1.1. Constructori cu liste de iniţializare În exemplele anterioare, constructorii iniţializau membrii unui obiect prin atribuiri. Există şi modalitatea de a iniţializa membrii printr-o listă de instanţiere (iniţializare), care apare în implementarea constructorului, între antetul şi corpul acestuia. Lista conţine operatorul :, urmat de numele fiecărui membru şi valoarea de iniţializare, în ordinea în care membrii apar în definiţia clasei. Exemplu: Pentru clasa complex s-a implementat un constructor de iniţializare cu listă de instanţiere class complex { double real,imag; public: 144

CAPITOLUL 10

Clase şi obiecte

complex(double x, double y); //constructor }; complex::complex(double x, double y) :real(x),imag(y) // Listă de iniţializare a membrilor { return; } void main() { complex z1(1,3),z2(2,3); }

// Sau: class complex { double real,imag; public: complex(double x, double y) :real(x),imag(y) { }

//constructor cu listă de iniţializare };

10.3.1.2. Constructori de copiere Pentru o clasă, se poate defini un contructor de copiere, care să permită copierea obiectelor. Deoarece parametrii unui constructor nu pot fi de tipul definit de clasa al cărei membru este, constructorul de copiere pentru clasa cls, are, de obicei, prototipul: cls (const cls &); Parametrul transmis prin referinţă este obiectul a cărui copiere se realizează, modificatorul de acces const interzicând modificarea acestuia. Constructorul de copiere poate avea şi alţi parametri, care trebuie să fie impliciţi. Dacă programatorul nu defineşte un constructor de copiere, compilatorul generează un asemenea constructor, implicit. În situaţiile în care un tip de date are ca membrii pointeri, este necesară implementarea unui constructor pentru iniţializare (este de dorit să se aloce dinamic memorie) şi a unui constructor de copiere.

10.3.2. DESTRUCTORI Destructorii sunt metode ale claselor care acţionează în sens invers, complementar, faţă de constructori. Constructorii sunt folosiţi pentru alocarea memoriei, iniţializarea datelor membru sau alte operaţii (cum ar fi, incrementarea unui contor pentru instanţele clasei). Constructorul este apelat în momentul declarării obiectelor. Destructorul eliberează memoria alocată de constructori. Destructorul este apelat automat, la ieşirea din blocul în care este recunoscut acel obiect. Proprietăţile destructorilor  Destructorul are acelaşi nume ca şi clasa a căror metodă este;  Numele destructorului este precedat de semnul ~;  O clasă are un singur destructor;  Destructorul nu are parametri şi nu returnează nici o valoare (antetul nu conţine cuvântul cheie void, iar în corpul destructorului nu apare instrucţiunea return;);  Dacă programatorul nu a definit un destructor, compilatorul generează automat un destructor pentru clasa respectivă;  Destructorii se apelează la încheierea timpului de viaţă a obiectelor, în ordine inversă apelurilor constructorilor;  Obiectele dinamice nu se distrug automat, deoarece doar programatorul ştie când nu mai este necesar un astfel de obiect.

145

CAPITOLUL 10

Clase şi obiecte

Exerciţiu: Să se definească tipul punct, cu datele membre x şi y, reprezentând abscisa şi ordonata unui punct. Operaţiile care pot fi realizate asupra obiectelor de tip punct, sunt: afişare (afişează coordonatele unui punct), deplasare (deplasează un punct, noile coordonate ale punctului fiind obţinute prin adunarea unor valori transmise ca parametri, la valorile anterioare ale coordonatelor), abscisa (returnează valoarea abscisei), ordonata (returnează valoarea ordonatei). Se vor implementa, deasemenea, constructor cu parametri impliciţi, constructor având ca parametri valorile abscisei şi a ordonatei, constructor de copiere şi destructor. Să se definească tipul segment, cu datele membre A şi B, de tip punct, reprezentând capetele unui segment (originea şi vârful). Operaţiile care pot fi realizate asupra obiectelor de tip segment, sunt: afişare (afişează coordonatele capetellor segmentului), deplasare (translatează un segment, deplasând capetele acestuia cu valorile transmise ca parametri), origine (returnează originea segmentului), vârf (returnează vârful segmentului). Se vor implementa, deasemenea, constructor, constructor de copiere şi destructor. Să se testeze tipurile de date punct şi segment. #include #include #include #include

<stdlib.h> <stdio.h>

//CLASA PUNCT class punct { double x,y; public: punct() {x=0;y=0;cout<<"Constr. implicit pentru punct("<<x<<","<
//constructor initializare punct(double,double); punct(punct&); //constructor copiere ~punct(); //destructor double abscisa(){return x;} double ordonata(){return y;} void afisare(); void deplasare(double,double); };

//CLASA SEGMENT class segment {private: punct A,B; public: segment(punct&,punct&); //constructor segment(segment&); //constructor copiere ~segment(); //destructor punct origine(); punct varf(); void afisare(); void translatie(double,double); };

//METODELE CLASEI PUNCT punct::punct(double valx,double valy) { x=valx;y=valy; cout<<"Constructor punct ("<<x<<","<
CAPITOLUL 10 void punct::afisare() { cout<<"Punct ("<<x<<','<
Clase şi obiecte

//METODELE CLASEI SEGMENT segment::segment(punct &A1,punct &B1) { A=A1;B=B1;cout<<"Constructor segment[";A.afisare();B.afisare(); cout<<"]\n";} segment::segment(segment &AB) { A=AB.A; B=AB.B; cout<<"Constructor copiere segment ["; A.afisare(); B.afisare();cout<<"]\n";} punct segment::origine() { return A;} punct segment::varf() { return B;} void segment::afisare() { cout<<"[";A.afisare();cout<<','; B.afisare();cout<<"]"<<'\n'; } segment::~segment() { cout<<"Destructor segment [";A.afisare(); cout<<",";B.afisare(); cout<<"]\n";} void segment::translatie(double dx,double dy) { A.deplasare(dx,dy); B.deplasare(dx,dy);} void main() {clrscr(); punct P(7.8,-20.4),Q(-4.82,8.897),A,B;

/*Constructor punct (7.8,-20.4) (Pentru punctul P) Constructor punct (-4.82,8.897) (Pentru punctul Q) Constr. implicit pentru punct(0,0) Constr. implicit pentru punct(0,0) (pentru punctele A, B)*/ punct P3, Q3;

// Constr. implicit pentru punct(0,0)Constr. implicit pentru punct(0,0) (pentru punctele P3, Q3) segment S(P,Q);

/* Constr. implicit pentru punct(0,0)Constr. implicit pentru punct(0,0) (pentru membrii A, B ai obiectului S, deci pentru S.A şi S.B) Constructor segment[Punct (7.8,-20.4)Punct (-4.82,8.897)] (pentru obiectul S, de tip segment) */ segment S1(P3,Q3);

/* Constr. implicit pentru punct(0,0) Constr. implicit pentru punct(0,0) (pentru membrii A, B ai obiectului S1, deci pentru S1.A şi S1.B) Constructor segment[Punct (0,0)Punct (0,0)] (pentru obiectul S1, de tip segment) */ printf("Apasa un car. ptr. continuare!\n"); getch(); cout<<"Punctele:\n"; P.afisare();cout<<'\n'; Q.afisare();cout<<'\n'; P3.afisare();cout<<'\n'; Q3.afisare();cout<<'\n'; A.afisare(); cout<<'\n'; B.afisare();cout<<'\n'; cout<<"\nSegment:"; S.afisare(); cout<<'\n';

/* Punctele: Punct (7.8,-20.4) (pentru obiectul P) Punct (-4.82,8.897) (pentru obiectul Q) Punct (0,0) (pentru obiectul A) Punct (0,0) (pentru obiectul B) Punct (0,0) (pentru obiectul P3) Punct (0,0) (pentru obiectul Q3) Segment:[Punct (7.8,-20.4),Punct (-4.82,8.897)] */ punct D(1,2); punct C;

// Constructor punct (1,2)Constr. implicit pentru punct(0,0) (pentru punctele D, C) C=D; //operaţie de atribuire C.afisare(); // Punct (1,2) (pentru punctul C) getch(); punct CC=C; // Constructor copiere pct (1,2) cout<<"In urma copierii:"; CC.afisare(); 147

CAPITOLUL 10

Clase şi obiecte

// În urma copierii:Punct (1,2) (pentru punctul CC) cout<<"Se deplaseaza punctul CC cu valorile 10, 20. Noile coord.="; CC.deplasare(10, 20); CC.afisare();

// Se deplaseaza punctul CC cu valorile 10, 20. Noile coord.=Punct (11,22) cout<<"Abscisa CC="<
// Translatie S1 cu 100,1000. S1 translatat este:[Punct (107.8,979.6),Punct (95.18,1008.897)] /* Constr. implicit pentru punct(0,0) (pentru S2.A) Constr. implicit pentru punct(0,0) (pentru S2.B) Constructor copiere segment [Punct (107.8,979.6)Punct (95.18,1008.897)] Destructor segment [Punct (107.8,979.6),Punct (95.18,1008.897)]*/ segment S2=S1;

cout<<"Segment S2 obtinut prin copiere:"; S2.afisare(); // Segment S2 obtinut prin copiere:[Punct (107.8,979.6),Punct (95.18,1008.897)] cout<<"Iesire din main\n"; // Iesire din main }

/* La ieşirea din funcţia main, deci la terminarea duratei de viaţă a obiectelor, se apelează automat destructorii, în ordinea inversă în care au fost apelaţi constructorii, astfel: Destructor segment [Punct (107.8,979.6),Punct (95.18,1008.897)] (pentru segmentul S2) Destructor punct (95.18,1008.897) (pentru membrii B, respectiv A, ai segmentului S2: S2.B, apoi S2.A) Destructor punct (107.8,979.6) Destructor punct (7.8,-20.4) (pentru CC) Destructor punct (1,2) (pentru C) Destructor punct (1,2) (pentru D) Destructor segment [Punct (107.8,979.6),Punct (95.18,1008.897)] (pentru segmentul S1) Destructor punct (95.18,1008.897) (pentru membrii B, respectiv A, ai segmentului S1: S1.B, apoi S1.A) Destructor punct (107.8,979.6) Destructor segment [Punct (7.8,-20.4),Punct (-4.82,8.897)] (pentru segmentul S) Destructor punct (-4.82,8.897) (pentru membrii B, respectiv A, ai segmentului S: S.B, apoi S.A) Destructor punct (7.8,-20.4) Destructor punct (0,0) (pentru punctul Q3) Destructor punct (0,0) (pentru punctul P3) Destructor punct (0,0) (pentru punctul B) Destructor punct (0,0) (pentru punctul A) Destructor punct (-4.82,8.897) (pentru punctul Q) Destructor punct (7.8,-20.4) (pentru punctul P) */ Exerciţiul evidenţiază următoarele probleme: 1. În situaţia în care o clasă C1 are ca date membre obiecte ale altei clase C2 (clasa segment are ca date membre obiecte de tipul punct), la construirea unui obiect din C1, se apelează întâi constructorul C2 pentru membrii (de tip C2), apoi constructorul C1. Un astfel de exemplu îl constituie declararea segmentului S: segment S(P,Q);

148

CAPITOLUL 10

Clase şi obiecte

Se apelează întâi constructorul implicit al clasei punct pentru membrii A şi B ai segmentului S (deci pentru S.A şi S.B), apoi constructorul clasei segment (figura 10.4.). La distrugerea obiectului din clasa C1, destructorii sunt apelaţi în ordinea inversă constructorilor (întâi se apelează destructorul clasei segment - învelişul exterior, apoi destructorul pentru membrii de tip punct). 2. Să revedem secvenţa: punct D(1,2); punct C; C=D;

În acest caz, se realizează o atribuire, membru cu membru, echivalentă cu C.x=D.x şi C.y=D.y. 3. Să revedem secvenţa:

A.x

punct CC=C;

În acest caz, se apelează constructorul de copiere, care crează punctul CC prin copierea punctului C. Apelul constructorului de copiere se poate realiza şi explicit: punct CC=punct(C); 4.

Parametrii transmişi unei funcţii pot fi obiecte, pointeri către obiecte sau referinţe către obiecte. Valoarea returnată de o funcţie poate fi un obiect, pointer către obiect sau referinţă către obiect. Să luăm ca exemplu constructorul clasei segment:

A

S B

7.8

A.y

-20.4

B.x

B.y

-4.82

8.897

Figura 10.4. Apelul constructorilor

segment::segment(punct &A1,punct &B1) { A=A1;B=B1;cout<<"Constructor segment\n";}

Constructorul primeşte ca parametri referinţe către obiecte de tipul punct. Apelul constructorului: segment S(P, Q);

Parametrii efectivi P şi Q sunt referinţe pentru A1 şi B1 (aceleaşi obiecte). Ca urmare, se apelează cei doi constructori impliciţi pentru membrii A şi B ai segmentului S. În urma operaţiei de atribuire din corpul constructorului segmentului, ei sunt iniţializaţi. Mesajele: "Constructor pct implicit!!" (pentru membrul A al segmentului S) "Constructor pct implicit!!" (pentru membrul B al segmentului S) "Constructor segment" (pentru segmentului S) Constructorului puteau să i se transmită parametri prin pointeri: segment::segment(punct *A1,punct *B1) { A=*A1;B=*B1;cout<<"Constructor segment\n";} Apelul: segment S(&P, &Q); Parametrii formali A1 şi B1 sunt iniţializaţi în momentul apelului constructorului cu adresele punctelor P,

respectiv Q. Situaţia este similară celei anterioare, mesajele obţinute sunt identice celor obţinute în cazul transmiterii parametrilor prin referinţă. Constructorului puteau să i se transmită parametri prin valoare: segment::segment(punct A1,punct B1) { A=A1;B=B1;cout<<"Constructor segment\n";} segment S(P, Q);

Apelul: În această situaţie, la apel, pentru parametrii formali A1 şi B1 se rezervă memorie pe stivă: obiectele locale constructorului, A1 şi B1, sunt iniţializate prin copiere (la transmiterea parametrilor prin valoare, se realizează o copiere a parametrilor efectivi în parametrii formali, vezi capitolul 6.3.1.). La terminarea execuţiei corpului funcţiei, punctele A1 şi B1 sunt distruse. De aceea, mesajele din această situaţie, sunt: "Constructor copiere punct!!" (pentru A1, local constructorului) "Constructor copiere punct!!" (pentru B1, local constructorului) "Constructor pct. implicit!!" (pentru membrul A al segmentului) "Constructor pct. implicit!!" (pentru membrul B al segmentului) "Constructor segment!" (pentru segmentul S) "Destructor punct!" (pentru B1, la ieşirea din constructor) "Destructor punct!" (pentru A1, la ieşirea din constructor) Exerciţiu: Pentru tipurile punct şi segment implementate anterior, să se scrie şi să se testeze următorul program, în care obiectele A, B (de tip punct) şi AB (de tip segment) sunt globale (declarate în afara oricărei funcţii). Se folosesc, deasemenea, variabile locale statice. Pentru variabilele globale (A, B, AB) şi cele locale declarate explicit statice (P1 din test1, U şi V din blocul interior funcţiei main), se alocă memorie statică. 149

CAPITOLUL 10

Clase şi obiecte

Pentru variabilele locale se alocă memorie automatic, pe stivă. Să se urmărească evidenţieze crearea şi distrugerea obiectelor statice şi automatici, domeniul de vizibilitate şi timpul de viaţă. class punct{ //. . . . . }; class segment { //. . . . . };

//Implementarea metodelor clasei punct //Implementarea metodelor clasei segment punct test1() { cout<<"Intrare in test1\n"; static punct P1(20,10); P1.deplasare(1,1); cout<<"Iesire din test1\n";return P1;} punct test2() { punct P1(100,100);P1.deplasare(10,20);return P1; } punct A(1,2), B; segment AB(A, B); void main() {cout<<"S-a intrat in main!\n"; punct E(1, 1), F, G; F=test1(); cout<<"F="; F.afisare(); getch();G=test2();cout<<"Intrare in test1\n";cout<<"G="; G.afisare(); { cout<<"Intrare in blocul interior\n"; static punct U(5,2);punct C(9,9), D(5.5,6.6);static punct V(8,3);getch(); F=test1(); cout<<"Intrare in test1\n";F.afisare();G=test2(); cout<<"Intrare in test2\n";G.afisare(); cout<<"Iesire din blocul interior\n"; } getch();A.afisare();F.afisare();AB.afisare();AB.translatie(10, 10); cout<<"Segment translatat:"; AB.afisare(); cout<<"Segmentul AB are originea:"; (AB.origine()).afisare(); cout<<"Segmentul AB are varful:"; (AB.varf()).afisare(); cout<<"Iesire din main()\n"; }

10.3.4. TABLOURI DE OBIECTE Obiectele de acelaşi tip pot fi grupate în tablouri. Dacă un tablou este declarat fără a fi iniţializat, pentru construirea elementelor sale se apela constructorul implicit. Elementele unui tablou pot fi iniţializate şi cu ajutorul constructorilor cu parametri. Exemplu: Fie clasele punct şi segment din exerciţiul anterior. class punct{ //……… }; class segment{

//………

};

//implementarea metodelor claselor punct si segment void main() {punct P(7, -7), Q(-8, 8); punct PC=P, QC=Q; punct V[3]; //apelul constructorului implicit pentru fiecare din cele 3 elemente ale vectorului V punct V1[2]={P, Q}; //apelul constructorului de copiere pentru elementele V1[0] si V1[1] punct V2[2]={punct(10,10), punct(100,100)};

//apelul constructorului cu parametri pentru fiecare din cele 2 elemente, V2[0] si V2[1] segment SV[2];

//EROARE: deoarece exista un constructor cu parametri, nu se genereaza automat constructorul implicit 150

CAPITOLUL 10 Clase şi obiecte segment SV[2]={segment(PC, P), segment(QC, Q)}; segment SV[2]={segment(punct(5,5), punct(55,55)), segment (punct(10,10), punct(100,100))}; }

10.4. FUNCŢII PRIETENE Funcţiile prietene (friend) sunt funcţii ne-membre ale unei clase, care au acces la datele membre private ale unei clase. Funcţiile prietene ale unei clase trebuie precizate în definiţia clasei. În acest sens, prototipurile unor astfel de funcţii sunt precedate de cuvântul cheie friend. Spre deosebire de funcţiile membre, funcţiile prietene ale unei clase nu posedă pointerul implicit this. De aceea, deosebirea esenţială două funcţii care realizează aceleaşi prelucrări, o funcţie membră şi o funcţie prietenă, constă în faptul că funcţia prietenă are un parametru în plus faţă de funcţia membru. O funcţie poate fi în acelaşi timp funcţie membră a unei clase şi funcţie prietenă a altei clase. În exemplul următor, f1 este funcţie membră a clasei cls1 şi funcţie prietenă a clasei cls2. Exemplu: class cls1{ // . . . int f1(int, char); // f1 - metodă a clasei cls1 // . . . }; class cls2{ //. . . friend int cls1::f1(int, char);

// f1 - prietenă a clasei cls2

//. . . };

În cazul în care se doreşte ca toate funcţiile membre ale unei clase să aibă acces la membrii privaţi ai altei clase (să fie funcţii prietene), prima clasă poate fi declarată clasa prietenă pentru cea de-a doua clasă. În exemplul următor, clasa cls1 este clasa prietenă a clasei cls2. Exemplu: class cls1; class cls2{ //. . . friend cls1;

// clasa cls1 este clasă prietenă a clasei cls2

//. . . };

Relaţia de clasa prietenă nu este tranzitivă. Astfel, dacă clasa cls1 este clasa prietenă a clasei cls2, iar clasa cls2 este clasă prietenă a clasei cls3, aceasta nu implică faptul că cls1 este clasă prietenă pentru cls3.

ÎNTREBĂRI ŞI EXERCIŢII Chestiuni teoretice 1. Când acţionează constructorul unei clase? 2. Când acţionează destructorul unei clase? 3. Când este absolut necesară definirea unui constructor de copiere? 4. Când se justifică utilizarea funcţiilor inline? 5. Caracteristicile destructorului unei clase. 6. Care este utilitatea moştenirii? 7. Care sunt deosebirile între o funcţie membră a unei clase şi o functie prietenă a unei clase? 8. Ce fel de metode pot acţiona asupra datelor membre statice ale unei clase?

9. Ce funcţii au acces la membrii privaţi ai unei clase? 10. Ce observaţie aveţi în legătură cu metodele definite în interiorul clasei şi funcţiile inline? 11. Ce operator permite referirea unui membru al structurii ? 12. Ce sunt clasele? 13. Ce sunt constructorii impliciţi? 14. Ce sunt destructorii ? 15. Ce sunt funcţiile inline? 16. Ce este o metodă?

151

CAPITOLUL 10

Clase şi obiecte

17. Constructorii unei clase pot primi ca parametri instanţe ale clasei respective? Dacă da, în ce condiţii? 18. Ce sunt funcţiile prietene? 19. Cine impune comportamentul unui obiect? 20. Cum se alocă memoria pentru datele membre nestatice în momentul declarării mai multor obiecte din aceeasi clasă? 21. Cum se declară funcţiile prietene? 22. Deosebiri între stucturi şi clase. 23. Enumerati facilitatile oferite de programarea orientata obiect. 24. Explicaţi conceptul de încapsulare a datelor. 25. Explicaţi în câteva cuvinte ce este mostenirea multiplă. 26. Explicaţi în câteva cuvinte ce este moştenirea.

27. Niveluri de acces la membrii şi metodele unei clase. 28. O clasă poate avea mai mulţi desctructori? Dacă da, în ce condiţii? 29. O clasă poate fi prietenă a altei clase ? Dacă da, ce înseamnă acest lucru? 30. Operatorul :: şi rolul său. 31. Prin ce se caracterizează datele membre statice? 32. Prin ce se realizează comunicarea între obiectele unei clase? 33. Prototipul constructorului de copiere. 34. Nici funcţiile prietene, nici metodele statice ale unei clase nu primesc ca argument implicit pointerul this. Explicaţi care sunt, totuşi, diferenţele dintre ele.

Chestiuni practice Completaţi tipul de date complex, cu funcţiile (membre sau prietene) care vor realiza următoarele operaţii: Adunarea unei date de tip complex cu o dată de tip real; scăderea a două date de tip complex; scăderea unui real dintr-un complex; împărţirea a două date de tip complex; înmulţirea unui real cu un complex; ridicarea la o putere întreagă a unei date de tip complex; compararea a două date de tip complex. 2. Să se scrie un program care citeşte câte două date de tip complex, până la întâlnirea perechii de date (z1=0+i*0, z2=0+i*0). Pentru fiecare pereche de date, să se afişeze suma, diferenţa, produsul şi câtul. 3. Să se scrie un program care citeşte datele a, b, c de tip complex, rezolvă şi afişează rădăcinile ecuaţiei de gradul doi: ax 2 +bx+c=0. 4. Care sunt greşelile din următoarele secvenţe? 1.

a) class ex1{ char *nume; int lungime; void init (char *s, int l) {strcpy(nume, s); lungime=l;} }; ex1 A; A.init("teava", 20); b) union numar{ private: char exponent, mantisa[3]; public: char exp(); }; c) class complex{ int real, imag; complex (int x, int y) {real=x; imag=y;} }; void main() { complex Z(20, 30); }

152

CAPITOLUL 11

Supraîncărcarea operatorilor

SUPRAÎNCĂRCAREA OPERATORILOR 11.1. Moduri de supraîncărcare a operatorilor 11.1.1. Supraîncărcarea prin funcţii membre 11.1.2. Supraîncărcarea prin funcţii prietene 11.2. Restricţii la supraîncărcarea operatorilor 11.3. Supraîncărcarea operatorilor unari 11.4. Membrii constanţi ai unei clase 11.5. Supraîncărcarea operatorilor insertor şi extractor

1

11.6. Supraîncărcarea operatorului de atribuire = 11.7. Supraîncărcarea operatorului de indexare [ ] 11.8. Supraîncărcarea operatorilor new şi delete 11.9. Supraîncărcarea operatorului ( ) 11.10. Supraîncărcarea operatorului -> 11.11. Conversii

11.1. MODURI DE SUPRAÎNCĂRCARE A OPERATORILOR Supraîncărcarea (supradefinirea, termenul overloading) operatorilor permite atribuirea de noi semnificaţii operatorilor uzuali (operatorilor intâlniţi pentru tipurile de date predefinite). Aşa cum am subliniat în numeroase rânduri, clasa reprezintă un tip de date (o mulţime de valori pentru care s-a adoptat un anumit mod de reprezentare şi o mulţime de operaţii care pot fi aplicate acestora). Astfel, operatorul + foloseşte la adunarea a două date de tip int, float sau double, însă aceluiaşi operator i se poate atribui semnificaţia de "alipire" a două obiecte de tipul şir, sau de adunare a două obiecte de tipul complex, vector sau matrice. Observaţie: Operatorii sunt deja supradefiniţi pentru a putea opera asupra mai multor tipuri de bază (de exemplu, operatorul + admite operanzi de tip int, dar şi float sau double), sau pot avea seminificaţii diferite (de exemplu, operatorul * poate fi folosit pentru înmulţirea a doi operanzi numerici sau ca operator de deferenţiere, operatorul >> poate avea semnificaţia de operator extractor sau operator de deplasare pe bit). Prin supraîncărcarea operatorilor, operaţiile care pot fi executate asupra instanţelor (obiectelor) unei clase pot fi folosite ca şi în cazul tipurilor de date predefinite. Exemplu: Pentru clasa punct (vezi capitolul 10), putem atribui operatorului + semnificaţia: expresia a+b (a, b sunt obiecte din clasa punct) reprezintă "suma" a două puncte şi este un punct ale cărui coordonate sunt date de suma coordonatelor punctelor a şi b. Astfel, supradefinirea operatorului + constă în definrea unei funcţii cu numele: operator + tip_val_întoarsă operator op (lista_declar_parametri) {

// . . . .

corpul funcţiei

}

Deci, limbajul C++ permite supradefinirea operatorului op prin definirea unei funcţii numite operator op Funcţia trebuie să poată accesa datele membre private ale clasei, deci supradefinirea operatorilor se poate realiza în două moduri:  printr-o funcţie membră a clasei;  printr-o funcţie prietenă a clasei.

11.1.1. SUPRAÎNCĂRCAREA OPERATORILOR PRIN FUNCŢII MEMBRE În situaţia în care supraîncărcarea operatorului + se realizează printr-o funcţie membră, aceasta primeşte ca parametru implicit adresa obiectului curent (pentru care este apelată). Deci primul operand al operatorului este transmis implicit. 153

CAPITOLUL 11

Supraîncărcarea operatorilor

Exemplu: class punct{ double x, y; public:

// . . . . . . punct operator + (punct); };

//Metodele clasei punct…………………… punct punct::operator + (punct a) { punct p; p.x=x + a.x; //echivalent cu p.x=this->x+a.x; p.y=y + a.y; //echivalent cu p.y=this->y+a.y; return p; } void main() { punct A(1.1, 2.2); A.afişare(); punct B(-5.5, -6.6); B.afişare(); punct C; C=A+B; C.afişare(); C=A+B+C; C.afişare(); }

Expresia C=A+B este interpretată ca C = A.operator + (B). Expresia C=A+B+C poate fi interpretată, în funcţie de compilator, astfel: Unele compilatoare crează un obiect temporar T: T = A.operator + (B) C = T.operator + (C)

Alte compilatoare interpretează expresia ca: C=(A.operator + (B)).operator + (C).

11.1.2. SUPRAÎNCĂRCAREA OPERATORILOR PRIN FUNCŢII PRIETENE Fie clasa punct definită anterior. Reamintind faptul că funcţiile prietene au acces la membrii privaţi ai unei clase, însă nu primesc ca argument implicit pointerul către obiectul curent (this), să supraîncărcăm operatorul + printr-o funcţie prietenă a clasei punct: class punct{ double x, y; public:

// . . . . . . };

friend punct operator + (punct, punct);

//Metodele clasei punct……………………. punct operator + (punct a, punct b) { punct p; p.x=a.x + b.x; p.y=a.y + b.y; return p; } void main() { punct A(1.1, 2.2); A.afişare(); punct B(-5.5, -6.6); B.afişare(); punct C; C=A+B; C.afişare(); C=A+B+C; C.afişare(); }

Expresia C=A+B este interpretată de compilator ca C=operator + (A, B). Expresia C=A+B+C este evaluată ţiinându-se cont de regulile de prioritate şi de asociativitate a operatorului: (A+B)+C , ceea ce conduce la un apel de forma: operator + (operator + (A, B), C).

154

CAPITOLUL 11

Supraîncărcarea operatorilor

Observaţie: În exemplul anterior, transmiterea parametrilor către funcţia prietenă de supraîncărcare a operatorului + se realizează prin valoare. Parametrii pot fi transmişi şi prin referinţă, pentru a evita crearea (în momentul apelului funcţiei) unor copii locale ale parametrilor efectivi în cei formali. La transmiterea parametrilor prin referinţă, funcţia operator + are prototipul: punct operator + (punct &, punct &);

Pentru a proteja argumentele transmise prin referinţă la eventualele modificări, se poate folosi modificatorul de acces const: punct operator + (const punct &, const punct &);

11.2. RESTRICŢII LA SUPRAÎNCĂRCAREA OPERATORILOR Supraîncărcarea operatorilor se poate realiza, deci, prin funcţii membre sau funcţii prietene. Dacă supraîncărcăm acelaşi operator printr-o metodă şi printr-o funcţie prietenă, funcţia prietenă va avea, întotdeauna, un parametru în plus faţă de metodă (deoarece funcţiei prietene nu i se transmite ca parametru implicit pointerul this). Totuşi, supraîncărcarea operatorilor este supusă următoarelor restricţii:  Se pot supraîncărca doar operatorii existenţi; nu se pot crea noi operatori.  Nu se poate modifica aritatea (numărul de operanzi) operatorilor limbajului (operatorii unari nu pot fi supraincărcaţi ca operatori binari, şi invers).  Nu se poate modifica precedenţa şi asociativitatea operatorilor.  Deşi operatorii supraîncărcaţi păstrează aritatea şi precedenţa operatorilor predefiniţi, ei nu moştenesc şi comutativitatea acestora.  Nu pot fi supraîncărcaţi operatorii . ::? şi : Observaţii:  În tabelul 2.8. (capitolul 2) sunt prezentaţi operatorii existenţi, precedenţa şi asociativitatea acestora.  Dacă operatorul = nu este supraîncărcat, el are o semnificaţie implicită.  Operatorii , new delete [ ] -> şi cast impun restricţii suplimentare care vor fi discutate ulterior.  Funcţia operator trebuie să aibă cel puţin un argument (implicit sau explicit) de tipul clasei pentru care sa supraîncărcat operatorul. Astfel:  La supraîncărcarea unui operator unar printr-o funcţie membră a clasei, aceasta are un argument implicit de tipul clasei (obiectul care îl apelează) şi nici un argument explicit. La supraîncărcarea operatorului unar printr-o funcţie prietenă, aceasta are un argument explicit de tipul clasei.  La supraîncărcarea unui operator binar printr-o funcţie membră a clasei, aceasta are un argument implicit de tipul clasei (obiectul care îl apelează) şi un argument explicit. La supraîncărcarea operatorului binar printr-o funcţie prietenă, aceasta are două argumente explicite de tipul clasei.  Se poate atribui unui operator orice semnificaţie, însă este de dorit ca noua semnificaţie să fie cât mai apropiată de semnificaţia naturală. De exemplu, pentru adunarea a două obiecte se poate supraîncărca operatorul * , dar este mai naturală folosirea operatorului + cu semnificaţia de adunare.  În cazul supradefinirii operatorilor, nu se poate conta pe comutativitatea acestora. De exemplu, dacă se supraîncarcă operatorul + pentru clasa complex printr-o funcţie prietenă a clasei complex: complex operator + (complex, double) Operatorul poate fi folosit în expresii cum ar fi: a+7.8 (a este obiect al clasei complex), dar nu în expresii ca: 7.8 + a.  Dacă un operator trebuie să primească ca prim parametru un tip predefinit, acesta nu poate fi supradefinit printr-o funcţie membră.  Operatorii care prezintă şi alte particularităţi, vor fi trataţi separat (vezi 11.7.,11.8., 11.10., 11.11.).  În principiu, metodele care supraîncarcă un operator nu sunt statice. Excepţia o constituie operatorii new şi delete (vezi 11.8.).  Diferenţa între forma prefixată şi postfixată, la supraîncărcarea operatorilor predefiniţi ++ şi --, se poate face doar de anumite compilatoare (de exemplu, compilatorul de BorlandC, versiune>3.0, se poate face diferenţa).

155

CAPITOLUL 11

Supraîncărcarea operatorilor

11.3. SUPRAÎNCĂRCAREA OPERATORILOR UNARI Operatorii unari pot fi supradefiniţi printr-o funcţie membră nestatică (fără parametri expliciţi) sau printr-o funcţie prietenă cu un parametru explicit de tipul clasă. Ca exemplu, să supraîncarcăm operatorul unar ++ pentru clasa punct, pentru a putea fi folosit atât în formă prefixată, cât şi postfixată (doar pentru compilatoarele care permit acest lucru!!). Vom folosi clasa punct implementată anterior, cu modificarea ca datele membre sunt de tipul int. class punct{ int x, y; public: // . . . punct & operator ++ (int ); punct & punct::operator++(); };

//forma postfixată //forma prefixată

punct punct::operator ++ (int) {punct p=*this;x++; y++;return p;} punct & punct::operator++() {x++;y++; return *this;} void main() { punct A(11, 10); punct C=A++;A.afişare( ); C.afişare( ); punct C=++A;A.afişare( ); C.afişare( ); }

11.4. MEMBRII CONSTANŢI AI UNEI CLASE Aşa cum s-a subliniat în capitolul 10, o clasă poate avea membrii statici: date membru statice (figurează întrun singur exemplar pentru toate instanţele clasei) sau metode statice (nu li se transmite pointerul this şi pot modifica doar date membru statice). Deasemenea, o clasă poate avea metode constante. O metodă este declarată constantă prin utilizarea modificatorului const în antetul ei (vezi exemplul de la pagina 150), după lista parametrilor formali. Metodele constante nu modifică obiectul pentru care sunt apelate. Ca oricăror variabile de tip predefinit, şi obiectelor de tip definit de utilizator li se poate aplica modificatorul const. Pentru un obiect constant este permis doar apelul metodelor constante, a constructorilor şi a destructorilor.

11.5. SUPRAÎNCĂRCAREA OPERATORILOR INSERTOR ŞI EXTRACTOR Operatorul << se numeşte operator insertor, deoarece inserează date în stream-ul (fluxul) de ieşire. Operatorul >> se numeşte operator extractor, deoarece extrage date din stream-ul (fluxul) de intrare. În exemplul următor, aceşti operatori sunt supraîncărcaţi pentru clasa complex, astfel încât să poată fi folosiţi ca pentru obiectele de tip predefinit. Exemplu: complex z1, z2; cin>>z1>>z2; cout<<"z1="<=0) ecran<<'+';ecran<>(istream &tastatura, complex &z) {tastatura>>z.re>>z.im;return tastatura;}

Prototipurile funcţiilor operator << şi >> pentru un tip abstract tip, sunt: friend ostream &operator<<(ostream &,const tip&); friend istream &operator >> (istream &,tip&);

11.6. SUPRAÎNCĂRCAREA OPERATORULUI DE ATRIBUIRE = În cazul în care operatorul de atribuire nu este supraîncărcat explicit, compilatorul generează unul implicit (ca în exemplul clasei punct sau segment). În absenţa unei supraîncărcări explicite, operatorul copie valorile datelor membre ale operandului drept în datele membre ale operandului stâng. Exemplu: punct a (8,9), b; b=a; /* operator atribuire implicit: zona de memorie ocupat de a se copie, bit cu bit, în zona de memorie ocupată de b: b.x=a.x si b.y=a.y */

Operatorul de atribuire implicit este nesatisfăcător în situaţiile în care obiectele clasei au ca date membre pointeri, sau în situaţiile în care memoria este alocată în mod dinamic. O supraîncărcare explicită a operatorului pentru clasa complex (ambii operanţi de tip complex) poate fi făcută fie prin metodă, fie prin funcţie prietenă.

a b

//this este pointer către obiectul curent, a în main } void main() {complex a, b; a = b; //a.operator=(b); (figura 11.1.) }

temp.re

temp

class complex { double re,im; public: complex operator = (complex ); }; complex complex::operator = (complex z) { re=z.re; im=z.im; return *this;

z

temp.im

a.re

a.im

b.re

b.im

z.re

z.im

(Obiectul z este local funcţiei operator=) Figura 11.1. Supraîncărcarea operatorului = prin metodă a clasei complex

Deoarece funcţia operator= returnează valoare de tip complex, se construieşte un obiect temporar temp, a cărui valoare se atribuie lui a. O altă modalitate, mai eficientă, de a supraîncărca operatorul de atribuire prin metodă a clasei complex, este aceea prin care funcţia primeşte ca parametru referinţă către operandul drept (se lucrează, astfel, chiar cu obiectul b, deoarece z şi b sunt variabile referinţă; în plus, modificatorul const interzice modificarea operandului transmis ca parametru referinţă; în plus, nu se mai crează obiectul local z, se ia ca referinţă obiectul existent) şi returnează o referinţă (adresa obiectului a), aşa cum prezintă figura 11.2. complex &complex::operator = (const complex &z) { re=z.re; im=z.im; return *this;} void main() {complex a, b; a = b; //a.operator=(b); (figura 11.2.) }

a b,z

a.re

a.im

b.re

b.im

Figura 11.2. Supraîncărcarea operatorului = prin metodă a clasei complex 157

CAPITOLUL 11

Supraîncărcarea operatorilor

Deasemenea, operatorul binar de atribuire poate fi supraîncărcat prin funcţie prietenă (în acest caz, nu primeşte parametrul implicit this, deci are doi operanzi). Paramertrii z1, z2 sunt transmişi prin referinţă, deci se lucrează chiar cu obiectele a, b. Funcţia returnează adresa obiectului a. Modificatorul const interzice modificarea operandului drept. class complex { double re,im; public: friend complex&operator=(complex&,const complex&); //funcţie prietenă constantă }; complex & operator = (complex &z1, complex &z2) a.re a.im a,z1 { z1.re=z2.re; z1.im=z2.im; return z1;} void main() b.re b.im {complex a, b; b,z2 a = b; //a.operator=(b); (figura 11.3.) } Figura 11.3. Supraîncărcarea operatorului =

prin funcţie prietenă a clasei complex Deoarece întotdeauna operandul stâng al operatorului de atribuire este de tipul clasei pentru care se supraîncarcă, este preferabil ca supraîncărcarea să se realizeze prin metodă a clasei. Reamintim că asociativitatea operatorului este de la dreapta la stânga. Operatorul poate apare în expresii de forma: a=b=c=d; //FISIERUL complex.h #define PI 3.14159265358979 #include class complex { double re,im; public: complex (double r=0,double i=0); //constructor complex (const complex&); //constructor copiere ~complex(){cout<<"Destructor complex("<
//operator + binar friend complex operator+(const complex&,const complex&);//complex+complex friend complex operator+(const double,const complex &); //real+complex friend complex operator +(const int,const complex &); //int+complex friend complex operator +(const complex&,const double); // complex+double complex operator - (const complex &) const; //operator - binar: complex-complex

//operator inmultire binar: complex*complex friend complex operator * (const complex &,const complex &); complex operator *(const complex ) const;

/*TEMA: friend complex operator / (const complex &,const complex &); complex operator / (const complex &); */ complex & operator + () const; //operator + unar; metodă constantă complex operator - () const; //operator - unar complex complex complex complex complex

&operator=(const complex &); & operator += (const complex &z); operator += (const double); operator -= (const complex&); & operator /= (const complex &z);

/* TEMA complex operator *= (const complex&);

158

CAPITOLUL 11

Supraîncărcarea operatorilor

complex operator /= (const complex&);*/

};

complex & operator ++ (); complex operator ++ (int); //forma postfixată complex operator--(); //decrementarea părţii reale a obiectului complex curent complex operator ! (); //calcul. rădăcinii pătrate a obiectului complex curent int operator == (complex &z); //compară doi complecşi şi returnează 1 în caz de egalit. friend int operator == (complex &, complex &); //return. 1 daca 2 compl egali int operator != (complex &); friend int operator != (complex &, complex &); friend ostream &operator<<(ostream &,const complex&); //operator afisare complex friend istream & operator >> (istream &,complex&); //operator citire complex

// FISIERUL complex.cpp #include "complex.h" #include <stdlib.h> #include <math.h> inline complex::complex(double r,double i) {re=r;im=i;cout<<"Constructor implicit ptr complex("<0) return 0.0; else return PI; if (re==0) if (im==0) return PI/2; else return (3*PI)/2; a=atan(im/re); if (re<0) return PI+a; if (im<0) return 2*PI+a; return a; } inline void complex::ipi() { im++; } inline void complex::dpi() { im--; } complex operator +(const complex &a, const complex &b) {complex z; z.re=a.re+b.re; z.im=a.im+b.im; return z; } complex operator +(const double d, const complex &a) {complex z;z.re=d+a.re;z.im=a.im;return z;} complex operator +(const int d, const complex &a) {complex z;z.re=d+a.re;z.im=a.im;return z;} complex operator +(const complex &a, const double d) {complex z;z.re=d+a.re;z.im=a.im;return z;} complex complex::operator-(const complex &a) const {complex z;z.re=re+a.re;z.im=im+a.im;return z;} complex operator *(const complex &x,const complex &y) {complex z;z.re=x.re*y.re-x.im*y.im;z.im=x.re*y.im+x.im*y.re;return z;} complex complex::operator *(const complex x) const {complex z;z.re=re*x.re-im*x.im;z.im=re*x.im+im*x.re;return z;} complex & complex::operator +() const {return *this;} complex complex::operator -() const {complex z;z.re=-re;z.im=-im;return z;} complex & complex::operator=(const complex &a) 159

CAPITOLUL 11

Supraîncărcarea operatorilor

{re=a.re;im=a.im;return *this;} // returnează obiectul curent complex & complex::operator+=(const complex &x) {double re1=re*x.re-im*x.im;double im1=re*x.im+im*x.re; re=re1; im=im1;return *this;} complex complex::operator+=(const double d) {re+=d; return *this;} complex complex::operator-=(const complex &x) {re-=x.re;im-=x.im; return *this;} complex &complex::operator /= (const complex &z) {double numitor=z.re*z.re+z.im*z.im; double re1=(double)(re*z.re+im*z.im)/numitor; double im1=(double)(im*z.re-re*z.im)/numitor; re=re1; im=im1;return *this;} complex & complex::operator++() //forma prefixata {cout<<"F. prefixata!\n";re++; return *this;} complex complex::operator ++ (int) //forma postfixata { cout<<"F. postfixata!\n";complex z=*this; re++; return z;} complex complex::operator--() {re--; return *this;} complex complex::operator ! () { complex w; double d,e; if ((d=modul())==0) return w; e=arg();d=sqrt(d); e=e/2;w.re=d*cos(e);w.im=d*sin(e);return w;} int complex::operator==(complex &x) {return re==x.re && im==x.im;} int operator==(complex &x, complex &y) {return (x.re==y.re && x.im==y.im);} int complex::operator!=(complex &x) {return !(re==x.re && im==x.im);} int operator!=(complex &x, complex &y) {return !(x.re==y.re && x.im==y.im);} ostream &operator<<(ostream &ecran, const complex &z) {ecran<<"("<=0) ecran<<'+';ecran<>(istream &tastatura, complex &z) {tastatura>>z.re>>z.im;return tastatura;} //FISIERUL de test #include "complex.cpp" #include #include <stdio.h> complex a(2, -6), b; void main() { cout<<"Intrare in main\n"; complex x(3,7), y(-1, 28), z; cout<<"b="<>w; cout<<"w citit este: "<<w<<'\n'; w.ipi(); cout<<"Dupa increm. p. imag:"<<w<<'\n'; w.dpi(); cout<<"Dupa decrem. p. imag:"<<w<<'\n'; cout<<"Modulul lui w este:"<<w.arg()<<'\n'; cout<<"Argumentul lui w este:"<<w.arg()<<'\n'; printf("Pentru continuare introdu un car!\n"); char rasp; cout<<"Se iese din blocul interior!\n"; } printf("Pentru continuare introdu un car!\n");getch(); cout<<"a="<
CAPITOLUL 11 Supraîncărcarea operatorilor --a; cout<<"Dupa decrem. p. reale: "< #include "complex.cpp" complex a1(2,-6),b1; void main() { cout<<"Intrare in main!!\n"; complex a(1,3), b(-1.3, 2.4),c;cout<<"a="<>d; cout<<"d="<
Exerciţiu: În exerciţiul următor se implementează clasa fracţie. Ea are ca date membre private nrt şi nmt (numărătorul şi numitorul). Tot ca metodă privată este definită metoda simplifică(), folosită pentru evitarea unor calcule cu numere mari. Ca metode publice sunt definite: un constructor, un destructor, funcţia numărător care returnează valoarea datei membre nrt, funcţia numitor care returnează valoarea datei membre nmt, funcţia valoare care returnează valoarea reală obţinută prin împărţirea numărătorului la numitor şi funcţia afişare. Se supraîncarcă operatorii +, -, *, / prin funcţii prietene ale clasei fracţie. Semnificaţia dată este cea de a realiza operaţii de adunare, scădere, înmulţire şi împărţire a obiectelor din clasa fracţie. Se supraîncarcă operatorii +=, -=, *=, /= prin funcţii membre ale clasei fracţie. Funcţia cmmdc (care implementează algoritmul lui Euclid pentru aflarea celui mai mare divizor comun a două numere) nu este nici funcţie membră a clasei fracţie, nici funcţie prietenă. Ea este apelată de funcţia simplifică. Funcţia simplifică este utilizată pentru a evita obţinerea unor valori mari pentru datele membre nrt şi nmt. #include class fracţie { int nrt,nmt; // numărător,numitor void simplifică(); //metodă de simplificare a fracţiei public: fracţie(int nrti=0, int nmti=1); // constructor iniţializare ~fracţie() {cout<<"DESTRUCTOR!!!\n";}; //destructor int numărător() {return nrt;} int numitor() {return nmt;} double valoare() {return (double)nrt/(double)nmt;} void afişare(); 161

CAPITOLUL 11 Supraîncărcarea operatorilor friend fracţie operator+(const fracţie&, const fracţie&); friend fracţie operator-(const fracţie&, const fracţie&); friend fracţie operator*(fracţie&, fracţie&); friend fracţie operator/(fracţie&, fracţie&); fracţie& operator =(const fracţie&); fracţie& operator +=(const fracţie&); fracţie& operator -=(const fracţie&); fracţie& operator *=(const fracţie&); fracţie& operator /=(const fracţie&); }; int cmmdc(int x,int y) //calculează şi returnează cmmdc pentru x, y {int z; if (x==0 || y==1) return 1; if (x<0) x=-x; if (y<0) y=-y; while (x!=0){ if (y>x) {z=x;x=y;y=z;} x%=y; }return y;} void fracţie::simplifică() {int cd; if (nmt<0) {nrt=-nrt;nmt=-nmt;} if (nmt>1){ cd=cmmdc(nrt,nmt);if (cd>1) {nrt/=cd; nmt/=cd;} } } fracţie::fracţie(int nri, int nmi) {nrt=nri; nmt=nmi; simplifică(); cout<<"Constructor!\n";} fracţie operator +(const fracţie &f1, const fracţie &f2) {int dc; fracţie f; dc=cmmdc(f1.nmt,f2.nmt);f.nmt=(f1.nmt/dc)*f2.nmt; f.nrt=f1.nrt*(f2.nmt/dc)+f2.nrt*(f1.nmt/dc);f.simplifică();return f;} fracţie operator -(const fracţie &f1, const fracţie &f2) {int dc; fracţie f;dc=cmmdc(f1.nmt,f2.nmt);f.nmt=(f1.nmt/dc)*f2.nmt; f.nrt=f1.nrt*(f2.nmt/dc) - f2.nrt*(f1.nmt/dc);f.simplifica();return f;} fractie operator * (fractie &f1, fractie &f2) { int dc;fractie f;dc=cmmdc(f1.nrt,f2.nmt); if (dc>1) {f1.nrt/=dc;f2.nmt/=dc;} dc=cmmdc(f2.nrt,f1.nmt); if (dc>1) {f2.nrt/=dc;f1.nmt/=dc;} f.nrt=f1.nrt*f2.nrt; f.nmt=f1.nmt*f2.nmt;return f; } fractie operator / (fractie & f1, fractie & f2) { int dc;fractie f;dc=cmmdc(f1.nrt,f2.nrt); if (dc>1) {f1.nrt/=dc;f2.nrt/=dc;} dc=cmmdc(f2.nmt,f1.nmt);if (dc>1) {f2.nmt/=dc;f1.nmt/=dc;} f.nrt=f1.nrt*f2.nmt; f.nmt=f1.nmt*f2.nrt;return f;} void fractie::afisare() {cout<<"f="<1) {nrt/=dc;f1.nmt/=dc;} dc=cmmdc(f1.nrt,nmt);if (dc>1) {f1.nrt/=dc;nmt/=dc;} nrt=nrt*f1.nrt;nmt=nmt*f1.nmt;simplifica();return *this;} fractie& operator /=(const fractie &f1) { int dc;dc=cmmdc(nrt,f1.nrt); 162

CAPITOLUL 11 Supraîncărcarea operatorilor if (dc>1) {nrt/=dc;f1.nrt/=dc;} dc=cmmdc(f1.nmt,nmt); if (dc>1) {f1.nmt/=dc;nmt/=dc;} nrt=nrt*f1.nmt; nmt=nmt*f1.nrt;return *this;} void main() { double n1, n2;fractie f(4,5);f.afisare(); fractie f1(5,4);fractie sum=f+f1;sum.afisare(); cout<<"NOUA AFISARE:\n"<<sum;cout<<"Numarator:"; cin>>n1; cout<<"Numitor:"; cin>>n2;fractie f4(n1, n2); f4.afisare(); f4+=f1;f4.afisare(); f4=f=f2; f4.afisare(); fractie f2; f2.afisare(); }

Observaţii:  Nu a fost necesară definirea unui constructor de copiere şi nici supraîncărcarea operatorului de atribuire, deoarece clasa fracţie nu contine pointeri către date alocate dinamic. În ambele situaţii se face o copiere bit cu bit, conform procedurii standard de copiere a structurilor din limbajul C. Într-o atribuire cum ar fi: f4=f; (unde f4, f de tip fractie), se realizează copierea bit cu bit a fracţiei f în fracţia f4.  Operatorii binari simpli +, -, *, / au fost supraîncărcaţi prin funcţii prietene, pentru a putea fi folosiţi în expresii de tipul n+f, în care n este operand de tip int şi f este operand de tip fracţie. Dacă aceşti operatori ar fi fost supraîncărcaţi prin metode ale clasei fracţie, ar fi putut fi utilizaţi doar în expresiile în care operandul stâng era de tip fracţie.  Operatorii binari compuşi +=, -=, *=, /= au fost supraîncărcaţi prin funcţii membre, deoarece operandul stâng este întotdeauna de tip fracţie.  În programul de test fracţiile f şi f1 au fost iniţializate cu valorile 4 si 5, respectiv 5 si 4. Fracţia f2 a fost iniţializată cu 0 şi 1, datorită parametrilor impliciţi ai constructorului clasei. Acest lucru se observă în urma apelului funcţiei afisare pentru obiectele f, f1, f2.  În cazul unei atribuiri de forma f=n; , unde f este fracţie si n este de tip int, înainte de atribuirea propriuzisă are loc o conversie a lui n în fracţie. Întregul n este convertit automat în fracţie (n,1). Acelaşi efect s-ar fi obţinut în urma unei conversii explicite: (fractie) n.  Pentru un obiect din clasa fracţie, constructorul poate fi apelat explicit: f=fractie(4,4); (în loc de fractie f(4,5); ). Pentru a evita lucrul cu fişiere care au sute de linii de cod, se folosesc două abordări: a) Se crează fişierul sursă numit fractie.h (header al utilizatorului) care conţine declararea clasei fracţie. Se crează fişierul fractie.cpp în care se implementează metodele clasei fracţie. În acest fişier se include header-ul "fractie.h". Se crează un al treilea fişier care testează tipul de date fracţie, în care se include fişierului "fractie.cpp". Se compilează, se linkeditează şi se lansează în execuţie fişierul executabil obţinut. b) Se construieşte un proiect. De exemplu, dacă se lucrează sub un mediu integrat, cum ar fi BorlandC, se crează cele trei fişiere (fractie.h care conţine declararea clasei, fractie.cpp care implementează metodele clasei şi fişierul de test ( test_fractie.cpp)). Fişierul header fractie.h va fi inclus atât în fractie.cpp, cât şi în test_fractie.cpp. Din meniul "Project" se selectează "Open Project", apoi comanda "Add item…", acre permite adăugarea fişierelor fractie.cpp si test_fractie.cpp. Pentru a evita includerea aceluiaşi fişier header de mai multe ori, se folosesc directivele de compilare condiţionată. Exemplu: #ifndef _fractie_h #include "fractie.h" #define _fractie_h #endif Exerciţiu: Se defineşte tipul şir, cu date membre (private):

163

CAPITOLUL 11

Supraîncărcarea operatorilor

 int lung

Lungimea propriu-zisă (nr. de caractere din şir), fără terminator  char *sirul

Adresa început şir (şirul-pointer către început şir) Metode:  sir();

Constructor vid  sir (char *);

Constructor de iniţializare care primeşte ca parametru un pointer către un şir de caractere (alocare dinamică).  sir(const sir&);

Constructor de copiere: primeşte ca argument o referinţă către un obiect din clasa şir şi realizează copierea.  ~sir();

Destructor care eliberează memoria alocată dinamic.  int lungime();

Returnează valoarea datei membre lung (nr. de carctere din şir).  const char *continut();

Returnează conţinutul unui obiect de tip şir.  sir &operator=(const sir&);

Supraîncărcarea operatorului de atribuire printr-o funcţie membră. A fost necesară supraîncarcarea operatorului de atribuire datorită faptului că această clasă conţine ca date membre, pointeri.  sir &operator+=(const sir&);

Operator supraîncarcat prin funcţie membră care realizează concatenarea obiectului curent (operandul implicit, de tip şir) cu obiectul de tip şir primit ca parametru.  friend sir operator+(const sir& s1, const sir& s2);

Supraîncarcă operatorul de adunare printr-o funcţie prietenă. Acesta concatenează obiectele de tip şir primite ca parametri. Returnează şirul obţinut în urma concatenării.  friend ostream &operator<<(ostream &, const sir&);

Supraîncarcă operatorul insertor printr-o funcţie prietenă a clasei şir.  friend istream &operator>>(istream &, sir&);

Supraîncarcă operatorul extractor printr-o funcţie prietenă a clasei şir. // FISIERUL sir.h #include class sir { int lung; char *sirul; public: sir(); sir (char *); sir(const sir&);

//lungimea propriu-zisa, fara terminator //adresa inceput sir (sirul-pointer catre inceput sir) //constructor vid: construieste un sir vid //constructor initializare primeste ca arg. un sir standard

// constructor copiere: primeste ca arg. o referinta catre un obiect din cls. sir si trebuie sa faca o copiere ~sir(); //destructor int lungime() //metoda care return. nr de car din componenta sirului const char *continut() //metoda inline-returneaza continutul sirului curent sir &operator=(const sir&); /*supraincarcare operator atribuire(necesar dat. faptului ca, in cls sir, exista un pointer catre data membra "sirul" supraincarcat prin f-ctie membra, ptr ca intotdeauna membrul stang este un obiect din clasa sir */ sir &operator+=(const sir&); //concateneaza argumentul primit la sirul curent friend sir operator+(const sir&, const sir&); //concateneaza argumentele friend ostream &operator<<(ostream &,const sir&);//supraincarcare operator insertor friend istream &operator>>(istream &,sir&);//supraincarcare operator extractor }; // FISIERUL sir.cpp

//conţine definiţiile funcţiilor din clasa şir. 164

CAPITOLUL 11 #ifndef _sir_h #include "sir.h" #define _sir_h #endif

Supraîncărcarea operatorilor

#ifndef _stdio_h #include "stdio.h" #define _stdio_h #endif #ifndef _string_h #include "string.h" #define _string_h #endif #ifndef _iostream_h #include "iostream.h" #define _iostream_h #endif sir::sir() {sirul=0;lung=0; cout<<"Constructor vid\n";} sir::sir(char *s) {cout<<"Apel constructor\n"; lung=strlen(s); if (lung>0){sirul=new char[lung+1]; if (sirul!=0) strcpy(sirul,s); else lung=0; } else sirul=0; } sir::sir(const sir &s) {cout<<"Constructor copiere\n"; if (s.lung>0){ sirul=new char[s.lung+1]; if (sirul!=0){ strcpy(sirul,s.sirul); lung=s.lung; } else lung=0; } else {lung=0;sirul=0;} } sir::~sir() {cout<<"Destructor\n";if (sirul!=0) delete sirul;} int sir::lungime() {return lung;} const char *sir::continut() {return sirul;} sir &sir::operator=(const sir&s) {cout<<"Operator de atribuire\n"; if (sirul!=0) delete sirul; if (s.lung>0){ sirul=new char[s.lung+1]; if (sirul!=0){ strcpy(sirul,s.sirul); lung=s.lung; } else lung=0; } else {sirul=0; lung=0;} return *this;

165

CAPITOLUL 11 Supraîncărcarea operatorilor } sir & sir::operator+=(const sir &s) { if (s.lung>0){ char *ps; int lung1; lung1=lung+s.lung; ps=new char[lung1+1]; if (ps!=0){ strcpy(ps,sirul); strcat(ps,s.sirul); delete sirul; sirul=ps;lung=lung1; } } return *this; } sir operator+(const sir &s1, const sir &s2) { sir s; s.lung=s1.lung+s2.lung; s.sirul=new char[s.lung+1]; if (s.sirul!=0){ if (s1.lung>0) strcpy(s.sirul,s1.sirul); else strcpy(s.sirul,""); if (s2.lung>0) strcat(s.sirul,s2.sirul); } else {s.lung=0; s.sirul=0;} return s; } ostream &operator<<(ostream &ies, const sir &s) { if (s.lung>0) ies<<s.sirul; return ies; } istream &operator>>(istream &intr, sir &s) { char s1[100];printf("Astept sir:"); scanf("%s", s1);s=s1;return intr;} // FISIERUL test_sir.cpp

// Program de test pentru clasa şir #include "sir.cpp" #include void main( ) { const char *p;clrscr();cout<<"Declaratii obiecte din cls sir\n"; sir s1("SIR INITIALIZAT!"), s2(s1), s3, s4;getch();cout<<"\nAfisari\n"; cout<<"s1="<<s1<<'\n'; cout<<"s2="<<s2<<'\n'; cout<<"s3="<<s3<<'\n'; cout<<"s4="<<s4<<'\n';s3=s2;cout<<"\nAtribuire: s3=s2 :s3="<<s3<<'\n'; getch();s4="Proba de atribuire";cout<<"s4="<<s4<<'\n'; cout<<"\nConcatenare s1 (ob. curent) cu s4.\n"; s1+=s4;cout<<"s1="<<s1<<'\n'; cout<<"\nConcatenare s1 cu s4, rezultat in s3\n";s3=s1+s4; cout<<"s3="<<s3<<'\n';getch();sir q;cout<<"Astept car. din sirul q:"; cin>>q;cout<<"Nr car. introduse in sir:"<
Aşa cum se observă din exerciţiul prezentat, în corpul constructorului se alocă memorie dinamic (cu operatorul new). Destructorul eliberează memoria alocată dinamic (cu operatorul delete).

11.7. SUPRAÎNCĂRCAREA OPERATORULUI DE INDEXARE [ ] Operatorul de indexare este un operator binar şi are forma generală: nume[expresie]. Să considerăm clasa vector, definită astfel: class vector{ private: int nrcomp; //nr. componente double *tabcomp; //tabloul componentelor; adresa de început public: double &operator[](int); } 166

CAPITOLUL 11

Supraîncărcarea operatorilor

Pentru tipul abstract vector, operatorul de indexare poate fi supradefinit, astfel încât să permită accesarea elementului de indice n. În acest caz, operatorul de indexare se poate supradefini printr-o funcţie membră a clasei (deoarece operandul stâng este de tipul clasei), şi poate fi folosit sub forma: v[n] (unde v este obiect al clasei vector; n-expresie întreagă). Expresia v[n] este echivalentă cu v.operator[](n) (apelul explicit al funcţiei operator []). Transferul parametrului către funcţia care supraîncarcă operatorul se poate face prin valoare sau prin referinţă. În mod obligatoriu, funcţia trebuie să returneze referinţa către elementul aflat pe poziţia n (pentru a permite eventualele modificări ale elementului, deoarece vector[n] este lvalue). Pentru un tip abstract, prototipul funcţiei care supradefineşte operatorul de indexare este (const protejează argumentul la modificările accidentale): tip_element & operator [ ] (const int); În cazul în care operatorul se supraîncarcă printr-o funcţie prietenă, prototipul funcţiei este: tip_elem & operator (tip, int); Exerciţiu: Să definim clasa vector are ca date membre (private) (figura 11.4.):  int nrcomp; - numărul elementelor vectorului  int err; - indice de eroare  double *tabcomp; - adresa de început a tabloului componentelor Metode:  vector(int nrc=0);

Constructor iniţializare cu parametru implicit: număr elemente 0. Crează dinamic un vector de elemente reale (double) şi iniţializează elementele cu valoarea 0.  vector(const vector&);

Constructor de copiere: Pe baza vectorului primit ca argument, crează un nou vector (de aceeaşi dimensiune, cu aceleaşi valori ale componentelor).  virtual ~vector();

Destructor: eliberează memoria alocată dinamic la crearea unui obiect din clasa vector.  int dimens() const;

Returnează dimensiunea (numărul elementelor) pentru obiectul curent.  double &operator[](int);

Supraîncarca operatorul de indexare [] prin funcţie membră (metodă). Returnează referinţa către elementul cu numărul de ordine indicat ca argument.  vector &operator=(const vector&);

Supraîncarcarea operatorului de atribuire printr-o funcţie membră. A fost necesară supraîncarcarea operatorului de atribuire datorită faptului că această clasă conţine pointeri către datele membre.  int nrerori() const;

Metodă constantă (nu poate modifica obiectul curent) care returnează valoarea datei membre err;  void anulari();

Metoda care anulează indicele de eroare.  int comparare(const vector&) const;

Metoda constantă care compară obiectul curent (operand stâng, argument implicit) cu obiectul primit ca parametru (tot vector). Returnează o valoare întreagă, care este: 2 dacă vectorii au număr de componente diferit; 0 dacă obiectul curent are 0 elemente sau dacă vectorii comparaţi au aceleaşi elemente; 1 dacă vectorii au cel puţin un element diferit. Metoda nu modifică operandul stâng. Mod de apelare: a.comparare(b); (unde a, b vectori).  friend int prodscal(const vector& v1, const vector& v2, double& p);

Funcţie prietenă a clasei vector care calculează şi returnează valoarea produsului scalar pentru vectorii v1 şi v2, transmişi ca argumente: nrcomp −1

p=

∑ v1[k ] * v2[k ] k =0

 friend int suma(const vector& v1, const vector& v2, vector& v);

Funcţie prietenă care calculează vectorul sumă v:

v[ k ] = v1[ k ] + v 2[ k ] 167

CAPITOLUL 11

Supraîncărcarea operatorilor

Returnează o valoare întreagă: 1 dacă numărul de elemente din v1 este diferit de numărul elementelor din v2; 0 dacă v2 are 0 elemente, sau dacă s-a calculat suma; 3 dacă v are 0 elemente  friend int diferenta(const vector& v1, const vector& v2, vector& v);

Funcţie prietenă care calculează vectorul diferenţă v:

v[ k ] = v1[ k ] −v 2[ k ] Returnează o valoare întreagă: 1 dacă numărul de elemente din v1 este diferit de de numărul elementelor din v2; 0 dacă v2 are 0 elemente, sau dacă s-a calculat diferenţa; 3 dacă v are 0 elemente  friend ostream &operator<<(ostream &ies, const vector&);

Operator de afişare supraîncarcat prin funcţie prietenă. Apelează metoda privată constantă afişare.  virtual void afisare(ostream &)const;

Cuvântul virtual care apare în antetul funcţiei indică faptul că metoda este o funcţie virtuală. Ea poate fi eventual redefinită (cu acelaşi prototip) şi în clasele derivate din clasa vector. În cazul unei redefiniri, funcţia ar fi supusă "legării dinamice", ceea ce înseamnă că selecţia ei se va face abia în momentul execuţiei.  double operator *(const vector& v1) const;

Operator de înmulţire supraîncărcat prin metodă constantă care returnează o valoare reală reprezentând produsul scalar dintre obiectul curent şi cel primit ca argument. În funcţie nu se crează o copie a lui v1, se lucrează cu v1 din programul apelant (parametru transmis prin referinţă).  vector operator+(const vector&) const;

Operator de adunare supraîncărcat prin metodă constantă care returnează un vector (întoarce o copie care poate fi utilizată în programul apelant) reprezentând suma dintre obiectul curent şi cel primit ca argument.  vector operator-(const vector&) const;

Operator de scădere supraîncărcat prin metoda constantă care returnează un vector (întoarce o copie care poate fi utilizată în programul apelant) reprezentând diferenţa dintre obiectul curent şi cel primit ca argument.  vector &operator+=(const vector& b);

Operator supraîncărcat prin metodă, deoarece întotdeauna operandul stâng este de tipul vector. Este folosit în expresii cum ar fi: a+=b (a şi b de tipul vector).  vector &operator-=(const vector&); Operatorul -= supraîncărcat prin metodă, deoarece întotdeauna operandul stâng este de tipul vector.  int sort(char='A');

Metodă care testează argumentul primit. Dacă acesta este valid ('A' sau 'D') apelează metoda quicksort, pentru ordonarea crescătoare sau descrescătoare a alementelor unui vector.  void quicksort(int, int, char);

Metoda este protejată şi realizează ordonarea crescătoare (argumentul 'A') sau descrescătoare (argumentul 'D'). v.nrcomp v.err vector v

v.tabcomp

Figura 11.4. Obiectul v, de tip vector Se prezintă varianta de lucru în care se crează un proiect. // FISIERUL vector.h #ifndef _iostream_h #include #define _iostream_h #endif class vector{ 168

CAPITOLUL 11 Supraîncărcarea operatorilor private: int nrcomp; //nr. componente int err; //indice eroare double *tabcomp; //tabloul componentelor; adresa de început public: vector(int nrc=0); //constructor initializare pentru un vector vid vector(const vector&); //constr. copiere ~vector(); //destructor int dimens() const //metoda constanta {return nrcomp;} double &operator[](int); //supraincarc operator indexare vector &operator=(const vector&); //supraincarcare operator de atribuire int nrerori() const {return err;} void anulari() {err=0;} int comparare(const vector&) const; //compara 2 vectori friend int prodscal(const vector&, const vector&, double&); friend int suma(const vector&, const vector&, vector&); friend int diferenta(const vector&, const vector&, vector&); friend ostream &operator<<(ostream &ies, const vector&); double operator *(const vector&) const; vector operator+(const vector&) const;

//intoarce copie care poate fi utilizata in progr. apelant vector operator-(const vector&) const; vector &operator+=(const vector&); //a+=b vector &operator-=(const vector&); int sort(char='A'); private: void afisare(ostream &)const; void quicksort(int,int,char); }; // FISIERUL vector.cpp #ifndef _vector_h #include "vector.h" #define _vector_h #endif #ifndef _string_h #include <string.h> #define _string_h #endif #ifndef _ctype_h #include #define _ctype_h #endif vector::vector(int nrc) {err=0;cout<<"Constructor vector\n"; if (nrc>0){ nrcomp=nrc; tabcomp=new double[nrcomp+1]; if (tabcomp==0) nrcomp=0; else for (int i=0;i<=nrcomp;i++) tabcomp[i]=0; //initializare elemente vector cu 0 } else{ nrcomp=0; tabcomp=0; } } 169

CAPITOLUL 11 Supraîncărcarea operatorilor vector::vector(const vector &v) { err=v.err; cout<<"Constructor copiere!\n"; if (v.nrcomp>0){nrcomp=v.nrcomp; tabcomp=new double[nrcomp+1]; if (tabcomp==0){ nrcomp=0; tabcomp=0; err=1; } else{ for(int k=0;k<=nrcomp;k++) tabcomp[k]=v.tabcomp[k];} } else{ nrcomp=0; tabcomp=0; } } vector::~vector() {cout<<"Destructor!\n"; if (tabcomp!=0) delete tabcomp; } double &vector::operator[](int i) { if (i<0 || i>=nrcomp){err++; return tabcomp[nrcomp]; } else return tabcomp[i]; } int vector::comparare(const vector&v) const

//w.comparare(v) { int k; if (nrcomp!=v.nrcomp) return 2; if (nrcomp==0) return 0; for (k=0;k
//v.afisare(cout) void vector::afisare(ostream &ies) const { ies<<'['; for (int i=0;i<(nrcomp-1);i++) ies<0) ies<0){ tabcomp=new double[v.nrcomp+1]; if (tabcomp!=0){ nrcomp=v.nrcomp; err=v.err; for (int k=0;k<=nrcomp;k++) tabcomp[k]=v.tabcomp[k]; } } return *this; } int prodscal(const vector &v1, const vector &v2, double &p)

//p=SUMA(v1[k]v2[k]) { p=0; if (v1.nrcomp!=v2.nrcomp) return 1; if (v1.nrcomp==0) return 2; for (int k=0;k
// sau: p+=v1[k]*v2[k] pentru ca s-a supraincarcat operatorul de indexare //mod apel j=prodscal(w,v,p); //mod apel prodscal(w,v,p); return 0; } int suma(const vector &v1, const vector &v2, vector &v) { v.nrcomp=v.err=0; if (v.tabcomp!=0) delete v.tabcomp; v.tabcomp=0;

170

CAPITOLUL 11 Supraîncărcarea operatorilor if (v1.nrcomp!=v2.nrcomp) return 1; if (v2.nrcomp==0) return 0; v.tabcomp=new double[v1.nrcomp+1]; if (v.tabcomp==0) return 3; v.nrcomp=v1.nrcomp; for (int k=0;k<=v1.nrcomp;k++)v.tabcomp[k]=v1.tabcomp[k]+v2.tabcomp[k]; return 0; } int diferenta(const vector &v1, const vector &v2, vector &v) { v.nrcomp=v.err=0; if (v.tabcomp!=0) delete v.tabcomp; v.tabcomp=0; if (v1.nrcomp!=v2.nrcomp) return 1; if (v2.nrcomp==0) return 0; v.tabcomp=new double[v1.nrcomp+1]; if (v.tabcomp==0) return 3; v.nrcomp=v1.nrcomp; for (int k=0;k<=v1.nrcomp;k++) v.tabcomp[k]=v1.tabcomp[k]-v2.tabcomp[k]; return 0; } double vector::operator*(const vector &b) const

//z=a*b; a-operandul din stânga { double z=0.0; if (nrcomp!=b.nrcomp){

// err++; cout<<"Nr. componente diferit! Nu se poate face produs scalar!\n"; return 4;} else if (nrcomp>0) for (int k=0;k
//c=a+b {if (nrcomp!=b.nrcomp) {vector c;c.err=1;return c;} if (nrcomp==0) {vector c;return c;} vector c(nrcomp); for (int k=0;k
//c=a-b {if (nrcomp!=b.nrcomp){vector c;c.err=1;return c;} if (nrcomp==0) {vector c;return c;} vector c(nrcomp); for (int k=0;k0) for (int k=0;k0) for (int k=0;k
CAPITOLUL 11 Supraîncărcarea operatorilor void vector::quicksort(int i1,int i2,char modsort) {int i,j; double a,y;i=i1;j=i2;a=tabcomp[(i1+i2)/2]; do{ switch (modsort) { case 'A': while (tabcomp[i]a) j--; break; case 'D': while (tabcomp[i]>a) i++; while (tabcomp[j]>w[k];} cout<<"Vect. v neordonat:\n"<
172

CAPITOLUL 11

Supraîncărcarea operatorilor

11.7. SUPRAÎNCĂRCAREA OPERATORILOR NEW ŞI DELETE Avantajul alocării dinamice a memoriei şi a eliberării acesteia cu ajutorul operatorilor new şi delete, faţă de utilizarea funcţiilor malloc, calloc sau free (vezi capitolul 6.9.), constă în faptul că operatorii alocă (eliberează) memorie pentru obiecte, date de tip abstract. Acest lucru este posibil deoarece aceşti operatori au o supraîncărcare globală, standard. În cazul în care supraîncărcarea standard este insuficientă, utilizatorul poate supraîncărca operatorii prin metode (implicit!) statice. Pentru operatorul new, funcţia care supraîncarcă ooperatorul new are prototipul: void * nume_clasa::operator new (size_t lungime); Funcţia returnează un pointer generic a cărui valoare este adresa de început a zonei de memorie alocate dinamic. Tipul size_t este definit în stdlib.h (vezi capitolul 6.9.). La aplicarea operatorului, nu se indică nici o valoare pentru parametrul lungime (mărimea zonei de memorie necesare obiectului pentru care se alocă dinamic memorie), deoarece compilatorul o determină, automat. Modul de utilizare pentru operatorul new: nume_clasa *p = new nume_clasa; Sau: nume_clasa *p = new nume_clasa(p1, p2, p3); Aplicarea operatorului new supradefinit de utilizator determină, automat, apelul constructorului corespunzător clasei, sau al constructorului implicit. În a doua formă, la alocarea dinamică a memoriei apar şi parametrii constructorului (p1, p2, p3). Operatorul delete se supradefineşte printr-o funcţie cu prototipul: void nume_clasa::operator delete (void *); La aplicarea operatorului delete se apelează, automat, destructorul clasei. Exemple: class c1{ double n1, n2; public: c1(){n1=0; n2=0;} };

//. . . . void main( ) { c1 *pc1=new c1; c1 *pc2=new c1(7, 5); }

//se alocă memorie pentru păstrarea unui obiect de tip c1 //odată cu alocarea dinamică, se realizează şi iniţializarea

Operatorii new, delete permit alocarea dinamică şi pentru tablouri. În aceste situaţii, se utilizează întotdeauna operatorii supraîncărcaţi global predefiniţi şi se apelează constructorul fără parametri (dacă acesta există). Exemplu:

}

class c1{ double n1, n2; public: c1(){n1=0; n2=0;} c1(double x, double y) {n1=x; n2=y;} }; c1 *pct1; pct1=new c1[100]; /*Se rezervă memorie ptr. 100 obiecte de tip c1. Se apelează constructorul implicit de 100 de ori */ a 7.53 n

Exemplu:

b

#include class numar { double *n;

173

p1

n

n

2.14

-1.74

Figura 11.5. Obiectele a, b, p1 de tip număr şi pointer spre număr

CAPITOLUL 11 Supraîncărcarea operatorilor public: număr (double nr1); ~număr(); double val(){return *n;} }; număr::număr(double nr1) {n=new double(nr1);} număr::~număr() { delete n;} void main() {număr a(7.53),b(2.14); număr *p1,*p2; p1=new număr(-1.74); cout<
11.10. SUPRAÎNCĂRCAREAOPERATORULUI -> Supraîncărcarea operatorului unar -> se realizează printr-o metodă nestatică. Expresia obiect -> expresie va fi interpretată ca (obiect.operator->())->expresie De aceea, funcţia-operator trebuie să returneze fie un pointer la un obiect al clasei, fie un obiect de un tip pentru care este supradefinit operatorul ->. Exemplu: #include typedef struct ex{

int membru; };

class ex1{ ex *pointer; public: void set(ex &p) {pointer=&p;} ex * operator -> (void) {return pointer;} }; class ex2{ 174

CAPITOLUL 11

Supraîncărcarea operatorilor

ex1 *pointer; public: void set(ex1 &p) {pointer=&p;} ex1 * operator -> (void) {return pointer;} }; void main() {ex A; ex1 B; ex2 C; B.set(A); B->membru=10; //apel al funcţiei ex1::operator->() cout<membru<<'\n'; } Exerciţiu: Se implementeaz clasa matrice. Matricea este privită ca un vector de linii.

Date membre (protected):  int Dim1,Dim2;  double *tab;  int err;

// nr. linii, nr. coloane // pointer către tabloul componentelor // indice de eroare

Metode:    

matrice (int dim1=0, int dim2=0);Constructor matrice, cu alocare dinamică. matrice(const matrice&); Constructor de copiere ~matrice(); Destructor, cu rolul de a elibera memoria alocată dinamic. int pune(int ind1, int ind2, double elem);

Iniţializează elementul de indici (ind1, ind2) cu valoarea transmisă ca argument (al treilea parametru). Întoarce valoarea întreagă 1 dacă indicii sunt incorecţi.  int dim1()const; Metodă constantă care returnează numărul de linii.  int dim2() const; Metodă constantă care returnează numărul de coloane.  int nrerori() const;

Metodă constantă (nu poate modifica obiectul curent) care returnează valoarea datei membre err;  void anulari(); Metodă care anulează indicele de eroare.  double elem(int ind1, int ind2) const;

Metodă constantă care returnează valoarea elementuluilui de indici (ind1,ind2).  friend ostream &operator<<(ostream &, const matrice&);

Supraîncărcarea operatorului de inserţie printr-o funcţie membră. Apelează metoda afişare.  void afişare(ostream &)const;  matrice &operator=(const matrice&);

Supraîncărcarea operatorului de atribuire printr-o funcţie membră. A fost necesară supraîncărcarea operatorului de atribuire datorită faptului că această clasă conţine pointeri către datele membre.  int dimtab()const;

Metodă constantă care returnează numărul de elemente din matrice (Dim1*Dim2).  virtual int comparare(const matrice&) const;

Metodă constantă care compară obiectul curent cu obiectul primit ca argument.  matrice operator+(const matrice&) const;

Operator de adunare supraîncărcat prin metodă constantă care returnează o matrice (întoarce o copie care poate fi utilizată în programul apelant) reprezentând suma dintre obiectul curent şi cel primit ca argument.  matrice operator-(const matrice&) const;

Operator de scădere supraîncărcat prin metodă constantă care returnează o matrice (întoarce o copie care poate fi utilizată în programul apelant) reprezentând suma dintre obiectul curent şi cel primit ca argument.  matrice &operator+=(const matrice&);

Operator supraîncărcat prin metodă, deoarece întotdeauna operandul stâng este de tipul matrice. Este folosit în expresii cum ar fi: a+=b (a şi b de tipul matrice).  matrice &operator-=(const matrice&);

Operatorul -= supraîncărcat prin metodă, deoarece întotdeauna operandul stâng este de tipul matrice. Este folosit în expresii cum ar fi: a-=b (a şi b de tipul matrice).  friend matrice operator*(double, const matrice&); 175

CAPITOLUL 11

Supraîncărcarea operatorilor

Operator supraîncărcat prin funcţie prietenă, pentru a putea fi utilizat în expresii n*M, unde n este de tip real sau întreg şi M de tipul matrice.  matrice operator*(const matrice&) const;

Operator supraîncărcat prin funcţie membră, care înmulţeşte obiectul curent (tip matrice) cu obiectul primit ca argument (tot tip matrice).  int indtab(int i,int j) const;

Metodă care returnează indicele din tabloul unidimensional (matricea este privită ca un tablou unidimensional, cu elementele memorate într-un tablou unidimensional, întâi elementele primei linii, în continuare elementele celei de-a doua linii, etc.) pentru elementul[i][j].  int erind(int, int) const; Metodă care testează eventualele erori de indexare.  matrice transp() const; Metoda calculează şi returnează matricea transpusă pentru obiectul curent. Nu modifică obiectul curent.  double operator ( ) (int i, int j);

Supraîncărcarea operatorului ( ) prin metodă a clasei care returnează valoarea elementului de indici i şi j pentru obiectul curent, sau 0 în cazul în care apare o eroare de indexare (vezi şi metoda elem). Apelul metodei elem: A.elem(1, 3) este echivalent cu apelul A(1, 3). //FISIERUL matrice.h #ifndef _iostream_h #include #define _iostream_h #endif class matrice{ int Dim1,Dim2; double *tab; int err; public: matrice (int dim1=0,int dim2=0); matrice(const matrice&); ~matrice(); int pune(int ind1, int ind2, double elem);

//pune elem. elem pe poz. de indici (ind1, ind2); întoarce 1 dacă indicii sunt incorecţi friend ostream &operator<<(ostream &, const matrice&); matrice transp() const; matrice &operator=(const matrice&); int dim1()const {return Dim1;} int dim2() const {return Dim2;} int dimtab() const; int nrerori() const {return err;} void anulerori() {err=0;} double elem(int ind1, int ind2) const; //întoarce val. elem-lui de indici (ind1,ind2) int comparare(const matrice&) const; matrice operator+(const matrice&) const; matrice operator-(const matrice&) const; matrice &operator+=(const matrice&); matrice &operator-=(const matrice&); friend matrice operator*(double, const matrice&); matrice operator*(const matrice&) const; // PTR MATRICI SIMETRICE: double operator()(int i, int j); private: int indtab(int i,int j) const {return i*Dim2+j;} //indicele din tab al unui elem. int erind(int, int) const; //test er. de indexare void afisare(ostream &)const; 176

CAPITOLUL 11 matrice inv() const; };

Supraîncărcarea operatorilor

// FISIERUL matrice.cpp #ifndef _matrice_h #include "matrice.h" #define _matrice_h #endif matrice::matrice (int d1,int d2)

// constructor matrice {int k,dtab; err=0; if (d1<=0 || d2<=0) {Dim1=0;Dim2=0;tab=0;} else{ dtab=d1*d2; tab=new double[dtab]; if (tab!=0) { Dim1=d1;Dim2=d2; for (k=0;k
//constructor copiere {cout<<"Constructor copiere!\n";err=0;int k,dtab; if (M.Dim1<=0 || M.Dim2<=0) {Dim1=0;Dim2=0;tab=0;} else{ dtab=M.Dim1*M.Dim2;tab=new double[dtab]; if (tab!=0) { Dim1=M.Dim1; Dim2=M.Dim2; for (k=0;k=Dim1 || j<0 || j>=Dim2) return 1; return 0; } void matrice::afisare(ostream & ies) const { int i,j; if (tab!=0){ ies<<'\n'; for (i=0;i
//dimens. M if (dtab==0){ Dim1=0;Dim2=0; 177

CAPITOLUL 11

Supraîncărcarea operatorilor

if (tab==0){delete [] tab;tab=0;} } else{ vdtab=Dim1*Dim2; if (vdtab!=dtab){ delete [] tab; tab=new double [dtab]; if (tab!=0){Dim1=0;Dim2=0;err=1;} } if (tab!=0){ Dim1=M.Dim1;Dim2=M.Dim2; for (k=0;k
178

CAPITOLUL 11 Supraîncărcarea operatorilor matrice operator*(double a, const matrice &B) { if (B.tab==0) { matrice C; C.err=B.err; return C;} { int k,dtab; matrice C(B.Dim1, B.Dim2); if (B.tab==0) {C.err=3;return C;} dtab=C.Dim1*C.Dim2; for (k=0;k #define _iostream_h #endif #ifndef _matrice_h #include "matrice.h" #define _matrice_h #endif void main() {int M,N; cout <<"Nr. linii:"; cin>>M; cout <<"Nr. coloane:"; cin>>N; {matrice A(M,N),B(4,4);matrice C();int i,j; double val;

// introduc matr. A(M,N) 179

CAPITOLUL 11 Supraîncărcarea operatorilor for (i=0;i<M;i++) for (j=0;j b) tip_predefinit -> c) clasă -> d) clasă_1 ->

tip_predefinit_2 tip_definit_de_utilizator (clasă) tip_predefinit clasă_2

11.11.1. CONVERSII DIN TIP PREDEFINIT1 ÎN TIP PREDEFINIT2 Pentru realizarea unor astfel de conversii, se foloseşte operatorul unar de conversie explicită (cast), de forma: (tip) operand Exemplu: int k; double x; x = (double) k / (k+1);

/* În situaţia în care se doreşte obţinerea rezultatului real al împărţirii întregului k la k+1, trebuie realizată o conversie explicită, vezi capitolul 2.7. */ În limbajul C++ acelaşi efect se poate obţine şi astfel: x= double (k) / (k+1);

deoarece se apelează explicit constructorul tipului double.

11.11.2. CONVERSII DIN TIP PREDEFINIT ÎN CLASĂ

180

CAPITOLUL 11

Supraîncărcarea operatorilor

Astfel de conversii se pot realiza atât implicit, cât şi explicit, în cazul în care pentru clasa respectivă există un constructor cu parametri impliciţi, de tipul predefinit. Exemplu: Pentru clasa fracţie definită în cursurile anterioare: class

fracţie{ int nrt, nmt; public: fracţie( int nrt = 0, int nmt = 1);

// . . . }; fracţie f; f = 20;

/* Conversie IMPLICITĂ: înaintea atribuirii se converteşte operandul drept (de tip int) la tipul operandului stâng (tip fracţie). */ f = fractie(20);

/*Conversie EXPLICITĂ: se converteşte întregul 20 într-un obiect al clasei fracţie (nrt=20 şi nmt=1). */ }

11.11.3. CONVERSII DIN CLASĂ ÎN TIP PREDEFINIT Acest tip de conversie se realizează printr-un operator special (cast) care converteşte obiectul din clasă la tipul predefinit. Operatorul de conversie explicită se supraîncarcă printr-o funcţie membră nestatică. nume_clasa:: operator nume_tip_predefinit( ); La aplicarea operatorului se foloseşte una din construcţiile: (nume_tip_predefinit)obiect; nume_tip_predefinit (obiect); Exemplu: Pentru clasa fracţie, să se supraîncarce operatorul de conversie explicită, care să realizeze conversii fracţie -> int. #include class fracţie{ long nrt, nmt; public: fracţie(int n=0, int m=1) {nrt=n; nmt=m;} friend ostream &operator<<(ostream &, const fracţie &); operator int( ) {return nrt/nmt;} //conversie fracţie -> int }; ostream &operator<<(ostream &ies, const fracţie &f) {ies<<'('<complex operator double() const {return re;}//conversie complex->double friend ostream &operator<<(ostream &, const complex &); }; ostream &operator<<(ostream &ies, const complex &z) {ies<<'('<complex (constructor complex cu arg. double) e=complex(y,j); //apel explicit al constr. complex; întâi conversie j de la int la double k=a; //conversie complex->double->int u=a; //conversie complex->double z=(double)a/3; //conversie complex->double cout<<"r="<
CAPITOLUL 12

Crearea ierahiilor de clase

{cout<
// o alternativă pentru obţinerea sumei tuturor datelor membre este: // double calcul(){return bază::calcul()+b;} };

friend ostream &operator<<(ostream &, const deriv1 &);

class deriv2: protected bază { int b; public: deriv2(int a1, double w1, int c1, int b1):bază(a1, w1, c1) {b=b1; cout<<"Constructor deriv2\n";} ~deriv2() {cout<<"Destructor deriv2\n";} double calcul() {return w+c+b;} friend ostream &operator<<(ostream &, const deriv2 &); }; class deriv3: private bază { int b; public: deriv3(int a1, double w1, int c1, int b1):baza(a1, w1, c1) {b=b1; cout<<"Constructor deriv3\n";} ~deriv3() {cout<<"Destructor deriv3\n";} double calcul() {return w+c+b;} friend ostream &operator<<(ostream &, const deriv3 &); }; ostream &operator<<(ostream &ies, const baza &b) {ies<
188

CAPITOLUL 12

Crearea ierahiilor de clase

baza x(1, 1.23, 2); // Constructor cls. baza deriv1 y(2, 2.34, 3, 4); // Constructor cls. baza deriv2 z(3, 3.45, 4, 5); // Constructor cls. baza deriv3 v(4, 5.67, 6, 7); //Constructor cls. baza cout<<"x="<<x<<'\n'<<"z="<
Constructor deriv1 Constructor deriv2 Constructor deriv3

// x=1 1.23 2 (x.a, x.w, x.c) // z=3.45 4 5 // v=5.67 6 7 // x.calcul()=4.23 // y.calcul()=9.34 // z.calcul()=12.45

cout<<"x.calcul()="<<x.calcul()<<'\n'; cout<<"y.calcul()="< #include 189

CAPITOLUL 12 Crearea ierahiilor de clase class punct{ int x, y; //date membru private, inaccesibile în clasa punct_col public: punct (int abs=0, int ord=0) {x=abs; y=ord; cout<<"Constr punct "<<x<<","< #include class persoană { protected: şir numele,prenumele; char sexul; public: persoana () //constructor vid {numele="";prenumele="";sexul='m'; cout<<"Constr PERS vid!\n";} persoană(const şir&,const şir&,const char);

persoana sir numele, prenumele char sexul

student sir facultatea, specializarea int anul, grupa

student_bursier char tipul_bursei

//constructor

};

persoană (const persoana&); //constr. copiere Figura 12.3. virtual ~persoană(); //destructor const şir& nume()const; const şir&prenume() const; char sex() const; virtual void afişare(); friend ostream & operator<<(ostream &, const persoana &); friend istream & operator>>(istream &, persoana &);

class student:public persoană { protected: şir facultatea,specializarea; int anul,grupa; public: student(const şir&,const şir&,const char,const şir&,const şir&,const int,const int); student(const persoana&,const şir&,const şir&,const int,const int); student(const student&); virtual ~student(); const şir& facult(){return facultatea;} const şir& spec(){return specializarea;} int an(){return anul;} int grup(){return grupa;} virtual void afişare(); friend ostream & operator<<(ostream &, const student &); /* TEMA friend istream & operator>>(istream &, student &);*/ }; class student_bursier:public student { protected: char tipul_bursei; public: student_bursier(const student&,char); student_bursier(const student_bursier&); virtual ~student_bursier(); char tip_bursa() {return tipul_bursei;} double valoare_bursa(); virtual void afişare(); //TEMA friend ostream & operator<<(ostream &, const student_bursier &); //TEMA friend istream & operator>>(istream &, student_bursier &); 191

CAPITOLUL 12 };

Crearea ierahiilor de clase

// METODELE CLASEI PERSOANA persoană::persoană(const şir& nume,const şir& prenume,const char sex) {numele=nume;prenumele=prenume;sexul=sex; cout<<"Constr. PERSOANĂ\n";} persoana::persoana(const persoana& pers) { numele=pers.numele;prenumele=pers.prenumele;sexul=pers.sexul; cout<<"Constructor copiere PERSOANA\n";} persoană::~persoană() {cout<<"Destructor PERSOANĂ\n";} const şir& persoană::nume()const {return numele;} const şir& persoană::prenume()const {return prenumele;} char persoană::sex()const {return sexul;} void persoană::afişare() { cout<<"Afişare PERSOANĂ:\n"; cout<>(istream & tastat, persoana &p) {tastat>>p.numele>>p.prenumele>>p.sexul; return tastat;}

// METODELE CLASEI STUDENT student::student(const şir&nume,const şir&prenume,const char sex,const şir& facult,const şir& spec,const int an,const int gr):persoana(nume,prenume,sex) {numele=nume;prenumele=prenume; sexul=sex;facultatea=facult; specializarea=spec; anul=an; grupa=gr; cout<<"Construct STUD 1\n"; } student::student(const persoana &pers,const şir& facult,const şir& spec,const int an,const int gr):persoana(pers) { numele=pers.nume();prenumele=pers.prenume(); facultatea=facult; specializarea=spec;anul=an;grupa=gr; cout<<"Construct STUD 2\n";} student::student(const student& stud):persoana(stud.numele,stud.prenumele,stud.sexul) { facultatea=stud.facultatea; specializarea=stud.specializarea; anul=stud.anul; grupa=stud.grupa;cout<<"Construct copiere STUD!\n"; } student::~student() { cout<<"Destructor student!!\n"; } void student::afişare() { cout<>(istream &, student &);

//METODE CLASEI STUDENT_BURSIER 192

CAPITOLUL 12 /* TEMA student_bursier(student&,char); student_bursier(const student_bursier&);*/

Crearea ierahiilor de clase

student_bursier::student_bursier(const student&stud,char tip_burs):student(stud) {tipul_bursei=tip_burs;} student_bursier::student_bursier(const student_bursier &stud):student(stud.numele,stud.prenumele,stud.sexul,stud.facultatea,stud.specia lizarea,stud.anul,stud.grupa) {tipul_bursei=stud.tipul_bursei;} double student_bursier::valoare_bursa() { double val; switch (tipul_bursei) { case 'A': val=850000; break; case 'B': val=700000; break; } return val; } student_bursier::~student_bursier() {cout<<"Desctructor student bursier\n";} void student_bursier::afişare() { student::afişare(); cout<<"Tip bursa: "<>x2; cout<"Inf introduse:\n"; cout<<x2; cout<<"Apasa tasta...\n";getch(); //x1.afisare(); cout<<'\n'; student s(x, "N.I.E.", "EA", 1, 2311);

//Constructor copiere PERSOANA

Construct STUD 2!

s.afisare();cout<<'\n';

/* POP ION

Sex: m Facultatea: N.I.E. Specializare: EA Anul: 1 Grupa:2311 */

cout<<"Apasa tasta...\n";getch(); student s1(s); cout<<s1<<'\n';

/* Nume stud:POP Specializare :EA

//Constr. PERSOANA Construct copiere STUD!

Prenume stud:ION Sex stud:BARBATESC Facultate :N.I.E. Anul :1 Grupa :2311*/

cout<<"Apasa tasta...\n";getch(); student s3("STAN", "POPICA", 'm', "MECANICA", "I.M.T.", 1, 320);

//Constr. PERSOANA Construct STUD 1! cout<<s1<<'\n'; /* Nume stud:POP Facultate :N.I.E. Specializare :EA

Prenume stud:ION Sex stud:BARBATESC Anul :1 Grupa :2311 */

s3=s1; cout<<"In urma atribuirii s3="<<s3<<'\n';

/* In urma atribuirii s3= Nume stud:POPPrenume stud:ION Sex stud:BARBATESC Facultate :N.I.E. Specializare :EA Anul :1 Grupa :2311 */ s3.afisare( ); }

Observaţii: 193

CAPITOLUL 12

Crearea ierahiilor de clase

1. Să se completeze exemplul cu funcţiile date ca temă. Să se completeze programul de test (funcţia main). 2. Funcţia afişare este declarată virtuală în clasa de bază şi redefinită în clasa derivată. Redefinirea

funcţiei în clasa derivată are prioritate faţă de definirea funcţiei din clasa de bază. Astfel, o funcţie virtuală declarată în clasa de bază acţionează ca un substitut pentru păstrarea datelor care specifică o clasă generală de acţiuni şi declară forma interfeţei. Funcţia afişare are acelaşi prototip pentru toate clasele în care a fost redefinită (vezi paragraful 12.7.).

12.5. MOŞTENIREA MULTIPLĂ O clasă poate să moştenească mai multe clase de bază, ceea ce înseamnă că toţi membrii claselor de bază vor fi moşteniţi de clasa derivată. În această situaţie apare mecanismul moştenirii multiple. În paragraful 12.2. a fost prezentat modul de declarare a unei clase cu mai multe superclase. Exerciţiu: Se implementează ierahia de clase din figura 12.4. #include class bază1 { protected: bază2 bază1 int x; public: bază1 (int xx) {x=xx;cout<<"Constructor cls. bază1\n"; derivat cout<<x<<'\n';} ~baza1() {cout<<"Destructor bază1\n"<<x<<'\n';} Figura 12.4. Schemă de moştenire multiplă void aratax() {cout<<"x="<<x<<'\n';} }; class bază2 { protected: int y; public: bază2 (int yy) {y=yy; cout<<"Constructor bază2\n"<
/* Destructor derivat 1 2

Constructor bază2 8 Constructor derivat 7 8 */

Destructor bază2 2 194

Destructor bază1 1 */

CAPITOLUL 12 }

Crearea ierahiilor de clase

Aşa cum ilustrează exemplul, la declararea obiectului obiect de tipul derivat s-au apelat constructorii claselor de bază (bază1 şi bază2), în ordinea în care apar în declararea clasei derivate: mai întâi constructorul clasei bază1, în care x este dată membru protejată (accesibilă din clasa derivat); apoi constructorul clasei bază2 , în care y este dată membru protejată (accesibilă din clasa derivat); apoi constructorul clasei derivat care le încorporează pe acestea într-un singur obiect. Clasa derivat nu are date membre, ci doar metode (figura 12.5.). După ieşirea din blocul în care a fost declarată variabila obiect, se apelează automat destructorii, în ordine inversă apelării constructorilor.

x

obiect

y

7 8

Figura 12.5. Variabila obiect de tip derivat

12.6. REDEFINIREA MEMBRILOR UNEI CLASE DE BAZĂ ÎN CLASA DERIVATĂ Aşa cum s-a observat deja din exerciţiul anterior, unii membrii (fie date membru, fie metode) ai unei clase de bază pot fi redefiniţi în clasele derivate din aceasta. Exemplu în care se redefinesc datele membre ale clasei de bază în clasa derivată class bază{ protected: double x, y; public: bază(double xx=0, double yy=0) };

{x=xx; y=yy;}

class deriv:public bază{ protected: double x, y; public: deriv(double dx=0, double dy=0, double bx=0, double by=0): baza (bx, by) {x=dx; // x - membru redefinit în clasa derivată y=dy; // y - membru redefinit în clasa derivată } void arată() const; }; void deriv::arată() const {cout<<"x din clasă de bază:"<
Dacă ne întoarcem la exemplul în care implementam ierarhia de clase persoana, student, student_bursier, remarcăm faptul că metoda afisare din clasa persoana supraîncărcată în clasele derivate student şi student_bursier. Redefinirea unei metode a unei clase de bază într-o clasă derivată se numeşte polimorfism. Fie schema de moştenire prezentată în figura 12.6.

195

CAPITOLUL 12

Crearea ierahiilor de clase

La declararea unui obiect din clasa D, membrii clasei A (int a) sunt moşteniţi de obiectul din clasa D în dublu exemplar (figura 12.7.). Pentru a evita această situaţie, clasa A va fi declarată virtuală, pentru clasele derivate B şi C. Metoda arată este redefinită în clasele B,C, D (polimorfism) (vezi exerciţiul următor şi figura 12.8.). Clasa A int a

Clasa B int b

x (obiect din clasa D)

Clasa C int c

obiect din clasa B

obiect din clasa C

obiect din clasa A

obiect din clasa A

int a

int a

int b

int c

Clasa D int d

int d

Figura 12.6.

Figura 12.7.

Exerciţiu: #include class A{ int a; public: A(int aa) {a=aa; cout<<"Constructor A"<arată(); PA=&v1; PA->arată(); PB=&v1; PB->arată(); PA=&w; PB=&w; PD=&w; u.arată(); PA->arată(); PB->arată(); PD->arată(); }

// Constructor C 14 Constructor D 35 // Se selectează metoda arată din clasa A A.a=10 // Se selectează metoda arată din clasa A A.a=9 // Se selectează metoda arată din clasa B B.b=7 // Apelul metodei arată ptr. obiectul curent, clasa A A.a=31 // Se selectează metoda arată din clasa A A.a=31 // Se selectează metoda arată din clasa B B.b=9 // Se selectează metoda arată din clasa D D.d=35

u Aşa cum se observă din exemplu, pa, pb, pc şi pd sunt pointeri de tipurile A, B, C, respectiv D:

pa

a=10 v1

A *pa;B *pb;C *pc;D *pd;

În urma atribuirii

a=9

pb

b=7

pa=&v1;

pointerul pa (de tip A) va conţine adresa obiectului v1 (de tip B). Apelul metodei arată redefinite în clasa derivată B pa->arată();

A.a=10

B.b=7 sau: B v1(9, 7)

v2 a=8

pc

va determina selecţia metodei din clasa pointerului (A), şi nu a metodei din clasa obiectului a cărui adresa o conţine pointerul.

c=12

C.c=12 sau: C v2(8, 12)

w a=31 pd

b=9

c=14

D.d=35 sau: D w(31,9,14,35)

d=35 198

Figura 12.9. Un pointer către o clasă de bază iniţializat cu adresa unui obiect dintr-o clasă derivată

CAPITOLUL 12

Crearea ierahiilor de clase

În toate cazurile prezentate anterior, identificarea metodei redefinite se realizează în faza de compilare. Este vorba de o legare inţială, "early binding", în care toate informaţiile necesare selectării metodei sunt prezentate din timp şi pot fi utilizate din faza de compilare.

12.7. METODE VIRTUALE Aşa cum s-a subliniat, un pointer la o clasă de bază poate primi ca valoare adresa unui obiect dintr-o clasă derivată. Deci, având un tablou de pointeri la obiecte de tip A, putem lucra cu tablouri de obiecte eterogene, cu elemente de tipuri diferite (B, C sau D). În unele situaţii, informaţiile privind tipul obiectului la care pointează un element al tabloului sunt disponibile abia în momentul execuţiei programului. O rezolvare a identificării metodei în momentul execuţiei programului o constituie funcţiile virtuale. Identificarea unei metode supradefinite, în momentul execuţiei, se numeşte legare ulterioară, "late binding". Dacă dorim ca selectarea metodei arată, din exemplul anterior, să se realizeze în momentul execuţiei, metoda va fi declarată metodă virtuală . Exemplu: class A { public: virtual void arată(); //în loc de void arată()

// . . . . }; class B : virtual public A { public: virtual void arată(); //în loc de void arată()

// . . . . }; class C : virtual public A { public: virtual void arată(); //în loc de void arată()

// . . . . }; class B : public B, public C{ public: virtual void arată(); //în loc de void arată()

// . . . . };

În urma acestei modificări, rezultele execuţiei programului anterior ar fi fost: void main() { A u(10), *PA; // Constructor A 10 B v1(9, 7), *PB; // Constructor A 9 Constructor B 7 - ptr. v1 C v2(8,12), *PC; // Constructor A 8 Constructor C 12 - ptr. v2 D w(31, 9, 14, 35), *PD; // Constructor A 31 Constructor B 9 199

CAPITOLUL 12 PA=&u; PA->arată(); PA=&v1; PA->arată(); PB=&v1; PB->arată(); PA=&w; PB=&w; PD=&w; u.arată(); PA->arată(); PB->arată(); PD->arată(); }

Crearea ierahiilor de clase

// Constructor C 14 Constructor D 35 // Se selectează metoda arată din clasa A // Se selectează metoda arată din clasa B // Se selectează metoda arată din clasa B

A.a=10 B.b=7 B.b=7

// Apelul metodei arată ptr. obiectul curent, clasa A // Se selectează metoda arată din clasa D // Se selectează metoda arată din clasa D // Se selectează metoda arată din clasa D

A.a=10 D.d=35 D.d=35 D.d=35

Observaţie: 1. Deoarece metoda arată este virtuală, s-a selectat metoda pentru clasa obiectului spre care pointează pointerul. 2. Dacă în clasa de bază se declară o metodă virtuală, în clasele derivate metodele cu aceeaşi semnatură vor fi considerate implicit virtuale (chiar dacă ele nu sunt declarate, explicit, virtuale). În cazul unei funcţii declarate virtuală în clasa de bază şi redefinite în clasa derivată, redefinirea metodei în clasa derivată are prioritate faţă de definirea ei din clasa de bază. Astfel, o funcţie virtuală declarată în clasa de bază actionează ca un substitut pentru păstrarea datelor care specifică o clasă generală de acţiuni şi declară forma interfeţei. La prima vedere, redefinirea unei funcţii virtuale într-o clasă derivată pare similară cu supraîncărcarea unei funcţiei obişnuite. Totuşi, nu este aşa, deoarece prototipul unei metode virtuale redefinite trebuie să coincidă cu cel specificat în clasa de bază. În cazul supraîncărcării unei funcţii normale, caracteristicile prototipurilor trebuie să difere (prin tipul returnat, numărul şi/sau tipul parametrilor). Exerciţiu: Fie ierahia de clase din figura 12.10. Metoda virtuală virt_f , din clasa bază, este redefinită în clasele derivate. #include class baza{ bază public: bază() {cout<<"Constructor bază\n";} derivat1 derivat2 ~bază() {cout<<"Destructor bază\n";} virtual void virt_f() {cout<<"Metoda virt_f() din bază\n";} derivat1a derivat2a }; class derivat1: public baza{ Figura 12.10. Ierarhie de clase public: derivat1():baza() {cout<<"Constructor derivat1\n";} ~derivat1() {cout<<"Destructor derivat1\n";} virtual void virt_f() {cout<<"Metoda virt_f() din derivat1\n";} }; class derivat2: public baza{ public: derivat2():baza() {cout<<"Constructor derivat2\n";} ~derivat2() {cout<<"Destructor derivat2\n";} virtual void virt_f() {cout<<"Metoda virt_f() din derivat2\n";} }; class derivat1a: public derivat1{ public: derivat1a():derivat1() 200

CAPITOLUL 12

Crearea ierahiilor de clase

{cout<<"Constructor derivat1a\n";} ~derivat1a() {cout<<"Destructor derivat1a\n";} virtual void virt_f() {cout<<"Metoda virt_f() din derivat1a\n";}

}; class derivat2a: public derivat2{ public: derivat2a():derivat2() {cout<<"Constructor derivat2a\n";} ~derivat2a() {cout<<"Destructor derivat2a\n";} virtual void virt_f() {cout<<"Metoda virt_f() din derivat2a\n";} }; void main() { baza *p; //Constructor bază baza b; //Constructor bază derivat1 d1; // Constructor bază Constructor derivat1 derivat2 d2; // Constructor bază Constructor derivat2 derivat1a d1a; // Constructor bază Constructor derivat1 derivat2a d2a; // Constructor bază Constructor derivat2 p=&b; p->virt_f(); // Metoda virt_f() din bază p=&d1;p->virt_f(); // Metoda virt_f() din derivat1 p=&d2;p->virt_f(); // Metoda virt_f() din derivat2 p=&d1a;p->virt_f(); // Metoda virt_f() din derivat1a p=&d2a;p->virt_f(); // Metoda virt_f() din derivat2a }

// Destructor derivat2a // Destructor derivat1a // Destructor derivat2 // Destructor derivat1 // Destructor bază

Destructor derivat2 Destructor derivat1 Destructor bază Destructor bază

Constructor derivat1a Constructor derivat2a

Destructor bază Destructor bază

(pentru d2a) (pentru d1a) (pentru d2) (pentru d1) (pentru b)

Exerciţu: Fie ierarhia de clase din figura 12.11. Se prezintă o modalitate de lucru cu un tablou eterogen, cu 5 elemente, care conţine pointeri atât spre clasa baza, cât şi spre clasele derivat1 şi derivat2. Pentru a putea trata în mod uniform cele trei tipuri de obiecte, s-a creat clasa lista_eterogena. Aceasta are ca dată membru pointerul la tipul baza şi metoda afis (virtuală, redefinită în clasele derivate). #include #include class baza{ protected: int val; public: baza() {cout<<"Constructor baza\n";} ~baza() {cout<<"Destructor baza\n";} void set_val(int a) {val=a;} virtual void afis() {cout<<"Element baza="<
baza

derivat1

derivat2

Figura 12.11.

CAPITOLUL 12

Crearea ierahiilor de clase

{cout<<"Destructor derivat1\n";} void afis() {cout<<"Element derivat1="<
}; class derivat2: public baza{ public: derivat2():baza() {cout<<"Constructor derivat2\n";} ~derivat2() {cout<<"Destructor derivat2\n";} void afis() {cout<<"Element derivat2="<afis();} }; void main() { clrscr(); baza B[3]; //Constructor baza

Constructor baza // (pentru elementele tabloului B, de tip baza

Constructor baza

derivat1 D1; //Constructor baza Constructor derivat1 (pentru D1, de tip derivat1) derivat2 D2; //Constructor baza Constructor derivat2 (pentru D2, de tip derivat2) lista_eterogena L[5]; cout<<"Apasa o tasta. . .\n";getch(); B[0].set_val(10); B[1].set_val(100); B[2].set_val(1000); D1.set_val(444); D2.set_val(555); L[0].set_l(&B[0]); //L[0].set_val(B); L[1].set_l(&D1); L[2].set_l((baza*) &D2); L[3].set_l(&B[1]);

//L[3].set_l(B+1); //L[4].set_l(&B[2]); L[4].set_l(B+2); for (int i=0; i<5; i++)

/*Element baza=10 Element baza=100

L[i].afis();

Element derivat1=444 Element baza=1000*/

Element derivat2=555

}

În cazul unei ierarhii de clase şi a unei metode virtuale a clasei de bază, toate clasele derivate care moştenesc această metodă şi nu o redefinesc, o moştenesc întocmai. Pentru aceeaşi metodă moştenită şi redefinită în clasele derivate, selecţia se realizează în momentul executării programului (legarea târzie). Funcţiile virtuale nu pot fi metode statice ale clasei din care fac parte. Funcţiile virtuale nu pot fi funcţii prietene sau constructori, dar pot fi destructori. Destructorii virtuali sunt utili în situaţiile în care se doreşte distrugerea uniformă a unor masive de date eterogene. Metode virtuale pure În unele situaţii, o clasă de bază (din care se derivează alte clase) a unei ierarhii, poate fi atât de generală, astfel încât unele metode nu pot fi descrise la acest nivel (atât de abstract), ci doar în clasele derivate. Aceste metode se numesc funcţii pure . Metodele virtuale pure sunt metode care se declară, nu se definesc la acest nivel de abstractizare. O metodă virtuală pură trebuie să fie prezentă în orice clasă derivată. Exemple: class bază{ public: virtual void virt_f()=0; }; class vieţuitoare { 202

//metoda virt_f este o metodă virtuală pură

CAPITOLUL 12 Crearea ierahiilor de clase public: virtual void nutriţie()=0; }; //metoda nutriţie este o metodă virtuală pură

O clasă cu cel puţin o metodă virtuală pură se numeşte clasă abstractă (clasa vieţuitoare este abstractă şi, ca urmare, nu poate fi instanţiată).

ÎNTREBĂRI ŞI EXERCIŢII Chestiuni teoretice 1. Ce este o clasă derivată şi ce caracteristici are? 2. Funcţiile prietene pot fi funcţii virtuale? 3. Destructorii se moştenesc? 4. Ce este o clasă virtuală şi în ce situaţii este utilă? 5. Ce este o metodă virtuală pură şi cum se declară aceasta? 6. Explicaţi ce înseamnă legarea iniţială (early binding).

7. Modul de declarare a unei clase derivate, cu mai multe superclase. 8. Ce este o metodă virtuală ? 9. Funcţiile virtuale pot fi membrii statici ai clasei din care fac parte ? 10. Redefinirea unei funcţii virtuale într-o clasă derivată este similară cu supraincarcarea funcţiei respective? Argumentati răspunsul. 11. Care este utilitatea moştenirii? 12. Explicaţi ce înseamnă legarea ulterioară (late binding).

Chestiuni practice 1. Să se implementeze ierarhia de clase din figura 12.12., cu membrii pe care îi consideraţi necesari. 2. Concepeţi o ierarhie de clase a figurilor geometrice. Ca date membre pot fi considerate poziţia, dimensiunile şi atributele de desenare (culoare, tip linie). Metodele vor permite operaţii de afişare, deplasare, ştergere, modificarea atributelor figurii. Clasa de bază va avea proprietăţile generale ale oricărei figuri: coordonatele pe ecran şi vizibilitate. 3. Din clasa matrice, să se deriveze clasa c_matrice, care reprezintă o matrice de complecşi.

persoana

angajat

student student bursier

bugetar

(cu salariul de bază fix) muncitor

(salariu în acord global: nr_ore* tarif_oră) Figura 12.12.

203

CAPITOLUL 12

Crearea ierahiilor de clase

204

CAPITOLUL 13 Intrări/ieşiri

1

INTRĂRI/IEŞIRI 13.1. Principiile de bază ale sistemului de I/O din limbajul C++ 13.2. Testarea şi modificarea stării unui flux 13.3. Formatarea unui flux 13.3.1. Formatarea prin manipulatori 13.3.2. Formatarea prin metode

13.4. 13.5. 13.6. 13.7. 13.8.

Metodele clasei istream Metodele clasei ostream Manipulatori creaţi de utilizator Fluxuri pentru fişiere Fişiere binare

13.1. PRINCIPIILE DE BAZĂ ALE SISTEMULUI DE INTRĂRI/IEŞRI DIN LIMBAJUL C++ În limbajul C++ există două sisteme de intrări/ieşiri:  Unul tradiţional, moştenit din limbajul C, în care operaţiile de intrare/ieşire se realizează cu ajutorul unor funcţii din biblioteca standard a sistemului (vezi capitolul 8);  Unul propriu limbajului C++, orientat pe obiecte. Sistemul de I/O orientat pe obiecte al limbajului C++ tratează în aceeaşi manieră operaţiile de I/O care folosesc consola şi operaţiile care utilizează fişiere (perspective diferite asupra aceluiaşi mecanism). La baza sistemului de I/E din C++ se află:  două ierarhii de clase care permit realizarea operaţiilor de I/O;  conceptul de stream (stream-ul este un concept abstract care înglobează orice flux de date de la o sursă (canal de intrare) la o destinaţie (canal de ieşiere), la un consumator. Sursa poate fi tastatura (intrarea standard), un fişier de pe disc sau o zonă de memorie. Destinaţia poate fi ecranul (ieşirea standard), un fişier de pe disc sau o zonă de memorie (figura 13.1). Fluxuri din clasa istream

Fluxuri din clasa ostream Memoria internă

Fluxuri din clasa ifstream

fişiere

Fluxuri din clasa ofstream

Figura 13.1. Stream-ul - baza sistemului de I/O Avantajele utilizării stream-urilor sunt:  Flexibilitate mare în realizarea operaţiilor de I/O (mai mare decât în cazul folosirii funcţiilor din C);  Posibilitatea de eliminare a erorilor care apar în mod frecvent la apelul funcţiilor de I/O obişnuite, când numărul specificatorilor de format diferă de cel al parametrilor efectivi;

205

CAPITOLUL 13 Intrări/ieşiri

Posibilitatea de a realiza operaţii de I/O nu numai cu date de tipuri predefinite, ci şi cu obiecte de tip abstract. Biblioteca de clase de I/O a limbajului C++ utilizează moştenirea, polimorfismul şi clasele abstracte. Ierarhia are două clase de bază (figura 13.2.):  Clasa virtuală ios care oferă informaţii despre fluxurile de date (variabile de stare, metode) şi facilităţi tratarea erorilor;  Clasa streambuf (clasă prietenă cu ios), destinată operaţiilor de I/O cu format. 

streambuf

ios

istream

ostream

fstreambase

strstreambase

iostream

ifstream

ofstream

fstream

filebuf

Figura 13.2. Ierarhia claselor de intrare/ieşire Utilizarea ierarhiilor de clase de mai sus implică includerea headerului iostream.h.  Clasa ios are un pointer către streambuf. Are date membru pentru a gestiona interfaţa cu streambuf şi pentru tratarea erorilor. Clasele derivate din clasa de bază ios:  Clasa istream, care gestionează intrările: class istream:virtual public ios  Clasa ostream gestionează ieşirile: class ostream: virtual public ios  Clasa iostream, derivată din istream şi ostream, gestionează intrările şi ieşirile. Fiecărui flux de date i se asociază în memorie o zonă tampon numită buffer. Clasa furnizează funcţii generale pentru lucrul cu zonele tampon şi permite tratarea operaţiilor de I/O fără a avea în vedere formatări complexe. class iostream:public istream, public ostream Clasele istream, ostream şi iostream sunt, fiecare, clase de bază pentru clasele derivate: class istream_withassign:public istream class ostream_withassign:public ostream class iostream_withassign:public iostream Clasele cu sufixul _withassign furnizează următoarele fluxurile predefinite (instanţe), deja cunoscute: 1. cout (console output) obiect al clasei ostream_withassign, similar fişierului standard de ieşire definit de pointerul stdout (în C). Se foloseşte cu operatorul insertor, supraîncărcat pentru tipurile predefinite: Exemplu: int n; cout<>n;

206

CAPITOLUL 13 Intrări/ieşiri

Extrage din fluxul de intrare caracterele corespunzătoare, le converteşte din zecimal în binar şi le depune în memorie. 3. cerr: flux de ieşire conectat la ieşirea standard pentru erori (stderr în C) (fără buffer intermediar). 4. clog: flux de ieşire conectat la ieşirea standard pentru erori (fără buffer intermediar).  Clasa streambuf este clasă prietenă cu ios. Ea furnizează funcţii generale pentru lucrul cu zonele tampon (buffere) şi permite tratarea operaţiilor de I/O fără formatări complexe. Din clasa streambuf deriva clasa filebuf . Să urmărim care este rolul buffere-lor (zonă tampon asociată fiecărui flux de date), prin următorul exemplu: Exemplu: void main() {int a; double x; char sir[20]; cin>>a>>x>>şir; cout<
Memorie internă a

date de intrare

x sir

buffer cin

Zona tampon (buffer-ul) este interfaţa dintre program şi sistemul de operare.

buffer cout Să urmărim cum se realizează transferul informaţiei în cazul operaţiilor multiple: cin rel="nofollow">>a>>x>>şir;

Figura 13.3. Rolul buffere-lor în operaţiile de I/O

cin>>a;

Compilatorul verifică dacă numărul introdus este întreg (se opreşte când întâlneşte altceva decât o cifră: în acest caz - un blanc). Îl citeşte printr-o metodă a tipului int, îl converteşte în binar şi îl transmite în memoria internă în spaţiul rezervat pentru variabila a. În încheiere, cin>>a devine cin. cin>>a>>x; // fluxul de date se transmite şi lui x Presupunem că am introdus de la tastatură 325 –17.45e5 exemplu de şir, valorile pentru a, x şi sir. La apăsarea tastei Enter, se transmite către sistemul de operare un octet cu semnificaţia de sfârşit introducere date. Sistemul de operare transmite un semnal zonei tampon şi abia acum se transmite valoarea (fluxul de date) lui a. La citirea şirului de caractere (până la primul blank), se transferă octet cu octet (i se adaugă automat caracterul NULL), din zona tampon în memoria internă. Observaţii: 1. Stream-ul este secvenţial. 2. El poate realiza intrări/ieşiri formatate. 3. Este bine că în cazul unui flux de intrare să se verifice mai întâi dacă în zona tampon se găseşte ceva. Dacă nu, se poziţionează cursorul la începutul zonei tampon. 4. Informaţia de la buffer către memorie este gestionată prin program; informaţia de la zona tampon către alte periferice este gestionată de către sistemul de operare.

13.2. TESTAREA ŞI MODIFICAREA STĂRII UNUI FLUX Clasa ios, clasă de bază a ierahiei claselor de I/O, defineşte o mulţime de tipuri, variabile şi metode comune tuturor tipurilor de stream-uri. Starea unui stream (rezultatul ultimului acces la acesta) este păstrată în cuvântul de stare, care este dată membră a clasei ios. Fiecărei instanţieri a unei clase de intrare/ieşire i se asociază propriul cuvânt de stare

207

CAPITOLUL 13 Intrări/ieşiri

(o mulţime de indicatori de stare), care păstrează toate informaţiile aferente erorilor apărute în cursul operaţiilor cu stream-ul. Aceşti indicatori sunt memoraţi la nivel de bit în data membru state. class ios{ //…… protected: int state; public:

enum io_state{ goodbit=0x00, eofbit=0x01, failbit=0x02, badbit=0x04, };

hardbit=0x08

//păstrează la nivel de bit valorile indicatorilor de stare //ultima operaţie de intrare/ieşire corectă //s-a întâlnit sfârşitul de fişier într-o operaţie de intrare (lipsa //caracterelor disponibile pentru citire) //setat dacă ultima operaţie de intrare/ieşire a eşuat; stream-ul //respectiv nu va mai putea fi folosit în operaţii de I/O până când //bitul nu va fi şters //ultima operaţie de intrare/ieşire invalidă; în urma resetării flag-ului //este posibil ca stream-ul să mai poată fi utilizat //eroare irecuperabilă

};

Testarea valorii cuvântului de stare asociat unui stream se realizează cu ajutorul metodelor:  int good();//Returnează valoare diferită de zero dacă cuvântul de stare este 0  int eof();//Returnează valoare diferită de zero dacă eofbit este setat  int fail();//Returnează valoare diferită de zero dacă failbit, hardbit sau badbit sunt setaţi  int bad();//Funcţionează ca metoda fail, dar nu ia în considerare flagul failbit. Modificarea valorii cuvântului de stare asociat unui stream se realizează cu ajutorul metodelor:  void clear(int i=0); Setează data membru state la valoarea parametrului (implicit 0). Este capabilă să şteargă orice flag, cu excepţia flag-ului hardfail. Exemplu:: a.clear(ios::badbit); Setează bitul failbit şi anulează valorile celorlaţi biţi din cuvântul de stare a fluxului a. Pentru ca ceilalţi biţi să rămână nemodificaţi, se apelează metoda rdstate. Exemplu: a.clear(a.rdstate()|val_f); Se setează un singur flag, lăsându-le nemodificate pe celelalte; val_f are una din valorile definite de enumerarea io_state.  int rdstate(); Returnează valoarea cuvântului de stare, sub forma unui întreg (valoarea datei membru state). Exemplu: a.clear(ios::badbit | a.rdstate()); Setează bitul failbit, fără a modifica valorile celorlalţi biţi: Testarea cuvântului de stare se poate realiza şi prin folosirea operatorilor ! şi void* :  Operatorul ! este supraîncărcat printr-o funcţie membră, cu prorotipul: int operator ! (); care returnează 0 dacă un bit de eroare din cuvântul de stare este setat (ca şi metoda fail). Exemplu: if (!cin) //echivalent cu if (!cin.good()) else 

cout<<"Bit de eroare setat în cuvântul de stare!\n"; cin>>a;

Operatorul void* converteşte stream-ul într-un pointer generic. Conversia are ca rezultat zero dacă cel puţin un bit de eroare este setat: operator void *();

208

CAPITOLUL 13 Intrări/ieşiri

Exemplu: O construcţie de forma: cin>>s; are ca valoare o referinţă la stream-ul cin, din clasa istream. Această referinţă poate fi utilizată sub forma: if (cin>>s). . . Pentru scoaterea unui stream din starea de eroare, fie dispare cauza erorii, fie trebuie şterse flagurile care semnalizează eroarea.

13.3. FORMATAREA DATELOR DIN FLUXURILE DE INTRARE/IEŞIRE Unul dintre principalele avantaje oferite de sistemul de I/O din limbajul C++ îl reprezintă ignorarea aspectului formatării, folosindu-se o formatare implicită. În plus, se permite definirea unei formatări specifice pentru o anumită aplicaţie. Aşa cum s-a subliniat în cazul cuvântului de eroare, cuvântul de format poate fi privit ca un întreg, pentru care fiecare bit reprezintă o constantă predefinită din clasa ios. În cadrul acestui cuvânt sunt definite câmpuri de biţi (cuvântul de format este dată membră care conţine un număr de indici ce sunt biţi individuali). class ios { //…… protected: long flag_x; int x_width; public: enum {skipws = 0x0001, left = 0x0002, right = 0x0004, internal = 0x0008, dec = 0x0010, oct = 0x0020, hex = 0x0040, showbase = 0x0080, showpoint = 0x0100, uppercase = 0x0200, showpos = 0x0400, scientific = 0x0800, fixed = 0x1000, unitbuf = 0x2000, stdio=0x4000 }; };

//păstrează la nivel de bit indicatorii de format //numărul de caractere utilizate pentru afişarea unei valori pe ecran // salt peste spaţiile albe de la intrare // aliniere la stânga la ieşire // aliniere la dreapta la iesire // aliniere după semn sau specificator al bazei la ieşire // conversie în baza 10 // conversie octală la intrare/ieşire // conversie hexa la intrare/ieşire // afişarea bazei la ieşire // afişarea punctului zecimal pentru numere reale la ieşire // afişarea cu majuscule a cifrelor hexa şi a literei E la ieşire. // afişarea semnului + pentru numerele pozitive la ieşire //folosirea formatului exponenţial (ştiinţific) pentru numerele reale // folosirea formatului în virgulă fixă pentru numere reale la // goleşte zona tampon după fiecare ieşire // goleşte "stdout" şi "stdin" după fiecare inserare

În figura 13.4. sunt prezentate numele câmpurilor de biţi (acolo unde este cazul). În cadrul fiecărui câmp de biţi (adjustfield, basefield, floatfield) un singur bit poate fi activ.

209

Figura 13.4. Câmpurile de biţi din cuvântul de stare

skipws

left

right

adjustfield

internal

dec

oct

hex

showbase

showpoint

uppercase

basefield

showpos

scientific

fixed

unitbuf

floatfield floatfield

CAPITOLUL 13 Intrări/ieşiri

Modificarea cuvântului de format se poate realiza în următoarele moduri: 1. Cu ajutorul manipulatorilor (cu sau fără parametri); 2. Cu ajutorul unor funcţii membre ale claselor istream sau ostream.

13.3.1. FORMATAREA PRIN MANIPULATORI Manipulatorii sunt funcţii speciale, asemănătoare operatorilor, care pot fi folosite împreună cu operatorii de inserţie într-un flux de ieşire sau de extracţie dintr-un flux de intrare, în scopul modificării caracteristicilor formatului informaţiilor de intrare/ieşire. Manipulatorii furnizează, ca rezultat, fluxul obţinut în urma acţiunii manipulatorilor, ceea ce permite ca aceştia să fie trataţi ca informaţii de transmis. Manipulatorii permit, deasemenea, înlănţuirea operatorilor insertori sau extractori care utilizează formate diferite. Manipulatorii pot fi:  Manipulatori fără parametri;  Manipulatori cu parametri;

13.3.1.1. Manipulatori fără parametri Prototipul manipulatorilor fără parametri este: ostream & nume_manipulator(ostream &); istream & nume_manipulator(istream &); Manipulatorii fără parametri (prezentaţi în tabelul 13.1.) se folosesc astfel: flux_ieşire<<manipulator; flux_intrare>>manipulator; Tabelul 13.1 Manipula Intrare/ tor Ieşire dec I/O hex I/O oct I/O ws I endl O ends O flush O

Acţiune Formatează datele numerice în zecimal (activează bitul de conversie zecimală) Formatează datele numerice în hexa (activează bitul de conversie hexazecimală) Formatează datele numerice în octal (activează bitul de conversie octală) Ignoră caracterele "spaţii albe" (activează bitul de salt peste spaţiile albe) Afişează (inserează) un caracter '\n' şi eliberează fluxul Inserează un caracter null, de sfârşit de flux (\0) Videază (goleşte) buffer-ul, eliberează fluxul

13.3.1.2. Manipulatori cu parametri Prototipul manipulatorilor fără parametri (prezentaţi în tabelul 13.2.) este: istream & manipulator (argument); ostream & manipulator (argument); Tabelul 13.2. Manipulator setbase(int baza) resetiosflags(long f)

Intrare/ Ieşire I/O I/O

Acţiune Stabileşte baza de conversie Atribuie valoarea 0 tuturor biţilor indicaţi de argument,

210

CAPITOLUL 13 Intrări/ieşiri

lăsând restul biţilor nemodificaţi (dezactivează indicatorii specificaţi de f) setiosflags (long f) I/O Atribuie valoarea 1 tuturor biţilor indicaţi de argument, lăsând restul biţilor nemodificaţi (activează indicatorii specificaţi de f) setfill (int c) I/O Defineşte caracterul de umplere (cel implicit este spaţiul liber, blank-ul) setprecision (int p) I/O Defineşte precizia pentru numerele reale setw (int w) I/O Defineşte lăţimea câmpului (numărul de octeţi care vor fi citiţi sau afisaţi) Utilizarea manipulatorilor impune includerea header-ului iomanip.h. Exerciţiu: Exemplificarea modului de folosire a manipulatorilor pentru intrări/ieşiri formatate. #include #include void main() {int a,b,c;double alfa,x,y;long ll;char xx,yy; float u,v; unsigned int w; unsigned char ab,db; a=125,b=10; ll=100*a*b; cout<<"100*a*b="< void main() {int i=123, j=456; double x=1234.567890123456, y=5678; cout.width(5); cout<<<' '<<j<<endl; //123 456 cout<
Observaţie: Toate metodele sunt persistente, cu excepţia metodei width care este valabilă numai pentru operaţia următoare, după care se setează la 0.

13.4. METODELE CLASEI istream Clasa istream este derivată din clasa ios: ios:istream.  Supradefinirea operatorului de extracţie >> 212

CAPITOLUL 13 Intrări/ieşiri

Operatorul de extracţie din stream-ul (fluxul) de intrare este supraîncărcat printr-o funcţie membră, pentru toate tipurile de bază. El are rolul de a extrage din fluxul de intrare caracterele necesare pentru a obţine o valoare dintr-un tip de bază. istream & operator >> (&tip_de_bază); Primul argument este implicit (this): clasa care îl apelează. Al doilea argument este o referinţă către un tip_ de_bază. Operatorul poate fi supraîncărcat printr-o funcţie prietenă (vezi capitolul 11), pentru a extrage din fluxul de intrare informaţiile despre un obiect dintr-un tip definit de utilizator (clasă): friend istream & operator >>(istream &, clasa &);  Metoda get istream & get (char &); Metoda extrage din fluxul de intrare un caracter şi îl memorează în variabila referinţă, transmisă ca parametru. Întoarce o referinţă către istream. Exemplu: char c; cin.get(c); Metoda get este supraîncărcată şi astfel: int get ( ); Extrage din fluxul de intrare un singur caracter şi îl returnează sub forma unui întreg. Această metodă este utilizată, în special, la testarea sfârşitului de fişier (EOF, -1). Metoda get este supraîncărcată şi astfel: istream & get (char * şir, int lungime, char delimitator = ‘\n’) Se extrage din fluxul de date de intrare un şir de caractere (char * şir = pointer către şir), cu lungimea maximă specificată prin argumentul lungime, până la întâlnirea delimitatorului (delimitator nu se extrage din fluxul de date de intrare). Rezultatul se depune în variabila şir.  Metoda getline Metoda determină preluarea din fluxul de intrare a unui şir de caractere, terminat cu un caracter cunoscut. istream & getline (char * şir, int lungime, char delimitator = EOF); Acţionează asemănător cu metoda get supraîncărcată prin ultima formă, cu deosebirea că din fluxul de intrare se extrage şi delimitatorul. Delimitatorul nu se introduce în şir. Exemplu: char şir[50];cin.get (şir, 50, ’\ n’); // sau cin.get(şir, 50); Din fluxul de date de intrare se extrag caracterele până la sfârşit de linie, cel mult 50 de caractere. Extragerea caracterelor din fluxul de intrare se termină fie la întâlnirea terminatorului, fie atunci când s-a citit un număr de caractere egal cu lungime-1 .  Metoda gcount returnează un întreg care reprezinată numărul efectiv de caractere preluat prin getline. int gcount();  Metoda read extrage din fluxul de intrare un şir de caractere (octeţi) de lungime impusă şi-l depozitează în zona de memorie şir. istream & read (char * şir, int lungime); Eemplu: char t[30]; cin.read(t, 20);  Metoda ignore este utilizată la golirea zonei tampon a stream-ului de intrare. istream & ignore (int lungime, char delimitator = ‘ | ’); Se extrag din fluxul de intrare caracterele până la delimitator, dar nu mai multe decât numărul indicat de parametru lungime. Caracterele extrase sunt eliminate, nu sunt memorate.  Metoda peck furnizează primul caracter din fluxul de intrare, fără a-l extrage însă din flux. Caracterul va fi primul caracter extras la următoarea citire. int peck( ); Exemplu: c = cin.peck ( );  Metoda putback inserează în fluxul de intrare caracterul trimis ca argument. istream & putback(char &); Observaţii: 1) int a; cin.get (a) ⇔ cin >> a; 2) Metodele pot fi folosite în serie: Exemplu: cin.get(a).get(b).get (c);

213

CAPITOLUL 13 Intrări/ieşiri

13.5. METODELE CLASEI ostream 



Supradefinirea operatorului de inserţie << Operatorul de inserţie în fluxul de ieşire este supraîncărcat printr-o funcţie membră pentru toate tipurile de bază. El are rolul de a introduce (insera) în fluxul de ieşire caracterele necesare pentru a afişa o valoare dintr-un tip de bază. ostream & operator << (tip_de_bază); Primul argument este implicit (this). Al doilea argument este o expresie de tip de bază. Operatorul poate fi supraîncărcat printr-o funcţie prietenă, pentru a insera în fluxul de ieşire informaţiile despre un obiect dintr-un tip definit de utilizator (clasă): friend ostream & operator << (ostream &, clasa &); Metoda put inserează în fluxul de date de ieşire caracterul transmis ca parametru. ostream put (char); Exemple: char c='S'; cout . put ( c ); // echivalent cu cout << c; char c1='A',c2='B',c3='C';cout.put(c1).put(c2).put(c3); //echivalent cu:cout.put(c1);cout.put(c2); cout.put(c3); // echivalent cu cout<


Metoda write inserează în fluxul de date de ieşire un număr de caractere, de lungime impusă, existente în zona tablou. ostream & write (char * tablou, int lungime); Exemplu: char t[]="Bună ziua!\n"; cout.write(t, 4); //Inserează în fluxul de ieşire 4 caractere, începând de la adresa t.

Exerciţiu: Se ilustrează formatarea unui flux de intrare/ieşire atât prin funcţii membre, cât şi cu ajutorul manipulatorilor (cu sau fără parametri). #include #include void main() {int i=123; char car, mesaj[]=" Apasă tasta Enter\n"; cout<<setw(5)<
13.6. MANIPULATORI CREAŢI DE UTILIZATOR Manipulatorii sunt funcţii speciale care pot fi utilizate alături de operatorii de inserţie/extracţie pentru a formata datele de ieşire/intrare. Pe lângă manipulatorii predefinţi, utilizatorul îşi poate crea manipulatori proprii. Crearea manipulatorilor fără parametri ostream &nume_manipulator (ostream &); 214

//pentru un flux de ieşire

CAPITOLUL 13 Intrări/ieşiri

istream &nume_manipulator (istream &); Crearea manipulatorilor cu parametri Crearea manipulatorilor fără parametri necesită folosirea a două funcţii: ostream & nume_manipulator (ostream &, int);

//pentru un flux de intrare

omanip nume_manipulator (int n) // funcţie şablon (template) cu parametru de tip int {return omanip (nume_manipulator, n);} Deci, manipulatorii sunt funcţii ale căror corpuri sunt stabilite de utilizator. Exemplu: În exemplul următor se definesc manipulatorii indentare (indentare = înaintea liniei propriuzise, se lasă un număr de spaţii albe), convhex (realizează conversia din zecimal în hexa), init (iniţializează lungimea câmpului la 10 caractere, stbileşte precizia la 4 şi caracterul de umplere $). #include #include ostream &convhex (ostream &ies) {ies.setf(ios::hex, ios::basefield); ies.setf(ios::showbase); return ies;} ostream &indentare (ostream &ies, int nr_spaţii) {for (int i=0; i indentare (int nr_spaţii) {return omanip (indentare, nr_spaţii);} void main() {cout<<712<<' '<
13.7. FLUXURI DE DATE PENTRU FIŞIERE Un flux de intrare/ieşire poate fi asociat unui fişier. Pentru a asocia un flux de ieşire unui fisier, este necesară crearea unui obiect de tipul ofstream. Clasa ofstream este derivată din clasele ostream (moşteneşte metodele care permit inserarea informaţiilor într-un flux de ieşire) şi fstreambase (moşteneşte operaţiile privitoare la asocierea fişierelor). Pentru a asocia un flux de intrare unui fişier, este necesară crearea unui obiect de tipul ifstream. Clasa ifstream este derivată din clasele istream (moşteneşte metodele care permit extragerea informaţiilor dintr-un flux de intrare) şi fstreambase (moşteneşte operaţiile privitoare la asocierea fişierelor). Pentru crearea unor obiecte din clasele ifstream sau ofstream se impune includerea headere-lor iostream.h şi fstream.h. Constructorii clasei ofstream sunt: ofstream(); Fluxul nu este asociat nici unui fişier ofstream(const char *nume_fisier, int mod_acces=ios:out); Parametri constructorului sunt numele fişierului şi modul de acces la acesta (scriere, fişier de ieşire). ofstream (int fd, char *buffer, int lung_buffer); Parametrii sunt numărul intern al fisierului (fd), zona tampon de intrări/ieşiri (buffer) şi lungimea zonei tampon (lung_buffer). Constructorii clasei ifstream sunt:

ifstream(); 215

CAPITOLUL 13 Intrări/ieşiri

Fluxul nu este asociat nici unui fişier ifstream(const char *nume_fisier, int mod_acces=ios:in); Parametri constructorului sunt numele fişierului şi modul de acces la acesta (citire, fişier de intrare). ifstream (int fd, char *buffer, int lung_buffer); Parametrii sunt numărul intern al fişierului (fd), zona tampon de intrări/ieşiri (buffer) şi lungimea zonei tampon (lung_buffer). Exemplu: Se declară obiectul numit fis_ies, de tipul ofstream, obiect asociat unui fişier cu numele DATE.txt, deschis apoi pentru ieşire (scriere). Scrierea în fişierul asociat obiectului se face printr-un flux care beneficiază de toate "facilităţile" clasei ostream. ofstream fis_ies("DATE.txt", ios::out); //sau: ofstream fis_ies("DATE.txt"); Scrierea în fişierul DATE.txt: fis_ies<< . . . << . . .; Pentru scriere formatată sau scriere binară în fişierul asociat: fis_ies.write(. . . ); Pentru examinarea stării fluxului de eroare corespunzător: if (fis_ies) . . .; Se declară obiectul numit fis_intr, de tipul ifstream, obiect asociat unui fişier cu numele NOU.txt, deschis apoi pentru intrare (citire). Citirea din fişierul asociat obiectului se face printr-un flux care beneficiază de toate "facilităţile" clasei istream. ifstream fis_intr("DATE.dat", ios::in); //sau: ifstream fis_intr("NOU.txt"); Citirea în fişierul NOU.txt: fis_intr>> . . . >> . . .; Pentru citire formatată sau citire binară din fişierul asociat: fis_intr.read(. . . ); Pentru examinarea stării fluxului de eroare corespunzător: if (fis_intr) . . .; Observaţii: Conectarea (asocierea) unui flux unui fişier presupune:  Fie existenţa a două tipuri de obiecte: un flux şi un fişier;  Fie declararea unui flux care va fi asociat ulterior unui fişier. Clasa fstream moşteneşte atât clasele ifstream, cât şi ofstream. Ea permite accesul în citire/scriere. Constructorul cu parametri al clasei fstream: fstream(char *nume_fişier,int mod_deschid,int protec=filebuf::openprot); Modul de deschidere (mod_deschid) a unui fişier poate fi: ios::in fişier de intrare ios::out fişier de ieşire ios::ate după deschiderea fişierului, poziţia curentă este sfârşitul acestuia ios::app mod append (de scriere la sfârşitul unui fişier existent) ios::trunc dacă fişierul există, datele existente se pierd ios::nocreate dacă fişierul asociat nu există, nu va fi creat decât la scriere ios::binary fişier binar ios::noreplace fişierul nu trebuie să existe Modul de deschidere este definit printr-un cuvânt de stare, în care fiecare bit are o semnificaţie particulară. Pentru setarea (activarea) mai multor biţi, se foloseşte operatorul | (sau logic, pe bit), ca în exemplu. class ios{ //…….. public: enum open_mode{ in=0x01, out=0x02, ate=0x04, app=0x08, trunc=0x10, nocreate=0x20, noreplace=0x40, binary=0x80 }; };

216

CAPITOLUL 13 Intrări/ieşiri Exemple: ifstream f1("DATE.txt", ios::in|ios::nocreate); fstream f2("pers.dat", ios::in|ios::out); 

Metoda open Prelucrarea unui fişier începe cu deschiderea acestuia, care se poate realiza:  La instanţierea obiectelor din clasele ifstream, ofstream sau fstream, cu ajutorul constructorului cu parametri (constructorul apelează automat funcţia open);  Prin apelul explicit al funcţiei open. Metoda open este definită în clasa fstreambase şi este supraîncărcată în funcţiile derivate din aceasta. Ea are aceeaşi parametri ca şi constructorul clasei şi prototipul: void open (char *nume_fişier, int mod_deschidere, int protecţie);



Metoda close realizează închiderea unui fişier: void close();

Exerciţiu: În exemplul următor se asociază fluxului de ieşire (obiectului fis_ies) fişierul numit text.txt. Se testează cuvântul de stare de eroare, iar dacă nu au fost probleme la deschidere, în fişier se scrie un text (2 linii), apoi, o serie de constante numerice, formatate. Se închide fişierul. Acelaşi fişier este asociat unui flux de intrare (pentru citire). Textul (scris anterior) este citit în variabila şir, apoi este afişat. Se citesc din fişier şi apoi sunt afişate constantele numerice. Se închide fişierul. #include #include int main() {ofstream fis_ies("text.txt"); //deschidere fişier text.txt if (!fis_ies){ cout<<"Eroare la dechiderea fişierului!\n"; return 1;} fis_ies<<"Scrierea unui text \n în fişier\n"; fis_ies<<100<>sir; cout<<sir<<'\n';} cout<<endl; int i, j; double u; fis_intr>>i>>hex>>j>>u; cout<<<' '<<j<<' '<
Observaţii: Deschiderea şi închiderea fişierului s-ar fi putut realiza şi astfel: fstream fis_ies;fis_ies.open("text.txt", ios::out);fis_ies.close(); fstream fis_in;fis_in.open("text.txt", ios::in);fis_in.close();

Exemplu: Să se scrie un program care concatenează două fişiere, depunând rezultatul în al treilea fişier. Se crează trei fluxuri, f1, f2 şi dest, care sunt ataşate fişierelor corespunzătoare (prin metoda open). Copierea în fişierul destinaţie se realizează prin folosirea metodelor get, put şi funcţia copy. Metoda close întrerupe legătura logică dintre fluxuri şi fişiere. #include #include <process.h> #include void semn_er(char *c) { cerr<<"\n\n Eroare la deschiderea fisierului "<
}

CAPITOLUL 13 Intrări/ieşiri {char nume_sursa1[14], nume_sursa2[14], destinatie[14]; ifstream f1, f2; ofstream dest; cout<<"Numele celor 3 fisiere:"<<"Destinatie Sursa1 Sursa2\n"; cin.read(destinatie,13);cin.read(nume_sursa1,13);cin.read(nume_sursa2,13); destinatie[13]=nume_sursa1[13]=nume_sursa2[13]='\0'; f1.open(nume_sursa1, ios::nocreate); if (!f1) semn_er(nume_sursa1); //utiliz operator ! redefinit f2.open(nume_sursa2, ios::nocreate); if (!f2) semn_er(nume_sursa2); //utiliz operator ! redefinit dest.open(destinatie); if (!dest) semn_er(destinatie); //utiliz operator ! redefinit copiere (dest, f1); copiere(dest, f2); f1.close();f2.close();dest.close(); }

Exerciţiu: Se ilustrează folosirea fluxurilor pentru operaţii de citire/scriere în fişiere text. #include #include void main() { ofstream fisierout("nou.txt"); // Deschide fisierul text nou.txt.. fisierout << " Teste fisiere "; //Scrie un text in fisier fisierout.close(); // Inchide fisierul.

// Deschide un alt fisier text si scrie in acesta niste numere (numerele sunt separate prin spatii) fisierout.open("numere.txt"); fisierout <<15<<" "<<42<<" "<<1;fisierout.close();

// Deschide fisierul nou.txt pentru citire. ifstream fisierin; fisierin.open("nou.txt"); char p[50]; // Stabileste o zona de memorie folosita pentru citirea textului. fisierin >>p; // Citeste si afiseaza primele doua cuvinte din fisier. cout <>p; cout <
// Deschide fisierul text numere.c pentru citirea numerelor intregi // Citeste numere din fisier pana cand ajunge la sfarsitul fisierului. while (!fisierin.eof()) {fisierin >> numar; if (!fisierin.eof())

// Citeste un numar intreg. cout<
// Daca nu a ajuns la sfarsitul fisierului afiseaza numarul. } fisierin.close(); // Inchide fisierul.

// se citeste textul din fisierul nou.txt cuvant cu cuvant. fisierin.open("nou.txt"); while (!fisierin.eof()) // Citeste cuvinte din fisier pana ajunge la sfarsitul fisierului. { // Citeste cuvant cu cuvant. fisierin >>p; cout <
13.8. FIŞIERE BINARE Fişierele binare reprezintă o succesiune de octeţi, asupra cărora la intrare sau la ieşire nu se realizează nici o conversie. Ele sunt prelucrate binar, spre deosebire de exemplul anterior, în care prelucrarea se realiza în mod text (un număr în format intern este reprezentat binar, dar în format extern el apare ca un şir de caractere. Un şir în formatul extern are terminatorul '\n', iar în format intern are terminatorul '\0'). Deoarece 218

CAPITOLUL 13 Intrări/ieşiri

nu există un terminator de linie, trebuie specificată lungimea înregistrării. Metodele write, read nu realizează nici o conversie.  

Metoda write scrie în fişier un număr de n octeţi: ostream &write(const char * tab, int n); Metoda read citeşte din fişier n octeţi. istream &read (const char * tab, int n);

Limbajul C++ oferă (ca şi limbajul C) posibilitatea de acces direct într-un fişier conectat la un flux de date. După fiecare operaţie de citire (extragere din fluxul de intrare) sau citire (inserţie în fluxul de ieşire), pointerul care indică poziţia curentă în fluxul de date este incrementat cu un număr egal cu numărul de octeţi transferaţi. În această situaţie se realizează un acces secvenţial (ca în cazurile precedente). Clasele ifstream şi ofstream au ca metode funcţiile seekg (membră a clasei istream), respectiv seekp (membră a clasei ostream) care permit modificarea valorii pointerului. istream & seekg(long); istream & seekg(long, seek_dir); ostream & seekp(long); ostream & seekp(long, seek_dir); Ambele metode primesc doi parametri:  un întreg reprezentând deplasarea pointerului în raport cu baza (referinţa) precizată de al doilea parametru;  o constantă întreagă care precizează baza. Valorile constantei sunt definite în clasa ios: class ios{ //. . . public: enum seek_dir{

beg=0, cur=1, end=2 };

deplasare faţă de începutul fişierului (implicit) deplasare faţă de poziţia curentă deplasare faţă de sfârşitul fişierului

};

Pentru aflarea valorii pointerului, clasa ifstream are metoda tellg, iar clasa ofstream are metoda tellp, cu prototipurile: long istream::tellg(); long ostream::tellp(); Exemplu: Se crează ierarhia de clase din figura 13.5., în care se implementează clasa student şi clasa proprie fisier_stud, clasă class fstream derivată din fstream. În acelaşi fişier, binar, clase prietene se realizează şi scrierea şi citirea. Datorită posibilităţii de acces direct, class fisier_stud class student se pot modifica informaţiile referitoare la studentul înregistrat în fişier pe poziţia k. Figura 13.5. Ierarhia de clase

#include #include class student { char nume[20]; int grupa, note[3]; public: void citire_date(); friend ostream &operator<<(ostream &, const student &); 219

CAPITOLUL 13 Intrări/ieşiri friend class fisier_stud; }; class fisier_stud:public fstream //fişier binar cu obiecte din cls student {public: fisier_stud(){;}; fisier_stud(const char*num_f,int mod,int protecţie=filebuf::openprot):fstream(num_f,mod |ios::binary,protecţie){} void open(const char *num_f, int mod, int protectie=filebuf::openprot) {fstream::open(num_f,mod|ios::binary,protectie);}//apelul met. open din cls. fstream int citeste_f(student &); int scrie_f (const student &); }; void student::citire_date() { int gr, OK; for (int k=0; k<21; k++) nume[k]=0; cin.ignore(1000, '\n'); cout<<"Numele studentului:"; cin.get(nume, 21);cin.ignore(1000, '\n'); do{ cout<<"Grupa:"; cin>>gr; OK=(cin && gr>0 && gr<8000); if (!OK){ cout<<"Eroare. Repetaţi introducerea!\n"; cin.clear(); } else {grupa=gr; cin.ignore(1000, '\n');} }while (!OK); for (int k=0; k<3; k++){ int n; do{ cout<<"nota "<>n; OK=cin && n>0 && n<=10; if (!OK){ cout<<"Nota gresită.Repetati!\n";cin.clear();cin.ignore(1000, '\n'); } }while (!OK); note[k]=n;cin.ignore(1000, '\n'); } } ostream &operator << (ostream &ies, const student &stud) {ies<<"Student:"<<stud.nume<<" Grupa:"<<stud.grupa<<" Note:"; for (int k=0; k<3; k++) ies<<stud.note[k]<<' '; ies<<endl;return ies;} typedef union {student s; char sbin[sizeof(student)];} stud; int fisier_stud::scrie_f(const student &s) {stud s1; s1.s=s; write(s1.sbin, sizeof(student)); int fisier_stud::citeste_f(student &s) {stud s1; read(s1.sbin, sizeof(student));

return bad();

s=s1.s;

}

return bad();}

main() {char Nume_Fis[]="Stud_bin.bin"; student s; fisier_stud fs; int gata; //deschidere fişier ptr. scriere fs.open (Nume_Fis, ios::out); if (!fs){ cout<<"eroare la deschiderea fişierului "<
CAPITOLUL 13 Intrări/ieşiri cout<<"Scrieţi date în fişier [D|N] ?"; rasp=toupper(cin.get());cin.ignore(1000, '\n'); } while (răsp!='D' && răsp!='N'); if (răsp == 'D'){ fs.scrie_f(s); cout<<"Poz. în fişier: "<
//citire fs.open(Nume_Fis, ios::in); if (!fs){ cout<<"Eroare la deschiderea fiş-lui "<>k; cin.ignore(1000, '\n'); fs.seekg(k*sizeof(stud));s.citire_date();fs.scrie_f(s); fs.seekg(0); k=0; while(!fs.eof()){ fs.citire_f(s); if (!fs && !fs.eof()){cout<<"Eroare la citirea din fiş:"<
ÎNTREBĂRI ŞI EXERCIŢII Chestiuni teoretice 1. Ce înţelegeţi prin conceptul de stream? 2. Care consideraţi că ar fi avantajele utilizării

3. Ce sunt manipulatorii? 4. Cum îşi poate crea utilizatorul manipulatorii proprii?

abordării orientate pe obiecte a sistemului de I/O?

221

CAPITOLUL 13 Intrări/ieşiri

Chestiuni practice Rezolvaţi problemele din capitolul 8, folosind abordarea orientată pe obiecte.

222

Bibliografie

BIBLIOGRAFIE

[1]

Borland C++ 4, Ghidul programatorului, Editura Teora, 1996

[2]

Brookshear, J.G., Introducere în informatică, Editura Teora, Bucureşti, 1998

[3]

Bumbaru, S., Note de curs

[4]

Somnea, D., Turturea, D., Iniţiere în C++ - Programarea orientată pe obiecte, Editura Tehnică, Bucureşti, 1993

[5]

Spircu, C., Lopătan, I., POO-Analiza, proiectarea şi programarea orientate spre obiecte, Editura Teora, Bucureşti, 1996

[6]

Stroustrup, B., A Beginners C++, www.cs.uow.edu.av/people/nabg/ABC/ABC.html

[7]

Stroustrup, B., C++ Annotations, www.icce.rug.nl/docs/cplusplus/cplusplus.html

223

MOSTENIRE Mostenirea permite crearea ierarhiilor de clase, deci a ierarhiilor de concepte (Un concept poate fi implementat printr-o clasa. Clasele grupeaza obiecte de acelasi tip; reprezinta aceeasi idee, acelasi concept). Aceasta proprietate se manifesta prin faptul ca din orice clasa putem deriva alte clase.

Class A

Class B

Class E

Class C

Class D

Class F

A – clasa de baza; B, C, D – clase derivate din clasa de baza A E, F – clase derivate din clasa de baza B Informatia comuna apare in clasa de baza, iar informatia specifica - in clasa derivata. Clasa derivata reprezinta o specializare a clasei de baza. Orice clasa derivata mosteneste datele membru si metodele clasei de baza. Deci acestea nu trebuie redeclarate in clasa derivata. Mostenirea poate fi: simpla (orice clasa are o singura superclasa) sau multipla (o clasa are mai multe superclase). Mostenirea simpla Declararea unei clase derivate class : <modificator_de_acces> { //corpul clasei derivate - elemente specifice clasei derivate };

cls. de baza

In functie de modificatorii de acces care apar in clasa de baza, in declararea clasei derivate si in clasa derivata, lucrurile se pot rezuma astfel:

Modificator acces in clasa de baza private protected sau public public protected protected public

Modificator de acces (protectie) din declararea clasei derivate private, protected, public private protected protected public public

1

Accesul in clasa derivata la elementul mostenit de la clasa de baza inaccesibil private protected protected protected public

MOSTENIRE Constructorii claselor derivate Constructorii si destructorii sunt functii membre care nu se mostenesc. La instantierea unui obiect din clasa derivata se apeleaza mai intai constructorul clasei de baza, apoi constructorul clasei derivate. La distrugerea obiectelor, se apeleaza intati destructorul clasei derivate, apoi destructorul clasei de baza. Transmiterea argumentelor unei functii constructor din clasa de baza se face folosind o forma extinsa a declaratiei constructorului clasei derivate, care transmite argumentele unui sau mai multor constructori din clasa de baza. In general, clasele utilizeaza constructori definiti de programator. In cazul in care acestia lipsesc, compilatorul genereaza automat un constructor implicit pentru clasa respectiva. Acelasi lucru se intampla si in cazul constructorilor de copiere. La instantierea unui obiect din clasa derivata, o parte din valorile primite ca parametri folosesc la initializarea datelor membru ale claselor de baza, iar restul initializeaza datele membru specifice clasei derivate. In exemplul urmator, este construita urmatoarea ierarhie de clase:

baza public

private

protected deriv1

deriv2

#include class baza { int a; protected: double w; void seteaza_a (int a1){a=a1;} void seteaza_w (int w1) {w=w1;} public: int c; baza (int a1, double w1, int c1) {a=a1; w=w1; c=c1;cout<<"Constructor cls. baza\n";} ~baza() {cout<<"Destructor baza\n";} void arata() {cout<
deriv3

MOSTENIRE public: deriv2(int a1, double w1, int c1, int b1):baza(a1, w1, c1) {b=b1; cout<<"Constructor deriv2\n";} ~deriv2() {cout<<"Destructor deriv2\n";} double calcul() {return w+c+b;} friend ostream &operator<<(ostream &, const deriv2 &); }; class deriv3: private baza { int b; public: deriv3(int a1, double w1, int c1, int b1):baza(a1, w1, c1) {b=b1; cout<<"Constructor deriv3\n";} ~deriv3() {cout<<"Destructor deriv3\n";} double calcul() {return w+c+b;} friend ostream &operator<<(ostream &, const deriv3 &); }; ostream &operator<<(ostream &ies, const baza &b) {ies< class persoana { protected: sir numele,prenumele; char sexul; public: persoana () //constructor vid {numele="";prenumele="";sexul='m';} persoana(const sir&,const sir&,const char); //constructor persoana (const persoana&); //constr. copiere virtual ~persoana(); //destructor const sir& nume(); const sir&prenume(); char sex(); virtual void afisare(); friend ostream & operator<<(ostream &, const persoana &); friend istream & operator>>(istream &, persoana &); }; class student:public persoana { protected: sir facultatea,specializarea; int anul,grupa; 4

MOSTENIRE public: student(); student(const sir&,const sir&,const char,const sir&,const sir&,const int,const int); student(const persoana&,const sir&,const sir&,const int,const int); student(const student&); virtual ~student(); const sir& facult(){return facultatea;} const sir& spec(){return specializarea;} int an(){return anul;} int grup(){return grupa;} virtual void afisare(); friend ostream & operator<<(ostream &, const student &); // friend istream & operator>>(istream &, student &); }; class student_bursier:public student {protected: char tipul_bursei; public: student_bursier(const student&,char); student_bursier(const student_bursier&); virtual ~student_bursier(); char tip_bursa() {return tipul_bursei;} double valoare_bursa(); virtual void afisare(); // friend ostream & operator<<(ostream &, const student_bursier &); // friend istream & operator>>(istream &, student_bursier &); }; persoana::persoana(const sir& nume,const sir& prenume,const char sex) {numele=nume;prenumele=prenume;sexul=sex; cout<<"Constr. PERSOANA\n";} persoana::persoana(const persoana& pers) { numele=pers.numele;prenumele=pers.prenumele;sexul=pers.sexul; cout<<"Constructor copiere PERSOANA\n";} persoana::~persoana() {cout<<"Destructor PERSOANA\n";} const sir& persoana::nume() {return numele;} const sir& persoana::prenume() {return prenumele;} char persoana::sex() {return sexul;} void persoana::afisare() {cout<<"Afisare PERSOANA:\n"; cout<
MOSTENIRE istream & operator>>(istream & tastat, persoana &p) {tastat>>p.numele>>p.prenumele>>p.sexul; return tastat;} // METODE CLS. STUDENT student::student(const sir&nume,const sir&prenume,const char sex,const sir& facult,const sir& spec,const int an,const int gr):persoana(nume,prenume,sex) {numele=nume;prenumele=prenume; sexul=sex;facultatea=facult; specializarea=spec; anul=an; grupa=gr; } student::student() {numele="";prenumele="";sexul=0;facultatea=0;specializarea="";anul=0; grupa=0;} student::student(const persoana &pers,const sir& facult,const sir& spec,const int an,const int gr):persoana(pers) { numele=pers.nume();prenumele=pers.prenume(); facultatea=facult; specializarea=spec;anul=an;grupa=gr; } student::student(const student& stud):persoana(stud.numele,stud.prenumele,stud.sexul) { facultatea=stud.facultatea; specializarea=stud.specializarea; anul=stud.anul; grupa=stud.grupa; } student::~student() { cout<<"Destructor student!!\n"; } void student::afisare() { cout<>(istream &, student &); //METODE CLS. STUDENT_BURSIER /* student_bursier(student&,char); student_bursier(const student_bursier&);*/ student_bursier::student_bursier(const student &stud,char tip_bursa):student(stud) {tipul_bursei=tip_bursa;} student_bursier::student_bursier(const student_bursier &stud):student(stud.numele,stud.prenumele,stud.sexul,stud.facultatea,stud.specializarea,stud.anul,stud.grupa) {tipul_bursei=stud.tipul_bursei;} double student_bursier::valoare_bursa() { double val; switch (tipul_bursei) { case 'A':val=85000; break; case 'B':val=70000; break; } return val; } 6

MOSTENIRE student_bursier::~student_bursier() {cout<<"Desctructor cls. 3\n";} void student_bursier::afisare() { student::afisare(); cout<<"Tip bursa: "<>x2; cout<"Inf introduse:\n"; cout<<x2; getch(); x1.afisare(); cout<<'\n'; student s(x, "N.I.E.", "EA", 1, 2311); s.afisare();cout<<'\n'; getch(); student s1(s); cout<<s1<<'\n'; getch(); }

7

Related Documents