carte c 2003 - universitatea din craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia...

278
I CUPRINS Capitolul I INTRODUCERE ÎN ARHITECURA SISTEMELOR DE CALCUL ............................... 1 1.1. Importanţa limbajului C....................................................1 1.2 Arhitectura de bază a unui calculator ................................4 1.2.1 Microprocesorul .........................................................6 1.2.2 Memoria .....................................................................8 1.2.3 Echipamentele periferice ..........................................10 1.3. Programarea calculatorului .............................................13 1.3.1. Sistemul de operare .................................................14 1.3.2. Tipuri de fişiere .......................................................17 1.3.3. Construirea fişierului executabil .............................18 Capitolul II REPREZENTAREA DATELOR ÎN CALCULATOR................. 23 2.1. Reprezentarea internă/externă a numerelor...........................24 2.2. Reprezentarea externă a numerelor ......................................25 2.2.1. Reprezentarea externă a numerelor întregi..............26 2.2.2. Reprezentarea externă a numerelor reale ................29 2.3 Reprezentarea internă a numerelor ..................................31 2.3.1. Reprezentarea internă a numerelor întregi ..............31 2.3.2 Adunarea, scăderea şi înmulţirea numerelor întregi.33 2.3.3 Reprezentarea internă a numerelor reale ..................35 Game de reprezentare pentru numerele reale ....................46 2.3.5. Codificare BCD.......................................................47 Capitolul III ELEMENTELE DE BAZĂ ALE LIMABJULUI C...................... 49 3.1. Crearea şi lansarea în execuţie a unui program C...........49 3.2. Structura unui program C ...............................................51 3.3. Mulţimea caracterelor .....................................................53

Upload: others

Post on 15-Jan-2020

4 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

I

CUPRINS Capitolul I INTRODUCERE ÎN ARHITECURA SISTEMELOR DE CALCUL............................... 1

1.1. Importanţa limbajului C....................................................1 1.2 Arhitectura de bază a unui calculator ................................4

1.2.1 Microprocesorul .........................................................6 1.2.2 Memoria .....................................................................8 1.2.3 Echipamentele periferice..........................................10

1.3. Programarea calculatorului .............................................13 1.3.1. Sistemul de operare .................................................14 1.3.2. Tipuri de fişiere .......................................................17 1.3.3. Construirea fişierului executabil .............................18

Capitolul II REPREZENTAREA DATELOR ÎN CALCULATOR................. 23

2.1. Reprezentarea internă/externă a numerelor...........................24 2.2. Reprezentarea externă a numerelor......................................25

2.2.1. Reprezentarea externă a numerelor întregi..............26 2.2.2. Reprezentarea externă a numerelor reale ................29

2.3 Reprezentarea internă a numerelor ..................................31 2.3.1. Reprezentarea internă a numerelor întregi ..............31 2.3.2 Adunarea, scăderea şi înmulţirea numerelor întregi.33 2.3.3 Reprezentarea internă a numerelor reale..................35 Game de reprezentare pentru numerele reale....................46 2.3.5. Codificare BCD.......................................................47

Capitolul III ELEMENTELE DE BAZĂ ALE LIMABJULUI C...................... 49

3.1. Crearea şi lansarea în execuţie a unui program C...........49 3.2. Structura unui program C ...............................................51 3.3. Mulţimea caracterelor .....................................................53

Page 2: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

II

3.3.1. Litere şi numere.......................................................53 3.3.2. Caractere whitespace...............................................53 3.3.3. Caractere speciale şi de punctuaţie..........................54 3.3.4. Secvenţe escape.......................................................54

3.4. Identificatori ...................................................................56 3.5. Cuvintele cheie ale limbajului C.....................................56 3.6. Constante ........................................................................56

3.6.1. Constante caracter ...................................................57 3.6.2. Constante întregi .....................................................57 3.6.3. Constante în virgulă mobilă ....................................57 3.6.4. Constante şir............................................................58 3.6.5. Constanta zero .........................................................59 3.6.6. Obiecte constante ....................................................59 3.6.7. Enumerări ................................................................60

Capitolul IV OPERANZI ŞI OPERATORI ÎN C................................................ 61

4.1. Operanzi..........................................................................61 4.2. Operatori .........................................................................61

4.2.1. Operatori aritmetici .................................................62 4.2.2. Operatori de incrementare şi decrementare.............63 4.2.3. Operatori relaţionali ................................................63 4.2.4. Operatori logici .......................................................64 4.2.5. Operatori logici la nivel de bit.................................65 4.2.6. Operatorul de atribuire ............................................70 4.2.7. Operatorul sizeof .....................................................70 4.2.8. Operatorul ternar ? .................................................71 4.2.9. Operatorul virgulă ...................................................72 4.2.10. Operatorul de forţare a tipului sau de conversie

explicită (expresie cast) ........................................72 4.2.11. Operatorii paranteză ..............................................73 4.2.12. Operatorul adresă ..................................................73 4.2.13. Alţi operatori ai limbajului C ................................74 4.2.14. Regula conversiilor implicite şi precedenţa

operatorilor ...........................................................74

Page 3: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

III

Capitolul V INSTRUCŢIUNI .............................................................................. 76

5.1. Instrucţiuni etichetate (instrucţiunea goto) .....................76 5.2. Instrucţiuni expresie........................................................77 5.3. Instrucţiuni compuse.......................................................77 5.4. Instrucţiuni de selecţie ....................................................78

5.4.1. Instrucţiunea if.........................................................78 5.4.2. Instrucţiuni de selecţie multiplă: if - else if.............79 5.4.3. Instrucţiunea switch.................................................80

5.5. Instrucţiuni repetitive......................................................82 5.5.1. Instrucţiunea for ......................................................82 5.5.2. Instrucţiunea while ..................................................86 5.5.3. Instrucţiunea do-while.............................................87 5.5.4. Bucle încuibate........................................................89 5.5.5. Instrucţiunea break ..................................................91 5.5.6. Instrucţiunea continue .............................................92

Capitolul VI TIPURI DE DATE STRUCTURATE ............................................ 93

6.1. Tablouri unidimensionale ...............................................93 6.1.1. Constante şir............................................................94 6.1.2. Iniţializarea vectorilor de caractere .........................95 6.1.3. Funcţii pentru prelucrarea şirurilor (fişierul antet

string.h).................................................................97 6.2. Tablouri cu două dimensiuni (matrice).........................100

6.2.1. Iniţializarea matricelor ..........................................100 6.2.2. Tablouri bidimensionale de şiruri .........................101

6.3. Tablouri multidimensionale..........................................101 6.4. Structuri ........................................................................102

6.4.1. Tablouri de structuri ..............................................104 6.4.2. Introducerea structurilor în funcţii ........................110 6.4.3. Tablouri şi structuri în structuri.............................114

6.5. Uniuni ...........................................................................114 6.6. Enumerări .....................................................................116

Page 4: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

IV

Capitolul VII POINTERI ...................................................................................... 118

7.1. Operatori pointer...........................................................118 7.1.1. Importanţa tipului de bază.....................................120 7.1.2. Expresii în care intervin pointeri ...........................120

7.2. Pointeri şi tablouri.........................................................125 7.2.1. Indexarea pointerilor .............................................126 7.2.2. Pointeri şi şiruri .....................................................128 7.2.3. Preluarea adresei unui element al unui tablou.......129 7.2.4. Tablouri de pointeri...............................................129 7.2.5. Pointeri la pointeri.................................................130 7.2.6. Iniţializarea pointerilor..........................................131 7.2.7. Alocarea dinamică a memoriei..............................132 7.2.8. Pointeri la structuri ................................................134 7.2.9. Structuri dinamice liniare de tip listă ...................137

Capitolul VIII FUNCŢII ......................................................................................... 150

8.1. Forma generală a unei funcţii .......................................150 8.2. Reîntoarcerea dintr-o funcţie ........................................152 8.3. Valori returnate .............................................................153 8.4. Domeniul unei funcţii ...................................................154

8.4.1. Variabile locale .....................................................155 8.4.2. Parametri formali...................................................156 8.4.3. Variabile globale ...................................................157

8.5. Apelul funcţiilor............................................................161 8.6. Apelul funcţiilor având ca argumente tablouri .............163 8.7. Argumentele argc şi argv ale funcţiei main() ...............166 8.8. Funcţii care returnează valori neîntregi ........................168 8.9. Returnarea pointerilor ...................................................169 8.10. Funcţii de tip void.......................................................172 8.11. Funcţii prototip ...........................................................173 8.12. Funcţii recursive .........................................................174 8.13. Clase de memorare (specificatori sau atribute)...........176 8.14. Pointeri la funcţii ........................................................181

Page 5: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

V

Capitolul IX PREPROCESAREA ...................................................................... 183

9.1. Directive uzuale ............................................................183 9.2. Directive pentru compilare condiţionată ......................185 9.3. Modularizarea programelor .........................................189

Capitolul X INTRĂRI/IEŞIRI ........................................................................... 194

10.1. Funcţii de intrare şi ieşire - stdio.h ...............................194 10.2. Operaţii cu fişiere .......................................................197 10.3. Nivelul inferior de prelucrare a fişierelor ...................199

10.3.1. Deschiderea unui fişier........................................200 10.3.2. Scrierea într-un fişier...........................................204 10.3.3. Citirea dintr-un fişier...........................................206 10.3.4. Închiderea unui fişier...........................................208 10.3.5. Poziţionarea într-un fişier....................................208 10.3.6 Ştergerea unui fişier .............................................210 10.3.7. Exemple de utilizare a funcţiilor de intrare/ieşire de

nivel inferior .......................................................211 10.4. Nivelul superior de prelucrare a fişierelor ..................216

10.4.1. Funcţia fopen() ....................................................216 10.4.2. Funcţia fclose()....................................................218 10.4.3. Funcţiile rename() şi remove()...........................219 10.4.4. Funcţii de tratare a erorilor..................................219 10.4.5. Funcţii cu acces direct .........................................220 10.4.6. Funcţii pentru poziţionare ...................................221 10.4.7. Ieşiri cu format ....................................................223 10.4.8. Intrări cu format ..................................................226 10.4.9. Funcţii de citire şi scriere a caracterelor..............228

Capitolul XI UTILIZAREA ECRANULUI ÎN MOD TEXT ........................... 233

11.1. Setarea ecranului în mod text .....................................233 11.2. Definirea unei ferestre ................................................234 11.3. Ştergerea unei ferestre ................................................234 11.4. Deplasarea cursorului .................................................235

Page 6: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

VI

11.5. Setarea culorilor..........................................................235 11.6. Funcţii pentru gestiunea textelor ................................236

Capitolul XII UTILIZAREA ECRANULUI ÎN MOD GRAFIC....................... 240

12.1. Iniţializarea modului grafic.........................................240 12.2. Gestiunea culorilor......................................................242 12.3. Setarea ecranului.........................................................244 12.4. Utilizarea textelor în mod grafic.................................244 12.5. Gestiunea imaginilor...................................................246 12.6. Desenarea şi colorarea figurilor geometrice ...............247

Capitolul XIII FUNCŢII MATEMATICE............................................................ 253

13.1 Funcţii trigonometrice .................................................253 13.2 Funcţii trigonometrice inverse .....................................254 13.3 Funcţii hiperbolice .......................................................254 13.4 Funcţii exponenţiale şi logaritmice..............................254 13.5 Generarea de numere aleatoare....................................255 13.6 Alte tipuri de funcţii matematice .................................256

Capitolul XIV ELEMENTE DE PROGRAMARE AVANSATĂ ....................... 257

14.1 Gestionarea memoriei ..................................................257 14.1.1 Memoria convenţională........................................257 14.1.2 Memoria expandată ..............................................260 14.1.3 Memoria extinsă ...................................................260 14.1.4 Stiva......................................................................260

14.2 Servicii DOS şi BIOS ..................................................261 14.2.1 Serviciile BIOS ....................................................262 14.2.2 Serviciile DOS......................................................266

14.3 Bibliotecile C ...............................................................270 14.3.1 Reutilizarea unui cod obiect .................................270 14.3.2 Lucrul cu fişiere bibliotecă...................................270

14.3 Fişierele antet...............................................................271

BIBLIOGRAFIE ............................................................................ 272

Page 7: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

1

Capitolul I

INTRODUCERE ÎN ARHITECURA SISTEMELOR DE CALCUL

1.1. Importanţa limbajului C

Creat în anul 1972 de programatorii de sistem Dennis M. Ritchie şi Brian W. Kernighan de la Bell Laboratories cu scopul de a asigura implementarea portabilă a sistemului de operare UNIX, C-ul este astăzi unul din cele mai cunoscute şi puternice limbaje de programare. Eficient, economic şi portabil, C-ul este o alegere bună pentru realizarea oricărui tip de programe, de la editoare de texte, jocuri cu facilităţi grafice, programe de gestiune şi pentru calcule ştiinţifice, până la programe de sistem, constituind unul dintre cele mai puternice instrumente de programare.

Adesea referit ca limbaj portabil, C-ul permite transferul programelor între calculatoare cu diferite procesoare şi în acelaşi timp facilitează utilizarea caracteristicilor specifice ale maşinilor particulare, programele scrise în C fiind considerate cele mai portabile.

Dacă evoluţia limbajelor de programare a adus în prim plan nume ca FORTRAN, LISP, COBOL, ALGOL-60 sau PASCAL, unele cu răspândire mai mult ”academică” – fiind folosite pentru a prezenta conceptele de bază sau conceptele avansate de programare – ca de pildă ALGOL-60 sau PASCAL, altele cu răspândire industrială masivă – ca de pildă FORTRAN şi COBOL – limbajul C a pătruns mai lent, dar foarte sigur. Realitatea arată clar că, în momentul de faţă, piaţa producătorilor de programe este dominată net de C şi de variantele evoluate ale acestuia.

Elementele principale care au contribuit la succesul C-ului sunt următoarele:

- modularizarea programelor – ce dă posibilitatea unui singur programator să stăpânească relativ uşor programe de zeci de mii de linii de sursă;

Page 8: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

2

- capacitatea de programare atât la nivel înalt cât şi la nivel scăzut – ceea ce dă posibilitatea utilizatorului de a programa fie fără a ”simţi” sistemul de operare şi maşina de calcul, fie la un nivel apropiat de sistemul de operare ceea ce permite un control foarte bun al eficienţei programului din punct de vedere viteză/memorie;

- portabilitatea programelor – ce permite utilizarea programelor scrise în C pe o mare varietate de calculatoare şi sisteme de operare;

- facilităţile de reprezentare şi prelucrare a datelor – materializate printr-un număr mare de operatori şi funcţii de bibliotecă ce fac programarea mult mai uşoară.

Prin anii ’80 interesul pentru programarea orientată pe obiecte a crescut, ceea ce a condus la apariţia de limbaje care să permită utilizarea ei în scrierea programelor. Limbajul C a fost dezvoltat şi el în această direcţie şi în anul 1980 a fost dat publicităţii limbajul C++, elaborat de Bjarne Stroustrup de la AT&T. La ora actuală, majoritatea limbajelor de programare moderne au fost dezvoltate în direcţia programării orientate pe obiecte.

Limbajul C++, ca şi limbajul C, se bucură de o portabilitate mare şi este implementat pe o gamă largă de calculatoare începând cu microcalculatoare şi până la cele mai mari supercalculatoare.

Limbajul C++ a fost implementat pe microcalculatoarele compatibile IBM PC în mai multe variante. Cele mai importante implementări ale limbajelor C++ pe aceste calculatoare sunt cele realizate de firmele Microsoft şi Borland.

Conceptele programării orientate pe obiecte au influenţat în mare măsură dezvoltarea limbajelor de programare în ultimul deceniu. De obicei, multe limbaje au fost extinse astfel încât ele să admită conceptele mai importante ale programării orientate pe obiecte. Uneori s-au făcut mai multe extensii ale aceluiaşi limbaj. De exemplu, limbajul C++ are ca extensii limbajul E ce permite crearea şi gestiunea obiectelor persistente, lucru deosebit de important pentru sistemele de gestiune a bazelor de date, limbajul O ce încearcă să îmbine facilităţile de nivel înalt cu cele ale programării de sistem, limbajul Avalon/C++ destinat calculului distribuit, şi nu în ultimul rând binecunoscutul de acum limbaj Java, specializat în aplicaţii Internet.

Interfeţele utilizator au atins o mare dezvoltare datorită facilităţilor oferite de componentele hardware ale diferitelor calculatoare. În principiu, ele simplifică interacţiunea dintre programe

Page 9: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

3

şi utilizatorii acestora. Astfel, diferite comenzi, date de intrare sau rezultate pot fi exprimate simplu şi natural utilizând diferite standarde care conţin ferestre, bare de meniuri, cutii de dialoguri, butoane, etc. Cu ajutorul mouse-ului toate acestea pot fi accesate extrem de rapid şi uşor fără a mai fi nevoie să cunoşti şi să memorezi o serie întreagă comenzi ale sistemului de operare sau ale limbajului de programare. Toate acestea conduc la interfeţe simple şi vizuale, accesibile unui segment foarte larg de utilizatori.

Implementarea interfeţelor este mult simplificată prin utilizarea limbajelor orientate spre obiecte, aceasta mai ales datorită posibilităţii de a utiliza componente standardizate aflate în biblioteci specifice. Importanţa aplicării conceptului de reutilizare a codului rezultă din faptul că interfeţele utilizator adesea ocupă 40% din codul total al aplicaţiei.

Firma Borland comercializează o bibliotecă de componente standardizate care pot fi utilizate folosind limbajul C++, bibliotecă cunoscută sub numele Turbo Vision.

De obicei, interfeţele utilizator gestionează ecranul în mod grafic. O astfel de interfaţă utilizator se numeşte interfaţă utilizator grafică. Una din cele mai populare interfeţe utilizator grafice pentru calculatoarele IBM PC este produsul Windows oferit de firma Microsoft.

Windows este un mediu de programare ce amplifică facilităţile oferite de sistemul de operare MS-DOS. Aplicaţiile Windows se pot dezvolta folosind diferite medii de dezvoltare ca: Turbo C++ pentru Windows, Pascal pentru Windows, Microsoft C++, Microsoft Visual Basic, Visual C şi Visual C++.

Componentele Visual permit specificarea în mod grafic a interfeţei utilizator, a unei aplicaţii, folosind mouse-ul, iar aplicaţia propriu-zisă se programează într-un limbaj de tip Basic, C sau C++.

Dacă în ani ’70 se considera că o persoană este rezonabil să se poată ocupa de o aplicaţie de 4-5 mii de instrucţiuni, în prezent, în condiţiile folosirii limbajelor de programare orientate pe obiecte, această medie a ajuns la peste 25 de mii de instrucţiuni.

Un limbaj de programare trebuie privit nu doar la suprafaţa sa – sintaxă şi mod de butonare a calculatorului pentru o implementare particulară – ci mai ales în profunzime, prin conceptele pe care se bazează, prin stilul de programare, prin modul de structurare a aplicaţiei şi, implicit, a programului, prin filozofia de rezolvare a

Page 10: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

4

problemelor folosind limbajul. Din aceste puncte de vedere, C-ul nu poate lipsi din cultura unui programator, iar pentru un profesionist C-ul este, şi mai mult, o necesitate vitală, acesta fiind piatra de temelie pentru înţelegerea şi utilizarea eficientă a limbajelor de nivel înalt orientate pe obiecte şi Visual.

1.2 Arhitectura de bază a unui calculator

Calculatoarele de tip PC (calculatoare personale) reprezintă cele

mai răspândite şi mai utilizate dintre calculatoare, datorită gradului de accesibilitate şi preţului relativ scăzut. Indiferent de tipul calculatorului, modul general de concepţie, de alcătuire şi funcţionare este acelaşi. Calculatorul este o maşină programabilă. Două dintre principalele caracteristici ale unui calculator sunt:

1. Răspunde la un set specific de instrucţiuni într-o manieră bine definită.

2. Calculatorul poate executa o listă preînregistrată de instrucţiuni, numită program.

Calculatoarele moderne sunt electronice şi numerice. � Partea de circuite electrice şi electronice precum şi conexiunile

fizice dintre ele se numeşte hardware. � Totalitatea programelor precum şi datele aferente acestor

programe poartă denumirea de software.

Echipamente de stocare date (HDD, FDD, CD-ROM, etc.)

Echipamente de intrare

Echipamente de ieş ire

UPC Unitatea de procesare

ş i control

Fig.1.1 Configuraţia standard pentru utilizator

Page 11: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

5

Partea hardware a unui calculator este formată din totalitatea componentelor sale fizice. Toate calculatoarele de uz general necesită următoarele componente hardware:

� memorie: Permite calculatorului să stocheze, cel puţin temporar, date şi programe.

� dispozitive de stocare externe: Permit calculatoarelor să stocheze permanent programe şi mari cantităţi de date. Cele mai uzuale dispozitive de stocare externă sunt HDD (hard disk drives), FDD (floppy disk drive) şi CD-ROM (Compact Disk-Read Only Memory) sau CD-R/W (Compact Disk-Read/Write).

� dispozitive de intrare : În mod uzual sunt reprezentate de tastatură (keyboard) şi de mouse. Aceste dispozitive reprezintă calea uzuală de introducere a datelor şi instrucţiunilor care gestionează funcţionarea unui calculator.

� dispozitive de ieşire: Reprezintă modalitatea prin care calculatorul transmite utilizatorului uman rezultatele execuţiei programelor. Ecranul monitorului sau imprimanta sunt astfel de dispozitive uzuale.

� unitatea de procesare şi control (UPC) : Este partea principală a unui calculator deoarece este componenta care execută instrucţiunile. În mod uzual această unitate de procesare şi control este reprezentată de un microprocesor care se plasează pe placa de bază (mainboard) a calculatorului împreună cu memoria internă RAM. În plus faţă de aceste componente orice calculator este prevăzut cu o magistrală (bus) prin care se gestionează modalitatea de transmitere a datelor între componentele de bază ale calculatorului. Magistrala reprezintă o colecţie de trasee electrice care leagă microprocesorul de dispozitivele de intrare/ieşire şi de dispozitivele interne/externe de stocare a datelor. Putem distinge magistrala de date, magistrala de adrese şi magistrala de comandă şi control.

În figura 1.2 este prezentată interacţiunea dintre componentele HARD principale ale unui calculator.

Calculatoarele pot fi în general clasificate după dimensiuni sau putere de calcul. Nu se poate face însă la ora actuală o distincţie netă între următoarele categorii de calculatoare: PC (Personal Computer): Un calculator de dimensiuni mici, monoutilizator (single-user), bazat pe un microprocesor. În plus

Page 12: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

6

acesta este dotat standard cu tastatură, mouse, monitor şi dispozitive periferice de stocare a datelor.

Memoria secundară

Memoria principală

UNITATEA CENTRALĂ

Echipament de intrare

Echipament de ieşire

Fig. 1.2 Arhitectura minimală a unui sistem de calcul

� staţii de lucru (workstation): Un calculator monoutilizator de

mare putere. Aceasta este asemănător unui PC dar are un microprocesor mai puternic şi un monitor de înaltă calitate (rezoluţie mai mare).

� minicalculator (minicomputer): Un calculator multiutilizator (multi-user) capabil să lucreze simultan cu zeci sau chiar sute de utilizatori.

� mainframe: Un calculator multiutilizator capabil să lucreze simultan cu sute sau chiar mii de utilizatori.

� supercomputer: Un computer extrem de rapid care poate executa sute de milioane de operaţii într-o secundă.

1.2.1 Microprocesorul Microprocesorul este cea mai importantă şi cea mai scumpă

componentă a unui calculator de performanţele acesteia depinzând în mare măsură rezultatele întregului sistem.

Din punct de vedere fizic, microprocesorul este un cip ce conţine un circuit integrat complex ce îi permite să prelucreze informaţii prin executarea unor operaţii logice şi matematice diverse (adunări, scăderi, înmulţiri, împărţiri, comparări de numere).

El este compus din două părţi importante: unitatea de execuţie (EU – Execution Unit) şi unitatea de interfaţă a magistralei de date (BIU – Bus Interface Unit). Prima componentă realizează efectiv

Page 13: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

7

operaţiile, iar cea de-a doua are funcţia de transfer a datelor de la şi înspre microprocesor.

Microprocesorul reprezintă de fapt unitatea centrală a unui calculator şi îndeplineşte o serie de activităţi specifice cum ar fi: execută operaţii aritmetice şi logice, decodifică instrucţiuni speciale, transmite altor cipuri din sistem semnale de control. Toate aceste operaţii sunt executate cu ajutorul unor zone de memorie ale microprocesorului, numite registre. Orice microprocesor are un set finit de instrucţiuni pe care le recunoaşte şi pe care le poate executa.

Calculatoarele IBM PC folosesc procesoare INTEL sau compatibile, realizate de alte companii cum ar fi: AMD, NexGen, CYRIX. Numele microprocesorului este folosit la identificarea calculatorului. Se folosesc frecvent expresii de tipul calculator 386, calculator 486, calculator Pentium II, etc.

În 1971 firma Intel a fost abordata de o companie Japoneza, acum dispărută, pentru a construi un circuit dedicat pentru un nou calculator. Designerul Ted Hoff a propus o soluţie programabilă, de uz general, şi astfel s-a născut circuitul Intel 4004. Au urmat la scurt timp chipurile 4040 si 8008 dar lor le lipseau multe din caracteristicile microprocesoarelor aşa cum le ştim noi azi. În 1974 Intel a prezentat pentru prima oară circuitul Intel 8080 care a fost folosit in sistemele Altair şi IMSAI. Curând după aceea au apărut procesoarele Motorola 6800 şi 6502 de la MOS Technology. Doi dintre proiectanţii de la Intel au părăsit firma, creând corporaţia ZILOG care a produs chipul Z80 (compatibil cu 8080 dar cu set de instrucţiuni mai puternic şi de două ori mai rapid.

Cipul Intel 4004 a fost primul procesor comercial, lansat la sfârşitul anului 1971. La un preţ de circa 200$ şi înglobând 2300 de tranzistori, cipul 4004 dezvolta mai multă putere de calcul decât ENIAC, primul calculator electronic, cu 25 de ani în urma. Faţă de cele 18.000 de tuburi cu vacuum ce ocupau 900 metri cubi, procesorul 4004 putea dezvolta 60.000 de operaţii pe secundă. Această invenţie a contribuit la revoluţionarea domeniilor de aplicaţii ale computerelor, dând startul unui adevărat galop de inovaţii tehnologice. Următorul pas a fost în 1980, când IBM a inclus un procesor Intel în arhitectura primului PC.

Astăzi PC-urile sunt pretutindeni în jurul nostru. Un copil care lucrează la o maşina ce incorporează un procesor Pentium Pro beneficiază de mult mai multă putere de calcul decât dispunea

Page 14: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

8

guvernul SUA în perioada lansării primelor echipaje umane către Lună.

Într-un număr aniversar al publicaţiei Communications of the ACM, Gordon Moore, co-fondator al companiei Intel, era optimist în privinţa evoluţiei PC-urilor şi a microprocesoarelor: "complexitatea microprocesoarelor, care se măsoară prin numărul de tranzistori pe cip, s-a dublat aproape constant la fiecare 18 luni, de la apariţia primului prototip 4004. Aceasta evoluţie exponenţială a determinat o continuă creştere a performanţelor PC-urilor şi o scădere a costului procesului de calcul. Pe când în 1991 un PC bazat pe procesorul Intel 486 costa aproape 225$ pentru o performanţă de un milion de instrucţiuni pe secundă (MIPS), astăzi, un sistem desktop ce utilizează un cip Pentium Pro este evaluat la circa 7$ pe MIPS. Nu se întrevede nici o dificultate care să frâneze această rată de dezvoltare".

1.2.2 Memoria Microprocesorul are capacitatea de a memora date care urmează

a fi prelucrate, cât şi rezultatele intermediare. Se observă că rolul său principal este de a prelucra şi transmite informaţiile şi rezultatele şi deci capacitatea sa de memorare este mică neputând stoca programe. De aceea, un calculator necesită şi o memorie care să găzduiască date şi programe.

Memoria este formată din punct de vedere fizic din cipuri ce stochează informaţia sub forma a două niveluri de tensiune ce corespund valorilor 0 şi 1 din sistemul de numeraţie. Celulele de bază ale memoriei (ce pot avea valoarea 0 sau 1) se numesc biţi şi ele reprezintă particulele cele mai mici de informaţie din calculator. Pentru citirea informaţiilor nu se folosesc biţi în mod individual ci aceştia sunt grupaţi într-o succesiune. Astfel o succesiune de 8 biţi formează un octet (sau un byte) aceasta reprezentând unitatea de măsură a capacităţii de memorie. Deoarece reprezentarea numerelor în calculator se face în baza 2 şi nu în baza 10, aşa cum suntem obişnuiţi în mod normal să lucrăm, şi multiplii unui byte vor fi puteri ale lui 2, astfel:

1 KB=210B=1024 B 1 MB=210KB=1 048 576 B 1 GB=210MB=230 B Abrevierile K (kilo), M (mega), G (giga) se scriu de obicei cu

litere mari şi reprezintă mii, milioane şi, respectiv, miliarde.

Page 15: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

9

Memoria unui calculator are două componente: memoria principală (internă) şi memoria secundară (externă). Memoria internă este memoria ce poate fi accesată în mod direct de către microprocesor şi în care sunt încărcate programele înainte de a fi executate de către microprocesor. Dacă primele procesoare puteau accesa doar 1 MB de memorie astăzi un procesor Pentium poate accesa peste 256 MB. Memoria principală este formată din două tipuri de circuite: cip-uri ROM şi cip-uri RAM.

Circuitele de tip ROM (Read Only Memory) au memorate programele care controlează iniţial calculatorul (sistemul de operare). Aceste memorii pot fi doar citite (conţinutul lor nu poate fi modificat). Înscrierea conţinutului acestor memorii se face de către fabricant, iar operaţiunea de înscriere cu programe se mai numeşte „arderea memoriilor”.

Circuitele de tip RAM (Random Acces Memory ) sunt memorii la care utilizatorul are acces şi al căror conţinut se şterge la deconectarea calculatorului. În memoria RAM informaţia este stocată temporar. De exemplu, programele de aplicaţii curente şi datele asociate acestor aplicaţii sunt încărcate în memoria RAM înainte de a fi prelucrate de către microprocesor.

Deoarece capacitatea de memorie a unui calculator nu poate fi atât de mare încât să poată păstra toate programele pe vrem să le executăm, a apărut necesitatea existenţei unor memorii externe, care să fie solicitate la nevoie. Rolul acestora îl joacă discurile şi ele pot fi asemănate cu cărţile dintr-o bibliotecă pe care le putem consulta ori de câte ori avem nevoie de anumite informaţii.

Primele discuri apărute pentru PC-uri, numite şi dischete, floppy disk-uri sau discuri flexibile, permiteau stocarea a maximum 160 KB de informaţie. Astăzi mai există doar dischete cu diametrul de 3.5 inch cu capacitatea de 1,44 MB. Existenţa acestora pare a fi pusă însă în pericol de apariţia CD-urilor reinscriptibile a căror capacitate de memorare depăşeşte 700 MB iar evoluţia tehnologică, din ce în ce mai rapidă, dă semne că lucrurile nu se vor opri aici.

Dischetele folosesc metode magnetice de memorare a informaţiei motiv pentru care ele se mai numesc şi suporturi magnetice de informaţie.

Principala componentă a unui calculator utilizată pentru memorarea programelor o reprezintă hard discul. Acesta poate fi asemănat cu o dischetă de mare capacitate, integrată într-o unitate

Page 16: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

10

încapsulată. Iniţial, puţine PC-uri prezentau hard discuri, dar cum preţurile acestora au scăzut considerabil, iar performanţele şi capacităţile au crescut, în prezent toate calculatoarele prezintă acest dispozitiv. În clipa de faţă, capacitatea de memorare a unui hard disc a depăşit valoarea de 40 de GB.

1.2.3 Echipamentele periferice Comunicarea om-maşină se realizează cu ajutorul

echipamentelor periferice prin intermediul cărora utilizatorul poate programa sau da anumite comenzi calculatorului sau poate vizualiza rezultatele obţinute de către anumite programe. Principalele echipamente periferice ale unui calculator sunt următoarele: tastatura, mouse-ul, scanner-ul, monitorul şi imprimanta. Ele pot fi grupate în echipamente de intrare – cele prin care calculatorul primeşte informaţii sau comenzi (tastatură, mouse, scanner) - şi echipamente de ieşire – cele prin care calculatorul transmite informaţii în exterior (monitor, imprimantă). În continuare sunt prezentate câteva caracteristici ale fiecărui echipament.

Tastatura – este principalul dispozitiv de intrare al calculatorului prin intermediul căruia se transmit comenzi către unitatea centrală. Cuplarea la calculator a tastaturii se face prin intermediul unui cablu de conectare.

Din punct de vedere al dispunerii tastelor, tastatura se aseamănă destul de mult cu cea a unei maşini de scris dar are şi părţi care o individualizează.

Primele tastaturi au avut 83/84 de taste, pentru ca, ulterior, ele să fie îmbogăţite prin dublarea tastelor existente sau adăugarea altora noi, ajungându-se la 101/102 taste.

Din punct de vedere al funcţionalităţii lor ele pot fi împărţite în patru categorii:

- taste alfanumerice; - taste cu scopuri speciale; - taste direcţionale şi numerice; - taste funcţionale.

Tastele alfanumerice conţin literele, cifrele şi semnele de punctuaţie şi ocupă partea centrală a tastaturii. Acţionarea unei astfel de taste determină apariţia caracterului corespunzător pe ecranul calculatorului. Tastele cu scopuri speciale sunt aşezate în acelaşi bloc cu tastele alfanumerice şi determină efectuarea anumitor acţiuni fără

Page 17: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

11

înscrierea de caractere pe ecran. Tastele de mişcare se află situate în partea dreaptă a tastaturii şi ele funcţionează în două moduri, care pot fi comutate prin acţionarea altei taste, aflate deasupra lor, pe care scrie NumLock. Dacă ledul corespunzător acestei taste este aprins, modul de lucru este numeric, în caz contrar fiind comutat pe semnificaţia direcţională. Tastele funcţionale sunt un grup de 12 taste situate în partea de sus a tastaturii având pe ele litera F urmată de un număr între 1 şi 12. Acţionarea acestor taste determină efectuarea unor operaţii specifice de la program la program.

Mouse-ul – este tot un echipament de intrare mai uşor de manevrat decât tastatura dar care poate efectua mai puţine operaţii. Totuşi, foarte multe aplicaţii (în special aplicaţiile grafice) nu mai pot fi concepute fără mouse. Un mouse are aspectul unei bucăţi de săpun, uşor manevrabil, având dedesubt o bilă poziţionabilă, cu sensibilitate şi viteză reglabile.

Mişcarea maouse-ului pe o suprafaţă plană este corelată cu deplasarea pe ecran a unui cursor cu o formă deosebită: cruciuliţă, săgeată, etc. Declanşarea unei acţiuni se face prin poziţionarea cursorului în zona corespunzătoare şi apăsarea unuia dintre butoanele aflate pe partea posterioară. Iniţial un mouse avea două sau trei butoane. Acum există mouse-uri cu 5 butoane şi 2 rotiţe ce îndeplinesc o serie de funcţii corespunzătoare unor taste speciale.

Folosirea mouse-ului uşurează mult munca utilizatorilor, nemaifiind necesar ca aceştia să memoreze numărul relativ mare de comenzi corespunzător fiecărui produs, ca în situaţia în care se foloseşte numai tastatura. Utilitatea mouse-ului este şi mai evidentă în cazul aplicaţiilor grafice. De altfel, WINDOWS este un sistem de operare creat special pentru lucrul cu mouse-ul.

Scanner-ul – reprezintă dispozitive care se cuplează la un PC şi cu care, prin intermediul unui software adecvat, se pot capta imagini, fotografii, texte etc., în vederea unei prelucrări ulterioare. Astfel se pot manevra imagini foto, se pot crea efecte grafice speciale, care nu se pot obţine prin metode tradiţionale. După captare, imaginea poate fi prelucrată, mutată, mărită, micşorată, rotită, colorată, umbrită, suprapusă cu altă imagine etc.

Cu un software de recunoaştere optică a caracterelor datele sau documentele tipărite pe coli de hârtie pot fi transformate în fişiere, putându-se realiza chiar o stocare a lor sub formă de arhivă.

Page 18: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

12

Monitorul - Sistemul video este format din două părţi: un adaptor (placă) video şi un monitor sau display. Adaptorul video reprezintă dispozitivul care realizează legătura (interfaţa) cu calculatorul şi se află în interiorul acestuia. El va fi corespunzător tipului de monitor video care îi este ataşat. Adaptorul video realizează o rezoluţie orizontală şi una verticală. Rezoluţia reprezintă numărul de elemente, în cazul de faţă puncte – pixeli – care pot fi afişate pe ecran. De exemplu, un monitor VGA, în mod video, are o rezoluţie de 640 x 480 pixeli.

Standardul VGA (Video Graphics Array) a fost introdus de către IBM o dată cu calculatoarele PS/2, iar modurile video VGA reprezintă un superset al standardelor video anterioare, CGA (Color Graphics Adapter) şi EGA (Enhanced Graphics Adapter). Cea mai importantă îmbunătăţire adusă de standardul VGA a fost rezoluţia superioară a caracterelor în modul text, precum şi posibilitatea de a afişa 256 de culori la un moment dat.

Monitorul, denumit uneori şi display, permite vizualizarea datelor introduse de la tastatură sau rezultate în urma execuţiei unor comenzi sau programe, fiind încadrat în categoria echipamentelor periferice de ieşire. Ca piesă principală, monitorul conţine un tub de vacuum, similar cu cel de la televizor şi trei tunuri de electroni (corespunzătoare celor trei culori fundamentale).

Standardele video MDA, CGA şi EGA folosesc monitoare digitale. Datele care descriu culorile pixelilor sunt trimise de adaptorul video la monitor sub forma unor serii de semnale digitale care sunt echivalente unor serii de biţi.

Standardul VGA a introdus un nou tip de monitor care utilizează semnale analogice pentru transferul informaţiilor privind culoarea de la adaptorul video la monitor. Dacă semnalele digitale prezintă niveluri care indică prezenţa sau absenţa unui bit, semnalele analogice pot prezenta orice valoare între una minimă şi una maximă.

Imprimanta - Reprezintă un dispozitiv care poate fi ataşat unui calculator, cu scopul tipăririi de texte şi grafică, putând fi considerată un fel de maşină de scris automată. Până în prezent au fost realizate un număr destul de mare de tipuri de imprimante pentru PC-uri, ele diferind atât prin performanţe, cât şi prin modalităţile tehnice de ralizare. Fiecare dintre ele prezintă avantaje şi dezavantaje, ideal fiind a o folosi pe cea care corespunde cel mai bine tipului de lucrări

Page 19: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

13

executate. În funcţie de modul în care este realizată imprimarea se disting următoarele tipuri de imprimante:

- imprimante matriceale cu 9, 18 sau 24 de ace – realizează imprimarea prin impactul acelor peste o bandă de hârtie;

- imprimante cu jet de cerneală – funcţionează prin pulverizarea fină a unor picături de cerneală pe hârtia de imprimat;

- imprimante laser – ce utilizează o rază laser sau mici diode luminiscente care încarcă electrostatic un tambur de imprimare, corespunzător caracterului care urmează a fi imprimat;

- imprimante rapide de linii – ce imprimă mai multe linii odată, fiind folosite mai mult la sisteme de calcul de dimensiuni mari.

Calitatea imprimării creşte de la primul la ultimul tip prezentat, dar în mod corespunzător şi preţul echipamentului.

1.3. Programarea calculatorului

Programele de calculator, cunoscute sub numele de software,

sunt constituite dintr-o serie de instrucţiuni pe care le execută calculatorul. Când se creează un program, trebuie specificate instrucţiunile pe care calculatorul trebuie să le execute pentru a realiza operaţiile dorite. Procesul de definire a instrucţiunilor pe care le execută calculatorul se numeşte programare.

Programele executate pe un calculator pot fi împărţite în trei categorii:

• programe de aplicaţie – sunt acele programe care interacţionează direct cu utilizatorul, specializate în realizarea unei categorii de prelucrări. Editoarele de texte, programele pentru gestiunea bazelor de date, programele de tehnoredactare asistată de calculator, de grafică etc. sunt programe de aplicaţie.

• utilitare – programe, care la fel ca programele de aplicaţie, interacţionează direct cu utilizatorul, dar, spre deosebire de acestea, realizează prelucrări de uz general. Utilitarele realizează o serie de operaţii de „gospodărie” cum ar fi: copierea fişierelor, pregătirea discurilor magnetice pentru utilizare, crearea de copii de salvare, testarea echipamentului, etc.

• programe de sistem – realizează legătura între componentele electronice ale calculatorului şi programele de aplicaţie şi utilitare. Rolul programului de sistem este acela de a uşura sarcina programatorului, simplificând îndeplinirea acelor sarcini care sunt

Page 20: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

14

comune marii majorităţi a programelor de aplicaţie: alocarea memoriei, afişarea caracterelor pe ecran şi la imprimantă, citirea caracterelor de la tastatură, accesul la informaţiile stocate pe disc magnetic, etc.

1.3.1. Sistemul de operare Sistemul de operare este o parte componentă a software-ului

unui calculator, care mai cuprinde un număr variabil de programe utilitare selectate conform cu necesităţile programatorilor.

Sistemul de operare este un program cu funcţii de coordonare şi control asupra resurselor fizice ale calculatorului şi care intermediază dialogul om-calculator. Sistemul de operare permite rularea programelor şi păstrarea informaţiilor pe disc. În plus, fiecare sistem de operare pune la dispoziţia aplicaţiilor o serie de servicii care permit programelor să aloce memorie, să acceseze diferite echipamente periferice, cum ar fi imprimanta, şi să gestioneze alte resurse ale calculatorului.

Un sistem de operare trebuie să aibă capacitatea de a se adapta rapid la modificările tehnologice, rămânând în acelaşi timp compatibil cu hardware-ul anterior. Lanţul de comunicare utilizator – calculator este prezentat în Figura 1.3:

Sistemul de operare este cel mai important program care rulează pe un calculator. Orice calculator de uz general este dotat cu un sistem de operare care permite execuţia altor programe. Sistemele de operare execută operaţiuni de bază precum: recunoaşterea unei intrări de la tastatură (preluare caracter), trimiterea unui caracter pentru afişare pe ecranul monitorului, gestionarea fişierelor şi a directoarelor pe disc (floppy-disk sau hard-disk), controlul fluxului de date cu echipamentele periferice ca drivere de disc sau imprimante.

U TILIZATO R

APLIC AŢ II

SISTE M D E O PER AR E

C ALC U LATO R

Fig. 1.3. Comunicarea utilizator - calculator

Page 21: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

15

Sistem de operare

Aplicaţie

Tastaturã Imprimantã

Monitor

Mouse

Disk-drive

Fig. 1.4 Rolul sistemului de operare

Sistemul de operare al unui calculator este partea de software necesară şi suficientă pentru execuţia oricăror alte aplicaţii dorite de utilizator. Un calculator nu poate funcţiona decât sub gestiunea unui sistem de operare. Orice aplicaţie lansată în execuţie de către un utilizator apelează la resursele puse la dispoziţie de către sistemul de operare. Sistemul de operare interfaţează calculatorul cu operatorul uman de o manieră cât mai transparentă cu putinţă astfel încât utilizatorul nu trebuie să facă eforturi mari de adaptare dacă lucrează cu arhitecturi hardware diferite.

Pentru sisteme mai mari, sistemele de operare au responsabilităţi şi capabilităţi şi mai mari. Ele acţionează ca un gestionar al traficului de date şi al execuţiei programelor. În principal sistemul de operare asigură ca diferite programe şi diferiţi utilizatori să nu interfereze unele cu altele. Sistemul de operare este de asemenea responsabil cu securitatea, asigurând inaccesibilitatea persoanelor neautorizate la resursele sistemului.

Sistemele de operare se pot clasifica după cum urmează: � multi-user: Permit ca doi sau mai mulţi utilizatori să ruleze în

acelaşi timp programe (utilizatori concurenţi). Anumite sisteme de operare permit sute sau chiar mii de utilizatori concurenţi.

� multiprocesor: Permit execuţia unui program pe mai mult de un microprocesor.

� multitasking: Permit mai multor programe să ruleze în acelaşi timp (execuţie concurentă).

Page 22: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

16

� multithreading: Permit diferitelor părţi ale unui program să fie executate concurent.

� timp real (real time): Răspund instantaneu la diferite intrări. Sistemele de operare de uz general, ca DOS sau UNIX nu sunt sisteme de operare de timp real.

Sistemele de operare furnizează o platformă software pe baza căreia alte programe, numite programe de aplicaţie, pot rula (pot fi executate). Programele de aplicaţie trebuie să fie scrise pentru a rula pe baza unui anumit sistem de operare. Alegerea unui anumit sistem de operare determină în consecinţă mulţimea aplicaţiilor care pot fi rulate pe calculatorul respectiv. Pentru PC-uri, cele mai populare sisteme de operare sunt DOS, OS/2 sau Windows, dar mai sunt disponibile şi altele precum Linux.

Ca utilizator se interacţionează cu sistemul de operare prin intermediul unor comenzi. Spre exemplu, sistemul de operare DOS acceptă comenzi precum COPY sau RENAME pentru a copia fişiere sau pentru a le redenumi. Aceste comenzi sunt acceptate şi executate de o parte a sistemului de operare numită procesor de comenzi sau interpretor de linie de comandă.

Interfaţele grafice cu utilizatorul (GUI, Graphical user interfaces) permit introducerea unor comenzi prin selectarea şi acţionarea cu mouse-ul a unor obiecte grafice care apar pe ecran. Spre exemplu, sistemul de operare Windows are un desktop ca intefaţă garfică cu utilizatorul. Pe acest desktop (birou) se află diferite simboluri grafice (icoane, icons) ataşate diferitelor aplicaţii disponibile pe calculatorul respectiv. Utilizatorul are multiple posibilităţi de configurare a acestei intefeţe grafice.

Primul sistem de operare creat pentru calculatoare a fost CP/M (Control Program for Microcomputers), realizat pentru calculatoarele pe 8 biţi. O dată cu perfecţionarea componentelor HARD s-a impus şi necesitatea dezvoltării unui SOFT adecvat. Astfel, în 1981, a apărut prima versiune a sistemului de operare MS-DOS. Sistemul de operare MS–DOS (MicroSoft Disk Operating System) este destinat gestionării resurselor software si hardware ale microcalculatoarelor cu o arhitectura de tip IBM – PC sau compatibilă cu aceasta şi echipate cu procesoare 8086 sau 80x86, Pentium. Odată cu creşterea capabilităţilor hardware ale calculatoarelor, acesta s-a transformat, prin dezvoltări succesive, în Windows.

Page 23: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

17

Indiferent de sistemul de operare utilizat, din punctul de vedere al utilizatorului, informaţiile sunt scrise pe disc sub forma unor fişiere. Un fişier este o colecţie de informaţii grupate sub acelaşi nume. Un fişier poate fi un program executabil, un text, o imagine, un grup de comenzi sau orice altceva.

Un fişier este identificat prin numele său. Numele unui fişier este format dintr-un şir de caractere (care în funcţie de sistemul de operare este limitat la un anumit număr maxim de caractere), urmate eventual de semnul punct (.) şi de încă maximum 4 caractere, numite extensie, ca de exemplu: nume.ext.

Pentru a putea avea acces rapid la fişiere, sistemul de operare creează nişte fişiere speciale, numite directoare, care pot fi asemănate cu cuprinsul unei cărţi, deoarece ele conţin numele fişierelor şi adresa de început a acestora. De asemenea, un director poate conţine la rândul său alte directoare creându-se astfel o structură arborescentă de directoare în care poate fi găsit foarte repede un anumit fişier.

1.3.2. Tipuri de fişiere Fişierele se pot împărţi în două categorii – executabile şi

neexecutabile. În prima categorie intră acele fişiere al căror nume scris în dreptul prompterului (în cazul sistemului de operare DOS) determină executarea unor activităţi de către sistemul de operare. O parte dintre fişierele executabile sunt programe şi sunt recunoscute prin extensia lor care poate fi EXE sau COM, altele fiind constituite în fişiere de comenzi proprii sistemului de operare, a căror extensie este BAT.

Fişierele COM, numite adesea şi comenzi, conţin informaţii în formatul imagine de memorie. Ele sunt mai compacte şi mai rapide decât fişierele EXE, dar lungimea lor nu poate să depăşească 64 K. Fişierele EXE pot să ajungă la dimensiuni mai mari prin segmentarea programului în fragmente a căror dimensiune să fie de maximum 64K.

Dintre fişierele neexecutabile vom aminti câteva mai importante:

• fişiere text ; • fişiere cu extensia SYS sau DRV, cunoscute sub numele de

driver-e şi care conţin instrucţiuni despre modul în care sistemul de operare trebuie să controleze diferite componente hardware;

• surse de programe scrise în diferite limbaje (cu extensiile PAS – limbajul Pascal, C – limbajul C, CPP – limbajul C++, etc.);

Page 24: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

18

• fişiere care conţin informaţii intermediare între cele în limbaj sursă şi cele executabile (extensiile OBJ, OVL);

• fişiere ce conţin imagini (extensiile JPEG, GIF, BMP); • fişiere ce conţin sunete (extensiile WAV, MIDI, MP3) etc.

1.3.3. Construirea fişierului executabil Instrucţiunile pe care le execută un calculator sunt de fapt

grupuri de 1 ş 0 (cifre binare) care reprezintă semnale electronice produse în interiorul calculatorului. Pentru a programa primele calculatoare (în anii 1940-1950), programatorii trebuiau să înţeleagă modul în care calculatorul interpreta diferitele combinaţii de 0 şi 1, deoarece programatorii scriau toate programele folosind cifre binare. Cum programele deveneau din ce în ce mai mari, acest mod de lucru a devenit foarte incomod pentru programatori. De aceea au fost create limbaje de programare care permit exprimarea instrucţiunilor calculatorului într-o formă mai accesibilă programatorului. După ce programatorul scrie instrucţiunile într-un fişier - numit fişier sursă, un al doilea program – numit compilator, converteşte instrucţiunile limbajului de programare în şirurile 1 şi 0 – cunoscute sub numele de cod maşină.

Pentru a obţine un program executabil, orice program sursă trebuie eventual translatat (tradus) în limbaj cod maşină sau cod obiect pe care îl poate înţelege microprocesorul. În urma acestui proces, alături de fişierul sursă apare şi fişierul cod obiect (object file.) Această translatare sau traducere este efectuată de către compilatoare, interpretoare sau asambloare.

Compilatorul este folosit pentru transformarea codului sursă, adică a programului scris într-un limbaj de programare de nivel înalt, în cod obiect (object code). Acest cod obiect va fi transformat în faza de editare de legături în cod maşină executabil de microprocesorul sistemului de calcul.

Programatorii scriu programe într-o formă numită cod sursă. Acest cod sursă parcurge apoi câţiva paşi înainte de a deveni program executabil.

Pe scurt, un compilator este un program special care procesează instrucţiuni scrise într-un limbaj de programare particular şi le transformă în limbaj maşină sau cod maşină pe care îl poate executa microprocesorul.

Page 25: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

19

La ora actuală un limbaj de programare este inclus într-un mediu de programare mai complex care include un editor de texte pentru introducerea instrucţiunilor în limbajul de programare de nivel înalt, un compilator şi un editor de legături folosite pentru translatarea codului sursă în cod maşină.

În mod tipic, un programator scrie declaraţii într-un limbaj precum Pascal, C sau MATLAB folosind un editor. Se creează astfel un fişier numit fişier cod sursă ce conţine o colecţie de instrucţiuni şi declaraţii scrise în limbajul respectiv. Primul pas este prelucrarea codului sursă de către compilator, care translatează instrucţiunile de nivel înalt într-o serie de instrucţiuni cod obiect. Când este lansat în execuţie compilatorul acesta, într-o primă etapă, lansează un analizor sintactic, gramatical, numit parser. Acesta parcurge şi analizează sintactic, secvenţial, în ordinea în care au fost introduse, toate instrucţiunile scrise în limbajul de nivel înalt. O instrucţiune de nivel înalt se translatează într-una sau mai multe instrucţiuni specifice microprocesorului pentru care a fost conceput compilatorul. Aceste instrucţiuni ale microprocesorului sunt înlocuite cu codurile lor binare, fiecare instrucţiune a microprocesorului fiind codificată de către constructor. Codurile binare ale instrucţiunilor microprocesorului împreună cu reprezentările interne ale datelor manipulate formează codul obiect.

Deci în unul sau mai multe faze (parserul este una dintre faze) din codul sursă de intrare se produce un cod de ieşire, numit în mod tradiţional cod obiect. Este foarte important ca referiri la alte module de cod să fie corect reprezentate în acest cod obiect.

Pasul final în producerea programului executabil, după ce compilatorul a produs codul obiect, este prelucrarea codului obiect de către un editor de legături (link-editor sau linker). Acest linker combină diferitele module (le leagă) şi dă valori reale, efective, tuturor adreselor simbolice existente în codul obiect. În urma acestei prelucrări se obţine codul maşină, salvat într-un fişier cu extensia .exe. Acest cod maşină poate fi executat secvenţial, instrucţiune cu instrucţiune, de către microprocesor.

Cu alte cuvinte, un program executabil (executable program - aflat pe disc cu extensia .exe) se obţine prin salvarea pe disc a codului maşină obţinut prin prelucrarea succesivă a fişierului cod sursă de către compilator (compiler) şi apoi de către link-editor (linker).

Page 26: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

20

Fig. 1.5 Procesul de elaborare a unui program executabil

Procesul de obţinere a unui executabil este prezentat în figura

de mai jos. Blocurile tridimensionale reprezintă entităţile principale ale mediului de programare: editorul de texte, compilatorul (compiler) şi editorul de legături (linker). Blocurile dreptunghiulare reprezintă fişierele rezultate în urma aplicării celor trei utilitare de sistem:

� în urma utilizării editorului de texte obţinem fişierul text sursă cod cu numele generic “nume”. Dacă folosim limbajul de programare C spre exemplu, se obţine fişierul nume.c care se va salva pe disc.

� în urma lansării în execuţie a compilatorului, acesta preia fişierul sursă şi îl prelucrează corespunzător, semnalizându-se toate erorile fatale pentru program sau avertismente utile programatorului în procesul de depanare. În cazul în care compilarea se efectuează cu succes, se obţine un fişier cod obiect, salvat pe disc sub numele nume.obj

� în urma lansării în execuţie a editorului de legături, se preia fişierul cod obiect nume.obj şi se leagă cu toate modulele necesare (inclusiv funcţii de bibliotecă sau alte module externe), obţinându-se un program executabil (cod maşină) cu numele nume.exe la care adresele nu mai sunt simbolice ci absolute relativ la adresa de început a programului. La lansarea în execuţie a programului fluxul de informaţie este complet controlat de către microprocesor, toate salturile de adresă fiind făcute corespunzător.

Interpretorul (interpreter) este un program care execută instrucţiuni scrise într-un limbaj de nivel înalt. Numai anumite limbaje

Page 27: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

21

de nivel înalt, spre exemplu BASIC, LISP sau MATLAB, sunt prevăzute cu un interpretor.

Există două modalităţi de a executa un program scris în limbaj de nivel înalt. Cel mai comun mod este acela de a compila programul. Cealaltă modalitate este “pasarea” programului unui interpretor. Un interpretor translatează instrucţiunile de nivel înalt într-o formă intermediară care este apoi executată. Prin contrast, un compilator translatează instrucţiunile de nivel înalt direct în limbaj maşină (cod maşină). Programele compilate rulează în general mai rapid decât cele interpretate. Un alt avantaj al programelor compilate este acela al desprinderii din context în sensul că programele executabile generate în urma procesului de compilare pot fi executate direct sub sistemul de operare al calculatorului. Un program interpretat se execută sub mediul în care a fost creat.

Spre exemplu, pentru a rula un program scris în limbajul BASIC se lansează în execuţie mediul BASIC, apoi se deschide fişierul sursă-BASIC corespunzător şi se lansează interpretorul de BASIC pentru execuţia sa.

Avantajul unui interpretor este acela al evitării procesului de compilare consumator de timp în cazul în care avem programe de mari dimensiuni. Interpretorul poate executa imediat programele sursă. Pentru acest motiv interpretoarele se folosesc mai ales în procesul de dezvoltare al programelor, când programatorul doreşte adăugarea unor mici porţiuni de program pe care să le testeze rapid. De asemenea, interpretoarele permit o programare interactivă fiind des folosite în procesul de instrucţie.

În mediul de programare MATLAB, mediu interpretor, orice comandă utilizator se execută imediat. Se pot edita şi fişiere script, care conţin secvenţe de comenzi care se execută secvenţial.

Programele de descriere a paginii (Page Description Languages) ca PostScript spre exemplu folosesc un interpretor. Fiecare imprimantă PostScript are incorporat un interpretor care execută instrucţiuni PostScript.

Asamblorul (assembler) este un program care face translaţia unui program scris în limbaj de asamblare (limbaj de nivel scăzut, corespunzător microprocesorului sistemului de calcul) în limbaj cod maşină. Putem spune că asamblorul reprezintă pentru limbajul de asamblare ceea ce reprezintă compilatorul pentru limbajele de nivel înalt. Cum limbajul de asamblare conţine instrucţiuni mai puţin

Page 28: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

22

complexe decât cele de nivel înalt, asamblorul face practic o convertire biunivocă între mnemonicele limbajului de asamblare şi codurile binare corespunzătoare acestor mnemonice (instrucţiuni).

E d ito r d e te x te (e ve n tua l inc o rp o ra t în m e d iu)

L in k -e d ita re ( le ga re a tu turo r m o d ule lo r ne c e s a re )

L a n s a re a în e xe c u ţie d e c ã tre s is te m ul d e o p e ra re

a e xe c uta b ilu lu i n u m e .e x e

F iş ie r te x t ( f iş ie r s u rs ã ) c u e x te ns ia a d e c va tã :

n u m e .p a s ( lim b a j P a s c a l) n u m e .c ( lim b a j C )

n u m e .c p p ( lim b a j C + + ) n u m e .b a s ( lim b a j B A S IC ) , e t c .

Ins truc ţiun ile în lim b a ju l d e n ive l în a lt s e in tro d uc d e la ta s ta turã .

T o t c e s e in tro d uc e d e la ta s ta turã e s te v izib il p e m o nito r

F iş ie ru l s urs ã c u num e le “num e ” ş i e x te ns ia c o re s p un zã to a re s e s a lve a zã d in m e m o ria R A M p e ha rd d is k

C o m p ila to r S e c o m p ile a zã fiş ie ru l s urs ã

S e o b ţin e fiş ie ru l c o d o b ie c t: n u m e .o b j

S e s a lve a zã p e ha rd d is k fiş ie ru l

n u m e .o b j

S e o b ţine fiş ie ru l c o d m a ş in ã (e x e c u ta b il) :

n u m e .e x e S e s a lve a zã p e ha rd d is k fiş ie ru ln u m e .e x e

Fig. 1.6 Detalierea procesului de generare a unui executabil

Page 29: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

23

Capitolul II

REPREZENTAREA DATELOR ÎN CALCULATOR

Se ştie că un calculator numeric prelucrează numere binare.

Acest lucru ţine de suportul fizic de manipulare, transport şi stocare a datelor interne, mai bine zis este legat de faptul că semnalul fizic purtător de informaţie este o tensiune continuă cu două valori: una înaltă (High) şi una joasă (Low). Acestor două valori li se asociază natural două valori logice: T (true, adevărat) şi F (false, fals) sau cele două cifre binare1 şi 0.

timp

Tensiune

H igh= ’1 ’

Lo w = ’0 ’

Ca urmare a acestei asocieri spunem, prin abuz de limbaj, că un

calculator numeric prelucrează numere binare. Ca şi un număr zecimal, un număr binar are mai multe cifre binare. Sistemul de numeraţie binar folosit pentru reprezentarea informaţiei în calculatoare este un sistem de numeraţie ponderal, întocmai ca sistemul de numeraţie zecimal.

Reprezentarea naturală a numerelor la nivelul percepţiei umane este cea zecimală, pe când reprezentarea proprie maşinilor de calcul este cea binară. De aici rezultă necesitatea compatibilizării sau interfaţării între aceste două moduri de reprezentare a numerelor. Cum cele două sisteme de numeraţie sunt ponderale, o primă diferenţă este aceea că sistemul zecimal foloseşte ca ponderi puterile întregi (pozitive sau negative) ale lui 10 (zece) iar sistemul binar va folosi puterile întregi (pozitive sau negative) ale lui 2.

Page 30: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

24

În altă ordine de idei, dacă pentru reprezentarea externă sunt semnificative simbolurile de reprezentare (cifre, semnele + sau -, punct zecimal sau binar, mantisă sau exponent), pentru reprezentarea internă sunt necesare convenţii de reprezentare: indiferent de tipul datelor, acestea vor fi colecţii sau şiruri de cifre binare cărora, prin convenţie, li se atribuie semnificaţii.

Într-o primă instanţă, este foarte important să facem o distincţie între tipurile de date recunoscute de un calculator (sau mai bine zis de microprocesorul cu care este dotat calculatorul personal) şi formatele de reprezentare ale acestor date ce reprezintă convenţii pentru reprezentarea tipurilor de date, atât la nivel intern (în memoria calculatorului) cât şi la nivel extern, al percepţiei umane.

Din punctul de vedere al tipurilor de date care sunt implementate în limbajul C putem spune că distingem două mari categorii, date de tip întreg (integer) şi date de tip real (float). Formatele de reprezentare internă/externă vor fi prezentate în cele ce urmează. Cel mai simplu de reprezentat sunt numerele naturale. Se face apoi trecerea la numerele întregi negative şi apoi la numerele reale care au o parte întreagă şi una fracţionară.

2.1. Reprezentarea internă/externă a numerelor

Reprezentarea internă a numerelor se referă la modul în care se

stochează datele în memoria RAM a calculatorului sau în regiştrii microprocesorului. În acest format se prelucrează numerele pentru implementarea diverselor operaţii aritmetice. La nivelul calculatorului informaţia nu poate fi decât binară. În această reprezentare putem scrie numere întregi pozitive sau negative sau numere reale.

Există un standard IEEE care reglementează modul de reprezentare internă a datelor.

Reprezentarea externă este reprezentarea numerelor la nivelul utilizatorului uman, deci în principiu se poate folosi orice bază de numeraţie pentru reprezentarea numerelor. La nivel de reprezentare externă se foloseşte semnul “-” în faţa unui număr în cazul în care acesta este negativ sau punctul care separă partea întreagă de cea fracţionară. De asemenea, numerele întregi interpretate fără semn se pot afişa şi în format binar, octal sau hexazecimal, deci în bazele 2, 8 sau 16.

În cele ce urmează ne vom pune următoarele probleme: - cum se reprezintă extern un număr natural

Page 31: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

25

- cum se reprezintă intern un număr natural - cum se reprezintă extern un număr întreg negativ - cum se reprezintă intern un număr întreg negativ - cum se face conversia de la reprezentarea externă la cea

internă - cum se face conversia de la reprezentarea internă la cea

externă 2.2. Reprezentarea externă a numerelor

În ceea ce priveşte reprezentarea externă, nu sunt nici un fel de

dificultăţi deoarece fiecare este familiarizat cu reprezentarea zecimală a numerelor naturale sau reale. Trebuie menţionat de la început că orice tip de reprezentare pe care o vom folosi este ponderală în sensul că poziţia cifrelor în număr nu este întâmplătoare ci conformă cu o pondere corespunzătoare unei puteri a bazei de numeraţie.

O caracteristică a reprezentărilor externe este folosirea unor convenţii de format unanim acceptate şi de altfel foarte naturale pentru un utilizator uman. Spre exemplu, pentru a exprima numere negative se foloseşte semnul “-” iar pentru reprezentarea numerelor reale se foloseşte punctul “.” pentru delimitarea părţii întregi de cea fracţionară. De asemenea, suntem familiarizaţi şi cu notaţia ştiinţifică în care intervine mantisa şi exponentul (în virgulă mobilă).

Reprezentarea zecimală este cea mai naturală pentru utilizatorul uman. Vom oferi în continuare câteva exemple de reprezentări zecimale externe:

Număr Reprezentare normală

Reprezentare ştiinţifică

37 37 0.37x102

-37 -37 -0.37x102

0.375 0.375 0.375x100

-0.375 -0.375 -0.375x100

0.00375 0.00375 0.375x10-2

-0.00375 -0.00375 -0.375x10-2

12.375 12.375 0.12375x102

-12.375 -12.375 -0.12375x102

În general dorim să obţinem rezultatele numerice ale programelor pe care le concepem într-o formă de reprezentare accesibilă. Totuşi, calculatorul trebuie informat asupra formatului de reprezentare în care dorim să se afişeze datele necesare. Aceasta

Page 32: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

26

înseamnă că va trebui să specificăm câte cifre se vor folosi la partea întreagă şi câte la partea fracţionară sau dacă dorim reprezentare ştiinţifică sau nu. De altfel şi operatorul uman face aceleaşi convenţii

de reprezentare. Spre exemplu ştim că numărul 3

1 nu poate fi exact

reprezentat ca un număr zecimal, deci fixăm un format de reprezentare. Dacă formatul ale se limitează la 4 cifre zecimale, atunci

vom scrie 3333.03

1≅

Limbajul C are o serie de funcţii de reprezentare cu format a datelor numerice sau alfanumerice prin care programatorul poate impune un format extern cu care se manipulează datele.

2.2.1. Reprezentarea externă a numerelor întregi

Numerele naturale se pot reprezenta fie în baza de numeraţie 10, fie în orice altă bază.

În general, un număr întreg în baza b se poate reprezenta cu un număr predeterminat de cifre { }1,2,.....,2,1,0 −−=∈ bbBci .

Mulţimea B reprezintă mulţimea cifrelor sau simbolurilor de reprezentare. Spre exemplu:

{ }

{ }

{ }9,8,7,6,5,4,3,2,1,010

6,5,4,3,2,1,07

1,02

=⇒=

=⇒=

=⇒=

Bb

Bb

Bb

Noi suntem obişnuiţi să folosim mulţimea cifrelor zecimale. Dacă totuşi se foloseşte o bază de reprezentare mai mare decât 10, atunci mulţimea cifrelor zecimale nu mai este suficientă pentru reprezentarea numerelor în acea bază. Spre exemplu să considerăm baza b = 16 care va folosi 16 cifre hexazecimale (sau mai simplu hexa). Prin convenţie, cele 16 cifre hexazecimale vor fi:

Cifra Simbol Cifra Simbol 0 0 8 8

1 1 9 9

2 2 10 A

3 3 11 B

4 4 12 C

5 5 13 D

6 6 14 E

7 7 15 F

Page 33: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

27

Forma generală de reprezentare externă a numerelor întregi este de forma:

{ }

−−=∈

±= −−

1,2,.....,2,1,0

...... 01221

bbBc

cccccN

k

nnb

Valoarea numerică zecimală a numărului bN va fi:

( ) ∑−

=

−−

−− ⋅±=⋅+⋅++⋅+⋅±=

1

0

00

11

22

11 ...

n

k

kk

nn

nnb bcbcbcbcbcN

În continuare vom studia următoarele probleme: - cum se face conversia unui număr din baza 10=b în baza

2=b - cum se face conversia inversă, din baza 2=b în baza 10=b - cum se face conversia dintr-o bază oarecare 1b în altă bază 2b

Pentru a reprezenta un număr natural din baza 10 în baza 2, se împarte succesiv numărul la 2 şi se utilizează resturile la aceste împărţiri în ordinea inversă de cum au fost obţinute.

a) Conversia din baza 10 în baza 2 şi invers Fie de exemplu numărul zecimal 37. Reprezentarea sa binară va fi

obţinută astfel: 3710 = 1001012

37 36

2

18 2

1 18 9 2 0 8

1

4 2 4

0

2 2 2 1 0

Conversia inversă, din baza 2 în baza 10 este simplă şi

utilizează ponderea 2:

1001012 =

25 24 23 22 21 20 1 0 0 1 0 1 = 1x25 + 1x22 + 1x20=37

Cu aceste numere naturale putem face o serie de operaţii

aritmetice. Adunarea numerelor naturale binare se face întocmai ca la cele în reprezentare în baza 10, după regula:

0+0=0

Page 34: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

28

0+1=1 1+0=1 1+1=0, transport 1 spre rangul următor

Astfel, să facem adunarea 37+25 în binar:

1 0 0 1 0 1+ 1 1 0 0 1 1 1 1 1 1 0

37 25 62

Se observă cum se obţine rezultatul corect.

Înmulţirea se face în mod asemănător, ca o adunare repetată. Spre exemplu, să calculăm 37x25

1 0 0 1 0 1x 1 1 0 0 1 1 0 0 1 0 1 1 0 0 1 0 1 1 0 0 1 0 1 1 1 1 0 0 1 1 1 0 1

37 25 925

11100111012 = 1x20 + 1x22 + 1x23 +1x24 +1x27 +1x28+1x29 = 1+4+8+16+128+256+512 = 92510

b) Conversia dintr-o bază oarecare 1b într-o altă bază 2b .

Fie spre exemplu numărul 1149 care se doreşte scris în baza 13. Pentru a realiza această conversie, vom folosi baza intermediară 10. Vom converti mai întâi 114A în baza 10 şi apoi numărul zecimal obţinut îl vom trece în baza 13. Se observă cum un număr în baza 11 poate conţine şi cifra A=10 iar un număr în baza 13 poate conţine cifrele A=10, B=11, C=12.

1010

11 54104411411104 =+=⋅+⋅=A

54 52

13

4 13

2 0 0 4

1311

1310

424

4253

=

=

A

Page 35: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

29

2.2.2. Reprezentarea externă a numerelor reale

Semnificativă pentru utilizatorul uman este reprezentarea zecimală (în baza b=10) a numerelor reale, cu care suntem obişnuiţi. Faţă de reprezentarea numerelor întregi, la numerele reale intervine simbolul punct “.” care delimitează partea întreagă de partea fracţionară. Cu alte cuvinte, cu ajutorul numerelor reale putem reprezenta şi numere care nu sunt întregi. Forma generală a unui număr real reprezentat într-o bază oarecare b este:

{ }

−−=∈

•±= −+−−−−−

1,2,...,2,1,0

...... 1210121

bbBc

ccccccccN

k

mmnnb

Valoarea zecimală a numărului de mai sus va fi:

( ) ∑−

−=

−−

+−+−

−−

−−

−−

−− ⋅±=++⋅+++++±=

11

12

21

10

01

12

21

110

n

mk

kk

mm

mm

nn

nn bcbcbcbcbcbcbcbcbcN

Se observă cum punctul delimitează partea întreagă (exprimată printr-o combinaţie de puteri pozitive ale bazei b) şi partea fracţionară (exprimată printr-o combinaţie de puteri negative ale bazei b).

Semnificaţie pentru programator şi pentru producătorii de software sau microprocesoare au bazele de reprezentare 10=b şi

2=b , deoarece baza 10 este naturală pentru reprezentarea externă a numerelor iar baza 2 este naturală pentru reprezentarea binară, internă, a numerelor.

În formulele de mai sus avem o reprezentare a unui număr real cu n cifre pentru partea întreagă şi m cifre pentru partea fracţionară.

Aşa cum în sistemul zecimal reprezentăm cu un număr finit de cifre zecimale numerele reale, acelaşi lucru se va întâmpla şi în sistemul binar. Punctul binar va avea o semnificaţie asemănătoare cu punctul zecimal, care face separarea între partea întreagă şi cea fracţionară. Cifrele binare situate după punctul binar vor corespunde puterilor negative ale lui 2. Astfel, în general, un număr real va avea reprezentarea binară:

( )nn

mm

mmnmm bbbbbbbbbbbbbbN −

−−−−− +++++++=±= 2...222222....... 22

11

00

11

11210112

Spre exemplu, numărul 12.25 va avea reprezentarea binară: 223

10 22201.110025.12 −++==

Page 36: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

30

Partea întreagă a unui număr real se reprezintă binar precum numerele întregi (cu sau fără semn). Pentru a determina partea fracţionară, se procedează în mod invers ca la partea întreagă.

Astfel, dacă partea fracţionară zecimală se reprezintă binar, atunci aceasta se înmulţeşte succesiv cu 2. Dacă rezultatul depăşeşte valoarea 1, atunci se înscrie un bit 1. Se continuă mai departe cu dublarea valorii care depăşeşte 1. Dacă rezultatul nu depăşeşte valoarea 1, atunci se înscrie un bit 0 şi se continuă multiplicarea cu 2. Spre exemplificare, vom vedea cum se obţine reprezentarea binară a lui 12.25. Partea întreagă este 12. Ea se reprezintă binar prin împărţiri succesive la 2 şi considerarea resturilor. Partea fracţionară este 0.25

Partea fracţionară

P.F.

P.F. x 2 Noua P.F.

Bitul înscris

0.25 0.5 0 0.5 1 0 1 0

Obţinem exact rezultatul căutat: 12.25 = 1100.01 Să mai considerăm un alt exemplu. Să reprezentăm numărul 5.37 Partea întreagă are reprezentarea 510 =1012

Partea fracţionară P.F.

P.F. x 2 Noua P.F.

Bitul înscris

0.37 0.74 0.74 0

0.74 1.48 0.48 1

0.48 0.96 0.96 0

0.96 1.92 0.92 1

0.92 1.84 0.84 1

0.84 1.68 0.68 1

0.68 1.36 0.36 1

0.36 0.72 0.72 0

0.72 1.44 0.44 1

Etc.. Etc..

Obţinem: 5.3710 = 101.010111101...2

Cu cât mai multe cifre binare vom reţine după punctul binar, cu atât vom fi mai aproape de valoarea exactă 5.37.

Obţinem un rezultat foarte important: Deşi un număr zecimal poate avea un număr finit de cifre zecimale după punctul zecimal, reprezentarea sa binară internă poate avea un număr infinit de cifre binare. Este valabilă şi reciproca: un număr real zecimal cu un număr

Page 37: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

31

infinit de cifre se poate reprezenta într-o altă bază pe un număr finit

de cifre ( ex: 310 1.0...3...3333.03

1== ). Cum orice reprezentare

binară internă este pe un număr finit de biţi, numărul poate să nu fie reprezentat exact în calculator, ci cu o anumită aproximaţie. Acest lucru este decisiv pentru a înţelege importanţa lungimii reprezentării numerelor în calculator. Cu cât un număr binar se reprezintă pe un număr mai mare de biţi, cu atât precizia de reprezentare creşte.

2.3 Reprezentarea internă a numerelor

Deoarece semnalul intern purtător de informaţie într-un

calculator este de tip binar, un număr zecimal (întreg sau real) se va reprezenta intern în baza 2 cu ajutorul unui număr binar. O cifră binară se numeşte bit (Binary Digit) şi poate fi fie 0 fie 1.

În reprezentarea externă a numerelor am văzut că se poate folosi orice bază de numeraţie (cu cifrele corespunzătoare). De asemenea, numerele pot fi prefixate cu un simbol de semn ± şi pot include în reprezentare şi punctul de separaţie între partea întreagă şi cea fracţionară.

În reprezentarea internă acest lucru nu mai este posibil deoarece semnele plus (+), minus (-) sau punct (.) nu au nici o semnificaţie pentru calculator. Orice număr (orice tip de dată) este reprezentat la nivel intern de un număr prestabilit de biţi. Specialiştii din industria software au ajuns la un consens de reprezentare concretizat prin standardul IEEE 754 de reprezentare a internă a numerelor reale în computere.

Reprezentarea internă a numerelor a impus în limbajul C definirea aşa-numitelor tipuri de date.

Tipul unei date reprezintă modul în care microprocesorul stochează în memorie şi prelucrează cu ajutorul regiştrilor interni o dată. Tipul unei date se referă la lungimea sa de reprezentare (pe câţi biţi se reprezintă data) precum şi ce semnificaţie au anumite câmpuri de biţi din cadrul reprezentării.

2.3.1. Reprezentarea internă a numerelor întregi

Un număr binar este o colecţie de cifre binare ponderate fiecare cu o putere a lui 2. Bitul corespunzător ponderii celei mai mari, situat cel mai în stânga, se numeşte MSB (Most Significand Bit) iar cel

Page 38: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

32

corespunzător ponderii celei mai mici, situat cel mai în dreapta, se numeşte LSB (Less Significand Bit). În cazul reprezentării binare a numerelor naturale, reprezentarea externă (cea percepută de operatorul uman) şi cea internă (cea prelucrată de procesorul calculatorului) sunt asemănătoare. Cum pentru operatorul uman operatorii ‘+’ sau ‘-‘ semnifică faptul că un număr este pozitiv sau negativ, este necesară o convenţie pentru reprezentarea internă a numerelor întregi negative.

Această convenţie prevede folosirea MSB pentru reprezentarea semnului numerelor întregi. Dacă numărul este pozitiv, se adaugă în poziţia MSB bitul de semn ‘0’, iar dacă numărul este negativ se utilizează în poziţia MSB bitul de semn ‘1’. Mai mult, numerele negative se reprezintă în aşa numitul complement faţă de 2.

Reprezentarea numerelor întregi negative în complement faţă de 2

Această formă de reprezentare a numerelor negative necesită parcurgerea următorilor paşi:

pas1. Se reprezintă modulul numărului negativ, folosind bit de semn (egal cu 0, evident)

pas2. Se complementează toţi biţii numărului astfel obţinut. Complementarea înseamnă transformarea bitului 0 în bitul 1 şi a bitului 1 în bitul 0.

pas3. Numărul astfel obţinut se adună cu 1. De exemplu, să reprezentăm numărul -37. pas1. |-37| = 37 [ ] 100101010010137 210

semnbit

==

pas2. 0100101---->1011010 pas3. 1011010 + 1 = 1011011 => -3710 = 10110112

Evident, MSB este bitul de semn şi este egal cu 1. La o primă vedere, este posibil să credem că prin utilizarea

complementului faţă de 2 putem pierde semnificaţia numărului negativ. Pentru a vedea ce număr negativ este reprezentat, putem repeta procedeul de mai sus şi obţinem reprezentarea numărului pozitiv dat de modulul său.

O modalitate mai simplă este alocarea ponderii corespunzătoare

bitului de semn dar pe care o considerăm că reprezintă un număr negativ. Astfel: 10110112 = -1x26 + 1x24 + 1x23 + 1x21 + 1x20 = -64 + 27 = -37

Page 39: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

33

2.3.2 Adunarea, scăderea şi înmulţirea numerelor întregi

Aceste operaţii se execută folosind reprezentarea în complement faţă de 2 a numerelor întregi, sau, mai bine zis, se execută folosind în algoritmi bitul de semn ca pe un bit obişnuit. De exemplu, dorim să calculăm:

37-25 25-37

(-25)x37 (-25)x(-37)

Pentru efectuarea acestor calcule, vom scrie reprezentările cu bit de semn ale numerelor implicate:

=−

==

=−

==

101101137

010010110010137

10011125

0110011100125

10

210

210

210

Se observă că 25 şi (-25) se reprezintă pe 6 biţi iar 37 şi (-37) pe 7 biţi.

Deoarece am observat că biţii unui întreg cu semn nu au toţi aceeaşi semnificaţie, este nevoie să reprezentăm numerele cu care lucrăm pe un acelaşi număr de biţi. La adunări sau scăderi, biţii de semn se vor afla în aceeaşi poziţie (vor avea aceeaşi pondere) şi vom obţine astfel rezultate corecte. Pentru a avea o scriere pe un acelaşi număr de biţi, se adaugă (completează) la stânga bitul de semn de un număr corespunzător de ori. Astfel:

==

==−

001100101100125

110011110011125

2

210

10120001100

1100111

0100101

11001110100101)25(372537

=

−−−−−−

+

+=−+=−

==

=−

001100101100125

101101137

2

1252641110100

1011011

0011001

10110110011001)37(253725

−=+−=

−−−−−−

+

+=−+=−

Page 40: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

34

În continuare vom pune în evidenţă importanţa gamei de reprezentare, adică a domeniului de valori ale datelor. Să considerăm, spre exemplu, adunarea a două numere cu semn reprezentate pe un octet (8 biţi). Aceste numere sunt cuprinse în gama

[ ] [ ]127,12812,2 77−=−− .

Dacă vom dori să adunăm două numere din acest domeniu şi să reprezentăm rezultatul tot pe un octet, putem avea surprize. De exemplu, să considerăm operaţiile (117-12) şi (117+12). Se observă că operanzii sunt în gama de reprezentare a numerelor cu semn pe 8 biţi. Prin prima scădere, ne aşteptăm să obţinem un rezultat, 105, în aceeaşi gamă de reprezentare. 117-12=117+(-12) = 01110101+11110100 = 01101001 = 10510, rezultat corect.

117+12 = 01110101+00001100 = 10000001 = -12710, rezultat evident incorect.

Incorectitudinea provine de la faptul că rezultatul a depăşit gama de reprezentare. Dacă rezultatul este interpretat pe 9 biţi de exemplu, gama de reprezentare devine [ ]255,256− şi rezultatul va fi 117+12 = 001110101+000001100 = 010000001 = 12910, rezultat corect.

Ca o concluzie preliminară, reţinem că pentru a obţine rezultate corecte este necesar să precizăm dacă se lucrează sau nu cu bit de semn şi pe câţi biţi se face reprezentarea, pentru că numai în acest context interpretarea rezultatelor este corectă.

În ceea ce priveşte înmulţirea numerelor întregi cu semn (cu bit de semn), aici problema nu mai are o rezolvare asemănătoare, în sensul că nu putem trata biţii de semn la fel cu cei de reprezentare ai valorii. Astfel, procesorul studiază biţii de semn şi ia o decizie în privinţa semnului rezultatului. De fapt, se realizează funcţia logică XOR a biţilor de semn. Numerele negative se vor lua în modul, iar operaţiile de înmulţire se vor face numai cu numere pozitive. La final, funcţie de semnul rezultatului, se ia decizia reprezentării corecte a rezultatului.

Spre exemplu, să calculăm (-25)x37. Pentru aceasta, procesorul va primi pentru procesare următoarele două numere:

[ ] [ ]10011111001010)25(37 ×=−x

Page 41: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

35

Se analizează separat biţii de semn şi se ia decizia că rezultatul va fi negativ, deci, la final, se va reprezenta în complement faţă de 2. Mai departe se va lucra cu 25, modulul numărului (-25), care se obţine prin complementarea faţă de 2 a numărului binar 1100111:

1100111�0011000+1=0011001 Se va reţine pentru procesare numai numărul (fără semn) 11001, care se va înmulţi cu numărul (fără semn) 100101, obţinând, aşa cum am arătat mai sus, valoarea 1110011101. Mai departe, se adaugă bitul de semn, 0 pentru numere pozitive, obţinându-se 01110011101. Acest ultim număr se va complementa faţă de 2, obţinându-se 10001100010+1=[1]0001100011, adică valoarea -1024+99 = -925, valoarea corectă.

Ca o concluzie, pentru a furniza rezultate corecte, procesorul va trebui informat în permanenţă despre ce fel de numere prelucrează (cu sau fără semn) şi care este lungimea lor de reprezentare (toate trebuie să aibă aceeaşi lungime).

Reprezentarea în complement faţă de 2 se poate folosi şi pentru numerele reale negative, bitul de semn fiind MSB de la partea întreagă. Astfel, -12.25 poate avea reprezentarea:

1021014

2

210

25.1275.03162222211.10011

11.1001101.010.1001101.01100

01.0110001.110025.12

−=++−=++++−=

=+→

→=

−−

Pentru înmulţirea numerelor reale rămân valabile considerentele de la numere întregi.

În cazul de mai sus, problema reprezentării numărului negativ a fost rezolvată cu ajutorul bitului de semn dar problema reprezentării punctului binar va avea altă rezolvare.

2.3.3 Reprezentarea internă a numerelor reale

Din considerentele de la reprezentarea externă a datelor putem trage alte concluzii importante din punct de vedere al reprezentării interne. Numerele binare întregi fără semn au aceeaşi reprezentare atât externă cât şi internă.

Numerele întregi cu semn (care în reprezentare externă sunt prefixate cu ± ) au ca reprezentare internă un bit de semn, dar care se tratează deosebit de ceilalţi biţi ai reprezentării. Toţi întregii cu semn, care au MSB=1, sunt reprezentaţi intern în complement faţă de 2.

Page 42: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

36

Numerele reale se pot reprezenta identic cu cele întregi cu semn, cu o precizare: nu se face o deosebire netă între biţii reprezentării părţii întregi şi cei ai reprezentării părţii fracţionare. Acest tratament nediferenţiat provine de la reprezentarea ştiinţifică uzuală cu mantisă şi exponent. Fie, spre exemplu, reprezentarea binară a numărului

12.25: 410 2110001.001.110025.12 x==

Calculatorul poate reprezenta şirul de biţi 110001 şi reţine faptul că punctul se pune după primii 4 biţi ai reprezentării. Acest lucru se întâmplă şi în realitate. Deci, singura deosebire între reprezentarea numerelor reale şi a celor întregi constă în faptul că numerele reale necesită o informaţie suplimentară despre aşa numitul exponent, în cazul nostru numărul pozitiv 4.

În cele ce urmează, vom prezenta tipurile de bază pe care le pot avea datele în reprezentarea internă.

Tipul unei date determină modul în care procesorul stochează şi prelucrează data respectivă. Cum primele procesoare care au condus la apariţia pe piaţă a primelor calculatoare pentru neprofesionişti (aşa numitele Home Computers) au fost procesoare capabile să prelucreze şi să transmită în paralel 8 biţi, a fost naturală gruparea a 8 biţi într-o entitate numită byte.

1B = 8b (adică un byte reprezintă 8 biţi) Procesoarele au evoluat, ajungându-se în prezent la procesoare

pe 64 de biţi. Cum evoluţia lor s-a făcut trecându-se succesiv prin multipli de 8 biţi, s-au impus şi alte entităţi de reprezentare a informaţiei, pe care le vom prezenta sintetic în tabelul de mai jos.

Denumire

Dimensiune Notaţie

Nr. byte

Nr. biti

Denumire echivalentă

Byte 1B 8 b octet B Word 2B 16 b cuvânt W Double_Words 4B 32 b Cuvânt dublu DW

Quad_Words 8B 64 b Cuvânt cvadruplu QW

Ten_Words 10B 80 b TW A determina reprezentarea internă înseamnă să determinăm

lungimea reprezentării (de obicei în multipli de octeţi), modul de interpretare al biţilor ce compun reprezentarea şi gama de

Page 43: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

37

reprezentare, adică să determinăm magnitudinea (valorile minime şi maxime pozitive şi negative) ce pot fi reprezentate în formatul respectiv.

În limbajul C, există două tipuri de reprezentare pe care le putem numi principale: tipul întreg şi tipul real, fiecare având şi anumite particularizări. Astfel, tipul întreg (int) include şi tipul caracter (char) iar tipul real (float) include şi tipul real extins (double).

Tipurile de date le vom reprezenta de la simplu la complex, în ordinea char, int, float, double.

Tipurile de bază sunt char, int, float, double şi cu ajutorul modificatorilor de tip putem obţine diverse particularizări. Modificatorii pot fi signed, unsigned, short, long.

Ca o generalitate, numerele sunt reprezentate intern luându-se în considerare bitul de semn, deci implicit numerele întregi sau reale au MSB bit de semn. Dacă se specifică explicit, prin modificatorul unsigned, nu se mai consideră (interpretează) bitul de semn.

2.3.3.1 Tipul char

Codul ASCII (American Standard Code for Information Interchange) este un cod de reprezentare a caracterelor. Prin caracter înţelegem unităţile de bază care se pot tasta (intrări de la tastatură), tipări la imprimantă sau afişa pe ecran. Tastatura reprezintă, de exemplu, dispozitivul de intrare care conţine de fapt o întreagă colecţie de caractere ce pot fi emise prin apăsarea unei taste. Pentru a fi receptat, emis sau prelucrat de către calculator, fiecare caracter are asociat un cod binar (o combinaţie de biţi) care îl identifică în mod unic. Cum cu un octet putem codifica 28 = 256 caractere, octetul s-a dovedit o entitate suficientă pentru codificarea caracterelor utilizate în informatică. În 256 de coduri distincte se pot include literele mari şi mici ale alfabetului anglo-saxon (inclusiv litere specifice diverselor alfabete precum cel chirilic sau particularităţi ale diferitelor ţări: ş, ţ, â, î, Ş... în română, de exemplu). Se mai pot include caractere ce reprezintă numere, semne de punctuaţie sau alte caractere de control. Codul ASCII a standardizat această codificare, astfel încât el este folosit în cvasitotalitatea calculatoarelor (doar mainframe-urile IBM mai folosesc un alt cod, mai vechi, numit EBCIDIC). Dacă se declară o dată de tip char, ea este considerată explicit de tipul signed char (cu MSB bit de semn), deci reprezentarea internă este de forma:

Page 44: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

38

S b 6 b 5 b 4 b 3 b 2 b 1 b 0

B it d e s e m n Gama de reprezentare este cuprinsă între

[ ]127,1281282min

12712max7

7

−⇒

−=−=

=−=

Dacă se declară tipul unsigned char, atunci nu se mai consideră (interpretează) bitul de semn şi data se consideră întreagă pozitivă, în gama

[ ]255,00min

25512max 8⇒

=

=−=

Tabelele de mai sus conţin codurile ASCII ale primelor 128 de caractere. Coloana D semnifică valoarea zecimală (decimal) a octetului, coloana H reprezintă aceeaşi valoare reprezentată în format hexazecimal (baza 16) iar în coloana Sym se reprezintă simbolul afişat pe monitoarele PC.

Întregul alfabet al limbajului C se regăseşte în mulţimea primelor 128 de caractere ASCII. Restul de 128 de caractere se mai numeşte şi set de caractere extins ASCII şi poate fi vizualizat printr-un program simplu.

Trebuie menţionat faptul că reprezentarea datelor în format hexazecimal este foarte răspândită în tehnica programării calculatoarelor. Avantajul reprezentării interne a datelor în format hexazecimal constă în folosirea unui număr mai mic de cifre (de 4 ori mai mic decât numărul de cifre binare).

Reprezentarea unui număr natural în format hexazecimal se realizează cu metoda împărţirii succesive la 16 sau, mai simplu, pornind de la reprezentarea binară a numărului.

Cum mulţimea cifrelor hexa conţine 16 simboluri (0…9 şi A…F), pentru codificarea celor 16 cifre avem nevoie de 4 cifre binare

( 1624= ). Pentru a reprezenta un octet vom avea nevoie de 2 cifre

hexazecimale şi vom proceda astfel: - se divide octetul în două grupe de câte 4 biţi

Page 45: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

39

- se înlocuieşte fiecare grup de 4 biţi cu cifra hexazecimală pe care o codifică.

De exemplu, să presupunem că avem numărul 217.

2179208169161391001.110111011001217 01162210 =+=⋅+⋅==== D

În acest mod, dacă un număr are o reprezentare internă pe un număr de k octeţi, se poate reprezenta simplu cu ajutorul a k2 cifre hexazecimale.

În tabelele de mai jos se prezintă codificarea ASCII a caracterelor.

Codurile corespunzătoare simbolurilor alfanumerice din tabel sunt exact semnalele binare care se transmit în reprezentarea internă. Cu alte cuvinte, dacă la tastatură se tastează simbolul “a”, atunci circuitele corespunzătoare transmit spre calculator semnale binare corespunzătoare codului 1010 0001, adică 61H sau 97 în zecimal.

La fel se întâmplă când se lucrează cu procesoare de text sau când se tipăreşte un document la imprimantă. Sistemul de calcul manevrează codurile ASCII corespunzătoare literelor şi cifrelor pe care utilizatorul le poate interpreta.

D H Sym D H Sym D H Sym D H Sym 0 0 Null 16 10 ► 32 20 48 30 0 1 1 ☺ 17 11 ◄ 33 21 ! 49 31 1 2 2 ☻ 18 12 ↕ 34 22 " 50 32 2 3 3 ♥ 19 13 ‼ 35 23 # 51 33 3 4 4 ♦ 20 14 ¶ 36 24 $ 52 34 4 5 5 ♣ 21 15 § 37 25 % 53 35 5 6 6 ♠ 22 16 ▬ 38 26 & 54 36 6 7 7 23 17 ↨ 39 27 ' 55 37 7 8 8 24 18 ↑ 40 28 ( 56 38 8 9 9 25 19 ↓ 41 29 ) 57 39 9 10 a LF 26 1a → 42 2a * 58 3a : 11 b ♂ 27 1b ← 43 2b + 59 3b ; 12 c ♀ 28 1c ∟ 44 2c , 60 3c < 13 d CR 29 1d ↔ 45 2d - 61 3d = 14 e ♫ 30 1e ▲ 46 2e . 62 3e > 15 f ☼ 31 1f ▼ 47 2f / 63 3f ?

Page 46: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

40

D H Sym D H Sym D H Sym D H Sym 64 40 @ 80 50 P 96 60 ` 112 70 p 65 41 A 81 51 Q 97 61 a 113 71 q 66 42 B 82 52 R 98 62 b 114 72 r 67 43 C 83 53 S 99 63 c 115 73 s 68 44 D 84 54 T 100 64 d 116 74 t 69 45 E 85 55 U 101 65 e 117 75 u 70 46 F 86 56 V 102 66 f 118 76 v 71 47 G 87 57 W 103 67 g 119 77 w 72 48 H 88 58 X 104 68 h 120 78 x 73 49 I 89 59 Y 105 69 i 121 79 y 74 4a J 90 5a Z 106 6a j 122 7a z 75 4b K 91 5b [ 107 6b k 123 7b { 76 4c L 92 5c \ 108 6c L 124 7c | 77 4d M 93 5d ] 109 6d M 125 7d } 78 4e N 94 5e ^ 110 6e n 126 7e ~ 79 4f O 95 5f _ 111 6f o 127 7f ⌂

2.3.3.2 Tipul int Acest tip se foloseşte pentru reprezentarea numerelor întregi cu

sau fără semn. Odată cu standardizarea ANSI C din 1989, s-a trecut la modul de reprezentare a întregilor impus de noul procesor Intel 80386 dotat şi cu coprocesorul matematic Intel 80387.

S b3 0

b0

LSB

M SB

O ctetul 1

O ctetul 2

O ctetul 3

O ctetul 4

Tipul int este identic cu signed int şi utilizează o reprezentare pe

4B a numerelor întregi cu semn. Reprezentarea pe 4 octeţi duce la posibilitatea măririi gamei de reprezentare astfel:

( ) ( ) 933310303131

31

10210222222;2min

12max⋅≅⋅≅⋅=⋅=

−=

−=

Page 47: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

41

Rezultă că putem reprezenta numere întregi în gama:

[ ] [ ]999 102,102101475.2 ⋅⋅−≅⋅± unsigned int nu va mai lua în considerare bitul de semn, astfel încât reprezentarea internă este de forma din figura de mai jos. Evident,

( ) ( ) 933310303232

10410424242;0min

12max⋅≅⋅≅⋅=⋅=

=

−=

Gama de reprezentare se poate schimba cu ajutorul modificatorilor short sau long.

S b14

b0

LSB

MSB

short int se va reprezenta pe 2B, sub forma

[ ]32767,32768232222;2min

12max 101051515

15

−⇒⋅=⋅=

−=

−=.

unsigned short int va schimba gama de reprezentare în [ ]65535,0 long int se va reprezenta pe 8B şi va conduce la o gamă imensă

de reprezentare a numerelor întregi, lucru dovedit de

( ) 1818610363 102234.9108222 ⋅±=⋅±≅⋅±=± unsigned long int va considera numai numere întregi pozitive în

gama [ ]1910844.1,0 ⋅ .

2.3.3.2 Tipul float Acest tip de reprezentare este de tip real, fiind cunoscut şi ca

reprezentare în virgulă mobilă (floating point). Acest tip descrie mecanismul de bază prin care se manipulează datele reale. Conceptul fundamental este acela de notaţie ştiinţifică, prin care orice număr se poate exprima ca un număr zecimal (deci, cu punct zecimal) multiplicat cu o putere a lui zece sau ca un număr real binar (cu punct binar) multiplicat cu o putere a lui 2.

3210 210101.020101.101.10125.5 xx ===

Page 48: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

42

Se observă cum stocarea în calculator a unei date floating-point necesită trei părţi:

- bitul de semn (sign) - mantisa, fracţia (significand) - exponent (exponent) Folosind formatul specific I80386, în limbajul C se disting trei

tipuri de date reale: - float , cu reprezentare pe 4 octeţi (32 biţi, double word) - double, cu reprezentare pe 8 octeţi (64 biţi, quad word) - long double, cu reprezentare pe 10 octeţi (80 biţi, ten word)

b31 b30

b0

LSB

MSB

S Exponent biased

Significand

S Exponent = 8b Bias = 7FH=127

Significand = 23b 31 30 23 22 0

float

S Exponent = 11b Bias = 3FFH=1023

Significand = 52b 63 62 52 51 0

double

S Exponent = 15b Bias = 3FFFH=16383

Significand = 52b 79 78 64 63 0

long double

Tipurile float şi double sunt formate pentru numere reale ce

există numai în memorie. Când un astfel de număr este încărcat de procesor în stiva pentru numere reale (flotante) pentru prelucrare sau

Page 49: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

43

ca rezultat al prelucrării, el este automat convertit la formatul long double (sau extended).

În cazul în care acest număr se stochează în memorie, el se converteşte la tipul float sau double. Toate cele trei subtipuri reale au un format comun, care va fi prezentat în continuare. Ceea ce le deosebeşte este numărul de biţi alocaţi pentru exponent şi pentru mantisă, precum şi interpretarea biţilor mantisei (significand).

Semnul are alocat în toate formatele un singur bit: 0 pentru numere pozitive şi 1 pentru numere negative.

Mărimea câmpului exponent variază cu formatul şi valoarea sa determină câţi biţi se mută la dreapta sau la stânga punctului binar.

Câmpul significand este analogul mantisei în notaţia ştiinţifică. El conţine toţii biţii semnificativi ai reprezentării, deci biţii semnificativi atât ai părţii întregi cât şi ai părţii fracţionare cu singura restricţie ca aceşti biţi să fie consecutivi. Deoarece punctul binar este mobil, cu cât sunt mai mulţi biţi alocaţi părţii întregi, cu atât vor fi mai puţini pentru partea fracţionară şi invers. Cu cât formatul este mai larg, cu atât se vor reprezenta mai precis numerele.

Pentru a salva un spaţiu preţios de stocare, nici unul dintre cele trei formate float nu stochează zerouri nesemnificative. De exemplu, pentru numărul 42101.00000101.0 −

= x câmpul significand va stoca numărul 101, nu şi cele 4 zerouri nesemnificative ale părţii fracţionare. Pentru a salva şi mai mult spaţiu, pentru formatele float şi double câmpul significand nu va conţine primul bit semnificativ care obligatoriu este 1. Câştigând acest bit (numit bit phantom), se dublează gama de reprezentare. Formatul long double va conţine totuşi bitul de semn 1 cel mai semnificativ. Punctul binar se pune exact înaintea primului bit din câmpul significand, adică după bitul 1 implicit (phantom). În cazul long double, se aplică după primul bit 1.

Pentru a uşura operarea cu aceste numere, câmpul exponent nu este stocat ca un număr întreg cu semn, ci este decalat (normalizat, cu bias) pentru a reprezenta numai numere pozitive (deci exponentul este interpretat ca număr natural fără semn). Biasul adăugat se scade pentru a afla exponentul exact. Avantajul exponentului decalat constă, pe lângă faptul că nu mai are nevoie de bit de semn, în faptul că pentru a compara două numere reale putem începe prin compararea biţilor pornind de la MSB către LSB, cel mai mare fiind cel care are 1 la primul bit diferit. Se decide astfel foarte rapid care număr este cel mai mare. Ca exemplu, să considerăm un format float în care se stochează:

Sign = 0

Page 50: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

44

Exponent = 10000010 = 13010 Significand = 1001000…00

Valoarea reală a exponentului va fi 130 - 127 = 3 Biţii câmpului significand se obţin adăugând MSB phantom, deci aceştia vor fi 11001000...00 Numărul real care s-a stocat este:

0.110010...00 x 24 = 1100.1 =12.5 Reprezentarea internă a numărului 12.5, pe 4 octeţi (float), este următoarea:

0 1

LSB

Semn

0 0 0 0 1 0

0 1 0 0 1 0 0 0

0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0

Cu alte cuvinte, putem spune că reprezentarea internă a numărului real 12.5 este (în format hexazecimal):

1610 414800005.12 =

În cazul în care dorim să reprezentăm numărul negativ –12.5, singurul bit care se va modifica va fi bitul de semn, care devine 1. Astfel, reprezentarea internă în format float a numărului negativ real –12.5 este:

1 1

LSB

Semn

0 0 0 0 1 0

0 1 0 0 1 0 0 0

0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0

1610 14800005.12 C=−

Dacă numărul 12.5 se reprezintă în formatul double, deci pe 8 octeţi, atunci reprezentarea sa internă se va realiza astfel:

- bitul de semn va fi 0

Page 51: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

45

- exponentul nu va mai fi pe 8 biţi ca la tipul float, ci pe 11 biţi, deci se va schimba şi bias, care va fi 1023. Atunci:

01000000001210241026102331exponent

=+==+− bias

- significand va fi acelaşi ca la tipul float, dar reprezentat pe 52 de biţi

0 1

LSB

Semn

0 0 0 0 0 0

0 0 1 0 1 0 0 1

0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0

1610 00000040290000005.12 =

Reţinem că la numere reale numai bitul de semn indică dacă numărul este pozitiv sau negativ, mantisa şi exponentul se reprezintă ca numere naturale fără bit de semn. Formatele prezentate mai sus respectă standardul IEEE 754 de reprezentare a internă a numerelor reale în computere. Se poate pune o întrebare legitimă: de ce bias-ul în cazul float spre exemplu este 127? Pentru a răspunde la această întrebare, putem face următorul raţionament:

- exponentul cu semn este reprezentat pe 8 biţi, deci este în gama de reprezentare [ ]127,128 +− .

- pentru a obţine un exponent pozitiv, adăugăm numărul 128. - deoarece bitul phantom nu este reprezentat, exponentul

trebuie micşorat cu o unitate pentru a indica unde anume se poziţionează exact punctul binar.

- Exponent pozitiv = exponent +128 – 1 = exponent + bias de unde rezultă evident faptul că bias = 127 în cazul tipului float.

În final să analizăm un exemplu de procesare a produsului a două numere reale. Vrem să calculăm valoarea 5.25 x 1.5. Pentru aceasta, vom scrie cei doi factori ai produsului în forma:

Page 52: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

46

( ) ( )[ ]

;

211.10101.5.125.5

211.1.15.1

210101.01.10125.5

13

1210

3210

××=×

×==

×==

+

0111111.

10101

10101

11.

10101.

−−−−−

−−−−−

×

875.7111.11120111111.5.125.5 4==×=×⇒

Se observă cum câmpurile exponent şi significand sunt procesate separat, în final corelându-se forma de reprezentare internă.

Game de reprezentare pentru numerele reale

Gama de reprezentare pentru fiecare din tipurile reale prezentate mai sus se calculează luând în considerare cel mai mare număr şi cel mai mic număr posibil a fi scris în respectiva reprezentare. Astfel, exponentul este decisiv pentru gama de reprezentare.

La tipul float, avem

( ) ( ) 38361212108128max

maxmax

1056.2102561024256222

128127255_exponent255_exponent

⋅=⋅≅⋅=⋅==

=−=⇒=

n

realbias

Valoarea maximă exactă, calculată fără a aproxima ca mai sus:

310 10100010242 =≅= este 38max 104028.3 ⋅=n

( ) ( ) ( ) 391310121010312107127min

minmin

10828222222

1271270_exponent0_exponent

−−−−−−−⋅≅⋅=⋅⋅=⋅==

−=−=⇒=

n

realbias

Valoarea pozitivă minimă exactă este 39min 108775.5 −

⋅=n La tipul double vom obţine:

( ) ( ) 3073063061021041024max

maxmax

106.11016102416222

102410232047_exponent2047_exponent

⋅=⋅≅⋅=⋅==

=−=⇒=

n

realbias

Valoarea maximă exactă este 308max 107.1 ⋅=n

Page 53: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

47

( ) 3061021031023min

minmin

10125.222

102310230_exponent0_exponent

−−−−⋅≅⋅==

−=−=⇒=

n

realbias

Valoarea pozitivă minimă exactă este 308min 101125.1 −

⋅=n Efectuând aceleaşi consideraţii şi calcule pentru tipul long double,

vom obţine

⋅=

⋅=

−4932min

4932max

104.3

101.1

n

n

2.3.5. Codificare BCD

Procesorul I80386 este considerat primul procesor care are capacitatea de a procesa operaţii aritmetice asupra unor numere reprezentate în zecimal codificat binar (BCD, binary-coded decimal) în locul formatelor binare standard. Reprezentarea numerelor în cod BCD este folosită pentru a face numerele binare mai accesibile operatorului uman. Neajunsul acestei reprezentări este faptul că numerele BCD ocupă spaţiu de stocare mai mare decât numerele binare. Ele sunt mai uşor de interpretat de către programatorul uman, pentru computer neavând nici un fel de relevanţă. Procesorul 80386 poate manevra două tipuri de formate BCD: împachetat şi neîmpachetat (packed BCD şi unpacked BCD). În formatul unpacked BCD, o cifră zecimală se stochează pe un octet. Spre exemplu, cifra zecimală 5 va fi reprezentată intern sub forma 00001001. Formatul packed BCD stochează două cifre zecimale pe un octet, crescând capacitatea de stocare internă precum şi gama de reprezentare pe un acelaşi număr de octeţi. Ambele codificări folosesc reprezentarea pe 4 biţi a cifrelor zecimale. Spre exemplu, numărul 9817 se stochează pe 4 octeţi în format unpacked BCD şi pe 2 octeţi în format packed BCD:

unpacked BCD: 9817 = 0000 1001 0000 1000 0000 0001 0000 0111

packed BCD: 9817 = 1001 1000 0001 0111 Se observă cum valoarea maximă care se poate stoca pe un octet

este 9 pentru unpacked BCD, 99 pentru packed BCD şi 255 pentru codificarea binară fără semn standard.

Toate formatele reale prezentate se conformează standardului IEEE 754 pentru reprezentarea numerelor în virgulă mobilă în format binar.

Page 54: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

48

Ca o concluzie la acest capitol, decisiv pentru înţelegerea dezvoltărilor ulterioare, putem sintetiza următoarele:

� Reprezentarea externă a numerelor se referă la modul în care operatorul uman acceptă schimbul de date cu calculatorul. Acest schimb de date are dublu sens: de la operatorul uman către calculator şi invers. Reprezentarea externă este de obicei zecimală şi are un format aproape identic cu formatul matematic uzual: simbol de semn prefixat, punct zecimal, mantisă sau exponent. Numerele naturale se mai pot reprezenta şi în format octal sau hexazecimal. În format extern se introduc datele de la tastatură pentru prelucrare şi se obţin pe monitor sau la imprimantă rezultatele oferite de calculator.

� Reprezentarea internă a numerelor se referă la modul în care se stochează datele în memoria RAM a calculatorului şi respectiv în regiştrii interni ai microprocesorului. Această reprezentare internă este legată de noţiunea de tip de dată.

� Tipul de dată întreg (integer) se reprezintă intern pe 2, 4 sau 8 octeţi în complement faţă de 2, cu cel mai semnificativ bit (MSB) bit de semn: 1 pentru numere întregi negative şi 0 pentru numere întregi pozitive. Un caz particular de dată de tip întreg este tipul character, interpretat ca întreg pe un octet.

� Tipul de dată real (float) se reprezintă intern pe 4, 8 sau 10 octeţi şi conţine 3 câmpuri de biţi distincte: bit de semn, câmp mantisă şi câmp exponent, de lungimi corespunzătoare. Dacă se specifică explicit, toate numerele se pot defini fără semn (unsigned), caz în care calculatorul nu mai interpretează bitul de semn (MSB) diferit ci îl include în câmpul de reprezentare al mărimii, crescând gama de reprezentare.

Page 55: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

49

Capitolul III

ELEMENTELE DE BAZĂ ALE LIMABJULUI C

3.1. Crearea şi lansarea în execuţie a unui program C

Prezentăm câteva comenzi simple pentru a lansa în execuţie un

program C folosind compilatorul BORLANDC v3.1 (versiunea pentru sistemul de operare DOS):

• după setarea pe directorul corespunzător se tastează - bc – pentru a intra în mediul BorlandC; - <Alt>-F – pentru a selecta meniul File; - N – pentru a deschide un fişier nou.

• se editează programul sursă folosind editorul mediului BorlandC; Exemplu: #include <stdio.h> void main (void)

{

printf("Primul program in C!");

}

• se acţionează tasta F2 şi se indică numele fişierului (cu extensia .c sau .cpp) pentru salvarea programului sursă pe disc – de exemplu mesaj.c - (se recomandă salvarea pe disc după efectuarea oricărei modificări în programul sursă pentru evitarea pierderii accidentale a acesteia);

• se realizează compilarea, link-editarea (realizarea legăturilor) şi lansarea în execuţie a programului executabil mesaj.exe tastând <CTRL>-F9;

• pentru a vizualiza rezultatele execuţiei programului se tastează <Alt>-F5;

• se revine în fereastra de editare a mediului acţionând o tastă oarecare;

• pentru a închide un fişier sursă se tastează <Alt>-F3 iar pentru a ieşi din program în mediul de operare se tastează <Alt>-X.

Page 56: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

50

La fel de simplu poate fi utilizată şi varianta pentru sistemul de operare WINDOWS a compilatorului BORLANDC v3.1:

• se lansează în execuţie programul bcw.exe; • se selectează din meniul File opţiunea New, creându-se

fişierul noname00.cpp; • în fereastra noname00.cpp se introduce codul programului; • din meniul File se selectează opţiunea Save As… sau Save

iar în căsuţa de dialog care apare se va salva fişierul program cu extensia .cpp;

• din meniul Compile se selectează opţiunea Build All ce va afişa caseta de dialog Compiling (compilare);

• dacă operaţia de compilare se încheie cu succes (nu există erori de sintaxă în program) compilatorul va afişa mesajul Press any key, caz în care compilatorul va crea fişierul executabil;

• lansarea în execuţie a fişierului executabil se poate realiza folosind opţiunea Run din meniul Run sau combinaţia de taste <CTRL>-F9.

Fiecare limbaj de programare are un set de reguli, denumite reguli sintactice, reguli ce trebuie respectate la editarea unui cod sursă. Dacă este încălcată o regulă sintactică, programul nu va fi compilat cu succes. În acest caz, pe ecran va fi afişat un mesaj de eroare ce specifică linia ce conţine eroarea, precum şi o scurtă descriere a erorii. În exemplul următor programului îi lipseşte caracterul punct şi virgulă după utilizarea funcţie printf:

#include <stdio.h>

void main (void)

{

printf("Primul program in C!")

}

La compilare pe ecran vor apare următoarele mesaje de eroare: Compiling NONAME00.CPP:

Error NONAME00.CPP 5: Statement missing ;

Error NONAME00.CPP 5: Compound statement missing }

Cu toate că în codul sursă există doar o eroare, compilatorul de C va afişa două mesaje de eroare. Lipsa caracterului punct şi virgulă provoacă o serie de erori în cascadă. Pentru a corecta erorile sintactice se merge cu ajutorul cursorului în linia indicată de către mesajul de eroare şi se corectează instrucţiunea respectivă.

Page 57: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

51

3.2. Structura unui program C

Conceptul de bază folosit în structurarea programelor scrise în limbajul C este funcţia. Astfel, un program în C este compus din cel puţin o funcţie şi anume funcţia main() sau funcţia principală. La rândul ei, funcţia main() poate apela alte funcţii definite de utilizator sau existente în bibliotecile ce însoţesc orice mediu de dezvoltare de programare în C. Structura generală a unei funcţii C este de forma:

tip nume_funcţie (param_1, param_2, ...,param_n)

−−−−−−−−

−−−−−−−−

−−−−−−−−Instrucţiuni declarare tip parametri

{

−−−−−−−

−−−−−−−

−−−−−−− Corp funcţie=secvenţă de instrucţiuni sau apel de funcţii

} unde: - tip reprezintă tipul de dată returnat de funcţie (în mod implicit o funcţie returnează tipul int);

- nume_funcţie reprezintă numele sub care funcţia este cunoscută în program;

- param_1,...,param_n - parametrii cu care funcţia este apelată şi al căror tip poate fi declarat direct în această listă, sau prin instrucţiuni separate plasate imediat după lista parametrilor.

Corpul funcţiei este definit ca o secvenţă de instrucţiuni şi/sau apeluri de funcţii şi este delimitat de restul funcţiei prin paranteze acolade.

În limbajul C există două categorii de funcţii. O primă categorie este formată de funcţiile ce returnează o valoare la revenirea din ele în punctul de apel, tipul acestei valori fiind definit de de tipul funcţiei. Cealaltă categorie conţine funcţiile ce nu returnează nici o valoare la revenirea din ele , pentru aceste funcţii fiind utilizat cuvântul cheie void în calitate de tip. El semnifică lipsa unei valori returnate la revenirea din funcţie.

Exemple: 1) void f(void) {

………… }

Page 58: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

52

Funcţia f nu are parametri şi nu returnează nici o valoare. 2) double g(int x) { …………

}

Funcţia g are un parametru x de tipul int şi returnează la revenirea în programul principal o valoare flotantă în dublă precizie.

Funcţiile C sunt în general unităţi independente, compilabile separat. Instrucţiunile, la rândul lor, pot defini tipul unor date folosite în program, sau operaţii ce trebuie executate prin program.

Din punct de vedere sintactic, orice instrucţiune trebuie terminată cu caracterul ";", iar grupurile de instrucţiuni pot fi delimitate prin caracterele { şi } pentru a forma unităţi sintactice noi de tip bloc. Funcţiile apelate vor primi valori pentru argumentele (parametrii) lor şi pot returna către funcţia apelantă valori de un anumit tip.

Cu aceste precizări generale, dacă avem un program compus din două funcţii, şi anume funcţia principală şi o funcţie apelată f(), atunci structura acestuia va fi de forma:

principala Functia

}

--------

/ * f() functiei apelul * / ); f(

--------

{

) main(tip

_ _ _ _ _ _ _ _ _ _ _

) f( Functia

}

{

) f(

−−−−−−

−−−−−−

Programul începe cu execuţia funcţiei main(). Aceasta funcţie

este folosită în general fără parametri. La rândul lor, funcţiile apelate pot fi scrise în limbaj C, sau realizate în alte limbaje de programare: asamblare, Fortran, Pascal etc.

Page 59: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

53

3.3. Mulţimea caracterelor

În programele C pot fi utilizate două mulţimi de caractere: mulţimea caracterelor C şi mulţimea caracterelor C reprezentabile. Mulţimea caracterelor C se compune din litere, cifre, semne de punctuaţie care au o semnificaţie specifică pentru compilatorul C. Programele C sunt formate din combinaţii ale caracterelor din mulţimea de caractere C constituite în instrucţiuni semnificative. Mulţimea caracterelor C este o submulţime a mulţimii caracterelor C reprezentabile. Mulţimea caracterelor reprezentabile este formată din totalitatea literelor, cifrelor şi simbolurilor grafice. Dimensiunea mulţimii de caractere reprezentabile depinde de tipul de terminal, consolă etc. Fiecare caracter din mulţimea caracter din mulţimea caracterelor C are un înţeles explicit pentru compilatorul C. Compilatorul dă mesaje de eroare când întâlneşte caractere întrebuinţate greşit sau caractere care nu aparţin mulţimii caracterelor C. În continuare sunt descrise caracterele şi simbolurile din mulţimea caracterelor C şi utilizarea acestora.

3.3.1. Litere şi numere

Mulţimea caracterelor C include literele mari şi mici ale alfabetului englez şi cifrele zecimale din sistemul de numere arabe.

Literele mari şi mici ale alfabetului englez sunt următoarele: A B C D E F G H I J K L M N O P R S T U V W X Y Z

a b c d e f g h i j k l m n o p r s t u v w x y z iar cifrele zecimale: 0 1 2 3 4 5 6 7 8 9. Aceste litere şi cifre pot fi folosite pentru a forma constante,

identificatori şi cuvinte cheie. Compilatorul C prelucrează litere mari şi mici în mod distinct.

3.3.2. Caractere whitespace

Spaţiul, tab-ul, linefeed (linie nouă), carriage return (revenire la capătul rândului), form feed, tab-ul vertical şi newline sunt numite caractere whitespace deoarece servesc pentru spaţiere între cuvinte, aliniere la o nouă coloană, salt la linie nouă. Aceste caractere separă instrucţiuni definite de utilizator, constante şi identificatori, de celelalte instrucţiuni dintr-un program. Compilatorul C ignoră caracterele whitespace dacă nu sunt folosite ca separatori sau

Page 60: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

54

drept componente de constante, sau ca şiruri de caractere. Caracterele whitespace sunt utilizate pentru a face programele mai lizibile. Comentariile sunt de asemenea tratate ca whitespace.

3.3.3. Caractere speciale şi de punctuaţie

Caracterele speciale şi de punctuaţie din mulţimea caracterelor C sunt folosite pentru mai multe scopuri. Tabelul următor prezintă aceste caractere.

Aceste caractere au o semnificaţie specială pentru compilatorul de C. Caracterele de punctuaţie din setul de caractere reprezentabile C care nu apar în acest tabel pot fi utilizate numai în şiruri, constante caracter şi comentarii.

Caracter Nume Caracter Nume , Virgulă ! Semnul

exclamării . Punct | Bară verticală ; Punct şi virgulă / Slash : Două puncte \ Backslash ? Semnul

întrebării ~ Tilda

’ Apostrof _ Underscore ” Ghilimele # Diez ( Paranteză

stânga % Procent

) Paranteză dreapta

& Ampersand

[ Paranteză dreaptă stânga

^ Săgeată sus

] Paranteză dreaptă dreapta

* Asterisc

{ Acoladă stânga - Minus } Acoladă dreapta = Egal > Mai mare + Plus < Mai mic

3.3.4. Secvenţe escape

Secvenţele escape sunt combinaţii speciale de caractere formate din whitespace şi caractere negrafice constituite în şiruri şi constante caracter. Ele sunt în mod tipic utilizate pentru a specifica

Page 61: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

55

acţiuni precum carriage return şi tab pe terminale şi imprimante şi pentru a furniza reprezentarea caracterelor care normal au înţeles special, cum ar fi ghilimelele (”). O secvenţă escape constă dintr-un backslash urmat de o literă sau combinaţii de cifre. Setul complet de secvenţe escape cuprinde:

\a caracterul BEL - activare sunet \b caracterul BS (backspace) - revenire cu un spaţiu \f caracterul FF (form feed) - salt de pagină la imprimantă \n caracterul LF (line feed) - rând nou \r caracterul CR (carriage return) - revenire la coloana 1 \t caracterul HT (horizontal tab) - tab orizontal \v caracterul VT (vertical tab) - tab vertical \\ caracterul \ (backslash) \" caracterul " (double qoute) - ghilimele \' caracterul ' (single qoute) - apostrof \0 caracterul NULL \ooo - constantă octală \xhh - constantă hexazecimală

Backslash-ul care precede un caracter neinclus în lista de mai

sus este ignorat şi acest caracter este reprezentat ca un literal. De exemplu, forma „\c” reprezintă caracterul c într-un literal sau într-o constantă caracter. Secvenţele \ooo şi \xdd permit scrierea oricărui caracter din setul ASCII ca un număr octal format din trei cifre sau ca un număr hexagesimal format din două cifre.

Exemplu: '\6' '\x6' 6 ASCII

'\60' '\x30' 48 ASCII

'\137' '\x5f' 95 ASCII

Numai cifrele octale (de la 0 la 7) pot apare într-o secvenţă escape octală şi trebuie să apară cel puţin o cifră. De exemplu, caracterul backspace poate fi scris ca „\10” în loc de „\010”.

Similar, o secvenţă hexagesimală poate să conţină cel puţin o cifră, iar a doua cifră poate fi omisă. Totuşi, când se utilizează secvenţe escape în şiruri, este indicat să se scrie toate cele trei cifre ale secvenţei. Altfel, caracterul care urmează după secvenţa escape ar putea fi interpretat ca o parte a secvenţei, dacă se întâmplă să fie o cifră octală sau hexagesială. De exemplu, secvenţa \0331 este interpretată drept ESC şi 1. Dacă am scrie \331, omiţând primul zero, atunci am avea o interpretare greşită.

Page 62: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

56

Secvenţele escape permit caractere de control negrafice pentru a fi transmise către display. Caracterele negrafice trebuie totdeauna reprezentate ca secvenţe escape. Plasând necorespunzător un caracter negrafic în programe C, el are rezultat imprevizibil.

3.4. Identificatori

Identificatorii sunt nume ce sunt date variabilelor, funcţiilor şi

etichetelor utilizate în program. Un nume este o succesiune de litere şi eventual cifre, primul caracter fiind literă. În calitate de litere se pot utiliza literele mici şi mari ale alfabetului englez, precum şi caracterul subliniere (_). Numărul de caractere care intră în componenţa unui nume nu este limitat. Numele sunt utilizate pentru a defini diferite variabile sau funcţii într-un program C.

În mod implicit, numai primele 32 de caractere dintr-un nume sunt luate în considerare, adică două nume sunt diferite dacă ele diferă în primele 32 de caractere ale lor. Exemple de nume: a, b1, a1b2c3, Fs, _hG, Nume, nUME, …

Se recomandă ca numele să fie sugestive, adică ele să sugereze pe cât posibil scopul alegerii lor sau a datei pe care o reprezintă.

3.5. Cuvintele cheie ale limbajului C

În limbajul C există un număr de cuvinte care au o utilizare

predefinită, numite cuvinte cheie. Utilizatorul nu poate să utilizeze aceste cuvinte pentru a denumi variabile sau funcţii într-un program. Tabelul următor prezintă cuvintele cheie ale limbajului C: Cuvintele cheie ale limbajului C auto default float register struct volatile

break do for return switch while

case double goto short typedef char

else if signed union const enum

int sizeof unsigned continue extern long

static void

3.6. Constante

În C, constantele se referă la valori fixe pe care programul nu le poate modifica. Constantele pot fi: întregi, în virgulă mobilă sau reale, constante-caracter, constante-şir sau enumerări. Zero poate fi folosit ca o constantă pentru tipurile pointer, iar şirurile de caractere sunt de fapt constante de tip char[]. Este posibil, de asemenea, să se specifice

Page 63: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

57

constante simbolice. O constantă simbolică este un nume a cărui valoare nu poate fi modificată în domeniul său.

În C există trei feluri de constante simbolice: 1. orice valoare de orice tip poate fi folosită ca şi

constantă prin adaugarea cuvântului cheie const la definirea sa; 2. un set de constante întregi definite ca o enumerare; 3. orice nume de vector sau funcţie.

3.6.1. Constante caracter O constantă caracter este un caracter inclus între apostrofuri. De

exemplu, 'a', 'A' şi '%' sunt constante caracter. Valoarea unei constante caracter este chiar valoarea numerică corespunzătoare caracterului dat în setul de caractere al maşinii. De pildă, dacă pe un calculator caracterele se reprezintă în cod ASCII, atunci constanta '1' are valoarea 0618, 4910 sau 3116. Constantele caracter pot fi folosite în operaţii de calcul exact ca şi întregii.

Caracterele pot fi reprezentate şi prin secvenţe escape (de exemplu, prin constanta '\n' se introduce caracterul newline).

3.6.2. Constante întregi Constantele întregi se reprezintă în 4 forme: zecimale, octale,

hexazecimale şi constante caracter. Constantele zecimale sunt cel mai frecvent folosite şi se reprezintă ca şiruri de cifre zecimale. O constantă care începe cu zero urmat de x (0x) este un număr hexazecimal, iar o constantă care începe cu zero este un număr octal. Pentru a reprezenta cifrele hexazecimale 10,...,15 se folosesc literele a,...,f sau literele mari corespunzătoare. Notaţiile octale şi hexazecimale sunt utile în exprimarea succesiunilor de biţi. Exemplu: int hex = 0xFF; /* numărul 255 în zecimal */

int oct = 011 ; /* numărul 9 în zecimal */

3.6.3. Constante în virgulă mobilă O constantă în virgulă mobilă are tipul float, double sau long

double. Compilatorul, ca şi în cazul constantelor întregi, trebuie să semnaleze eroare în cazul în care constantele sunt prea mari pentru a putea fi reprezentate.

Exemple de constante în virgulă mobilă: 123.23 .23 0.23 1.0 1. 1.2e10 1.256-15

Page 64: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

58

Observaţie. În interiorul constantelor întregi sau reale nu pot apare spaţii albe. De exemplu, 56.62 e - 17 nu este o constantă în virgulă mobilă, ci sunt de fapt 4 atomi lexicali: 56.62, e, -, 17 şi se va genera o eroare de sintaxă.

Dacă se doreşte o constantă de tip float, aceasta se poate defini astfel: const float pi8 = 3.14159265;

3.6.4. Constante şir Constantele şir constau din caractere cuprinse între ghilimele,

ca în faimosul “Hello, world\n“. Constantele şir, spre deosebire de altele, au o locaţie în memoria calculatorului. Caracterele dintr-un şir sunt stocate în memorie, iar valoarea numerică a constantei este adresa acestei memorii. În plus, compilatorul stochează caracterul null ‘\0’ la sfârşitul şirului, marcând astfel sfârşitul său.

În cazul setului de caractere ASCII, constanta şir “0“ arată astfel în memorie:

3000 48 (30H)

‘0’ 0 ‘\0’

iar valoarea constantei este adresa sa din memorie (în exemplul de mai sus valoarea 3000), pe când valoarea caracterului 0 este 48 sau 30H.

Cea mai scurtă constantă şir este şirul null scris drept “ “ şi este stocat în memorie ca un singur caracter null ‘\0’. De exemplu, dacă avem constanta şir “ABC“ atunci, la o anumită adresă de memorie vom avea:

Adresa 61

‘A’ Adresa+1 62

‘B’ Adresa+2 63

‘C’ Adresa+3 0

‘/0’ Valoarea constantei şir “ABC“ va fi Adresa, adică valoarea

adresei locaţiei în care se stochează primul caracter din şir. Ca o ultimă remarcă, vom face precizarea că din punctul de

vedere al reprezentării, constanta caracter ‘A’, spre exemplu, este

Page 65: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

59

diferită de consta şir “A“, care se stochează în memorie la o anumită adresă şi se termină cu caracterul null, deci are alocaţi doi octeţi.

Fiecare constantă şir conţine cu un caracter mai mult decât numărul de caractere din şir deoarece aceasta se termină totdeauna cu caracterul \0 care are valoarea 0.

De exemplu, sizeof("asaf") va fi 5. Tipul unui şir este vector de un număr de caractere a.i. "asaf"

are tipul char[5]. Şirul vid se notează prin " " şi are tipul char[1]. De notat că, pentru fiecare şir s, strlen(s) == sizeof(s) - 1, deoarece funcţia strlen() nu numără şi terminatorul \0.

În interiorul unui şir se poate folosi convenţia de notaţie cu \. Aceasta face posibilă reprezentarea caracterului ghilimele (") şi \ în interiorul unui şir. Cel mai frecvent caracter folosit este caracterul '\n'=newline (NL).

De exemplu, instrucţiunea: printf ("beep at end of message\007\n");

determină scrierea unui mesaj, a caracterului BEL şi a caracterului NL. O secvenţă de forma \n într-un şir nu determină introducerea unui caracter NL în şir, ci este o simplă notaţie (\n este caracter neafişabil).

Nu este permisă continuarea şirurilor de caractere de pe o linie pe alta.

Atunci când se include o constantă numerică într-un şir de caractere utilizând notaţia octală sau hexazecimală este recomandat să se folosească 3 cifre pentru număr. Exemplu: char v1[] = "a\x0fah\0129";//'a' 'x0f' 'a' 'h' '\012' '9' char v2[] = "a\xfah\ 129"; /* 'a' 'xfa' 'h' '\12' '9' */

char v3[] = "a\xfad\127"; /* 'a' 'xfa' 'd' '\127' */

3.6.5. Constanta zero Zero poate fi utilizat ca o constantă pentru tipurile întregi, în

virgulă mobilă sau pointer. Nu se recomandă alocarea unui obiect la adresa zero. Tipul lui zero va fi determinat de context.

3.6.6. Obiecte constante Cuvântul cheie const poate fi inclus într-o declaraţie a unui

obiect pentru a determina ca tipul acestui obiect să fie constant şi nu variabil. Exemplu : const int model = 145; const int v[ ] = {1, 2, 3, 4};

Page 66: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

60

Deoarece nu i se poate atribui o valoare, o constantă poate fi doar iniţializată. Declarând ceva ca fiind constant, ne asigurăm că valoarea sa nu se modifică în domeniul său. Astfel instrucţiunile: model = 165; /* eroare */

model++; /* eroare */

vor determina apariţia unor mesaje de eroare corespunzătoare. De notat că const modifică un tip ceea ce înseamnă că

restricţionează felul în care se poate utiliza un obiect, şi nu modul de alocare. Pentru o constantă, compilatorul nu rezervă memorie deoarece i se cunoaşte valoarea (precizată la iniţializare). Mai mult, iniţializatorul pentru o expresie constantă este, de obicei (dar nu întotdeauna), o expresie constantă. Dacă este aşa, aceasta poate fi evaluată în timpul compilării.

3.6.7. Enumerări Folosirea cuvântului cheie enum este o metodă alternativă

pentru definirea constantelor întregi, ceea ce este uneori mult mai util decât utilizarea lui const. De exemplu,

enum {ASM , AUTO , BREAK }; defineşte 3 constante întregi denumite enumeratori şi le atribuie valori.

Deoarece valorile enumeratorilor sunt atribuite implicit, începând cu 0, aceasta este echivalentă cu: const ASM = 0; const AUTO = 1;

const BREAK = 2;

O enumerare poate avea nume. De exemplu, enum Keyword {ASM , AUTO , BREAK };

defineşte o enumerare cu numele Keyword. Numele enumerării devine sinonim cu int şi nu cu un nou tip. Declararea unei variabile Keyword în loc de int poate oferi atât utilizatorului, cât şi compilatorului, o sugestie asupra modului de utilizare. De exemplu,

enum Keyword Key; //declara var. Key de tip enum Keyword

switch (Key) {

case ASM:

...........

break;

case BREAK:

...........

break; }

determină compilatorul să iniţieze un avertisment deoarece sunt folosite numai două din cele trei valori ale lui Key.

Page 67: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

61

Capitolul IV

OPERANZI ŞI OPERATORI ÎN C 4.1. Operanzi

O expresie, în limbajul C, este formată dintr-un operand sau mai mulţi legaţi prin operatori. Un operand poate fi:

- o constantă; - o constantă simbolică; - numele unei variabile; - numele unui tablou; - numele unei structuri; - numele unui tip; - numele unei funcţii; - elementele unui tablou; - elementele unei structuri; - o expresie inclusă între paranteze rotunde.

Unui operand îi corespunde un tip şi o valoare. Dacă tipul operandului este bine precizat la compilare, valoarea operandului se determină fie la compilare, fie la execuţie.

Exemple:

1. 6353 – este o constantă întreagă zecimală de tip int şi reprezintă un operand constant de tip int.

2. float x2 – reprezintă declaraţia variabilei x2, iar numele x2 reprezintă un operand de tipul float.

3. 0xa13d – este o constantă întreagă hexazecimală de tip unsigned şi reprezintă un operand de tipul unsigned.

4. produs(a,b) – este un apel al funcţiei produs. Această funcţie reprezintă un operand al cărui tip coincide cu tipul valori returnate de funcţia produs.

4.2. Operatori

Operatorii pot fi unari sau binari în funcţie de numărul de

operanzi cărora li se aplică. Un operator unar se aplică unui singur operand, iar un operator binar se aplică la doi operanzi. Operatorul

Page 68: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

62

binar se aplică la operandul care îl precede imediat şi la care îl urmează imediat.

Operatorii limbajului C nu pot avea ca operanzi constante şir (şiruri de caractere). C are mai multe clase generale de operatori: aritmetici, relaţionali şi logici, operatori pentru prelucrare biţi, precum şi câţiva operatori speciali pentru sarcini particulare.

La scrierea unei expresii se pot utiliza operatori din toate clasele. La evaluarea unei astfel de expresii este necesar să se ţină seama de priorităţile operatorilor care aparţin diferitelor clase de operatori, de asociativitatea operatorilor de aceeaşi prioritate şi de regula conversiilor implicite.

4.2.1. Operatori aritmetici Lista operatorilor aritmetici este următoarea:

+ reprezintă operatorul plus unar sau binar, în funcţie de context - reprezintă operatorul minus unar sau binar, în funcţie de context * reprezintă operatorul de înmulţire (binar) / reprezintă operatorul de împărţire (binar) % reprezintă operatorul modulo (binar)

Operandul operatorului unar plus trebuie să fie de tip aritmetic sau pointer, iar rezultatul este valoarea operandului. Un operand întreg presupune o promovare a întregilor.

Operandul operatorului unar minus trebuie să fie de tip aritmetic, iar rezultatul este numărul negativ corespunzător. Un operand întreg presupune promovarea întregilor.

Operanzii operatorilor * şi / trebuie să fie de tip aritmetic, iar ai lui % trebuie să fie de tip întreg. Operatorul binar / reprezintă câtul, iar % oferă restul împărţirii primului operand la al doilea. Dacă al doilea operand al operatorului / sau % este zero, rezultatul este nedefinit. Pentru operanzi de tip întreg este adevărată egalitatea:

(a / b) * b + a % b = a În expresii operatorii binari + şi - au aceeaşi precedenţă, care

însă este mai mică decât a grupului *, / şi %. Precedenţa ultimului grup este mai mică decât cea a operatorilor unari + şi -. Folosirea parantezelor în expresii poate schimba precedenţa între operatori în timpul evaluării acestora. Exemplu: Dacă a, b, c, d sunt variabile de tip int, atunci:

- expresia d * b % a este echivalentă cu (d * b) % a; - expresia -a / d este echivalentă cu (-a) / d;

Page 69: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

63

- expresia a=b=c=d-15 este echivalentă cu a=(b=(c=(d -15)));

- expresia a%-b*c este echivalentă cu (a%(-b))*c;

4.2.2. Operatori de incrementare şi decrementare În C, operaţiile de forma i = i+1 şi j = j-1 pot fi programate

folosind doi operatori unari specifici şi anume ++ pentru incrementare cu 1 şi -- pentru decrementare cu 1. Aceşti operatori pot fi folosiţi atât ca prefix pentru variabile (de exemplu, ++i, --j) sau ca sufix (i++, j--). Între aceste moduri de utilizare există diferenţe. Astfel, în expresia ++i, variabila i este incrementată înainte de a-i folosi valoarea, în timp ce în expresia i++, variabila i este incrementată după întrebuinţarea valorii acesteia. Exemplu: Considerăm secvenţa: x = 10;

y = ++x;

Dacă se afişează y, atunci vom găsi y = 11 deoarece mai întâi se incrementează x şi apoi se atribuie valoarea lui y. Dacă scriem: x = 10; y = x++;

vom găsi y=10 (mai întâi se face atribuirea lui x la y şi apoi incrementarea lui x).

Precedenţa tuturor operatorilor aritmetici este:

Înaltă ++ -- + - (unari) * / %

Scăzută + - (binari) Operatorii de aceeaşi precedenţă sunt evaluaţi de al stânga la

dreapta.

4.2.3. Operatori relaţionali Operatorii relaţionali permit compararea a două valori şi luarea

unei decizii după cum rezultatul comparării este adevărat sau fals. Dacă rezultatul operaţiei este fals, atunci valoarea returnată este zero, iar dacă este adevărat, valoarea returnată este 1.

Operatorii relaţionali folosiţi în C sunt:

Page 70: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

64

== != < <= > >=

egal diferit mai mic strict mai mic sau egal mai mare strict mai mare sau egal

Operatorii relaţionali au o precedenţă mai mică decât operatorii

aritmetici, astfel o expresie de forma a < b + c este interpretată ca a<(b+c).

4.2.4. Operatori logici Operatorii logici binari && (ŞI, AND) şi || (SAU, OR) precum

şi operatorul logic unar de negare “!“ (NOT), atunci când sunt aplicaţi unor expresii, conduc la valori întregi 0 şi 1, cu semnificaţia fals şi adevărat. Semantica acestor operatori se deduce din tabelul următor, unde e1 şi e2 sunt două expresii:

e1 e2 e1&&e2 e1||e2 ! e1

zero zero

diferit de zero diferit de zero

zero diferit de zero

zero diferit de zero

0 0 0 1

0 1 1 1

1 1 0 0

Expresiile legate prin operatori logici binari sunt evaluate de la

stânga la dreapta. Precedenţa operatorilor logici şi relaţionali este următoarea:

! > >= < <= == !=

Înaltă

&&

Scăzută ||

Astfel, expresia: 10>5 && !(10<9) || 3<4 este adevarată; expresia: 1 && !0 || 1 este adevarată; expresia; 1 && ! (0 ||1) este falsă.

Programul următor tipăreşte numerele pare cuprinse între 0 şi 100. # include <stdio.h>

main ()

Page 71: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

65

{ int i;

for (i = 0; i <= 100; i++)

if (! (i%2)) printf ("%d" , i); }

Operatorii logici şi relaţionali sunt utilizaţi în formarea instrucţiunilor repetitive precum şi a instrucţiunii if.

4.2.5. Operatori logici la nivel de bit Ne reîntoarcem la cei trei operatori de tip booleean & (AND,

§I), | (OR, SAU) şi ~ (NOT) precum şi la un al patrulea operator, denumit SAU-EXCLUSIV ^ (EXCLUSIVE-OR). Aceşti operatori se aplică la nivel de bit sau grupuri de biţi, după tabelele:

x x x 0 1

AND &

0 1

OR |

0 1

NOT ~

EXCLUSIVE-OR ^

y

0 1

0 0 0 1

y 0 1

0 11 1

y

0 1

10

y

0 1

0 1 1 0

În C, aceşti operatori se aplică în paralel biţilor corespunzători

aflaţi în orice poziţie. Din această cauză ei se mai numesc şi operatori logici pe bit. Trebuie făcută distincţia faţă de operatorii logici, care folosesc notaţii dublate: &&, ||, sau !. Operatorii logici au aceleaşi denumiri, dar ei tratează întregul operator ca pe o singură valoare, adevărată sau falsă. În scriere, se mai foloseşte şi denumirea bit-and, bit-or, bit-negate sau exclusive-or.

Ca exemplu, considerăm operaţia bit-not. Fie numărul binar: N2 = 0000000000000111 = 0x0007 = 710

Negarea sa pe bit se realizează cu instrucţiunea ~0x7 sau ~07 sau ~7

şi valoarea sa va fi ~N2 = 1111111111111000 = 0xFFF8 sau 0177770

pe un computer cu întreg pe 16 biţi sau 0xFFFFFFF8 pe un computer cu întreg pe 32 de biţi.

Exemplul următor realizează un SAU şi un ŞI pentru două

caractere: ‘a’ | ’c’ = 0110 0001 | 0110 0011 = 0110 0011 = ‘c’ ‘a’ & ’c’ = 0110 0001 & 0110 0011 = 0110 0010 = ‘a’

Page 72: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

66

Deoarece limbajul C a fost gândit să înlocuiască limbajul de asamblare în majoritatea operaţiilor de programare, acesta trebuie să aibă capacitatea să suporte toţi (sau cel puţin mulţi) operatorii utilizaţi în asamblare.

Operatorii pentru prelucrarea biţilor se aplică biţilor dintr-un byte sau cuvânt, ambele variabile de tip char şi short int. Aceşti operatori nu se aplică tipurilor float, double, long double, void sau altor tipuri mai complexe.

Operatorii pentru prelucrarea biţilor utilizaţi în C sunt:

& | ^ ~ >> <<

AND OR exclusive OR (XOR) complement faţă de unu (NOT) deplasare dreapta deplasare stânga

Operaţiile pe biţi sunt utilizate de obicei în drivere, pentru

testarea şi mascarea anumitor biţi. De exemplu, operaţia AND poate fi folosită pentru ştergerea unor biţi dintr-un byte sau dintr-un cuvânt, OR poate fi folosită pentru setarea unor biţi, iar XOR pentru complementarea unor biţi şi testarea egalităţii a 2 bytes.

Observaţie: Operatorii relaţionali şi logici (&&, ||, !,...) produc totdeauna un rezultat care este fie 0, fie 1, pe când operatorii similari destinaţi prelucrării biţilor pot produce orice valoare arbitrară, în concordanţă cu operaţia specifică.

Operatorii >> şi << deplasează toţi biţii dintr-o variabilă la dreapta, respectiv la stânga. Forma generală a operaţiilor de deplasare este:

variabilă >> număr_de_poziţii_bit - pentru deplasare dreapta. variabilă << număr_de_poziţii_bit - pentru deplasare stânga.

În poziţiile rămase libere, după deplasare, se introduc zerouri. Operaţiile de deplasare pot fi utile când se decodifică perifericele de intrare cum ar fi convertoarele D/A (digital/analogice) şi când se citesc informaţii de stare. Operatorii de deplasare se pot utiliza şi pentru realizarea cu rapiditate a operaţiilor de înmulţire şi împărţire. Se ştie că o deplasare stânga cu 1 bit realizează înmulţirea cu 2, iar o deplasare dreapta cu 1 bit realizează o împărţire cu 2.

Exemplu: x = 7; 0 0 0 0 0 1 1 1 7

Page 73: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

67

x << 1; 0 0 0 0 1 1 1 0 14

x << 3; 0 1 1 1 0 0 0 0 112

x << 2; 1 1 0 0 0 0 0 0 192 x >> 1; 0 1 1 0 0 0 0 0 96

x >> 2; 0 0 0 1 1 0 0 0 24

Următorul exemplu evidenţiază efectul operatorilor de deplasare: # include <stdio.h>

void disp_binary();

/* prototipul functiei disp_binary() */

void main() {

int i = 1, t;

for (t=0;t<8;t++) {

disp_binary(i);

i=i<<1;} printf (" \n");

for (t=0;t<8;t++) {

i=i>>1;

disp_binary(i);}}

void disp_binary(int i)

/* se defineste functia disp_binary() */

/* care afiseaza bitii dintr-un byte */

{register int t;

for (t=128;t>0;t=t/2)

if (i&t) printf("1");

else printf("0");

printf("\n");}

Programul produce următoarea ieşire: 0 0 0 0 0 0 0 1

0 0 0 0 0 0 1 0

. . . . . . . . . . . .

1 0 0 0 0 0 0 0

1 0 0 0 0 0 0 0

0 1 0 0 0 0 0 0

. . . . . . . . . . . .

0 0 0 0 0 0 0 1

Deşi limbajul C nu conţine un operator de rotire, se poate realiza o funcţie care să efectueze această operaţie. De exemplu rotirea la stânga cu o poziţie a numărului 10101010 ne conduce la numărul 01010101 şi se realizează după schema:

1 0 1 0 1 0 1 0

0 1 0 1 0 1 0 1

Page 74: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

68

O posibilitate de realizare a operaţiei de rotire necesită utilizarea unei uniuni cu două tipuri de date diferite. De exemplu utilizând uniunea:

union rotate {

char ch[1];

unsigned int i;

} rot;

Următoarea funcţie realizează o rotire cu 1 bit. void rotate_bit(union rotate *rot)

{rot->ch[1]=0;

rot->i=rot->i<<1;

if (rot->ch[1]) rot->i=rot->i|1;}

Atât întregul i cât şi cele două caractere ch[0] şi ch[1] partajează primii doi octeţi din cei 4 rezervaţi de uniune.

Numărul de rotit se introduce (pe 8 biţi) în ch[0]. Se roteşte apoi întregul i (deci se rotesc toţi cei 4 octeţi care îi corespund). Se testează MSB al lui ch[0] care se găseşte în urma rotirii în poziţia LSB din ch[1]. Dacă este 1, atunci se setează la 1 LSB din ch[0], realizându-se astfel operaţia de rotaţie.

Un exemplu de program care să utilizeaze această funcţie: # include <stdio.h>

union rotate {

char ch[1];

unsigned int i;

} rot;

void disp_binary();

void rotate_bit();

void main() {

register int t;

rot.ch[0]=147;

for (t=0;t<7;t++) {

disp_binary(rot.i);

rotate_bit(&rot);}}

/* se defineste functia rotate_bit() */

void rotate_bit(union rotate *rot)

{rot->ch[1]=0;

rot->i=rot->i<<1; if (rot->ch[1]) rot->i=rot->i|1;}

/* se defineste functia disp_binary() */

void disp_binary(int i)

{register int t;

for (t=128;t>0;t=t/2)

if (i&t) printf("1");

else printf("0");

printf("\n");}

Page 75: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

69

Acest program realizează rotirea numărului 14710=100100112

cu 6 poziţii 10010011

00100111

01001110

10011100

00111001

01110010

11100100

Programul de mai sus funcţionează pentru numere reprezentabile pe un octet (mai mici de 255). Dacă dorim să facem o rotire pe doi octeţi, atunci se poate modifica programul de mai sus după cum urmează: # include <stdio.h>

union rotate {

char ch[3];

unsigned int i;

} rot;

void disp_binary();

void rotate_bit();

void main() {

register int t;

rot.i=17843;

for (t=0;t<7;t++) {

disp_binary(rot.i);

rotate_bit(&rot);}}

/* se defineste functia rotate_bit() */

void rotate_bit(union rotate *rot)

{rot->ch[2]=0;

rot->i=rot->i<<1;

if (rot->ch[2]) rot->i=rot->i|1;}

/* se defineste functia disp_binary() */

void disp_binary(int i)

{register int t;

for (t=32768;t>0;t=t/2)

if (i&t) printf("1");

else printf("0");

printf("\n");}

Operatorul " ~ " realizează complementul faţă de 1. O utilizare interesantă a complementului faţă de 1 este aceea că ne permite să vedem setul caracterelor extinse implementate în calculator: # include <stdio.h>

# include <conio.h>

void main() {char ch;

do {ch = getch();

printf ("%c %c\n", ch, ~ch);} while (ch != 'q');}

Page 76: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

70

4.2.6. Operatorul de atribuire

În C, operatorul de atribuire (asignare) este semnul egal (=). Valoarea expresiei din dreapta se atribuie variabilei din stânga operatorului "=". În C, forma:

suma = a + b + c;

trebuie privită ca o nouă expresie, numită expresie de asignare. Valoarea ei este chiar valoarea expresiei din dreapta operatorului de atribuire. Dacă într-o expresie se fac mai multe atribuiri, atunci evaluarea se face de la dreapta la stânga:

x = y = z = 0 este echivalentă cu (x=(y=(z=0))); O expresie de atribuire de forma x = x + 5 în care variabila

din stânga apare imediat după operatorul = se poate scrie într-o formă compactă de tipul x += 5, unde operatorul += este tot un operator de atribuire. Majorităţii operatorilor binari le corespund operatori de atribuire de forma "op = " unde op poate fi : +, -, *, %, <<, >>, &, ^ ,

Operatorii de atribuire (asignare) sunt:

= , += , -= , *= , /= , %= , <<= , >>= , &= , ^= , |= Deci, o expresie de asignare de forma :

var = (var) op (expr)

unde var este o variabilă şi expr este o expresie, admite o reprezentare compactă de forma:

var op= expresie

Într-o formă compactă, ca mai sus, var este evaluată o singură dată.

4.2.7. Operatorul sizeof Operatorul sizeof returnează numărul de octeţi necesar

memorării variabilei sau tipului datei care este operandul său. Dacă sizeof operează asupra unui tip de date, atunci tipul trebuie să apară între paranteze. De exemplu, sizeof(char) va fi 1, sizeof(int) va fi 4 etc., deci rezultatul este un număr întreg, fără semn. Fişierul standard stddef.h defineşte tipul size_t al rezultatului oferit de operatorul sizeof. Dacă sizeof se aplică unui tablou, rezultatul este numărul total de octeţi din tablou. Exemplul din programul următor prezintă dimensiunea principalelor tipuri de date:

# include <stdio.h>

# include <stddef.h>

Page 77: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

71

void main(){

printf("\nTip caracter pe %d octet",sizeof(char));

printf("\nTip short int pe %d octeti",sizeof(short int));

printf("\nTip int pe %d octeti",sizeof(int));

printf("\nTip long int pe %d octeti",sizeof(long int));

printf("\nTip float pe %d octeti",sizeof(float));

printf("\nTip double pe %d octeti",sizeof(double));

printf("\nTip long double pe %d octeti\n", sizeof(long

double));}

În urma execuţiei acestui program, se va afişa (rezultatele depind de tipul de procesor sau de compilator):

Tip caracter pe 1 octet

Tip short int pe 2 octeti

Tip int pe 4 octeti

Tip long int pe 4 octeti

Tip float pe 4 octeti

Tip double pe 8 octeti

Tip long double pe 8 octeti

4.2.8. Operatorul ternar ? Operatorul " ? " poate fi utilizat pentru a înlocui instrucţiunea if

/ else având forma: if (conditie)

expresie1

else

expresie2

Operatorul ternar " ? " necesită trei operanzi şi are forma generală: Expr1 ? Expr2 : Expr3

unde Expr1, Expr2 şi Expr3 sunt expresii. Se evaluează expresia Expr1. Dacă este adevărată, se evaluează

Expr2, care devine valoarea întregii expresii. Dacă Expr1 este falsă, se evaluează Expr3, iar valoarea acesteia devine valoarea întregii expresii: Exemplu: x = 10; y = x > 9 ? 100 : 200;

Cum 10 > 9, valoarea lui y va fi 100. Dacă x ar fi mai mic decât 9, y va primi valoarea 200.

Acelaşi program scris cu if /else va fi: x = 10;

if (x > 9) y = 100;

else y = 200;

Page 78: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

72

În alcătuirea expresiilor din declaraţia operatorului ternar " ? " pot fi folosite şi funcţii: Exemplu: # include <stdio.h>

f1();

f2(); // prototipurile functiilor f1() si f2()

void main() {

int t;

printf (": ");

scanf("%d",&t); // se introduce numarul intreg t

t?f1()+f2(t): printf(" S-a introdus zero\n");} f1() {printf ("S-a introdus "); }

f2(int n) {printf ("%d\n", n);}

Dacă se introduce zero, atunci va fi apelată printf() şi va afişa " S-a introdus zero". Dacă se introduce alt număr, atunci programul va executa atât funcţia f1(), cât şi funcţia f2().

4.2.9. Operatorul virgulă Operatorul virgulă se utilizează într-un şir în care se introduc

mai multe expresii. Astfel, instrucţiunea: x = (y = 3, y+1),

are că efect atribuirea valorii 4 variabilei x. Deci expresiile separate prin virgulă sunt evaluate de la stânga

la dreapta, prima expresie evaluată căpătând valoarea void. Dacă se utilizează un operator de atribuire, valoarea atribuită variabilei din stânga operatorului de atribuire este valoarea ultimei expresii din dreapta, după evaluare. Exemplu: y = 10;

x = (y = y - 5, 30 / y);

Variabila x va căpăta valoarea 6. Observaţie Deoarece operatorul virgulă are o precedenţă mai

mică decât operatorul de atribuire, pentru ca atribuirile să se facă corect, trebuie utilizate paranteze.

4.2.10. Operatorul de forţare a tipului sau de conversie explicită (expresie cast)

Adesea se doreşte specificarea conversiei valorii unui operand spre un tip dat. Acest lucru este posibil folosind o construcţie de forma: (tip) operand

Printr-o astfel de construcţie valoarea operandului se converteşte spre tipul indicat în paranteze. În construcţia de mai sus (tip) se consideră că este un operator unar. Acest operator este

Page 79: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

73

cunoscut sub numele de operator de forţare a tipului sau de conversie explicită. De cele mai multe ori însă este utilizată denumirea engleză a operatorului şi anume expresie cast.

Exemplu: Presupunem că o funcţie oarecare f are un parametru de tip

double. Pentru ca această funcţie să poată fi apelată cu un parametru int n (n este un parametru de tip întreg) acesta trebuie mai întâi convertit la tipul double. Acest lucru se poate realiza printr-o atribuire: double x

f(x=n)

Un alt mod mai simplu de conversie a parametrului întreg spre tipul double este utilizarea unei expresii cast:

f((double)n) Operatorul de forţare a tipului fiind unar, are aceeaşi prioritate

ca şi ceilalţi operatori unari ai limbajului C.

4.2.11. Operatorii paranteză Parantezele rotunde se utilizează fie pentru a include o expresie,

fie la apelul funcţiilor. O expresie inclusă în paranteze rotunde formează un operand. În acest mod se poate impune o altă ordine în efectuarea operaţiilor, decât cea care rezultă din prioritatea şi asociativitatea operatorilor.

Operanzii obţinuţi prin includerea unei expresii între paranteze impun anumite limite asupra operatorilor. De exemplu, la un astfel de operand nu se pot aplica operanzii de incrementare şi decrementare sau operatorul adresă. Astfel construcţiile: (a-5+b)++ --(a+b) &(a*b)

sunt eronate. La apelul unei funcţii, lista parametrilor efectivi se include între

paranteze rotunde. În acest caz se obişnuieşte să se spună că parantezele rotunde sunt operatori de apel de funcţie.

Parantezele pătrate include expresii care reprezintă indici. Ele se numesc operatori de indexare.

Parantezele sunt operatori de prioritate maximă. Operatorii unari au prioritatea imediat mai mică decât parantezele.

4.2.12. Operatorul adresă Operatorul adresă este unar şi se notează prin caracterul

&. El se aplică pentru a determina adresa de început a zonei de

Page 80: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

74

memorie alocată unei date. În forma cea mai simplă, acest operator se utilizează în construcţii de forma: &nume unde nume este numele unei variabile simple sau al unei structuri. În cazul în care nume este numele unui tablou, atunci acesta are ca valoare chiar adresa de început a zonei de memorie alocată tabloului respectiv şi, în acest caz, nu se mai utilizează operatorul adresă &.

4.2.13. Alţi operatori ai limbajului C În limbajul C se mai utilizeză şi operatorii: „ * ” , „ . ” şi „->” Operatorul „ * ” unar (a nu se confunda cu operatorul aritmetic

binar de înmulţire) se utilizează pentru a face acces la conţinutul unei zone de memorie definită prin adresa ei de început. Se obişnuieşte să se spună că operatorul de adresă & este operator de referenţiere, iar operatorul „ * ” este operator de dereferenţiere.

Operatorii „ . ” şi „ -> ” se utilizează pentru a se accesa componentele unei structuri. Ei au prioritate maximă, având aceeaşi prioritate cu parantezele.

4.2.14. Regula conversiilor implicite şi precedenţa operatorilor Regula conversiilor implicite se aplică la evaluarea expresiilor.

Ea acţionează atunci când un operator binar se aplică la doi operanzi de tipuri diferite. În acest caz, operandul de tip inferior se converteşte spre tipul superior al celuilalt operand şi rezultatul este de tip superior. Înainte de toate se convertesc operanzii de tip char şi enum în tipul int.

Dacă operatorul curent se aplică la operanzi de acelaşi tip, atunci se execută operatorul respectiv, iar tipul rezultatului coincide cu tipul comun al operanzilor. Dacă rezultatul aplicării operatorului reprezintă o valoare în afara limitelor tipului respectiv, atunci rezultatul este eronat (are loc o „depăşire”).

Exemplu: Rezultatul împărţiirii 7/3 este 2 şi nu 2.5 deoarece cei doi operanzi sunt de tip întreg şi prin urmare rezultatul (care este de tip real) este şi el convertit la tipul întreg.

Dacă operatorul binar se aplică la operanzi de tipuri diferite, atunci se face o conversie înainte de execuţia operatorului, conform algoritmului umător:

1. Dacă unul din operanzi este de tip long double, atunci celălalt operand se converteşte spre tipul long double iar tipul rezultatului aplicării operatorului este de asemenea de tip long double.

Page 81: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

75

2. Altfel, dacă unul din operanzi este de tip double atunci celălalt operand se converteşte spre tipul double iar tipul rezultatului aplicării operatorului este de asemenea de tip double.

3. Altfel, dacă unul din operanzi este de tip float atunci celălalt operand se converteşte spre tipul float iar tipul rezultatului aplicării operatorului este de asemenea de tip float.

4. Altfel, dacă unul din operanzi este de tip unsigned long atunci celălalt operand se converteşte spre tipul unsigned long iar tipul rezultatului aplicării operatorului este de asemenea de tip unsigned long.

5. Altfel, dacă unul din operanzi este de tip long atunci celălalt operand se converteşte spre tipul long iar tipul rezultatului aplicării operatorului este de asemenea de tip long.

6. Altfel, unul din operanzi trebuie sa fie de tip unsigned, celălalt de tip int şi acesta se converteşte spre tipul unsigned, iar tipul rezultatului aplicării operatorului este de tip unsigned.

Precedenţele operatorilor C sunt prezentate în tabelul următor.

Operatorii aflaţi pe aceeaşi linie au aceeaşi prioritate. Ei se asociază de la stânga la dreapta, exceptând operatorii unari, condiţionali şi de atribuire, care se asociază de la dreapta la stânga.

Precedenţa Operatorul Înaltă Scăzută

() [ ] -> . ! ~ ++ -- - (type) * & sizeof * / % + - << >> < <= > >= == != & ^ | && || ? : = += -= *= /= ,

Page 82: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

76

Capitolul V

INSTRUCŢIUNI

Limbajul C posedă un set variat de instrucţiuni, set care îi

permite să realizeze principalele compuneri de operaţii: secvenţierea, repetiţia cu test final, repetiţia cu test iniţial, repetiţia cu număr cunoscut de paşi, decizia şi selecţia, saltul necondiţionat, ieşirea prematură dintr-un ciclu. Instrucţiunile pot fi clasificate în: instrucţiuni etichetate, instrucţiuni expresie, instrucţiuni compuse, instrucţiuni de selecţie, instrucţiuni repetitive, instrucţiuni de salt.

5.1. Instrucţiuni etichetate (instrucţiunea goto)

Instrucţiunile etichetate posedă etichete ca prefixe şi au forma:

etichetă: instrucţiune

Eticheta formată dintr-un identificator defineşte identificatorul ca destinaţie pentru o instrucţiune de salt, singura utilizare a sa fiind ca destinaţie a unei instrucţiuni goto. Etichetele nu pot fi redeclarate. Etichetele sunt locale în corpul funcţiei în care sunt definite. Instrucţiunea goto are următorul format:

goto etichetă

La întâlnirea instrucţiunii goto, se realizează un salt la

instrucţiunea prefixată de eticheta aflată după instrucţiunea goto. Deoarece o etichetă este locală în corpul unei funcţii rezultă că

ea este nedefinită în afara corpului funcţiei respective, deci, o instrucţiune goto nu poate face salt la o instrucţiune din afara corpului funcţiei în care este definită.

Nu este recomandată utilizarea abuzivă a acestei instrucţiuni deoarece programul devine mai puţin lizibil şi pot apare erori logice în program foarte greu de detectat. Instrucţiunea goto se utilizează în special pentru ieşirea din mai multe cicluri imbricate.

Exemplu: Următorul program utilizează instrucţiunea goto pentru a afişa numerele de la 1 la 100:

Page 83: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

77

#include <stdio.h>

void main(void)

{ int nr=1;

eticheta: printf(”%d”, nr++);

if (nr<=100)

goto eticheta;

}

5.2. Instrucţiuni expresie

Cele mai multe instrucţiuni sunt instrucţiunile expresie, care au forma:

[ expresie ]; unde expresie este opţională.

Majoritatea instrucţiunilor expresie sunt atribuiri sau apeluri de funcţii. Deci, o instrucţiune expresie constă dintr-o expresie urmată de caracterul ";". Exemplu: a = b * c + 3;

printf ("FAC. DE AUTOMATICA");

Dacă expresia expresie de mai sus lipseşte, construcţia “;“ se numeşte instrucţiune vidă. Aceasta nu are nici un efect, dar este utilizată pentru a înlocui un corp vid al unei iteraţii sau pentru a plasa o etichetă. 5.3. Instrucţiuni compuse

O instrucţiune compusă este o posibilă listă de declaraţii şi/sau

instrucţiuni închise între acolade. Exemplu: { a = b + 2; b++; }

O instrucţiune compusă se numeşte bloc. Un bloc ne permite să tratăm mai multe instrucţiuni ca pe una singură. Corpul unei funcţii este o instrucţiune compusă. Domeniul de vizibilitate al unui identificator declarat într-un bloc se întinde din punctul declaraţiei până la sfârşitul blocului. Identificatorii utilizaţi într-un bloc pot fi ascunşi prin declaraţii de acelaşi nume în blocurile interioare blocului iniţial. Exemplu: # include <stdio.h>

int x = 34; /* x este global */

void main(void) {

int *p = &x; /*p preia adresa variabilei globale*/

int x1, x2; printf("x = %d\n", x);

Page 84: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

78

{int x; /*x este local si il ascunde pe cel global */

x = 1; /* atribuirea se face la cel local */

x1 = x; printf("x = %d\n", x1);}

{ int x; /* se ascunde prima variabila locala */

x = 2; /* se atribuie valoarea 2 acestui x */

x2 = x;

printf("x = %d\n",x2); }

printf("x = %d %d %d \n",x,x1,x2); } 5.4. Instrucţiuni de selecţie

5.4.1. Instrucţiunea if O instrucţiune if cu care în C se implementează o structură de control de selecţie sau o structură alternativă, are următorul format general: if (conditie) instructiune1;

else instructiune2;

unde conditie este orice expresie care prin evaluare conduce la o valoare întreagă. Dacă valoarea expresiei este diferită de zero (condiţie adevărată), atunci se execută instructiune1; altfel, dacă valoarea expresiei este zero (condiţie falsă), se execută instructiune2. În ambele cazuri, după executarea lui instructiune1 sau instructiune2, controlul este transferat la instrucţiunea ce urmează după if. Aici, prin instructiune1 sau instructiune2 se înţelege o instrucţiune simplă, o instrucţiune compusă (un bloc) sau o instrucţiune vidă. Porţiunea else instructiune2; este opţională, în acest fel putându-se obţine o structură de selecţie cu o ramură vidă de forma: if (conditie) instructiune; Exemplu: Următorul program citeşte două numere şi afişează pe cel mai mare dintre ele. # include <stdio.h>

void main (void) {

int x, y;

printf("Introduceti doua numere intregi: \n");

scanf ("%d %d", &x, &y);

if (x > y)

printf ("Cel mai mare este : %d\n",x);

else

printf("Cel mai mare este : %d\n", y); }

Deoarece partea else dintr-o instrucţiune if este opţională, apare o ambiguitate atunci când else este omis dintr-un if inclus (încuibat).

Page 85: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

79

În C acest lucru se rezolvă prin asocierea lui else cu cel mai apropiat if. De exemplu, în secvenţa: if (x)

if (y) printf ("1");

else printf ("2");

else este asociat cu instrucţiunea if(y). Dacă dorim ca else să fie asociat cu if(x) trebuie să utilizăm acolade, astfel: if (x)

{ if (y) printf ("1");

}

else printf ("2");

Secvenţa anterioară este echivalentă cu: if (x) { if (y) printf ("1");

else ;}

else printf ("2");

5.4.2. Instrucţiuni de selecţie multiplă: if - else if Într-o instrucţiune if se poate include, pe o ramură, o altă instrucţiune if. În acest fel se creează posibilitatea de a codifica structuri de selecţie multiplă, folosindu-se perechi else if. O asemenea construcţie este de forma: if (conditie1)

instructiune1;

else if (conditie2)

instructiune2;

else if (conditie3)

instructiune3;

. . . . . . . . . . . . . . . .

else if (conditieN)

instructiuneN;

else instructiuneN+1;

În acest caz, condiţiile sunt testate în ordine. Dacă una din ele este adevărată, atunci este executată instrucţiunea corespunzătoare, după care controlul este transferat la instrucţiunea următoare din program. Codul pentru fiecare alternativă poate fi format dintr-o instrucţiune simplă (inclusiv instrucţiunea vidă) sau dintr-un bloc delimitat prin { şi }. Dacă nici una dintre expresii nu este adevărată, atunci se execută secvenţa corespunzătoare ultimei alternative introdusă prin else. Această ultimă alternativă nu este obligatorie, structura putându-se încheia după secvenţa notată cu instructiuneN.

Page 86: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

80

Exemplu: Considerăm un program care realizează conversiile inch-cm şi cm-inch. Presupunem că indicăm unitatea intrării cu i pentru inch şi c pentru centimetru: # include <stdio.h>

# include <conio.h>

void main(void) { const float fact = 2.54;

float x,in,cm;

char ch = 0;

printf ("\nIntroduceti numarul: \n");

scanf("%f",&x);

printf("\nIntroduceti unitatea: \n");

ch = getche(); /* se introduce un caracter de la

tastatura care se afiseaza pe ecran */

if (ch == 'i') {

in = x;

cm = x * fact;}

else if(ch == 'c') {

in = x/fact;

cm = x; }

else in = cm = 0;

printf("\n%5.2f in = %5.2f cm \n",in,cm); }

5.4.3. Instrucţiunea switch Într-o instrucţiune de selecţie switch, se compară, pe rând, o valoare cu constantele dintr-o mulţime şi în momentul găsirii unei coincidenţe se execută instrucţiunea sau blocul de instrucţiuni asociate acelei constante. Forma generală a instrucţiunii switch este: switch (variabila) {

case constanta1 :

secventa_instructiuni_1

break;

case constanta2 :

secventa_instructiuni_2

break;

case constanta3 :

secventa_instructiuni_3

break;

. . . . . . . . . . . . . . . . . . .

case constantaN :

secventa_instructiuni_N

break;

default :

secventa_instructiuni_N+1

}

Page 87: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

81

Instrucţiunea switch realizează transferul controlului la una din secvenţele de instrucţiuni dacă valoarea variabila ce trebuie să aibă tipul întreg coincide cu una din constantele de dupa case. Secvenţa de instrucţiuni se execută pâna se întâlneşte break, după care se trece la instrucţiunea imediat următoare după switch. Dacă nu se găseşte nici o coincidenţă, se execută secvenţa de instrucţiuni de după default, iar dacă default lipseşte, deoarece prezenţa acesteia este opţională, se trece la instrucţiunea următoare. Exemplu: Decizia din exemplul anterior poate fi realizată şi astfel: # include <stdio.h>

# include <conio.h>

void main(void) { const float fact = 2.54;

float x, in, cm;

char ch = 0;

printf ("\nIntroduceti numarul: \n");

scanf("%f", &x);

printf("\nIntroduceti unitatea: \n");

ch = getche();

switch(ch) {

case 'i': in = x;

cm = x * fact;

break;

case 'c': in = x/fact;

cm = x;

break;

default: in = cm = 0;

break; }

printf("\n%5.2f in = %5.2f cm \n",in,cm);

}

Observaţie: Constantele case trebuie sa fie distincte. Pentru a ieşi din instrucţiunea switch se foloseşte instrucţiunea break. Exemplu: # include <stdio.h>

void main (void) {

int t;

for (t = 0; t < 10; t++)

switch (t) {

case 1 :

printf ("Now");

break;

case 2 :

printf (" is ");

break;

case 3 :

Page 88: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

82

case 4 :

printf (" the ");

printf (" time for all good men \n"); break;

case 5 :

case 6 :

printf (" to ");

break;

case 7 :

case 8 :

case 9 :

printf (" . ");

break; } }

Rulând acest program, vom obţine: Now is the time for all good men

the time for all good men

to to . . .

Instrucţiunea switch este foarte eficientă în scrierea programelor care afişează pe ecran o listă de opţiuni (un meniu) din care utilizatorul alege câte una şi o execută. Instrucţiunile switch pot fi şi incluse (încuibate) una în alta. 5.5. Instrucţiuni repetitive

5.5.1. Instrucţiunea for Forma generală a instrucţiunii for este:

for (initializare; conditie; incrementare)

instructiune; unde: - initializare este o instrucţiune de atribuire utilizată pentru iniţializarea variabilei de control a ciclului. Nu există nici o restricţie privitoare la tipul său;

- condiţie este o expresie relaţională care se testează înaintea fiecărei iteraţii: dacă condiţia este adevărată (diferită de 0), ciclul se continuă; dacă condiţia este falsă (egală cu 0), instrucţiunea for se încheie;

- incrementare se evaluează după fiecare iteraţie specificând astfel reiniţializarea ciclului. Exemplu: Următorul program afişează pe ecran numerele de la 1 la 100. # include <stdio.h>

void main (void) { int x;

for (x = 1; x <= 100; x++) printf("%d ", x); }

Page 89: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

83

Nu întotdeauna ciclul for trebuie să se desfăşoare în sensul creşterii variabilei de control. Putem crea cicluri for în care variabila de control se decrementează. Exemplu: Programul următor afişează numerele de la 100 la 1. # include <stdio.h>

void main (void) {

int x;

for (x =100; x > 0; x--) printf("%d", x); }

Nu există restricţii în incrementarea sau decrementarea variabilei de control a ciclului. Exemplu: Următorul program afişează pe ecran numerele de la 0 la 100 din 5 în 5: # include <stdio.h>

void main (void) {

int x;

for (x = 0; x <= 100; x = x + 5) printf ("%d", x); }

Instructiunea instrucţiune din declaraţia ciclului for poate fi o instrucţiune simplă sau un bloc (un grup de instrucţiuni delimitate de acolade) care va fi executat repetitiv. Exemplu: Programul următor afişează pe ecran numerele de la 0 la 99, precum şi pătratul acestora: # include <stdio.h>

void main (void) { int i;

for (i = 0; i < 100; i++) {

printf (" Acesta este i : %3d", i);

printf (" si i patrat : %5d \n", i*i); } }

Exemplu: Calculul factorialului unui număr: n! = 123...n # include <stdio.h>

void main (void)

{ int n, i;

long int factorial;

printf ("Introduceti n : ");

scanf ("%d", &n);

factorial = 1; for (i = 1; i <= n; i++)

factorial *= i;

printf (" %d ! = %ld \n", n, factorial); }

Instrucţiunile for pot fi incluse una în alta. Exemplu: Programul următor parcurge un şir de caractere de la stânga la dreapta, afişând subşirurile ce au ca bază primul caracter. # include <stdio.h>

# include <string.h> void main (void)

Page 90: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

84

{ int l, n, i;

char sir[81];

puts ("Tastati un sir terminat cu <CR> : "); gets (sir);

l = strlen (sir);

for (n = 0; n <= l; ++n) {

for (i = 0; i < n; ++i)

putchar (sir[i]);

putchar ('\n'); } }

Variante ale ciclului for: Limbajul C permite mai multe variante ale ciclului for care determină creşterea flexibilităţii acestuia în diferite situaţii. Una din cele mai utilizate variante constă în folosirea a mai multe variabile de control a ciclului. În exemplul următor, atât x cât şi y sunt variabile de control a ciclului:

# include <stdio.h>

void main (void)

{ int x, y;

for (x = 0, y = 0; x + y < l00; x++, y++) printf ("%d ", x + y);

}

Acest program tipăreşte numerele de la 0 la 98 din 2 în 2. Se observă că iniţializările şi incrementările celor două variabile sunt separate prin virgulă.

Terminarea ciclului presupune testarea nu numai a variabilei de control cu anumite valori prestabilite, ci condiţia de terminare a ciclului poate fi orice expresie C corectă. Exemplu: Considerăm un program pentru antrenarea unui copil în exerciţiile de adunare. Dacă copilul vrea să se oprească se apesa tasta T, atunci când calculatorul îl întreabă dacă să continue.

# include <stdio.h>

# include <conio.h>

void main (void)

{ int i, j, raspuns;

char terminare = ' ';

for (i=1; i<100; i++) {

for (j=1; j<100 && terminare !='t';j++) {

printf ("\nCit este %d + %d ? ",i,j); scanf("%d",&raspuns);

if (raspuns != i+j) printf("\nGresit !");

else printf("\nCorect !");

printf(" Continuam ? ");

terminare = getchar(); }

/* Pentru terminare se apasa tasta t */ } }

Page 91: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

85

O altă caracteristică interesantă a ciclului for este aceea că nu se impune definirea tuturor celor trei parametri ai ciclului for, oricare dintre ei putând fi opţionali. De exemplu, ciclul următor se va executa până când de la tastatura se introduce numărul 123: for (x = 0; x != 123;) scanf("%d", &x);

Deoarece instrucţiunea de incrementare a lui x lipseşte, de fiecare dată când ciclul se repetă, programul testează ca x să fie egal cu 123, dar nu modifică pe x în nici un fel. Dacă de la tastatură se introduce 123, condiţia buclei devine falsă şi ciclul se termină. Exemplu: O variantă de calcul a lui n! ar fi următoarea: # include <stdio.h>

void main (void)

{ int n, i;

long int factorial;

printf ("Introduceti n : ");

scanf ("%d", &n);

factorial = 1;

for (i = 1; i <= n;) {

factorial *= i ++;

printf (" %d ! = %ld \n", n, factorial); } }

Bucle infinite: Una din cele mai interesante utilizări ale ciclului for constă în crearea de bucle infinite. Dacă nici una din cele trei expresii care formează ciclul for nu sunt precizate, se obţine o buclă fără sfârşit, ca în exemplul următor în care se consideră că elementul condiţie are valoarea adevărat: for (;;)

printf ("Aceasta bucla va rula la nesfirsit. \n "); Ieşirea dintr-o bucla for: Pentru terminarea unei bucle for, chiar şi a buclei for(; ;) se foloseşte instrucţiunea break care se plasează oriunde în corpul ciclului şi determină încheierea imediată a ciclului (la întâlnirea acesteia), programul continuându-se cu instrucţiunea ce urmează după instrucţiunea for.

Exemplu: Acest program va rula până când de la tastatura se apasă tasta A: # include <stdio.h>

# include <ctype.h>

void main (void)

{ for (; ;) { ch = getche ();

if (ch == 'a') break; }

printf (" Ai apasat tasta A "); }

Page 92: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

86

Utilizarea ciclurilor for fără corp (instrucţiune) : Pentru crearea unor întârzieri de timp se pot folosi cicluri for cu corp vid de forma: for (t = 0; t < O_ANUMITA_VALOARE; t++);

Observaţie: De obicei, instrucţiunea for este legată de parcurgerea unor structuri de date de tip tablou.

5.5.2. Instrucţiunea while Forma generală a instrucţiunii repetitive while este:

while (conditie)

instructiune;

unde instructiune poate fi o instrucţiune vidă, o instrucţiune simplă sau un bloc de instrucţiuni ce vor fi executate repetitiv. În timpul execuţiei se evaluează mai întâi condiţia buclei a cărei valoare trebuie să fie întreagă. Dacă valoarea calculată este diferită de 0 (condiţie adevărată), atunci instructiune se execută. Dacă, după o evaluare (inclusiv prima) rezultă o valoare 0 (condiţie falsă), atunci controlul este transferat la instrucţiunea ce urmează după while. Astfel, instrucţiunea asociată cu while se execută repetat, cât timp valoarea asociată condiţiei este diferită de 0 sau condiţia este adevărată. Exemplu: Programul următor calculează c.m.m.d.c. pentru o pereche x, y de numere întregi pozitive. # include <stdio.h>

void main (void) {

int xi, yi, x, y;

printf (" Introduceti doua numere pozitive: \n");

scanf ("%d %d", &xi, &yi);

x = xi; y = yi;

while (x != y)

if (x > y) x -= y;

else y -= x;

printf (" C.m.m.d.c. (%d, %d) = %d", xi, yi, x); }

Metoda de calcul se bazează pe faptul că: ♦ daca x > y, atunci cmmdc (x, y) = cmmdc (x-y, x); ♦ daca x < y, atunci cmmdc (x, y) = cmmdc (x, y-x); ♦ daca x = y, atunci cmmdc (x, y) = x =y . De exemplu, cmmdc (14, 21) = 7. Deoarece instrucţiunea while realizează testarea condiţiei la începutul instrucţiunii, aceasta instrucţiune este bună de utilizat în situaţiile în care nu se doreşte execuţia buclei, evident dacă condiţia nu este adevărată.

Page 93: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

87

Exemplu: Programul următor realizează centrarea unui text pe ecran: # include <stdio.h>

# include <ctype.h>

void main (void) {

char sir[255];

printf(" Introduceti un sir de caractere: \n");

gets (sir);

centreaza (strlen (sir));

printf (sir); }

/* Se calculează numărul de spaţii pentru centrarea

unui şir de

caractere cu lungimea lung */

centreaza (lung)

int lung; { lung = (80 - lung)/2;

while (lung > 0) {

printf (" "); lung--; } } Dacă dorim să programăm un ciclu infinit, atunci se poate găsi

o expresie care ramâne tot timpul adevărată. Un exemplu uzual este următorul:

while (1) { Corpul ciclului }

Ieşirea din ciclu, în acest caz, se asigură prin mecanisme de tip break, goto sau return. Corpul ciclului while poate conţine şi numai instrucţiunea vidă. De exemplu,

while ((ch = getche ()) != 'A');

este o buclă simplă care se execută până când de la tastatură se va introduce caracterul "A". Observaţie: Instrucţiunea while reprezintă mecanismul sintactic de bază pentru a programa cicluri în C. Reamintim că instrucţiunea for se foloseşte după următorul format general:

for (initializare; conditie; incrementare) instructiune; care este echivalentă semantic cu secvenţa: initializare; while (conditie) { instructiune; incrementare; }

5.5.3. Instrucţiunea do-while Spre deosebire de ciclurile programate cu while sau for, unde condiţia de ciclare este verificată la început, în cazul folosisii mecanismului do-while, condiţia se evaluează după execuţia secvenţei

Page 94: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

88

de instrucţiuni ce reprezintă corpul ciclului. Forma generală a buclei do-while este: do { instructiune;

} while (conditie);

Semantic, do-while este echivalentă cu secvenţa: instructiune; while (conditie)

instructiune;

Deşi acoladele nu sunt necesare când instructiune este o instrucţiune simplă, de obicei se utilizează pentru a evita confuzia cu while. Se remarcă faptul că instructiune ce reprezintă corpul ciclului (adică, o instrucţiune simplă, o instrucţiune compusă sau o instrucţiune vidă) este executată cel puţin odată. Celelalte execuţii sunt condiţionate de valoarea întreagă rezultată din evaluarea condiţiei. Dacă această valoare este 0 (condiţie falsă), atunci controlul se transferă la următoarea instrucţiune din program; în caz contrar se execută corpul ciclului şi se reevaluează condiţia. Exemplu: Următoarea secvenţă asigură preluarea corectă a unei valori întregi între 1 şi 10: # include <stdio.h>

void main (void) {

int num;

do {

printf("\n\nIntrod. un intreg între 1 si 10: ");

scanf ("%d", &num);

printf (" Numarul introdus este : %d ", num);

} while (num < 1 || num > 10); }

Un caz tipic de utilizare a instrucţiunii do-while este oferit de programele interactive în care selecţia unei opţiuni se face pe baza unui meniu afişat pe ecranul terminalului. Exemplu: Următorul program implementează o versiune a unui meniu de verificare a corectitudinii ortografice într-un text: # include <stdio.h>

# include <ctype.h>

void main (void) {

char ch;

printf ("1. Verificarea ortografiei \n ");

printf ("2. Corectarea erorilor de ortografie \n");

printf ("3. Afisarea erorilor de ortografie \n ");

do {

printf ("\n Introduceti optiunea dumneavoastra: ");

ch=getche(); // Se citeste optiunea de la tastatura

switch (ch) {

case '1':

Page 95: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

89

verifica_ortografia();

break;

case '2': corecteaza_erorile();

break;

case '3':

afiseaza_erorile();

break; }

} while (ch != '1' && ch != '2' && ch != '3'); }

După afişarea opţiunilor, programul va bucla până când se va selecta o opţiune validă. Exemplu: Adunarea elementelor a doi vectori: int a[10], b[10], c[10];

. . . . . . . . . . . . . .

i = 0;

do { c[i] = a[i] + b[i]; i = i + 1;

} while (i < 10);

sau i = 0;

do { c[i] = a[i] + b[i]; i++;

} while (i < 10);

5.5.4. Bucle încuibate Când o buclă este introdusă în altă buclă, bucla interioară se

spune a fi inclusă (nested, încuibată) în bucla exterioară. Exemplu: Programul următor afişează primele 4 puteri ale numerelor cuprinse între 1 şi 9: # include <stdio.h>

void main (void)

{ int i, j, k, p;

printf (" i i^2 i^3 i^4 \n ");

for (i = 1; i < 10; i++) {

for (j = 1; i < 5; j++) {

p = 1;

for (k = 1; i < j; k++)

p = p * i;

printf (" %9d ", p); }

printf (" \n "); } }

Când se execută acest program se obţin următoarele rezultate: i i^2 i^3 i^4

1 1 1 1

2 4 8 16

3 9 27 81

. . . . . . . . . . .

9 81 729 6561

Page 96: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

90

Alinierea rezultatelor se datoreşte utilizării în printf() a unui format de afişare corespunzător (%9d) care precizează dimensiunea minimă a câmpului specificat.

Un alt exemplu, puţin mai complex, este un program de înmulţire a două matrice. Evident, în acest caz vom avea 3 bucle for incluse una în cealaltă. // Program de inmultire a doua matrici # include <stdio.h>

float a[100][100],b[100][100],c[100][100];

float elem, s;

int la, ca, lb, cb, lc, cc, i, j, k;

void main(void) {

la=101; ca=101;

lb=ca+1; cb=ca;

printf("Program de inmultire a doua matrici\nSe declara

dimensiunile fiecarei matrici\n\n");

/* Introducem pe rand dimensiunile fiecarei matrici.

Verificam sa nu se depaseasca dimensiunile maxime si

verificam posibilitatea inmultirii matricilor */

while (ca!=lb){

printf("Se verifica daca dimensiunile declarate sunt

compatibile pentru inmultire!\n\n");

while ((la>=100)||(ca>=100)) {

printf("Intoduceti dimensiunile primei matrice");

printf("\nNr. linii matrice A = \n");

scanf("%d",&la);

printf("Nr. coloane matrice A = \n");

scanf("%d",&ca); }

while ((lb>=101)||(cb>=101)) {

printf("Intoduceti dimens. celei de-a doua matrice");

printf("\nNr. linii matrice B = \n");

scanf("%d",&lb); printf("Nr. coloane matrice B = \n");

scanf("%d",&cb); }

if(ca!=lb) {

la=101;ca=101;

lb=ca+1;cb=ca;} }

/* Se introduc matricile */

for(i=0; i<=la-1; i++)

for(j=0; j<=ca-1; j++) {

printf("a(%d,%d) = ", i, j);

scanf("%f",&elem);

a[i][j] = elem; }

for(i=0;i<=lb-1;i++)

for(j=0;j<=cb-1;j++) {

printf("b(%d,%d) = ",i,j);

scanf("%f",&elem);

Page 97: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

91

b[i][j]=elem; }

// Se calculeaza fiecare element al matricei produs

for(i=0;i<=la-1;i++) for(j=0;j<=cb-1;j++)

{ s=0;

for(k=0;k<=ca-1;k++)

s = s+a[i][k]*b[k][j];

c[i][j] = s; }

// Se afisaza matricile

printf("\n\nA = \n");

for(i=0;i<=la-1;i++)

{ printf("\n");

for(j=0;j<=ca-1;j++)

printf("%6.3f ",a[i][j]); }

printf("\n\nB = \n");

for(i=0;i<=lb-1;i++)

{ printf("\n");

for(j=0;j<=cb-1;j++)

printf("%6.3f ",b[i][j]); }

printf("\n\nC = A*B\n");

for(i=0;i<=la-1;i++)

{ printf("\n");

for(j=0;j<=cb-1;j++)

printf("%6.3f ",c[i][j]); }}

5.5.5. Instrucţiunea break Instrucţiunea break are două utilizări. Prima utilizare constă în terminarea unui case în cadrul instrucţiunii switch. A doua utilizare constă în terminarea imediată a unui ciclu scurtcircuitând testul condiţional normal al buclei. Dacă într-o buclă se întâlneşte o instrucţiune break, calculatorul termină (părăseşte) imediat bucla şi controlul programului se transferă la instrucţiunea ce urmează instrucţiunii de buclare. De exemplu, programul: # include <stdio.h>

void main (void)

{ int t;

for (t = 0; t < 100; t++) { printf (" %3d ", t);

if (t == 10) break; }}

tipăreşte numerele până la 10 şi atunci se opreşte deoarece break determină ieşirea imediată din ciclu. În cazul buclelor incluse, este important de notat ca break determină ieşirea imediată numai din bucla interioară (din bucla în care este introdus). De exemplu, programul: # include <stdio.h>

void main (void)

Page 98: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

92

{ int t;

for (t = 0; t < 100; ++t) {

count = 1; for (;;) {

printf (" %d ", count);

count++;

if (count == 10) break; } }

va afişa pe ecran numerele de la 1 la 10 de 100 de ori. Instrucţiunea break se poate utiliza şi în cadrul ciclurilor programate cu while sau do-while, schema generală de utilizare fiind următoarea: while (expresie) { . . . . . . . . . . . . . . . . . if (conditie) break; . . . . . . . . . . . . . . . . . } Dacă la una din iteraţii, condiţia din if este îndeplinită, atunci ciclul se termină automat, altfel el poate continua până când expresia din while are valoarea fals. Dacă instrucţiunea break se execută în cadrul unei instrucţiuni switch, care la rândul ei este inclusă într-un ciclu programat cu while, for sau do-while, atunci ea determină terminarea numai a instrucţiunii switch, nu şi ieşirea din ciclu.

5.5.6. Instrucţiunea continue Instrucţiunea continue, executată într-un ciclu, determină oprirea iteraţiei curente şi asigură trecerea imediată la iteraţia următoare. De exemplu, programul următor va afişa pe ecran numai numerele pare. # include <stdio.h>

void main (void)

{ int x;

for (x = 0; t < 100; x++) {

if (x % 2) continue;

printf (" %d ", x); } }

Se observă că atunci când se generează un număr impar se execută instrucţiunea continue ce va determina trecerea la iteraţia următoare by-pasând instrucţiunea printf(). În cazul instrucţiunilor while şi do-while, o instrucţiune continue determină trecerea direct la testul condiţional şi prin urmare, continuarea procesului de buclare. În cazul unui for, se realizează mai întâi operaţia de incrementare a variabilei de control a ciclului, apoi testarea condiţiei de continuare a buclei.

Page 99: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

93

Capitolul VI

TIPURI DE DATE STRUCTURATE

În C există două categorii de tipuri de date structurate:

tablourile şi structurile. Un tablou este o colecţie omogenă de valori de acelaşi tip identificate printr-un indice, iar o structură este o colecţie neomogenă de valori identificate prin nume simbolice, denumite selectori.

6.1. Tablouri unidimensionale

Un tablou este o colecţie de variabile de acelaşi tip care sunt

referite printr-un nume comun. În C, un tablou constă din locaţii de memorie contigue. Adresa cea mai mică corespunde primului element, iar adresa cea mai mare corespunde ultimului element. Un tablou poate avea de la una la mai multe dimensiuni. Accesul la un element specific al tabloului se face utilizând un index. Cel mai utilizat tablou este tabloul de caractere. Şirurile de caractere pot fi definite prin conceptele: vector de caractere şi pointer-caracter.

Declararea unui tablou cu o singură dimensiune are următoarea formă generală:

tip var_nume[size]; Aici, tip, declară tipul de bază al tabloului. Tipul de bază determină tipul de dată al fiecărui element al tabloului. var_nume este numele tabloului, iar size este numărul elementelor pe care le va conţine tabloul. Exemple: int a[10]; // vectorul a contine 10 intregi float v[3]; // vectorul v contine 3 reali În C toate tablourile folosesc pe zero ca index al primului lor element. Elementele tabloului a[10] sunt a[0],...,a[9]. Exemplu: Programul următor încarcă un tablou de întregi cu numerele de la 0 la 9: void main (void) {

int x[10]; // se rezerva 10 elemente intregi

int t;

for (t = 0; t < 10; t++) x[t] = t; }

Page 100: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

94

Pentru un tablou unidimensional, dimensiunea totală, în bytes, a acestuia va fi: Total bytes = sizeof (tip) * lungimea_tabloului Observaţie: Limbajul C nu realizează verificarea dimensiunilor unui tablou: astfel, nu există nimic care să ne oprească să nu trecem peste sfârşitul tabloului. Dacă se trece peste sfârşitul unui tablou într-o operaţie de atribuire, atunci se vor atribui valori unor alte variabile sau chiar se vor distruge părţi din program. Exemplu: Deşi următorul program este incorect, compilatorul C nu semnalează nici o eroare: void main (void) {

int crash[10], i;

for (i = 0; i < 100; i++) crash[i] = i; }

Se observă că bucla se iterează de 100 de ori, deşi vectorul crash conţine numai 10 elemente. Aceste verificări rămân în sarcina exclusivă a programatorului.

Tablourile unidimensionale sunt, de fapt, liste de informaţii de acelaşi tip. De exemplu, prin rularea programului: char ch[7];

void main (void)

{ int i;

for (i = 0; i < 7; i++) ch[i] = 'A' + i; }

vectorul “ch“ arată astfel: ch(0) ch(1) ch(2) ch(3) ch(4) ch(5) ch(6)

A B C D E F G

6.1.1. Constante şir În C o constantă şir este o secvenţă de caractere închisă între ghilimele. Exemplu: "acesta este un sir". Fiecare constantă şir conţine cu un caracter mai mult decât numărul de caractere din şir, deoarece aceasta se termină totdeauna cu caracterul NULL '\0' care are valoarea 0. De exemplu, sizeof ("asaf") = 5. Tipul unui şir este "vector de un număr de caractere"; astfel "asaf" are tipul char[5]. §irul vid este descris prin " " şi are tipul char[1]. De notat că, pentru fiecare şir s, funcţia strlen(s) din fişierul antet "string.h" întoarce numărul caracterelor din şir fără terminatorul 0, adică: strlen(s) = sizeof(s) - 1. În interiorul unui şir se poate folosi convenţia de notaţie cu \. Aceasta face posibilă reprezentarea caracterelor " şi \ în interiorul unui

Page 101: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

95

şir. Cel mai frecvent caracter folosit este caracterul '\n' = new line (NL). De exemplu, instrucţiunea:

printf("beep at end of message \007 \n "); determină scrierea unui mesaj, a caracterului BEL şi a caracterului NL. Nu este permisă continuarea şirurilor de caractere de pe o linie pe alta. Exemplu: "this is not a string

but a syntax error".

O secvenţă de forma \n într-un şir nu determină introducerea unui caracter NL în şir, ci este o simplă notaţie. Este posibil să folosim caracterul null într-un şir, dar majoritatea programelor nu testează dacă mai sunt caractere după el.

6.1.2. Iniţializarea vectorilor de caractere • Citirea unui şir de la tastatură utilizând funcţiile scanf() şi gets(). o Utilizarea funcţiei scanf(). Exemplu: # include <stdio.h>

void main (void) {

char nume[21], adresa[41];

printf ("\n Nume: ");

scanf ("%s", nume);

printf ("\n Adresa: ");

scanf ("%s", adresa);

printf ("%s\n%s\n", nume, adresa); }

S-au definit variabilele nume şi adresa ca tip şir de caractere de maximum 20 şi 40 de caractere. Şirul "%s" care apare în apelul funcţiei scanf() precizează că se vor citi în variabilele nume, respectiv adresa, valori de tip şir de caractere. În printf() descriptorul "%s" are rolul de a preciza cum trebuie convertite valorile datelor de afişat (în cazul de faţă, valorile variabilelor nume şi adresa). Funcţia scanf() citeşte un şir de caractere din bufferul de intrare până când întâlneşte un spaţiu, un caracter TAB, sau ajunge la sfârşitul acestuia. Astfel, dacă se tastează, "ENE ALEXANDRU", atunci în variabila nume se va memora doar valoarea "ENE". Pentru a obţine şirul în întregime este recomandat să se transmită numele sub forma: "ENE_ALEXANDRU". o Cea mai bună cale de a introduce un şir de la tastatură constă în utilizarea funcţiei gets() din fişierul antet "stdio.h". Forma generală a funcţiei gets() este: gets (nume_vector)

Page 102: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

96

Pentru a citi un şir se apelează gets() având ca argument numele vectorului, fără nici un index. Funcţia gets() returnează vectorul ce va păstra şirul de caractere introdus de la tastatură. gets() va continua să citească caractere până la introducerea caracterului CR. Exemplu: Programul următor afişează şirul de caractere introdus de la tastatură. # include <stdio.h> void main (void) {

char sir[80];

gets (sir); /* citeste un sir de la tastatura */

printf ("%s", sir); }

Se observă că funcţia printf() poate primi ca argument un şir de caractere. Dacă se introduce un şir mai lung decât dimensiunea tabloului, vectorul va fi suprascris. • Iniţializarea vectorilor de caractere utilizând constantele şir Folosind constantele şir, vectorii de caractere se iniţializează sub forma:

char nume_vector[size] = "sir_de_caractere" unde size = numărul caracterelor din şir plus 1. Exemplu: # include <stdio.h>

void main (void) {

char nume[14] = "ENE ALEXANDRU";

char adresa[24] = "Str. A. I. Cuza, nr.13";

puts (nume);

puts (adresa); }

Vectorul nume va ocupa începând de la adresa nume, 13 + 1 = 14 octeţi, iar cel de-al doilea vector va ocupa începând de la adresa adresa, 23 + 1 = 24 locaţii (bytes). Funcţia puts() scrie pe stdout şirul memorat în vectorul al cărui nume apare ca parametru al funcţiei puts(), precum şi caracterul "\n". De multe ori, în C se realizează iniţializarea unor vectori de caractere a căror dimensiune nu este precizată. Dacă dimensiunea vectorului nu este precizată, compilatorul C va crea un vector suficient de lung încât să permită iniţializarea dorită. Exemplu: În loc să scriem :

char e1[12] = "read error\n";

char e2[13] = "write error\n";

char e3[18] = "cannot open file\n";

putem scrie: char e1[ ] = "read error\n";

char e2[ ] = "write error\n";

Page 103: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

97

char e3[ ] = "cannot open file\n";

Cu această ultimă iniţializare, instrucţiunea printf ("%s are lungimea %d\n", e2, sizeof (e2));

va tipari: write error

are lungimea 13

o Iniţializarea unui vector (tablou unidimensional) se poate face şi cu o listă de iniţializatori scrişi între acolade. Dacă vectorul are o lungime necunoscută, numărul de iniţializatori determină mărimea tabloului, iar tipul devine complet. Dacă tabloul are lungime fixă, numărul de iniţializatori nu poate depăşi numărul de membri din tablou. În cazul în care sunt mai puţini iniţializatori, membrii în plus sunt iniţializaţi cu zero. Exemple:

- Instrucţiunea următoare iniţializează un vector de 10 întregi cu numerele de la 1 la 10:

int i[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

Rezultă că: i[0] = 1, ... , i[9] = 10. - Instrucţiunea următoare declară şi iniţializează vectorul x ca

un tablou unidimensional cu 3 membri: int x[] = {1, 2, 3};

- Instrucţiunea următoare: char sir[6] = { 'h', 'e', 'l', 'l', 'o', '\0' };

este echivalentă cu: char sir[6] = "hello";

6.1.3. Funcţii pentru prelucrarea şirurilor (fişierul antet string.h) • Funcţia strcpy() Apelul funcţiei strcpy() are următoarea formă generală:

strcpy (nume_sir, constanta_sir); Funcţia strcpy() copiază conţinutul constantei_sir (inclusiv caracterul terminator '\n') în nume_sir. Exemplu: # include <string.h>

void main(void) {

char sir[80];

strcpy (sir, "hello");

printf("%s", sir); }

Acest program va copia "hello" în şirul sir. • Funcţia strcat() Apelul funcţiei strcat() are forma:

Page 104: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

98

strcat (s1, s2); Funcţia strcat() concatenează şirul s2 la sfârşitul şirului s1 şi întoarce şirul s1. Şirul s2 nu se modifică. Ambele şiruri trebuie să aibă caracterul terminator NULL, iar rezultatul va avea de asemenea caracterul terminator NULL. Exemplu: # include <stdio.h>

# include <string.h>

void main(void) {

char first[20], second[10];

strcpy (first, "hello");

strcpy (second, "there");

strcat (first, second);

printf ("%s", first); }

Acest program va afişa "hellothere" pe ecran. • Funcţia strcmp() Se apelează sub forma:

strcmp (s1, s2); Această funcţie compară şirurile s1 şi s2 şi returnează valori negative, dacă s1 < s2, 0, dacă s1 = s2 şi un număr pozitiv, dacă s1 > s2. Exemplu: Această funcţie poate fi folosită ca o subrutină de verificare a parolei:

# include <stdio.h>

# include <string.h>

void main (void) {

char s[80];

printf ("Introduceti parola: ");

gets (s);

if (strcmp (s, "pasword")) {

printf (" Invalid pasword \n ");

return 0;}

return 1; } • Funcţia strlen() Funcţia strlen() se apelează sub forma:

strlen (s) unde s este un şir. Funcţia strlen() returnează lungimea şirului s. Exemplu: Programul următor returnează lungimea unui şir introdus de la tastatură. # incude <stdio.h>

# incude <string.h>

void main (void) {

char sir[80];

Page 105: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

99

printf ("Introduceti un sir: ");

gets (sir);

printf ("Sirul %s contine %d caractere ", sir, strlen(sir)); }

Observaţie: Funcţia strlen() nu numără şi caracterul NULL. Exemplu: Programul următor afişează inversul unui şir de caractere introduse de la tastatură. # include <stdio.h>

# include <string.h>

void main (void) {

char sir[80]; int i;

gets(sir);

for(i=strlen(sir)-1;i>=0;i--) printf("%c",sir[i]); }

Exemplu: Programul următor realizează introducerea unor şiruri, compararea lor, concatenarea lor şi afişarea rezultatului. # include <stdio.h>

# include <string.h>

void main (void) {

char s1[80], s2[80];

gets(s1); gets(s2);

printf("Lungimi: %d %d \n",strlen(s1),strlen(s2));

if (!strcmp (s1, s2))

printf ("Sirurile sunt egale\n");

strcat (s1, s2);

printf ("%s\n", s1); }

Dacă se rulează acest program şi se introduc şirurile s1 = "AUTOMATICA" şi s2 = "AUTOMATICA", se va afişa: Lungimi 10 10

Sirurile sunt egale

AUTOMATICAAUTOMATICA

Dacă şirurile sunt egale, funcţia strcmp() returnează fals (0) şi din această cauză în instrucţiunea if s-a folosit !strcmp(). Observaţie: Caracterul NULL de terminare a vectorului de caractere poate fi utilizat în buclele for ca în exemplul următor, unde se converteşte un şir de caractere scris cu litere mici la litere mari. # include <stdio.h>

# include <string.h>

void main (void) {

char sir[80];

int i;

strcpy (sir, "acesta este un test");

for(i = 0; sir[i]; i++) sir[i] = toupper (sir[i]);

printf("%s", sir); }

Page 106: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

100

Conversia caracterelor se face cu funcţia toupper() care returnează litera mare corespunzătoare argumentului (literei mici). Ciclul funcţionează până când sir[i] devine caracterul null.

6.2. Tablouri cu două dimensiuni (matrice)

Tablourile bidimensionale (matricele) sunt reprezentate ca vectori de vectori. De exemplu, declaraţia:

int v[2][5]; declară un vector cu 2 elemente, fiecare element fiind un vector de tip int[5]. Se observă că fiecare dimensiune a tabloului este separată (închisă) între paranteze, iar dimensiunile nu sunt separate prin virgulă. Astfel, declaraţia: int v[2, 5]; conduce la eroare.

6.2.1. Iniţializarea matricelor Declaraţia : char v[2][5] = { 'a', 'b', 'c', 'd', 'e',

'0', '1', '2', '3', '4' };

conduce la iniţializarea primului vector cu primele 5 litere, iar a celui de-al doilea cu primele 5 cifre. Exemplu: Programul: # include <stdio.h>

void main (void) {

char v[2][5] = { 'a', 'b', 'c', 'd', 'e',

'0', '1', '2', '3', '4' };

int i, j;

for (i = 0; i < 2; i++){

for(j = 0; j < 5; j++)

printf ("v[%d][%d] = %c", i, j, v[i][j]);

printf ("\n"); } }

va produce : v[0][0]=a v[0][1]=b v[0][2]=c v[0][3]=d v[0][4]=e

v[1][0]=0 v[1][1]=1 v[1][2]=2 v[1][3]=3 v[1][4]=4.

Exemplu: Secvenţa de instrucţiuni: # include <stdio.h>

void main (void) { int l, c, num[3][4];

for (l = 0; l < 3; ++l)

for (c = 0; c < 4; ++c)

num[l][c] = (l * 4) + c + 1; }

conduce la încărcarea tabloului num[3][4]cu numerele de la 1 la 12. Astfel, num[0][0] = 1, ..., num[2][3] = 12.

Page 107: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

101

Se observă că limbajul C memoreză tablourile bidimensionale într-o matrice linii-coloane, unde primul indice se referă la linie şi al doilea indice se referă la coloană. Cantitatea de memorie alocată permanent pentru un tablou, exprimată în bytes, este:

nr_linii * nr_coloane * sizeof(tipul_datei) Declaraţia: float y[4][3] = { {1,3,5}, {2,4,6},

{3,5,7},};

este o iniţializare cu paranteze complete şi are următorul efect: - numerele 1, 3, 5 iniţializează prima linie a tabloului: y[0][0], y[0][1], y[0][2] sau y[0]; - numerele 2, 4, 6 iniţializează pe y[1]; - numerele 3, 5, 7 iniţializează pe y[2]. Întrucât iniţializatorul se termină cu virgulă, elementele lui y[3] vor fi iniţializate cu 0. Acelaşi efect ar fi putut fi realizat de:

float y[4][3]={1, 3, 5, 2, 4, 6, 3, 5, 7, }; Secvenţa:

float y[4][3] = { {1}, {2}, {3}, {0}, };

iniţializează prima coloană a lui y, privit ca un tablou bidimensional, cu 1, 2, 3 şi 0, restul tabloului fiind iniţializat cu 0.

6.2.2. Tablouri bidimensionale de şiruri Pentru crearea unui tablou de şiruri se foloseşte un tablou de caractere, bidimensional, în care mărimea indicelui din stânga determină numărul de şiruri, iar indicele din drepta specifică lungimea maximă a fiecărui şir. De exemplu, declaraţia : char sir_tablou[30][80];

defineşte un tablou de 30 de şiruri, fiecare şir având maximum 80 de caractere. Accesul la un singur şir este foarte uşor: se specifică numai primul indice. De exemplu: gets (sir_tablou[2])

întoarce al treilea şir din tabloul sir_tablou. Funcţional, instrucţiunea anterioară este echivalentă cu: gets (&sir_tablou[2][0]);

6.3. Tablouri multidimensionale

Forma generală a declaraţiei unui tablou multidimensional este: tip nume[size1][size2]...[sizeN];

De exemplu, declaraţia: int trei[4][10][3];

Page 108: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

102

creează un tablou de 4*10*3 întregi. Forma generală de iniţializare a tablourilor este următoarea: specificator_tip nume_tablou[size1][size2]...[sizeN]={lista_valori}; unde lista_valori este o listă de constante separate prin virgulă, compatibile cu tipul de bază al tabloului. Observaţie: Limbajul C permite şi iniţializarea tablourilor multidimensionale fără dimensiune. Trebuie menţionat că pentru aceasta este necesară precizarea indicelui celui mai din dreapta. Astfel, declaraţia:

int sqrs[5][2] = {1, 1, 2, 4, 3, 9, 4, 16, 5, 25};

este echivalentă cu declaraţia: int sqrs[ ][2] = {1, 1, 2, 4, 3, 9, 4, 16, 5, 25};

6.4. Structuri

O structură este o colecţie de variabile (de tipuri diferite)

referite sub un singur nume. Definiţia unei structuri formează un aşa numit şablon (template, tag) ce poate fi folosit la crearea variabilelor tip structură. Variabilele care formează structuri se numesc elementele structurii. De exemplu, fragmentul următor defineşte un şablon pentru o structură numită addr care defineşte la rândul său numele şi adresa unei persoane necesare în cazul transmiterii unei scrisori:

struct addr { struct {

char name[30]; char *name;

char street[40]; char *street; char city[20]; char *city;

char state[3]; char *state;

unsigned int zip; unsigned int zip;

}; } addr;

Pentru definirea unui şablon al unei structuri se foloseşte cuvântul cheie struct. Terminarea definiţiei se face cu ”;” (este unul din puţinele cazuri de utilizare a caracterului punct şi virgulă ”;” după acoladă). Precizăm că numele addr identifică structura particulară definită anterior (şablonul) şi este specificatorul său de tip. Programul anterior defineşte numai forma (tipul) datelor structurii, dar nu defineşte variabilele structură, deci trebuie făcută distincţie dintre structura-şablon şi variabila-structură. O variabilă de tip structură se declară cu ajutorul şablonului structurii.

Page 109: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

103

Pentru a declara o variabilă actuală cu această structură vom scrie struct addr addr_info;

Această linie declară variabila addr_info ca variabilă structură de tip addr. Limbajul C alocă suficientă memorie pentru a păstra toate variabilele ce alcătuiesc o structură. De exemplu, memoria alocată pentru structura addr_info va fi : Name 30 bytes Street 40 bytes City 20 bytes State 3 bytes Zip 4 bytes Când se defineşte o structură şablon, se pot declara una sau mai multe variabile-structuri, astfel : struct addr { char name[30]; char street[40]; char city[20]; char state[3]; unsigned int zip; } addr_info, binfo, cinfo; Secvenţa anterioară defineşte o structură şablon numită addr şi declară variabilele addr_info, binfo, cinfo de acelaşi tip. Pentru declararea unei singure structuri numite addr_info, nu mai este necesară includerea numelui addr al structurii, astfel: struct {

char name[30];

char street[40];

char city[20];

char state[3];

unsigned int zip;

} addr_info;

În cazul de mai sus se defineşte variabila-structură addr_info cu şablonul definit, dar fără nume Forma generală de definire a unei structuri este : struc nume_tip_structura {

tip nume_variabile;

tip nume_variabile;

tip nume_variabile;

. . . . . . . . . . . . . .

} variabile_structura;

Page 110: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

104

unde pot fi omise fie numele tipului structurii nume_tip_struct, fie variabile_structura, dar nu ambele. După cum se observă, nume_tip_structura după cuvântul cheie struct se referă la şablonul structurii (tipul său) iar variabile_structura se referă la lista de variabile de acest tip (cu această structură) Referirea individuală a elementelor unei structuri se face cu operatorul punct ".". De exemplu, instrucţiunea următoare va atribui elementului zip al variabilei structură addr_info valoarea 12345: addr_info.zip = 12345; Pentru accesarea elementelor unei structuri se foloseşte forma generală :

nume_structura.nume_element De exemplu, pentru afişarea variabilei zip se va scrie:

printf ("%d", addr_info.zip); În aceeaşi formă, se pot referi celelalte elemente ale structurii addr_info. De exemplu apelul:

gets (addr_info.name); are ca efect trecerea la un pointer-caracter la primul caracter al elementului nume. Pentru a avea acces la fiecare caracter al elementului addr_info.name, se poate indexa name. De exemplu, pentru afişarea conţinutului lui addr_info.name, caracter cu caracter, se foloseşte programul: register int t;

for (t = 0; addr_info.name[t]; ++t) putchar (addr_info.name[t]);

6.4.1. Tablouri de structuri Cel mai uzual mod de folosire a structurilor este în tablouri de structuri. Pentru declararea unui tablou de structuri, mai întâi se defineşte o structură şi apoi se declară un tablou de variabile de acel tip. De exemplu, pentru declararea unui tablou cu 100 de structuri addr definite anterior, se va scrie:

struct addr addr_info[100]; Pentru a avea acces la o structură oarecare din cele 100 se va indexa numele structurii (în cazul acesta addr_info). De exemplu:

printf ("%d", addr_info[2].zip);

are ca efect afişarea codului zip din a treia structură. Se observă că, la fel orice variabilă tablou, tablourile de structuri încep cu indexul 0.

Page 111: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

105

Exemplu de program pentru actualizarea unei liste de corespondenţa - maillist Pentru a ilustra modul de utilizare a structurilor şi tablourilor de structuri prezentăm un exemplu de program pentru actualizarea unei liste de corespondenţă. Informaţiile ce vor fi memorate se referă la name, street, city, state, zip. Pentru definirea structurii de bază addr care va conţine aceste informaţii vom scrie: struct addr {

char name[20];

char street[30];

char city[15];

char state[10];

unsigned int zip;

} addr_info[SIZE];

Tabloul addr_info contine SIZE structuri de tip addr, unde SIZE se defineşte după necesităţi. Prima funcţie necesară în program este main(), a cărei structură este următoarea: void main() {

char choice;

init_list();

for (;;) {

choice = menu();

switch (choice) {

case 'e' : enter(); break;

case 'd' : display(); break;

case 's' : save(); break;

case 'l' : load(); break;

case 'q' : exit(); }}}

Funcţia init_list() pregăteşte tabloul structură pentru utilizare prin punerea unui caracter null în primul byte al câmpului "nume". Programul impune ca o variabilă structură să nu fie utilizată dacă câmpul nume este vid. Această iniţializare are loc în memoria internă a calculatorului (nu în fişierul maillist de pe disc). Structura funcţiei de initializare init_list() ar putea fi următoarea:

/* Functia init_list() */

void init_list() {

register int t;

for (t = 0; t < SIZE; t++)

*addr_info[t].name = '\0'; }

Page 112: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

106

Funcţia de selectare a meniului menu() va afişa mesajele opţiunilor şi va returna varianta aleasă. Prin tastarea literei din paranteze, se va lansa în execuţie o anumită procedură. /* Functia menu() */

char menu() {

char s[5],ch;

do {

printf ("(E)nter\n");

printf ("(D)isplay\n");

printf ("(L)oad\n");

printf ("(S)ave\n"); printf ("(Q)uit\n");

printf (" Alegeti optiunea: ");

gets(s);

ch=s[0];

} while (!strrchr("edlsq",ch));

return tolower(ch); }

Observaţie: Funcţia strrchr(cs,c) din <string.h> întoarce un pointer la ultima apariţie a lui c în cs sau NULL dacă nu apare. Funcţia enter() are ca efect introducerea unor noi informaţii în următoarea structură liberă a listei addr_info[SIZE]. Această introducere se efectuează prin determinarea primei structuri libere din memorie (cu addr_info.name setată la 0) şi prin completarea sa cu informaţii culese de la tastatură.

/* Functia enter() */

void enter() {

register int i;

for (i=0; i < SIZE; i++)

if (!*addr_info[i].name) break;

if (i == SIZE) {

printf ("addr_info full \n"); /* Lista plina */

return;}

printf ("Name: ");

gets (addr_info[i].name);

printf ("Street: ");

gets (addr_info[i].street);

printf ("City: ");

gets (addr_info[i].city);

printf ("State: ");

gets (addr_info[i].state); printf ("Zip: ");

scanf ("%d",&addr_info[i].zip);}

Rutinele save() şi load() se utilizează pentru actualizarea bazei de date. Aceste rutine lucrează cu fişierul disc maillist. Când se apelează load(), atunci se copiază în tabloul structură din memorie

Page 113: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

107

datele stocate în fişierul maillist. Funcţia load() realizează operaţiunea inversă, de supraînscriere a fişierului disc cu datele din memorie. Spre exemplu, dacă dorim să adăugăm date la fişierul maillist care conţine deja date introduse anterior, ordinea de lansare a comenzilor va fi: init_list(), load(), enter(), save(), exit(). Structura acestor rutine este următoarea:

/* Functia save() */

void save() {

register int i;

if ((fp = fopen("maillist", "wb")) == NULL) {

printf (" Cannot open file\n ");

return;}

for (i = 0; i <= SIZE; i++)

if(*addr_info[i].name)

if(fwrite(&addr_info[i],sizeof(struct addr),1,fp) !=1)

printf (" File write error \n ");

fclose (fp);}

/* Functia load() */

void load() { register int i;

if ((fp = fopen("maillist","rb")) == NULL) {

printf("Cannot open file\n ");

return;}

for (i = 0; i < SIZE; i++)

if(fread(&addr_info[i],sizeof(struct addr), 1, fp) == 1);

else if (feof(fp)) {

fclose (fp); return;}

else printf ("File read error\n"); }

Atât save() cât şi load() confirmă un succes a unei operaţii cu fişiere prin verificarea valorilor întoarse de funcţiile fread() sau fwrite(). În plus, load() trebuie să caute şi indicatorul de sfârşit de fişier utilizând funcţia feof() deoarece fread() întoarce aceeaşi valoare dacă se întâlneşte indicatorul de sfârşit de fişier sau dacă apare o eroare. Funcţia display() afişează pe ecran întregul tablou structură din memorie care conţine date valide:

/* Functia display() */

void display() {

register int t;

for (t=0;t<SIZE;t++) {

if (*addr_info[t].name!='\0') {

printf("%s\n",addr_info[t].name);

printf("%s\n",addr_info[t].street);

printf("%s\n",addr_info[t].city);

Page 114: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

108

printf("%s\n",addr_info[t].state);

printf("%d\n\n",addr_info[t].zip);

getchar();}}}

Listingul complet al programului va fi: # include <stdio.h>

# include <ctype.h>

# include <string.h>

# define SIZE 100

struct addr {

char name[20];

char street[30];

char city[15];

char state[10];

unsigned int zip; } addr_info[SIZE];

FILE *fp;

void init_list(),enter(),save(),load();

void display(),exit();

char menu();

void main() {

char choice;

init_list();

for (;;) {

choice = menu();

switch (choice) {

case 'e' : enter(); break;

case 'd' : display(); break;

case 's' : save(); break;

case 'l' : load(); break;

case 'q' : exit(); }}}

/* Functia init_list() */

void init_list() {

register int t;

for (t = 0; t < SIZE; t++)

*addr_info[t].name = '\0'; }

/* Functia menu() */

char menu() {

char s[5],ch; do {

printf ("(E)nter\n");

printf ("(D)isplay\n");

printf ("(L)oad\n");

printf ("(S)ave\n");

printf ("(Q)uit\n");

printf (" Alegeti optiunea: ");

gets(s);

Page 115: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

109

ch=s[0];

} while (!strrchr("edlsq",ch));

return tolower(ch); }

/* Functia enter() */

void enter() {

register int i;

for (i=0; i < SIZE; i++)

if (!*addr_info[i].name) break;

if (i == SIZE) {

printf ("addr_info full \n"); /* Lista plina */

return;}

printf ("Name: ");

gets (addr_info[i].name);

printf ("Street: ");

gets (addr_info[i].street); printf ("City: ");

gets (addr_info[i].city);

printf ("State: ");

gets (addr_info[i].state);

printf ("Zip: ");

scanf ("%d",&addr_info[i].zip);}

/* Functia save() */

void save() {

register int i;

if ((fp = fopen("maillist", "wb")) == NULL) {

printf (" Cannot open file\n ");

return;} for (i = 0; i <= SIZE; i++)

if(*addr_info[i].name)

if(fwrite(&addr_info[i], sizeof(struct addr), 1,fp) !=1)

printf (" File write error \n ");

fclose (fp);}

/* Functia load() */

void load()

{ register int i;

if ((fp = fopen("maillist","rb")) == NULL) {

printf("Cannot open file\n ");

return;}

for (i = 0; i < SIZE; i++)

if(fread(&addr_info[i],sizeof(struct ddr),1,fp)==1); else if (feof(fp)) {

fclose (fp); return;}

else printf ("File read error\n"); }

/* Functia display() */

void display() {

register int t;

Page 116: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

110

printf("\n%20s","Name");

printf("%30s","Street");

printf("%15s","City"); printf("%10s","State");

printf("%5s\n","Zip");

for (t=0;t<SIZE;t++) {

if (*addr_info[t].name!='\0') {

printf("%20s",addr_info[t].name);

printf("%30s",addr_info[t].street);

printf("%15s",addr_info[t].city);

printf("%10s",addr_info[t].state);

printf("%5d",addr_info[t].zip);

getchar();}}}

6.4.2. Introducerea structurilor în funcţii a) Introducerea elementelor unei structuri în funcţii Când un element al unei variabile tip structură este transmis (pasat) unei funcţii, de fapt este transmisă valoarea acelui element. Exemplu:

struct struct1{

char x;

int y;

float z;

char s[10];

} struct2;

Modalitatea de a introduce fiecare element într-o funcţie este următoarea:

func (struct2.x);

/* se paseaza valoarea caracterului x */ func2 (struct2.y);

/* se paseaza valoarea intregului y */ func3 (struct2.z);

/* se paseaza valoarea reala a lui z */ func4 (struct2.s);

/* se utilizeaza adresa sirului s */ func (struct2.s[2]);

//se utilizeaza valoarea caracterului lui s[2] unde func(), func2(), func3(), func4() sunt numele unor funcţii. Pentru a transmite funcţiei func() adresele elementelor din structura struct2, se scrie astfel:

func (&struct2.x); /* se paseaza adresa caracterului x */ func (&struct2.s[2]); /* se paseaza adresa caracterului s[2] */

Page 117: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

111

b) Transmiterea unei întregi structuri unei funcţii Când o structură este utilizată ca argument al unei funcţii, limbajul C transmite funcţiei întreaga structură utilizând metoda standard a apelului prin valoare. Aceasta înseamnă că orice modificare asupra conţinutului structurii în interiorul funcţiei în care este apelată structura, nu afectează structura folosită ca argument. Ceea ce trebuie avut neapărat în vedere atunci când, ca parametru, se utilizează o structură este ca tipul argumentului să fie identic cu tipul parametrului. Exemplu: # include <stdio.h>

void f1();

void main() {

struct {

int a,b; char ch;

} arg;

arg.a = 1000;

f1(arg); /* se apeleaza functia f1() */ }

void f1(param) /* se declara functia f1 */

struct {

int x, y;

char ch;

} param;

{printf ("%d\n", param.x); }

Acest program declară atât argumentul arg al lui f1, cât şi parametrul param ca având acelaşi tip de structură. Programul va afişa pe ecran valoarea 1000. Pentru a face programele mai compacte se procedează la definirea structurii ca variabilă globală şi apoi la utilizarea numelui acesteia pentru a declara diversele variabile structură. Exemplu:

# include <stdio.h>

void f1();

/* Se defineste un tip structura */

struct struct_tip {

int a, b;

char ch;};

void main() {

struct struct_tip arg;

/* se declara structura arg */

arg.a = 1000;

f1(arg);}

Page 118: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

112

void f1(struct struct_tip param) /* se declara

functia f1() */

{printf ("%d\n",param.a); } Pentru exemplificare, propunem următorul program:

- se preia de la tastatura un prim şir de numere întregi - se preia de la tastatura un al doilea şir de numere întregi - se concatenează cele două şiruri - şirul rezultat se sortează în ordine crescătoare, având în vedere

ca primele poziţii să fie ocupate de numerele pare din şir sortate crescător după care să urmeze numerele impare din şir sortate tot crescător.

Pentru a realiza acest program, vom utiliza nu variabile de tip şir ci o structură care să conţină ca prim element şirul respectiv iar cel de-al doilea element să fie lungimea acestuia. Se vor construi funcţii care să realizeze citirea şirurilor de la tastatură, scrierea lor pe display, respectiv să le ordoneze şi să le sorteze după paritate. Toate aceste funcţii comunică prin intermediul unei variabile globale de tip structură şi a mai multor variabile locale de tip structură.

Programul în C este prezentat în continuare: # include <stdio.h>

// definim prototipurile functiilor utilizate

struct sir_lung cit_sir();

struct sir_lung concat_sir();

struct sir_lung ord_sir(); struct sir_lung par_sir();

struct sir_lung impar_sir();

void tip_sir();

/* se defineste structura sir+lungime si variabila

globala sir */

struct sir_lung {

int sir[80];

int lung;} sir;

// programul principal

void main(){

struct sir_lung sir_init,sir_ord,sir_par,sir_impar;

sir_init=cit_sir();

getchar();

sir_ord=cit_sir();

sir_init=concat_sir(sir_init,sir_ord);

sir_par=par_sir(sir_init);

sir_par=ord_sir(sir_par);

sir_impar=impar_sir(sir_init);

sir_impar=ord_sir(sir_impar);

sir_ord=concat_sir(sir_par,sir_impar);

Page 119: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

113

tip_sir(sir_init);

tip_sir(sir_ord);}

// se definesc functiile struct sir_lung cit_sir()

{int result=1, i=0;

sir.lung=0;

while (result) {

result=scanf("%d",&sir.sir[i]);

i++;}

sir.lung=--i;

return sir;}

void tip_sir(struct sir_lung sirf)

{ int i;

for (i=0;i<sirf.lung;i++)

printf("%d ",sirf.sir[i]);printf("\n");}

struct sir_lung concat_sir(struct sir_lung sir1, struct

sir_lung sir2)

{ int i;

struct sir_lung sir_concat;

sir_concat=sir1;

for (i=0;i<sir2.lung;i++)

sir_concat.sir[sir1.lung+i]=sir2.sir[i];

sir_concat.lung=sir1.lung+sir2.lung;

return sir=sir_concat;}

struct sir_lung ord_sir(struct sir_lung sir1)

{int i,j,temp;

for (i=0;i<sir1.lung;i++)

for (j=i+1;j<sir1.lung;j++)

if (sir1.sir[i]>sir1.sir[j])

{temp=sir1.sir[i];sir1.sir[i]=sir1.sir[j];

sir1.sir[j]=temp;}

return sir=sir1;}

struct sir_lung par_sir(struct sir_lung sir1)

{ int i,j=0;

struct sir_lung sir_par;

for (i=0;i<sir1.lung;i++)

if (!(sir1.sir[i]%2))

{sir_par.sir[j]=sir1.sir[i];j++;}

sir_par.lung=j;

return sir=sir_par;}

struct sir_lung impar_sir(struct sir_lung sir1) { int i,j=0;

struct sir_lung sir_impar;

for (i=0;i<sir1.lung;i++)

if (sir1.sir[i]%2)

{sir_impar.sir[j]=sir1.sir[i];j++;}

sir_impar.lung=j;

return sir=sir_impar;}

Page 120: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

114

Din funcţia cit_sir() se poate ieşi prin tastarea oricarui caracter nenumeric. Se observă cum aceasta funcţie lucrează direct cu variabila globală şir iar celelalte cu variabile locale proprii care apoi sunt asignate variabilei globale şir la returnare. Rulând programul vom obţine rezultatele:

1 2 17 -3 6 -4; -9 -2 31 2 -7; 1 2 17 -3 6 -4 -9 -2 31 2 -7 -4 -2 2 2 6 -9 -7 -3 1 17 31

6.4.3. Tablouri şi structuri în structuri Elementele unei structuri pot fi simple (int, char etc.) sau complexe (tablouri de elemente de diferite tipuri, structuri). Exemplu: Considerăm structura : struct x {

int a[10][10]; /* tablou de 10x10 intregi */

float b;} y;

De exemplu, referirea întregului a[3][7] se face prin: y.a[3][7] Când o structură este un element al altei structuri, structurile se numesc încuibate (nested, incluse). Exemplu:

struct sal {

struct addr adresa;

float salariu;

} muncitor;

Se observă că elementele variabilei-structură "adresa" sunt incluse în structura "sal". Aici "addr" este structura definită anterior. Exemplul anterior defineşte structura "sal" cu 2 elemente: primul este o structură de tip "addr" care conţine adresa salariatului; al doilea element este salariul săptămânal al acestuia. Instrucţiunea următoare va atribui codul 90178 variabilei "zip" din adresa muncitorului

muncitor.adresa.zip = 90178; Se observă că elementele fiecărei structuri sunt referite de la exterior către interior, respectiv de la stânga la dreapta.

6.5. Uniuni

În C o uniune este o zonă de memorie utilizată de mai multe

variabile ce pot fi diferite ca tip. Definitia uniunilor este similară celei a structurilor.

Page 121: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

115

Exemplu: union tip_u {

int i;

char ch;};

O variabilă de acest tip poate fi declarată fie prin plasarea numelui său la sfîrşitul acestei definiţii, fie utilizând o instrucţiune de declarare separată. De exemplu, instrucţiunea :

union tip_u exuniune; declară variabila-uniune "exuniune" în care atât întregul i, cât şi caracterul ch ocupă aceeaşi zonă de memorie (cu toate ca int ocupa 4 octeţi, iar char numai un octet).

Când se declară o variabilă union compilatorul C rezervă o zonă de memorie suficient de lungă capabilă să preia variabila cu lungimea cea mai mare. Pentru a accesa elementele unei uniuni se utilizează aceeaşi sintaxă folosită la structuri (operatorii punct şi " -> "). Când un element al unei uniuni se adresează direct, se utilizează operatorul ".", iar când elementul se adresează printr-un pointer, se foloseşte operatorul "-> ". De exemplu, pentru a atribui elementul întreg i al uniunii anterioare "exuniune" valoarea 10, se va scrie:

exuniune.i = 10; În exemplul următor, programul transferă un pointer la "exuniune" unei funcţii : func1(union tip_u *un)

{ un -> i = 10; }

/* se atribuie valoarea 10 intregului i al uniunii exuniune */ In C, uniunile sunt des utilizate când sunt necesare conversii de tip. De exemplu, funcţia standard putw() ce realizează scrierea binară a unui întreg într-un fişier de pe disc, poate fi scrisă folosind o uniune. Pentru aceasta, mai întâi se crează o uniune ce cuprinde un întreg şi un vector de două caractere, astfel: union pw {

int i;

char ch[2];};

Atunci, structura lui putw(), utilizând aceasta uniune este : putw(word, fp)

union pw word; /* se declara uniunea word */

FILE *fp {

putc(word ->ch[0]); // se scrie primul caracter

putc(word->ch[1]); // se scrie al doilea caracter }

Page 122: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

116

6.6. Enumerări

O enumerare este o mulţime de constante întregi ce pot lua toate valorile unei variabile de un anumit tip. Enumerările se definesc în acelaşi mod ca şi structurile, utilizând cuvântul cheie enum ce semnalează începutul unui tip enumerare. Forma generală de definire a unei enumerări este:

enum nume_tip_enum { lista_enumeratori} lista_variabile; unde atât nume_tip_enum, cât şi lista_variabile sunt opţionale.

Exemplu: Următorul fragment defineşte o enumerare numită "bancnota" cu care apoi se declară o enumerare numită "bani" având acest tip: enum bancnota {suta,douasute,cincisute,mie,cincimii,zecemii};

enum bancnota bani;

Dându-se această definiţie şi declaraţie, sunt valabile urmatoarele instructiuni: bani = mie;

if (5*bani == cincimii) printf("Sunt 5000 lei.\n");

Trebuie precizat că fiecare enumerator este caracterizat printr-o valoare întreaga. Fără nici o altă iniţializare, valoarea primului enumerator este 0, a celui de-al doilea este 1, s.a.m.d. De aceea, instrucţiunea: printf ("%d %d, suta, mie); va afisa pe ecran: 0 3 Se pot specifica valorile unuia sau mai multor simboluri folosind iniţializatori. De exemplu: enum bancnota {suta, douasute, cincisute, mie=1000, cincimii, zecemii}; face ca simbolurile din enumerarea bancnota să aibă valorile: suta = 0

douasute = 1

cincisute = 2

mie = 1000

cincimii = 1001 zecemii = 1002

Urmatorul fragment de program nu funcţionează, deoarece "bani" este un întreg şi nu un şir : bani = cincimii;

printf ("%s", bani); Nici acest program nu functionează: gets (s);

strcpy (bani, s);

Page 123: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

117

Pentru a afişa tipurile bancnotelor conţinute în enumerarea "bani", se va scrie: switch (bani) {

case suta: printf (" suta "); break;

case douasute: printf (" douasute "); break;

. . . . . . . . . . . . . . . . . . . . . . . . . .

case zecemii: printf (" zecemii "); }

Uneori pentru a translata valoarea unui enumerator în şirul corespunzător, se poate declara un tablou de şiruri şi utiliza valoarea enumeratorului ca index. De exemplu, următorul fragment va afişa şirul corespunzător: char name[ ][20] = {

"suta",

"douasute",

"cincisute",

"mie",

"cincimii"

"zecemii"

};

. . . . . . printf ("%s", name[bani]);

Fragmentul anterior va funcţiona numai dacă nu se realizează iniţializarea simbolurilor, deoarece indexarea şirurilor începe cu zero. Următorul program afişează numele bancnotelor: # include <stdio.h>

enum bancnota { suta, douasute, cincisute,mie,

cincimii,zecemii,cincizecimii,sutamii,cincisutemii};

char name[][20]=

{"suta","douasute","cincisute","mie","douamii","cincimii","zecemii","cincizecimii" "sutamii","cincisutemii"};

void main() {

enum bancnota bani;

for (bani = suta; bani <= cincisutemii; bani ++)

printf ("%s\n", name[bani]);}

Dacă variabilei uniune y din exemplul următor i se aplică operatorul sizeof() vom găsi sizeof(y) = 8. # include <stdio.h>

union {

char ch;

int i;

double f;

} y;

void main() {printf("%d\n",sizeof(y));}

Deci compilatorul va reţine valoarea celei mai largi tipuri de date din uniune.

Page 124: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

118

Capitolul VII

POINTERI

Un pointer este o variabilă care păstrează adresa unui obiect de tip corespunzător. Forma generală pentru declararea unei variabile pointer este:

tip * nume_variabila; unde tip poate fi oricare din tipurile de bază admise în C, iar nume_variabila este numele variabilei pointer. Tipul de bază al pointerului defineşte tipul variabilelor spre care indică pointerul.

Variabila pointer este o variabilă de un tip special, aparte de tipurile char, int, float. Cuvântul cheie tip din declaraţia unui pointer se referă la tipul de dată spre care indică pointerul, nu la formatul în care se stochează efectiv o variabilă pointer în memorie. Formatul în care se stochează o variabilă pointer în memorie depinde de tipul de compilator care se foloseşte, deci depinde în mare măsură de tipul procesorului pentru care a fost proiectat compilatorul. O indicaţie despre formatul în care se stochează o variabilă pointer în memorie poate fi obţinută prin tipărirea conţinutului unei variabile pointer (o adresă) utilizând printf() cu formatul %p. Exemplu: char *p; /* pointer la caracter */

int *temps, *start; /* pointeri la intregi */

char *const q; /* pointer constant la caracter */

7.1. Operatori pointer

Există doi operatori pointer speciali * şi &:

• Operatorul & este un operator unar care oferă (returnează) adresa unei variabile (adresa operandului său).

• Operatorul * este complementarul lui &. Este un operator unar care returnează valoarea variabilei plasată la adresa care urmează după acest operator.

Exemplu: # include <stdio.h>

void main (void) {

int *count_addr, count, val;

count = 100; /* int count are valoarea 100 */

Page 125: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

119

count_addr = &count; /*count_addr indica spre count.

Aceasta instructiune plaseaza in count_addr adresa din

memorie a variabilei count, adresa care nu are nici o legatura cu valoarea lui count */

val = count_addr; /* val preia valoarea de la adresa

count_addr. Aceasta instructiune plaseaza valoarea lui

count aflata la adresa count_addr în variabila val */

printf ("%d", val); /*Se va tipari numarul 100 */ }

Spre exemplu, să considerăm porţiunea de program:

short i, j; // i si j sunt ambele intregi scurti

short *p // p este pointer la tip intreg scurt

i = 123;

p = &i;

j = *p; Să presupunem că zona de stocare a celor trei variabile arată astfel:

i 1200

j 1202

p 1204

Adresa Memoria

După primele două atribuiri i = 123;

p = &i;

zona de stocare va arăta astfel:

i 1200 123

j 1202

p 1204 1200

Adresa Memoria

Conţinutul variabilei p (de tip pointer) va fi valoarea 1200, adică adresa variabilei i.

Instrucţiunea j = *p; copiază un întreg scurt de la locaţia 1200 în j, locaţiile de memorie fiind acum ca cele din figură:

i 1200 123

j 1202 123

p 1204 1200

Adresa Memoria

Page 126: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

120

7.1.1. Importanţa tipului de bază Considerăm declaraţia: val = *count_addr;

Se pune întrebarea: care va fi numărul de bytes ce va fi transferat variabilei val de la adresa indicată prin *count_addr. Sau, mai general, de unde ştie compilatorul câţi bytes să transfere în cazul oricărei asignări care utilizează pointeri. Răspunsul la aceste întrebări este acela că, tipul de bază al pointerului determină tipul datei spre care indică pointerul. Exemplu: /* Acest program nu lucreaza corect */

# include <stdio.h>

void main (void) {

float x = 10.12, y;

short int *p; /* pointer la intreg */

p = &x; /* p preia adresa lui x */

y = *p; /* y preia valoarea de la adresa p */

printf ("x = %f y = %f",x,y); }

Acest program nu va atribui valoarea lui x lui y, deoarece în program se declară p ca fiind pointer la întreg scurt şi compilatorul va transfera în y numai 2 bytes (corespunzători reprezentării unui întreg scurt) şi nu 4 bytes, corespunzători unui număr real în virgulă mobilă.

7.1.2. Expresii în care intervin pointeri În general, expresiile în care intervin pointeri respectă aceleaşi reguli ca orice alte expresii din limbajul C.

• Atribuirea pointerilor Ca orice variabilă, un pointer poate fi folosit în membrul drept al unei instrucţiuni de asignare (atribuire), pentru atribuirea valorii unui pointer unui alt pointer. Exemplu: # include <stdio.h>

void main (void) {

int x;

int *p1,*p2; /* pointeri la intregi */

p1 = &x; /* p1 indica spre x */

p2 = p1 /* p2 indica tot spre x */

printf ("p1 = %p p2 = %p", p1, p2); }

/* Se afiseaza valoarea hexa a adresei lui x, nu valoarea

lui x */

Se observă că în funcţia printf() tipărirea se face cu formatul %p care specifică faptul că variabilele din listă vor fi afişate ca adrese pointer.

Page 127: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

121

Din cele de mai sus se observă că operaţia fundamentală care se execută asupra unui pointer este indirectarea, ceea ce presupune referirea la un obiect indicat de acel pointer. Exemplu: char c1 = 'a';

char *p = &c1; /* p contine adresa lui c1 */

char c2 = *p; /*c2 preia valoarea de la adresa p*/

printf ("c1 = %c c2 = %c", c1, c2);

Deci variabila indicată de p este c1, iar valoarea memorată în c1 este 'a', aşa încât valoarea lui *p atribuită lui c2 este 'a'.

• Operaţii aritmetice efectuate asupra pointerilor o Utilizarea operatorilor de incrementare şi decrementare Fie secvenţa: int *p1; /* pointer la intreg */

p1++;

De fiecare dată când se incrementează p1, acesta va indica spre următorul întreg. Astfel, dacă p1 = 2000, după efectuarea instrucţiunii p1++, acesta va fi p1 = 2004 (va indica spre următorul întreg).

� După fiecare incrementare a unui pointer, acesta va indica spre următorul element al tipului său de bază.

� După fiecare decrementare a unui pointer, acesta va indica spre elementul anterior.

Valoarea pointerilor va fi crescută sau micşorată în concordanţă

cu lungimea tipului datelor spre care aceştia indică, aşa cum se poate vedea în exemplul următor: char *ch = 3000;

short int *i = 3000;

ch 3000 i ch + 1 3001 ch + 2 3002 i + 1 ch + 3 3003 ch + 4 3004 i + 2 ch + 5 3005 ch + 6 3006 i + 3

Memoria

Cum valoarea indirectată de un pointer este o l-valoare, ea poate fi asignată şi incrementată ca orice altă variabilă. O l-valoare (left value) este un operand care poate fi plasat în stânga unei operaţii de atribuire. Verificaţi utilizarea pointerilor din programul următor:

Page 128: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

122

# include <stdio.h>

void main(void) {

short *pi, *pj, t; long *pl;

double *pd;

short i, j;

i=1; j=2; t=3;

printf("i= %d, j= %d\n", i, j);

pi=&i; pj=&j;

printf("pi= %p, pj= %p\n", pi, pj);

*pj /= *pi+1;

printf("*pi= %d *pj= %d\n", *pi, *pj);

*pj /= *pi+2;

printf("*pi= %d *pj= %d\n", *pi, *pj);

printf("++pj= %p, ++*pj= %d\n",++pj,++*pj); }

În urma rulării sale, pe calculatoarele mai moderne obţinem rezultatul

i= 1,j= 2

pi= 0065FDE0,pj= 0065FDDC

*pi= 1 *pj= 1

*pi= 1 *pj= 0

++pj= 0065FDDE,++*pj= 1 o Utilizarea operatorilor de adunare şi de scădere La sau dintr-un pointer, se pot aduna sau scădea valori de tip întreg. Rezultatul este un pointer de acelaşi tip cu cel iniţial, indicând spre un alt element din tablou. De exemplu, p1 = p1 + 9;

face ca p1 să indice spre al 9-lea element având tipul lui p1, considerând că elementul curent este indicat de p1. Evident că valoarea pointerului se va modifica corespunzător lungimii tipului datei indicată prin pointer. Exemplu: int *p1; /* Pointer la intreg */

p1 = p1 + 9;

Dacă valoarea p1 = 3000, atunci p1 + 9 va avea valoarea: (valoarea lui p1)+9*sizeof(int)=3000+9*4=3036

Aceleaşi considerente sunt valabile în cazul în care un întreg este scăzut dintr-un pointer. Dacă doi pointeri de acelaşi tip sunt scăzuţi, rezultatul este un număr întreg cu semn care reprezintă deplasamentul dintre cei doi pointeri (pointerii la obiecte vecine diferă cu 1). În cazul tablourilor, dacă pointerul rezultat indică în afara tabloului, rezultatul este nedefinit.

Page 129: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

123

Dacă p indică spre ultimul membru dintr-un tablou, atunci (p+1) are valoare nedeterminată. Observaţii : � Nu se pot aduna sau scădea valori de tip float sau double la/sau

dintr-un pointer. � Nu se pot efectua operaţii de înmulţire şi împărţire cu pointeri. Exemplu: Scăderea a doi pointeri este exemplificată în programul:

# include <stdio.h>

void main(){

int i=4, j;

float x[] = {1,2,3,4,5,6,7,8,9}, *px;

j = &x[i]-&x[i-2];

px = &x[4]+i;

printf("%d %f %p %p\n",j,*px,&x[4],px); }

o Compararea pointerilor Doi pointeri de acelaşi tip se pot compara printr-o expresie relaţională, astfel: dacă p şi q sunt doi pointeri, atunci instrucţiunile: if (p < q)

printf (“ p indica spre o adresa mai mica decit q \n “);

sunt corecte. Compararea pointerilor se utilizează când doi sau mai mulţi

pointeri indică spre acelaşi obiect comun. Exemplu: Un exemplu interesant de utilizare a pointerilor constă în examinarea conţinutului locaţiilor de memorie ale calculatorului. /* Programul afiseaza continutul locatiilor de memorie de

la o adresa specificata */

# include <stdio.h>

# include <stdlib.h> dump (start);

void main (void) {

/* start = adresa de inceput */

unsigned long int start;

printf (“Introduceti adresa de start: “);

scanf (“ %lu “, &start);

dump (start); /* Se apeleaza functia dump () */ }

dump (start) /* Se defineste functia dump() */

unsigned long int start;

{char far *p;

int t;

p = (char far *) start; /*Conversie la un pointer*/

for (t = 0; ; t++, p++) {

if (!(t%16)) printf ("/n");

printf ("%2X ", *p);

Page 130: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

124

/*Afiseaza in hexazecimal continutul locatiei

de memorie adresata cu *p*/

if (kbhit()) return;} // Stop cand se apasa orice tasta }

Programul introduce cuvântul cheie far care permite pointerului p să indice locaţii de memorie care nu sunt în acelaşi segment de memorie în care este plasat programul. Formatul %lu din scanf() înseamnă: "citeşte un unsigned long int". Formatul %X din printf() ordonă calculatorului să afişeze argumentul în hexazecimal cu litere mari (Formatul %x afişează în hexazecimal cu litere mici).

Programul foloseşte explicit un şablon de tip pentru transferul valorii întregului fără semn, start, pe care îl introducem de la tastatură, într-un pointer. Funcţia kbhit() returnează ADEVARAT dacă se apasă o tastă, altfel returnează FALS. o Utilizarea pointerilor ca parametri formali ai funcţiilor

În exemplele de până acum, s-au folosit funcţii C care atunci când erau apelate, parametrii acestor funcţii erau (întotdeauna) actualizaţi prin pasarea valorii fiecărui argument. Acest fapt ne îndreptăţeşte să numim C-ul ca un limbaj de apel prin valoare. Există totuşi o excepţie de la această regulă atunci când argumentul este un tablou. Această excepţie este explicată, pe scurt, prin faptul că valoarea unui nume al unui tablou (vector, matrice etc.) neindexate este adresa primului său element.

Deci, în cazul unui argument de tip tablou, ceea ce se pasează unei funcţii este o anumită adresă.

Folosind variabile pointer se pot pasa adrese pentru orice tip de date. Spre exemplu, funcţia scanf() acceptă un parametru de tip pointer (adresă): scanf(“%1f“,&x);

Ceea ce este important de evidenţiat este cum anume se poate scrie o funcţie care să accepte ca parametri formali sau ca argumente pointeri ?. Funcţia care recepţionează o adresă ca argument va trebui să declare acest parametru ca o variabilă pointer. De exemplu, funcţia swap() care va interschimba valorile a doi întregi poate fi declarată astfel: # include <stdio.h>

void swap(); /*Prototipul functiei swap()*/

void main(void)

{ int i,j;

i=1; j=2; printf("i= %d j= %d\n", i, j);

swap(&i,&j); /* Apelul functiei */

Page 131: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

125

printf("i= %d j= %d\n", i, j);

}

void swap(int *pi, int *pj) { short t;

t = *pi; *pi = *pj; *pj = t; } 7.2. Pointeri şi tablouri

Între pointeri şi tablouri există o strânsă legătură în limbajul C.

Există însă o mare deosebire între tablouri şi pointeri pe care trebuie să o avem mereu în vedere. Un tablou constă întotdeauna dintr-o mare cantitate de memorie, îndeajuns de mare pentru a reţine toţi octeţii corepunzători tuturor elementelor tabloului. Astfel, tabloul q declarat ca short q[100]; rezervă 2x100 octeţi de memorie iar int q[100] rezervă 4x100 octeţi de memorie. Pe de altă parte, pentru un pointer se alocă o zonă de memorie redusă la numai câţiva octeţi necesari pentru a reţine o adresă de memorie. De fapt, zona de memorie alocată unui pointer depinde de tipul pointerului (tipul datelor spre care punctează acesata) şi va fi de numai câţiva octeţi ( în exemplele anterioare - 4 octeţi). Considerăm secvenţa: char sir[80];

char *p1;

p1 = sir;

Acest fragment face ca p1 să adreseze primul element al tabloului sir. În C numele unui tablou fără indici este adresa de start a

tabloului. De fapt, numele tabloului este un pointer la tablou. Ca o concluzie, un pointer declarat sau numele unui tablou fără

indici reprezintă adrese, pe când numele unui tablou cu indici se referă la valorile stocate în acea poziţie în tablou.

Deoarece indicele inferior al oricărui vector este zero, pentru a referi al 5-lea element al şirului sir, putem scrie sir[4] sau *(p1+4) sau p1[4]. Deci pointerul p1 indică spre primul element al lui sir.

Pentru a avea acces la elementul unui tablou, în limbajul C, se folosesc 2 metode : 1. utilizarea indicilor tabloului; 2. utilizarea pointerilor .

Deoarece a doua metodă este mai rapidă, în programarea în C, de regulă, se utilizează această metodă.

Page 132: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

126

Pentru a vedea modul de utilizare a celor două metode, considerăm un program care tipăreşte cu litere mici un şir de caractere introdus de la tastatură cu litere mari: Exemplu: Versiunea cu indici void main (void) {

char sir[80];

int i;

printf ("Introduceti un sir de caractere scrise cu

litere mari: \n");

gets (sir);

printf ("Acesta este sirul in litere mici: \n");

for(i=0;sir[i];i++)

printf("%c", tolower(str[i])); }

Exemplu: Versiunea cu pointeri void main (void) {

char sir[80] , *p;

printf ("Introduceti un sir de caractere scrise cu

litere mari: \n");

gets (sir);

printf (" Acesta este sirul in litere mici: \ n");

p = sir; /* p preia adresa de inceput a sirului */

while (*p) printf (" %c ", tolower(*p++)); } Pointerii sunt rapizi şi uşor de utilizat când se doreşte referirea elementelor unui tablou în ordine strict crescătoare sau strict descrescătoare. Dacă însă se doreşte referirea aleatoare a elementelor unui tablou, atunci indexarea tabloului este cel mai simplu şi sigur de utilizat.

7.2.1. Indexarea pointerilor În C, dacă p este un pointer, iar i este întreg, p[i] este identic cu

*(p+i). Dacă avem declaraţiile: short q[100];

short *pq

atunci sunt permise şi posibile următoarele declaraţii:

Varianta cu tablou

Varianta cu pointeri

Descriere

pq=&q[0] pq=q

pq=&q[0] pq=q

Pointerul pq indică adresa primului element al tabloului q

q[n] pq[n] *(pq+n)

pq[n] înseamnă acelaşi lucru cu *(pq+n)

În C, dacă se pune un index unui pointer, ca în pq[n], atunci se

consideră această utilizare echivalentă cu *(pq+n). Cu alte cuvinte,

Page 133: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

127

orice referire la pq[n] înseamnă acelaşi lucru cu valoarea de la adresa (pq+n).

Exemplu: Programul următor realizează tipărirea pe ecran a numerelor cuprinse între 1 şi 10.

void main (void) {

int v[10]={1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

int *p, i;

p = v; /* p indica spre v */

for (i=0;i<10;i++) printf ("%d", p[i]); }

Utilizarea constantelor şir în locul poinetrilor la caractere este posibilă dar nu este uzuală. Exemplu: # include <stdio.h>

void main(){

char *sir = "To be or not to be", *altsir;

printf("%s\n", "That don't impress me much"+5);

printf("%c\n",*("12345"+3));

printf("%c\n","12345"[1]);

puts("string\n");

altsir = "American pie";

printf("sir = %s\naltsir = %s\n",sir,altsir); }

Exemplu de utilizare a stivei. Stiva este o listă cu acces tip LIFO (last in - first out). Pentru a avea acces la elementele stivei, se utilizează doua rutine: push() şi pop(). Calculatorul păstrează stiva într-un tablou, iar rutinele push() şi pop() realizează accesul la elementele stivei utilizând pointeri. Pentru memorarea vârfului stivei, utilizăm variabila tos (top of stack).

Considerăm că folosim numai numere de tip întreg. În programul main(), rutinele push() şi pop() sunt utilizate astfel: push() citeşte numerele introduse de la tastatură şi le memorează în stivă dacă acestea sunt nenule. Când se introduce un zero, pop() extrage valoarea din stivă.

# include <stdlib.h>

# include <stdio.h>

void push(); /* Prototipul functiei push() */

int pop(); /* Prototipul functiei pop() */

// Se rezerva pentru stiva 50x4 = 200 locatii

int stack[50];

int *p1, *tos;

void main(void) {

int value;

p1 = stack; /* p1 preia adresa bazei stivei */

tos = p1; /* tos va contine varful stivei */

do { scanf ("%d", &value);

Page 134: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

128

if (value != 0) push (value);

else printf ("Nr. extras din stiva este %d \n",

pop()); } while (value != -1); }

void push(int i) /* Functia push() */

{ p1++;

if (p1==(tos+50)) {

printf("\n Stiva este plina.");

exit (0);}

*p1 = i;

printf("introdus in stiva\n");

}

int pop()

{ if (p1 == tos) {

printf ("\n Stiva este complet goala.");

exit (0); }

p1 --;

return *(p1+1); }

7.2.2. Pointeri şi şiruri Deoarece numele unui tablou fără indici este un pointer la primul element al tabloului, pentru implementarea unor funcţii care manipulează şiruri, se pot utiliza pointeri. §tim că funcţia strcmp(s1, s2) realizează compararea şirurilor s1 şi s2 şi întoarce 0 dacă s1 = s2, o valoare negativă, dacă s1 < s2 şi o valoare pozitivă, dacă s1 > s2. Exemplu: Prezentăm o variantă de scriere a funcţiei strcmp(s1,s2) char *s1, *s2;

{ while (*s1)

if (*s1 - *s2) /* Daca valoarea punctata de s1

este diferita de valoarea punctata de s2, */

return *s1-*s2; /* Returneaza diferenta */

else {s1++; s2++;}

return '\0'; //Se returneaza 0 in caz de egalitate }

Reamintim că un şir în C se termină cu caracterul NULL. De aceea, instructiunea while(*s1) rămâne adevărată până când se întâlneşte caracterul NULL, care este o valoare falsă. Dacă într-o expresie se utilizează un şir constant, calculatorul tratează constanta ca pointer la primul caracter al şirului. Exemplu: Programul următor afişează pe ecran mesajul " Acest program funcţionează ": # include <stdio.h>

void main (void) {

char *s;

s = " Acest program functioneaza ";

printf (s); }

Page 135: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

129

7.2.3. Preluarea adresei unui element al unui tablou Până acum s-a văzut că un pointer poate să adreseze primul element al unui tablou. Este posibil să se adreseze orice element al unui tablou aplicând operatorul & unui tablou indexat. De exemplu,

p = &x[2]; plasează adresa celui de-al 3-lea element al vectorului x în pointerul p. Un domeniu în care această practică este esenţiala constă în găsirea unui subşir într-un şir dat. Exemplu: Programul următor afişează ultima parte a unui şir introdus de la tastatură, din punctul în care se întâlneşte primul spaţiu: # include <stdio.h>

void main (void) {

char s[80];

char *p;

int i; printf (" Introduceti un sir : \n ");

gets (s);

/* Gaseste primul spatiu sau sfarsitul sirului */

for (i = 0; s[i] && s[i] != ' '; i++)

p = & s[i+1];

printf (p); }

Dacă p indică spre un spaţiu, programul va afişa spaţiul şi apoi subşirul rămas. Dacă în şirul introdus nu este nici un spaţiu, p indică spre sfârşitul şirului şi atunci nu se va afişa nimic. De exemplu, dacă se introduce “my friend“, atunci printf() afişează mai întâi un spaţiu şi apoi “friend“.

7.2.4. Tablouri de pointeri Putem construi tablouri de pointeri în aceeaşi manieră în care se definesc alte tipuri de date. Exemplu: int *x[10]; // Vector de 10 pointeri la intregi

char *p[20]; // Vector de 20 pointeri la caracter

Pentru atribuirea unei variabile întregi, var, celui de al treilea element al tabloului de pointeri *x[10], se va scrie: x[2] = &var; Pentru găsirea valorii lui var, se va scrie: y = *x[2]; //Valoarea lui var este atribuita lui y Exemplu: Tablourile de pointeri pot fi utilizate în construirea mesajelor de eroare, astfel:

Page 136: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

130

char *err[ ] = {

" Cannot open file \n ",

" Read error \n ", " Write error \n " };

selmes (int num) /* Selecteaza un mesaj */

{ scanf("%d", &num); /* Se introduce un

numar de la tastatura */

printf("%s", err[num]); }

Funcţia printf() este apelată din funcţia selmes(). Aceasta va afişa mesajul de eroare indexat prin numărul de eroare num, care este pasat ca argument funcţiei selmes(). De exemplu, dacă se introduce 2, atunci se va afişa mesajul: Write error Atentie !. Trebuie facută distincţia între: int *v[10]; // Tablou de 10 pointeri la intregi

int (*v)[10]; // Pointer la un tablou de 10 intregi

Pentru aceasta trebuie ţinut cont de faptul că * este un operator prefixat, iar [] şi () sunt operatori postfixaţi. Deoarece prioritatea operatorilor postfixaţi este mai mare decât cea a operatorilor prefixaţi, atunci când se doreşte schimbarea priorităţii, trebuie folosite paranteze.

7.2.5. Pointeri la pointeri Un tablou de pointeri este ceea ce numim pointeri la pointeri. Conceptul de tablou de pointeri este simplu, deoarece indexarea tabloului conduce la clarificarea semnificaţiei lui.

Un pointer la un pointer este o formă de indirectare multiplă sau un lanţ de pointeri.

În cazul unei indirectari simple, valoarea pointerului este adresa variabilei care conţine valoarea dorită: Pointer Variabilă

Adresă ---------> Valoare În cazul unui pointer la pointer, primul pointer conţine adresa celui de-al doilea pointer, care indică spre variabila ce conţine valoarea dorită:

Pointer Pointer Variabilă Adresă ---------> Adresă ---------> Valoare

Declararea indirectărilor multiple se face sub forma: /* cpp este un pointer la pointer la caracter */ char **cpp;

/* newbalance este un pointer la pointer la float */

Page 137: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

131

float **newbalance; Pentru a avea acces la o valoare indirectată printr-un pointer la pointer este necesară, de asemenea, utilizarea operatorului * de două ori, aşa cum se vede în exemplul următor: # include <stdio.h>

void main (void) {

int x, *p, **q;

x = 10;

p = &x; /* p preia adresa lui x */

q = &p; /* q preia adresa lui p */

printf(" %d ", **q);/*Se afiseaza valoarea lui x*/ }

7.2.6. Iniţializarea pointerilor Secvenţa: char *p;

char s[] = "Hello world \n ";

p = s; /* p indica spre s */

este echivalentă cu: char *p = "Hello world \n";

Într-un program, p din ultima declaraţie poate fi utilizat ca orice alt şir. Astfel, programul următor este corect: # include <stdio.h>

char *p = " Hello world \n ";

void main (void) {

register int t; /*Se tipareste sirul in ordine directa si inversa*/

printf (p);

for(t = strlen(p)-1; t > -1; t--)

printf("%c",p[t]); }

Observaţie: Neiniţializarea pointerilor sau iniţializarea greşită a acestora, poate duce la erori care, în cazul programelor de dimensiuni mari, sunt foarte greu de depistat şi pot avea urmări catastrofale. Exemplu: Considerăm programul: # include <stdio.h>

void main(void) {

int x, *p; x = 10;

*p = x;

printf ("%d\n", *p); }

Acest program atribuie valoarea 10 anumitor locaţii de memorie necunoscute. Programul nu va oferi niciodată o valoare pointerului p dar va tipări valoarea lui x.

Page 138: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

132

Exemplu: Considerăm acum următorul program: # include <stdio.h>

void main (void) {

int x, *p;

x = 10; p = x;

printf("%d",*p); }

Funcţia printf() nu va afişa niciodată valoarea lui x (care este 10), dar va tipări o valoare necunoscută. Aceasta datorită atribuirii greşite: p = x; Instrucţiunea atribuie valoarea 10 pointerului p, care se presupune că reprezintă o adresă şi nu o valoare. Pentru a corecta programul, trebuie scris: p = &x;

7.2.7. Alocarea dinamică a memoriei Există două metode principale prin care un program C poate memora informaţii în memoria principală a calculatorului.

• Prima metodă foloseşte variabilele globale şi locale. În cazul variabilelor globale, memoria ce li se alocă este fixă pe tot timpul execuţiei programului. Pentru variabilele locale, programul alocă memorie în spaţiul stivei, în timpul execuţiei programului. Deşi variabilele locale sunt eficient de implementat, în C, de multe ori, utilizarea acestora, necesită cunoaşterea în avans a cantităţii de memorie necesare în fiecare situaţie.

M emoria

H igh Stiva

M em or ie liber á

pentr u alocar e

(heap)

V ar iabile globale

(statice)

L ow Pr ogr am

• A doua metodă de alocare a memoriei, constă în utilizarea

funcţiilor de alocare dinamică malloc() şi free(). Prin această metodă, un program alocă memorie pentru diversele informaţii în spaţiul

Page 139: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

133

memoriei libere numită heap, plasată între programul util şi memoria sa permanentă şi stivă. Se observă că stiva creşte în jos, iar dimensiunea acesteia depinde de program. Un program cu multe funcţii recursive va folosi mult mai intens stiva în comparaţie cu un program ce nu utilizeaza funcţii recursive, aceasta deoarece adresele de retur şi variabilele locale corespunzătoare acestor funcţii sunt salvate în stivă.

Funcţiile malloc() şi free() Aceste funcţii formează sistemul de alocare dinamică a memoriei în C şi fac parte din fisierul antet <stdlib.h>. Acestea lucrează împreună şi utilizează zona de memorie liberă plasată între codul program şi memoria sa permanentă (fixă) şi vârful stivei, în scopul stabilirii şi menţinerii unei liste a variabilelor memorate. De fiecare dată când se face o cerere de memorie, funcţia malloc() alocă o parte din memoria rămasă liberă. De fiecare dată când se face un apel de eliberare a memoriei, funcţia free() eliberează memorie sistemului. Declararea funcţiei malloc() se face sub forma: void *malloc (int numar_de_bytes); Aceasta întoarce un pointer de tip void, ceea ce înseamnă că trebuie utilizat un şablon explicit de tip atunci când pointerul returnat de malloc() se atribuie unui pointer de un alt tip. Dacă apelul lui malloc() se execută cu succes, malloc() va returna un pointer la primul byte al zonei de memorie din heap ce a fost alocată. Dacă nu este suficientă memorie pentru a satisfce cererea malloc(), apare un eşec şi malloc() returnează NULL. Pentru determinarea exactă a numărului de bytes necesari fiecărui tip de date, se poate folosi operatorul sizeof(). Prin aceasta, programele pot deveni portabile pe o varietate de sisteme. Funcţia free() returnează sistemului memoria alocată anterior cu malloc(). După eliberarea memoriei cu free(), aceasta se poate reutiliza folosind un apel malloc(). Declararea funcţiei free() se realizează sub forma: free(void *p); Funcţia free() eliberează spaţiul indicat de p şi nu face nimic dacă p este NULL. Parametrul actual p trebuie să fie un pointer la un spaţiu alocat anterior cu malloc(), calloc() sau realloc(). Exemplu: Următorul program va aloca memorie pentru 40 de întregi, va afişa valoarea acestora, după care eliberează zona, utilizând free():

Page 140: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

134

# include <stdio.h>

# include <stdlib.h>

void main(void) { int t, *p;

p = (int *) malloc(40*sizeof(int));

if (!p) printf("Out of memory \n");

//Verificati neaparat daca p este un pointer corect

else {

for (t=0; t<40; ++t) *(p + t) = t;

for (t=0; t < 40; ++t)

printf("%d", *(p + t));

free(p);

} }

Funcţiile calloc() şi realloc() Funcţia calloc() alocă un bloc de n zone de memorie, fiecare de dim octeţi şi setează la 0 zonele de memorie; funcţia returnează un pointer la acel bloc (adresa primului octet din bloc). Declararea funcţiei se face cu:

void *calloc(unsigned int n, unsigned int dim);

Funcţia realloc() primeşte un pointer la un bloc de memorie alocat în prealabil (declarat pointer de tip void) şi redimensionează zona alocată la dim octeţi (dacă este nevoie, se copiază vechiul conţinut într-o altă zonă de memorie). Declararea funcţiei se face cu:

void *realloc(void *ptr, unsigned int dim);

7.2.8. Pointeri la structuri Limbajul C recunoaşte pointerii la structuri în acelaşi mod în care se recunoaşte pointerii la orice alt tip de variabilă. Declararea unui pointer la structură se face plasând operatorul * în faţa numelui unei variabile structură. De exemplu, pentru structura addr definită mai înainte, următoarea instrucţiune declară pe addr_pointer ca pointer la o dată de acest tip :

struct addr *addr_pointer;

O utilizare importantă a pointerilor la structură constă în realizarea apelului prin adresă într-o funcţie. Când unei funcţii i se transmite ca argument un pointer la o structură, calculatorul va salva şi va reface din stivă numai adresa structurii, conducând astfel la cresterea vitezei de executare a programului.

Page 141: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

135

Pentru a găsi adresa unei variabile structură, se plasează operatorul & înaintea numelui variabilei structură. De exemplu, dându-se următorul fragment : struct balanta {

float balance;

char name[80];

} person;

struct balanta *p;

/* se declara un pointer la structura */

atunci: p = &person;

plasează adresa lui person în pointerul p. Pentru a referi elementul balance, se va scrie:

(*p).balance

Deoarece operatorul punct are prioritate mai mare decât operatorul *, pentru o referire corectă a elementelor unei structuri utilizând pointerii sunt necesare paranteze. Actualmente, pentru referirea unui element al unei variabile structură dându-se un pointer la acea variabilă, există două metode: Prima metodă utilizează referirea explicită a pointerului nume-structură şi este considerată o metoda învechită (obsolete), iar a doua metodă, modernă, utilizează operatorul săgeată -> (minus urmat de mai mare). Exemplu: Pentru a vedea cum se utilizează un pointer-struct, examinăm următorul program care afişează ora, minutul şi secunda utilizând un timer software. # include <stdio.h>

void actualizeaza();

void afiseaza(), delay(); struct tm { /* se defineste structura tm */

int ore;

int minute;

int secunde;};

void main()

{struct tm time; // Structura time de tip tm

time.ore = 0;

time.minute = 0;

time.secunde = 0;

for (;;) {

actualizeaza (&time);

afiseaza (&time); }}

void actualizeaza(t)

/*Versiunea 1- referirea explicita prin pointeri */

struct tm *t; {

Page 142: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

136

(*t).secunde ++;

if ((*t).secunde == 60) {

(*t).secunde = 0; (*t).minute ++; }

if ((*t).minute == 60) {

(*t).minute = 0;

(*t).ore ++;}

if ((*t).ore == 24) (*t).ore = 0;

delay();}

void afiseaza(t) // Se defineste functia afiseaza()

struct tm *t; {

printf ("%d : ", (*t).ore);

printf ("%d : ", (*t).minute);

printf ("%d ", (*t).secunde);

printf ("\n");}

void delay() /* Se defineste functia delay() */

{ long int t;

for (t = 1;t<140000000;++t);}

Pentru ajustarea duratei întârzierii se poate modifica valoarea contorului t al buclei. Se vede ca programul defineşte o structură globală numită tm, dar nu declară variabilele. In interiorul funcţiei main() se declară structura "time" şi se iniţializează cu 00:00:00. Programul transmite adresa lui time funcţiilor actualizeaza() şi afiseaza(). În ambele funcţii, argumentul acestora este declarat a fi un pointer-structură de tip "tm". Referirea fiecărui element se face prin intermediul acestui pointer. Elementele unei structuri pot fi referite utilizând în locul operatorului ".", operatorul "->". De exemplu : (*t).ore este echivalent cu t -> ore Atunci programul de mai sus se poate rescrie sub forma: # include <stdio.h>

void actualizeaza();

void afiseaza(), delay(); struct tm { /* se defineste structura tm */

int ore;

int minute;

int secunde;};

void main()

{struct tm time; // Declara structura time de tip tm

time.ore = 0;

time.minute = 0;

time.secunde = 0;

Page 143: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

137

for (;;) {

actualizeaza (&time);

afiseaza (&time); }}

void actualizeaza(t)

/*Versiunea 1- referirea explicita prin pointeri */

struct tm *t; {

t->secunde ++;

if (t->secunde == 60) {

t->secunde = 0;

t->minute ++; }

if (t->minute == 60) {

t->minute = 0;

t->ore ++;}

if (t->ore == 24) t->ore = 0;

delay();}

void afiseaza(t) // Se defineste functia afiseaza()

struct tm *t; {

printf ("%d : ", t->ore);

printf ("%d : ", t->minute);

printf ("%d ", t->secunde);

printf ("\n");}

void delay() /* Se defineşte funcţia delay() */

{ long int t;

for (t = 1;t<140000000;++t);}

7.2.9. Structuri dinamice liniare de tip listă Structura a fost introdusă ca fiind o grupă (colecţie) ordonată de date care pot fi de tipuri diferite şi care au anumite legături logice între ele. Adesea, această grupă de date se repetă de mai multe ori. Se ajunge astfel la noţiunea de tablou, ale cărui elemente sunt fiecare câte o structură. Tabloul de date definit în acest fel este şi el de acest tip nou, pe care îl mai numim şi tip structurat.

După cum s-a remarcat, în exemplele de până acum am folosit structuri de tip static. Static se referă la faptul că tablourile de structuri au dimensiuni predefinite. Termenul structuri de date statice exprimă ideea că putem modifica cu uşurinţă valorile componentelor dar este foarte dificil să mărim numărul lor peste limita maximă declarată înainte de compilare. Mai mult, prin ştergerea unor elemente structură dintr-un tablou de structuri obţinem goluri în tablou, pe care le putem umple numai printr-o gestiune foarte precisă a poziţiilor din tablou. Folosind pointeri la tabloul de structuri, este foarte posibil să indicăm spre un element care a fost şters. Dacă dorim o reprezentare contiguă

Page 144: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

138

în memorie, va trebui să compactăm (sau să defragmentăm) tabloul la fiecare ştergere a unui element de tip structură. Mai mult, dacă dorim să schimbăm ordinea în care s-au stocat elementele din tablou sau să inserăm într-o poziţie intermediară un element nou, aceaste operaţii devin foarte anevoioase. Într-un exemplu anterior am folosit secvenţa # define SIZE 100

struct addr {

char name[20];

char street[30];

char city[15];

char state[10];

unsigned int zip;

} addr_info[SIZE];

Rezultă că am definit un tablou static cu 100 de elemente, cu numele addr_info, la care fiecare element este o structură cu şablonul addr. Dacă în această aplicaţie, chiar în timpul execuţiei programului, constatăm că avem nevoie de mai mult de 100 de rezervări de memorie, nu există nici o posibilitate de a mări tabloul fără a modifică şi apoi recompila sursa programului. Tabloul trebuie redeclarat cu o dimansiune mai mare (în cazul nostru prin #define SIZE 200, de exemplu), apoi se recompilează programul şi abia apoi se execută cu succes. Acest lucru prezentă două inconveniente (vezi [Mocanu, 2001]):

1- Execuţia şi obţinerea rezultatelor sunt amânate şi produc întârzieri pentru modificarea programului sursă, recompilare şi reintroducerea datelor care fuseseră deja introduse până în momentul în care s-a constatat necesitatea măririi tabloului.

2- Este posibil ca programul sursă să nu fie disponibil. Eliminarea neajunsurilor prezentate mai sus se face prin implementarea listelor cu ajutorul unor structuri de date dinamice.

Când apare necesitatea introducerii unui element în listă, se va aloca dinamic spaţiu pentru respectivul element, se va crea elementul prin înscrierea informaţiilor corespunzătoare şi se va lega în listă. Când un element este scos din listă spaţiul de memorie ocupat de acesta va fi eliberat şi se vor reface legăturile.

Structurile dinamice se construiesc prin legarea componentelor structurii, numite variabile dinamice. O variabilă dinamică ce intră în componenţa unor structuri de date dinamice (nod) prezintă în structura sa două părţi:

Page 145: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

139

1. Partea de informaţie (info) ce poate aparţine unui tip simplu (int, char, float, double, etc.) conform cu necesităţile problemei.

2. Partea de legătură (link) ce conţine cel puţin un pointer de legătură (next) la o altă variabilă dinamică, de obicei de acelaşi tip, ce realizează înlănţuirea variabilelor dinamice în structuri de date dinamice.

Dintre structurile de date dinamice, cele mai simple şi mai utilizate sunt listele. Lista este o structură liniară, de tipul unui tablou unidimensional (vector), care are un prim element şi un ultim element. Celelalte elemente au un predecesor şi un succesor. Elementele unei liste se numesc noduri.

La rândul lor, listele pot fi grupate în mai multe categorii, cele mai importante fiind listele simplu înlănţuite, listele circulare şi listele dublu legate.

Principalele operaţii care se pot efectua asupra unei liste sunt: crearea listei, adăugare/ştergere/modificare au unui element (nod), accesul la un element şi ştergerea în totalitate a listei.

Lista simplu înlănţuită este cel mai simplu tip de listă din punctul de vedere al legării elementelor: legătura între elemente este într-un singur sens, de la primul către ultimul. Există un nod pentru care pointerul spre nodul următor este NULL. Acesta este ultimul nod al listei simplu înlănţuite (sau simplu legate). De asemenea, există un nod spre care nu pointează nici un alt nod, acesta fiin primul nod al listei. O listă simplu înlănţuită poate fi identificată în mod unic prin primul element al listei. Determinarea ultimului element se poate face prin parcurgerea secvenţială a listei până la întâlnirea nodului cu pointerul spre nodul următor cu valoarea NULL.

Listă simplă înlănţuită

info

next

info

next

info

NULL

Listele circulare sunt un alt tip de liste pentru care relaţia de precedenţă nu mai este liniară ci ultimul element pointează către primul. Procedurile necesare pentru crearea şi utilizarea unei liste circulare sunt extrem de asemănătoare cu cele de la liste liniare, cu

Page 146: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

140

singura deosebire că ultimul element nu are adresa de pointer vid (NULL) ci adresa primului element.

Listă circulară

info

next

info

next

info

next

Listele dublu legate sunt utile în rezolvarea unor probleme care necesită parcurgeri frecvente ale structurilor dinamice în ambele sensuri. Ele pot fi privite ca structuri dinamice ce combină două liste liniare simplu înlănţuite ce partajează acelaşi câmp comun info, una fiind imaginea în oglindă a celeilalte.

Listă dublu legată

info

next

NULL

info

next

previous

info

NULL

previous

Pointerul next indică spre următorul nod, iar câmpul previous

indică spre câmpul anterior. Vom prezenta în continuare modul în care putem proiecta

funcţiile principale care acţionează asupra unei structuri dinamice. Pentru aceasta vom utiliza două variabile globale de tip pointer, una care pointează spre primul nod al listei iar cealaltă spre ultimul nod al listei. Vom denumi aceste variabile first respectiv last. Particularizările se vor face pe exemplul bazei de date construite anterior.

Tipul unui nod se declară în felul următor:

General Particular struct tip_nod { declaratii

struct tip_nod *next; };

struct addr { char name[20]; char street[30]; char city[15]; char state[10]; unsigned int zip; struct addr *next; };

Page 147: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

141

Atât la crearea unei liste cât şi la inserarea unui nod se va apela funcţia malloc() pentru a rezerva spaţiu de memorie pentru un nod. Zona alocată prin intermediul funcţiei malloc() se poate elibera folosind funcţia free(). Propunem conceperea unui program asemănător cu programul de exploatare a unei baze de date conceput anterior dar care să folosească în locul unui tablou static o listă simplu înlănţuită. Programul poate fi extins ulterior pentru structuri dinamice mai complexe, care să folosească liste dublu înlănţuite sau circulare. Programul va avea următoarele facilităţi:

1. Crearea unei liste simplu înlănţuite în memorie (pentru prima oară).

2. Exploatarea unei liste simplu înlănţuite în memorie: 2.1. Inserarea unor înregistrări (noduri):

a) Inserarea unui nod înaintea primului nod al listei b) Inserarea unui nod înainte/după un nod intern al listei c) Inserarea unui nod după ultimul nod al listei

(adăugare la coada listei) 2.2. Ştergerea unor înregistrări

a) Ştergerea primului nod al listei b) Ştergerea unui nod intern al listei c) Ştergerea ultimului nod al listei

3. Salvarea din memorie pe disc a unei liste simplu înlănţuite 4. Citirea de pe disc în memorie a bazei de date salvate

anterior 5. Afişarea pe ecran, înregistrare cu înregistrare, a bazei de

date conţinute în lista dinamică. Programul este prevăzut cu o intefaţă simplă prin care

utilizatorul poate alege dintre opţiunile pe care le are la dispoziţie. Interfaţa este sub forma unui meniu din care, prin tastarea iniţialelor comenzilor, acestea se lansează în execuţie.

Vom descrie pe rând funcţiile care îndeplinesc sarcinile enumerate mai sus. Pentru o mai bună proiectare a programului, vom folosi atât modularizarea internă prin construirea mai multor funcţii cât şi o modularizare externă prin izolarea programului principal de restul funcţiilor care se folosesc.

Page 148: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

142

Bază de date cu listă simplu înlănţuită

Interfaţa cu utilizatorul

Comenzi pentru citire/scriere

pe disc

Citire de

pe disc în lista

dinamică

Salvarea pe

disc a listei dinamice din

memorie

Comenzi pentru procesarea

listei dinamice

Inserare Ştergere

- prima înregistrare - ultima înregistrare - înregistrare

intermediară

Afişare pe ecran a înregistrărilor din baza de date

Exemplu: Programul principal bd_main.c # include "local.h" void main() {

char choice;

for (; ;) { choice = menu();

switch (choice) {

case 'c' : create_list(); break;

case 'l' : loadf_list(); break;

case 's' : savef_list(); break;

case 'd' : display_list(); break;

case 'i' : insert(); break;

case 'e' : erase(); break;

case 'q' : exit(0); break;}}}

Fişierul header local.h este: # include <stdio.h>

# include <ctype.h>

# include <string.h>

# include <stdlib.h>

typedef struct addr {

char name[20];

char street[30];

char city[15];

char state[10];

Page 149: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

143

unsigned int zip;

struct addr *next;}TNOD;

TNOD *first, *last; FILE *fp;

extern int create_list();

extern char menu();

extern void display_list(), insert(), erase();

extern int loadf_list(), savef_list();

extern TNOD *add_nod();

Cu ajutorul acestui fişier header se realizează definirea şablonului structurii addr şi apoi, prin comanda typedef, se defineşte un nou tip de date numit TNOD. First şi last sunt pointeri la structuri de acest tip iar fp este pointer la fişier (file pointer). Cu extern se definesc prototipurile unor funcţii care nu se găsesc în fişierul bd_main.c ci în fişierul bd_bib.c unde sunt colectate toate funcţiile pe care le folosim. Acest fişier are la rândul său un fişier header numit local1.h care conţine:

# include <stdio.h>

# include <ctype.h>

# include <string.h>

# include <stdlib.h>

extern FILE *fp;

typedef struct addr {

char name[20];

char street[30];

char city[15];

char state[10];

unsigned int zip;

struct addr *next;}TNOD;

extern TNOD *first, *last;

Prin cele două fişiere header cele două fişiere sursă bd_main.c şi bd_bib.c se pot compila împreună, rezultând un singur fişier executabil. Interfaţa cu utilizatorul Aceasta constă într-un meniu principal, care permite accesarea funcţiilor principale, şi din două submeniuri pentru operaţiile de ştergere şi inserare de înregistrări. /* Functia menu() principala */

char menu() { char s[5],ch;

do {

printf ("\n(C)reate new list\n");

printf ("(L)oad list from file\n");

printf ("(S)ave to file\n");

Page 150: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

144

printf ("(D)isplay list\n");

printf ("(I)nsert record\n");

printf ("(E)rase\n"); printf ("(Q)uit\n");

printf (" Alegeti optiunea: ");

gets(s);

ch=s[0];

} while (!strrchr("clsdieq",ch));

return tolower(ch); }

//meniu functia de stergere

char menu_del() {

char s[5],ch;

do {

printf ("(E)ntire list delete\n");

printf ("(F)irst record delete\n");

printf ("(L)ast record delete\n");

printf ("(I)ntermediate delete\n");

printf ("(Q)uit\n");

printf (" Option ? ");

gets(s);ch=s[0];

} while (!strrchr("efliq",ch));

return tolower(ch); }

//meniu functia de inserare

char menu_insert() {

char s[5],ch;

do {

printf ("(F)irst record insert\n");

printf ("(L)ast record insert\n"); printf ("(I)ntermediate insert\n");

printf ("(Q)uit\n");

printf (" Option ? ");

gets(s);ch=s[0];

} while (!strrchr("fliq",ch));

return tolower(ch); }

Încărcarea bazei de date de pe disc Baza de date este înregistrată pe disc în fişierul maillist.dat care se crează la prima salvare a listei dinamice din memorie. Funcţia loadf_nod() citeşte o înregistrare (record) din fişier, returnând 1 în caz de reuşită, -1 dacă se ajunge la sfârşitul fişierului şi 0 în cazul în care există o eroare de citire de pe disc. /* Functia loadf_nod() from file*/

int loadf_nod(TNOD *p)

{if (fread(p,sizeof(TNOD),1,fp)==1) return 1;

else if (feof(fp)) {fclose (fp); return -1;}

else {printf ("File read error\n"); return 0;}}

Page 151: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

145

/* Functia loadf_list() from file */

int loadf_list() {

int n=sizeof(TNOD),i; TNOD *p;

first=last=NULL;

if ((fp = fopen("maillist","rb")) == NULL) {

printf("Cannot open file\n ");return 0;}

while (((p=(TNOD

*)malloc(n))!=NULL)&&((i=loadf_nod(p))==1))

if (first==NULL){ /* prima creare */

first=last=p;

first->next=NULL;}

else {

last->next=p;last=p;

last->next=NULL;}

if (p==NULL){ printf("Memorie insuficienta pentru

lista\n");

return 0;}

free(p);return i;}

Funcţia loadf_list() alocă fiecare înregistrare citită de pe disc unui nod al listei dinamice construite în memorie. În pointerii first şi last se stochează în permanenţă adresele primei şi ultimei înregistrări (nod sau structură). Salvarea bazei de date pe disc Reprezintă operaţiunea inversă celei de citire. Lista simplu înlănţuită din memorie se salvează în întregime pe disc în fişierul maillist.dat. Spre deosebire de funcţia loadf_list() care apela la funcţia loadf_nod(), funcţia savef_list() nu face nici un apel la o altă funcţie definită de utilizator: /* Functia savef_list() */

int savef_list() {

TNOD *p;

if ((fp = fopen("maillist", "wb")) == NULL) { printf (" Cannot open file\n ");return 0;}

p=first;

do {

if(fwrite(p, sizeof(TNOD), 1,fp) !=1)

{printf (" File write error \n ");

fclose (fp);return 0;}

p=p->next;} while (p!=NULL);

fclose (fp);return 1;} Crearea unei liste simple înlănţuite La început variabilele first şi last au valoarea NULL, lista fiind vidă. Crearea unei liste se face prin funcţia create_list. Ea returnează 0

Page 152: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

146

pentru eroare şi 1 dacă operaţia reuşeşte. Prin această funcţie se iniţializează o listă dinamică în memorie, fără a face o citire a unei baze de date create anterior. Aceasta este prima funcţie care se apelează când construim o bază de date nouă. /* Functia create_list() new */

void create_list() {

int n=sizeof(TNOD);

char ch='c';

TNOD *p;

first=last=NULL;

while ((p=(TNOD *)malloc(n))!=NULL)

{p=add_nod(p);

if (first==NULL){ /* prima creare */

first=last=p;

first->next=NULL;}

else {

last->next=p;

last=p;

last->next=NULL;}

printf ("Exit? (x): ");

if ((ch=getchar())=='x') break;}

if (p==NULL){

printf("Memorie insuficienta pentru lista\n"); free(p);}}

Afişarea listei dinamice din memorie Prin această operaţie, realizată de funcţia display_list(), se

afişează secvenţial toate înregistrările conţinute în nodurile listei pornind de la prima şi terminând cu ultima. // functie de afisare antet

void disp_antet() {

printf("\n%20s","Name");

printf("%30s","Street");

printf("%15s","City");

printf("%10s","State");

printf("%5s\n","Zip");}

// afisare o singura inregistrare (nod)

void disp_nod(TNOD *p) {

printf("%20s",p->name);

printf("%30s",p->street);

printf("%15s",p->city);

printf("%10s",p->state);

printf("%5d",p->zip);}

/* Functia display_list() */ void display_list() {

TNOD *p;

disp_antet();

Page 153: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

147

p=first;

if (p!=NULL)

do { disp_nod(p);

p=p->next;

getchar();} while (p!=NULL);

else printf("Lista vida !\n");}

Funcţia display_list() apelează la funcţia disp_nod() care afişeză o singură înregistrare. Dacă este nevoie de afişarea unui cap de tabel (antet) se apelează la funcţia disp_antet().

Inserarea unor noduri în lista dinamică Această operaţie presupune introducerea unor noduri

(înregistrări) fie la începutul listei înaintea primului nod, fie la sfârşitul său (adăugare) după ultimul nod, fie între două noduri vecine nesituate la extremităţi. Funcţiile listate mai jos realizează aceste sarcini. // functia de inserare

void insert() {

char choice;

for (; ;) {

choice = menu_insert();

switch (choice) {

case 'f' : ins_first(); break;

case 'l' : ins_last(); break;

case 'i' : ins_int(); break;

case 'q' : break;} break;}}

/* Functia insert_last() */

void ins_last() {

int n=sizeof(TNOD);

char ch='c',s[2];

TNOD *p;

/* ne pozitionam pe ultimul nod */

p=first;

while (p->next!=NULL) p=p->next;

// se creaza lista in memorie

while (ch!='x') { p=(TNOD *)malloc(n);

last->next=p;

p=add_nod(p);

p->next=NULL;

last=p;

printf("Exit? (x): ");

gets(s);ch=s[0];}}

/* Functia insert_first() */

void ins_first() {

int n=sizeof(TNOD);

char ch='c',s[2];

Page 154: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

148

TNOD *p;

while (ch!='x') {

p=(TNOD *)malloc(n); p->next=first;

p=add_nod(p);

first=p;

printf("Exit? (x): ");

gets(s);ch=s[0];}}

// inserare dupa inregistrarea afisata

void ins_int() {

TNOD *p, *pi;

char ch='n', s[2];

disp_antet();

p=first;

while ((p!=NULL)&&(ch!='y'))

{ disp_nod(p);

printf("Here ? [y]: ");

gets(s);ch=s[0];

if (ch!='y') p=p->next;}

pi=(TNOD *)malloc(sizeof(TNOD));

pi=add_nod(pi);

pi->next=p->next;

p->next=pi;}

La inserarea unui nod, este nevoie ca acesta să fie legat de cele între care se introduce cu ajutorul pointerilor next. Dacă se doreşte ca nodul inserat să fie primul sau ultimul din listă, este nevoie să modificăm pointerii globali first şi last. Ştergerea unor noduri din lista dinamică Nodurile pe care dorim să le ştergem pot fi interioare sau se pot afla la extremităţi. În acest caz, este nevoie să modificăm pointerii first şi last . În toate cazurile este nevoie să refacem legăturile distruse după dispariţia prin ştergere a unor noduri. §tergerea nu este efectivă, ci numai se refac legăturile şi se eliberează cu funcţia free() zona de memorie alocată în prealabil (prin inserare sau creare) cu funcţia malloc(). Mai mult, avem opţiunea de a şterge întreaga listă din memorie în scopul înlocuirii în totalitate a datelor conţinute în înregistrări. // funcţia de ştergere

void erase() {

char choice;

for (; ;) {

choice = menu_del();

switch (choice) {

case 'e' : del_list(); break;

Page 155: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

149

case 'f' : del_first(); break;

case 'l' : del_last(); break;

case 'i' : del_int(); break; case 'q' : break;} break;}}

// se sterge intreaga lista si se elibereaza memoria

void del_list() {

TNOD *p,*pu;

p=first;pu=p->next;

while (pu!=NULL) {free(p);

p=pu;pu=pu->next;}

first=NULL;last=NULL;}

// sterge prima inregistrare

void del_first() {

int n=sizeof(TNOD);

char ch='c',s[2];

TNOD *p,*pu;

while (ch!='x') {

p=first;pu=p->next;

free(p);

first=pu;

printf("Exit? (x): ");

gets(s);ch=s[0];}}

// stergere ultima inregistrare

void del_last() {

int n=sizeof(TNOD);

char ch='c',s[2];

TNOD *p;

/* ne pozitionam pe penultimul nod */ while (ch!='x') { p=first;

while (p->next!=last) p=p->next;

free(p->next);

p->next=NULL;

last=p;

printf("Deleted. Exit? (x): ");

gets(s);ch=s[0];}}

// stergere inregistrare intermediara

void del_int() {

TNOD *p, *pa, *pu;

char ch='n', s[2];

disp_antet();

pa=first;p=pa->next;pu=p->next;

while ((p!=last)&&(ch!='y'))

{ disp_nod(p);

printf("Delete ? [y]: ");

gets(s);ch=s[0];

if (ch='y') {pa->next=pu;

free(p);}

else {pa=p;p=pu;pu=pu->next;}}}

Page 156: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

150

Capitolul VIII

FUNCŢII

8.1. Forma generală a unei funcţii

Principalul mijloc prin care se pot modulariza programele C

este oferit de conceptul de funcţie (unele funcţii standard au fost deja folosite pentru diverse operaţii). În C orice funcţie "întoarce" (returnează), după apel, o valoare al cărui tip trebuie cunoscut. În practică, însă, de multe ori valorile returnate de funcţii sunt ignorate. Standardul limbajului C permite chiar declararea explicită a funcţiilor care nu returnează valori ca fiind de tip void. În C o funcţie poate fi definită, declarată şi apelată. Definirea unei funcţii C se realizează după următorul format general:

tip nume_funcţie (lista_parametri)

declaraţii_parametri

{

declaraţii _locale

instrucţiuni

}

sau, o formă mai nouă adoptată de ANSI-C în 1989: tip nume_funcţie (declaraţii _parametri)

{

declaraţii _locale

instrucţiuni

}

Tipul unei funcţii corespunde tipului valorii pe care funcţia o va returna utilizând instrucţiunea return. Acesta poate fi un tip de bază (char, int, float, double etc.) sau un tip derivat (pointer, structură etc.). Dacă pentru o funcţie nu se specifică nici un tip, atunci, implicit se consideră că funcţia întoarce o valoare întreagă. Lista parametrilor, lista_parametri, este o listă de nume de variabile separate prin virgulă care vor primi valorile argumentelor în momentul apelării acesteia. Tipul acestor parametri este descris fie în paragraful declaraţii_parametri, fie direct în lista parametrilor. Lista parametrilor este închisă între paranteze. Chiar dacă o funcţie nu are parametri, parantezele nu trebuie să lipsească.

Page 157: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

151

De exemplu, funcţia max(a, b), care returnează cel mai mare dintre numerele întregi a şi b, se poate defini sub forma:

int max(a, b) sau int max(int a, int b) int a, b;

{ {

if (a > b) if (a > b)

return (a); return (a);

else else

return (b); return (b);

} }

În cazul în care tipul parametrilor formali ai unei funcţii nu se declară, atunci ei sunt consideraţi implicit de tip int. În cazul compilatoarelor moderne, programul următor va genera două avertismente. Exemplu: # include <stdio.h>

float max(); // Prototipul functiei max()

float x;

void main()

{ x = max(3, 4);

printf("max= %d",x);

}

float max(a, b)

float a, b;

{ if (a > b)

return (a);

else

return (b); }

În urma compilării va rezulta: Compiling...

test.c

C:\cpp_examples\test.c(11): warning C4244: 'return':

conversion from 'int' to 'float', possible loss of data

C:\cpp_examples\test.c(13): warning C4244: 'return':

conversion from 'int' to 'float', possible loss of data

test.obj - 0 error(s), 2 warning(s)

Aceste avertismente sunt generate deoarece, la primul pas al compilării, la parcurgerea liniei de declarare a funcţiei:

float max(a,b)

parametrii formali a şi b sunt consideraţi de tip întreg. Programul, modificat ca mai jos, va duce la o compilare fără

probleme: Exemplu:

# include <stdio.h>

Page 158: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

152

float max();

float x;

void main() { x = max(3.2, 4.1);

printf("max= %d\n",x);

}

float max(float a, float b)

{ if (a > b)

return (a);

else

return (b); }

Tot corect va rula şi programul:

# include <stdio.h>

int max();

int x;

void main()

{ x = max(-3,4);

printf("max= %d\n",x);

}

int max(a,b)

{ if (a > b)

return (a);

else

return (b); }

Se observă că nu mai este nevoie de declararea explicită a parametrilor formali de tip întreg a şi b.

8.2. Reîntoarcerea dintr-o funcţie

Mai intâi precizăm că instrucţiunea return are două utilizări

importante: ♦ return determină ieşirea imediată din funcţia în care se află

instrucţiunea şi reîntoarcerea în programul apelant; ♦ return poate fi folosită pentru a întoarce o valoare.

Reîntoarcerea dintr-o funcţie în programul apelant (funcţia

apelantă) se poate face în două moduri: a) După parcurgerea codului corespunzător funcţiei se revine în programul apelant la instrucţiunea imediat următoare. Exemplu: Aceasta funcţie tipăreşte un şir în ordine inversă: # include <string.h>

void afis_invers(char s[]);

void main() {

char s[10];

Page 159: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

153

printf("Introduceti un sir de caracrere de la

tastatura (max 10)\n");

scanf("%s",s); afis_invers(s); }

void afis_invers(char s[])

{ register int t;

for (t = strlen(s)-1; t >= 0; t--)

printf("%c", s[t]);

printf("\n"); }

b) Al doilea mod de întoarcere dintr-o funcţie se realizează utilizând funcţia return. Funcţia return poate fi folosită fără nici o valoare asociată. Exemplu: Funcţia următoare afişează rezultatele ridicării unui număr întreg la o putere întreagă pozitivă: power (baza, exp){

int baza, exp, i;

scanf("%d %d", &baza, &exp);

if (exp < 0) return; /* Functia se termina

daca exp e negativ */

i = 1;

for (; exp; exp--) i = baza * i;

printf (" Rezultatul este %d \n", i); }

Dacă exponentul exp este negativ, instrucţiunea return determină terminarea funcţiei înainte ca sistemul să întâlnească }, dar nu returnează nici o valoare. O funcţie poate conţine mai multe instrucţiuni return, care pot simplifica anumite algoritme.

8.3. Valori returnate

Toate funcţiile, cu excepţia celor daclarate a fi de tip void,

returnează o valoare. Această valoare este fie explicit specificată prin return, fie este zero dacă nu se utilizează instrucţiunea return. Dacă o funcţie este declară ta ca fiind de tip void, aceasta poate fi folosită în orice expresie C.

O funcţie nu poate fi membrul stâng într-o expresie de atribuire. De exemplu, instrucţiunea: swap(x,y) = 100; este greşită. Funcţiile care nu sunt de tip void se pot împărţi în trei categorii: 1) Funcţii "pure" sunt funcţiile care efectuează operaţii asupra argumentelor şi returnează o valoare de bază pe acea operaţie. Exemplu: sqrt() şi sin() returnează respectiv radăcina pătrată şi sinusul argumentului. 2) A doua categorie de funcţii sunt cele care manipulează informaţii şi întorc o valoare care arată reuşita sau eşecul acestei

Page 160: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

154

manipulări. Un exemplu este fwrite() folosită pentru a scrie informaţii pe disk. Dacă scrierea se face cu succes, fwrite() întoarce numărul de octeţi înscrişi (ceruţi să se înscrie); orice altă valoare indică apariţia unei erori. 3) A treia categorie de funcţii sunt cele care nu trebuie să întoarcă o valoare explicită. De exemplu, funcţia printf() întoarce numărul de caractere tipărite, număr care, de obicei, nu are o utilizare ulterioară.

Dacă pentru o funcţie care returnează o valoare nu se specifică o operaţie de atribuire, calculatorul va ignora valoarea returnată. Exemplu: Considerăm următorul program care utilizează funcţia mul(): # include <stdio.h>

mul();

void main (void){

int x, y, z;

x = 10; y = 20;

z = mul(x, y); //- primul apel al lui mul()

printf("%d\n", mul(x,y)); /- al doilea apel al lui mul()

mul(x,y); //- al treilea apel al lui mul() } mul(a,b) // Se defineste functia mul()

{ return a*b; } Linia a atribuie valoarea returnată de mul() lui z. În linia b,

valoarea returnată nu este atribuită, dar aceasta este utilizată de printf(). In linia c valoarea returnată este pierdută, deoarece nu se atribuie nici unei variabile ce va fi utilizată în altă parte a programului.

8.4. Domeniul unei funcţii

În C, fiecare funcţie este un bloc de instrucţiuni. Codul unei funcţii este propriu acelei funcţii şi nu poate fi accesat (utilizat) prin nici o instrucţiune din orice altă funcţie, cu excepţia instrucţiunii de apel al acelei funcţii. (De exemplu, nu putem utiliza goto pentru a realiza saltul dintr-o funcţie în mijlocul unei alte funcţii). Blocul de instrucţiuni care descrie corpul unei funcţii este separat de restul programului şi dacă acesta nu utilizează variabile globale sau date, el nici nu poate afecta, nici nu va fi afectat de alte părţi ale programului. Codul şi datele care sunt definite în interiorul unei funcţii nu pot interacţiona cu codul şi datele definite în altă funcţie, deoarece cele două funcţii au scopuri diferite.

Page 161: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

155

În cadrul unei funcţii se deosebesc trei tipuri de variabile, astfel: variabile locale, parametri formali şi variabile globale. Domeniul unei funcţii determină modul în care alte părţi ale programului pot avea acces la aceste trei tipuri de variabile stabilind şi durata de viaţă a acestora.

8.4.1. Variabile locale Variabilele declarate în interiorul unei funcţii se numesc variabile locale. Variabilele locale pot fi referite numai prin instrucţiuni interioare blocului în care au fost daclarate aceste variabile. Variabilele locale nu sunt cunoscute în afara blocului în care au fost daclarate, domeniul lor limitându-se numai la acest bloc. Mai exact, variabilele locale există numai pe durata execuţiei blocului de cod în care acestea au fost daclarate; deci o variabilă locală este creată la intrarea în blocul său şi distrusă la ieşire. De obicei, blocurile de program în care se declară variabilele locale sunt funcţiile. Implicit, o variabilă locală este auto, deci se stochează în memoria stivă. Ea poate fi declarată şi register, caz în care se stochează în regiştrii interni ai microprocesorului sau poate fi declarată static, caz în care se stochează în memoria de date sau statică, valoarea sa păstrându-se şi la ieşirea din funcţie. Exemplu: func1() {

int x;

x = 10; }

func2() {

int x;

x = -199; }

Aici variabila întreagă x este declarată de două ori, o dată în func1() şi o dată în func2(). x din func1() nu are nici o legatură cu x din func2(), deoarece fiecare x este cunoscut numai în blocul în interiorul căruia a fost declarat. Limbajul C conţine cuvântul cheie auto, care poate fi folosit pentru declararea de variabile locale. Cu toate acestea, întrucât C presupune că toate variabilele neglobale sunt prin definiţie (implicit) variabile locale, deci au atributul auto, acest cuvânt cheie nu se utilizează. De obicei, variabilele locale utilizate în interiorul unei funcţii se declară la începutul blocului de cod al acestei funcţii. Acest lucru nu este neapărat necesar, deoarece o variabilă locală poate fi declarată

Page 162: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

156

oriunde în interiorul blocului în care se utilizează, dar înainte de a fi folosită. Exemplu: Considerăm următoarea funcţie: func (){

char ch;

printf (" Continuam (y / n) ? : ");

ch = getche(); //Se preia optiunea de la tastatura

/* Daca raspunsul este yes */

if (ch == 'y') {

char s[80];

/* s se creeaza numai dupa intrarea in acest bloc */ printf (" Introduceti numerele: \n ");

gets (s);

prelucreaza_nr (s); /* Se prelucreaza numerele */

} }

Aici, func() creează variabila locală s la intrarea în blocul de cod a lui if şi o distruge la ieşirea din acesta. Mai mult, s este cunoscută numai în interiorul blocului if şi nu poate fi referită din altă parte, chiar din altă parte a funcţiei func() care o conţine. Deoarece calculatorul creează şi distruge variabilele locale la fiecare intrare şi ieşire din blocul în care acestea sunt daclarate, conţinutul lor este pierdut o dată ce calculatorul părăseste blocul. Astfel, variabilele locale nu pot reţine valorile lor după încheierea apelului funcţiei.

8.4.2. Parametri formali Dacă o funcţie va folosi argumente, atunci aceasta trebuie să declare variabilele care vor accepta (primi) valorile argumentelor. Aceste variabile se numesc parametri formali ai funcţiei. Parametrii formali ai funcţiei se comportă ca orice altă variabilă locală din interiorul funcţiei. Declararea parametrilor formali se face după numele funcţiei şi înaintea corpului propriu-zis al funcţiei. Exemplu: /* Funcţia următoare întoarce 1 dacă caracterul c

aparţine şirului s altfel întoarce 0 */

# include <stdio.h>

int func (char s[10],char c) {

while (*s)

if (*s == c) return 1;

else s++;

return 0; }

void main() {

char s[10], c;

scanf("%c %s", &c, &s);

Page 163: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

157

if (func(s, c))

printf("Caracterul se afla in sir\n");

else printf("Caracterul NU se afla in sir\n"); }

Funcţia func() are doi parametri: s şi c. Aceasta funcţie întoarce 1 dacă caracterul c aparţine şirului şi 0 dacă c nu aparţine şirului. Precizăm că argumentele cu care se va apela funcţia trebuie să aibă acelaşi tip cu parametrii formali declaraţi în funcţie. Aceşti parametri formali pot fi utilizaţi ca orice altă variabilă locală.

Un al doilea mod (recomandat de ANSI-C în 1989) de a declara parametrii unei funcţii constă în declararea completă a fiecărui parametru în interiorul parantezelor asociate funcţiei. De exemplu, declararea parametrilor funcţiei func() de mai sus se poate face şi sub forma: func (char *s, char c)

{ . . . . . . . . . . .

}

8.4.3. Variabile globale Spre deosebire de variabilele locale, variabilele globale sunt cunoscute întregului modul program şi pot fi utilizate de orice parte a programului. De asemenea, variabilele globale vor păstra valorile lor pe durata execuţiei complete a programului, deci se stochează în memoria statică. Variabilele globale se creează prin declararea lor în afara oricărei funcţii (inclusiv main()). Variabilele globale pot fi plasate în orice parte a programului, evident în afara oricărei funcţii, şi înainte de prima lor utilizare. De obicei, variabilele globale se plasează la începutul unui program, mai exact înaintea funcţiei main(). Exemplu: int count; /* count este global */

void main (void) {

count = 100;

func1(); }

func1() /* Se defineste functia func1() */

{ int temp;

/* temp preia variabila globala count */

temp = count;

func2();

printf ("count is %d",count); // Se va afisa 100

}

func2() /* se defineste functia func2() */

{ int count; /* count este local */

Page 164: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

158

for (count = 1; count < 10; count ++)

printf ("%2d\n" , count); }

Se observă că, deşi nici funcţia main() şi nici funcţia func1() nu au declarat variabila count, ambele o folosesc. Funcţia func2() a declarat o variabilă locală count. Când se referă la count, func2() se va referi numai la variabila locală count şi nu la variabila globală count declarată la începutul programului. Reamintim că, dacă o variabilă globală şi o variabilă locală au acelaşi nume, toate referirile la numele variabilei în interiorul funcţiei în care este declarată variabila locală se vor efectua numai asupra variabilei locale şi nu vor avea nici un efect asupra variabilei globale. Deci, o variabilă locală ascunde o variabilă globală. Variabilele globale sunt memorate într-o zonă fixă de memorie destinată special acestui scop (memoria statică), rămânând în această zonă pe parcursul întregii execuţii a programului. Variabilele declarate explicit extern sunt tot variabile globale, dar accesibile nu numai modulului program în care au fost declarate, ci şi tuturor modulelor în care au fost declarate de tip extern. Un alt exemplu util şi pentru înţelegerea lucrului cu pointeri este următorul: se declară o variabilă globală x, şi apoi două variabile locale cu acelaşi nume, x, care se vor “ascunde“ una pe cealaltă. Pentru a vedea şi modul în care se stochează în memorie aceste variabile, vom afişa şi locaţiile de memorie pentru fiecare tip de variabilă x, precum şi pentru pointerul p corespunzător. Reţinem că, în general, dacă:

p = &x => p = &x = &(*p) = (&*)p = p

*p = *(&x) = (*&)x = x

Faptul că * respectiv & sunt operaţiuni complementare (inverse una celeilalte) se observă din relaţiile de mai sus, din care deducem că:

&* = *& = identitate

&(*z)= z; // z este pointer

*(&z) = z; // z este o variabila de un anume tip

(z este de alt tip în fiecare din egalităţile de mai sus, pointer sau variabilă).

Page 165: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

159

Adresa Memoria

.. .. .. .. .. .. .. .. .

x

.. .. .. .. .. .. .. ..

.. .. .. .. .. .. .. ..

p=&x

.. .. .. .. .. .. .. ..

q=&p

&x=adresa x

&p=adresa p

&q=adresa q

# include <stdio.h>

int x = 34; /* x este global */

void main(void) {

int *p = &x, *r;

/* p este o variabila pointer catre un intreg */

void **q;

printf("x=%d &x=%p *p=%d p=%p &p=%p\n",x,&x, *p, p, &p);

{ int x;

x = 1;

/* Acest prim x este o variabila locala ce o ascunde

pe cea globala */

p = &x;

printf("x=%d &x=%p *p=%d p=%p &p=%p\n",x,&x, *p, p, &p);}

{ int x; /* Acest al doilea x ascunde prima

variabila locala x */

x = 2; // Se atribuie valoarea 2 acestui x

p = &x; /* Pointerul p retine adresa variabilei x */ printf("x=%d &x=%p *p=%d p=%p &p=%p\n",x, &x, *p, p, &p);

q = &p; // q retine adresa pointerului p

r = *q; // r retine valoarea de la adresa q

/*Cum q = &p => r = *(&p) = p => *r = *p = x */

printf("q=%p *q=%p **q=%d &q=%p\n", q, *q, *r, &q); } }

În urma execuţiei programului, obţinem următorul rezultat: x=34 &x=00426A54 *p=34 p=00426A54 &p=0065FDF4

x=1 &x=0065FDE8 *p=1 p=0065FDE8 &p=0065FDF4

x=2 &x=0065FDE4 *p=2 p=0065FDE4 &p=0065FDF4

q=0065FDF4 *q=0065FDE4 **q=2 &q=0065FDEC

Prin declaraţia int *p = &x; variabila p este declarată variabilă pointer către o dată de tip întreg şi este iniţializată cu adresa variabilei spre punctează, anume x. Pointerul q este declarat ca un pointer la un alt pointer.

Page 166: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

160

Caracteristici ale variabilei

Caracteristici ale variabilei pointer ataşate

Denum. Variab.

Tipul variabilei

Adresa &x Valoare x

Adresa &p Valoarea p *p

int x globală 00426A54 34 0065FDF4 00426A54 34

int x locală 0065FDF0 1 0065FDF4 0065FDF0 1

int x locală 0065FDEC 2 0065FDF4 0065FDEC 2

p pointer local

p = &x *p = x

q pointer local

q = &p *q = p &q= 0065FDEC

q= 0065FDF4

**q= 2

Acelaşi lucru se face pentru celelalte două variabile locale. Din

interpretarea rezultatelor de mai sus putem trage următoarele concluzii. Spre exemplu, printf(“%d”,x); este echivalentă cu printf(“%d”,*p); scanf(“%d”,&x); este echivalentă cu scanf(“%d”,p);

dacă în prealabil s-a făcut atribuirea p = &x; Se mai observă cum pointerul p, care iniţial indica spre

variabila globală x, este încărcat cu adresa de memorie 4336176 (00426A54 H), pe când în cazurile când indica variabilele locale x se alocau adresele de memorie 6684140 (0065FDF0 H) şi 6684144 (0065FDEC H), adrese adiacente, la un interval de 4 octeţi, atât cât sunt alocaţi pentru variabila de tip întreg. Se observă că variabila globală se află într-o altă zonă de memorie decât variabilele locale.

Modul de lucru cu pointerii este scos în evidenţă prin instrucţiunile: q=&p; // q retine adresa pointerului p r=*q; // r retine valoarea de la adresa q

// q=&p => r = *(&p) = p => *r = *p = x

printf("q=%p *q=%p **q=%d &q=%p\n",q,*q,*r,&q);

prin care se iniţializează pointerul q cu adresa pointerului p, apoi pointerul r va primi valoarea *q, adică valoarea p.

Una din principalele caracteristici a limbajelor structurate o constituie compartimentarea codului şi a datelor. În C, compartimentarea se realizează folosind variabile şi funcţii. De exemplu, pentru scrierea unei funcţii mul() care determină produsul a doi întregi se pot utiliza două metode, una generală şi una specifică, astfel:

Page 167: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

161

General : Specific : mul (x, y) int x, y;

int x, y mul ()

{ return (x * y);} { return (x * y);}

Când se doreşte realizarea produsului a oricăror doi întregi x şi y se utilizează varianta generală a funcţiei, iar când se doreşte produsul numai al variabilelor globale x şi y se utilizează varianta specifică. Exemplu: # include <stdio.h>

# include <string.h>

int count; // count este global intregului program

play(); // Prototipul pentru functia play()

void main(void) {

char sir[80];

// sir este variabila locala a functiei main()

printf("Introduceti un sir : \n");

gets(sir);

play(sir);

}

play(char *p) // Se declara functia play()

{ // p este local functiei play()

if (!strcmp(p, "add")) {

int a,b; /* a si b sunt locale blocului if din

interiorul functiei play()*/

scanf ("%d %d", &a, &b);

printf ("%d \n", a+b); }

// int a, b nu sunt cunoscute sau evidente aici

else if(!strcmp(p,"beep")) printf("%c",7); } 8.5. Apelul funcţiilor

Apelul unei funcţii înseamnă referirea funcţiei, împreună cu valorile actuale ale parametrilor formali, precum şi preluarea valorii returnate, dacă este necesar. La apelul funcţiei, tipul argumentelor trebuie să fie acelaşi cu cel al tipului parametrilor formali ai funcţiei. Dacă apar nepotriviri de tip (de exemplu, parametrul formal al funcţiei este de tip int, iar apelul funcţiei foloseşte un argument de tip float) de obicei, compilatorul C nu semnalizează eroare, dar rezultatul poate fi incorect. În C transmiterea argumentelor de la funcţia apelantă spre funcţia apelată se face prin valori sau prin adrese.

Page 168: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

162

a) În cazul transmiterii argumentului prin valoare, se realizează copierea (atribuirea) valorilor fiecărui argument în (la) câte un parametru formal al funcţiei apelate. Exemplu: Se apelează o funcţie ce calculeaza pătratul unui număr întreg. # include <stdio.h>

square(); // Prototipul functiei sqrt()

void main(void) {

int t = 10; printf("%d %d\n", t, square(t)); }

square(x) // Se declara functia sqrt()

int x;

{ x = x*x; return(x); }

Se observă că prin această metodă, schimbările survenite asupra parametrului formal x nu afectează variabila utilizată pentru apelul funcţiei (schimbările lui x nu modifică în nici un fel pe t). b) Dacă transmiterea argumentului se realizează prin adrese, atunci la apelul funcţiei în loc de valori se folosesc adrese, iar în definiţie, parametrii formali se declară ca pointeri. Exemplu: O funcţie swap() care schimbă valorile a două variabile reale se poate defini astfel:

void swap(float *x, float *y){

float temp;

temp = x; /* temp preia valoarea de la adresa x */

*x = *y; /* valoarea de la adresa y este copiata

la adresa x */

y = temp; /* la adresa y se copiaza valoarea

lui temp */

}

Se observă că parametrii formali ai funcţiei swap() sunt pointeri la float. Programul următor arată modul de apel al acestei funcţii. # include <stdio.h>

void swap(float *x,float *y);

void main(void) {

float x, y; // x si y sunt de tip float

scanf("%f,%f",&x,&y);/*Se introduc de la tastatura

doua numere reale separate prin virgula*/ printf ("x = %f, y = %f \n ",x,y);

swap(&x,&y); /*Se apeleaza functia swap() avand ca

argumente adresele lui x si y */

printf("x = %f, y = %f \n ",x,y);

}

Prin &x şi &y, programul transferă adresele lui x şi y funcţiei swap() şi nu valorile lui x şi y.

Page 169: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

163

Un apel combinat, valoare-referinţă este prezentat în exemplul următor: # include <stdio.h>

void f();

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

printf("x = %d, y = %d \n", x, y);

f(x,&y);}

void f(int val, int *ref) {

val++;

(*ref)++;

printf("x = %d, y = %d \n",val,*ref); }

8.6. Apelul funcţiilor având ca argumente tablouri

Când se apelează o funcţie având ca argument un tablou, acesteia i se va transmite un pointer la primul element al tabloului. Reamintim că în C numele unui tablou fără nici un indice este un pointer la primul element al tabloului. Deci, un argument de tipul T[ ] (vector de tipul T) va fi convertit la T * (pointer de tipul T). Rezultă că vectorii, ca şi tablourile multidimensionale, nu pot fi transmise prin valoare. Aceasta înseamnă că declararea parametrului formal trebuie să fie compatibilă tipului pointer. Există trei moduri de a declara un parametru care va primi un pointer la un tablou (vector). a) Parametrul formal poate fi declarat ca un tablou, astfel: # include <stdio.h>

display(); // Prototipul functiei display()

void main(void) {

int v[10], i;

for (i = 0; i < 10; ++i) v[i] = i;

display(v);}

display(num) // Se defineste functia display()

int num[10];

{ int i;

for (i = 0; i < 10; i++) printf ("%d", num[i]); }

Chiar dacă acest program declară parametrul num ca pe un vector de 10 întregi, compilatorul C va converti automat pe num la un pointer la întreg, deoarece parametrul nu poate primi întregul tablou (vector). b) O a doua cale de a declara un parametru vector (tablou), constă în a specifica parametrul ca pe un vector fără dimensiune: display(int num[])

{ int i;

for(i = 0; i < 10; i++) printf ("%d",num[i]); }

Page 170: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

164

Aceasta funcţie declară pe num ca fiind un vector de întregi cu dimensiune necunoscută. Deoarece limbajul C nu verifică dimensiunea vectorilor, dimensiunea actuală a vectorului este irelevantă ca parametru al funcţiei. §i de aceasta dată, num va fi convertit la un pointer la întreg. c) Ultima metodă prin care se poate declara un parametru tablou este ca pointer, astfel: display(int *num)

{ int i;

for (i = 0; i < 10; i++) printf ("%d", num[i]); }

Limbajul C permite acest tip de declaraţie deoarece putem indexa orice pointer utilizând []. Toate cele trei metode de declarare a unui tablou ca parametru produc acelaşi rezultat: un pointer. Cu toate acestea, un element al unui tablou folosit ca argument al unei funcţii va fi tratat ca orice altă variabilă. Astfel, programul de mai sus poate fi rescris sub forma: # include <stdio.h>

void main (void) {

int v[10], i;

for (i = 0; i < 10; i++) v[i] = i;

for (i = 0; i < 10; i++) display (v[i]); }

display(int num) { printf ("%d" , num); }

De data aceasta, parametrul din display() este de tip int, deoarece programul utilizează numai valoarea elementului tabloului. Exemplu: Vom prezenta un program pentru afişarea tuturor numerelor prime cuprinse între două limite întregi. Programul principal apelează două funcţii: nr_prim() returnează 1 dacă argumentul său întreg este prim şi 0 dacă nu este prim; numerele prime sunt grupate într-un vector, care se afişează ulterior cu funcţia display(). # include <stdio.h>

int nr_prim(); // Se declara prototipul

void display();

void main (void) {

int a,b,i,j,v[80];

printf("Introduceti limitele: "); scanf("%d %d", &a, &b);

j = 0;

for (i=a; i<=b; ++i)

if (nr_prim(i)) {v[j]=i; ++j;}

display(v,j);}

int nr_prim(int i) // Decide daca i este prim

{ int j;

Page 171: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

165

for (j=2; j<=i/2; j++)

if (i%j==0) return 0;

return 1; } void display(int *p, int j) /* Tipareste un vector

de intregi */

{ int i;

for (i=0; i<j; ++i) printf("%d ", p[i]); }

Din cele de mai sus, trebuie reţinut că atunci când un tablou se utilizează ca argument al unei funcţii, calculatorul transmite funcţiei adresa de început a tabloului. Acest lucru constituie o excepţie a limbajului C în convenţia de transmitere a parametrilor prin valoare. Astfel, codul funcţiei poate acţiona asupra conţinutului tabloului şi îl poate altera. Exemplu: Programul următor va modifica conţinutul vectorului sir din funcţia main() după apelul funcţiei afis_litmari(). # include <stdio.h>

# include <ctype.h> afis_litmari();

void main (void) {

char sir[80];

gets(sir);

afis_litmari(sir);

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

// Se defineste functia afis_litmari()

afis_litmari(char *s)

{ register int t;

for (t = 0; s[t]; ++t) {

// Se modifica continutul sirului sir

s[t] = toupper(s[t]);

printf("%c",s[t]);}}

Rezultatul rulării programului va fi: abcdefghijklmnoprstuvxyzw

ABCDEFGHIJKLMNOPRSTUVXYZW

ABCDEFGHIJKLMNOPRSTUVXYZW

Exemplu: Dacă nu dorim să se întâmple acest lucru, programul de mai sus se poate rescrie sub forma: # include <stdio.h>

# include <ctype.h>

afis_litmari();

void main (void) {

char sir[80];

gets(sir);

afis_litmari(sir);

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

afis_litmari(char *s)

Page 172: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

166

/* Se defineste functia afis_litmari() */

{ register int t;

for (t = 0; s[t]; ++t) printf("%c",toupper(s[t])); } //Nu se modifica continutul sirului sir

Rezultatul rulării va fi de această dată: abcbdefghijklmnoprstuvxyzw

ABCBDEFGHIJKLMNOPRSTUVXYZW

abcbdefghijklmnoprstuvxyzw

În aceasta variantă conţinutul tabloului ramâne nemodificat, deoarece programul nu-i schimbă valoarea.

Un exemplu clasic de transmitere a tablourilor într-o funcţie îl constituie funcţia gets() din biblioteca C standard. Prezentăm o variantă simplificată a acestei funcţii numită xgets(). xgets(s)

char *s; {

char ch;

int t;

for (t = 0; t < 80; ++t) {

ch = getchar();

switch (ch) {

case '\n' :

s[t] = '\0'; /* terminare sir */

return;

case '\b':

if (t > 0) t--;

break;

default:

s[t] = ch; } }

s[80] ='\0'; }

Funcţia xgets() trebuie apelată având ca argument un tablou de caractere, care, prin definiţie, este un pointer la caracter. Numărul caracterelor introduse de la tastatură, prin funcţia for este de 80. Dacă se introduc mai mult de 80 de caractere, funcţia se încheie cu return. Dacă se introduce un spaţiu, contorul t este redus cu 1. Când se apasă CR, xgets() introduce terminatorul de şir. 8.7. Argumentele argc şi argv ale funcţiei main()

Singurele argumente pe care le poate avea funcţia main() sunt

argv şi argc. Parametrul argc conţine numărul argumentelor din linia de

comandă şi este un întreg. Întotdeauna acesta va fi cel puţin 1, deoarece numele programului este codificat ca primul argument.

Page 173: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

167

Parametrul argv este un pointer la un tablou de pointeri la caractere. Fiecare element din acest tablou indică spre un argument linie_comanda. Toate argumentele linie_comanda sunt şiruri. Exemplu: Următorul program arată modul de utilizare al argumentelor linie_comanda şi va afişa Hello urmat de numele dumneavoastră, dacă vă introduceţi numele, imediat după numele programului: # include <stdio.h>

void main (argc, argv) // Numele programului

int argc;

char *argv[];

{if (argc != 2) {

printf (" Ati uitat sa va introduceti numele \n");

return; }

printf ("Hello %s !", argv[1]); }

Dacă acest program se numeşte ARG_LC.C şi numele dumneavoastră este DAN, atunci, pentru a executa programul, în linia de comandă, veţi tipări ARG_LC DAN. Ieşirea programului va fi Hello DAN !.

Argumentele linie_comanda trebuie separate prin spaţiu sau TAB şi nu prin virgulă, sau;.

Parametrul argv[] se declară, de obicei, sub forma char *argv[]; şi reprezintă un tablou de lungime nedeterminată, mai precis reprezintă un tablou de pointeri. Accesul la elementele lui argv[] se realizează prin indexarea acestuia, astfel: argv[0] va indica spre primul şir, care este întotdeauna numele programului; argv[1] va indica spre primul argument etc. Evitaţi folosirea sa fără paranteze, adică char *argv. Următorul program numit "nrinvers" numără invers de la o valoare specificată prin linia de comandă şi transmite un beep când ajunge la zero. Precizăm că programul converteşte primul argument, care conţine numărul la un întreg folosind funcţia standard atoi(). Dacă şirul "display" apare ca al doilea argument_comanda, programul va afişa, de asemenea, numărul introdus pe ecran. # include <stdio.h>

# include <string.h>

# include <stdlib.h>

void main(int argc, char *argv[]) /* nrinvers */ { int disp, count;

if (argc < 2) {

printf ("Trebuie introdusa lungimea numarului

in linia de comanda\n");

Page 174: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

168

return; }

if (argc==3 && !strcmp(argv[2], "display"))

disp = 1; else disp = 0;

for (count = atoi(argv[1]); count; --count)

if (disp) printf("%d ",count);

printf("%c",7); /* Se emite un beep */ }

Observaţie: Dacă în linia de comandă nu se specifică nici un argument, programul va afişa un mesaj de eroare. 8.8. Funcţii care returnează valori neîntregi

Dacă nu se declară explicit tipul funcţiei, compilatorul C o va

declara implicit de tip int. Pentru ca funcţia să întoarcă un tip diferit de int trebuie, pe de o parte, să se precizeze un specificator de tip al funcţiei şi apoi să se identifice tipul funcţiei înaintea apelului acesteia. O funcţie C poate returna orice tip de dată din C. Declararea tipului este similară celei de la declararea tipului variabilei: specificatorul de tip ce precede funcţia indică tipul datei întoarse de funcţie. Pentru a nu se genera incertitudini datorate dimensiunii de reprezentare, înainte de utilizarea unei funcţii ce întoarce tipuri neîntregi, tipul acestei funcţii trebuie făcut cunoscut programului. Acest lucru este necesar deoarece compilatorul nu cunoaşte tipul datei întoarse de funcţie şi acesta va genera un cod greşit pentru apelul funcţiei. Pentru a preveni această greşeală, la începutul programului se plasează o formă specială de declaraţie care să precizeze compilatorului ce tip de valoare va returna acea funcţie. Această declaraţie se numeşte prototipul funcţiei. Exemplu: # include <stdio.h>

float sum();//Prototipul functiei (fara parametri)

void main(void) {

float first = 123.23, second = 99.09;

printf("%f\n", sum(first, second)); }

float sum(float a, float b) // Definitie sum()

//Se returnează o valoare de tip float

{ return a+b; }

Instructiunea de declarare a tipului funcţiei are forma generală: specificator_de_tip nume_funcţie();

Chiar dacă funcţia are argumente, în declaraţia de tip acestea nu se precizează (cu excepţia compilatoarelor mai vechi de 1989, care nu sunt adaptate la cerinţele ANSI-C).

Page 175: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

169

Dacă o funcţie ce a fost declarată int întoarce un caracter, calculatorul converteşte valoarea caracter într-un întreg. Deoarece conversiile caracter --> întreg-caracter sunt fără probleme, o funcţie ce întoarce un caracter poate fi definită ca o funcţie care întoarce un întreg.

8.9. Returnarea pointerilor

Deşi funcţiile care întorc pointeri se manipulează în acelaşi mod

ca şi celelalte tipuri de funcţii, trebuie discutate câteva concepte importante. Pointerii la variabile nu sunt nici întregi, nici întregi fără semn. Pointerii sunt adrese de memorie a anumitor tipuri de date: int, char, float, double, struct etc. Motivul acestei distincţii este legat de faptul că atunci când se prelucrează un pointer aritmetic, această prelucrare este dependentă de tipul datei indirectate: de exemplu, dacă este increment un pointer la int, noua valoare (a adresei) va fi cu 4 mai mare faţă de valoarea anterioară. În general, când un pointer este incrementat sau decrementat, acesta va indica către elementul următor, respectiv anterior, din tabloul pe care îl indirectează. De exemplu, dacă funcţia int f() returnează un întreg, atunci funcţia int *f(), returnează un pointer la o dată de tip int. Deoarece fiecare tip de date poate avea lungimi diferite, compilatorul trebuie "să ştie" ce tip de dată este indirectată de pointer, pentru a-l face să indice corect spre următorul element. Exemplu: Programul următor conţine o funcţie care întoarce un pointer într-un şir în locul în care calculatorul găseşte o coincidenţă de caractere. char *match (char c, char *s)

{int count;

count = 0;

while (c!=s[count] && s[count] != '\0') count ++;

return (&s[count]); }

Funcţia match() va încerca să întoarcă un pointer la locul (elementul) din şir unde calculatorul găseşte prima coincidenţă cu caracterul c. Dacă nu se găseste nici o coincidenţă, funcţia va întoarce un pointer la terminatorul de şir (NULL). Un scurt program ce ar utiliza funcţia match() este următorul : # include <stdio.h>

Page 176: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

170

# include <conio.h>

char *match(); // Prototipul functiei

void main (void) { char s[80], *p, ch;

gets (s); /* Se introduce un sir */

ch = getche(); /* Se introduce un caracter */

p = match (ch, s); /* Apelul functiei */

/* p preia valoarea functiei match() */

if (p) {

printf("\n Adresa caracterului ce coincide cu

cel dat este: %p", p);

printf("\n Subsirul de la adresa caracterului

ce coincide cu cel dat este:\n %s\n",p);}

else

printf("Nu exista nici o coincidenta"); }

Acest program citeşte mai întâi un şir şi apoi un caracter. În cazul în care caracterul este în şir, atunci se tipăreste şirul din punctul unde se află caracterul, altfel se tipăreşte "Nu există nici o coincidenţă". Un caz interesant este oferit de funcţiile care returnează pointeri către şiruri de caractere. O astfel de funcţie se declară sub forma:

char *f() Exemplu: Programul următor arată modul în care se defineşte, se declară şi se apelează o astfel de funcţie. # include <stdio.h>

void main(void) {

int i;

char *NumeLuna();

scanf("%d", &i);

printf("%s \n ", NumeLuna(i)); }

char *NumeLuna(nr) int nr;

{ char *luna[]=

{"Eroare", "Ianuarie", "Februarie", "Martie",

"Aprilie", "Mai", "Iunie","Iulie", "August",

"Septembrie", "Octombrie", "Noiembrie",

"Decembrie"};

return ((nr>=1) && (nr <= 12)?luna[nr]:luna[0]); } Un alt exemplu va fi reprezentat de o variantă a funcţiei strcpy()

din string.h , deci o funcţie care copiază caracterele din şirul s2 în şirul s1. Rezultatul se găseşte în s1. /* Vom incepe cu definirea functiei strcpy2() si apoi vom declara programul principal main(). In acest fel nu mai este necesara declararea prototipului functiei strcpy2() */

Page 177: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

171

# include <stdio.h>

char *strcpy2(register char s1[],register char s2[])

{ char *s0 = s1; // Echivalent: char *s0;s0 = s1; while ((*s1++ = *s2++) != '\0');

return s0; }

void main()

{ char *sir1,*sir2;

puts(“Introduceti un sir de la tatstatura \n”);

gets(sir2);

puts(strcpy2(sir1,sir2));}

Se observă cum se iniţializează s0 cu s1. Bucla while atribuie valorile (caracterele) (*s2) în locaţiile indicate de pointerul s1, incrementând ambii pointeri simultan. Bucla se termină la întâlnirea caracterului null, care se copiază şi el. Valoarea returnată, s0, reţine adresa de început a şirului s1.

Un ultim exemplu îl constituie un program de manipulare a unor matrici. Acest program realizează citirea unei matrici, transpunerea sa şi respectiv afişarea rezultatului apelând la funcţiile cit_mat(), trans_mat() şi tip_mat(). # include <stdio.h>

# define DIM_MAX 10

void cit_mat();

void tip_mat();

int *trans_mat();

void main()

{ int a[DIM_MAX][DIM_MAX], dim_lin, dim_col, *p;

printf("Introduceti dimensiunea matricei [dim_lin

dim_col]: ");

scanf("%d %d", &dim_lin, &dim_col);

cit_mat(a, dim_lin, dim_col); tip_mat(a, dim_lin, dim_col);

p = trans_mat(a, dim_lin, dim_col);

tip_mat(a, dim_col, dim_lin); }

void cit_mat(int p[][DIM_MAX], int lin, int col)

{ int i, j;

for (i=0; i<lin; i++)

for (j=0; j<col; j++)

{ printf("x[%d][%d] = ", i, j);

scanf("%d", &p[i][j]); } }

void tip_mat(int p[][DIM_MAX], int lin, int col)

{ int i, j;

for (i=0; i<lin; i++)

{ for (j=0; j<col; j++)

printf("%d ",p[i][j]);

Page 178: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

172

printf("\n"); }

printf("\n"); }

int *trans_mat(int p[][DIM_MAX], int lin, int col)

{ int t, i, j;

for (i=0; i<lin; i++)

for (j=i; j<col; j++)

{t = p[i][j], p[i][j] = p[j][i], p[j][i] = t;}

return p; } 8.10. Funcţii de tip void

Din punct de vedere sintactic, tipul void se comportă ca un tip fundamental (de bază). Nu există obiecte de tip void. Tipul void este utilizat pentru declararea implicită a acelor funcţii care nu întorc o valoare. void se utilizează şi ca tip de bază pentru pointeri la un obiect de tip necunoscut. Exemplu: void f(void) /* functia f nu intoarce o valoare */

void *pv /* pointer la un obiect necunoscut */

Utilizând void se impiedică folosirea funcţiilor ce nu întorc o valoare în orice expresie, prevenind astfel o întrebuinţare greşită a acestora. De exemplu, funcţia afis_vertical() afişează pe ecran argumentul său şir, vertical, şi întrucât nu întoarce nici o valoare, este declarată de tip void. void afis_vertical (sir)

char *sir;

{ while (*sir)

printf ("%c \n", *sir ++); }

Înaintea utilizării acestei funcţii sau oricărei alte funcţii de tip void, aceasta trebuie declarată. Dacă nu se declară, compilatorul C consideră că aceasta întoarce o valoare întreagă. Astfel, modul de utilizare al funcţiei afis_vertical() este următorul: # include <stdio.h>

void afis_vertical(); // Se declara prototipul

void main (void) {

afis_vertical ("Hello "); }

void afis_vertical (sir)

char *sir;

{ while (*sir)

printf ("%c \n", *sir ++); }

Page 179: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

173

8.11. Funcţii prototip După cum se ştie, înaintea folosirii unei funcţii care întoarce o altă valoare decât int, aceasta trebuie definită. Funcţiile prototip au fost adăugate de comitetul ANSI-C standard. Declararea unei funcţii prototip se face conform următorului format: tip nume_funcţie (tip_arg1, tip_arg2,...) unde: tip = tipul valorii întoarse de funcţie; tip_arg1, tip_arg2,... = tipurile argumentelor funcţiei. Exemplu: Programul următor va determina compilatorul să emită un mesaj de eroare sau de avertisment deoarece acesta încearcă să apeleze funcţia func() având al doilea argument de tip int, în loc de float, cum a fost declarat în funcţia func(): #include <stdio.h>

void func(int, float);//Prototipul functiei func()

void main (void) {

int x, y;

x = 10; y = 10;

func (x, y); } /* Se afiseaza o nepotrivire */

void func (x, y) /* Parametrii functiei sunt: */

int x; /* x - intreg */

float y; /* y - real */

{ printf ("%f", y/(float) x); } Funcţiile prototip se folosesc pentru a ajuta compilatorul în prima fază în care funcţiile utilizate sunt definite după programul principal. Acesta trebuie înştiinţat asupra tipul datei returnat de o funcţie pentru a aloca corect memoria. Dacă funcţiile sunt declarate înaintea liniei de program main(), funcţiile prototip nu mai sunt necesare, deoarece compilatorul extrage informaţia despre funcţii în momentul în care parcurge corpul definiţiei lor.

Spre exemplu, programul de mai sus se poate scrie şi sub forma următoare, în care nu vom mai avea o declaraţie de funcţie prototip: #include <stdio.h>

void func (x, y) /* Parametrii functiei sunt: */

int x; /* x - intreg */

float y; /* y - real */

{ printf ("%f", y/(float) x); }

void main (void) {

int x, y;

x = 10; y = 10;

func (x, y); } /* Nu se afiseaza nepotrivire */

Page 180: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

174

Utilizând recomandările ANSI-C din 1989, programul de mai sus se poate scrie mai compact: #include <stdio.h>

void func (int x, float y) /* Parametrii formali

includ tipul */

{ printf ("%f", y/(float) x); }

void main (void) {

int x, y;

x = 10; y = 10;

func (x, y); }//afisare avertisment de conversie

sau, folosind funcţia prototip: #include <stdio.h>

void func(); /* Declarare prototip fara

parametri formali ! */

void main (void) {

int x, y;

x = 10; y = 10;

func (x, y); }

void func (int x, float y) /* Parametrii formali

includ tipul */

{ printf ("%f", y/(float) x); }

În ultimul program am evidenţiat o recomandare care simplifică efortul de programare în sensul că în linia de declarare a prototipurilor funcţiilor folosite este necesar să definim tipul funcţiei nu şi tipul parametrilor formali. Compilatorul se informează despre tipul parametrilor formali la parcurgerea corpului definiţiei funcţiei.

Din cele de mai sus se observă ca folosirea funcţiilor prototip ne ajută la verificarea corectitudinii programelor, deoarece nu este permisă apelarea unei funcţii cu alte tipuri de argumente, decât tipul celor declarate. 8.12. Funcţii recursive

Funcţiile C pot fi recursive, adică se pot autoapela direct sau indirect. O funcţie este recursivă dacă o instrucţiune din corpul funcţiei este o instrucţiune de apel al aceleiaşi funcţii. Uneori o funcţie recursivă se numeşte şi funcţie circulară. Un exemplu de o astfel de funcţie este funcţia factorial() care determină factorialul unui număr. Această funcţie se poate organiza recursiv, ştiind că: n! = n(n-1)!. Având în vedere 0!=1, această funcţie se poate organiza astfel: long factorial (int n) {

if (n == 0) return (1);

Page 181: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

175

else

return (n * factorial(n-1)); }

Programul de apel al acestei funcţii se scrie sub forma: # include <stdio.h>

void main (void) {

int n;

printf("Introduceti un numar intreg : \n");

scanf ("%d, &n);

printf ("(%d) ! = %ld",n,factorial(n)); }

long factorial (int n) {

if (n == 0) return (1);

else

return (n * factorial(n-1)); } Observaţie: Atunci când o funcţie se autoapelează recursiv, la fiecare apel al funcţiei se memorează pe stivă atât valorile parametrilor actuali, cât şi întregul set de variabile dinamice definite în cadrul funcţiei. Din aceasta cauză stiva trebuie dimensionată corespunzător. O variantă echivalentă a funcţiei factorial() definită mai sus ar fi următoarea: long factorial(int n) {

if (!n) return (1);

else

return (n * factorial (n-1)); } Un alt exemplu interesant este dat de şirul lui Fibonacci, în care termenul general an este dat de relaţia de recurenţă: an = an-1+ an-2 ,

unde a0 = 0 şi a1=1. Codul funcţiei poate fi scris sub forma: long fib(int n) {

if (n == 0)

return (0);

else if (n == 1)

return (1);

else return (fib(n-1)+fib(n-2)); } Utilizarea recursivităţii poate să nu conducă la o reducere a memoriei necesare, atât timp cât stiva este folosită intens pentru fiecare apel recursiv. De asemenea şi execuţia programului poate să nu fie mai rapidă. Dar codul recursiv este mai compact şi de multe ori mai uşor de scris şi înţeles decât echivalentul său recursiv. Recursivitatea este convenabilă în mod deosebit pentru operaţii pe structuri de date definite recursiv, cum sunt listele, arborii etc.

Page 182: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

176

8.13. Clase de memorare (specificatori sau atribute)

Din punct de vedere al execuţiei programelor C, memoria

computerului este organizată în trei zone, cunoscute în mod tradiţional ca segment de memorie text, segment de memorie statică (sau de date) şi segment de memorie dinamică (sau stivă). Segment de memorie text (memorie program)

Conţine instrucţiunile programului, deci programul executabil

Segment de memorie statică

Conţine variabilele a caror locaţie rămâne fixă

Segment de memorie dinamică (de tip stivă)

Conţine variabilele de tip automatic, parametrii funcţiilor şi apelurile şi retururile de/din funcţii

În tabelul următor se prezintă caracteristicile claselor de memorie. Specificator de memorie

Domeniul de vizibilitate al variabilei

Durata de viaţă a variabilei

Plasament

Auto (automatic)

Local fiecărei funcţii sau fiecărui bloc în care a fost declarată

Temporară, numai când se execută funcţia în care este declarată

În memoria dinamică (de tip stivă)

Register (registru)

Local fiecărei funcţii Temporară, numai când se execută funcţia în care este declarată

În regiştrii microprocesorului

Extern Global, de către toate funcţiile dintr-un fişier sursă sau din mai multe fişiere sursă

Permanentă, pe parcursul rulării programului executabil

În memoria statică

Static Local sau global Permanentă, cât timp este in memorie programul executabil

In memoria statică

Vizibilitatea precizează domeniul sau locul în care o variabilă

este vizibilă. Domeniul de vizibilitate este în general determinant şi în stabilirea duratei de viaţă a variabilei.

Din punctul de vedere al duratei de viaţă a variabilei, aceasta poate fi temporară (există numai pe perioada în care funcţia care o

Page 183: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

177

declară este activată) sau permanentă (există pe toată durata de execuţie a programului).

Dacă tipul se declară explicit în declaratorul variabilei, clasa de memorie se determină prin specificatorul de clasă de memorie şi prin locul unde se face declaraţia (în interiorul unei funcţii sau înaintea oricărei funcţii).

Variabilele cele mai folosite sunt cele care sunt declarate în blocurile aparţinând unei funcţii. Aceste variabile sunt de două feluri:

- auto, aşa cum sunt marea majoritate a variabilelor declarate numai prin tip. Acesta este un specificator implicit, deci nu este nevoie să îl invocăm la declararea variabilelor. Variabilele auto sunt plasate în memoria stivă, iar domeniul de vizibilitate este local, numai pentru funcţia în care variabila a fost declarată, iar din punctul de vedere al duratei de viaţă sunt volatile, adică dispar din memoria stivă după reîntoarcerea din funcţie.

- static, declarate explicit. Variabilele static sunt plasate în memoria statică, iar domeniul de vizibilitate este local, numai pentru funcţia în care variabila a fost declarată, iar din punctul de vedere al duratei de viaţă sunt permanente, adică nu dispar din memoria statică după reîntoarcerea din funcţie.

- register, declarate explicit. Variabilele register sunt identice cu cele auto cu excepţia faptului că stocarea nu are loc în memoria stivă ci în regiştrii interni ai microprocesorului în scopul sporirii vitezei de execuţie a programelor.

- extern, declarate explicit. Din punct de vedere al modulării unor programe, este preferabil să divizăm un program complex în mai multe module program care se leagă în faza de link-editare. O variabilă declarată extern într-un modul program semnalează compilatorului faptul că această variabilă a fost declarată într-un alt modul. Aceste variabile sunt globale, adică sunt văzute de orice modul de program şi de orice funcţie componentă a unui modul program. Stocarea are loc în memoria statică iar durata de viaţă este permanentă, pe toată perioada execuţiei programului.

Iniţializarea unei variabile static diferă de cea a unei variabile auto prin aceea că iniţializarea este făcută o singură dată, la încărcarea programului în memorie şi lansarea sa în execuţie. După prima iniţializare, o variabilă static nu mai poate fi reiniţializată (de exemplu, la un nou apel al funcţiei în care este iniţializată).

Page 184: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

178

Iată ilustrat acest lucru prin două exemple simple. Se tipăreşte, cu ajutorul funcţiei receip(), un număr care este mai întâi iniţializat cu valoarea 1 şi returnat incrementat cu o unitate. În cazul folosirii variabilelor implicite locale auto se rulează programul:

# include <stdio.h>

short receip();

void main(){

printf("First = %d\n",receip());

printf("Second = %d\n",receip());}

short receip()

{ short number = 1;

return number++;}

şi se obţine rezultatul: First = 1

Second = 1

Dacă se modifică în funcţia receip() variabila number din auto în static, vom avea

# include <stdio.h>

short receip();

void main(){

printf("First = %d\n",receip());

printf("Second = %d\n",receip());}

short receip()

{ static short number = 1;

return number++;}

şi obţinem rezultatul First = 1

Second = 2

Limbajul C suportă patru specificatori ai claselor de memorare: auto, extern, static, register. Aceştia precizează modul de memorare al variabilelor care îi urmează. Specificatorii de memorare preced restul declaraţiei unei variabile care capătă forma generală:

specificator_de_memorare specificator_de_tip lista_de_variabile; Specificatorul auto Se foloseşte pentru a declară varibilele locale (obiectele dintr-un bloc). Totuşi, utilizarea acestuia este foarte rară, deoarece, implicit, variabilele locale au clasa de memorare automată (auto). Specificatorul extern Se utilizează pentru a face cunoscute anumite variabile globale declarare într-un modul de program (fişier) altor module de programe (fişiere) cu care se va lega primul pentru a alcătui programul complet. Exemplu:

Page 185: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

179

Modulul 1 Modulul 2

int x, y; extern int x, y;

char ch; extern char ch; main() func22()

{ {

. . . . . . x = y / 10;

. . . . . . } }

func23()

func1() {

{ x = 123; } y = 10; }

Dacă o variabilă globală este utilizată într-una sau mai multe funcţii din modulul în care acestea au fost declarate nu este necesară utilizarea opţiunii extern. Dacă compilatorul găseşte o variabilă ce n-a fost declarată, atunci acesta o va căuta automat printre variabilele globale. Exemplu: int first, last; /* variabile globale */

main( ) {

extern int first;}//folosire optionala declaratie extern Variabile statice Obiectele statice pot fi locale unui bloc sau externe tuturor blocurilor, dar în ambele situaţii ele îşi păstrează valoarea la ieşirea şi intrarea, din sau în funcţii. Variabile locale statice Când cuvântul cheie static se aplică unei variabile locale, compilatorul C crează pentru aceasta o memorie permanentă în acelaşi mod ca şi pentru o variabilă globală. Diferenţa dintre o variabilă locală statică şi o variabilă globală este că variabila locală statică este cunoscută numai în interiorul blocului în care a fost declarată. Un exemplu de funcţie care necesită o astfel de variabilă este un generator de numere care produce un nou număr pe baza celui anterior. serie() {static int numar_serie;

numar_serie = numar_serie + 23;

return (numar_serie); }

Se observă că variabila numar_serie continuă să existe între două apeluri ale funcţiei serie() fără ca aceasta să fi fost declarată ca variabilă globală. Se observă de asemenea că funcţia nu atribuie nici o valoare iniţială variabilei numar_serie, ceea ce înseamnă că valoarea inţială a acesteia este 0.

Page 186: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

180

Variabile globale statice O variabilă globală cu atributul static este o variabilă globală cunoscută numai în modulul în care a fost declarată. Deci o variabilă globală statică nu poate fi cunoscută şi nici modificată din alte module de program (alte fişiere). Exemplu: static int numar_serie;

//var. globala este cunoscuta numai in acest fisier

serie() {

numar_serie = numar_serie + 23;

return (numar_serie); }

/* initializarea variabilei numar_serie */

serie_start(val_init)

int val_init;{

numar_serie = val_init; }

Apelul funcţiei serie_start() cu o valoare intreagă iniţializează seria generatoare de numere, după care apelul funcţiei serie() va genera următorul număr din serie. Specificatorul register Acest modificator se aplică numai variabilei de tip int şi char. Acest specificator precizează faptul ca variabilele declarate cu acest modificator sunt des utilizate şi se pastrează de obicei în registrele CPU. Specificatorul register nu se aplica variabilelor globale.

Exemplu: Aceasta funcţie calculeaza me pentru întregi : int_putere (m, e)

int m;

register int e; {

register int temp;

temp = 1;

for (; e; e--) temp * = m;

return temp; }

În acest exemplu au fost declarate ca variabile registru atât e cât şi temp. De obicei utilizarea unei variabile registru conduce la micşorarea timpului de execuţie al unui program. Exemplu : unsigned int i;

unsigned int delay;

main() {

register unsigned int j;

long t;

t = time ('\0');

for (delay = 0; delay < 10; delay++)

for (i = 0; i < 64000; i++);

Page 187: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

181

printf("Timpul pentru bucla non-registru: %ld\n"

,time('\0')-t);

t = time ('\0'); for (delay = 0; delay < 10; delay++)

for (j = 0; j < 64000; j++);

printf ("Timpul bucla registru: %ld",time ('\0')-t);}

Dacă se execută acest program se va găsi că timpul de execuţie al buclei registru este aproximativ jumătate din timpul de execuţie al variabilei non-registru.

8.14. Pointeri la funcţii

Într-un fel, un pointer funcţie este un nou tip de dată. Chiar

dacă o funcţie nu este o variabilă, aceasta are o locaţie fizică în memorie care poate fi atribuită unui pointer. Adresa atribuită pointerului este punctul de intrare al funcţiei. Acest pointer poate fi utilizat în locul numelui funcţiei. Pointerul permite de asemenea funcţiilor să fie pasate (trecute) ca argumente în alte funcţii. Adresa unei funcţii se obţine utilizând numele funcţiei fără nici o paranteză sau argumente (ca în cazul tablourilor). Exemplu: # include <stdio.h>

# include <ctype.h>

void check();

int strcmp(); /* prototip functie */

void main() {

char s1[80], s2[80];

void *p; /* p preia adresa de intrare a functiei */

p = strcmp; gets(s1);

gets(s2);

check(s1,s2,p); }

void check (char *a, char *b, int (*cmp) ())

/* cu int (*cmp) () se declara un pointer functie */

{ printf (" Test de egalitate \n ");

if (!(*cmp) (a,b)) printf ("Egal\n");

else printf ("Neegal\n"); }

Declararea lui strcmp() în main() s-a facut din două motive: 1) programul trebuie să ştie ce tip de valoare returnează strcmp(); 2) numele trebuie cunoscut de compilator ca şi funcţie. Deoarece în C nu există o modalitate de a declara direct un pointer funcţie, acesta se declară indirect folosind un pointer void care poate primi orice fel de pointer.

Page 188: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

182

Apelul funcţiei check() se face având ca parametri doi pointeri la caracter şi un pointer funcţie. Instrucţiunea : (*cmp)(a, b) realizează apelul funcţiei, în acest caz strcmp() iar a şi b sunt argumentele acestuia. Exemplu:

# include <stdio.h>

# include <ctype.h>

int strcmp(); /* prototip functie */

void main() {

char s1[80], s2[80];

int (*p)(); /* p este pointer la functie */

p = strcmp;

gets (s1);

gets (s2);

printf (" Test de egalitate \n ");

if (!(*p) (s1,s2)) printf ("Egal\n");

else printf("Neegal\n"); }

Observaţie: Funcţia check() poate utiliza direct funcţia strcmp() sub forma:

check (s1, s2, strcmp); Exemplu: # include <stdio.h>

# include <ctype.h>

void check ();

int strcmp(); /* prototip functie */

void main() {

char s1[80], s2[80];

gets (s1);

gets (s2);

check (s1, s2, strcmp); }

void check (char *a, char *b, int (*cmp) ())

// se defineste functia check()

/* cu int (*cmp) () se declara un pointer functie */

{ printf (" Test de egalitate \n ");

if (!(*cmp) (a,b)) printf ("Egal\n");

else printf ("Neegal\n"); }

Page 189: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

183

Capitolul IX

PREPROCESAREA Un preprocesor C realizează substituirea macrodefiniţiilor, o serie de calcule adiţionale şi incluziunea fişierelor. Liniile programului sursă care încep cu "#", precedat eventual de spaţiu comunică cu preprocesorul. Sintaxa acestor linii este independentă de restul limbajului; pot apare oriunde în program şi pot avea efect care se menţine (indiferent de domeniul în care apare) până la sfârşitul unitatii de translatare. Preprocesorul C conţine următoarele directive: #if #include #ifdef #define #ifndef #undef #else #line #elif #error

#pragma 9.1. Directive uzuale

Directiva #define se utilizează pentru a defini un identificator

şi un şir (o secvenţă) pe care compilatorul îl va atribui identificatorului de fiecare dată când îl întâlneşte în textul sursă. Forma generală a directivei #define este : #define identificator şir Se observă că directiva #define nu conţine "; ". În secvenţa de atomi lexicali "şir" nu trebuie să apară spaţiu. Linia se termina cu CR. Exemplu: # define TRUE 1 # define FALSE 0 Când în program se întâlnesc numele TRUE şi FALSE, acestea se vor înlocui cu 1, respectiv 0. Instrucţiunea: printf ("%d %d %d", FALSE, TRUE, TRUE + 5);

va afişa pe ecran 0 1 6.

Page 190: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

184

După definirea unui macro_name, acesta poate fi folosit pentru definirea altui macro_name. Exemplu: # define ONE 1 /* Se defineşte macro_name ONE */

# define TWO ONE + ONE /* Se utilizează macro_name ONE */

# define THREE ONE + TWO

Deci această macrodefiniţie realizează simpla înlocuire a unui identificator cu şirul asociat. Dacă, de exemplu, se doreşte definirea unui mesaj standard de eroare, se poate scrie: # define E_MS "standard error on input \n"

. . . . . . . . . .

printf (E_MS);

Ultima linie este echivalentă cu : printf ("standard error on input\n");

atunci când în program se întâlneşte identificatorul E_MS. Exemplu: Programul următor nu va afişa "this is a test", deoarece argumentul lui printf() nu este închis între ghilimele. # define XYZ this is a test

. . . . . . . . . . . . . . . . .

printf ("XYZ");

Se va afişa XYZ şi nu "this is a test". Dacă şirul este prea lung şi nu încape pe o linie, acesta se scrie

sub forma: # define LONG_STRING " this is a very long \

string that is used as an example "

Observaţie: De obicei macro_names sunt definite cu litere mari. Directiva #define poate fi folosită şi pentru precizarea dimensiunii unui tablou, astfel: # define MAX_SIZE 100

float balance [ MAX_SIZE ];

Macro_nameul dintr-o directiva #define poate avea şi argumente. Exemplu : # define MIN (a ,b) a < b ? a : b

void main() {

int x, y;

x = 10; y = 20;

printf("Numarul mai mic este: %d ", MIN (x,y)); }

După substituirea lui MIN(a, b) în care a = x şi b = y, instrucţiunea printf() va arata astfel :

printf("Numarul mai mic este: %d",(x<y)?x:y);

Page 191: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

185

Directiva #error Directiva #error forţează compilatorul să stopeze operaţia de compilare când această este intilnita în program. Este utilizata în primul rind pentru depanarea programelor. Forma generală a directivei este: #error mesaj_de_eroare Aceasta linie determină procesorul să scrie mesajul de eroare şi să termine compilarea. Directiva # include Directiva # include comandă compilatorului să includă în fişierul ce conţine directiva #include un alt fişier sursă al cărui nume este specificat în directivă. Formele directivei sunt : # include <nume_fisier>

# include "nume_fisier"

Prima formă se referă la fişiere header (cu extensia .h) care se găsesc în subdirectorul include din fiecare mediu de programare C, iar cea de-a doua la fişiere header create în directorul de lucru al utilizatorului (directorul curent). Directivele # include pot fi folosite şi una în interiorul celeilalte.

9.2. Directive pentru compilare condiţionată

Limbajul C conţine câteva directive care ne permit să compilăm selectiv anumite porţiuni de program. Directivele #if, #else, #elif şi #endif Forma generală a lui #if este: #if expresie_constanta

secventa de instructiuni

#endif

Dacă expresie_constanta este adevărată, compilatorul va compila fragmentul de cod cuprins între #if şi #endif, iar dacă expresie_constanta este falsă, compilatorul va sări peste acest bloc. Exemplu: #define MAX 100

void main() {

#if MAX > 99

printf("Se compileaza pentru tablouri > 99\n");

#endif }

Observaţie: Expresie_constanta se evaluează în timpul compilării. De aceea, aceasta trebuie să conţină numai variabile constante definite anterior utilizării lor. Expresie_constanta nu trebuie să conţină operatorul sizeof.

Page 192: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

186

Directiva #else lucrează similar cu instrucţiunea else determinând o alternativă de compilare. Exemplu : # define MAX 10

void main() {

#if MAX > 99

printf("Se compileaza pentru tablouri > 99\n");

#else

printf("Se compileaza pentru tablouri < 99\n");

#endif }

Deoarece MAX = 10, compilatorul va compila numai codul cuprins între #else şi #endif, deci va tipări mesajul : Se compilează pentru tablouri < 99 Directiva #elif inlocuieşte "else if" şi este utilizată pentru realizarea opţiunilor multiple de tip if / else / if utilizate la compilare. Forma generală a directivelor #if , #elif, #endif este: #if expresie

Secventa_de_instructiuni #elif expresie_1

Secventa_de_instructiuni_1

#elif expresie_2

Secventa_de_instructiuni_2

. . . . . . . . . . . . . .

#elif expresie_N

Secventa_de_instructiuni_N

#endif

Dacă "expresie" este adevărată se compilează "Secventa_de_instructiuni" şi nu se mai tastează nici o altă expresie #elif. Dacă "expresie" este falsă, compilatorul verifică următoarele expresii în serie, compilându-se "Secventa_de_instructiuni_i", corespunzatoare primei "expresie_i" adevărată, i = 1, 2, . . . , N. Directivele #if şi #elif se pot include unele pe altele. Exemplu: #if MAX > 100

#if VERSIUNE_SERIALA

int port = 198;

#elif int port = 200;

#endif

#else

char out_buffer[100];

#endif Directivele #ifdef şi #ifndef O altă metodă de compilare condiţionată utilizează directivele #ifdef şi #ifndef, care înseamnă "if defined" şi "if not defined".

Page 193: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

187

Forma generală a lui #ifdef este : #ifdef macro_name

Secventa_de_instructiuni

#endif

Dacă anterior apariţiei secvenţei de mai sus s-a definit un macro_name printr-o directivă #define, compilatorul va compila "Secventa_de_instructiuni" dintre #ifdef şi #endif. Forma generală a lui #ifndef este: #ifndef macro_name Secventa_de_instructiuni

#endif

Dacă macro_name nu este definit prîntr-o directivă #define, atunci se va compila blocul dintre #ifndef şi #endif. Atât #ifdef, cât şi #ifndef pot utiliza un #else, dar nu #elif. Exemplu: # define TOM 10

void main() {

#ifdef TOM

printf("Hello TOM !\n");

#else

printf("Hello anyone !\n");

#endif

#ifndef JERY

printf ("Jery not defined \n");

#endif }

Programul va afişa: Hello TOM ! şi JERY not defined. Dacă nu s-a definit TOM, atunci programul va afişa : Hello anyone !. Directiva #undef Se utilizează pentru a anula definiţia unui macro_name definit printr-o directivă #define. Exemplu: #define LENGTH 100

#define WIDTH 100

char array[LENGTH][WIDTH];

#undef LENGTH

#undef WIDTH

Acest program defineşte atât LENGTH, cât şi WIDTH până se întâlneşte directiva #undef. Principala utilizare a lui #undef este de a permite localizarea unui macro_name numai în anumite secţiuni ale programului. Directiva #line O linie cu una din formele:

Page 194: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

188

#line numar "nume_fiaier"

#line numar

determină compilatorul să considere, din motive de diagnosticare a erorilor, că numărul de linie al urmatoarei linii din programul sursă este dat de "număr", iar numele fişierului în care se află programul sursă este dat de "nume_fişier". Dacă lipseste "nume_fişier", programul sursă se află în fişierul curent. Exemplu: Următoarea secvenţă face ca numărul de linie să înceapă cu 100. # line 100

void main() /* linia 100 */

{ /* linia 101 */

printf ("%d\n" , __LINE__); /* linia 102 */

}

Instructiunea printf() va afişa valoarea 102 deoarece această reprezintă a treia linie în program, după instrucţiunea #line 100. Directiva #pragma O linie de control de forma: #pragma nume

determină compilatorul să realizeze o acţiune care depinde de modul de implementare al directivei #pragma. "nume" este numele acţiunii #pragma dorite. Limbajul C defineşte două instrucţiuni #pragma: warn şi inline.

Directiva warn determină compilatorul să emită un mesaj de avertisment. Forma generală a lui warn este : #pragma warn mesaj

unde "mesaj" este unul din mesajele de avertisment definite în C. Forma generală a directivei inline este : #pragma inline şi avertizează compilatorul că programul sursă conţine şi cod în limbajul de asamblare. Directiva vidă O linie de forma: # nu are nici un efect. Macro_names (macrosimboluri) predefinite Limbajul C conţine câţiva identificatori predefiniţi, care la compilare se expandează pentru a produce informaţii speciale. Aceştia sunt:

Page 195: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

189

__LINE__ o constanta zecimală care conţine numele liniei sursă curente. __FILE__ un şir care conţine numele fişierului care se compilează. __DATA__ un şir care conţine data compilării sub forma luna/zi/an. __TIME__ un şir care conţine ora compilării sub form: hh:mm:ss __STDC__ constanta 1. Acest identificator este 1 numai în implementarile standard; dacă constanta este orice alt număr, atunci implementarea este diferită de cea standard. Aceste macrosimboluri, împreună cu simbolurile definite cu #define nu pot fi redefinite.

9.3. Modularizarea programelor

De obicei (vezi [Mocanu, 2001] programele C constau din fişiere sursă unice, cu excepţia fişierelor header. Un singur fişier sursă este în general suficient în cazul programelor mici. Modularizarea internă este un principiu de bază al programării în C şi constă în utilizarea pe scară largă a funcţiilor definite de utilizator. Scrierea programului principal (main) se concentrează mai ales pe apelul acestor funcţii. În cazul în care corpul de definiţie al funcţiilor utilizator se află după corpul de definiţie main, este necesar ca să declarăm prototipul funcţiilor utilizate de main() pentru a informa corect compilatorul despre tipul variabilelor returnate de funcţii. O altă modalitate este aceea de a defini funcţiile utilizator înaintea funcţiei principale main(), caz în care nu mai sunt necesare prototipurile. Programul este modularizat cu ajutorul funcţiilor prin divizarea sa în nuclee funcţionale. Acestea pot fi comparate cu nişte mici piese de lego cu ajutorul cărora se pot construi ulterior structuri (programe) foarte complexe.

Pe scurt, modularizarea internă constă în descompunerea sarcinii globale a unui program în funcţii de prelucrare distincte.

O funcţie de uz general este o funcţie care poate fi folosită într-o varietate de situaţii şi, probabil, de către mai mulţi utilizatori. Este de preferat ca aceste funcţii de uz general să nu primească informaţii prin intermediul unor variabile globale ci prin intermediul parametrilor. Sporeşte astfel foarte mult flexibilitatea în folosirea acestor funcţii.

Page 196: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

190

Modularizarea externă constă în divizarea unui program foarte complex în mai multe subprograme. Astfel, un fişier sursă mai mare se poate diviza în două sau mai multe fişiere sursă mai mici. Evident, aceste fişiere sunt strâns legate între ele pentru a forma în final un tot unitar echivalent cu programul complex iniţial (dinainte de divizare).

În figura de mai sus se prezintă un ecran al Microsoft Visual C++ din MSDN 6.0 Noţiunea cea mai cuprinzătoare este aceea de Workspace (spaţiu de lucru) care cuprinde în esenţă o colecţie de proiecte corelate şi prelucrabile împreună. Un workspace cuprinde unul sau mai multe Projects (proiecte) dintre care numai unul este principal şi restul sunt subordonate (subprojects). Fiecare proiect este compus la rândul său din mai multe fişiere, de acelaşi tip sau de tipuri diferite. Prezentarea exhaustivă a organizării acestui mediu de dezvoltare a aplicaţiilor C/C++ este un demers în afara prezentei lucrări. Ceea ce merită să subliniem este faptul că, în cadrul cel mai întâlnit, anume un workspace care include un singur project, acest proiect conţine mai ales fişiere sursă şi fişiere de tip header. Aceste fişiere se numesc module. Modulul principal este fişierul care conţine funcţia principală main(). Celelalte fişiere sursă, dacă există, se numesc module

Page 197: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

191

secundare. De obicei, cu fiecare modul secundar se asociază un fişier header propriu separat. Acest fişier header trebuie să conţină toate directivele şi declaraţiile de variabile necesare pentru o corectă compilare separată a modulului cu care se asociază. Pentru a exemplifica cele de mai sus, vom modulariza un exemplu anterior, anume al unei baze de date simple. Workspace-ul va conţine un singur project, care va conţine următoarele 4 fişiere: bd_main.c local.h

bd_bib.c local1.h bd_main.c (bd - bază de date) este modulul principal, cel care conţine funcţia main(). El are asociat fişierul header local.h. În mod asemănător, local1.h este fişierul header asociat cu modulul secundar bd_bib.c (bib - bibliotecă) care conţine toate definiţiile funcţiilor utilizator. Conţinutul lor este prezentat în continuare.

Modulul bd_main.c este: # include "local.h"

void main() {

char choice;

init_list();

for (; ;) {

choice = menu();

switch (choice) {

case 'e' : enter(); break;

case 'd' : display(); break;

case 's' : save(); break;

case 'l' : load(); break;

case 'q' : exit(); }}}

Fişierul local.h conţine: # include <stdio.h>

# include <ctype.h>

# include <string.h>

# define SIZE 100

struct addr {

char name[20];

char street[30];

char city[15];

char state[10];

unsigned int zip;

} addr_info[SIZE];

FILE *fp;

extern void init_list();

extern char menu();

extern void enter(),save(),load(); extern void display(),exit();

Page 198: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

192

Modulul bd_bib.c este: # include "local1.h"

/* Functia init_list() */

void init_list() {

register int t;

for (t = 0; t < SIZE; t++)

*addr_info[t].name = '\0'; }

/* Functia menu() */

char menu() {

char s[5],ch;

do {

printf ("(E)nter\n");

printf ("(D)isplay\n"); printf ("(L)oad\n");

printf ("(S)ave\n");

printf ("(Q)uit\n");

printf (" Alegeti optiunea: ");

gets(s);

ch=s[0];

} while (!strrchr("edlsq",ch));

return tolower(ch); }

/* Functia enter() */

void enter() {

register int i;

for (i=0; i < SIZE; i++)

if (!*addr_info[i].name) break;

if (i == SIZE) {

printf ("addr_info full \n"); /* Lista plina */

return;}

printf ("Name: ");

gets (addr_info[i].name);

printf ("Street: ");

gets (addr_info[i].street);

printf ("City: ");

gets (addr_info[i].city);

printf ("State: ");

gets (addr_info[i].state);

printf ("Zip: "); scanf ("%d",&addr_info[i].zip);}

/* Functia save() */

void save() {

register int i;

if ((fp = fopen("maillist", "wb")) == NULL) {

printf (" Cannot open file\n ");

return;}

Page 199: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

193

for (i = 0; i <= SIZE; i++)

if(*addr_info[i].name)

if(fwrite(&addr_info[i],sizeof(struct addr),1,fp) !=1) printf (" File write error \n ");

fclose (fp);}

/* Functia load() */

void load()

{ register int i;

if ((fp = fopen("maillist","rb")) == NULL) {

printf("Cannot open file\n ");

return;}

for (i = 0; i < SIZE; i++)

if(fread(&addr_info[i],sizeof(struct addr),1,fp)==1);

else if (feof(fp)) {

fclose (fp); return;}

else printf ("File read error\n"); }

/* Functia display() */

void display() {

register int t;

printf("\n%20s","Name");

printf("%30s","Street");

printf("%15s","City");

printf("%10s","State");

printf("%5s\n","Zip");

for (t=0;t<SIZE;t++) {

if (*addr_info[t].name!='\0') {

printf("%20s",addr_info[t].name); printf("%30s",addr_info[t].street);

printf("%15s",addr_info[t].city);

printf("%10s",addr_info[t].state);

printf("%5d",addr_info[t].zip);

getchar();}}}

Fişierul local1.h conţine: # include <stdio.h>

# include <ctype.h>

# include <string.h>

# define SIZE 100

extern struct addr {

char name[20]; char street[30];

char city[15];

char state[10];

unsigned int zip;

} addr_info[SIZE];

extern FILE *fp;

Se poate verifica cum fiecare modul în parte este compilabil fără erori, iar la link-editare nu se semnalează, de asemenea, erori.

Page 200: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

194

Capitolul X

INTRĂRI/IEŞIRI 10.1. Funcţii de intrare şi ieşire - stdio.h

Limbajul C nu dispune de instrucţiuni de intrare/ieşire. Aceste operaţii se realizează prin intermediul unor funcţii din biblioteca standard a limbajului C. Aceste funcţii pot fi aplicate în mod eficient la o gamă largă de aplicaţii datorită multiplelor facilităţi pe care le oferă. De asemenea, ele asigură o bună portabilitate a programelor, fiind implementate într-o formă compatibilă pe toate sistemele de operare. O altă caracteristică a limbajului C constă în faptul că nu există un sistem de gestionare a fişierelor care să permită organizări de date, aşa cum în alte limbaje există fişiere cu organizare relativă sau indexată. În limbajul C toate fişierele sunt tratate ca o înşiruire de octeţi, neexistând structuri de date specifice care să se aplice acestor fişiere. Programatorul poate să interpreteze datele după cum doreşte Prin urmare, prin scrierea/citirea datelor se scriu/citesc un număr de octeţi fără o interpretare specifică.

Funcţiile de intrare/ieşire, tipurile şi macrodefiniţiile din "stdio.h" reprezintă aproape o treime din bibliotecă.

În C, intrarea standard respectiv ieşirea standard sunt în mod implicit reprezentate de terminalul de la care s-a lansat programul.

Prin fişier înţelegem o mulţime ordonată de elemente păstrate pe diferite suporturi. Aceste elemente se numesc înregistrări. Suporturile cele mai des utilizate sunt cele magnetice (floppy sau harddiscuri). Ele se mai numesc suporturi reutilizabile deoarece zona utilizată pentru păstrarea înregistrărilor unui fişier poate fi ulterior reutilizată ulterior pentru păstrarea înregistrărilor unui alt fişier.În C un fişier reprezintă o sursă sau o destinaţie de date, care poate fi asociată cu un disc sau cu alte periferice.

Biblioteca acceptă fişiere de tip text şi binar, deşi în anumite sisteme, de exemplu UNIX, acestea sunt identice.

Un fişier de tip text este o succesiune de linii, fiecare linie având zero sau mai multe caractere terminate cu ' \n '. Într-o altă

Page 201: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

195

reprezentare, anumite caractere pot fi convertite într-o succesiune de caractere, adică să nu existe o relaţie unu la unu între caracterele scrise (citite) şi acţiunea perifericului. De exemplu, caracterul NL (new line), ' \n ', corespunde grupului CR (carriage return) şi LF (line feed).

În aceeaşi idee, se consideră că datele introduse de la un terminal formează un fişier de intrare. Înregistrarea se consideră că este formată de datele unui rând tastate de la terminal (tastatură, keyboard), deci caracterul de rând nou NL se consideră ca fiind terminator de înregistrare. În mod analog, datele care se afişează pe terminal (monitor, display) formează un fişier de ieşire. Şi în acest caz înregistrarea este formată din caracterele unui rând.

Ceea ce este important de subliniat este că fişierele text pot fi accesate la nivel de octet sau de caracter, ele putând fi interpretate drept o colecţie de caractere, motiv pentru care se şi numesc fişiere text. Toate funcţiile de intrare/ ieşire folosite până acum se pot utiliza şi pentru fişierele text. Un fişier de tip binar este o succesiune de octeţi neprelucraţi care conţin date interne, cu proprietatea că dacă sunt scrise şi citite pe acelaşi sistem, datele sunt egale. Aceste fişiere sunt organizate ca date binare, adică octeţii nu sunt consideraţi ca fiind coduri de caractere. La fişierele binare înregistrarea se consideră că este o colecţie de date structurate numite articole. Structurile de date sunt pretabile pentru stocarea în astfel de fişiere Tratarea fişierelor se poate face la două nivele, inferior şi superior.

Nivelul inferior de prelucrare a fişierelor oferă o tratare a fişierelor fără zone tampon (buffere), făcând apel direct la sistemul de operare. Rezervarea de zone tampon este lăsată pe seama utilizatorului. Fişierele de tip text se pretează la o astfel de tratare. Nivelul superior de prelucrare a fişierelor se bazează pe utilizarea unor proceduri specializate în prelucrarea fişierelor care printre altele pot rezerva şi gestiona automat zonele tampon necesare. Fişierele binare se pot manipula cu facilitate la acest nivel. Funcţiile specializate de nivel superior au denumiri asemănătoare cu cele de nivel inferior, doar prima literă a numelui este f. În practică operaţiile de intrare/ieşire (I/O) cu memoria externă (hard-disk sau floppy-disk) sunt mult mai lente decât cele cu memoria internă. Din această cauză, pentru a spori viteza de lucru, se încearcă

Page 202: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

196

să se reducă numărul de operaţii de acces la disc. În acest scop se folosesc bufferele. Un buffer este o zonă de memorie în care sistemul memorează o cantitate de informaţie (număr de octeţi), în general mai mare decât cantitatea solicitată de o operaţie de I/O. Dacă un program efectuează o operaţie de citire a 2 octeţi dintr-un fişier, atunci sistemul citeşte într-un buffer întreg sectorul de pe disc (512 octeţi) în care se găsesc şi cei 2 octeţi solicitaţi, eventual chiar mai mult, în funcţie de dimensiunea bufferului (zonei tampon). Dacă în continuare se vor solicita încâ 2 octeţi, aceştia vor fi preluaţi din bufferul din memorie, fără a mai fi nevoie să mai accesăm discul pe care se află fişierul din care se face citirea. Operaţiile de citire continuă în acest mod până la citirea tuturor octeţilor din buffer, moment în care se va face o nouă umplere a bufferului cu noi date prin citirea următorului sector de pe disc. Invers, dacă un program efectuează o operaţie de scriere a unui număr de octeţi pe disc, aceştia se vor înscrie de fapt secvenţial în buffer şi nu direct pe disc. Scrierea va continua astfel până la umplerea bufferului, moment în care sistemul de operare efectuează o operaţie de scriere a unui secto de pe disc cu cei 512 octeţi din buffer (se goleşte bufferul prin scriere). În acest fel, reducând numărul de operaţii de acces la disc (pentru citire sau scriere) creşte viteza de execuţie a programelor şi fiabilitatea dispozitivelor de I/O. Bufferele au o mărime implicită, dar ea poate fi modificată prin program. Dimensiunea trebuie aleasă în funcţie de aplicaţie ţinând cont de faptul că prin mărirea bufferului creşte viteza de execuţie dar scade dimensiunea memoriei disponibile codului programului şi invers, prin micşorarea sa creşte memoria cod disponibilă dar scade viteza de lucru. Bufferul de tastatură are, spre exemplu, dimensiunea de 256 octeţi, din care 254 sunt puşi la dispoziţie. Orice fişier are o înregistrare care marchează sfârşitul de fişier. În cazul fişierelor de intrare ale căror date se introduc de la terminal, sfârşitul de fişier se generează în funcţie de sistemul de operare considerat. Pentru sistemele de operare MS-DOS sau MIX şi RSX11 se tastează CTRL/Z iar pentru UNIX se tastează CTRL/U. Un fişier stocat pe suport magnetic se mai numeşte şi fişier extern. Când se prelucrează un astfel de fişier se crează o imagine a acestuia în memoria internă (RAM) a calculatorului. Această imagine se mai numeşte şi fişier intern.

Page 203: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

197

Un fişier intern este conectat la un fişier extern sau dispozitiv prin deschidere; conexiunea este întreruptă prin închidere. Deschiderea unui fişier întoarce un pointer la un obiect de tip FILE, care conţine toate datele necesare pentru controlul fişierului. Operaţiile de deschidere şi închidere a fişierelor se poate realiza în C prin funcţii specializate din biblioteca standard I/O a limbajului. Alte operaţii care sunt executate frecvent în prelucrarea fişierelor sunt: • Crearea unui fişier (acest fişier nu există în format extern) • Actualizarea unui fişier (deja existent) • Adăugarea de înregistrări unui fişier deja existent • Consultarea unui fişier • Poziţionarea într-un fişier • Redenumirea unui fişier • Ştergerea unui fişier

Ca şi operaţiile de deschidere şi închidere de fişiere, operaţiile indicate mai sus pot fi realizate printr-un set de funcţii aflate în biblioteca standard I/O a limbajului. Aceste funcţii realizează acţiuni similare sub diferite sisteme de operare, dar multe dintre ele pot depinde de implementare. În cele ce urmează se prezintă funcţiile care au o utilizare comună pe diferite medii de programare şi sunt cele mai frecvent utilizate.

10.2. Operaţii cu fişiere

În acest subcapitol vom detalia principalele operaţii efectuate asupra unor fişiere. În timpul lucrului cu fişierele, sistemul de operare păstrează un indicator de fişier care indică poziţia curentă în fişier, poziţie la care se va face următoarea operaţie de scriere sau citire. De exemplu, la deschiderea unui fişier pentru citire indicatorul de fişier va indica la începutul fişierului. Dacă se va face o operaţie de citire a 2 octeţi se vor citi octeţii cu numărul de ordine 0 şi 1 iar indicatorul va indica spre următorul octet, adică cel cu numărul de ordine 3.

Pentru o mai corectă înţelegere a acestor funcţii le vom structura după nivelul la care se utilizează: inferior sau superior. În momentul începerii execuţiei unui program, interfeţele standard (cu ecranul, tastatura şi porturile seriale şi paralele) sunt deschise în mod text. Principalele funcţii sunt grupate în tabelul de mai jos:

Page 204: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

198

Descriere Nume funcţie de nivel inferior

Nume funcţie de nivel superior

Deschidere _open fopen Creare _creat fcreate

Citire _read fread

Scriere _write fwrite

Închidere _close fclose

Poziţionare _lseek fseek

Ştergere _unlink remove

Redenumire _rename rename În afara acestor funcţii principale mai există anumite funcţii specializate, cum ar fi:

- Funcţii pentru prelucrarea pe caractere a unui fişier: putc (scriere caracter) şi getc (citire caracter).

- Funcţii pentru Intrări/Ieşiri cu format: fscanf şi fprintf. - Funcţii pentru Intrări/Ieşiri de şiruri de caractere: fgets şi

fputs. Pentru ca sistemul de operare să poată opera asupra fişierelor ca

fluxuri (stream) de intrare/ieşire trebuie să cunoască anumite informaţii despre ele. Acest lucru se realizează prin operaţia de deschidere a fluxurilor (stream-urilor). Pointerul fişier În urma operaţiei de deschidere se crează în memorie o variabilă de tip structură FILE care este o structură predefinită. În această variabilă, care se numeşte bloc de control al fişierului, FCB (File Control Block) sistemul păstrează informaţii despre fişierul deschis, precum:

• Nume • Dimensiune • Atribute fişier • Descriptorul fişierului

Un pointer-fişier este un pointer la informaţiile care definesc diferitele aspecte ale unui fişier: nume, stare, poziţie curentă. Un pointer-fişier este o variabilă pointer de tip FILE, definită în "stdio.h".

Tipul FILE este un tip structurat care depinde de sistemul de operare. Dacă facem abstracţie de cazurile speciale de calculatoare tip

Page 205: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

199

VAX sau U3B, pe majoritatea implementărilor tipul FILE se defineşte prin următoarea structură:

typedef struct {

unsigned char *_ptr;

int _cnt;

unsigned char *_base;

char _flag;

char _file;

} FILE;

Variabila de tip FILE este creată şi gestionată de către suportul pentru exploatarea fişierelor în limbajul C. În urma deschiderii unui fişier, programul primeşte un pointer la variabila creată, deci un pointer la o structură de tip FILE. Se spune că s-a deschis un stream (flux de date). Toate operaţiile care se fac pe acest stream se referă la fişierul asociat stream-ului.

În limbajul C există 5 stream-uri standard, definite în <stdio.h>: FILE *stdin;

care se referă la dispozitivul standard de intrare (tastatura). Orice operaţie de citire de la stream-ul stdin înseamnă citire de la tastatură. Bufferul folosit are o dimensiune de 254 de caractere şi bufferul se goleşte la tastarea NL (‘\n’). Se mai spune că stdin este cu buffer la nivel de linie. FILE *stdout; care se referă la dispozitivul standard de ieşire (ecranul). Orice operaţie de scriere la stream-ul stdout înseamnă scriere pe ecran. Spre deosebire de stdin, stdout este ne-bufferizat deoarece orice scriere pe ecran se face direct la scrierea unui caracter în fişierul stdout. FILE *stderr; care se referă la dispozitivul standard pentru afişarea mesajelor de eroare (ecranul). Este ne-bufferizat. FILE *stdprn; care se referă la primul port paralel PRN la care se conectează de obicei imprimanta (LPT). Este bufferizat la nivel linie. FILE *stdaux; care se referă la primul port serial COM1. Este ne-bufferizat.

10.3. Nivelul inferior de prelucrare a fişierelor

La acest nivel operaţiile de prelucrare a fişierelor se execută fără o gestiune automată a zonelor tampon, făcându-se apel direct la sistemul de operare. Programatorul are în gestiune o zonă declarată

Page 206: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

200

drept buffer şi trebuie să ţină cont de faptul că această bufferizare este la nivel linie. Numele funcţiilor de nivel inferior, orientate pe text (transfer de octeţi) încep de obicei cu _ (underline). Dacă un fişier se deschide în modul text, atunci, în cazul citirii dintr-un fişier, secvenţa de octeţi CR-LF (0DH, 0AH) este translatată (înlocuită) cu un singur caracter LF, iar în cazul scrierii în fişier caracterul LF este expandat la secvenţa CR-LF. De asemenea, în cazul MS-DOS sau Windows CTRL/Z este interpretat în cazul citirii drept caracter de sfârşit de fişier (EOF).

10.3.1. Deschiderea unui fişier Orice fişier înainte de a fi prelucrat trebuie deschis, motiv

pentru care operaţia de deschidere a unui fişier este de mare importanţă. Deschiderea unui fişier existent se realizează prin intermediul funcţiei _open. La revenirea din ea se returnează un aşa numit descriptor de fişier. Acesta este un număr întreg. El identifică în continuare fişierul respectiv în toate operaţiile realizate asupra lui.

În forma cea mai simplă funcţia _open se apelează printr-o expresie de atribuire de forma:

df = _open(spf,mod) unde: df – este un număr întreg care reprezintă descriptorul de fişier spf – este specificatorul fişierului care se deschide mod – defineşte modul de prelucrare a fişierului Specificatorul de fişier este fie un şir de caractere, fie un pointer spre un astfel de şir de caractere. Conţinutul şirului de caractere depinde de sistemul de operare folosit. În cea mai simplă formă el este un nume sau mai general o cale care indică plasamentul pe disc al fişierului care se operează. Fişierele deschise la acest nivel pot fi prelucrate în citire (consultare), scriere (adăugare de înregistrări) sau citire/scriere (actualizare sau punere la zi). Calea spre fişier trebuie să respecte convenţiile sistemului de operare MS-DOS în general. În cea mai simplă formă ea este un şir de caractere care defineşte numele fişierului, urmat de extensia fişierului. Aceasta presupune că fişierul se găseşte în directorul curent. Dacă fişierul nu se află în fişierul curent, atunci numele este precedat de o construcţie de forma: litera:\nume_1\nume_2\…\nume_k

unde:

Page 207: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

201

litera – defineşte discul (în general A, B pentru floppy-disk şi C, D,.. pentru hard-disk)

nume_i – este un nume de subdirector. Deoarece calea se include între ghilimele, caracterul ‘\’ se dublează. Spre exemplu, putem folosi o comandă de deschidere de forma:

int d;

d=_open(“A:\\JOC\\BIO.C“,O_RDWR);

caz în care fişierul BIO.C din directorul JOC de pe dscheta A se deschide în citire/scriere.

În funcţie de operaţia dorită, mod poate avea valorile: 0 - pentru citire 1 - pentru scriere 2 - pentru citire/scriere

Deschiderea unui fişier nu reuşeşte dacă unul dintre parametri este eronat. În acest caz funcţia _open returnează valoarea (-1).

int _open( const char *filename, int oflag [, int pmode] ); este definiţia generală a funcţiei _open. Modul de acces mod se poate furniza în mod explicit printr-o variabilă de tip întreg (oflag) care poate avea valorile:

Variabila mod Modul de deschidere a fişierului

_O_RDONLY Fişierul se deschide numai în citire (read-only) Nu se poate specifica împreună cu _O_RDWR sau _O_WRONLY

_O_WRONLY Fişierul se deschide numai în scriere (write-only) Nu se poate specifica împreună cu _O_RDWR sau _O_RDONLY

_O_RDWR Fişierul se deschide în citire/scriere (read/write)

_O_APPEND Fişierul se deschide pentru adăugarea de înregistrări la sfârşitul său.

_O_CREAT Crează şi deschide un nou fişier pentru scriere. Nu are nici un efect dacă fişierul este deja existent.

_O_BINARY Fişierul se prelucrează în mod binar

_O_TEXT Fişierul este de tip text, adică se prelucrează pe caractere sau octeţi (implicit)

Page 208: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

202

Menţionăm că în MSDN aceste variabile se mai numesc şi oflag (open-flag) şi sunt definite în fişierul header FCNTL.H. În cazul în care oflag este _O_CREAT, atunci este necesară specificarea constantelor opţionale pmode, care se găsesc definite în SYS\STAT.H. Acestea sunt:

_S_IREAD - este permisă numai citirea fişierului _S_IWRITE - este permisă şi citirea (permite efectiv

citirea/scrierea fişierului) _S_IREAD | _S_IWRITE - este permisă şi scrierea şi citirea

fişierului. Argumentul pmode este cerut numai când se specifică

_O_CREAT. Dacă fişierul există deja, pmode este ignorat. Altcumva, pmode specifică setările de permisiune asupra fişerului care sunt activate când fişierul este închis pentru prima oară.

_open aplică masca curentă de permisiune la fişier înainte de setarea accesului la fişier.

Pentru a crea un fişier nou se va utiliza funcţia _creat pentru a-l deschide. De fapt se deschide prin creare un fişier inexistent. Funcţia este definită astfel:

int _creat( const char *filename, int pmode ); în care parametrii au fost descrişi mai sus.

Protecţia unui fişier este dependentă de sistemul de operare. Spre exemplu, în UNIX protecţia se defineşte prin 9 biţi ataşaţi oricărui fişier, grupaţi în 3 grupe de câte 3 biţi. Fiecare bit controlează o operaţie de citire, scriere, execuţie. Protecţia operaţiilor se exprimă faţă de proprietar, grup sau oricine altcineva. Numărul octal 0751 permite proprietarului toate cele 3 operaţii indicate mai sus (7 = 1112), grupul la care aparţine proprietarul poate citi şi executa fişierul (5 = 1012) iar alţi utilizatori pot numai executa fişierul (1 = 0012). Funcţia _creat poate fi apelată şi în cazul în care se deschide un fişier deja existent, caz în care se pierde conţinutul vechi al fişierului respectiv şi se crează în locul lui unul nou cu acelaşi nume.

Fiecare din funcţiile _open sau _creat returnează un specificator de fişier (handle) pentru fişierul deschis. Acest specificator este o valoare întreagă pozitivă. Implicit, stdin are specificatorul 0, stdout şi stderr au specificatorii 1 respectiv 2 iar fişierele disc care sunt deschise primesc pe rând valorile 3, 4,..etc. până la numărul maxim admis de fişiere deschise.

Page 209: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

203

Valoarea returnată -1 indică o eroare de deschidere, în care caz variabila errno este setată la una din valorile:

EACCES – (valoare 13) s-a încercat deschiderea pentru scriere a unui fişier read-only sau modul de partajare a fişierului nu permite operaţia specificată sau calea nu specifică un nume de fişier ci de director.

EEXIST – (valoare 17) flagurile _O_CREAT şi _O_EXCL sunt specificate, dar numele de fişier este al unui fişier deja existent.

EINVAL – (valoare 22) unul dintre argumentele oflag sau pmode sunt invalide.

EMFILE – (valoare 24) nu mai sunt disponibile specificatoare de fişier (prea multe fişiere deschise).

ENOENT – (valoare 2) fişierul sau calea nu au fost găsite. Variabila globală errno păstrează codurile de eroare folosite de funcţiile perror (print error) sau strerror (string error) pentru tratarea erorilor. Constantele manifest pentru aceste variabile sunt declarate în STDLIB.H după cum urmează:

extern int _doserrno; extern int errno; errno este setată de o eroare într-un apel de funcţie la nivel de

sistem (la nivelul inferior). Deoarece errno păstrează valoarea setată de ultimul apel, această valoare se poate modifica la fiecare apel de funcţie sistem. Din această cauză errno trebuie verificată imediat înainte şi după un apel care poate s-o modifice. Toate valorile errno, definite drept constante manifest în ERRNO.H, sunt compatibile UNIX. Valorile valide pentru aplicaţiile Windows pe 32 de biţi sunt un subset al acestor valori UNIX. Valorile specificate mai sus sunt valabile pentru aplicaţii Windows pe 32 de biţi.

La o eroare, errno nu este setată în mod necesar la aceeaşi valoare cu codul erorii de sistem. Numai pentru operaţii de I/O se foloseşte _doserrno pentru a accesa codul erorilor sistemului de operare echivalent cu codurile semnalate de errno. Exemplu: Acest program foloseste _open pentru a deschide un fisier numit OPEN.C pentru citire si un fisier numit OPEN.OUT scriere. Apoi fisierele sunt inchise

#include <fcntl.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <io.h>

Page 210: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

204

#include <stdio.h>

void main( void )

{

int fh1, fh2; fh1 = _open( "OPEN.C", _O_RDONLY );

if( fh1 == -1 )

perror( "open failed on input file" );

else

{ printf( "open succeeded on input file\n" );

_close( fh1 );} fh2=_open("OPEN.OUT",_O_WRONLY|_O_CREAT,_S_IREAD|_S_IWRITE);

if( fh2 == -1 )

perror( "Open failed on output file" );

else

{printf( "Open succeeded on output file\n" );

_close( fh2 );}}

Prin execuţia acestui program se vor obţine următoarele mesaje pe display:

open failed on input file: No such file or directory

Open succeeded on output file

Press any key to continue

10.3.2. Scrierea într-un fişier Scrierea într-un fişier se realizează folosind funcţia _write. Se

presupune că fişierul respectiv a fost în prealabil deschis prin funcţiile _open sau _creat. Ea este asemănătoare cu funcţia _read, doar că se realizează transferul de date în sens invers şi anume din memorie pe suportul fiierului. Funcţia _write, ca şi _read, se apelează printr-o atribuire de forma:

nr = _read(df,zt,n) unde: nr – este o variabilă de tip întreg căreia i se atribuie numărul de octeţi scrişi în fişier. df – este descriptorul de fişier returnat de funcţia _open la deschiderea sau _creat la crearea fişierului. zt - este un pointer spre zona tampon definită de utilizator, zonă din care se face scrierea. n – este dimensiunea zonei tampon sau numărul de octeţi care se doreşte să se scrie. Definiţia funcţiei este: int _write( int handle, const void *buffer, unsigned int count );

Page 211: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

205

Funcţia _write scrie count octeţi din buffer în fişierul asociat cu descriptorul handle. Operaţia de scriere începe la poziţia curentă a pointerului de fişier asociat cu fişierul dat. Dacă fişierul este deschis cu indicatorul _O_APPEND, operaţia de scriere începe la sfârşitul fişierului. După o operaţie de scriere pointerul de fişier este incrementat cu numărul de biţi scrişi efectiv. Dacă fişierul a fost deschis în mod text (implicit), atunci _write tratează CTRL/Z drept un caracter ce indică sfârşitul logic al fişierului. Când se scrie într-un dispozitiv, _write tratează CTRL/Z din buffer drept terminator al operaţiei de ieşire.

În general trebuie ca la revenirea din funcţia _write să avem nr=n, ceea ce semnifică faptul că s-au scris pe disc exact numărul de biţi din buffer. În caz contrar scrierea este eronată: aceasta semnifică faptul că pe disc a rămas mai puţin spaţiu (în octeţi) decât numărul de octeţi ai bufferului. Dacă valoarea returnată este -1, se semnalizează eşecul operaţiei de scriere. În acest caz variabila globală errno poate avea una din valorile EBADF (care semnifică un descriptor de fişier invalid sau că fişierul nu a fost deschis pentru scriere) sau ENOSPC (care semnifică lipsa spaţiului pe disc pentru operaţia de scriere). Funcţia _write poate fi utilizată pentru a scrie pe ieşirile standard (display). Astfel, pentru a scrie pe ieşirea standard identificată prin stdout se foloseşte descriptorul 1, iar pentru a scrie pe ieşirea standard pentru erori, stderr, se foloseşte descriptorul de fişier 2. De asemenea, în acest caz nu este nevoie să apelăm funcţia _open sau _creat deoarece fişierele respective se deschid automat la lansarea programului. Exemplu: /*Acest program deschide un fisier pentru scriere si

foloseste _write pentru a scrie octeti in fisier*/

#include <io.h>

#include <stdio.h>

#include <stdlib.h>

#include <fcntl.h>

#include <sys/types.h>

#include <sys/stat.h>

char buffer[]="This is a test of '_write' function";

void main( void )

{ int fh;

unsigned byteswritten;

if((fh=_open("write.o",_O_RDWR|_O_CREAT,

_S_IREAD|_S_IWRITE))!=-1)

{

if((byteswritten = write(fh,buffer,sizeof(buffer)))== -1)

Page 212: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

206

perror( "Write failed" );

else

printf( "Wrote %u bytes to file\n", byteswritten ); _close( fh );}}

În urma execuţiei programului, se va afişa mesajul: Wrote 36 bytes to file

Press any key to continue

10.3.3. Citirea dintr-un fişier Citirea dintr-un fişier deschis în prealabil cu funcţia _open se

realizează cu ajutorul funcţiei _read. Ea returnează numărul efectiv al octeţilor citiţi din fişier. Funcţia _read se poate apela folosind o expresie de atribuire de forma:

nr = _read(df,zt,n) cu definiţia generală:

int _read( int handle, void *buffer, unsigned int count ); unde: nr – este o variabilă de tip întreg căreia i se atribuie numărul de octeţi citiţi din fişier. df – este descriptorul de fişier returnat de funcţia open la deschiderea sau creat la crearea fişierului. zt - este un pointer spre zona tampon definită de utilizator, zonă în care se face citirea. n – reprezintă numărul de biţi care se citesc Funcţia _read citeşte maximum count octeţi în buffer din fişierul asociat cu descriptorul handle. Operaţia de citire începe de pe poziţia curentă îndicată de pointerul de fişier asociat cu fişierul dat. După operaţia de citire, pointerul de fişier indică spre următorul octet necitit din fişier. Dacă fişierul a fost deschis în mod text, citirea se termină la întâlnirea caracterului CTRL/Z, care este interpretat drept indicator de sfârşit de fişier.

_read returnează numărul de biţi citiţi din fişier, care poate fi mai mic decât count dacă sunt mai puţini decât count octeţi rămaşi în fişier sau dacă fişierul este deschis în mod text. În acest caz fiecare pereche CR-LF (carriage return–linefeed) (CR-LF) este înlocuită cu un singur caracter LF. Numai acest caracter LF se consideră în valoarea returnată. Înlocuirea nu afectează pointerul de fişier. Dacă funcţia încearcă să citească după sfârşitul fişierului, se returnează valoarea 0. Dacă descriptorul de fişier (handle) este invalid sau dacă

Page 213: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

207

fişierul nu este deschis pentru citire sau dacă este blocat, funcţia returnează valoarea negativă -1 şi setează variabila errno la EBADF. Tipul erorii şi depistarea ei este dependentă de sistemul de operare utilizat. Dacă n = 1, se citeşte un singur octet. De obicei, nu este eficient să se citească câte un octet dintr-un fişier, deoarece apelurile multiple ale funcţiei _read pot conduce la un consum de timp apreciabil. Dimensiunea maximă a lui n este dependentă de sistemul de operare. O valoare utilizată frecvent este 512, valoare optimă pentru MS-DOS sau pentru UNIX.

Funcţia _read citeşte maximum count biţi în zona buffer din fişierul cu descriptorul handle. Operaţia de citire începe de la poziţia curentă a pointerului de fişier asociat cu fişierul respectiv. După o operaţie de citire, pointerul fişier indică spre următorul caracter (octet) necitit din fişier. Dacă fişierul a fost deschis în mod text, _read se termină când se întâlnete indicatorul de fişier CTRL/Z. Funcţia _read poate fi utilizată pentru a citi de la intrarea standard (tastatură). În acest caz descriptorul de fişier are valoarea 0. De asemenea, în acest caz nu este nevoie să apelăm funcţia _open deoarece fişierul se deschide automat la lansarea programului. Exemplu: /* Acest program deschide fisierul WRITE.O creat anterior

si incearca sa citeasca 60000 octeti din fisier folosind

_read. Apoi va afisa numarul de octeti cititi */

#include <fcntl.h> /* Necesara numai pentru definirea

_O_RDWR */

#include <io.h> #include <stdlib.h>

#include <stdio.h>

char buffer[60000];

void main( void )

{ int fh;

unsigned int nbytes = 60000, bytesread;

/* Deschide fisierul in citire: */

if( (fh = _open( "write.o", _O_RDONLY )) == -1 )

{ perror( "open failed on input file" );

exit( 1 ); }

/* Read in input: */

if((bytesread = _read(fh,buffer,nbytes)) <= 0)

perror( "Problem reading file" );

else

Page 214: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

208

printf( "Read %u bytes from file\n", bytesread );

_close( fh );}

La execuţia programului se va afişa următorul mesaj: Read 36 bytes from file

Press any key to continue

10.3.4. Închiderea unui fişier După terminarea prelucrării unui fişier el trebuie închis. Acest

lucru se realizează automat dacă programul se termină prin apelul funcţiei exit. Programatorul poate închide un fişier folosind funcţia _close. Se recomandă ca programatorul să închidă orice fişier de îndată ce s-a terminat prelucrarea lui, deoarece numărul fişierelor ce pot fi deschise simultan este limitat între 15 şi 25, în funcţie de sistemul de operare. Menţionăm că fişierele corespunzătoare intrărilor şi ieşirilor standard nu trebuie închise de programator. Definiţia funcţiei este:

int _close( int handle );

Funcţia _close închide fişierul asociat cu descriptorul handle. Funcţia _close returnează valoarea 0 la o închidere reuşită şi -1

în caz de incident. Apelul ei se realizează printr-o expresie de atribuire de forma:

v =_ close(df) unde: v – este variabila de tip întreg ce preia valoarea returnată de funcţie df – este descriptorul de fişier (handle) al fişierului pe care dorim să-l închidem.

10.3.5. Poziţionarea într-un fişier Operaţiile de citire/scriere într-un fişier se execută secvenţial,

astfel încât: - fiecare apel al funcţiei _read citeşte înregistrarea din poziţia

următoare din fişier - fiecare apel al funcţiei _write scrie înregistrarea în poziţia

următoare din fişier. Acest mod de acces la fişier se numeşte secvenţial şi el este util

când dorim să prelucrăm fiecare înregistrare a fişierului. În practică apar însă şi situaţii în care noi dorim să scriem şi să citim înregistrări într-o ordine oarecare. În acest caz se spune că accesul la fişier este aleator. Pentru a realiza un acces aleator este nevoie să ne putem

Page 215: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

209

poziţiona oriunde în fişierul respectiv O astfel de poziţionare este posibilă pe hard-uri şi floppy-uri prin funcţia _lseek.

Definiţia funcţiei este: long _lseek( int handle, long offset, int origin );

Funcţia _lseek mută pointerul de fişier asociat cu descriptorul handle (df) pe o nouă locaţie care este situată la offset octeţi de origin. Următoarea operaţie de citire/scriere se va efectua de la noua locaţie. Argumentul origin trebuie să fie una dintre următoarele constante, definite în STDIO.H:

SEEK_SET – începutul fişierului (valoare 0) SEEK_CUR – poziţia curentă a pointerului de fişier (valoare 1) SEEK_END – sfârşitul fişierului (valoare implicită 2) Funcţia _lseek returnează valoarea 0 la poziţionare corectă şi -1

la incident. Ea poate fi apelată prin: v = _lseek(df, deplasament, origine)

unde: v – este o variabilă de tip întreg căreia i se atribuie valoarea returnată de către funcţie (0 sau -1) df – este descriptorul de fişier (handle) a cărui valoare a fost definită la deschiderea sau crearea fişierului. deplasament – este o variabilă de tip long şi conţine numărul de octeţi peste care se va deplasa capul de citire/scriere al discului. origine – are una din valorile 0 - deplasamentul se socoteşte de la începutul fişierului; 1 - deplasamentul se socoteşte din poziţia curentă a capului de

citire/scriere; 2 - deplasamentul se socoteşte de la sfârşitul fişierului.

Menţionăm că prin apelul lui _lseek nu se realizează nici un fel de transfer de informaţie ci numai poziţionarea în fişier. Operaţia următoare realizată prin apelul funcţiei _read sau _write se va realiza din această poziţie. Spre exemplu, apelul:

v = _lseek(df, 0l, 2)

permite să se facă poziţionarea la sfârşitul fişierului. În continuare se pot adăuga articole folosind funcţia _write. Poziţionarea la începutul fişierului se face prin apelul:

v = _lseek(df, 0l, 0)

Exemplu: #include <io.h>

#include <fcntl.h>

#include <stdlib.h>

Page 216: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

210

#include <stdio.h>

void main( void )

{ int fh; long pos; /* Pozitia pointerului fisier */

char buffer[10];

fh = _open( "write.o", _O_RDONLY );

/* Pozitionare la inceputul fisierului: */

pos = _lseek( fh, 0L, SEEK_SET );

if( pos == -1L )

perror( "_lseek inceput nu a reusit!" );

else

printf("Pozitia pentru inceputul fisierului = %ld\n",

pos );

/* Muta pointerul fisier cu 10 octeti */

_read( fh, buffer, 10 );

/* Gaseste pozitia curenta: */

pos = _lseek( fh, 0L, SEEK_CUR );

if( pos == -1L )

perror( "_lseek pozitia curenta nu a reusit!" );

else

printf( "Pozitia curenta = %ld\n", pos );

/* Pozitionare pe ultima pozitie: */

pos = _lseek( fh, 0L, SEEK_END );

if( pos == -1L )

perror( "_lseek sfarsit nu a reusit!" );

else printf( "Pozitia ultima este = %ld\n", pos );

_close( fh );}

În urma execuţiei programului se va afişa: Pozitia pentru inceputul fisierului = 0

Pozitia curenta = 10

Pozitia ultima este = 36

Press any key to continue

10.3.6 Ştergerea unui fişier Un fişier poate fi şters apelând funcţia _unlink astfel: v = _unlink(spf)

unde v este o variabilă de tip întreg căreia i se atribuie valoarea 0 pentru ştergere reuşită şi (-1) pentru ştergere nereuşită. spf este specificatorul de fişier folosit la deschidere a fişierului. Definiţia funcţiei este:

int _unlink( const char *filename ); Funcţia _unlink şterge de pe disc fişierul specificat prin filename. Exemplu:

Page 217: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

211

/* Acest program sterge fisierul WRITE.O creat si prelucrat anterior. */ #include <stdio.h>

void main( void )

{ if( _unlink( "write.o" ) == -1 )

perror( "Nu se poate sterge 'WRITE.O'" );

else

printf( "S-a sters 'WRITE.O'\n" );}

În urma execuţiei programului se afişează: S-a sters 'WRITE.O'

Press any key to continue

10.3.7. Exemple de utilizare a funcţiilor de intrare/ieşire de nivel inferior 1. Să se scrie un program care copiază intrarea standard la

ieşirea standard. Această problemă se poate rezolva uşor prin folosirea funcţiilor

getchar şi putchar. Acum o vom rezolva folosind funcţiile _read şi _write. # include <stdio.h>

# include <io.h>

void main() /* copiaza intrarea standard la iesirea

standard */

{ char c[1];

while (_read(0,c,1)>0)

_write(1,c,1);}

Menţionăm că cel de-al doilea parametru al funcţiei _read sau _write trebuie să fie pointer spre caractere.

Lucrul la nivelul inferior nu este chiar atât de simplu pe cât pare. Vom ilustra în continuare responsabilitatea pe care o are programatorul în gestionarea zonelor tampon. Să considerăm exemplul anterior în care zona tampon o mărim la 3 caractere, deci programul arată astfel: # include <stdio.h>

# include <io.h>

void main()

{ char c[3];

while (_read(0,c,3)>0)

_write(1,c,3);}

Citirea nu se va opri după 3 caractere, ci funcţia _read va continua să funcţioneze până la tastarea ENTER (CR+LF). Imediat funcţia _read va tipări grupele de 3 caractere introduse, inclusiv grupul final CR+LF. Zona tampon definită este supraînscrisă de fiecare dată când se introduc noi caractere.

Page 218: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

212

Dacă de la tastatură vom introduce 123456<CR><LF>

atunci se va tipări primul grup (prima înscriere a zonei tampon) 123, apoi a doua grupă 456 şi grupul <CR> şi <LF> va supraînscrie primele două caractere ale bufferului, anume codurile ASCII ale lui 4 şi 5 şi se va tipări <CR><LF>6.

123456 123456 6

Primul grup 123456 este scris prin ecou de la tastatură, iar următoru se înscrie de către program. Dacă în continuare vom introduce 1<ENTER> atunci se va tipări 1 urmat de două rânduri noi deoarece fiecare CR sau LF sunt expandate de stdout în perechi <CR><LF>.

Dacă mărim la 5 dimensiunea bufferului şi de la tastatură introducem 12<ENTER>, atunci se va tipări

12 12

¦

deoarece cel de-al 5-lea octet al bufferului nu a fost alocat prin citire, având o valoare nedefinită. Problemele de mai sus legate de gestiunea bufferului în/din care se face citirea/scrierea pot fi depăşite cu o modificare simplă, prezentată mai jos. Prin scriere nu se vor trimite spre stdout decât numărul de caractere citit de la stdin. # include <stdio.h>

# include <io.h>

# define LZT 10 // lungime zona tampon

void main() /* copiaza intrarea standard la iesirea

standard */

{ char zt[LZT];

int n;

while ((n=_read(0,zt,LZT))>0)

_write(1,zt,n);}

Programatorul trebuie să ţină cont însă şi de alte amănunte cum ar fi dimensiunea implicită a bufferului stdin, care este de 254 de caractere. 2. Să se scrie un program care citeşte un şir de numere flotante de la

intrarea standard şi crează 2 fişiere fis1.dat şi fis2.dat, primul conţinând numerele de ordin impar citite de la intrarea standard

Page 219: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

213

(primul, al 3-lea, al 5-lea, etc.) iar cel de-al doilea pe cele de ordin par citite de la aceeaşi intrare. Apoi să se listeze, la ieşirea standard, cele două fişiere în ordinea fis1.dat, fis2.dat câte un număr pe un rând în formatul

număr de ordine: număr Vom scrie programul folosindu-ne de funcţii definite de utilizator care să facă apel la funcţiile de nivel inferior. Programul arată astfel: # include <stdio.h>

# include <io.h> # include <sys/types.h>

# include <sys/stat.h>

# include <fcntl.h>

#include <stdlib.h>

char nume1[]="fis1.dat";

char nume2[]="fis2.dat";

union unr {

float nr;

char tnr[sizeof(float)];};

union unr nrcit;

int creare_fis(const char *nume)

{ int df;

if ((df=_creat(nume,_S_IWRITE|_S_IREAD))==-1)

{ printf("%s: ",nume);

printf("Nu se poate deschide fisierul in creare\n");

exit(1);}

return df;}

void scrie_fis(int df,char *nume)

{if(_write(df,nrcit.tnr,sizeof(float))!=sizeof(float))

{ printf("%s: ",nume);

printf("Eroare la scrierea fisierului\n");exit(1);}}

void date_fis(int df1,char *nume1,int df2,char *nume2)

{ int j=1,i;

while ((i=scanf("%f",&nrcit.nr))==1) { if(j%2) scrie_fis(df1,nume1);

else scrie_fis(df2,nume2);

j++;}}

void inchid_fis(int df, char *nume)

{ if (_close(df)<0) {

printf("%s: ",nume);

printf("eroare la inchiderea fisierului\n");

exit(1);}}

int deschid_fis_cit(char *nume)

{ int df;

if ((df=_open(nume,_O_RDONLY))==-1) {

Page 220: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

214

printf("%s: ",nume);

printf("Nu se poate deschide fisierul in citire\n");

exit(1);} return df;}

void list_fis(int df,char *nume,int ord)

{ int j,i;

if (ord%2) j=1; else j=2;

while ((i=_read(df,nrcit.tnr,sizeof(float)))>0) {

printf("%6d: %g\n",j,nrcit.nr);j+=2;}

if (i<0) {

printf("%s: ",nume);

printf("Eroare la citire din fisierul\n"); exit(1);}

_close(df);}

void main() {

int df1,df2;

df1=creare_fis(nume1);

df2=creare_fis(nume2);

date_fis(df1,nume1,df2,nume2);

inchid_fis(df1,nume1);inchid_fis(df2,nume2);

df1=deschid_fis_cit(nume1);

df2=deschid_fis_cit(nume2);

list_fis(df1,nume1,1);list_fis(df2,nume2,2);} 3. Să se realizeze programul de mai sus folosind un singur fişier

fis.dat. Programul va diferi faţă de cel anterior prin faptul că

înregistrările se stochează într-un singur fişier, deci funcţia de listare se va modifica pentru citirea din 2 în 2 a înregistrărilor. După fiecare citire din fişier, se va face un salt cu o înregistrare pentru a poziţiona capul de citire/scriere peste înregistrarea următoare. # include <stdio.h>

# include <io.h>

# include <sys/types.h>

# include <sys/stat.h>

# include <fcntl.h>

#include <stdlib.h>

char nume[]="fis.dat";

union unr {

float nr;

char tnr[sizeof(float)];};

union unr nrcit;

int creare_fis(const char *nume)

{ int df;

if ((df=_creat(nume,_S_IWRITE|_S_IREAD))==-1)

{ printf("%s: ",nume);

printf("Nu se poate deschide fisierul in creare\n");

Page 221: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

215

exit(1);}

return df;}

void scrie_fis(int df,char *nume) { if (_write(df,nrcit.tnr,sizeof(float))!=sizeof(float))

{ printf("%s: ",nume);

printf("Eroare la scrierea fisierului\n");exit(1);}}

void date_fis(int df,char *nume)

{ while (scanf("%f",&nrcit.nr)==1) {

scrie_fis(df,nume);}}

void inchid_fis(int df, char *nume)

{ if (_close(df)<0) {

printf("%s: ",nume);

printf ("eroare la inchiderea fisierului\n");

exit(1);}}

int deschid_fis_cit(char *nume)

{ int df;

if ((df=_open(nume,_O_RDONLY))==-1) {

printf("%s: ",nume);

printf("Nu se poate deschide fisierul in citire\n");

exit(1);}

return df;}

void list_fis(int df,char *nume)

{ int j,i;

j=1;

while ((i=_read(df,nrcit.tnr,sizeof(float)))>0) {

printf("%6d: %g\n",j,nrcit.nr);

// avans peste o inregistrare

if(_lseek(df,(long)sizeof(float),1)==-1l) break;

j+=2;}

if (i<0) {

printf("%s: ",nume);

printf("Eroare la citire din fisierul\n");

exit(1);}

j=2;

// pozitionare pe prima inregistrare

_lseek(df,0l,0);

// avans la inregistrarea a doua

_lseek(df,(long)sizeof(float),1);

while((i=_read(df,nrcit.tnr,sizeof(float)))>0)

{printf("%6d: %g\n",j,nrcit.nr);

// avans peste o inregistrare

if(_lseek(df,(long)sizeof(float),1)==-1l)

break; j+=2;}

if (i<0) { printf("%s: ",nume);

printf("Eroare la citire din fisierul\n");

exit(1);}

_close(df);}

Page 222: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

216

void main() {

int df;

df=creare_fis(nume); date_fis(df,nume);

inchid_fis(df,nume);

df=deschid_fis_cit(nume);

list_fis(df,nume);}

Atragem atenţia asupra modului în care lucrează funcţiile de intrare/ieşire pentru stdin şi stdout faţă de cele pentru disc. Dacă intrările şi ieşirile pentru perifericele standard le putem executa în formatul dorit cu ajutorul funcţiilor specializate scanf şi printf, pentru lucrul cu discul variabila float este tratată sub forma unui grup de 4 octeţi care se scriu sau se citesc pe disc aşa cum este reprezentarea lor internă. Există funcţii specializate pentru scrierea/citirea pe disc cu format, dar care sunt de nivel superior.

Ceea ce merită să subliniem este faptul că echivalentele de nivel superior pentru fişiere ale funcţiilor printf() şi scanf() sunt fprintf() şi fscanf(). Echipamentele periferice pot fi considerate fişiere externe şi deci funcţiile specializate pentru I/O cu fişiere pot fi folosite şi pentru operaţii de I/O cu echipamentele periferice. Funcţiile printf şi scanf sunt proiectate pentru a lucra implicit cu fişierele stdout respectiv stdin, deci cu monitorul şi tastatura. 10.4. Nivelul superior de prelucrare a fişierelor

Nivelul superior de prelucrare a fişierelor se referă la aşa

numitul format binar de reprezentare a informaţiei în fişiere care la rândul său face apel la informaţia structurată. Bufferul este alocat automat şi gestionat de funcţii C specializate.

10.4.1. Funcţia fopen() Funcţia fopen se apelează printr-o expresie de atribuire de forma: pf = fopen(spf,mod) unde: pf - este un pointer spre tipul FILE spf – este specificatorul fişierului care se deschide mod – este un şir de caractere care defineşte modul în care se deschide fişierul. Forma generală de declarare a funcţiei fopen() este: FILE *fopen(char *filename, char *mode);

Page 223: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

217

Funcţia deschide fişierul al cărui nume este specificat prin "filename" (de obicei un fişier disc) şi întoarce un pointer la FILE pentru operaţie reuşită şi NULL pentru operaţie nereuşită. Varibilele permise pentru modul "mode" sunt:

a _O_WRONLY | _O_APPEND (usual _O_WRONLY | _O_CREAT | _O_APPEND)

a+ _O_RDWR | _O_APPEND (usual _O_RDWR | _O_APPEND | _O_CREAT )

r _O_RDONLY

r+ _O_RDWR

w _O_WRONLY(usual _O_WRONLY | _O_CREAT | _O_TRUNC)

w+ _O_RDWR (usual _O_RDWR | _O_CREAT | _O_TRUNC)

b _O_BINARY

t _O_TEXT

c Nimic

n Nimic

Modul "a" nu şterge markerul de sfârşit d fişier EOF înainte de a adăuga la sfârşitul fişierului. După ce s-a făcut o adăugare, comanda MS-DOS TYPE tipăreşte datele până la markerul original EOF şi nu până la ultima dată adăugată. Modul "a+" şterge identificatorul de sfârşit de fişier EOF înainte de adăugarea de înregistrări la sfârşitul fişierului. După adăugare comanda MS-DOS TYPE va tipări toate datele conţinute în fiier. Modul "a+" este cerut pentru adăugarea la sfârşitul unui fişier care are marker terminator CTRL/Z = EOF. Dacă modul "mode" include "b" după litera iniţială, ca în "rb" sau "w+b" se indică un fişier binar. Numele fişierului conţine cel mult FILENAME_MAX caractere. La un moment dat pot fi deschise cel mult FOPEN_MAX fişiere. Menţionăm că stdin, stdout şi stderr sunt pointeri spre tipul FILE şi permit ca funcţiile de nivel superior de prelucrare a fişierelor să poată trata intrarea standard, ieşirea standard şi ieşirea standard pentru erori, la fel ca şi restul fişierelor. Singura deosebire constă în

Page 224: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

218

faptul că în acest caz programatorul nu trebuie să deschidă sau să închidă fişierele respective. Exemplu: FILE *fp, *fopen(); /* se declara pointerii de tip file *fp si *fopen() */ fp = fopen("test","w"); /* se deschide fisierul " test " pentru screiere */ Pentru detectarea unei erori la deschiderea unui fişier se utilizează secvenţa: if ((fp = fopen("test", "w")) == NULL) {

puts ("Cannot open file\n");

exit(1); }

Dacă pentru operaţia de citire se încearcă deschiderea unui fişier inexistent, fopen() va returna o eroare.

10.4.2. Funcţia fclose() Forma generală de declarare a funcţiei fclose() este: int fclose(FILE *fp); unde "fp" este pointerul la fişier returnat după apelul funcţiei fopen(). Funcţia fclose() se utilizează pentru a închide un fişier deschis cu fopen(). Funcţia fclose() scrie mai întâi în fişier datele rămase în fişierul buffer apoi închide fişierul. fclose() întoarce zero (0) pentru închidere reuşită şi EOF (-1) dacă apare o eroare. La execuţia funcţiei exit se închid automat toate fişierele deschise. Cu toate acestea, se recomandă ca programatorul să închidă un fişier de îndată ce s-a terminat prelucrarea lui, altfel se poate ajunge în situaţia de a se depăşi numărul limită admis pentru fişierele care pot fi simultan deschise într-un program. Exemplu: /* Acest program deschide fisierele numite "test1.c" si "test2.c".Apoi foloseste fclose pentru a inchide "test1.c" si _fcloseall pentru a inchide restul fisierelor deschise */ #include <stdio.h>

FILE *stream1, *stream2;

void main( void ){

int numclosed;

/* Deschidere in citire (esec daca fisierul "test1.c" nu

exista) */

if( (stream1 = fopen( "test1.c", "r" )) == NULL )

printf( "Fisierul 'test1.c' nu a fost deschis\n" );

else

printf( "Fisierul 'test1.c' a fost deschis\n" );

/* Deschidere pentru scriere */

if( (stream2 = fopen( "test2.c", "w+" )) == NULL )

printf( "Fisierul 'test2.c' nu a fost deschis\n" );

else

Page 225: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

219

printf( "Fisierul 'test2.c' a fost deschis\n" );

/* Inchide fisierul cu pointerul stream1 */

if( fclose( stream1 ) ) printf( "Fisierul 'test1.c' nu a fost inchis\n" );

/* Toate celelalte fisiere se inchid: */

numclosed = _fcloseall( );

printf( "Numarul fisierelor inchise cu _fcloseall: %u\n",

numclosed );}

În urma execuţiei programului se obţine:

Fisierul 'test1.c' a fost deschis

Fisierul 'test2.c' a fost deschis

Numarul fisierelor inchise cu _fcloseall: 1 Press any key to continue

10.4.3. Funcţiile rename() şi remove() Funcţia rename() schimbă numele vechi al fişierului,

"oldname", cu numele nou, "newname". Întoarce o valoare diferită de zero dacă incercarea nu reuseste. int rename (char *oldname, char *newname);

Funcţia remove() int remove(char *filename);

Funcţia remove() elimină fişierul cu numele specificat, astfel încât o incercare ulterioară de deschidere a fişierului va eşua. Întoarce o valoare diferită de zero dacă încercarea reuşeşte.

10.4.4. Funcţii de tratare a erorilor a) Funcţia ferror() Aceasta funcţie determină dacă în urma unei operaţii cu fişiere s-a produs sau nu o eroare. Forma generală de declarare este: int ferror(FILE *fp)

unde "fp" este un pointer la fişier. Funcţia ferror() întoarce o valoare diferită de zero dacă s-a detectat o eroare şi o valoare 0 dacă nu s-a detectat nici o eroare. b) Funcţia feof() int feof(FILE *fp)

Funcţia feof() întoarce o valoare diferită de zero dacă indicatorul de sfârşit de fişier este valid şi o valoare zero dacă indicatorul de sfârşit de fişier nu este valid. c) Funcţia perror() void perror(const char *s)

Page 226: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

220

Funcţia perror() scrie s şi un mesaj de eroare care depinde de implementare, corespunzator cu intregul din errno.h, astfel: fprintf(stderr,"%s %s\n, s, "error message")

10.4.5. Funcţii cu acces direct a) Funcţia fread() Permite citirea unui bloc de date. Forma generală de declarare: int fread(void *buffer,int num_bytes,int count,FILE *fp)

Funcţia fread() citeşte din fişierul specificat prin "fp" cel mult "count" obiecte, fiecare obiect având lungimea egală cu "num_bytes" şi îi trimite în zona de memorie indirectată prin "buffer" . *fp este un pointer fişier la fişierul deschis anterior cu fopen(). Funcţia întoarce numărul de obiecte citite, acesta putând fi mai mic decât cele cerute. Pentru a determina starea funcţiei se pot utiliza funcţiile feof(), ferror(). b) Funcţia fwrite() Permite scrierea unui bloc de date. Forma generală de declarare: int fwrite(void *buffer,int num_bytes,int count, FILE *fp) Funcţia fwrite() scrie din zona (tabloul) "buffer" în fişierul indirectat prin "fp", "count" obiect de lungime "nr_bytes". Funcţia întoarce numărul de obiecte scrise, care, în caz de eroare este mai mic decât "count". Exemplu: Programul următor scrise un număr real pe disc # include "stdio.h"

void main() {

FILE *fp;

float f = 12.23;

if ((fp = fopen ("test", "wb")) == NULL){ printf (" Cannot open file\n ");

return; }

fwrite (&f, sizeof (float), 1, fp);

fclose (fp); }

Aşa cum se vede din acest program, "buffer" poate fi o simplă variabilă. Exemplu: Programul următor copiază un tablou de numere reale "balance", în fişierul "balance": # include "stdio.h"

void main()

{ FILE *fp;

float balance[100]; /* tabloul balance */

if ((fp = fopen("balance", "w+")) == NULL) {

printf ("Cannot open file\n");

return;}

Page 227: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

221

. . . . . . . . . . . . . . . . .

fwrite (balance, sizeof (balance), 1, fp);

. . . . . . . . . . . . . . . . . fclose (fp); }

Exemplu: Programul următor deschide fişierul FREAD.OUT şi scrie în el 25 de caractere şi apoi îl redeschide şi citeşte din nou caracterele din fişier după care afişează numărul caracterelor citite şi conţinutul. #include <stdio.h>

void main( void )

{ FILE *stream;

char list[30];

int i, numread, numwritten;

/* Deschide fisierul in mod text: */

if( (stream = fopen( "fread.out", "w+t" )) != NULL ) { for ( i = 0; i < 25; i++ )

list[i] = (char)('z' - i);

/* Scrie 25 caractere in fisier */

numwritten = fwrite(list,sizeof(char),25,stream );

printf( "S-au scris %d caractere\n", numwritten );

fclose( stream );}

else

printf( "Probleme cu deschiderea fisierului\n" );

if( (stream = fopen( "fread.out", "r+t" )) != NULL )

{ /* Incearca sa citeasca 25 caractere */

numread = fread( list, sizeof( char ), 25, stream );

printf("Nr. caracterelor citite = %d\n", numread);

printf( "Continutul bufferului = %.25s\n", list );

fclose( stream );}

else

printf( "Fisierul nu a putut fi deschis\n" );}

În urma execuţie programului se obţine:

S-au scris 25 caractere

Nr. caracterelor citite = 25

Continutul bufferului = zyxwvutsrqponmlkjihgfedcb

Press any key to continue

10.4.6. Funcţii pentru poziţionare a) Funcţia fseek() Determină poziţionarea fişierului la citire sau scriere, începând cu poziţia selectată. Forma funcţiei:

int fseek(FILE *fp, long offset, int origin)

unde "fp" este un pointer-fişier returnat prin apelul funcţiei fopen(), "offset" este deplasamentul (număr octeţi) noii poziţii faţă de "origin", iar "origin" este una din următoarele macrodefiniţii: SEEK_SET - început de fişier;

Page 228: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

222

SEEK_CUR - poziţie curentă; SEEK_END - sfârşit de fişier. Funcţia returnează 0 dacă se execută cu succes şi o valoare nenulă în caz de eroare. Dacă nu s-a efectuat nici o operaţie de I/O de la deschiderea fişierului în mod APPEND (adăugare), atunci pointerul indică începutul fişierului. Nu se recomanda utilizarea funcţiei fseek() pentru fişiere text; se sugerează utilizarea acesteia numai pentru fişiere binare. Translaţiile CR-LF efectuate în mod text pot cauza funcţionarea defectoasă a funcţiei fseek. Funcţia fopen şi toate celelalte funcţii vor căuta să înlăture caracterul CTRL/Z terminator de fişier (EOF). Singurele operaţii garantate să funcţioneze corect când se utilizează fseek asupra fişierelor deschise în mod text este poziţionarea cu offset 0 relativă la orice poziţie din fişier şi poziţionarea faţă de începutul fişierului cu un offset returnat de funcţia ftell(). Funcţia ftell() este definită astfel:

long ftell( FILE *stream );

Funcţia returnează valoarea curentă a pointerului fişier. Poziţia este exprimată prin offsetul faţă de începutul fiierului.

În cazul fişierelor deschise în mod text, acest offset nu reflectă întotdeauna exact numărul de octeţi datorită translaţiei CR-LF. Este preferată folosirea simultană a funcţiilor fseek şi ftell pentru a opera asupra fişierelor text, dar se recomandă folosirea lor în special asupra fişierelor binare. Exemplu: Pentru a citi cel de-al 235 byte din fişierul numit "test" se poate folosi următorul program: func1() /* se declara funcţia func1() */

{ FILE *fp;

if ((fp = fopen("test", "rb")) == NULL) {

printf ("Cannot open file\n"); exit (1); }

fseek(fp, 235L, 0);

return getc(fp);} /* se citeste un caracter de la pozitia

235 */

Observaţie: L modifică constanta 235 la tipul long int. Exemplu: /* Acest program deschide fisierul FSEEK.OUT si muta

pointerul in diverse locuri din fisier */

#include <stdio.h>

void main( void )

{ FILE *stream; char line[81];

Page 229: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

223

int result;

stream = fopen( "fseek.out", "w+" );

if( stream == NULL ) printf( "”Fisierul fseek.out nu s-a deschis\n" );

else

{fprintf( stream, "Fseek incepe aici: "

"Acesta este fisierul 'fseek.out'.\n" );

result = fseek( stream, 19L, SEEK_SET);

if( result ) perror( "Fseek esec" );

else

{ printf( "Pointerul fisier este plasat la mijlocul

primei linii.\n" );

fgets( line, 80, stream );

printf( "%s", line );}

fclose( stream );}}

În urma execuţie programului se obţine: Pointerul fisier este plasat la mijlocul primei linii.

Acesta este fisierul 'fseek.out'.

Press any key to continue

10.4.7. Ieşiri cu format Funcţiile de tip printf() asigură conversiile de ieşire cu format. a) Funcţia fprintf() Forma acestei funcţii este: int fprintf(FILE *fp, "format", lista_argumente) Funcţia fprintf() realizează conversia şi scrierea la ieşire în fişierul indirectat cu "fp" sub controlul formatului, "format". Valoarea întoarsă de funcţie este numărul de caractere scrise, sau orice valoare negativă, dacă apare o eroare. Şirul "format" conţine două tipuri de obiecte: caractere obişnuite care sunt copiate în fişierul de ieşire şi descriptori de conversie, fiecare determinând conversia şi tipărirea argumentelor din lista de argumente. Fiecare descriptor începe cu caracterul % şi se încheie cu un caracter de conversie. Între % şi caracterul de conversie pot exista: 1) Indicatori (în orice ordine): "-" - determină alinierea la stânga a argumentului convertit în câmpul de reprezentare; "+" - precizează că numărul va fi reprezentat cu semn; " " - dacă primul caracter nu este un semn se va scrie un blanc la început; "0" - se utilizează în conversiile numerice şi indică umplerea cu zerouri la începutul câmpului;

Page 230: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

224

"#" - indică o formă de ieşire alternativă : pentru "0", prima cifra va fi zero; pentru "x" sau "X", la începutul fiecărui număr nenul se va scrie "0x" sau "0X"; pentru "e", "E", "g", "G", "f" ieşirea va avea întotdeauna un punct zecimal; pentru "g" şi "G" nu se vor elimina zerourile de la sfârşit. 2) Un număr care indică lungimea minimă a câmpului de reprezentare. Argumentul convertit va fi tipărit într-un câmp cu o lungime cel puţin egală cu cea specificată, dacă va fi nevoie şi mai mare. Dacă argumentul specificat are mai puţine caractere, atunci câmpul va fi umplut la stânga sau la dreapta, funcţie de aliniere. Caracterul de umplere este de obicei spatiul, dar este 0 dacă s-a ales această optiune (exemplu: %05d). 3) Un punct ce separă lungimea câmpului de precizie. 4) Un număr, precizia, care indică numărul maxim de caracetre care se vor tipări după virgulă pentru "e", "E", sau "f", sau numărul de cifre semnificative pentru conversiile "g" sau "G", sau numărul maxim de caractere ce se vor tipări dintr-un şir. Lungimea, sau precizia, sau amândoua se pot specifica şi prin "*". De exemplu: %10.4f - va afişa un număr de cel puţin 10 caractere cu 4 caractere după virgulă; %5.7s - va afişa un şir de cel puţin 5 caractere dar nu mai lung de 7 caractere; %-10.2f - va alinia la stânga un număr real cu 2 zecimale într-un câmp de reprezentare de cel puţin 10 caractere. Descriptorii de conversie utilizaţi de C sunt: %c - un singur caracter. %d, %i - notaţie zecimala cu semn. %x, %X - notaţie hexazecimală fără semn (fără 0x sau 0X). %u - notaţie zecimală fără semn.

%s - caracterele din şir sunt tipărite până se întâlneşte '\0' sau cât timp numărul de caractere tipărit precizia.

%f - notaţie zecimală de forma [-]mmm.ddd, unde numărul d-urilor este indicat de precizie; precizia implicită este 6, iar o precizie egală cu zero elimina punctul zecimal. %e, %E - notaţie zecimală de forma: [-]m.dddddde+/-xx sau [-]m.ddddddE+/-XX

Page 231: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

225

unde numărul de d-uri este indicat de precizie (precizia implicită este 6, iar o precizie egală cu 0 va elimina punctul zecimal).

%g, %G - se utilizează %e sau %E dacă exponentul este mai mic decât -4, sau precizie, în caz contrar se utilizează %f.

%p - afiseaza un pointer. %o - notaţie octalăa fără semn (fără 0 la început). %% - nu se face conversie, se tipăreşte "%".

%n - nu se realizează conversie de argument; numărul de caractere scrise până în acel moment este scris în argument. Există doi modificatori de format care permit funcţiei fprintf() să afişeze întregii long şi short. Aceşti modificatori se pot aplică caracterelor de conversie d, i, o, u şi x, precedându-i pe aceştia (exemplu: %ld, %li, %lu). Modificatorul l poate prefixa şi caracterele de conversie e, f şi g şi indică faptul că numerele tiparite sunt de tip double. Modificatorul h comandă funcţiei fprintf() să afişeze short int. Atunci %hu va preciza că data este de tip short unsigned int.] b) Funcţia printf() Forma funcţiei : int printf("format", lista-argumente)

Funcţia printf() este echivalentă cu : fprintf(stdout, "format", lista_argumente)

Exemplu: printf() ieşire

("%-5.2f", 123.456) 123.45

("%5.2f", 3.4565) 3.45

("%10s", "hello") hello

("%-10s", "hello") hello

(%5.7s", "123456789") 1234567

Exemplu de utilizare a functiei fprintf. /* Acest program foloseste fprintf pentru scrierea datelor cu diferite formate intr-un fisier si apoi tipareste fisierul folosind functia sistem system ce apeleaza comanda TYPE a sistemului de operare */ #include <stdio.h>

#include <process.h>

FILE *stream;

void main( void )

{ int i = 10;

double fp = 1.5;

char s[] = "this is a string";

Page 232: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

226

char c = '\n';

stream = fopen( "fprintf.out", "w" );

fprintf( stream, "%s%c", s, c );

fprintf( stream, "%d\n", i ); fprintf( stream, "%f\n", fp );

fclose( stream );

system( "type fprintf.out" );}

10.4.8. Intrări cu format Funcţiile de tip scanf() realizează conversiile de intrare cu format a) Funcţia fscanf() Forma acestei funcţii este: int fscanf(FILE *fp, "format", lista_argumente) Funcţia fscanf() citeşte din fişierul indirectat prin "fp" sub controlul formatului "format" şi atribuie valorile citite argumentelor următoare, fiecare argument trebuind să fie un pointer. Funcţia întoarce EOF dacă se detectează sfârşitul de fişier sau apare o altă eroare înainte de orice conversie. În caz contrar, funcţia întoarce numărul de elemente care au primit valori. Şirul "format" poate conţine: - specificatori de conversie, constând dintr-un caracter %, un caracter opţional de suprimare a atribuirii; un număr opţional care indică lungimea câmpului, un caracter opţional h, l sau L, care indică lungimea argumentului şi un caracter de conversie; - spaţii sau caractere HT sau VT care se ignoră; - caractere obişnuite (diferite de %) care indică următorul caracter diferit de caracterele albe cu care începe fişierul de intrare. Un câmp de intrare se defineşte ca un şir de caractere diferite de cele albe şi se întinde până la următorul caracter alb (spaţiu, tab-uri, CR, LF, VT, FF). Rezultatul conversiei unui câmp de intrare este plasat în variabilă indicată de argumentul corespunzător din lista de argumente. Dacă se indică suprimarea atributului prin "*" ca în %*s, câmpul de intrare este ignorat, fără a se face nici o atribuire. Descriptorii de conversie utilizaţi în C pentru citire sunt:

%c - citeşte un singur caracter; caracterele următoare sunt plasate în tablourile indicate, respectându-se numărul de caractere indicat de lungimea câmpului; implicit este 1. Nu se adaugă '\0'. %d - citeşte un număr întreg zecimal. %u - citeşte un număr întreg zecimal fără semn.

Page 233: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

227

%i - citeşte un număr întreg (intregul poate fi octal, cu 0 la început, sau hexazecimal, cu 0x sau 0X la început). %o - întreg octal (cu sau fără zero la început). %x - întreg hexazecimal (cu sau fără 0x sau 0X la început). %s - şir de caractere diferite de caracterele albe, indicând spre un tablou de caractere destul de mare pentru a memora şirul şi caracterele terminator '\0' care se va adauga. %e, %f, %g - numere zecimale în virgulă mobilă. %p - citeşte valoarea pointerului. %n - se scrie în argument numerele de caractere citite până în acel moment. Nu se citeşte nimic din intrare. %h - citeşte un întreg scurt. Un caracter obişnuit în şirul "format" determină ca funcţia fscanf() să citească un caracter ce coincide cu cele din "format". De exemplu, "%d, %d" face că fscanf() să citească un întreg, apoi caracterul "," şi apoi un alt întreg. Dacă calculatorul nu găseşte caracterul specificat, fscanf() va fi încheiată. Toate variabilele menite să primească valori prin fscanf() trebuie să fie transmise prin adresele lor. Aceasta înseamnă că toate argumentele trebuie să fie pointeri la variabilele utilizate ca argumente. b) Funcţia scanf() Forma funcţiei:

int scanf("format", lista-argumente)

Funcţia scanf() este echivalenta cu: fscanf(stdin, "format", lista-argumente)

Exemple: scanf ("%d", &count); /* se citeşte un întreg în

variabilă count */

scanf ("%s", address); /* se citeşte un şir de caractere

în vectorul address */

scanf ("%d %d", &r, &c); /* se citesc doua numere

separate prin spaţiu, tab sau linie noua */ Un * plasat între % şi caracterul de conversie, va suspenda atribuirea datei citite. Astfel, instrucţiunea : scanf("%d%*c%d", &x, &y);

face ca, dacă de la tastatură se introduce 10/20, 10 să fie atribuit lui x, %*c este ignorat (nu este atribuit), iar 20 se atribuie lui y. Instrucţiunea : scanf("%20s", sir);

Page 234: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

228

citeşte nu mai mult de 20 caractere în variabilă şir. Dacă se introduce un şir de mai mult de 20 caractere, vor fi reţinute numai primele 20, iar restul se pierd. Pentru caracterele rămase se poate apela din nou funcţia scanf() sub forma : scanf("%s", sir);

care va plasa restul caracterelor tot în "şir". Dacă de la tastatura se introduce 10#20, instrucţiunea : scanf("%s#%s", &x, &y);

va plasa 10 în x şi 20 în y. Instrucţiunea : scanf("%s ", name);

nu se încheie decât dacă după ultimul caracter se introduce un spaţiu. Exemplu de utilizare a funcţiilor fscanf şi fprintf. /* Acest program scrie date cu format cu printf intr-un fisier. Apoi foloseste fscanf pentru a citi datele din fisier */ #include <stdio.h>

FILE *stream;

void main( void )

{ long l;

float fp;

char s[81];

char c;

stream = fopen( "fscanf.out", "w+" );

if( stream == NULL )

printf( "Fisierul fscanf.out nu a fost deschis\n" );

else {

fprintf( stream, "%s %ld %f%c", "a-string",

65000, 3.14159, 'x' );

/* Seteaza pointerul la inceputul fisierului: */

fseek( stream, 0L, SEEK_SET );

/* Citeste datele inapoi din fisierul disc: */

fscanf( stream, "%s", s );

fscanf( stream, "%ld", &l );

fscanf( stream, "%f", &fp );

fscanf( stream, "%c", &c );

/* Tipareste datele citite din fisier: */

printf( "%s\n", s ); printf( "%ld\n", l );

printf( "%f\n", fp );

printf( "%c\n", c );

fclose( stream ); }}

10.4.9. Funcţii de citire şi scriere a caracterelor a) Funcţia fgetc() int fgetc(FILE *fp)

Page 235: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

229

Funcţia fgetc() întoarce următorul caracter al fişierului indirectat cu "fp", caracter de tip unsigned char (convertit la int) sau EOF dacă s-a detectat sfârşitul de fişier sau a apărut o eroare. Exemplu de utilizare a funcţiei fgetc(). /* Acest program foloseste getc pentru a citi 80 de caractere dintr-un fisier si apoi le plaseaza dintr-un buffer la intrarea standard */ #include <stdio.h>

#include <stdlib.h>

void main( void )

{ FILE *stream;

char buffer[81];

int i, ch;

/* Deschide fisierul pentru a citi o inregistrare */

if( (stream = fopen( "fgetc.c", "r" )) == NULL )

exit( 0 ); /* Citeste primele 80 de caractere si le plaseaza in

"buffer": */

ch = fgetc(stream);

for(i=0;(i<80) && (feof(stream)==0); i++ )

{ buffer[i] = (char)ch;

ch = fgetc( stream ); }

/* Adauga null la sfarsitul fisierului */

buffer[i] = '\0';

printf( "%s\n", buffer );

fclose( stream );}

b) Funcţia getc() int getc (FILE *fp)

Această funcţie este identică cu fgetc() cu deosebirea că este o macrodefiniţie, putând să evalueze fişierul mai mult decât o dată. Observaţie: "fp" este un pointer-fişier returnat de funcţia fopen(). Exemplu: Pentru a citi un fişier text până la întâlnirea indicatorului de sfârşit de fişier se scrie: ch = getch (fp);

while (ch != EOF) {

ch = getc (fp); }

c) Funcţia getchar() int getchar(void)

Funcţia getchar() este echivalentă cu getc (stdin) . Dezavantajul funcţiei getchar() este că această poate păstra în bufferul de intrare nu unul, ci mai multe caractere, primul caracter fiind preluat după apasarea tastei CR. d) Funcţiile getche() şi getch() int getche(void)

Page 236: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

230

int getch(void)

Funcţiile introduc un caracter de la tastatură. Funcţiile asteaptă până se apasă o tastă şi apoi întorc valoarea acesteia. Funcţia getche() preia un caracter cu "ecou" iar getch() preia caracterul fără ecou. e) Funcţia gets()

char *gets(char *s)

Funcţia gets() citeşte un şir de caractere introduse de la tastatură şi îl plasează în vectorul indirectat prin s. §irul se termină cu '\n' ce va fi înlocuit cu '\0'. Funcţia întoarce vectorul s sau EOF, în caz de eroare. f) Funcţia fgets() char *fgets(char *s, int n, FILE *fp)

Funcţia fgets() citeşte în tabloul s cel mult n-1 caractere, oprindu-se dacă a detectat NL (New Line) care este inclus în tablou, înlocuit prin'\0'. Funcţia întoarce tabloul s, sau NULL, dacă apare o eroare sau sfârşit de fişier. Exemplu de folosire a funcţiei fgets. /* Acest program utilizeaza fgets pentru afisarea unei linii dintr-un fisier la display */ #include <stdio.h>

void main( void )

{ FILE *stream;

char line[100];

if( (stream = fopen( "fgets.c", "r" )) != NULL )

{ if( fgets( line, 100, stream ) == NULL)

printf( "fgets error\n" );

else printf( "%s", line);

fclose( stream ); }} a') Funcţia fputc() int fputc(int ch, FILE *fp)

Funcţia fputc() scrie caracterul "ch" convertit la unsigned char, în fişierul indirectat prin "fp". Întoarce valoarea caracterului scris, sau EOF, dacă apare vreo eroare. Exemplu de utilizare a funcţie fputc. /* Acest program foloseste fputc si _fputchar pentru a trimite un sir de caractere la stdout. */ #include <stdio.h>

void main( void )

{ char strptr1[] = "Test pentru fputc !!\n";

char strptr2[] = "Test pentru _fputchar!!\n";

char *p;

/* Tipareste linia folosind fputc. */

Page 237: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

231

p = strptr1;

while((*p != '\0') && fputc(*(p++), stdout)!=EOF);

/* Identic cu _fputchar. (Aceasta functie nu este

compatibila ANSI */ p = strptr2;

while((*p != '\0') && _fputchar(*(p++))!=EOF);} În general, funcţiile care încep cu _ (subliniere) dar şi cu f (de la

file) sunt destinate lucrului cu interfeţele standard stdin şi stdout. b') Funcţia putc() int putc(int ch, FILE *fp)

Funcţia putc() este echivalenta cu fputc() cu excepţia că este o macrodefinitie, putând evalua fişierul mai mult decât o dată. c') Funcţia putchar() int putchar(int ch)

Funcţia putchar(ch) este echivalenta cu putc (ch, stdout). d') Funcţia de la punctul d) nu are un corespondent pentru ieşire. e') Funcţia puts() int puts(const char *s)

Funcţia puts() scrie şirul "s" şi NL în "stdout". Întoarce EOF, dacă apare o eroare, sau o valoare nenegativă în caz contrar. f') Funcţia fputs() int fputs(const char *s,FILE *fp)

Funcţia fputs() scrie şirul "s" (care nu este neapărat necesar să conţină '\n') în fişierul "fp". Întoarce EOF, în caz de eroare, o valoare nenegativă, în caz contrar. Spre exemplu,

/* Acest program foloseste fputs pentru a scrie o linie la terminalul standard */

#include <stdio.h>

void main( void )

{ fputs( "Hello world from fputs.\n", stdout );}

Funcţia ungetc() int ungetc(int ch,FILE *fp)

Funcţia ungetc() pune argumentul "ch" inpoi în fişier, de unde va fi preluat la următoarea citire. Se poate pune inapoi în fişier doar un singur caracter. Marcajul EOF nu poate fi pus înapoi. Funcţia întoarce caracterul ce trebuie pus, sau EOF, în caz de eroare. Funcţiile getw() şi putw() Aceste funcţii se folosesc pentru a citi, respectiv a scrie întregi dintr-un sau într-un fişier disc. Aceste funcţii lucrează exact că funcţiile getc() şi putc(). Exemplu de utilizare a functiilor fprintf() şi fscanf() :

Page 238: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

232

Programul permite actualizarea unei agende telefonice. # include "stdio.h"

void ad_num(void); /*prototipul functiilor */

void cauta(void);

char menu(void);

void main() {

char choice;

do { choice = menu();

switch (choice) {

case 'a' : ad_num(); break;

case 'c' : cauta(); break; }

} while (choice != 'q'); }

char menu() {/* Afiseaza meniul si preia optiunea */

char ch; do { printf ("A(dauga), C(auta), Q(uit) : ");

ch = tolower (getche());

printf ("\n");

} while (ch != 'q' && ch != 'a' && ch != 'c');

return ch; }

void ad_num() /* Adauga un nou numar */

{FILE *fp;

char name[80];

int a_code, schimb, numar;

if ((fp = fopen ("telefon", "a")) == NULL) {

printf ("Cannot open file \n"); exit (1);}

printf("Introduceti numele si numarul: ");

fscanf(stdin,"%s%d%d%d",nume,&a_code,&schimb, &numar);

fscanf(stdin,"%*c"); /* inlatura CR din sirul de

intrare */

/* Se scrie in fisier */

printf("%d",fprintf(fp,"%s %d %d %d\n", nume, a_cod,

schimb, numar));

fclose (fp); }

void cauta() /* Cauta un numar dandu-se un nume */

{ FILE *fp;

char nume[80], nume2[80];

int a_code, schimb, numar;

/* Se deschide fisierul pentru citire */

if ((fp = fopen ("telefon", "r")) == NULL) {

printf("Cannot open file\n "); exit (1); } printf ("Nume ?"); gets (nume);

/* Se cauta numarul */

while (!feof (fp)) {

fscanf(fp,"%s%d%d%d", nume2, &a_cod, &schimb, &numar);

if (!strcmp(nume, nume2)) { printf("%s : (%d) %d -

%d\n", nume, a_code, schimb, numar);

break;} }

fclose (fp);}

Page 239: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

233

Capitolul XI

UTILIZAREA ECRANULUI ÎN MOD TEXT

Biblioteca standard a limbajului C şi C++ conţine funcţii pentru

gestiunea ecranului. Acesta poate fi gestionat în două moduri: în mod text sau în mod grafic.

Prezentăm funcţiile standard mai importante utilizate la gestiunea ecranului în mod text. Toate funcţiile standard de gestiune a ecranului în mod text au prototipurile în fişierul antet <conio.h>.

Modul text presupune că ecranul este compus dintr-un număr de linii şi un număr de coloane. În mod curent se utilizează 25 de linii a 80 de coloane fiecare, deci ecranul are o capacitate de 25*80=2000 de caractere.

Poziţia pe ecran a unui caracter se defineşte printr-un sistem de două coordonate întregi (x,y) unde:

x este numărul coloanei în care este situat caracterul y este numărul liniei în care este situat caracterul Colţul din stânga sus al ecranului are coordonatele (1,1), iar

colţul din dreapta jos are coordonatele (80,25). În mod implicit, funcţiile de gestiune a ecranului în mod text au

acces la tot ecranul. Accesul poate fi însă limitat la o parte din ecran utilizând aşa numitele ferestre. Fereastra este o parte a ecranului în formă de dreptunghi şi care poate fi gestionată separat de restul ecranului.

Atributele unui caracter de pe ecran sunt următoarele: - coordonatele x şi y; - culoarea caracterului; - culoarea fondului pe care este afişat caracterul; - clipirea caracterului.

11.1. Setarea ecranului în mod text

Se realizează cu ajutorul funcţiei textmode care are următorul

prototip: void textmode (int modtext)

Page 240: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

234

unde modtext poate fi exprimat simbolic sau numeric în modul următor:

Modul text Constantă simbolică Valoare Caractere albe pe fond negru; 40 de coloane

BW40 0

Color 40 de coloane C40 1 Caractere albe pe fond negru; 80 de coloane

BW80 2

Color 80 de coloane C80 3 Monocrome 80 de coloane MONO 7 Color cu 50 de linii pentru placa VGA

C4350 64

Modul precedent LASTMODE -1 Modul MONO se poate seta pe un adaptor monocolor, în timp

ce celelalte moduri se pot seta pe adaptoare color.

11.2. Definirea unei ferestre

După setarea ecranului în mod text, este activ tot ecranul şi acesta are caracteristicile indicate în paragraful precedent. De multe ori însă se doreşte partajarea ecranului în zone care să poată fi gestionate în mod independent. Acest lucru poate fi realizat cu ajutorul ferestrelor. O fereastră se defineşte cu ajutorul funcţiei window care are următorul prototip:

void window (int x1, int y1, int x2, int y2);

unde: (x1,y1) – reprezintă coordonatele colţului stânga sus ; (x2,y2) – reprezintă coordonatele colţului dreapta jos. La un moment dat este activă o singură fereastră şi anume acea

definită la ultimul apel al funcţiei window. Dacă parametri de la apelul funcţiei window sunt eronaţi, aceasta nu are nici un efect.

11.3. Ştergerea unei ferestre

O fereastră activă se şterge utilizând funcţia clrscr care are

următorul prototip: void clrscr(void) După apelul acestei funcţii, fereastra activă (sau tot ecranul dacă

nu s-a definit în prealabil o fereastră cu funcţia window) devine vidă. Fondul ei are culoarea definită prin culoarea de fond curentă.

Page 241: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

235

Funcţia clrscr poziţionează cursorul pe caracterul din stânga sus al ferestrei active, adică în poziţia de coordonate (1,1).

11.4. Deplasarea cursorului

Cursorul poate fi plasat pe un caracter al ferestrei active

folosind funcţia gotoxy ce are următorul prototip: void gotoxy (int x, int y);

unde (x, y) reprezintă coordonatele caracterului pe care se plasează cursorul. Dacă la apelul funcţiei coordonatele sunt definite în afara ferestrei active, funcţia este ignorată.

Poziţia cursorului din fereastra activă poate fi determinată cu ajutorul funcţiilor wherex şi wherey care returnează numărul coloanei, respectiv liniei, din fereastra activă pe care se află cursorul, şi care au următoarele prototipuri:

int wherex(void);

int wherey(void);

În cazul în care se doreşte ascunderea cursorului (facerea invizibilă a cursorului) se utilizează o secvenţă ce utilizează funcţia geninterrupt: {

_AH=1;

_CH=0x20;

geninterrupt(0x10); }

Cursorul poate fi rafiaşat utilizând următoarea secvenţă:

{ _AH=1;

_CH=6;

_CL=7;

geninterrupt(0x10);}

11.5. Setarea culorilor

Culoarea fondului se setează cu ajutorul funcţiei textbackground ce are următorul prototip:

void textbackground(int culoare);

unde culoare este un întreg în intervalul [0, 7] şi are semnificaţia din tabelul de mai sus.

Pot fi setate ambele culori precum şi clipirea caracterului folosind funcţia textattr ce are următorul prototip:

void textattr (int atribut)

unde atribut se defineşte cu ajutorul relaţiei: atribut=16*culoare_fond+culoare_caracter+clipire;

Page 242: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

236

Culoarea caracterelor se setează cu ajutorul funcţiei textcolor ce are următorul prototip:

void textcolor(int culoare);

unde culoare este un întreg în intervalul [0,15] a cărui semnificaţie este explicată de tabelul următor:

Culoare Constantă simbolică Valoare Negru BLACK 0

Albastru BLUE 1 Verde GREEN 2

Turcoaz CYAN 3 Roşu RED 4

Purpuriu MANGETA 5 Maro BROWN 6

Gri deschis LIGHTGRAY 7 Gri închis DARKGRAY 8

Albastru deschis LIGHTBLUE 9 Verde deschis LIGHTGREEN 10

Turcoaz deschis LIGHTCYAN 11 Roşu deschis LIGHTRED 12

Purpuriu deschis LIGHTMANGETA 13 Galben YELLOW 14

Alb WHITE 15 Clipire BLINK 128

11.6. Funcţii pentru gestiunea textelor

Pentru afişarea caracterelor se pot folosi funcţiile:

- int putch (int c); – afişează un singur caracter; - int cputs (const char *str); – afişează un şir de caractere în

mod similar funcţiei puts; - int cprintf (const char *format); – afişează date sub controlul

formatelor în mod similar funcţiei printf. - void clreol (void); - şterge sfârşitul liniei începând cu poziţia

cursorului; - void delline (void); - şterge toată linia pe care este poziţionat

cursorul; - int gettext (int left, int top, int right, int bottom, void

*destination); - copiază textul cuprins în dreptunghiul definit de coordonatele (left, top) – stânga sus şi (right, bottom) – dreapta jos la adresa de memorie indicată de pointerul destination;

Page 243: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

237

- int puttext( int left, int top, int right, int bottom, void *source ); - citeşte textul cuprins în dreptunghiul definit de coordonatele (left, top) – stânga sus şi (right, bottom) – dreapta jos de la adresa de memorie indicată de pointerul source;

- int movetext( int left, int top, int right, int bottom, int destleft, int desttop ); - mută textul cuprins în dreptunghiul definit de coordonatele (left, top) – stânga sus şi (right, bottom) – dreapta jos în dreptunghiul cu coordonatele colţului din stânga sus (destleft, desttop);

- void insline (void); - inserează o linie vidă în fereastra activă; - int getch (void); - citeşte un caracter fără ecou de la tastatură,

adică după ce este citit caracterul nu mai este afişat pe ecran; funcţia returnează codul ASCII al caracterului citit de la tastatură.

- int getche (void); - citeşte un caracter cu ecou de la tastatură, adică după ce este citit caracterul este afişat automat pe ecran; funcţia returnează codul ASCII al caracterului citit de la tastatură.

- int kbhit (void); - controlează dacă s-a tastat ceva la tastatură. Dacă a fost apăsată o tastă se returnează o valoare diferită de zero, altfel se returnează valoarea 0. Exemplu: Următorul program desenează o fereastră şi scrie un număr în aceasta. #include <stdio.h>

#include <stdlib.h>

#include <conio.h>

#include <alloc.h>

#include <dos.h>

#define MAX 100

#define SIMPLU 1

#define DUBLU 2

typedef struct{

int x,y,u,v;

void *zonfer;

}ELEM;

ELEM *stiva[MAX];

int istiva;

void orizontal(int,int);

void vertical(int,int,int,int);

void fereastra(int st,int sus,int dr,int jos,int fond,int

culoare,

int chenar,int n)

//Afiseaza o fereastra limitata de un chenar

{ extern ELEM *stiva[];

extern int istiva;

Page 244: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

238

//memoreaza partea din ecran pe care se va afisa

fereastra

if(istiva==MAX){ printf("\nPrea multe ferestre!");

exit(1); }

if ((stiva[istiva]=(ELEM *)farmalloc(sizeof(ELEM)))==0){

printf("\nMemorie insuficienta\n");

exit(1); }

stiva[istiva]->x=st;

stiva[istiva]->y=sus;

stiva[istiva]->u=dr;

stiva[istiva]->v=jos;

if((gettext(st,sus,dr,jos,stiva[istiva]->zonfer))==0){

printf("\nEroare la memorarea ecranului!");

exit(1); }

istiva++;

//Activeaza fereastra si o afiseaza pe ecran

window(st,sus,dr,jos);

textattr(16*fond+culoare);

clrscr();

//Trasare chenar

if (chenar){

textcolor(WHITE);

highvideo();

switch(chenar){

case SIMPLU:

putch(218);

break; case DUBLU:

putch(201);

break; }

orizontal(dr-st-2,chenar);

switch(chenar){

case SIMPLU:

putch(191);

break;

case DUBLU:

putch(187);

break; }

vertical(jos-sus,1,2,chenar);

gotoxy(1,jos-sus+1);

switch(chenar){

case SIMPLU:

putch(192);

break;

case DUBLU:

putch(200);

Page 245: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

239

break; }

orizontal(dr-st-2,chenar);

vertical(jos-sus-1,dr-st,2,chenar); gotoxy(dr-st,jos-sus+1);

switch(chenar){

case SIMPLU:

putch(217);

break;

case DUBLU:

putch(188);

break; }

normvideo();

textattr(16*fond+culoare); }

gotoxy(3,3);

cprintf("%d",n); //Ascunde cursorul

_AH=1;

_CH=0x20;

geninterrupt(0x10); }

void orizontal(int a,int chenar)

{ while(a--)

switch(chenar){

case SIMPLU:

putch(196);

break;

case DUBLU:

putch(205);

break; } }

void vertical(int a,int col,int lin,int chenar)

{ while(a--) {

gotoxy(col,lin++);

switch(chenar){

case SIMPLU:

putch(179);

break;

case DUBLU:

putch(186);

break; }

} }

void main(void) { clrscr();

fereastra(4,4,60,20,3,10,2,6);

getch(); }

Page 246: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

240

Capitolul XII

UTILIZAREA ECRANULUI ÎN MOD GRAFIC

Pentru aplicaţiile grafice limbajul C pune la dispoziţie peste 60 de funcţii standard ce au prototipul în fişierul graphics.h. În continuare sunt prezentate cele mai importante funcţii ce permit gestiunea ecranului în mod grafic.

12.1. Iniţializarea modului grafic

Pentru a se putea lucra în mod grafic trebuie realizată o

iniţializare utilizând funcţia initgraph. Aceasta poate fi folosită singură sau împreună cu o altă funcţie numită detectgraph care determină parametrii adaptorului grafic. Prototipul ei este:

void far detectgraph(int far *gd, int far *gm);

unde: Pointerul gd păstrează adresa uneia din valorile din tabelul

următor (în funcţie de adaptorul grafic utilizat):

Constantă simbolică Valoare CGA 1

MCGA 2 EGA 3

EGA64 4 EGAMONO 5

IBM8514 6 HERCMONO 7

ATT400 8 VGA 9

PC3270 10

Valorile spre care pointează gd definesc nişte funcţii standard corespunzătoare adaptorului grafic. Aceste funcţii se numesc drivere. Ele se află în subdirectorului BGI. Funcţia detectgraph detectează adaptorul grafic prezent la calculator şi păstrează valoarea corespunzătoare acestuia în zona spre care pointează gd.

Page 247: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

241

Zona spre care pointează gm memorează una din valorile:

Adaptor Constantă simbolică - Valoare

Rezoluţie

CGAC0 – 0 320*200 CGAC1 – 1 320*200 CGAC2 – 2 320*200 CGAC3 – 3 320*200

CGA

CGAHI – 4 640*200

EGALO – 0 640*200 EGA EGAHI – 1 640*350

VGALO – 0 640*200 VGAMED – 1 640*350

VGA

VGAHI – 2 640*480

Modul grafic se defineşte în aşa fel încât el să fie cel mai performant pentru adaptorul grafic curent. Cele mai utilizate adaptoare sunt cele de tip EGA şi VGA.

Apelul funcţiei detectgraph trebuie să fie urmat de apelul funcţiei initgraph. Aceasta setează modul grafic în conformitate cu parametri stabiliţi de apelul prealabil al funcţiei detectgraph şi are următorul prototip:

void far initgraph(int far *gd,int far *gm, int far *cale);

unde: gd şi gm sunt pointeri ce au aceeaşi semnificaţie ca şi în cazul

funcţiei detectgraph; cale este pointer spre şirul de caractere care defineşte calea

subdirectorului BGI care conţine driverele. De exemplu dacă BGI este subdirector al directorului

BORLANDC, atunci se utilizează şirul de caractere: ”C:\\BORLANDC\\BGI”

Exemplu: Pentru setarea în mod implicit a modului grafic se poate utiliza următoarea secvenţă de instrucţiuni:

……………………………

int gd,gm;

detectgraph(&gd,&gm);

initgraph(&gd,&gm, ”C:\\BORLANDC\\BGI”);

……………………………

Doar după apelul funcţiei initgraph pot fi utilizate şi alte funcţii de gestionare a ecranului în mod grafic.

Page 248: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

242

Utilizatorul poate defini el însuşi parametri pentru iniţializarea modului grafic. De exemplu, secvenţa următoare:

……………………………

int gd=1,gm=0;

initgraph(&gd,&gm, ”C:\\BORLANDC\\BGI”);

……………………………

setează modul grafic corespunzător unui adaptor grafic CGA cu rezoluţia 320*200 de puncte.

În afara acestor funcţii mai pot fi utilizate şi următoarele funcţii: void far setgraphmode (int mode) – utilizată pentru setarea

modului grafic unde mode are valorile 0 – 4 pentru VGA, 0-1 pentru EGA, 0 – 2 pentru VGA;

void far retorecrtmode(void) – ce permite revenirea la modul precedent;

void far graphdefaults(void) – repune parametri grafici la valorile implicite;

int far getgraphmode(void) – returnează codul modului grafic; char *far getmodename(int mod) – returnează pointerul spre

numele modului grafic definit de codul numeric mod; char *far getdrivername(void) – returnează pointerul spre

numele drieverului corespunzător adaptorului grafic curent; void far getmoderange(int grafdriv,int far *min, int far *max)

– defineşte valorile minimale şi maximale ale modului grafic utilizat. void far closegraph(void) – se utilizează pentru a ieşi din

modul grafic.

12.2. Gestiunea culorilor

Adaptoarele grafice sunt prevăzute cu o zonă de memorie în care se păstrează date specifice gestiunii ecranului. Această zonă de memorie poartă denumirea de memorie video.

În mod grafic, ecranul se consideră format din puncte luminoase numite pixeli. Poziţia pe ecran a unui pixel se defineşte prin două valori întregi: (x,y)

unde: x – defineşte coloana în care este afişat pixelul; y – defineşte linia în care este afişat pixelul. Fiecărui pixel îi corespunde o culoare ce este pătrată în

memoria video. Numărul maxim de culori care pot fi afişate cu ajutorul unui adaptor EGA este 64.. Culorile se codifică prin numere întregi din intervalul [0, 63] şi prin constante simbolice. Cele 64 de

Page 249: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

243

culori nu pot fi afişate simultan. În cazul adaptorului EGA pe ecran se pot afişa cel mult 16 culori ce formează o paletă. Paleta implicită este dată de tabelul următor:

Denumire simbolică Valoare BLACK 0 BLUE 1

GREEN 2 CYAN 3 RED 4

MANGETA 5 BROWN 6

LLIGHTGRAY 7 DARKGRAY 8 LIGHTBLUE 9

LIGHTGREEN 10 LIGHTCYAN 11 LIGHTRED 12

LIGHTMANGETA 13 YELLOW 14 WHITE 15

În mod implicit, culoarea fondului este întotdeauna cea

corespunzătoare indicelui zero, iar culoarea pentru desenare este cea corespunzătoare indicelui 15.

Pentru controlul culorilor pot fi utilizate următoarele funcţii: void far setbkcolor(int culoare) – modifică culoarea

fundalului; int far getbkcolor(void) – returnează indexul din tabloul care

defineşte paleta pentru culoarea fundalului; void far setcolor(int culoare) – setează culoarea utilizată

pentru desenare; int far getcolor(void) – returnează indexul din tabloul care

defineşte paleta pentru culoarea de desenare; void far setpalette(int index,int cod) – setează o nouă culoare

în paleta ce este utilizată la colorare (index ia valori între [0, 15] iar cod între [0, 63]);

void far setallpalette(struct palettetype far* paleta) – modifică mai multe culori din paletă. Palettetype este o structură definită ca mai jos:

struct palettetype {

unsigned char size;

Page 250: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

244

signed char colors[MAXCOLORS+1];

};

unde size – este dimensiunea paletei; colors – este un tablou ale cărui elemente au ca valori

codurile culorilor componente ale paletei care se defineşte. Modificarea paletei curente cu ajutorul funcţiei setpalette sau

setallpalette conduce la schimbarea corespunzătoare a culorilor afişate pe ecran în momentul apelului funcţiilor respective.

void far getpalette(struct palettetype far* paleta) – determină codurile culorilor componente ale paletei curente;

int far getmaxcolor(void) – returnează numărul maxim de culori diminuat cu 1;

int far getpalettesize(void) – returnează numărul culorilor componente ale paletei.

12.3. Setarea ecranului

În mod grafic, ecranul se compune din n*m puncte luminoase

(pixeli), adică pe ecran se pot afişa m linii a n pixeli fiecare. Poziţia unui pixel este dată de două numere întragi (x,y) numite coordonatele pixelului. Pixelul aflat în stânga sus are coordonatele (0,0). Coloanele se numerotează de la stânga la dreapta, iar liniile de sus în jos.

Informaţii referitoare la ecran pot fi obţinute cu ajutorul următoarelor funcţii:

int far getmaxx(void) – returnează coordonta maximă pe orizontală;

int far getmaxy(void) – returnează coordonta maximă pe verticală;

int far getx(void) – returnează poziţia pe orizontală a pixelului curent;

int far gety(void) – returnează poziţia pe verticală a pixelului curent.

12.4. Utilizarea textelor în mod grafic

Afişarea textelor în modul grafic presupune definirea unor

parametri care pot fi controlaţi prin intermediul funcţiilor descrise în continuare:

a) void far settextstyle(int font,int direcţie,int charsize) unde:

font – defineşte setul de caractere şi poate lua următoarele valori:

Page 251: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

245

Constantă simbolică Valoare DEFAULT_FONT 0 TRIPLEX_FONT 1 SMALL_FONT 2

SANS_SERIF_FONT 3 GOTHIC_FONT 4

direcţie – defineşte direcţia de scris a textului, astfel:

- de la stânga la dreapta: HORIZ_DIR; - de jos în sus: VERT_DIR.

charsize – defineşte dimensiunea caracterului în pixeli, astfel: Valoarea

parametrului Matricea utilizată pentru

afişarea caracterului (în pixeli) 1 8*8 2 16*16 3 24*24

…. …….. 10 80*80

b) void far settextjustify(int oriz, int vert) – defineşte

cadrajul textului; oriz – defineşte încadrarea pe orizontală, astfel:

- în stânga: LEFT_TEXT; - în centru: CENTER_TEXT; - în dreapta: RIGHT_TEXT.

vert – defineşte încadrarea pe verticală, astfel: - marginea inferioară: BOTTOM_TEXT; - în centru: CENTER_TEXT; - marginea superioară: TOP_TEXT.

După setarea acestor parametri pot fi afişate texte folosind funcţiile outtext şi outtextxy care au următoarele prototipuri:

void far outtext(char far* şir) , unde şir este un pointer spre zona de memorie în care se păstrează caracterele de afişat, afişează caracterele începând cu poziţia curentă de pe ecran;

void far outtextxy(int x,int y,char far* şir) , unde şir este un pointer spre zona de memorie în care se păstrează caracterele de afişat, x,y defineşte poziţia de pe ecran unde se face afişarea.

Dimensiunile în pixeli ale unui şir de caractere se pot determina utilizând funcţiile textheight şi textwidth:

Page 252: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

246

void far textheight(char far* şir) – returnează înălţimea în pixeli a şirului păstrat în zona spre care pointează şir,

void far textwidth(char far* şir) – returnează lălţimea în pixeli a şirului păstrat în zona spre care pointează şir.

12.5. Gestiunea imaginilor

În modul grafic, ecranul poate fi partajat în mai multe părţi ce

pot fi gestionate independent. Aceste părţi se numesc ferestre grafice. Următoarele funcţii sunt utilizate pentru prelucrarea ferestrelor grafice:

void far setviewport(int st, int sus, int dr, int jos, int d) – defineşte o fereastră grafică, unde: - (st,sus) – coordonatele colţului stânga sus al ferestrei; - (dr,jos) – coordonatele colţului dreapta jos al ferestrei; - d – indicator cu privire la decuparea desenului. Dacă d are

valoarea 1, atunci funcţiile de afişare a textelor şi de desenare nu pot scrie sau desena în afara limitelor ferestrei.

void far clearviewport(void) – şterge fereastra activă; după apelul acestei funcţii, toţi pixelii ferestrei au aceeaşi culoare, şi anume culoarea de fond, iar poziţia curentă a cursorului este punctul de coordonate relative (0,0);

void far cleardevice(void) – şterge tot ecranul iar poziţia curentă a cursorului este colţul din stânga sus al ecranului;

void far getviewsettings(struct viewporttype far* fereastra) – returnează parametri ferestrei active.

Imaginea ecranului se păstrează în memoria video a adaptorului grafic şi formează o pagină. Funcţiile următoare sunt utilizate pentru gestionarea paginilor

void far setactivepage(int nrpag) – activează o pagină al cărei număr este specificat de parametrul nrpag;

void far setvisualpage(int nrpag) – cu toate că în mod normal este vizualizată pe ecran pagina activă, utilizatorul are posibilitatea de a vizualiza altă pagină decât cea activă utilizând această funcţie (această funcţie poate fi utilă pentru animaţie);

void far getimage(int st, int sus, int dr, int jos,void far* zt) – salvează o zonă dreptunghiulară de pe ecran, unde: - (st,sus) – coordonatele colţului stânga sus a zonei de pe ecran ce

se salvează;

Page 253: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

247

- (dr,jos) – coordonatele colţului dreapta jos a zonei de pe ecran ce se salvează;

- zt – pointer spre zona de memorie în care se salvează imaginea de pe ecran.

unsigned far imagesize(int st, int sus, int dr, int jos) – determină dimensiunea unei zone dreptunghiulare de pe ecran, unde: - (st,sus) – coordonatele colţului stânga sus a zonei de pe ecran; - (dr,jos) – coordonatele colţului dreapta jos a zonei de pe ecran.

void far putimage(int st, int sus, int jos,void far* zt, int op) – afişează oriunde pe ecran o zonă dreptunghiulară salvată cu funcţia getimage, unde: - (st,sus) – coordonatele colţului stânga sus a zonei de pe ecran ce

se salvează; - zt – pointer spre zona de memorie în care se păstrează imaginea

ce se va afişa pe ecran; - op – defineşte operaţia între datele aflate în zona spre care

pointează zt şi cele existente pe ecran în zona dreptunghiulară definită de parametri st, sus. Parametrul op se defineşte astfel:

Constantă simbolică

Valoare Acţiune

COPY_PUT 0 copiază imaginea din memorie pe ecran XOR_PUT 1 „sau exclusiv” între datele de pe ecran şi cele din

memorie OR_PUT 2 „sau” între datele de pe ecran şi cele din memorie

AND_PUT 3 „şi” între datele de pe ecran şi cele din memorie NOT_PUT 4 copiază imaginea din memorie pe ecran

completând datele aflate în memorie

12.6. Desenarea şi colorarea figurilor geometrice

Biblioteca standard pune la dispoziţia utilizatorului o serie de funcţii care permit desenarea şi colorarea unor figuri geometrice:

void far putpixel(int x, int y, int culoare) – afişează un pixel pe ecran în punctul de coordonate (x,y) (relativ la fereastra activă) şi având culoarea culoare;

unsigned far getpixel(int x, int y) – determină culoarea unui pixel aflat pe ecran în poziţia (x,y);

void far moveto(int x, int y) – mută cursorul în dreptul pixelului de coordonate (x,y);

Page 254: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

248

void far moverel(int dx, int dy) – mută cursorul în dreptul pixelului de coordonate (x+dx,y+dy), unde (x,y) reprezintă coordonatele pixelului curent;

void far line(int xi, int yi, int xf, int yf) – trasează un segment de dreaptă între punctele de coordonate (xi,yi) şi (xf,yf);

void far lineto(int x, int y) – trasează un segment de dreaptă între punctul curent şi punctul de coordonate (x,y);

void far linerel(int dx, int dy) – trasează un segment de dreaptă între punctul curent şi punctul de coordonate (x+dx,y+dy), unde (x,y) sunt coordonatele punctului curent;

void far arc(int xcentru, int ycentru, int unghistart, int unghifin,int raza) – trasează un arc de cerc, unghiurile fiind exprimate în grade sexagesimale;

void far circle(int xcentru, int ycentru, int raza) – trasează un cerc, cu (xcentru,ycentru) coordonatele centrului şi raza raza acestuia;

void far ellipse(int xcentru, int ycentru, int unghistart, int unghifin,int semiaxamare, int semiaxamică) – trasează un arc de elipsă cu centrul în punctul de coordonate (xcentru,ycentru), semiaxa mare definită de parametrul semiaxamare iar semiaxa mică definită de parametrul semiaxamică;

void far rectangle(int st, int sus, int dr, int jos) – trasează un dreptunghi definit de colţurile diagonal opuse;

void far drawpoly(int nr, int far* tabpct) – trasează o linie polignală, parametrul nr specificând numărul de laturi iar tabpct este un pointer spre un tablou de întregi ce definesc vârfurile liniei poligonale păstrate sub forma: abscisa_i,ordonata_i unde i are valorile 1,2,…., nr+1;

void far setlinestyle(int stil, unsigned şablon, int grosime) – defineşte stilul utilizat pentru trasarea liniilor, unde:

stil – este un întreg din intervalul [0,4] care defineşte stilul liniei conform următorului tabel:

Constantă simbolică Valoare Stil SOLID_LINE 0 Linie continuă

DOTTED_LINE 1 Linie punctată CENTER_LINE 2 Linie întreruptă formată din liniuţe de

două dimensiuni DASHED_LINE 3 Linie întreruptă formată din liniuţe de

aceeaşi dimensiune USERBIT_LINE 4 Stil definit de utilizator prin şablon

Page 255: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

249

şablon – defineşte stilul liniei şi are sens doar când parametrul stil are valoarea 4;

grosime – defineşte lăţimea liniei în pixeli, astfel: NORM_WIDTH – valoarea 1 pixel şi THICK_WIDTH – valoarea 3 pixeli.

void far getlinesettingstype(struct linesettingstype far* linieinfo) – este utilizată pentru a determina stilul curent;

void far bar(int st, int sus, int dr, int jos) – are aceeaşi semnificaţie cu funcţia rectangle însă dreptunghiul este colorat;

void far bar3d(int st, int sus, int dr, int jos, int profunzime, int ind) – funcţia desenează o prismă colorată pentru ind diferit de zero; pentru ind=0, nu se trasează partea de sus a prismei;

void far pieslice(int xcentru, int ycentru, int unghistart, int unghifin,int raza) – desenează un sector de cerc colorat;

void far fillpoly(int nr, int far* tabpct) – desenează un poligon colorat;

void far fillellipse(int xcentru, int ycentru, int semiaxamare, int semiaxamică) – desenează o elipsă colorată;

void far setfillstyle(int haşura, int culoare) – defineşte modul de colorare al figurilor, astfel:

culoare – defineşte culoarea utilizată pentru haşurare; haşura – defineşte haşura utilizată pentru colorare conform

tabelului: Constantă simbolică Valoare

EMPTY_FILL 0 SOLID_FILL 1 LINE_FILL 2

LTSLASH_FILL 3 SLASH_FILL 4

BKSLASH_FILL 5 LTBKSLASH_FILL 6

HATCH_FILL 7 XHATCH_FILL 8

INTERLEAVE_FILL 9 WIDE_DOT_FILL 10

CLOSE_DOT_FILL 11 USER_FILL 12

void far setfillpattern(char far *h_utilizator,int culoare) –

este utilizată pentru a defini o haşură a utilizatorului, astfel:

Page 256: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

250

culoare – defineşte culoarea de haşurare; h_utilizator – este un pointer spre o zonă de memorie care

defineşte haşura utilizatorului; void far getfillsettings(struct fillsettingstype far* stilculoare)

– este utilizată pentru determinarea stilului curent de colorare; void far floodfill(int x, int y, int culoare) – este o funcţie

utilizată pentru colorarea unui domeniu închis, astfel: (x,y) – reprezintă coordonatele unui punct din interiorul

domeniului închis; culoare – defineşte culoarea utilizată la trasarea conturului

figurii (interiorul este colorat în conformitate cu setările efectuate de funcţia setfillstyle). Exemplu: Prezentăm în acest exemplu un model de utilizare a modului grafic pentru trasarea graficelor unor funcţii matematice elementare. #include <stdio.h>

#include <math.h>

#include <graphics.h>

#include <conio.h>

int x,y;

float a,b; void desen(void) //functia care deseneaza axele si

//coloreaza ecranul

{ cleardevice();

setbkcolor(14);

setcolor(12);

line(0,y,2*x,y);

line(x,0,x,2*y);

line(2*x-4,y-4,2*x,y);

line(2*x-4,y+4,2*x,y);

line(x,0,x-4,4);

line(x,0,x+4,4); }

void interval(int l1, int l2) //functia care verifica // daca intervalele pe care sunt definite functiile

// trigonometrice, sunt respectate

{ while ((a<l1)||(b>l2))

{ clrscr();

cleardevice();

setbkcolor(0);

printf("reintroduce-ti intervalul astfel incat sa fie

cuprins intre -1 si 1:\n ");

printf("a=");

scanf("%f",&a);

printf("\n");

Page 257: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

251

printf("b=");

scanf("%f",&b);

printf("\n"); } desen();}

void grafic(float (*trig)(float))//functia care traseaza

// graficul functiilor trigonometrice

{ float ymax,i,i1,h,y0,y1,lx,ly;

h=0.001*(b-a);

if (abs(a)>abs(b)) lx=(x-25)/(abs(a)+1);

else lx=(x-25)/(abs(b)+1);

ymax=0;

for(i=a;i<=b;i+=h)

if (ymax<abs(trig(i))) ymax=abs(trig(i));

if (ymax>y/2) ymax=y-25;

ly=(y-25)/(ymax+1); if (ly>lx) ly=lx;

for(i=a;i<=b;i+=h)

{ y0=trig(i);

i1=i*lx ;

y1=y0*ly;

putpixel(x+i1,y-y1,4);} }

float sinx (float x)

{ return sin(x);}

float cosx(float x)

{ return cos(x);}

float tanx(float x)

{ return tan(x);} float ctanx(float x)

{ return 1/tan(x);}

float acosx(float x)

{ return acos(x);}

float asinx(float x)

{ return asin(x);}

float atanx(float x)

{ return atan(x);}

float actanx(float x)

{ return atan(1/x);}

void main()

{ int p,l,t,dr=DETECT, modgr;

initgraph(&dr,&modgr,"c:\\borlandc\\bgi"); setbkcolor(1);

x=getmaxx()/2,y=getmaxy()/2;

p=0;

while(p==0)

{ setbkcolor(1);

p=1;

printf("1 : sin(x)\n");

Page 258: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

252

printf("2 : cos(x)\n");

printf("3 : tg(x)\n");

printf("4 : ctg(x)\n"); printf("5 : arcsin(x)\n");

printf("6 : arccos(x)\n");

printf("7 : arctg(x)\n");

printf("8 : arcctg(x)\n");

printf("Alegeti nr. corespunzator functiei dorite: ");

scanf("%d",&t);

while(t<1 || t>8 )

{ printf("Reintroducet t-ul cuprins intre 1 si 8\n ");

printf("t=");

scanf("%d",&t);}

printf("Dati intervalul: \n");

do{

printf("a=");

scanf("%f",&a);

printf("\n");

printf("b=");

scanf("%f",&b);

printf("\n");

if (a>b) printf("Reintroduce-ti intervalul astfel incat a

sa fie mai mic ca b:\n ");

} while(a>b);

desen();

switch(t) {

case 1:grafic(sinx); break;

case 2:grafic(cosx); break; case 3:grafic(tanx); break;

case 4:grafic(ctanx); break;

case 5:interval(-1,1);

grafic(asinx); break;

case 6:interval(-1,1);

grafic(acosx); break;

case 7:grafic(atanx); break;

case 8:grafic(actanx); break;

defalut: p=0 ; }

getch();

clrscr();

cleardevice();

setbkcolor(0);

printf("Doriti graficul altei functii? 1-DA 0-NU :");

scanf("%d", &l);

if (l==1){clrscr();cleardevice(); p=0;}}

closegraph(); }

Page 259: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

253

Capitolul XIII

FUNCŢII MATEMATICE Limbajul C conţine mai multe funcţii matematice care utilizează argumente de tip double şi întorc valori de tip double. Aceste funcţii se împart în următoarele categorii: - funcţii trigonometrice; - funcţii hiperbolice; - funcţii exponenţiale şi logaritmice; - alte tipuri. Toate funcţiile matematice sunt incluse în fişierul antet "math.h". Acesta mai conţine o serie de macrodefiniţii cum ar fi EDOM, ERANGE şi HUGE_VAL. Macrodefiniţiile EDOM şi ERANGE se găsesc în fişierul "errno.h" şi sunt constante întregi diferite de zero, utilizate pentru a semnala erorile de domeniu şi de plajă ale funcţiei. HUGE_VAL (aflată tot în "errno.h") este o valoare pozitivă de tip double.

Dacă un argument al unei funcţii matematice nu este în domeniul pentru care a fost definită funcţia, atunci funcţia întoarce 0 şi în domeniul de eroare, "errno" este modificat la EDOM. Dacă o funcţie produce un rezultat prea mare pentru a fi reprezentat printr-un double, apare o depăşire, funcţia returnând HUGE_VAL cu semnul adecvat iar "errno" este modificat la ERANGE. Dacă se produce subdepăşire, funcţia întoarce zero, iar "errno" este modificat la ERANGE în funcţie de implementare.

13.1 Funcţii trigonometrice

- sin(x) , x în radiani - sinusul lui x; - cos(x) , x în radiani - cosinusul lui x. - tan(x) , x în radiani - tangenta lui x; Exemplu: Programul următor afişează valorile sinusului, cosinusului şi tangentei unghiului a[-1,+1] radiani, din 0.1 în 0.1.

# include <math.h>

void main() { double val = -1.0;

Page 260: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

254

do {

printf("sinusul lui %f este %f\n", val, sin(val));

printf("cosinusul lui %f este %f\n",val, cos(val)); printf("tangenta lui %f este %f\n", val, tan(val));

val += 0.1;}

while (val <= 1.0); } 13.2 Funcţii trigonometrice inverse

- asin(x) , cu x [-1,1] - arcsinusul lui x; - acos(x) , cu x [-1,1] - arccosinusul lui x; - atan(x) , x R - arctangenta lui x; - atan(y,x) , - returneaza arctg (y/x). Exemplu: Programul următor afişează valorile arcsinusului, arccosinusului şi arctangentei unghiului a[-1,+1], din 0.1 în 0.1.

# include <math.h>

void main() {

double val = -1.0;

do {

printf("arcsin lui %f este %f\n", val, asin(val));

printf("arccos lui %f este %f\n", val, asin(val));

printf("arctg lui %f este %f\n", val, asin(val));

val += 0.1;

}

while (val <= 1.0); }

13.3 Funcţii hiperbolice

- sinh(x) , x R - sinus hipebolic de x; - cosh(x) , x R - cosinus hipebolic de x; - tanh(x) , x R - tangenta hipebolica de x.

13.4 Funcţii exponenţiale şi logaritmice

- exp(x) , x R - exponentiala lui x. - log(x) , x > 0 - logaritmul natural al lui x; - log10(x) , x > 0 - logaritmul zecimal al lui x.

Exemplu: printf ("Valoarea lui e este: %f", exp(1.0)); Exemplu: Programul următor afişează valorile logaritmului natural şi logaritmului zecimal din 1 în 1 al numerelor de la 1 la 10.

# include <math.h>

void main() {

double val =1.0;

do{printf("%f %f %f\n",val,log(val),log10(val));

Page 261: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

255

val ++; }

while (val < 11.0);}

- pow(x,y); funcţia calculeaza xy. O eroare de domeniu apare dacă x = 0 şi y = 0 sau dacă x < 0 şi y nu este întreg. Exemplu: Programul următor afişeaza primele 11 puteri ale lui 10.

# include <math.h>

void main() { double x =10.0, y = 0.0;

do {

printf ("%f\n", pow(x,y)); y ++;} while (val < 11.0);

}

13.5 Generarea de numere aleatoare

În multe aplicaţii este necesară generarea de numere aleatoare. Pentru asemenea cazuri limbajul C dispune de două funcţii, rand şi random, care returnează numere întregi aleatore.

Funcţia rand are următorul prototip: int rand(void)

şi returnează un număr întreg, aleator, cuprins în intervalul de la 0 la RAND_MAX (valoare definită în fişierul antet stdlib.h).

Funcţia random are prototipul: int random(int val_maxima)

şi returnează un număr întreg, aleator, cuprins în intervalul [0, val_maxima]. Pentru generarea de numere aleatoare în virgulă mobilă se împarte rezultatul funcţiei random la o valoare întreagă. Următorul program exemplifică utilizarea acestor funcţii: Exemplu:

#include <stdio.h>

#include <stdlib.h>

void main(void)

{ int k;

printf(”Valorile furnizate de functia rand\n”); for(k=0;k<100;k++)

printf(”%d ”,rand()); printf(”Valorile furnizate de functia random(100)\n”);

for(k=0;k<100;k++) printf(”%d ”,random(100));

printf(”Valori reale intre 0 si 1\n”);

for(k=0;k<10;k++) printf(”%f ”,random(10)/10.0);

printf(”Valori intregi intre -10 si 10\n”);

for(k=0;k<10;k++) printf(”%d ”,10-random(20));}

Page 262: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

256

13.6 Alte tipuri de funcţii matematice

Nume funcţie Caracterizarea funcţiei

sqrt(x) - radicalul lui x, sqrt(x)= x ceil(x) - cel mai mic întreg, mai mare că x, convertit la

double (ex. ceil(1.05) va returna valoarea 2).

floor(x) - cel mai mare întreg, mai mic sau egal cu x, convertit la double (exemplu: floor(1.02) va returna valoarea 1.0, floor(-1.02) va returna valoarea -2.0).

fabs(x) - modulul numărului x;

ldexp(x, n) - calculeaza x*2n , unde n este de tip int.

fmod(x, y) - restul în virgulă mobila a lui x/y, cu acelaşi semn ca x.

modf(x, double *ip) - împarte pe x în parte întreagă şi parte fracţionară, fiecare cu acelaşi semn că x; memorează partea întreagă în "*ip" şi întoarce partea fracţionară.

modf(x, double *ip) - întoarce x într-o funcţie normalizată în intervalul [1/2, 1] şi o putere a lui 2, care se memoreaza în "*exp"; dacă x este 0, ambele părţi ale rezultatului sunt 0.

Modul de utilizare al acestor funcţii este similar cu al celorlalte funcţii matematice descrise în acest capitol.

Page 263: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

257

Capitolul XIV

ELEMENTE DE PROGRAMARE AVANSATĂ

14.1 Gestionarea memoriei

Un calculator poate avea trei tipuri de memorie: convenţională,

extinsă şi expandată. În programare memoria constituie un factor important ce influenţează viteza de lucru a programelor. Fiecare tip de memorie are diferite viteze de acces, ceea ce afectează performanţa programelor. Volumul şi tipul de memorie instalată poate fi determinat utilizând comanda DOS: C:>MEM /CLASSIFY (pentru versiuni ale sistemului de operare DOS mai mari de varianta 5). Sistemul de operare DOS dispune de capacităţi de gestionare a memoriei ce pot maximiza performanţele calculatorului.

14.1.1 Memoria convenţională

Primul PC compatibil IBM utiliza de obicei între 64Kb şi 256Kb memorie RAM (Read Only Memory). Pe atunci această memorie era mai mult decât suficientă. Astăzi, memoria convenţională a unui PC este formată din primul 1Mb de RAM. Programele DOS rulează, în mod obişnuit, cu primii 640Kb de memorie convenţională. PC-ul utilizează restul de 384Kb de memorie (numită memorie rezervată sau memorie superioară) pentru memoria video a calculatorului, driverele de dispozitive, alte dispozitive HARD mapate în memorie şi BIOS (Basic Input-Output Services – servicii intrare-ieşire de bază).

Sistemul de operare Windows utilizează modelul de memorie virtuală pentru a gestiona memoria, ceea ce înseamnă că eliberarea memoriei convenţionale nu are semnificaţie sub acest sistem de operare. Însă, memoria convenţională este importantă când se rulează programe în cadrul unei ferestre DOS sub Windows.

Structura memoriei convenţionale a unui calculator personal este următoarea:

Page 264: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

258

BIOS ROM Memorie rezervată

Memorie video COMMAND.COM

Memorie pentru programe

Intrări CONFIG.SYS

Nucleul DOS Zona de comunicaţii BIOS

Vectori de întrerupere BIOS

PC-ul împarte memoria în blocuri de 64Kb numite segmente. În mod obişnuit, programul utilizează un segment de cod (ce conţine instrucţiunile programului) şi un al doilea segment de memorie pentru date. Dacă un program este foarte mare compilatorul va trebui să dispună de mai multe segmente de cod sau de date, sau de ambele. Modelul de memorie defineşte numărul de segmente pe care le poate folosi pentru fiecare. Modele sunt foarte importante deoarece, dacă se utilizează un model de memorie necorespunzător, programul poate să nu deţină suficientă memorie pentru execuţie. Compilatorul va alege un model de memorie suficient de mare pentru a rula programul, însă cu cât memoria utilizată este mai mare cu atât viteza de execuţie a programului scade. Din această cauză trebuie ales modelul cel mai mic pentru necesităţile programului. Majoritatea compilatoarelor acceptă următoarele modele de memorie:

a) tiny – combină datele şi codul programului într-un singur segment de 64Kb (este cel mai mic şi mai rapid model de memorie);

b) small – utilizează un segment de memorie pentru cod şi un segment pentru date (este cel mai obişnuit model de memorie);

c) medium – utilizează un segment de 64Kb pentru date şi două sau mai multe segmente pentru codul programului. În acest caz datele sunt accesate rapid prin utilizarea de adrese near, în schimb însă, apelurile de funcţii se fac utilizând adrese far;

d) compact – alocă un segment de 64Kb pentru codul programului şi două sau mai multe segmente pentru date (este un model utilizat pentru programe mici ce manipulează un număr mare de date);

Page 265: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

259

e) large – alocă mai multe segmente atât pentru date cât şi pentru cod şi este cel mai lent model de memorie din cele prezentate până acum. El trebuie utilizat doar ca ultimă resursă;

f) huge – este un model utilizat doar în cazul utilizării unor matrici mai mari de 64Kb. Pentru a stoca o astfel de matrice programul trebuie să utilizeze cuvântul cheie huge pentru a crea un pointer, astfel:

int huge *matrice_uriaşă

după care programul trebuie să utilizeze funcţia halloc pentru alocarea memoriei şi funcţia hfree pentru eliberarea acesteia. Exemplul următor alocă o matrice de 400000 octeţi: Exemplu: #include <stdio.h>

#include <malloc.h>

void main(void)

{ long int k;

int huge *matrice_uriasa;

if ((matrice_uriasa=(int huge*) halloc(100000L,sizeof

(long int)))==NULL)

printf(”Eroare la alocarea matricii”);

else{

printf(”Completeaza matricea\n”);

for(k=0;k<100000L;k++)

matrice_uriasa[k]=k%32768;

for(k=0;k<100000L;k++)

printf(”%d ”,matrice_uriasa[k]); hfree(matrice_uriasa); } }

Pentru selectarea unui anumit model de memorie se include, de regulă, o opţiune în cadrul liniei de comandă a compilatorului. Majoritatea compilatoarelor predefinesc o constantă specifică pentru a determina modelul curent de memorie. În tabelul următor sunt prezentate aceste constante definite de compilatoarele Microsoft C şi Borland C:

Model de memorie Microsoft C Borland C Small M_I86SM _SMALL_

Medium M_I86MM _MEDIUM_ Compact M_I86CM _COMPACT_

Large M_I86LM _LARGE_ Programul poate verifica modelul de memorie utilizat folosind

următoarea secvenţă de instrucţiuni:

#ifndef _MEDIUM_

printf(”Programul cere modelul de memorie medium\n”);

Page 266: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

260

exit(1);

#endif

Atunci când un program trebuie să aloce memorie în mod dinamic se utilizează fie funcţia malloc pentru a aloca memorie din zona near (din segmentul curent), fie funcţia fmalloc pentru a aloca memorie far.

14.1.2 Memoria expandată În cazul programelor mari o memorie de numai 1Mb este

insuficientă. Pentru a permite accesul la mai mult de 1Mb de memorie, companiile Lotus, Intel şi Microsoft au creat o specificaţie pentru memoria expandată, care combină software şi o platformă specială de memorie expandată pentru a „păcăli” PC-ul în scopul accesării unor volume mari de memorie. Mai întâi în zona de memorie superioară se alocă un bloc de 64Kb după care acest bloc de memorie este împărţit în patru secţiuni de 16Kb, numite pagini în care se încarcă paginile logice ale programului. De exemplu un program de 128Kb este împărţit în opt pagini de 16Kb fiecare care sunt încărcate în funcţie de necesităţile programului în zona rezervată de 64Kb.

14.1.3 Memoria extinsă Calculatoarele cu procesoare peste 386 utilizează adresarea pe

32 de biţi ceea ce le dă posibilitatea de accesare directă de până la 4Gb de memorie. Programatorii au numit memoria de peste 1Mb memorie extinsă. Pentru a accesa memoria extinsă, trebuie încărcat un driver de dispozitiv pentru memoria extinsă, care în DOS este de obicei himem.sys. Pentru a utiliza însă memoria extinsă este necesară trecerea la modul protejat de lucru al procesorului, mod de lucru în care datele unui program nu pot fi scrise peste datele altui program ce rulează simultan cu acesta.

14.1.4 Stiva Stiva este o regiune de memorie în cadrul căreia programele

păstrează temporar datele pe durata execuţiei. De exemplu, atunci când programele transmit parametri către o funcţie, C plasează aceşti parametri în stivă. Când funcţia îşi încheie execuţia aceştia sunt scoşi din stivă. Stiva este numită astfel deoarece ultimele valori depuse sunt primele extrase. În funcţie de modelul de memorie utilizat, spaţiul de memorie ocupat de stivă diferă. Valoarea minimă a stivei este 4Kb. În

Page 267: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

261

cazul modelelor compact sau large, C alocă pentru stivă un întreg segment de 64Kb. Dacă un program plasează în stivă mai multe informaţii decât poate reţine aceasta, va apărea o eroare de depăşire a stivei (stack-overflow). Dacă programul a dezactivat testarea stivei, datele depuse în stivă pot fi suprapuse peste datele programului. Exemplul următor prezintă modul de determinare a dimensiunii stivei utilizând funcţia _stklen. Exemplu: #include <stdio.h>

#include <dos.h>

void main(void)

{

printf(”Dimensiunea stivei este de %d octeti”,_stklen);

}

14.2 Servicii DOS şi BIOS

Aşa cum am menţionat în paragraful anterior, BIOS-ul reprezintă serviciile de intrare-ieşire de bază. Pe scurt, BIOS este un cip din cadrul calculatorului ce conţine instrucţiunile pe care calculatorul le utilizează pentru a scrie pe ecran sau la imprimantă, pentru a citi caractere de la tastatură sau pentru a citi sau scrie pe disc. Programatorii au au proiectat rutinele BIOS pentru a fi utilizate de programe în limbaj de asamblare, totuşi, majoritatea compilatoarelor de C dispun de funcţiide bibliotecă ce permit utilizarea acestor servicii fără a avea nevoie de limbaje de asamblare.

DOS este un sistem de operare pentru calculatoarele compatibile IBM PC. Sistemul DOS permite rularea programelor şi păstrează informaţia pe disc. În plus, sistemul DOS pune la dispoziţie servicii ce permit programelor să aloce memorie, să acceseze dispozitive, cum ar fi imprimanta, şi să gestioneze alte resurse ale sistemului. Biblioteca limbajului C oferă o interfaţă la multe servicii DOS, prin intermediul funcţiilor.

Mulţi programatori confundă serviciile DOS cu serviciile BIOS. Tabelul următor prezintă relaţia dintre componenta HARD a calculatorului, serviciile BIOS, DOS şi componenta SOFT.

Programe

DOS BIOS

HARDWARE

Nivel înalt | |

Nivelul cel mai jos

Page 268: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

262

Aşa cum se observă, BIOS este situat imediat deasupra componentei hardware, serviciiile DOS deasupra serviciilor BIOS, iar programele deasupra sistemului DOS.

Uneori însă, programele pot evita serviciile DOS şi BIOS şi pot accesa direct o componentă hardware (cum este cazul memoriei video).

Se recomandă ca ori de câte ori poate fi utilizată o funcţie de bibliotecă C în locul unui serviciu DOS sau BIOS, aceasta să fie utilizată pentru a mări portabilitatea programelor şi la calculatoarele ce utilizează alte sisteme de operare (WINDOWS, UNIX, etc.). În acest caz, programul nu va mai trebui modificat pentru a putea fi rulat sub WINDOWS sau UNIX.

Toate versiunile de WINDOWS vor apela propriile lor servicii de sistem. Însă, serviciile de sistem WINDOWS apelează până la urmă serviciile BIOS penttru a accesa componentele hardware ale cal;culatorului.

14.2.1 Serviciile BIOS Prezentăm în continuare o serie de servicii BIOS ce pot fi accesate utilizând funcţii de bibliotecă ale limbajului C. 1) accesul la imprimantă Înainte ca un program să scrie ieşirea la imprimantă utilizând indicatorul de fişier stdprn se poate face o verificare dacă imprimanta este conectată şi dacă are hârtie utilizând funcţia biosprint din fişierul antet bios.h:

int biosprint(int comanda,int octet,int nr_port)

unde comanda specifică una din următoarele operaţii: 0 – tipăreşte octetul specificat; 1 – iniţializează portul imprimantei; 2 – citeşte starea imprimantei. Parametrul octet specifică valoarea ASCII a caracterului ce se

doreşte a fi scris la imprimantă iar nr_port specifică portul imprimantei care poate fi 0 pentru LPT1, 1 pentru LPT2, ş.a.m.d.

Funcţia biosprint returnează o valoarea înteagă pe un octet ai cărui biţi au următoarea semnificaţie:

0 – dispozitiv în pauză; 3 – eroare I/O; 4 – imprimantă selectată; 5 – lipsă hârtie;

Page 269: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

263

6 – confirmare dispozitiv; 7 – dispozitivul nu este ocupat.

2) operaţii intrare/ieşire Operaţiile intrare/ieşire de nivel jos pot fi realizate utilizând

funcţia biodisk ce are următoarea sintaxă: int biodisk(int operatie, int unitate, int head, int

track, int sector, int nr_sector, void *buffer)

unde parametrul unitate precizează numărul unităţii, care este 0 pentru A, 1 pentru B, şi aşa mai departe. Parametrii head, track, sector şi nr_sector precizează sectoarele fizice ale disculuice trebie scris sau citit. Parametru buffer este un pointer la bufferul din care sunt citite sau în care sunt scrise datele. Parametru operatie specifică funcţia dorită astfel:

0 Iniţializează sistemul de disc 1 Returnează starea ultimei operaţii pe disc 2 Citeşte numărul precizat de sectoare 3 Scrie numărul precizat de sectoare 4 Verifică numărul precizat de sectoare 5 Formatează pista specificată 6 Formatează pista specificată şi marchează sectoarele defecte 7 Formatează unitatea începând cu pista specificată 8 Returnează parametrii unităţii de disc 9 Iniţializează unitatea de disc 10 Execută o citire lungă – 512 octeţi de sector plus patru suplimentari 11 Execută o scriere lungă – 512 octeţi de sector plus patru

suplimentari 12 Execută o poziţionare pe disc 13 Iniţializarea alternativă a discului 14 Citeşte bufferul sectorului 15 Scrie bufferul sectorului 16 Testează dacă unitatea este pregătită 17 Recalibrează unitatea 18 Execută diagnosticarea unităţii de RAM 19 Execută diagnosticarea unităţii 20 Execută diagnosticarea internă a controlerului

Dacă se execută cu succes, funcţia returnează valoarea 0. Dacă apare o eroare, valoarea returnată precizează eroarea. 3) servicii de tastatură din BIOS

Pentru accesul la serviciile de tastatură din BIOS, C-ul pune la dispoziţie funcţia _bios_keybrd ce are următoarea sintaxă:

unsigned _bios_keybrd(unsigned comanda)

Page 270: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

264

unde parametrul comanda specifică operaţia dorită şi poate avea una din următoarele valori:

_KEYBRD_READ Indică funcţiei să citească un caracter de la tastatură

_KEYBRD_READY Determină dacă este prezent un caracter la bufferul tastaturii. Dacă funcţia returnează 0, înseamnă că nici o intrare de la tastatură nu este prezentă. Dacă valoarea returnată este 0xFFFF, utilizatorul a apăsat CTRL C

_KEYBRD_SHIFTSTATUS Returnează starea tastelor de control: Bit 7 – INS este activat Bit 6 – CAPSLOCK este activat Bit 5 – NUMLOCK este activat Bit 4 – SCRLLOCK este activat Bit 3 – ALT este apăsată Bit 2 – CTRL este apăsată Bit 1 – SHIFT stânga este apăsată Bit 0 – SHIFT dreapta este apăsată

_NKEYBRD_READ Indică funcţiei să citească un caracter de la tastatură, inclusiv tastele speciale, cum ar fi tastele cu săgeţi

_NKEYBRD_READY Determină dacă este prezent un caracter la bufferul tastaturii. Dacă funcţia returnează 0, înseamnă că nici o intrare de la tastatură nu este prezentă. Dacă valoarea returnată este 0xFFFF, utilizatorul a apăsat CTRL C Funcţia acceptă inclusiv tastele speciale, cum ar fi tastele cu săgeţi

_NKEYBRD_SHIFTSTATUS Returnează starea tastelor de control, inclusiv a tastelor speciale: Bit 15 – SYSREQ este activat Bit 14 – CAPSLOCK este activat Bit 13 – NUMLOCK este activat Bit 12 – SCRLLOCK este activat Bit 11 – ALT dreapta este apăsată Bit 10 – CTRL dreapta este apăsată Bit 9 – ALT stânga este apăsată Bit 8 – CTRL stânga este apăsată

4) obţinerea listei cu echipamente din BIOS Unele programe necesită determinarea caracteristicilor

hardware ale calculatorului. Pentru aceasta se utilizează funcţia _bios_equiplist care are următoarea sintaxă:

Page 271: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

265

unsigned _bios_equiplist(void);

Funcţia returnează o valoare pe 16 biţi a căror valoare are următoarea semnificaţie:

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0

15:14 – numărul de imprimante paralele instalate (de la 0 la 3); 13 – imprimanta serială; 12 – adaptorul de jocuri; 11:10:9 – numărul de porturi seriale COM (de la 0 la 7); 8 – prezenţa DMA (Direct Memory Acces); bitul are valoarea 0 dacă există DMA şi 1 dacă nu există; 7:6 – numărul drieverelor de disc; 5:4 – modul video: 00-neutilizat, 01-mod video 40x25 mono, 10-mod video 80x25 color, 11-mod video 80x25 mono; 3:2 – dimensiunea memorie RAM: 00-16Kb, 01-32Kb, 10-48Kb, 11-64Kb; 1 – prezenţa coprocesorului matematic; 0 – prezenţa unităţii de disc flexibile. 5) controlul intrărilor şi ieşirilor pentru portul serial

Pentru a executa operaţii intrare/ieşire utilizând portul serial se utilizează funcţia bioscom ce are următoarea sintaxă:

unsigned bioscom(int comanda,int port,char octet);

Parametrul comanda specifică operaţia dorită şi poate avea una din următoarele valori:

_COM_INIT Stabileşte valorile pentru comunicare ale portului _COM_RECEIVE Primeşte un octet de la port _COM_SEND Trimite un octet la port _COM_STATUS Returnează valorile portului

Parametrul port specifică portul serial ce se doreşte a fi utilizat, unde 0 corespunde lui COM1, 1 lui COM2 şi aşa mai departe.

Parametrul octet specifică fie octetul pentru ieşire, fie valorile de comunicare dorite. 6) determinarea volumului de memorie convenţională BIOS

Pentru a determina memoria convenţională ce poate fi utilizată de către un proggram se utilizează funcţia biosmemory ce are următoarea sintaxă:

int biosmemory(void);

Valoarea returnată de această funcţie nu cuprinde memoria extinsă, expandată sau superioară. 7) citirea cronometrului BIOS

Page 272: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

266

BIOS are incorporat un ceas intern ce bate de 18.2 ori pe secundă. Acest cronometru este util pentru a genera punctul iniţial al unui generator de numere aleatoare. Multe compilatoare de C pun la dispoziţie două funcţii pentru accesul la cronometrul BIOS: biostime şi _bios_timeofday. Sintaxa acestor funcţii este următoarea:

long biostime(int operatie,long timp_nou);

Parametrul operaţie poate lua două valori: 0 – dacă se doreşte ca funcţia să citească valoarea curentă a

cronometrului; 1 – pentru a fixa valoarea cronometrului la valoarea timp_nou. long _bios_timeofday(int operatie,long *batai);

Această funcţie poate fi, de asemenea, utilizată pentru a citi sau a fixa cronometrul BIOS.

14.2.2 Serviciile DOS În acest paragraf prezentăm o serie de servicii DOS ce pot fi

accesate utilizând funcţii de bibliotecă ale limbajului C. 1) suspendarea temporară a unui program

Execuţia unui program poate fi suspendată temporar utilizând funcţia sleep.h din fişierul antet dos.h:

void sleep(unsigned secunde);

parametrul secunde specificând numărul de secunde pe care este suspendat programul. 2) utilizarea sunetelor

Generarea de sunete ce utilizează difuzorul calculatorului se realizează utilizând funcţiile sound şi nosound:

void sound(unsigned frecventa)

generează un sunet cu frecvenţa frecventa; void sound(unsigned frecventa)

deconectează difuzorul. Programul următor generează un sunet de sirenă dezactivat la apăsarea unei taste: Exemplu: #include <dos.h>

#include <conio.h> void main()

{ unsigned frecventa;

do{ for (frecventa=500;frecventa<=1000;frecventa+=50)

{ sound(frecventa);

delay(50); }

for (frecventa=1000;frecventa>=500;frecventa-=50)

{ sound(frecventa);

delay(50); } }

Page 273: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

267

while(!kbhit());

nosound(); }

3) obţinerea de informaţii despre erori în DOS În cazul în care un serviciu al sistemului DOS eşuează,

programele pot cere informaţii suplimentare despre acea eroare folosind funcţia dosexterr:

int dosexterr(struct DOSERROR *info_eroare);

unde structura DOSERROR are următoarele câmpuri: struct DOSERROR{ int de_exterror; //eroare

int de_class; //clasa erorii

int de_action;//actiune recomandata

int de_locus;//sursa erorii };

Dacă funcţia returnează valoarea 0, apelul serviciului DOS nu a avut nici o eroare.

Clasa erorii descrie categotia erorii, astfel: 01H Resurse depăşite 02H Eroare temporară 03H Eroare de autorizare 04H Eroare de sistem 05H Eroare hardware 06H Eroare de sistem nedatorată programului curent 07H Eroare de aplicaţie 08H Articol neîntâlnit 09H Format nevalid 0AH Articol blocat 0BH Eroare de suport 0CH Articolul există 0DH Eroare necunoscută

Parametrul de_action indică programului cum să răspundă erorii, astfel:

01H Mai întâi încearcă din nou, apoi cere intervenţia utilizatorului

02H Încearcă din nou, cu o întârziere, apoi cere intervenţia utilizatorului

03H Cere intervenţia utilizatorului pentru soluţie

04H Renunţă şi elimină

05H Renunţă, dar nu elimina

06H Ignoră eroarea

07H Încearcă din nou după intervenţia utilizatorului

Parametrul de_locus specifică sursa erorii, astfel:

Page 274: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

268

01H Sursă necunoscută

02H Eroare de dispozitiv bloc

03H Eroare de reţea

04H Eroare de dispozitiv serial

05H Eroare de memorie

4) citirea valorilor registrului segment Codul programului, datele şi stiva sunt controlate de compilator

utilizând patru registre de segment: CS, DS, ES, SS. În unele cazuri este necesar să se cunoască valoarea acestor registre. Pentru astfel de cazuri se utillizează funcţia segread:

void segread(struct SREGS *segs);

Structura SREGS are următoarele câmpuri: struct SREGS

{ unsigned int es;

unsigned int cs;

unsigned int ss;

unsigned int ds; }

5) accesul la valorile de port Pentrul controlul hardware de nivel inferior, compilatoarele de C pun la dispoziţie următoarele funcţii:

- int inport (int adresa_port); - citeşte un cuvânt de la portul specificat de parametrul adresa_port;

- int inportb (int adresa_port); - citeşte un octet de la portul specificat de parametrul adresa_port;

- int outport (int adresa_port); - scrie un cuvânt de la portul specificat de parametrul adresa_port;

- int outportb (int adresa_port); - scrie un octet de la portul specificat de parametrul adresa_port;

6) suspendarea unui program Pentru suspendarea unui program pe un anumit interval de timp se poate utiliza funcţia delay, similară funcţiei sleep. Funcţia delay are însă ca parametru o constantă exprimată în milisecunde:

void delay(unsigned milisecunde);

7) apelarea unei comenzi interne DOS Pentru apelarea unei comenzi DOS sau a unui fişier pentru comenzi se utilizează funcţia system:

int system(const char *comanda);

Parametrul comanda este un şir de caracter care conţine numele comenzii DOS sau a fişierului de comenzi. Dacă funcţia reuşeşte să execute comanda, se returnează valoarea 0, altfel returnează -1.

Page 275: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

269

Programul următor prezintă utilizarea funcţiei system. Exemplu: #include <stdlib.h>

#include <stdio.h>

void main(void)

{ if(system("DIR"))

printf("EROARE!\n"); }

8) lucrul cu vectori de întrerupere Un vector de întrerupere este o adresă de segment şi de deplasament a codului care tratează o anumită întrerupere. Determinarea vectorului de întrerupere se realizează utilizând funcţia _dos_getvect în modul următor:

void interrupt(* _dos_getvect(unsigned nr_intr))();

Parametrul nr_intr specifică numărul întreruperii dorite ce poate avea valori de la 0 la 255. Programul următor va afişa vectorii pentru toate întreruperile calculatorului: Exemplu: #include <stdio.h>

#include <dos.h>

void main(void)

{ int k;

for(k=0;k<=255;k++)

printf(”Intrerupere: %x Vector %lx\n”,k,

_dos_getvect(k)); }

Dacă se doreşte crearea unui program de tratare a unei întreruperi, vectorul de întrerupere trebuie atribuit acestui program. Această atribuire se realizează cu ajutorul funcţiei _dos_setvect:

void _dos_setvect(unsigned nr_intr,

void interrupt(* handler)());

Parametrul nr_intr specifică întreruperea al cărui vector trebuie modificat.

Pentru activarea şi dezactivarea întreruperilor se utilizează funcţiile:

void _disable(void);

void _enable(void);

Dacă se doreşte reactivarea întreruperii originare se utilizează funcţia _chain_interrupt:

void chain_interrupt(void(interrupt far *handler)());

Generarea unei întreruperi se realizează folosind funcţia geninterrupt:

void geninterrupt(int intrerupere);

unde parametrul intrerupere specifică întreruperea generată.

Page 276: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

270

14.3 Bibliotecile C

Dacă se examinează fişierele ce însoţesc un compilator C, se remarcă multe fişiere cu extensia LIB. Aceste fişiere conţin biblioteci obiect. Atunci când este compilat şi link-editat un program, editorul de legături examinează fişierele LIB pentru a rezolva referinţele la funcţii. Când sunt create funcţii utile ce sunt necesare şi în alte programe, se pot construi biblioteci în care aceste funcţii să fie păstrate.

14.3.1 Reutilizarea unui cod obiect În cazul creării unei funcţii utile care se doreşte reutilizată, se

poate compila fişierul ce conţine funcţia respectivă pentru a crea codul obiect (de exemplu din fişierul funcţie.c prin compilare se obţine fişierul obiect funcţie.obj). Funcţia definită în acest fişier obiect poate fi reutilizată în alt program utilizând următoarea instrucţiune:

C:\>bc fisier_nou.c funcţie.obj Totuşi, acest mod de a reutiliza codul unor funcţii este destul de

dificil de utilizat în cazul în care se doreşte reutilizarea unui număr mare de funcţii aflate în fişiere obiect separate.

14.3.2 Lucrul cu fişiere bibliotecă Operaţiile acceptate de fişierele bibliotecă sunt următoarele:

- crearea unei biblioteci; - adăugarea unuia sau mai multor fişiere obiect la bibliotecă; - înlocuirea unui fişier obiect cu altul; - ştergerea unuia sau mai multor fişiere obiect din bibliotecă; - listarea rutinelor pe care le conţine biblioteca.

În funcţie de compilator, numele programului de bibliotecă şi opţiunile liniei de comandă pe care programul le acceptă vor diferi. În continuare prezentăm operaţiile ce pot fi realizate cu funcţiile bibliotecă utilizând programul TLIB al compilatorului Borland C. Presupunem că în urma compilării am creat fişierul obiect funcţie.obj ce conţine o serie de funcţii pe care dorim să le păstrăm într-o bibliotecă. Crearea unei bilioteci biblioteca.lib care să conţină acest fişier obiect se realizează cu următoarea linie de comandă:

C:\>tlib biblioteca.lib + functie.obj

După ce fişierul bibliotecă a fost creat, funcţiile acestuia sunt disponibile pentru compilarea şi legarea noilor programe.

Page 277: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

271

Funcţia de biliotecă TLIB a compilatorului Borland C are următoarea sintaxă:

tlib cale comandă, fişier

unde: - cale – este un şir de caractere care specifică calea până la

bilioteca asupra căreia se efectuează operaţia; - comandă – este formată dintr-un simbol şi numele unui fişier

obiect. Simbol poate fi unul din caracterele: + (adaugă un modul la bibliotecă), - (elimină un modul din bibliotecă), * (extrage un modul din bibliotecă într-un fişier cu acelaşi nume, fără al elimina), -+ (înlocuieşte un modul din bibliotecă), -* (extrage şi elimină un modul din bibliotecă);

- fisier – reprezintă numele fişierul în care se scrie ieşirea operaţiei efectuate asupra bibliotecii.

14.3 Fişierele antet

Fiecare program foloseşte una sau mai multe instrucţiuni

#include pentru a cere compilatorului de C să folosească instrucţiunile incluse într-un fişier antet. Când compilatorul întâlneşte o instrucţiune #include în program, el compilează codul din fişierul antet ca şi cum ar fi scris în fişierul sursă. Fişierele antet conţin definiţii frecvent utilizate şi furnizează compilatorului informaţii referitoare la funcţiile sale. Dacă la compilarea programului se afişează un mesaj de eroare, avertizând că nu se poate deschide un anumit fişier antet, trebuie verificat subdirectorul care conţine fişierele antet, pentru a vedea dacă acel fişier există sau nu. Dacă se găseşte fişierul respectiv, în linia de comandă din sistemul de operare DOS trebuie scrisă următoarea instrucţiune:

C:\>SET INCLUDE=C:\BORLANDC\INCLUDE

Page 278: Carte C 2003 - Universitatea din Craiova · 2007-11-05 · crescut, ceea ce a condus la apari ţia de limbaje care s ă permit ă utilizarea ei în scrierea programelor. Limbajul

272

BIBLIOGRAFIE 1. Plum T., Learning to program in C, Prentice Hall, 1983 2. Auslander D.,Tham C., Real-time software for control: program

examples in C, Prentice Hall, 1990. 3. Schild H., Using Turbo C, Borland, Osborne / McGraw Hill,

1988. 4. Holzner S., Borland C++ Programming, Brady Books, New

York, 1992. 5. Somnea D., Turturea D., Introducere în C++, Programarea

orientatã pe obiecte, Ed. Tehnicã, Bucureşti, 1993. 6. Marian Gh., Bãdicã C., Pãdeanu L., Limbajul PASCAL, Indrumar

de laborator, Reprografia Universitãţii din Craiova, 1993. 7. Negrescu L., Introducere în limbajul C, Editura

MicroInformatica, Cluj Napoca, 1993. 8. Petrovici V., Goicea F., Programarea în limbajul C, Editura

Tehnicã, Bucureşti, 1993. 9. Marian Gh., Muşatescu C., Laşcu M., Iordache Şt., Limbajul C,

Editura ROM TPT, Craiova, 1999. 10. Mocanu M., Ghid de programare în limbajele C/C++, Editura

SITECH, Craiova, 2001. 11. Zaharia, M.D., Structuri de date şi algoritmi. Exemple în

limbajele C şi C++, Ed. Albastră, Cluj Napoca, 2002. 12. Kernighan, B.W., Ritchie, D.M., The C programming languages,

Englewood. Cliffs, N.J. Prentice-Hall, 1978. 13. Bulac, C., Iniţiere în Turbo C++ şi Borland C, Editura Teora,

Bucureşti, 1995.