Sunteți pe pagina 1din 284

CUPRINS

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

1.1. Importana 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 fiiere..........................................................17 1.3.3. Construirea fiierului executabil................................18
Capitolul II.......................................................................................23 REPREZENTAREA DATELOR N CALCULATOR.................24

2.1. Reprezentarea intern/extern a numerelor.......................25 2.2. Reprezentarea extern a numerelor...................................26 2.2.1. Reprezentarea extern a numerelor ntregi................27 2.2.2. Reprezentarea extern a numerelor reale..................30 2.3 Reprezentarea intern a numerelor.....................................32 2.3.1. Reprezentarea intern a numerelor ntregi................32 2.3.2 Adunarea, scderea i nmulirea numerelor ntregi. .34 2.3.3 Reprezentarea intern a numerelor reale....................36 Game de reprezentare pentru numerele reale......................48 2.3.5. Codificare BCD..........................................................49
Capitolul III......................................................................................51 ELEMENTELE DE BAZ ALE LIMABJULUI C.....................51

3.1. Crearea i lansarea n execuie a unui program C.............51 3.2. Structura unui program C..................................................53 3.3. Mulimea caracterelor .......................................................55 3.3.1. Litere i numere.........................................................55 I

3.3.2. Caractere whitespace.................................................56 3.3.3. Caractere speciale i de punctuaie............................56 3.3.4. Secvene escape.........................................................57 3.4. Identificatori.......................................................................58 3.5. Cuvintele cheie ale limbajului C.......................................58 3.6. Constante............................................................................59 3.6.1. Constante caracter......................................................59 3.6.2. Constante ntregi........................................................59 3.6.3. Constante n virgul mobil.......................................60 3.6.4. Constante ir...............................................................60 3.6.5. Constanta zero............................................................62 3.6.6. Obiecte constante.......................................................62 3.6.7. Enumerri ..................................................................62
Capitolul IV......................................................................................63 ..........................................................................................................63 Operanzi i operatori n C...............................................................63

4.1. Operanzi ............................................................................63 4.2. Operatori ...........................................................................64 4.2.1. Operatori aritmetici....................................................64 4.2.2. Operatori de incrementare i decrementare...............65 4.2.3. Operatori relaionali...................................................66 4.2.4. Operatori logici..........................................................66 4.2.5. Operatori logici la nivel de bit...................................67 4.2.6. Operatorul de atribuire...............................................72 4.2.7. Operatorul sizeof........................................................73 4.2.8. Operatorul ternar ?....................................................73 4.2.9. Operatorul virgul......................................................74 4.2.10. Operatorul de forare a tipului sau de conversie explicit (expresie cast)..........................................75 4.2.11. Operatorii parantez.................................................75 4.2.12. Operatorul adres.....................................................76 4.2.13. Ali operatori ai limbajului C...................................76 4.2.14. Regula conversiilor implicite i precedena operatorilor..............................................................77
Capitolul V.......................................................................................78 ..........................................................................................................78

II

Instruciuni.......................................................................................78

5.1. Instruciuni etichetate (instruciunea goto)........................79 5.2. Instruciuni expresie...........................................................79 5.3. Instruciuni compuse..........................................................80 5.4. Instruciuni de selecie.......................................................81 5.4.1. Instruciunea if...........................................................81 5.4.2. Instruciuni de selecie multipl: if - else if...............82 5.4.3. Instruciunea switch...................................................83 5.5. Instruciuni repetitive.........................................................85 5.5.1. Instruciunea for.........................................................85 5.5.2. Instruciunea while.....................................................89 5.5.3. Instruciunea do-while...............................................90 5.5.4. Bucle ncuibate...........................................................92 5.5.5. Instruciunea break.....................................................94 5.5.6. Instruciunea continue................................................95
Capitolul VI......................................................................................95 ..........................................................................................................95 TIPURI DE DATE STRUCTURATE ...........................................96

6.1. Tablouri unidimensionale..................................................96 6.1.1. Constante ir...............................................................97 6.1.2. Iniializarea vectorilor de caractere...........................98 6.1.3. Funcii pentru prelucrarea irurilor (fiierul antet string.h).................................................................100 6.2. Tablouri cu dou dimensiuni (matrice)...........................103 6.2.1. Iniializarea matricelor.............................................103 6.2.2. Tablouri bidimensionale de iruri............................104 6.3. Tablouri multidimensionale.............................................104 6.4. Structuri............................................................................105 6.4.1. Tablouri de structuri.................................................107 6.4.2. Introducerea structurilor n funcii...........................113 6.4.3. Tablouri i structuri n structuri...............................117 6.5. Uniuni...............................................................................118 6.6. Enumerri.........................................................................119
Capitolul VII..................................................................................121 ........................................................................................................121 POINTERI.....................................................................................121

III

7.1. Operatori pointer..............................................................121 7.1.1. Importana tipului de baz.......................................123 7.1.2. Expresii n care intervin pointeri.............................123 7.2. Pointeri i tablouri............................................................128 7.2.1. Indexarea pointerilor................................................129 7.2.2. Pointeri i iruri .......................................................131 7.2.3. Preluarea adresei unui element al unui tablou.........132 7.2.4. Tablouri de pointeri..................................................133 7.2.5. Pointeri la pointeri....................................................133 7.2.6. Iniializarea pointerilor............................................134 7.2.7. Alocarea dinamic a memoriei................................135 7.2.8. Pointeri la structuri...................................................138 7.2.9. Structuri dinamice liniare de tip list......................141
Capitolul VIII.................................................................................153 FUNCII.......................................................................................153

8.1. Forma general a unei funcii..........................................153 8.2. Rentoarcerea dintr-o funcie...........................................156 8.3. Valori returnate................................................................157 8.4. Domeniul unei funcii......................................................158 8.4.1. Variabile locale........................................................158 8.4.2. Parametri formali.....................................................160 8.4.3. Variabile globale......................................................161 8.5. Apelul funciilor...............................................................165 8.6. Apelul funciilor avnd ca argumente tablouri................166 8.7. Argumentele argc i argv ale funciei main()..................170 8.8. Funcii care returneaz valori nentregi...........................171 8.9. Returnarea pointerilor......................................................172 8.10. Funcii de tip void..........................................................175 8.11. Funcii prototip..............................................................176 8.12. Funcii recursive............................................................178 8.13. Clase de memorare (specificatori sau atribute).............179 8.14. Pointeri la funcii...........................................................185
Capitolul IX....................................................................................186 PREPROCESAREA.....................................................................186

9.1. Directive uzuale...............................................................187 9.2. Directive pentru compilare condiionat.........................189 IV

9.3. Modularizarea programelor............................................193


Capitolul X.....................................................................................198 ........................................................................................................198 INTRRI/IEIRI...........................................................................198

10.1. Funcii de intrare i ieire - stdio.h................................198 10.2. Operaii cu fiiere..........................................................201 10.3. Nivelul inferior de prelucrare a fiierelor......................204 10.3.1. Deschiderea unui fiier..........................................204 10.3.2. Scrierea ntr-un fiier.............................................208 10.3.3. Citirea dintr-un fiier.............................................210 10.3.4. nchiderea unui fiier.............................................212 10.3.5. Poziionarea ntr-un fiier......................................212 10.3.6 tergerea unui fiier................................................214 10.3.7. Exemple de utilizare a funciilor de intrare/ieire de nivel inferior..........................................................215 10.4. Nivelul superior de prelucrare a fiierelor.....................220 10.4.1. Funcia fopen().......................................................220 10.4.2. Funcia fclose()......................................................222 10.4.3. Funciile rename() i remove().............................223 10.4.4. Funcii de tratare a erorilor....................................223 10.4.5. Funcii cu acces direct............................................224 10.4.6. Funcii pentru poziionare......................................226 10.4.7. Ieiri cu format.......................................................227 10.4.8. Intrri cu format.....................................................230 10.4.9. Funcii de citire i scriere a caracterelor................233
Capitolul XI....................................................................................237 ........................................................................................................237 Utilizarea ecranului.......................................................................237 n mod text.....................................................................................237

11.1. Setarea ecranului n mod text........................................238 11.2. Definirea unei ferestre...................................................238 11.3. tergerea unei ferestre...................................................239 11.4. Deplasarea cursorului....................................................239 11.5. Setarea culorilor.............................................................240 11.6. Funcii pentru gestiunea textelor...................................241
Capitolul XII..................................................................................244

........................................................................................................244 Utilizarea ecranului.......................................................................244 n mod GRAFIC............................................................................244

12.1. Iniializarea modului grafic...........................................244 12.2. Gestiunea culorilor.........................................................247 12.3. Setarea ecranului............................................................249 12.4. Utilizarea textelor n mod grafic....................................249 12.5. Gestiunea imaginilor......................................................250 12.6. Desenarea i colorarea figurilor geometrice..................252
Capitolul XIII.................................................................................258 Funcii matematice .......................................................................258

13.1 Funcii trigonometrice....................................................258 13.2 Funcii trigonometrice inverse........................................259 13.3 Funcii hiperbolice..........................................................259 13.4 Funcii exponeniale i logaritmice................................260 13.5 Generarea de numere aleatoare......................................260 13.6 Alte tipuri de funcii matematice....................................261
Capitolul XIV.................................................................................262 ELEMENTE DE PROGRAMARE AVANSAT.......................262

14.1 Gestionarea memoriei.....................................................262 14.1.1 Memoria convenional..........................................262 14.1.2 Memoria expandat.................................................265 14.1.3 Memoria extins......................................................265 14.1.4 Stiva.........................................................................266 14.2 Servicii DOS i BIOS.....................................................266 14.2.1 Serviciile BIOS.......................................................267 14.2.2 Serviciile DOS........................................................271 14.3 Bibliotecile C..................................................................275 14.3.1 Reutilizarea unui cod obiect...................................275 14.3.2 Lucrul cu fiiere bibliotec.....................................276 14.3 Fiierele antet..................................................................277
BIBLIOGRAFIE............................................................................277

VI

Capitolul I INTRODUCERE N ARHITECURA SISTEMELOR DE CALCUL

1.1. Importana 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 astzi unul din cele mai cunoscute i puternice limbaje de programare. Eficient, economic i portabil, C-ul este o alegere bun pentru realizarea oricrui tip de programe, de la editoare de texte, jocuri cu faciliti grafice, programe de gestiune i pentru calcule tiinifice, pn 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 acelai timp faciliteaz utilizarea caracteristicilor specifice ale mainilor particulare, programele scrise n C fiind considerate cele mai portabile. Dac evoluia limbajelor de programare a adus n prim plan nume ca FORTRAN, LISP, COBOL, ALGOL-60 sau PASCAL, unele cu rspndire 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 rspndire industrial masiv ca de pild FORTRAN i COBOL limbajul C a ptruns mai lent, dar foarte sigur. Realitatea arat clar c, n momentul de fa, piaa productorilor de programe este dominat net de C i de variantele evoluate ale acestuia. Elementele principale care au contribuit la succesul C-ului sunt urmtoarele: modularizarea programelor ce d posibilitatea unui singur programator s stpneasc relativ uor programe de zeci de mii de linii de surs;

capacitatea de programare att la nivel nalt ct i la nivel sczut ceea ce d posibilitatea utilizatorului de a programa fie fr a simi sistemul de operare i maina de calcul, fie la un nivel apropiat de sistemul de operare ceea ce permite un control foarte bun al eficienei 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; facilitile de reprezentare i prelucrare a datelor materializate printr-un numr mare de operatori i funcii de bibliotec ce fac programarea mult mai uoar. Prin anii 80 interesul pentru programarea orientat pe obiecte a crescut, ceea ce a condus la apariia de limbaje care s permit utilizarea ei n scrierea programelor. Limbajul C a fost dezvoltat i el n aceast direcie i n anul 1980 a fost dat publicitii limbajul C++, elaborat de Bjarne Stroustrup de la AT&T. La ora actual, majoritatea limbajelor de programare moderne au fost dezvoltate n direcia programrii orientate pe obiecte. Limbajul C++, ca i limbajul C, se bucur de o portabilitate mare i este implementat pe o gam larg de calculatoare ncepnd cu microcalculatoare i pn la cele mai mari supercalculatoare. Limbajul C++ a fost implementat pe microcalculatoarele compatibile IBM PC n mai multe variante. Cele mai importante implementri ale limbajelor C++ pe aceste calculatoare sunt cele realizate de firmele Microsoft i Borland. Conceptele programrii orientate pe obiecte au influenat n mare msur dezvoltarea limbajelor de programare n ultimul deceniu. De obicei, multe limbaje au fost extinse astfel nct ele s admit conceptele mai importante ale programrii orientate pe obiecte. Uneori s-au fcut mai multe extensii ale aceluiai 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 facilitile de nivel nalt cu cele ale programrii de sistem, limbajul Avalon/C++ destinat calculului distribuit, i nu n ultimul rnd binecunoscutul de acum limbaj Java, specializat n aplicaii Internet. Interfeele utilizator au atins o mare dezvoltare datorit facilitilor oferite de componentele hardware ale diferitelor calculatoare. n principiu, ele simplific interaciunea dintre programe 2

i utilizatorii acestora. Astfel, diferite comenzi, date de intrare sau rezultate pot fi exprimate simplu i natural utiliznd diferite standarde care conin ferestre, bare de meniuri, cutii de dialoguri, butoane, etc. Cu ajutorul mouse-ului toate acestea pot fi accesate extrem de rapid i uor fr a mai fi nevoie s cunoti i s memorezi o serie ntreag comenzi ale sistemului de operare sau ale limbajului de programare. Toate acestea conduc la interfee simple i vizuale, accesibile unui segment foarte larg de utilizatori. Implementarea interfeelor este mult simplificat prin utilizarea limbajelor orientate spre obiecte, aceasta mai ales datorit posibilitii de a utiliza componente standardizate aflate n biblioteci specifice. Importana aplicrii conceptului de reutilizare a codului rezult din faptul c interfeele utilizator adesea ocup 40% din codul total al aplicaiei. Firma Borland comercializeaz o bibliotec de componente standardizate care pot fi utilizate folosind limbajul C++, bibliotec cunoscut sub numele Turbo Vision. De obicei, interfeele utilizator gestioneaz ecranul n mod grafic. O astfel de interfa utilizator se numete interfa utilizator grafic. Una din cele mai populare interfee utilizator grafice pentru calculatoarele IBM PC este produsul Windows oferit de firma Microsoft. Windows este un mediu de programare ce amplific facilitile oferite de sistemul de operare MS-DOS. Aplicaiile 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 interfeei utilizator, a unei aplicaii, folosind mouse-ul, iar aplicaia 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 aplicaie de 4-5 mii de instruciuni, n prezent, n condiiile folosirii limbajelor de programare orientate pe obiecte, aceast medie a ajuns la peste 25 de mii de instruciuni. Un limbaj de programare trebuie privit nu doar la suprafaa 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 aplicaiei i, implicit, a programului, prin filozofia de rezolvare a

problemelor folosind limbajul. Din aceste puncte de vedere, C-ul nu poate lipsi din cultura unui programator, iar pentru un profesionist Cul este, i mai mult, o necesitate vital, acesta fiind piatra de temelie pentru nelegerea 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 rspndite i mai utilizate dintre calculatoare, datorit gradului de accesibilitate i preului relativ sczut. Indiferent de tipul calculatorului, modul general de concepie, de alctuire i funcionare este acelai. Calculatorul este o main programabil. Dou dintre principalele caracteristici ale unui calculator sunt: 1. Rspunde la un set specific de instruciuni ntr-o manier bine definit. 2. Calculatorul poate executa o list prenregistrat de instruciuni, numit program. Calculatoarele moderne sunt electronice i numerice. Partea de circuite electrice i electronice precum i conexiunile fizice dintre ele se numete hardware. Totalitatea programelor precum i datele aferente acestor programe poart denumirea de software.
Echipamente de ieire

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

UPC Unitatea de procesare i control

Echipamente de intrare

Fig.1.1 Configuraia standard pentru utilizator

Partea hardware a unui calculator este format din totalitatea componentelor sale fizice. Toate calculatoarele de uz general necesit urmtoarele componente hardware: memorie: Permite calculatorului s stocheze, cel puin temporar, date i programe. dispozitive de stocare externe: Permit calculatoarelor s stocheze permanent programe i mari cantiti 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 instruciunilor care gestioneaz funcionarea unui calculator. dispozitive de ieire: Reprezint modalitatea prin care calculatorul transmite utilizatorului uman rezultatele execuiei 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 instruciunile. 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 prevzut cu o magistral (bus) prin care se gestioneaz modalitatea de transmitere a datelor ntre componentele de baz ale calculatorului. Magistrala reprezint o colecie de trasee electrice care leag microprocesorul de dispozitivele de intrare/ieire 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 interaciunea 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 distincie net ntre urmtoarele categorii de calculatoare:

PC (Personal Computer): Un calculator de dimensiuni mici, monoutilizator (single-user), bazat pe un microprocesor. n plus 5

acesta este dotat standard cu tastatur, mouse, monitor i dispozitive periferice de stocare a datelor.
Memoria secundar Echipament de intrare UNITATEA CENTRAL Echipament de ieire

Memoria principal

Fig. 1.2 Arhitectura minimal a unui sistem de calcul

staii de lucru (workstation): Un calculator monoutilizator de mare putere. Aceasta este asemntor unui PC dar are un microprocesor mai puternic i un monitor de nalt calitate (rezoluie 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 operaii ntr-o secund.

1.2.1 Microprocesorul
Microprocesorul este cea mai important i cea mai scump component a unui calculator de performanele acesteia depinznd n mare msur rezultatele ntregului sistem. Din punct de vedere fizic, microprocesorul este un cip ce conine un circuit integrat complex ce i permite s prelucreze informaii prin executarea unor operaii logice i matematice diverse (adunri, scderi, nmuliri, mpriri, comparri de numere). El este compus din dou pri importante: unitatea de execuie (EU Execution Unit) i unitatea de interfa a magistralei de date (BIU Bus Interface Unit). Prima component realizeaz efectiv

operaiile, iar cea de-a doua are funcia de transfer a datelor de la i nspre microprocesor. Microprocesorul reprezint de fapt unitatea central a unui calculator i ndeplinete o serie de activiti specifice cum ar fi: execut operaii aritmetice i logice, decodific instruciuni speciale, transmite altor cipuri din sistem semnale de control. Toate aceste operaii sunt executate cu ajutorul unor zone de memorie ale microprocesorului, numite registre. Orice microprocesor are un set finit de instruciuni pe care le recunoate 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 disprut, pentru a construi un circuit dedicat pentru un nou calculator. Designerul Ted Hoff a propus o soluie programabil, de uz general, i astfel s-a nscut circuitul Intel 4004. Au urmat la scurt timp chipurile 4040 si 8008 dar lor le lipseau multe din caracteristicile microprocesoarelor aa 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. Curnd dup aceea au aprut procesoarele Motorola 6800 i 6502 de la MOS Technology. Doi dintre proiectanii de la Intel au prsit firma, crend corporaia ZILOG care a produs chipul Z80 (compatibil cu 8080 dar cu set de instruciuni mai puternic i de dou ori mai rapid. Cipul Intel 4004 a fost primul procesor comercial, lansat la sfritul anului 1971. La un pre de circa 200$ i nglobnd 2300 de tranzistori, cipul 4004 dezvolta mai mult putere de calcul dect 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 operaii pe secund. Aceast invenie a contribuit la revoluionarea domeniilor de aplicaii ale computerelor, dnd startul unui adevrat galop de inovaii tehnologice. Urmtorul pas a fost n 1980, cnd IBM a inclus un procesor Intel n arhitectura primului PC. Astzi PC-urile sunt pretutindeni n jurul nostru. Un copil care lucreaz la o maina ce incorporeaz un procesor Pentium Pro beneficiaz de mult mai mult putere de calcul dect dispunea

guvernul SUA n perioada lansrii primelor echipaje umane ctre Lun. ntr-un numr aniversar al publicaiei Communications of the ACM, Gordon Moore, co-fondator al companiei Intel, era optimist n privina evoluiei PC-urilor i a microprocesoarelor: "complexitatea microprocesoarelor, care se msoar prin numrul de tranzistori pe cip, s-a dublat aproape constant la fiecare 18 luni, de la apariia primului prototip 4004. Aceasta evoluie exponenial a determinat o continu cretere a performanelor PC-urilor i o scdere a costului procesului de calcul. Pe cnd n 1991 un PC bazat pe procesorul Intel 486 costa aproape 225$ pentru o performan de un milion de instruciuni pe secund (MIPS), astzi, un sistem desktop ce utilizeaz un cip Pentium Pro este evaluat la circa 7$ pe MIPS. Nu se ntrevede nici o dificultate care s frneze aceast rat de dezvoltare".

1.2.2 Memoria
Microprocesorul are capacitatea de a memora date care urmeaz a fi prelucrate, ct i rezultatele intermediare. Se observ c rolul su principal este de a prelucra i transmite informaiile i rezultatele i deci capacitatea sa de memorare este mic neputnd stoca programe. De aceea, un calculator necesit i o memorie care s gzduiasc date i programe. Memoria este format din punct de vedere fizic din cipuri ce stocheaz informaia sub forma a dou niveluri de tensiune ce corespund valorilor 0 i 1 din sistemul de numeraie. Celulele de baz ale memoriei (ce pot avea valoarea 0 sau 1) se numesc bii i ele reprezint particulele cele mai mici de informaie din calculator. Pentru citirea informaiilor nu se folosesc bii n mod individual ci acetia sunt grupai ntr-o succesiune. Astfel o succesiune de 8 bii formeaz un octet (sau un byte) aceasta reprezentnd unitatea de msur a capacitii de memorie. Deoarece reprezentarea numerelor n calculator se face n baza 2 i nu n baza 10, aa cum suntem obinuii n mod normal s lucrm, 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.

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 ctre microprocesor i n care sunt ncrcate programele nainte de a fi executate de ctre microprocesor. Dac primele procesoare puteau accesa doar 1 MB de memorie astzi 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 iniial calculatorul (sistemul de operare). Aceste memorii pot fi doar citite (coninutul lor nu poate fi modificat). nscrierea coninutului acestor memorii se face de ctre fabricant, iar operaiunea de nscriere cu programe se mai numete arderea memoriilor. Circuitele de tip RAM (Random Acces Memory ) sunt memorii la care utilizatorul are acces i al cror coninut se terge la deconectarea calculatorului. n memoria RAM informaia este stocat temporar. De exemplu, programele de aplicaii curente i datele asociate acestor aplicaii sunt ncrcate n memoria RAM nainte de a fi prelucrate de ctre microprocesor. Deoarece capacitatea de memorie a unui calculator nu poate fi att de mare nct s poat pstra toate programele pe vrem s le executm, a aprut necesitatea existenei unor memorii externe, care s fie solicitate la nevoie. Rolul acestora l joac discurile i ele pot fi asemnate cu crile dintr-o bibliotec pe care le putem consulta ori de cte ori avem nevoie de anumite informaii. Primele discuri aprute pentru PC-uri, numite i dischete, floppy disk-uri sau discuri flexibile, permiteau stocarea a maximum 160 KB de informaie. Astzi mai exist doar dischete cu diametrul de 3.5 inch cu capacitatea de 1,44 MB. Existena acestora pare a fi pus ns n pericol de apariia CD-urilor reinscriptibile a cror capacitate de memorare depete 700 MB iar evoluia tehnologic, din ce n ce mai rapid, d semne c lucrurile nu se vor opri aici. Dischetele folosesc metode magnetice de memorare a informaiei motiv pentru care ele se mai numesc i suporturi magnetice de informaie. Principala component a unui calculator utilizat pentru memorarea programelor o reprezint hard discul. Acesta poate fi asemnat cu o dischet de mare capacitate, integrat ntr-o unitate

ncapsulat. Iniial, puine PC-uri prezentau hard discuri, dar cum preurile acestora au sczut considerabil, iar performanele i capacitile au crescut, n prezent toate calculatoarele prezint acest dispozitiv. n clipa de fa, capacitatea de memorare a unui hard disc a depit valoarea de 40 de GB.

1.2.3 Echipamentele periferice


Comunicarea om-main se realizeaz cu ajutorul echipamentelor periferice prin intermediul crora utilizatorul poate programa sau da anumite comenzi calculatorului sau poate vizualiza rezultatele obinute de ctre anumite programe. Principalele echipamente periferice ale unui calculator sunt urmtoarele: tastatura, mouse-ul, scanner-ul, monitorul i imprimanta. Ele pot fi grupate n echipamente de intrare cele prin care calculatorul primete informaii sau comenzi (tastatur, mouse, scanner) - i echipamente de ieire cele prin care calculatorul transmite informaii n exterior (monitor, imprimant). n continuare sunt prezentate cteva caracteristici ale fiecrui echipament. Tastatura este principalul dispozitiv de intrare al calculatorului prin intermediul cruia se transmit comenzi ctre unitatea central. Cuplarea la calculator a tastaturii se face prin intermediul unui cablu de conectare. Din punct de vedere al dispunerii tastelor, tastatura se aseamn destul de mult cu cea a unei maini de scris dar are i pri care o individualizeaz. Primele tastaturi au avut 83/84 de taste, pentru ca, ulterior, ele s fie mbogite prin dublarea tastelor existente sau adugarea altora noi, ajungndu-se la 101/102 taste. Din punct de vedere al funcionalitii lor ele pot fi mprite n patru categorii: taste alfanumerice; taste cu scopuri speciale; taste direcionale i numerice; taste funcionale. Tastele alfanumerice conin literele, cifrele i semnele de punctuaie i ocup partea central a tastaturii. Acionarea unei astfel de taste determin apariia caracterului corespunztor pe ecranul calculatorului. Tastele cu scopuri speciale sunt aezate n acelai bloc cu tastele alfanumerice i determin efectuarea anumitor aciuni fr

10

nscrierea de caractere pe ecran. Tastele de micare se afl situate n partea dreapt a tastaturii i ele funcioneaz n dou moduri, care pot fi comutate prin acionarea altei taste, aflate deasupra lor, pe care scrie NumLock. Dac ledul corespunztor acestei taste este aprins, modul de lucru este numeric, n caz contrar fiind comutat pe semnificaia direcional. Tastele funcionale sunt un grup de 12 taste situate n partea de sus a tastaturii avnd pe ele litera F urmat de un numr ntre 1 i 12. Acionarea acestor taste determin efectuarea unor operaii specifice de la program la program. Mouse-ul este tot un echipament de intrare mai uor de manevrat dect tastatura dar care poate efectua mai puine operaii. Totui, foarte multe aplicaii (n special aplicaiile grafice) nu mai pot fi concepute fr mouse. Un mouse are aspectul unei buci de spun, uor manevrabil, avnd dedesubt o bil poziionabil, cu sensibilitate i vitez reglabile. Micarea maouse-ului pe o suprafa plan este corelat cu deplasarea pe ecran a unui cursor cu o form deosebit: cruciuli, sgeat, etc. Declanarea unei aciuni se face prin poziionarea cursorului n zona corespunztoare i apsarea unuia dintre butoanele aflate pe partea posterioar. Iniial un mouse avea dou sau trei butoane. Acum exist mouse-uri cu 5 butoane i 2 rotie ce ndeplinesc o serie de funcii corespunztoare unor taste speciale. Folosirea mouse-ului uureaz mult munca utilizatorilor, nemaifiind necesar ca acetia s memoreze numrul relativ mare de comenzi corespunztor fiecrui produs, ca n situaia n care se folosete numai tastatura. Utilitatea mouse-ului este i mai evident n cazul aplicaiilor 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 prelucrri ulterioare. Astfel se pot manevra imagini foto, se pot crea efecte grafice speciale, care nu se pot obine prin metode tradiionale. Dup captare, imaginea poate fi prelucrat, mutat, mrit, micorat, rotit, colorat, umbrit, suprapus cu alt imagine etc. Cu un software de recunoatere optic a caracterelor datele sau documentele tiprite pe coli de hrtie pot fi transformate n fiiere, putndu-se realiza chiar o stocare a lor sub form de arhiv.

11

Monitorul - Sistemul video este format din dou pri: un adaptor (plac) video i un monitor sau display. Adaptorul video reprezint dispozitivul care realizeaz legtura (interfaa) cu calculatorul i se afl n interiorul acestuia. El va fi corespunztor tipului de monitor video care i este ataat. Adaptorul video realizeaz o rezoluie orizontal i una vertical. Rezoluia reprezint numrul de elemente, n cazul de fa puncte pixeli care pot fi afiate pe ecran. De exemplu, un monitor VGA, n mod video, are o rezoluie de 640 x 480 pixeli. Standardul VGA (Video Graphics Array) a fost introdus de ctre 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 mbuntire adus de standardul VGA a fost rezoluia superioar a caracterelor n modul text, precum i posibilitatea de a afia 256 de culori la un moment dat. Monitorul, denumit uneori i display, permite vizualizarea datelor introduse de la tastatur sau rezultate n urma execuiei unor comenzi sau programe, fiind ncadrat n categoria echipamentelor periferice de ieire. Ca pies principal, monitorul conine un tub de vacuum, similar cu cel de la televizor i trei tunuri de electroni (corespunztoare 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 bii. Standardul VGA a introdus un nou tip de monitor care utilizeaz semnale analogice pentru transferul informaiilor privind culoarea de la adaptorul video la monitor. Dac semnalele digitale prezint niveluri care indic prezena sau absena unui bit, semnalele analogice pot prezenta orice valoare ntre una minim i una maxim. Imprimanta - Reprezint un dispozitiv care poate fi ataat unui calculator, cu scopul tipririi de texte i grafic, putnd fi considerat un fel de main de scris automat. Pn n prezent au fost realizate un numr destul de mare de tipuri de imprimante pentru PC-uri, ele diferind att prin performane, ct i prin modalitile tehnice de ralizare. Fiecare dintre ele prezint avantaje i dezavantaje, ideal fiind a o folosi pe cea care corespunde cel mai bine tipului de lucrri

12

executate. n funcie de modul n care este realizat imprimarea se disting urmtoarele tipuri de imprimante: - imprimante matriceale cu 9, 18 sau 24 de ace realizeaz imprimarea prin impactul acelor peste o band de hrtie; - imprimante cu jet de cerneal funcioneaz prin pulverizarea fin a unor picturi de cerneal pe hrtia de imprimat; - imprimante laser ce utilizeaz o raz laser sau mici diode luminiscente care ncarc electrostatic un tambur de imprimare, corespunztor 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 imprimrii crete de la primul la ultimul tip prezentat, dar n mod corespunztor i preul echipamentului.

1.3. Programarea calculatorului


Programele de calculator, cunoscute sub numele de software, sunt constituite dintr-o serie de instruciuni pe care le execut calculatorul. Cnd se creeaz un program, trebuie specificate instruciunile pe care calculatorul trebuie s le execute pentru a realiza operaiile dorite. Procesul de definire a instruciunilor pe care le execut calculatorul se numete programare. Programele executate pe un calculator pot fi mprite n trei categorii: programe de aplicaie sunt acele programe care interacioneaz direct cu utilizatorul, specializate n realizarea unei categorii de prelucrri. Editoarele de texte, programele pentru gestiunea bazelor de date, programele de tehnoredactare asistat de calculator, de grafic etc. sunt programe de aplicaie. utilitare programe, care la fel ca programele de aplicaie, interacioneaz direct cu utilizatorul, dar, spre deosebire de acestea, realizeaz prelucrri de uz general. Utilitarele realizeaz o serie de operaii de gospodrie cum ar fi: copierea fiierelor, pregtirea discurilor magnetice pentru utilizare, crearea de copii de salvare, testarea echipamentului, etc. programe de sistem realizeaz legtura ntre componentele electronice ale calculatorului i programele de aplicaie i utilitare. Rolul programului de sistem este acela de a uura sarcina

13

programatorului, simplificnd ndeplinirea acelor sarcini care sunt comune marii majoriti a programelor de aplicaie: alocarea memoriei, afiarea caracterelor pe ecran i la imprimant, citirea caracterelor de la tastatur, accesul la informaiile 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 numr variabil de programe utilitare selectate conform cu necesitile programatorilor. Sistemul de operare este un program cu funcii de coordonare i control asupra resurselor fizice ale calculatorului i care intermediaz dialogul om-calculator. Sistemul de operare permite rularea programelor i pstrarea informaiilor pe disc. n plus, fiecare sistem de operare pune la dispoziia aplicaiilor 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 modificrile tehnologice, rmnnd n acelai timp compatibil cu hardware-ul anterior. Lanul 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 execuia altor programe. Sistemele de operare execut operaiuni de baz precum: recunoaterea unei intrri de la tastatur (preluare caracter), trimiterea unui caracter pentru afiare pe ecranul monitorului, gestionarea fiierelor i a directoarelor pe disc (floppy-disk sau hard-disk), controlul fluxului de date cu echipamentele periferice ca drivere de disc sau imprimante.

CALCULATOR SISTEM DE OPERARE APLICAII UTILIZATOR

14

Fig. 1.3. Comunicarea utilizator - calculator


Aplicaie

Disk-drive Sistem de operare Mouse Monitor

Tastatur

Imprimant

Fig. 1.4 Rolul sistemului de operare

Sistemul de operare al unui calculator este partea de software necesar i suficient pentru execuia oricror alte aplicaii dorite de utilizator. Un calculator nu poate funciona dect sub gestiunea unui sistem de operare. Orice aplicaie lansat n execuie de ctre un utilizator apeleaz la resursele puse la dispoziie de ctre sistemul de operare. Sistemul de operare interfaeaz calculatorul cu operatorul uman de o manier ct mai transparent cu putin astfel nct utilizatorul nu trebuie s fac eforturi mari de adaptare dac lucreaz cu arhitecturi hardware diferite. Pentru sisteme mai mari, sistemele de operare au responsabiliti i capabiliti i mai mari. Ele acioneaz ca un gestionar al traficului de date i al execuiei programelor. n principal sistemul de operare asigur ca diferite programe i diferii utilizatori s nu interfereze unele cu altele. Sistemul de operare este de asemenea responsabil cu securitatea, asigurnd inaccesibilitatea persoanelor neautorizate la resursele sistemului. Sistemele de operare se pot clasifica dup cum urmeaz: multi-user: Permit ca doi sau mai muli utilizatori s ruleze n acelai timp programe (utilizatori concureni). Anumite sisteme de operare permit sute sau chiar mii de utilizatori concureni. multiprocesor: Permit execuia unui program pe mai mult de un microprocesor.

15

multitasking: Permit mai multor programe s ruleze n acelai timp (execuie concurent). multithreading: Permit diferitelor pri ale unui program s fie executate concurent. timp real (real time): Rspund instantaneu la diferite intrri. 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 creia alte programe, numite programe de aplicaie, pot rula (pot fi executate). Programele de aplicaie trebuie s fie scrise pentru a rula pe baza unui anumit sistem de operare. Alegerea unui anumit sistem de operare determin n consecin mulimea aplicaiilor 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 interacioneaz cu sistemul de operare prin intermediul unor comenzi. Spre exemplu, sistemul de operare DOS accept comenzi precum COPY sau RENAME pentru a copia fiiere 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. Interfaele grafice cu utilizatorul (GUI, Graphical user interfaces) permit introducerea unor comenzi prin selectarea i acionarea 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) ataate diferitelor aplicaii disponibile pe calculatorul respectiv. Utilizatorul are multiple posibiliti de configurare a acestei intefee grafice. Primul sistem de operare creat pentru calculatoare a fost CP/M (Control Program for Microcomputers), realizat pentru calculatoarele pe 8 bii. O dat cu perfecionarea componentelor HARD s-a impus i necesitatea dezvoltrii unui SOFT adecvat. Astfel, n 1981, a aprut prima versiune a sistemului de operare MS-DOS. Sistemul de operare MSDOS (MicroSoft Disk Operating System) este destinat gestionrii 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 creterea

16

capabilitilor hardware ale calculatoarelor, acesta s-a transformat, prin dezvoltri succesive, n Windows. Indiferent de sistemul de operare utilizat, din punctul de vedere al utilizatorului, informaiile sunt scrise pe disc sub forma unor fiiere. Un fiier este o colecie de informaii grupate sub acelai nume. Un fiier poate fi un program executabil, un text, o imagine, un grup de comenzi sau orice altceva. Un fiier este identificat prin numele su. Numele unui fiier este format dintr-un ir de caractere (care n funcie de sistemul de operare este limitat la un anumit numr 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 fiiere, sistemul de operare creeaz nite fiiere speciale, numite directoare, care pot fi asemnate cu cuprinsul unei cri, deoarece ele conin numele fiierelor i adresa de nceput a acestora. De asemenea, un director poate conine la rndul su alte directoare crendu-se astfel o structur arborescent de directoare n care poate fi gsit foarte repede un anumit fiier.

1.3.2. Tipuri de fiiere


Fiierele se pot mpri n dou categorii executabile i neexecutabile. n prima categorie intr acele fiiere al cror nume scris n dreptul prompterului (n cazul sistemului de operare DOS) determin executarea unor activiti de ctre sistemul de operare. O parte dintre fiierele executabile sunt programe i sunt recunoscute prin extensia lor care poate fi EXE sau COM, altele fiind constituite n fiiere de comenzi proprii sistemului de operare, a cror extensie este BAT. Fiierele COM, numite adesea i comenzi, conin informaii n formatul imagine de memorie. Ele sunt mai compacte i mai rapide dect fiierele EXE, dar lungimea lor nu poate s depeasc 64 K. Fiierele EXE pot s ajung la dimensiuni mai mari prin segmentarea programului n fragmente a cror dimensiune s fie de maximum 64K. Dintre fiierele neexecutabile vom aminti cteva mai importante: fiiere text ; fiiere cu extensia SYS sau DRV, cunoscute sub numele de driver-e i care conin instruciuni despre modul n care sistemul de operare trebuie s controleze diferite componente hardware;

17

surse de programe scrise n diferite limbaje (cu extensiile PAS limbajul Pascal, C limbajul C, CPP limbajul C++, etc.); fiiere care conin informaii intermediare ntre cele n limbaj surs i cele executabile (extensiile OBJ, OVL); fiiere ce conin imagini (extensiile JPEG, GIF, BMP); fiiere ce conin sunete (extensiile WAV, MIDI, MP3) etc. 1.3.3. Construirea fiierului executabil
Instruciunile 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 neleag modul n care calculatorul interpreta diferitele combinaii 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 instruciunilor calculatorului ntr-o form mai accesibil programatorului. Dup ce programatorul scrie instruciunile ntr-un fiier - numit fiier surs, un al doilea program numit compilator, convertete instruciunile limbajului de programare n irurile 1 i 0 cunoscute sub numele de cod main. Pentru a obine un program executabil, orice program surs trebuie eventual translatat (tradus) n limbaj cod main sau cod obiect pe care l poate nelege microprocesorul. n urma acestui proces, alturi de fiierul surs apare i fiierul cod obiect (object file.) Aceast translatare sau traducere este efectuat de ctre 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 legturi n cod main executabil de microprocesorul sistemului de calcul. Programatorii scriu programe ntr-o form numit cod surs. Acest cod surs parcurge apoi civa pai nainte de a deveni program executabil. Pe scurt, un compilator este un program special care proceseaz instruciuni scrise ntr-un limbaj de programare particular i le

18

transform n limbaj main sau cod main pe care l poate executa microprocesorul. La ora actual un limbaj de programare este inclus ntr-un mediu de programare mai complex care include un editor de texte pentru introducerea instruciunilor n limbajul de programare de nivel nalt, un compilator i un editor de legturi folosite pentru translatarea codului surs n cod main. n mod tipic, un programator scrie declaraii ntr-un limbaj precum Pascal, C sau MATLAB folosind un editor. Se creeaz astfel un fiier numit fiier cod surs ce conine o colecie de instruciuni i declaraii scrise n limbajul respectiv. Primul pas este prelucrarea codului surs de ctre compilator, care translateaz instruciunile de nivel nalt ntr-o serie de instruciuni cod obiect. Cnd este lansat n execuie compilatorul acesta, ntr-o prim etap, lanseaz un analizor sintactic, gramatical, numit parser. Acesta parcurge i analizeaz sintactic, secvenial, n ordinea n care au fost introduse, toate instruciunile scrise n limbajul de nivel nalt. O instruciune de nivel nalt se translateaz ntr-una sau mai multe instruciuni specifice microprocesorului pentru care a fost conceput compilatorul. Aceste instruciuni ale microprocesorului sunt nlocuite cu codurile lor binare, fiecare instruciune a microprocesorului fiind codificat de ctre constructor. Codurile binare ale instruciunilor microprocesorului mpreun cu reprezentrile 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 ieire, numit n mod tradiional 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 ctre un editor de legturi (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 prelucrri se obine codul main, salvat ntr-un fiier cu extensia .exe. Acest cod main poate fi executat secvenial, instruciune cu instruciune, de ctre microprocesor. Cu alte cuvinte, un program executabil (executable program aflat pe disc cu extensia .exe) se obine prin salvarea pe disc a codului

19

main obinut prin prelucrarea succesiv a fiierului cod surs de ctre compilator (compiler) i apoi de ctre link-editor (linker).

Fig. 1.5 Procesul de elaborare a unui program executabil

Procesul de obinere a unui executabil este prezentat n figura de mai jos. Blocurile tridimensionale reprezint entitile principale ale mediului de programare: editorul de texte, compilatorul (compiler) i editorul de legturi (linker). Blocurile dreptunghiulare reprezint fiierele rezultate n urma aplicrii celor trei utilitare de sistem: n urma utilizrii editorului de texte obinem fiierul text surs cod cu numele generic nume. Dac folosim limbajul de programare C spre exemplu, se obine fiierul nume.c care se va salva pe disc. n urma lansrii n execuie a compilatorului, acesta preia fiierul surs i l prelucreaz corespunztor, semnalizndu-se toate erorile fatale pentru program sau avertismente utile programatorului n procesul de depanare. n cazul n care compilarea se efectueaz cu succes, se obine un fiier cod obiect, salvat pe disc sub numele nume.obj n urma lansrii n execuie a editorului de legturi, se preia fiierul cod obiect nume.obj i se leag cu toate modulele necesare (inclusiv funcii de bibliotec sau alte module externe), obinndu-se un program executabil (cod main) cu numele nume.exe la care adresele nu mai sunt simbolice ci absolute relativ la adresa de nceput a programului. La lansarea n execuie a programului fluxul de

20

informaie este complet controlat de ctre microprocesor, toate salturile de adres fiind fcute corespunztor. Interpretorul (interpreter) este un program care execut instruciuni scrise ntr-un limbaj de nivel nalt. Numai anumite limbaje de nivel nalt, spre exemplu BASIC, LISP sau MATLAB, sunt prevzute cu un interpretor. Exist dou modaliti 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 instruciunile de nivel nalt ntr-o form intermediar care este apoi executat. Prin contrast, un compilator translateaz instruciunile de nivel nalt direct n limbaj main (cod main). Programele compilate ruleaz n general mai rapid dect 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 execuie mediul BASIC, apoi se deschide fiierul surs-BASIC corespunztor i se lanseaz interpretorul de BASIC pentru execuia sa. Avantajul unui interpretor este acela al evitrii 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, cnd programatorul dorete adugarea unor mici poriuni de program pe care s le testeze rapid. De asemenea, interpretoarele permit o programare interactiv fiind des folosite n procesul de instrucie. n mediul de programare MATLAB, mediu interpretor, orice comand utilizator se execut imediat. Se pot edita i fiiere script, care conin secvene de comenzi care se execut secvenial. Programele de descriere a paginii (Page Description Languages) ca PostScript spre exemplu folosesc un interpretor. Fiecare imprimant PostScript are incorporat un interpretor care execut instruciuni PostScript. Asamblorul (assembler) este un program care face translaia unui program scris n limbaj de asamblare (limbaj de nivel sczut,

21

corespunztor microprocesorului sistemului de calcul) n limbaj cod main. Putem spune c asamblorul reprezint pentru limbajul de asamblare ceea ce reprezint compilatorul pentru limbajele de nivel nalt. Cum limbajul de asamblare conine instruciuni mai puin complexe dect cele de nivel nalt, asamblorul face practic o convertire biunivoc ntre mnemonicele limbajului de asamblare i codurile binare corespunztoare acestor mnemonice (instruciuni).

22

In ciu ile nlim l stru n baju de n el n se in iv alt trodu c de la tastatu r.

T ce se in ot trodu de la ce tastatu este v ibil pe m itor r iz on

E itord texte d e (ev tu in en al corporat nm ) ediu

Fiier tex ( fiiersu s ) t r cuex sia adecv ten at: num e.pas (lim P baj ascal) num (lim C e.c baj ) num e.cpp (lim ba jC + + ) nu e.bas (lim B SIC etc. m baj A ),

Fiieru su cun m l rs u ele u ei ex sia nm ten corespu z n toare se salv eaz dinm oria R Mpe h em A arddisk

C p om ilator S com e pileaz fi ieru su l rs

Se obin f e iieru l codob iect: num e.obj Se salv pe h eaz arddisk f iieru l num e.obj L k -ed e in itar (leg area tu ror m lelor n tu odu ecesare)

S obin fiieru e e l codm ain (execu il): tab num e.exe

Se salv pe h eaz arddisk fiieru l num e.exe

L sarea nex ie de c an ecu tre sistem l de operare u a ex tabilu i ecu lu num e.exe

Fig. 1.6 Detalierea procesului de generare a unui executabil

Capitolul II
23

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 purttor de informaie 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, adevrat) i F (false, fals) sau cele dou cifre binare1 i 0. T siu e en n H =1 igh

L =0 ow tim p Ca urmare a acestei asocieri spunem, prin abuz de limbaj, c un calculator numeric prelucreaz numere binare. Ca i un numr zecimal, un numr binar are mai multe cifre binare. Sistemul de numeraie binar folosit pentru reprezentarea informaiei n calculatoare este un sistem de numeraie ponderal, ntocmai ca sistemul de numeraie zecimal. Reprezentarea natural a numerelor la nivelul percepiei umane este cea zecimal, pe cnd reprezentarea proprie mainilor de calcul este cea binar. De aici rezult necesitatea compatibilizrii sau interfarii ntre aceste dou moduri de reprezentare a numerelor. Cum cele dou sisteme de numeraie sunt ponderale, o prim diferen este aceea c sistemul zecimal folosete ca ponderi puterile ntregi (pozitive sau negative) ale lui 10 (zece) iar sistemul binar va folosi puterile ntregi (pozitive sau negative) ale lui 2. 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

24

intern sunt necesare convenii de reprezentare: indiferent de tipul datelor, acestea vor fi colecii sau iruri de cifre binare crora, prin convenie, li se atribuie semnificaii. ntr-o prim instan, este foarte important s facem o distincie 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 convenii pentru reprezentarea tipurilor de date, att la nivel intern (n memoria calculatorului) ct i la nivel extern, al percepiei 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 fracionar. 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 regitrii microprocesorului. n acest format se prelucreaz numerele pentru implementarea diverselor operaii aritmetice. La nivelul calculatorului informaia nu poate fi dect 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 numeraie pentru reprezentarea numerelor. La nivel de reprezentare extern se folosete semnul - n faa unui numr n cazul n care acesta este negativ sau punctul care separ partea ntreag de cea fracionar. De asemenea, numerele ntregi interpretate fr semn se pot afia i n format binar, octal sau hexazecimal, deci n bazele 2, 8 sau 16. n cele ce urmeaz ne vom pune urmtoarele probleme: - cum se reprezint extern un numr natural - cum se reprezint intern un numr natural - cum se reprezint extern un numr ntreg negativ

25

cum se reprezint intern un numr 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 privete reprezentarea extern, nu sunt nici un fel de dificulti deoarece fiecare este familiarizat cu reprezentarea zecimal a numerelor naturale sau reale. Trebuie menionat de la nceput c orice tip de reprezentare pe care o vom folosi este ponderal n sensul c poziia cifrelor n numr nu este ntmpltoare ci conform cu o pondere corespunztoare unei puteri a bazei de numeraie. O caracteristic a reprezentrilor externe este folosirea unor convenii de format unanim acceptate i de altfel foarte naturale pentru un utilizator uman. Spre exemplu, pentru a exprima numere negative se folosete semnul - iar pentru reprezentarea numerelor reale se folosete punctul . pentru delimitarea prii ntregi de cea fracionar. De asemenea, suntem familiarizai i cu notaia tiinific n care intervine mantisa i exponentul (n virgul mobil). Reprezentarea zecimal este cea mai natural pentru utilizatorul uman. Vom oferi n continuare cteva exemple de reprezentri zecimale externe: Numr Reprezentare Reprezentare normal tiinific
37 -37 0.375 -0.375 0.00375 -0.00375 12.375 -12.375 37 -37 0.375 -0.375 0.00375 -0.00375 12.375 -12.375 0.37x102 -0.37x102 0.375x100 -0.375x100 0.375x10-2 -0.375x10-2 0.12375x102 -0.12375x102

n general dorim s obinem rezultatele numerice ale programelor pe care le concepem ntr-o form de reprezentare accesibil. Totui, calculatorul trebuie informat asupra formatului de reprezentare n care dorim s se afieze datele necesare. Aceasta nseamn c va trebui s specificm cte cifre se vor folosi la partea

26

ntreag i cte la partea fracionar sau dac dorim reprezentare tiinific sau nu. De altfel i operatorul uman face aceleai convenii de reprezentare. Spre exemplu tim c numrul
1 nu poate fi exact 3

reprezentat ca un numr zecimal, deci fixm un format de reprezentare. Dac formatul ale se limiteaz la 4 cifre zecimale, atunci vom scrie
1 0.3333 3

Limbajul C are o serie de funcii 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 numeraie 10, fie n orice alt baz. n general, un numr ntreg n baza b se poate reprezenta cu un numr predeterminat de cifre ci B = { 0,1,2,....., b 2, b 1} . Mulimea B reprezint mulimea cifrelor sau simbolurilor de reprezentare. Spre exemplu: b = 2 B = { 0,1} b = 7 B = { 0,1,2,3,4,5,6} b = 10 B = { 0,1,2,3,4,5,6,7,8,9} Noi suntem obinuii s folosim mulimea cifrelor zecimale. Dac totui se folosete o baz de reprezentare mai mare dect 10, atunci mulimea cifrelor zecimale nu mai este suficient pentru reprezentarea numerelor n acea baz. Spre exemplu s considerm baza b = 16 care va folosi 16 cifre hexazecimale (sau mai simplu hexa). Prin convenie, cele 16 cifre hexazecimale vor fi: Cifra Simbol Cifra Simbol
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 8 9 A B C D E F

27

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

N b = c n 1c n 2 ......c 2 c1c 0 c k B = { 0,1,2,....., b 2, b 1}


Valoarea numeric zecimal a numrului N b va fi:

N b = c n1 b n 1 + c n 2 b n 2 + ... + c1 b1 + c 0 b 0 =

) ck b k k =0
n 1

n continuare vom studia urmtoarele probleme: - cum se face conversia unui numr din baza b = 10 n baza
b=2

cum se face conversia invers, din baza b = 2 n baza b = 10 cum se face conversia dintr-o baz oarecare b1 n alt baz b2 Pentru a reprezenta un numr natural din baza 10 n baza 2, se mparte succesiv numrul la 2 i se utilizeaz resturile la aceste mpriri n ordinea invers de cum au fost obinute. a) Conversia din baza 10 n baza 2 i invers Fie de exemplu numrul zecimal 37. Reprezentarea sa binar va fi obinut astfel: 3710 = 1001012

37 36 1

2 18 18 0

2 9 2 8 4 2 1 4 2 2 0 2 1 0

Conversia invers, din baza 2 n baza 10 este simpl i utilizeaz ponderea 2:

100101 2

25 24 23 22 21 20 = 1 0 0 1 0 1 = 1x2 5 + 1x2 2 + 1x20=37

Cu aceste numere naturale putem face o serie de operaii aritmetice. Adunarea numerelor naturale binare se face ntocmai ca la cele n reprezentare n baza 10, dup regula: 0+0=0

28

0+1=1 1+0=1 1+1=0, transport 1 spre rangul urmtor Astfel, s facem adunarea 37+25 n binar: 37 1 0 0 1 0 1+ 25 11001 62 111110 Se observ cum se obine rezultatul corect. nmulirea se face n mod asemntor, ca o adunare repetat. Spre exemplu, s calculm 37x25

37 25

925

1 0 0 1 0 1x 11001 100101 100101 100101 1110 011101

11100111012 = 1x20 + 1x22 + 1x23 +1x24 +1x27 +1x28+1x29 = 1+4+8+16+128+256+512 = 92510 b) Conversia dintr-o baz oarecare b1 ntr-o alt baz b2 . Fie spre exemplu numrul 4911 care se dorete scris n baza 13. Pentru a realiza aceast conversie, vom folosi baza intermediar 10. Vom converti mai nti 4A11 n baza 10 i apoi numrul zecimal obinut l vom trece n baza 13. Se observ cum un numr n baza 11 poate conine i cifra A=10 iar un numr n baza 13 poate conine cifrele A=10, B=11, C=12.

4 A11 = 10 110 + 4 111 = 44 + 10 = 5410


54 52 2 13 4 0 4 13 0

5310 = 4213 4 A11 = 4213


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 obinuii. Fa de reprezentarea numerelor ntregi, la numerele reale intervine simbolul punct . care delimiteaz partea ntreag de partea fracionar. Cu alte cuvinte, cu ajutorul numerelor reale putem reprezenta i numere care nu sunt ntregi. Forma general a unui numr real reprezentat ntr-o baz oarecare b este:

N b = c n 1c n 2 ...c1c 0 c 1c 2 ...c m +1c m c k B = { 0,1,2,..., b 2, b 1}


N1 0 = cn 1b n 1 + cn 2b n 2 + c1b1 + c0b 0 + c 1b 1 + c 2 b 2 + c m + 1b m + 1 + c m b m =

Valoarea zecimal a numrului de mai sus va fi:

) ck b k k= m
n 1

Se observ cum punctul delimiteaz partea ntreag (exprimat printr-o combinaie de puteri pozitive ale bazei b) i partea fracionar (exprimat printr-o combinaie de puteri negative ale bazei b). Semnificaie pentru programator i pentru productorii de software sau microprocesoare au bazele de reprezentare b = 10 i b = 2 , 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 numr real cu n cifre pentru partea ntreag i m cifre pentru partea fracionar. Aa cum n sistemul zecimal reprezentm cu un numr finit de cifre zecimale numerele reale, acelai lucru se va ntmpla i n sistemul binar. Punctul binar va avea o semnificaie asemntoare cu punctul zecimal, care face separarea ntre partea ntreag i cea fracionar. Cifrele binare situate dup punctul binar vor corespunde puterilor negative ale lui 2. Astfel, n general, un numr real va avea reprezentarea binar:

N 2 = bm bm 1...b1b0 .b 1b 2 ...b n = bm 2 m + bm 1 2 m 1 + b1 21 + b0 20 + b 1 2 1 + b 2 2 2 + ...+ b n 2 n


Spre exemplu, numrul 12.25 va avea reprezentarea binar:

12.2510 = 1100.01 = 2 3 + 2 2 + 2 2

30

Partea ntreag a unui numr real se reprezint binar precum numerele ntregi (cu sau fr semn). Pentru a determina partea fracionar, se procedeaz n mod invers ca la partea ntreag. Astfel, dac partea fracionar zecimal se reprezint binar, atunci aceasta se nmulete succesiv cu 2. Dac rezultatul depete valoarea 1, atunci se nscrie un bit 1. Se continu mai departe cu dublarea valorii care depete 1. Dac rezultatul nu depete valoarea 1, atunci se nscrie un bit 0 i se continu multiplicarea cu 2. Spre exemplificare, vom vedea cum se obine reprezentarea binar a lui 12.25. Partea ntreag este 12. Ea se reprezint binar prin mpriri succesive la 2 i considerarea resturilor. Partea fracionar este 0.25 Partea P.F. x 2 Noua Bitul fracionar P.F. nscris P.F.
0.25 0.5 0 0.5 1 0 0 1

Obinem exact rezultatul cutat: 12.25 = 1100.01 S mai considerm un alt exemplu. S reprezentm numrul 5.37 Partea ntreag are reprezentarea 510 =1012
Partea fracionar P.F. 0.37 0.74 0.48 0.96 0.92 0.84 0.68 0.36 0.72 Etc.. P.F. x 2 0.74 1.48 0.96 1.92 1.84 1.68 1.36 0.72 1.44 Noua P.F. 0.74 0.48 0.96 0.92 0.84 0.68 0.36 0.72 0.44 Bitul nscris 0 1 0 1 1 1 1 0 1 Etc..

Obinem: 5.3710 = 101.010111101...2 Cu ct mai multe cifre binare vom reine dup punctul binar, cu att vom fi mai aproape de valoarea exact 5.37. Obinem un rezultat foarte important: Dei un numr zecimal poate avea un numr finit de cifre zecimale dup punctul zecimal, reprezentarea sa binar intern poate avea un numr infinit de cifre binare. Este valabil i reciproca: un numr real zecimal cu un numr

31

infinit de cifre se poate reprezenta ntr-o alt baz pe un numr finit de 1 cifre ( ex: = 0.3333 ...3...10 = 0.13 ). Cum orice reprezentare binar 3 intern este pe un numr finit de bii, numrul poate s nu fie reprezentat exact n calculator, ci cu o anumit aproximaie. Acest lucru este decisiv pentru a nelege importana lungimii reprezentrii numerelor n calculator. Cu ct un numr binar se reprezint pe un numr mai mare de bii, cu att precizia de reprezentare crete.

2.3 Reprezentarea intern a numerelor


Deoarece semnalul intern purttor de informaie ntr-un calculator este de tip binar, un numr zecimal (ntreg sau real) se va reprezenta intern n baza 2 cu ajutorul unui numr binar. O cifr binar se numete bit (Binary Digit) i poate fi fie 0 fie 1. n reprezentarea extern a numerelor am vzut c se poate folosi orice baz de numeraie (cu cifrele corespunztoare). De asemenea, numerele pot fi prefixate cu un simbol de semn i pot include n reprezentare i punctul de separaie ntre partea ntreag i cea fracionar. n reprezentarea intern acest lucru nu mai este posibil deoarece semnele plus (+), minus (-) sau punct (.) nu au nici o semnificaie pentru calculator. Orice numr (orice tip de dat) este reprezentat la nivel intern de un numr prestabilit de bii. Specialitii 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 aa-numitelor tipuri de date. Tipul unei date reprezint modul n care microprocesorul stocheaz n memorie i prelucreaz cu ajutorul regitrilor interni o dat. Tipul unei date se refer la lungimea sa de reprezentare (pe ci bii se reprezint data) precum i ce semnificaie au anumite cmpuri de bii din cadrul reprezentrii.

2.3.1. Reprezentarea intern a numerelor ntregi


Un numr binar este o colecie de cifre binare ponderate fiecare cu o putere a lui 2. Bitul corespunztor ponderii celei mai mari, situat

32

cel mai n stnga, se numete MSB (Most Significand Bit) iar cel corespunztor ponderii celei mai mici, situat cel mai n dreapta, se numete LSB (Less Significand Bit). n cazul reprezentrii binare a numerelor naturale, reprezentarea extern (cea perceput de operatorul uman) i cea intern (cea prelucrat de procesorul calculatorului) sunt asemntoare. Cum pentru operatorul uman operatorii + sau - semnific faptul c un numr este pozitiv sau negativ, este necesar o convenie pentru reprezentarea intern a numerelor ntregi negative. Aceast convenie prevede folosirea MSB pentru reprezentarea semnului numerelor ntregi. Dac numrul este pozitiv, se adaug n poziia MSB bitul de semn 0, iar dac numrul este negativ se utilizeaz n poziia MSB bitul de semn 1. Mai mult, numerele negative se reprezint n aa numitul complement fa de 2. Reprezentarea numerelor ntregi negative n complement fa de 2 Aceast form de reprezentare a numerelor negative necesit parcurgerea urmtorilor pai: pas1. Se reprezint modulul numrului negativ, folosind bit de semn (egal cu 0, evident) pas2. Se complementeaz toi biii numrului astfel obinut. Complementarea nseamn transformarea bitului 0 n bitul 1 i a bitului 1 n bitul 0. pas3. Numrul astfel obinut se adun cu 1. De exemplu, s reprezentm numrul -37. pas1. |-37| = 37
37 10 = 100101 2 = [ 0] 100101
bit semn

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 semnificaia numrului negativ. Pentru a vedea ce numr negativ este reprezentat, putem repeta procedeul de mai sus i obinem reprezentarea numrului pozitiv dat de modulul su. O modalitate mai simpl este alocarea ponderii corespunztoare bitului de semn dar pe care o considerm c reprezint un numr negativ. Astfel: 10110112 = -1x26 + 1x24 + 1x23 + 1x21 + 1x20 = -64 + 27 = -37

33

2.3.2 Adunarea, scderea i nmulirea numerelor ntregi


Aceste operaii 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 obinuit. De exemplu, dorim s calculm: 37-25 25-37 (-25)x37 (-25)x(-37) Pentru efectuarea acestor calcule, vom scrie reprezentrile cu bit de semn ale numerelor implicate: 2510 = 110012 = 011001 25 = 100111 10 2 3710 = 1001012 = 0100101 3710 = 1011011 Se observ c 25 i (-25) se reprezint pe 6 bii iar 37 i (-37) pe 7 bii. Deoarece am observat c biii unui ntreg cu semn nu au toi aceeai semnificaie, este nevoie s reprezentm numerele cu care lucrm pe un acelai numr de bii. La adunri sau scderi, biii de semn se vor afla n aceeai poziie (vor avea aceeai pondere) i vom obine astfel rezultate corecte. Pentru a avea o scriere pe un acelai numr de bii, se adaug (completeaz) la stnga bitul de semn de un numr corespunztor de ori. Astfel:
2510 = 1001112 = 1100111 25 = 0110012 = 0011001

37 25 = 37 + (25) = 0100101 + 1100111 0100101 + 1100111 0001100 = 1210

34

1110100 = 64 + 52 = 12 n continuare vom pune n eviden importana gamei de reprezentare, adic a domeniului de valori ale datelor. S considerm, spre exemplu, adunarea a dou numere cu semn reprezentate pe un octet (8 bii). Aceste numere sunt cuprinse n gama
= [ 128, 127 ] . Dac vom dori s adunm dou numere din acest domeniu i s reprezentm rezultatul tot pe un octet, putem avea surprize. De exemplu, s considerm operaiile (117-12) i (117+12). Se observ c operanzii sunt n gama de reprezentare a numerelor cu semn pe 8 bii. Prin prima scdere, ne ateptm s obinem un rezultat, 105, n aceeai gam de reprezentare. 117-12=117+(-12) = 01110101+11110100 = 01101001 = 10510, rezultat corect.
7 7

37 = 1011011 25 = 0110012 = 0011001

25 37 = 25 + (37 ) = 0011001 + 1011011 0011001 + 1011011

[ 2 , 2 1]

117+12 = 01110101+00001100 = 10000001 = -12710, rezultat evident incorect. Incorectitudinea provine de la faptul c rezultatul a depit gama de reprezentare. Dac rezultatul este interpretat pe 9 bii de exemplu, gama de reprezentare devine [ 256, 255] i rezultatul va fi 117+12 = 001110101+000001100 = 010000001 = 12910, rezultat corect. Ca o concluzie preliminar, reinem c pentru a obine rezultate corecte este necesar s precizm dac se lucreaz sau nu cu bit de semn i pe ci bii se face reprezentarea, pentru c numai n acest context interpretarea rezultatelor este corect. n ceea ce privete nmulirea numerelor ntregi cu semn (cu bit de semn), aici problema nu mai are o rezolvare asemntoare, n sensul c nu putem trata biii de semn la fel cu cei de reprezentare ai valorii. Astfel, procesorul studiaz biii de semn i ia o decizie n privina semnului rezultatului. De fapt, se realizeaz funcia logic XOR a biilor de semn. Numerele negative se vor lua n modul, iar operaiile de nmulire se vor face numai cu numere pozitive. La final,

35

funcie de semnul rezultatului, se ia decizia reprezentrii corecte a rezultatului. Spre exemplu, s calculm (-25)x37. Pentru aceasta, procesorul va primi pentru procesare urmtoarele dou numere: 37 x(25) = [ 0]100101 [1]100111 Se analizeaz separat biii 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 numrului (-25), care se obine prin complementarea fa de 2 a numrului binar 1100111: 11001110011000+1=0011001 Se va reine pentru procesare numai numrul (fr semn) 11001, care se va nmuli cu numrul (fr semn) 100101, obinnd, aa cum am artat mai sus, valoarea 1110011101. Mai departe, se adaug bitul de semn, 0 pentru numere pozitive, obinndu-se 01110011101. Acest ultim numr se va complementa fa de 2, obinndu-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 fr semn) i care este lungimea lor de reprezentare (toate trebuie s aib aceeai 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: 12.2510 = 1100 .012 01100 .01

01100 .01 10011 .10 + 0.01 = 10011 .11 10011 .112 = 2 4 + 21 + 2 0 + 2 1 + 2 2 = 16 + 3 + 0.75 = 12.2510

Pentru nmulirea numerelor reale rmn valabile considerentele de la numere ntregi.


n cazul de mai sus, problema reprezentrii numrului negativ a fost rezolvat cu ajutorul bitului de semn dar problema reprezentrii 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 reprezentrii

36

interne. Numerele binare ntregi fr semn au aceeai reprezentare att extern ct 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 ceilali bii ai reprezentrii. Toi ntregii cu semn, care au MSB=1, sunt reprezentai intern n complement fa de 2. Numerele reale se pot reprezenta identic cu cele ntregi cu semn, cu o precizare: nu se face o deosebire net ntre biii reprezentrii prii ntregi i cei ai reprezentrii prii fracionare. Acest tratament nedifereniat provine de la reprezentarea tiinific uzual cu mantis i exponent. Fie, spre exemplu, reprezentarea binar a numrului 12.25:

12.2510 = 1100.01 = 0.110001 x 2 4

Calculatorul poate reprezenta irul de bii 110001 i reine faptul c punctul se pune dup primii 4 bii ai reprezentrii. Acest lucru se ntmpl i n realitate. Deci, singura deosebire ntre reprezentarea numerelor reale i a celor ntregi const n faptul c numerele reale necesit o informaie suplimentar despre aa numitul exponent, n cazul nostru numrul 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 apariia pe pia a primelor calculatoare pentru neprofesioniti (aa numitele Home Computers) au fost procesoare capabile s prelucreze i s transmit n paralel 8 bii, a fost natural gruparea a 8 bii ntr-o entitate numit byte. 1B = 8b (adic un byte reprezint 8 bii) Procesoarele au evoluat, ajungndu-se n prezent la procesoare pe 64 de bii. Cum evoluia lor s-a fcut trecndu-se succesiv prin multipli de 8 bii, s-au impus i alte entiti de reprezentare a informaiei, pe care le vom prezenta sintetic n tabelul de mai jos. Denumire Dimensiune Nr. byte
Byte Word 1B 2B

Denumire echivalent
octet cuvnt

Notaie

Nr. biti
8b 16 b B W

37

Denumire
Double_Words Quad_Words Ten_Words

Dimensiune
4B 8B 10B 32 b 64 b 80 b

Denumire echivalent
Cuvnt dublu Cuvnt cvadruplu

Notaie
DW QW TW

A determina reprezentarea intern nseamn s determinm lungimea reprezentrii (de obicei n multipli de octei), modul de interpretare al biilor ce compun reprezentarea i gama de reprezentare, adic s determinm 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 avnd i anumite particularizri. 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 obine diverse particularizri. Modificatorii pot fi signed, unsigned, short, long. Ca o generalitate, numerele sunt reprezentate intern lundu-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 nelegem unitile de baz care se pot tasta (intrri de la tastatur), tipri la imprimant sau afia pe ecran. Tastatura reprezint, de exemplu, dispozitivul de intrare care conine de fapt o ntreag colecie de caractere ce pot fi emise prin apsarea unei taste. Pentru a fi receptat, emis sau prelucrat de ctre calculator, fiecare caracter are asociat un cod binar (o combinaie de bii) 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

38

mici ale alfabetului anglo-saxon (inclusiv litere specifice diverselor alfabete precum cel chirilic sau particulariti ale diferitelor ri: , , , , ... n romn, de exemplu). Se mai pot include caractere ce reprezint numere, semne de punctuaie sau alte caractere de control. Codul ASCII a standardizat aceast codificare, astfel nct 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:

b6

b5

b4

b3

b2

b1

b0

Bt d se n i e m Gama de reprezentare este cuprins ntre max = 27 1 = 127 [ 128, 127] min = 27 = 128 Dac se declar tipul unsigned char, atunci nu se mai consider (interpreteaz) bitul de semn i data se consider ntreag pozitiv, n gama max = 2 8 1 = 255 [ 0, 255] min = 0 Tabelele de mai sus conin codurile ASCII ale primelor 128 de caractere. Coloana D semnific valoarea zecimal (decimal) a octetului, coloana H reprezint aceeai valoare reprezentat n format hexazecimal (baza 16) iar n coloana Sym se reprezint simbolul afiat pe monitoarele PC. ntregul alfabet al limbajului C se regsete n mulimea primelor 128 de caractere ASCII. Restul de 128 de caractere se mai numete i set de caractere extins ASCII i poate fi vizualizat printr-un program simplu. Trebuie menionat faptul c reprezentarea datelor n format hexazecimal este foarte rspndit n tehnica programrii calculatoarelor. Avantajul reprezentrii interne a datelor n format hexazecimal const n folosirea unui numr mai mic de cifre (de 4 ori mai mic dect numrul de cifre binare).

39

Reprezentarea unui numr natural n format hexazecimal se realizeaz cu metoda mpririi succesive la 16 sau, mai simplu, pornind de la reprezentarea binar a numrului. Cum mulimea cifrelor hexa conine 16 simboluri (09 i A F), pentru codificarea celor 16 cifre avem nevoie de 4 cifre binare ( 2 4 = 16 ). Pentru a reprezenta un octet vom avea nevoie de 2 cifre hexazecimale i vom proceda astfel: - se divide octetul n dou grupe de cte 4 bii - se nlocuiete fiecare grup de 4 bii cu cifra hexazecimal pe care o codific. De exemplu, s presupunem c avem numrul 217.
217 10 = 11011001 2 = 1101 .1001 2 = D916 = 13 161 + 9 16 0 = 208 + 9 = 217

n acest mod, dac un numr are o reprezentare intern pe un numr de k octei, se poate reprezenta simplu cu ajutorul a 2k cifre hexazecimale. n tabelele de mai jos se prezint codificarea ASCII a caracterelor. Codurile corespunztoare 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 corespunztoare transmit spre calculator semnale binare corespunztoare codului 1010 0001, adic 61H sau 97 n zecimal. La fel se ntmpl cnd se lucreaz cu procesoare de text sau cnd se tiprete un document la imprimant. Sistemul de calcul manevreaz codurile ASCII corespunztoare literelor i cifrelor pe care utilizatorul le poate interpreta. D 0 1 2 3 4 H Sym D 0 Null 1 6 1 1 7 2 1 8 3 1 9 4 20 H 1 0 1 1 12 1 3 1 4 Sym D H Sym D 32 20 4 8 3 21 ! 4 3 9 3 22 " 5 4 0 3 23 # 5 5 1 3 2 $ 52 6 4 H 3 0 3 1 32 3 3 3 4 Sym 0 1 2 3 4

40

5 6 7 8 9

5 6 7 8 9

1 a 0 1 b 1 12 c 1 3 1 4 1 5 D 6 4 6 5 6 6 6 7 6 8 6 9 7 d e f

LF CR

21 1 5 22 1 6 23 1 7 2 1 4 8 25 1 9 26 1a 27 1 b 28 1c 29 1 d 3 1e 0 3 1f 1 H 5 0 5 1 52

3 7 3 8 3 9 4 0 4 1 4 2 4 3 4 4 4 5 4 6 4 7

25 % 26 & 27 ' 28 ( 29 ) 2a 2 b 2c 2d 2e 2f * + , . /

5 3 5 4 5 5 5 6 5 7 5 8 5 9 6 0 6 1 62 6 3

3 5 3 6 3 7 3 8 3 9 3a 3 b 3c 3 d 3e 3f

5 6 7 8 9 : ; < = > ?

H 4 0 4 1 4 2 4 3 4 4 4 5 4

Sym D @ 8 0 A 8 1 B 82 C D E F 3

Sym D P 96 Q R S T U V 97 98 99

H 60 61 62 63

8 5 3 8 5 4 4 8 5 5 5 8 5

10 64 0 10 65 1 102 66

Sym D H ` 112 7 0 a 11 7 3 1 b 11 72 4 c 11 7 5 3 d 11 7 6 4 e 11 7 7 5 f 11 7

Sym p q r s t u v

41

0 7 1 7 2 7 3 7 4 7 5 7 6 7 7 7 8 7 9

6 4 7 4 8 4 9 4a 4 b 4c 4 d 4e 4f

6 G H I J K L M 3 N 4 O 5

6 8 5 7 7 8 5 8 8 8 5 9 9 9 5a 0 9 5 1 b 92 5c 9 5 d 9 5e 9 5f

W X Y Z [ \ ] ^ _

10 3 10 4 10 5 10 6 10 7 10 8 10 9 11 0 11 1

67 68 69 6a 6b 6c 6d 6e 6f

g h i j k L M n o

8 11 9 120

6 7 7 7 8 121 7 9 122 7a 123 7 b 12 7c 4 125 7 d 126 7e 127 7f

w x y z { | } ~

2.3.3.2 Tipul int Acest tip se folosete pentru reprezentarea numerelor ntregi cu sau fr 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.
MSB S b30 Octetul 1 Octetul 2 Octetul 3 b0 LSB Octetul 4

Tipul int este identic cu signed int i utilizeaz o reprezentare pe 4B a numerelor ntregi cu semn. Reprezentarea pe 4 octei duce la posibilitatea mririi gamei de reprezentare astfel:

42

max = 231 1 3 3 ; 231 = 2 230 = 2 210 2 10 3 2 10 9 31 min = 2

( )
9

( )

Rezult c putem reprezenta numere ntregi n gama: unsigned int nu va mai lua n considerare bitul de semn, astfel nct reprezentarea intern este de forma din figura de mai jos. Evident,
max = 232 1 32 3 3 ; 2 = 4 230 = 4 210 4 10 3 4 10 9 min = 0

[ 2.1475 10 ] [ 2 10 , 2 10 ]
9 9

( )

( )

Gama de reprezentare modificatorilor short sau long.


MSB S b14

se

poate

schimba

cu

ajutorul

b0 LSB

short int se va reprezenta pe 2B, sub forma

unsigned short int va schimba gama de reprezentare n [ 0, 65535] long int se va reprezenta pe 8B i va conduce la o gam imens de reprezentare a numerelor ntregi, lucru dovedit de
6 2 63 = 2 3 210 8 1018 = 9.2234 1018

max = 215 1 ; 215 = 2 5 210 = 32 210 [ 32768, 32767 ] . 15 min = 2

gama 0, 1.844 1019 .

unsigned long int va considera numai numere ntregi pozitive n

( )

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 notaie tiinific, prin care orice numr se poate exprima ca un numr zecimal (deci, cu punct zecimal) multiplicat cu o putere a lui zece sau ca un numr real binar (cu punct binar) multiplicat cu o putere a lui 2.

43

5.2510 = 101.01 = 1.0101 x 2 2 = 0.10101 x 2 3


Se observ cum stocarea n calculator a unei date floating-point necesit trei pri: - bitul de semn (sign) - mantisa, fracia (significand) - exponent (exponent) Folosind formatul specific I80386, n limbajul C se disting trei tipuri de date reale: - float , cu reprezentare pe 4 octei (32 bii, double word) - double, cu reprezentare pe 8 octei (64 bii, quad word) - long double, cu reprezentare pe 10 octei (80 bii, ten word)
MSB b31 b30

b0 LSB S 31 30 S Exponent biased 23 22 Exponent = 8b Bias = 7FH=127 Significand

0 Significand = 23b float

63 62 52 51 Exponent = 11b S Bias = 3FFH=1023 79 78 S 64 63

0 Significand = 52b double

0 Significand = 52b long double

Exponent = 15b Bias = 3FFFH=16383

44

Tipurile float i double sunt formate pentru numere reale ce exist numai n memorie. Cnd un astfel de numr este ncrcat de procesor n stiva pentru numere reale (flotante) pentru prelucrare sau ca rezultat al prelucrrii, el este automat convertit la formatul long double (sau extended). n cazul n care acest numr se stocheaz n memorie, el se convertete la tipul float sau double. Toate cele trei subtipuri reale au un format comun, care va fi prezentat n continuare. Ceea ce le deosebete este numrul de bii alocai pentru exponent i pentru mantis, precum i interpretarea biilor mantisei (significand). Semnul are alocat n toate formatele un singur bit: 0 pentru numere pozitive i 1 pentru numere negative. Mrimea cmpului exponent variaz cu formatul i valoarea sa determin ci bii se mut la dreapta sau la stnga punctului binar. Cmpul significand este analogul mantisei n notaia tiinific. El conine toii biii semnificativi ai reprezentrii, deci biii semnificativi att ai prii ntregi ct i ai prii fracionare cu singura restricie ca aceti bii s fie consecutivi. Deoarece punctul binar este mobil, cu ct sunt mai muli bii alocai prii ntregi, cu att vor fi mai puini pentru partea fracionar i invers. Cu ct formatul este mai larg, cu att se vor reprezenta mai precis numerele. Pentru a salva un spaiu preios de stocare, nici unul dintre cele trei formate float nu stocheaz zerouri nesemnificative. De exemplu, pentru numrul 0.0000101 = 0.101x 2 4 cmpul significand va stoca numrul 101, nu i cele 4 zerouri nesemnificative ale prii fracionare. Pentru a salva i mai mult spaiu, pentru formatele float i double cmpul significand nu va conine primul bit semnificativ care obligatoriu este 1. Ctignd acest bit (numit bit phantom), se dubleaz gama de reprezentare. Formatul long double va conine totui bitul de semn 1 cel mai semnificativ. Punctul binar se pune exact naintea primului bit din cmpul significand, adic dup bitul 1 implicit (phantom). n cazul long double, se aplic dup primul bit 1. Pentru a uura operarea cu aceste numere, cmpul exponent nu este stocat ca un numr ntreg cu semn, ci este decalat (normalizat, cu bias) pentru a reprezenta numai numere pozitive (deci exponentul este interpretat ca numr natural fr semn). Biasul adugat se scade pentru a afla exponentul exact. Avantajul exponentului decalat const, pe lng faptul c nu mai are nevoie de bit de semn, n faptul c pentru a compara dou numere reale putem ncepe prin compararea biilor pornind de la MSB ctre LSB, cel mai mare fiind cel care are 1 la

45

primul bit diferit. Se decide astfel foarte rapid care numr este cel mai mare. Ca exemplu, s considerm un format float n care se stocheaz: Sign = 0 Exponent = 10000010 = 13010 Significand = 100100000 Valoarea real a exponentului va fi 130 - 127 = 3 Biii cmpului significand se obin adugnd MSB phantom, deci acetia vor fi 11001000...00 Numrul real care s-a stocat este: 0.110010...00 x 24 = 1100.1 =12.5 Reprezentarea intern a numrului 12.5, pe 4 octei (float), este urmtoarea: Semn
0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0

LSB Cu alte cuvinte, putem spune c reprezentarea intern a numrului real 12.5 este (n format hexazecimal): 12 .510 = 41480000 16 n cazul n care dorim s reprezentm numrul negativ 12.5, singurul bit care se va modifica va fi bitul de semn, care devine 1. Astfel, reprezentarea intern n format float a numrului negativ real 12.5 este:
Semn 1 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 LSB

12 .510 = C1480000 16

46

Dac numrul 12.5 se reprezint n formatul double, deci pe 8 octei, atunci reprezentarea sa intern se va realiza astfel: - bitul de semn va fi 0 - exponentul nu va mai fi pe 8 bii ca la tipul float, ci pe 11 bii, deci se va schimba i bias, care va fi 1023. Atunci:
exponent 1

+ 1023 = 1026 = 1024 + 2 = 1000000001 0


bias

significand va fi acelai ca la tipul float, dar reprezentat pe 52 de bii


Semn 0 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 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 1 0 0 0 0 0 0 LSB

12.510 = 4029000000 000000 16


Reinem c la numere reale numai bitul de semn indic dac numrul este pozitiv sau negativ, mantisa i exponentul se reprezint ca numere naturale fr 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 rspunde la aceast ntrebare, putem face urmtorul raionament: - exponentul cu semn este reprezentat pe 8 bii, deci este n gama de reprezentare [ 128, + 127 ] . - pentru a obine un exponent pozitiv, adugm numrul 128. - deoarece bitul phantom nu este reprezentat, exponentul trebuie micorat cu o unitate pentru a indica unde anume se poziioneaz exact punctul binar. - Exponent pozitiv = exponent +128 1 = exponent + bias de unde rezult evident faptul c bias = 127 n cazul tipului float.

47

n final s analizm un exemplu de procesare a produsului a dou numere reale. Vrem s calculm valoarea 5.25 x 1.5. Pentru aceasta, vom scrie cei doi factori ai produsului n forma: .10101 .11 5.2510 = 101.012 = .10101 23 1 ; 10101 1.510 = 1.12 = .11 2 3+1 10101 5.25 1.5 = [ ( .10101) ( .11) ] 2 .0111111

5.25 1.5 = .0111111 24 = 111.111 = 7.875


Se observ cum cmpurile exponent i significand sunt procesate separat, n final corelndu-se forma de reprezentare intern.

Game de reprezentare pentru numerele reale


Gama de reprezentare pentru fiecare din tipurile reale prezentate mai sus se calculeaz lund n considerare cel mai mare numr i cel mai mic numr posibil a fi scris n respectiva reprezentare. Astfel, exponentul este decisiv pentru gama de reprezentare. La tipul float, avem exponent _ biasmax = 255 exponent _ realmax = 255 127 = 128

nmax = 2128 = 28 210 = 256 (1024 )12 256 1036 = 2.56 1038 Valoarea maxim exact, calculat fr a aproxima ca mai sus:
38 210 = 1024 1000 = 103 este n max = 3.4028 10

( )12
( )

exponent _ biasmin = 0 exponent _ realmin = 0 127 = 127


12 12 13 nmin = 2127 = 2 7 210 = 23 210 210 = 8 210 8 10 39

Valoarea pozitiv minim exact este n min = 5.8775 10 39 La tipul double vom obine: exponent _ biasmax = 2047 exponent _ realmax = 2047 1023 = 1024
102 nmax = 21024 = 24 210 = 16 (1024 ) 306 16 10306 = 1.6 10307

( )

( )

( )

48

Valoarea maxim exact este n max = 1.7 10 308 exponent _ biasmin = 0 exponent _ realmin = 0 1023 = 1023
102 nmin = 21023 = 23 210 .125 10 306

Valoarea pozitiv minim exact este n min = 1.1125 10 308 Efectund aceleai consideraii i calcule pentru tipul long double, 4932 n max = 1.1 10 vom obine n min = 3.4 10 4932

( )

2.3.5. Codificare BCD


Procesorul I80386 este considerat primul procesor care are capacitatea de a procesa operaii 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 reprezentri este faptul c numerele BCD ocup spaiu de stocare mai mare dect numerele binare. Ele sunt mai uor de interpretat de ctre programatorul uman, pentru computer neavnd nici un fel de relevan. Procesorul 80386 poate manevra dou tipuri de formate BCD: mpachetat i nempachetat (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, crescnd capacitatea de stocare intern precum i gama de reprezentare pe un acelai numr de octei. Ambele codificri folosesc reprezentarea pe 4 bii a cifrelor zecimale. Spre exemplu, numrul 9817 se stocheaz pe 4 octei n format unpacked BCD i pe 2 octei 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 fr semn standard.

49

Toate formatele reale prezentate se conformeaz standardului IEEE 754 pentru reprezentarea numerelor n virgul mobil n format binar. Ca o concluzie la acest capitol, decisiv pentru nelegerea dezvoltrilor ulterioare, putem sintetiza urmtoarele: 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 ctre 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 obin 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 regitrii interni ai microprocesorului. Aceast reprezentare intern este legat de noiunea de tip de dat. Tipul de dat ntreg (integer) se reprezint intern pe 2, 4 sau 8 octei 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 octei i conine 3 cmpuri de bii distincte: bit de semn, cmp mantis i cmp exponent, de lungimi corespunztoare. Dac se specific explicit, toate numerele se pot defini fr semn (unsigned), caz n care calculatorul nu mai interpreteaz bitul de semn (MSB) diferit ci l include n cmpul de reprezentare al mrimii, crescnd gama de reprezentare.

50

Capitolul III ELEMENTELE DE BAZ ALE LIMABJULUI C


3.1. Crearea i lansarea n execuie a unui program C
Prezentm cteva comenzi simple pentru a lansa n execuie un program C folosind compilatorul BORLANDC v3.1 (versiunea pentru sistemul de operare DOS): dup setarea pe directorul corespunztor se tasteaz - bc pentru a intra n mediul BorlandC; - <Alt>-F pentru a selecta meniul File; - N pentru a deschide un fiier nou. se editeaz programul surs folosind editorul mediului BorlandC; Exemplu:
#include <stdio.h> void main (void) { printf("Primul program in C!"); }

se acioneaz tasta F2 i se indic numele fiierului (cu extensia .c sau .cpp) pentru salvarea programului surs pe disc de exemplu mesaj.c - (se recomand salvarea pe disc dup efectuarea oricrei modificri n programul surs pentru evitarea pierderii accidentale a acesteia); se realizeaz compilarea, link-editarea (realizarea legturilor) i lansarea n execuie a programului executabil mesaj.exe tastnd <CTRL>-F9; pentru a vizualiza rezultatele execuiei programului se tasteaz <Alt>-F5; 51

se revine n fereastra de editare a mediului acionnd o tast oarecare; pentru a nchide un fiier surs se tasteaz <Alt>-F3 iar pentru a iei din program n mediul de operare se tasteaz <Alt>-X. La fel de simplu poate fi utilizat i varianta pentru sistemul de operare WINDOWS a compilatorului BORLANDC v3.1: se lanseaz n execuie programul bcw.exe; se selecteaz din meniul File opiunea New, crendu-se fiierul noname00.cpp; n fereastra noname00.cpp se introduce codul programului; din meniul File se selecteaz opiunea Save As sau Save iar n csua de dialog care apare se va salva fiierul program cu extensia .cpp; din meniul Compile se selecteaz opiunea Build All ce va afia caseta de dialog Compiling (compilare); dac operaia de compilare se ncheie cu succes (nu exist erori de sintax n program) compilatorul va afia mesajul Press any key, caz n care compilatorul va crea fiierul executabil; lansarea n execuie a fiierului executabil se poate realiza folosind opiunea Run din meniul Run sau combinaia 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 nclcat o regul sintactic, programul nu va fi compilat cu succes. n acest caz, pe ecran va fi afiat un mesaj de eroare ce specific linia ce conine eroarea, precum i o scurt descriere a erorii. n exemplul urmtor programului i lipsete caracterul punct i virgul dup utilizarea funcie printf:
#include <stdio.h> void main (void) { printf("Primul program in C!") }

La compilare pe ecran vor apare urmtoarele mesaje de eroare:


Compiling NONAME00.CPP: Error NONAME00.CPP 5: Statement missing ; Error NONAME00.CPP 5: Compound statement missing }

52

Cu toate c n codul surs exist doar o eroare, compilatorul de C va afia 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 ctre mesajul de eroare i se corecteaz instruciunea respectiv.

3.2. Structura unui program C


Conceptul de baz folosit n structurarea programelor scrise n limbajul C este funcia. Astfel, un program n C este compus din cel puin o funcie i anume funcia main() sau funcia principal. La rndul ei, funcia main() poate apela alte funcii definite de utilizator sau existente n bibliotecile ce nsoesc orice mediu de dezvoltare de programare n C. Structura general a unei funcii C este de forma: tip nume_funcie (param_1, param_2, ...,param_n)

Instruciuni declarare tip parametri

Corp funcie=secven de instruciuni sau apel de funcii

} unde: - tip reprezint tipul de dat returnat de funcie (n mod implicit o funcie returneaz tipul int); - nume_funcie reprezint numele sub care funcia este cunoscut n program; - param_1,...,param_n - parametrii cu care funcia este apelat i al cror tip poate fi declarat direct n aceast list, sau prin instruciuni separate plasate imediat dup lista parametrilor. Corpul funciei este definit ca o secven de instruciuni i/sau apeluri de funcii i este delimitat de restul funciei prin paranteze acolade. n limbajul C exist dou categorii de funcii. O prim categorie este format de funciile ce returneaz o valoare la revenirea din ele n punctul de apel, tipul acestei valori fiind definit de de tipul funciei. Cealalt categorie conine funciile ce nu returneaz nici o valoare la revenirea din ele , pentru aceste funcii fiind utilizat cuvntul cheie

53

void n calitate de tip. El semnific lipsa unei valori returnate la revenirea din funcie. Exemple:
1) void f(void) { }

Funcia f nu are parametri i nu returneaz nici o valoare.


2) double g(int x) { }

Funcia g are un parametru x de tipul int i returneaz la revenirea n programul principal o valoare flotant n dubl precizie. Funciile C sunt n general uniti independente, compilabile separat. Instruciunile, la rndul lor, pot defini tipul unor date folosite n program, sau operaii ce trebuie executate prin program. Din punct de vedere sintactic, orice instruciune trebuie terminat cu caracterul ";", iar grupurile de instruciuni pot fi delimitate prin caracterele { i } pentru a forma uniti sintactice noi de tip bloc. Funciile apelate vor primi valori pentru argumentele (parametrii) lor i pot returna ctre funcia apelant valori de un anumit tip. Cu aceste precizri generale, dac avem un program compus din dou funcii, i anume funcia principal i o funcie apelat f(), atunci structura acestuia va fi de forma:
tip m ain( ) { -------f( ); / * apelul -------} f() * /

functiei

Functia

principala

___________
f( ) { }

Functia f( )

54

Programul ncepe cu execuia funciei main(). Aceasta funcie este folosit n general fr parametri. La rndul lor, funciile apelate pot fi scrise n limbaj C, sau realizate n alte limbaje de programare: asamblare, Fortran, Pascal etc.

3.3. Mulimea caracterelor


n programele C pot fi utilizate dou mulimi de caractere: mulimea caracterelor C i mulimea caracterelor C reprezentabile. Mulimea caracterelor C se compune din litere, cifre, semne de punctuaie care au o semnificaie specific pentru compilatorul C. Programele C sunt formate din combinaii ale caracterelor din mulimea de caractere C constituite n instruciuni semnificative. Mulimea caracterelor C este o submulime a mulimii caracterelor C reprezentabile. Mulimea caracterelor reprezentabile este format din totalitatea literelor, cifrelor i simbolurilor grafice. Dimensiunea mulimii de caractere reprezentabile depinde de tipul de terminal, consol etc. Fiecare caracter din mulimea caracter din mulimea caracterelor C are un neles explicit pentru compilatorul C. Compilatorul d mesaje de eroare cnd ntlnete caractere ntrebuinate greit sau caractere care nu aparin mulimii caracterelor C. n continuare sunt descrise caracterele i simbolurile din mulimea caracterelor C i utilizarea acestora.

3.3.1. Litere i numere


Mulimea 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 urmtoarele: ABCDEFGHIJKLMNOPRSTUVWXYZ abcdefghijklmnoprstuvwxyz 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.

55

3.3.2. Caractere whitespace


Spaiul, tab-ul, linefeed (linie nou), carriage return (revenire la captul rndului), form feed, tab-ul vertical i newline sunt numite caractere whitespace deoarece servesc pentru spaiere ntre cuvinte, aliniere la o nou coloan, salt la linie nou. Aceste caractere separ instruciuni definite de utilizator, constante i identificatori, de celelalte instruciuni dintr-un program. Compilatorul C ignor caracterele whitespace dac nu sunt folosite ca separatori sau 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 punctuaie


Caracterele speciale i de punctuaie din mulimea caracterelor C sunt folosite pentru mai multe scopuri. Tabelul urmtor prezint aceste caractere. Aceste caractere au o semnificaie special pentru compilatorul de C. Caracterele de punctuaie din setul de caractere reprezentabile C care nu apar n acest tabel pot fi utilizate numai n iruri, constante caracter i comentarii. Caracte Nume Caracte Nume r r
, . ; : ? ( ) [ ] Virgul Punct Punct i virgul Dou puncte Semnul ntrebrii Apostrof Ghilimele Parantez stnga Parantez dreapta Parantez dreapt stnga Parantez ! | / \ ~ _ # % & ^ * Semnul exclamrii Bar vertical Slash Backslash Tilda Underscore Diez Procent Ampersand Sgeat sus Asterisc

56

{ } > <

dreapt dreapta Acolad stnga Acolad dreapta Mai mare Mai mic

= +

Minus Egal Plus

3.3.4. Secvene escape


Secvenele escape sunt combinaii speciale de caractere formate din whitespace i caractere negrafice constituite n iruri i constante caracter. Ele sunt n mod tipic utilizate pentru a specifica aciuni precum carriage return i tab pe terminale i imprimante i pentru a furniza reprezentarea caracterelor care normal au neles special, cum ar fi ghilimelele (). O secven escape const dintr-un backslash urmat de o liter sau combinaii de cifre. Setul complet de secvene escape cuprinde: \a caracterul BEL - activare sunet \b caracterul BS (backspace) - revenire cu un spaiu \f caracterul FF (form feed) - salt de pagin la imprimant \n caracterul LF (line feed) - rnd 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. Secvenele \ooo i \xdd permit scrierea oricrui caracter din setul ASCII ca un numr octal format din trei cifre sau ca un numr hexagesimal format din dou cifre.
Exemplu: '\6' '\60' '\137' '\x6' '\x30' '\x5f' 6 ASCII 48 ASCII 95 ASCII

57

Numai cifrele octale (de la 0 la 7) pot apare ntr-o secven escape octal i trebuie s apar cel puin o cifr. De exemplu, caracterul backspace poate fi scris ca \10 n loc de \010. Similar, o secven hexagesimal poate s conin cel puin o cifr, iar a doua cifr poate fi omis. Totui, cnd se utilizeaz secvene escape n iruri, este indicat s se scrie toate cele trei cifre ale secvenei. Altfel, caracterul care urmeaz dup secvena escape ar putea fi interpretat ca o parte a secvenei, dac se ntmpl s fie o cifr octal sau hexagesial. De exemplu, secvena \0331 este interpretat drept ESC i 1. Dac am scrie \331, omind primul zero, atunci am avea o interpretare greit. Secvenele escape permit caractere de control negrafice pentru a fi transmise ctre display. Caracterele negrafice trebuie totdeauna reprezentate ca secvene escape. Plasnd necorespunztor un caracter negrafic n programe C, el are rezultat imprevizibil.

3.4. Identificatori
Identificatorii sunt nume ce sunt date variabilelor, funciilor 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 (_). Numrul de caractere care intr n componena unui nume nu este limitat. Numele sunt utilizate pentru a defini diferite variabile sau funcii 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 ct posibil scopul alegerii lor sau a datei pe care o reprezint.

3.5. Cuvintele cheie ale limbajului C


n limbajul C exist un numr de cuvinte care au o utilizare predefinit, numite cuvinte cheie. Utilizatorul nu poate s utilizeze aceste cuvinte pentru a denumi variabile sau funcii ntr-un program. Tabelul urmtor prezint cuvintele cheie ale limbajului C:

58

Cuvintele cheie ale limbajului C


auto break case else int static default do double if sizeof void float for goto signed unsigned register return short union continue struct switch typedef const extern volatile while char enum long

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 enumerri. 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 constante simbolice. O constant simbolic este un nume a crui valoare nu poate fi modificat n domeniul su. n C exist trei feluri de constante simbolice: 1. orice valoare de orice tip poate fi folosit ca i constant prin adaugarea cuvntului cheie const la definirea sa; 2. un set de constante ntregi definite ca o enumerare; 3. orice nume de vector sau funcie.

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 corespunztoare caracterului dat n setul de caractere al mainii. 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 operaii de calcul exact ca i ntregii. Caracterele pot fi reprezentate i prin secvene 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 numr hexazecimal, iar o constant care ncepe cu zero este un numr octal.

59

Pentru a reprezenta cifrele hexazecimale 10,...,15 se folosesc literele a,...,f sau literele mari corespunztoare. Notaiile octale i hexazecimale sunt utile n exprimarea succesiunilor de bii.
Exemplu: int int hex = 0xFF; oct = 011 ; /* numrul 255 n zecimal */ /* numrul 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 Observaie. n interiorul constantelor ntregi sau reale nu pot apare spaii 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 dorete 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 locaie 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 sfritul irului, marcnd astfel sfritul su. 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 cnd 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:

60

61 A Adresa+1 62 B Adresa+2 63 C Adresa+3 0 /0 Valoarea constantei ir ABC va fi Adresa, adic valoarea adresei locaiei n care se stocheaz primul caracter din ir. Ca o ultim remarc, vom face precizarea c din punctul de vedere al reprezentrii, constanta caracter A, spre exemplu, este diferit de consta ir A, care se stocheaz n memorie la o anumit adres i se termin cu caracterul null, deci are alocai doi octei. Fiecare constant ir conine cu un caracter mai mult dect numrul 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 numr 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 funcia strlen() nu numr i terminatorul \0. n interiorul unui ir se poate folosi convenia de notaie cu \. Aceasta face posibil reprezentarea caracterului ghilimele (") i \ n interiorul unui ir. Cel mai frecvent caracter folosit este caracterul '\n'=newline (NL). De exemplu, instruciunea:
printf ("beep at end of message\007\n");

Adresa

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 notaie (\n este caracter neafiabil). Nu este permis continuarea irurilor de caractere de pe o linie pe alta. Atunci cnd se include o constant numeric ntr-un ir de caractere utiliznd notaia octal sau hexazecimal este recomandat s se foloseasc 3 cifre pentru numr. Exemplu:
char v1[] = "a\x0fah\0129";//'a' 'x0f' 'a' 'h' '\012' '9'

61

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


Cuvntul cheie const poate fi inclus ntr-o declaraie a unui obiect pentru a determina ca tipul acestui obiect s fie constant i nu variabil.
Exemplu : const const int int model = 145; v[ ] = {1, 2, 3, 4};

Deoarece nu i se poate atribui o valoare, o constant poate fi doar iniializat. Declarnd ceva ca fiind constant, ne asigurm c valoarea sa nu se modific n domeniul su. Astfel instruciunile:
model = 165; model++; /* eroare */ /* eroare */

vor determina apariia unor mesaje de eroare corespunztoare. De notat c const modific un tip ceea ce nseamn c restricioneaz felul n care se poate utiliza un obiect, i nu modul de alocare. Pentru o constant, compilatorul nu rezerv memorie deoarece i se cunoate valoarea (precizat la iniializare). Mai mult, iniializatorul pentru o expresie constant este, de obicei (dar nu ntotdeauna), o expresie constant. Dac este aa, aceasta poate fi evaluat n timpul compilrii.

3.6.7. Enumerri
Folosirea cuvntului cheie enum este o metod alternativ pentru definirea constantelor ntregi, ceea ce este uneori mult mai util dect utilizarea lui const. De exemplu, enum {ASM , AUTO , BREAK }; definete 3 constante ntregi denumite enumeratori i le atribuie valori. Deoarece valorile enumeratorilor sunt atribuite implicit, ncepnd cu 0, aceasta este echivalent cu:
const const const ASM = 0; AUTO = 1; BREAK = 2;

62

O enumerare poate avea nume. De exemplu, definete o enumerare cu numele Keyword. Numele enumerrii devine sinonim cu int i nu cu un nou tip. Declararea unei variabile Keyword n loc de int poate oferi att utilizatorului, ct 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; } enum Keyword {ASM , AUTO , BREAK };

determin compilatorul s iniieze un avertisment deoarece sunt folosite numai dou din cele trei valori ale lui Key.

Capitolul IV OPERANZI I OPERATORI N C


4.1. Operanzi
O expresie, n limbajul C, este format dintr-un operand sau mai muli legai 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 funcii; - 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 execuie. Exemple:

63

1. 6353 este o constant ntreag zecimal de tip int i reprezint un operand constant de tip int. 2. float x2 reprezint declaraia 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 funciei produs. Aceast funcie reprezint un operand al crui tip coincide cu tipul valori returnate de funcia produs.

4.2. Operatori
Operatorii pot fi unari sau binari n funcie de numrul de operanzi crora li se aplic. Un operator unar se aplic unui singur operand, iar un operator binar se aplic la doi operanzi. Operatorul 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, relaionali i logici, operatori pentru prelucrare bii, precum i civa 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 prioritile operatorilor care aparin diferitelor clase de operatori, de asociativitatea operatorilor de aceeai prioritate i de regula conversiilor implicite.

4.2.1. Operatori aritmetici


Lista operatorilor aritmetici este urmtoarea: reprezint operatorul plus unar sau binar, n funcie de context reprezint operatorul minus unar sau binar, n funcie de context reprezint operatorul de nmulire (binar) reprezint operatorul de mprire (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. + * / %

64

Operandul operatorului unar minus trebuie s fie de tip aritmetic, iar rezultatul este numrul negativ corespunztor. 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 ctul, iar % ofer restul mpririi primului operand la al doilea. Dac al doilea operand al operatorului / sau % este zero, rezultatul este nedefinit. Pentru operanzi de tip ntreg este adevrat egalitatea: (a / b) * b + a % b = a n expresii operatorii binari + i - au aceeai preceden, care ns este mai mic dect a grupului *, / i %. Precedena ultimului grup este mai mic dect cea a operatorilor unari + i -. Folosirea parantezelor n expresii poate schimba precedena ntre operatori n timpul evalurii 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; - 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, operaiile 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. Aceti operatori pot fi folosii att ca prefix pentru variabile (de exemplu, ++i, --j) sau ca sufix (i++, j--). ntre aceste moduri de utilizare exist diferene. 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 ntrebuinarea valorii acesteia. Exemplu: Considerm secvena:
x = 10; y = ++x;

Dac se afieaz y, atunci vom gsi y = 11 deoarece mai nti se incrementeaz x i apoi se atribuie valoarea lui y. Dac scriem: x = 10;
y = x++;

vom gsi y=10 (mai nti se face atribuirea lui x la y i apoi incrementarea lui x).

65

Precedena tuturor operatorilor aritmetici este: nalt Sczut ++ -+ - (unari) * / % + - (binari)

Operatorii de aceeai preceden sunt evaluai de al stnga la dreapta.

4.2.3. Operatori relaionali


Operatorii relaionali permit compararea a dou valori i luarea unei decizii dup cum rezultatul comparrii este adevrat sau fals. Dac rezultatul operaiei este fals, atunci valoarea returnat este zero, iar dac este adevrat, valoarea returnat este 1. Operatorii relaionali folosii n C sunt: == != < <= > >= egal diferit mai mic strict mai mic sau egal mai mare strict mai mare sau egal

Operatorii relaionali au o preceden mai mic dect 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 cnd sunt aplicai unor expresii, conduc la valori ntregi 0 i 1, cu semnificaia fals i adevrat. Semantica acestor operatori se deduce din tabelul urmtor, unde e1 i e2 sunt dou expresii: e1 zero zero diferit de zero diferit de zero e2 zero diferit de zero zero diferit de zero e1&&e2 0 0 0 1 e1||e2 0 1 1 1 ! e1 1 1 0 0

66

Expresiile legate prin operatori logici binari sunt evaluate de la stnga la dreapta. Precedena operatorilor logici i relaionali este urmtoarea: nalt ! > >= < <= == != && ||

Sczut

Astfel, expresia: 10>5 && !(10<9) || 3<4 este adevarat; expresia: 1 && !0 || 1 este adevarat; expresia; 1 && ! (0 ||1) este fals. Programul urmtor tiprete numerele pare cuprinse ntre 0 i 100.
# include <stdio.h> main () { int i; for (i = 0; i <= 100; i++) if (! (i%2)) printf ("%d" , i); }

Operatorii logici i relaionali sunt utilizai n formarea instruciunilor repetitive precum i a instruciunii if.

4.2.5. Operatori logici la nivel de bit


Ne rentoarcem 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). Aceti operatori se aplic la nivel de bit sau grupuri de bii, dup tabelele:
AND & x OR | y 0 1 11 x 0 1 0 1 NOT ~ y 0 1 1 0 EXCLUSIVE-OR ^ y 0 1 x 0 1 0 1 1 0

0 1 y 0 0 0 1 0 1

n C, aceti operatori se aplic n paralel biilor corespunztori aflai n orice poziie. Din aceast cauz ei se mai numesc i operatori logici pe bit. Trebuie fcut distincia fa de operatorii logici, care folosesc notaii dublate: &&, ||, sau !. Operatorii logici au aceleai denumiri, dar ei trateaz ntregul operator ca pe o singur valoare,

67

adevrat sau fals. n scriere, se mai folosete i denumirea bit-and, bit-or, bit-negate sau exclusive-or. Ca exemplu, considerm operaia bit-not. Fie numrul binar: N2 = 0000000000000111 = 0x0007 = 710 Negarea sa pe bit se realizeaz cu instruciunea ~0x7 sau ~07 sau ~7 i valoarea sa va fi ~N2 = 1111111111111000 = 0xFFF8 sau 0177770 pe un computer cu ntreg pe 16 bii sau 0xFFFFFFF8 pe un computer cu ntreg pe 32 de bii. Exemplul urmtor 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 Deoarece limbajul C a fost gndit s nlocuiasc limbajul de asamblare n majoritatea operaiilor de programare, acesta trebuie s aib capacitatea s suporte toi (sau cel puin muli) operatorii utilizai n asamblare. Operatorii pentru prelucrarea biilor se aplic biilor dintr-un byte sau cuvnt, ambele variabile de tip char i short int. Aceti operatori nu se aplic tipurilor float, double, long double, void sau altor tipuri mai complexe. Operatorii pentru prelucrarea biilor utilizai n C sunt: & | ^ ~ >> << AND OR exclusive OR (XOR) complement fa de unu (NOT) deplasare dreapta deplasare stnga

Operaiile pe bii sunt utilizate de obicei n drivere, pentru testarea i mascarea anumitor bii. De exemplu, operaia AND poate fi folosit pentru tergerea unor bii dintr-un byte sau dintr-un cuvnt, OR poate fi folosit pentru setarea unor bii, iar XOR pentru complementarea unor bii i testarea egalitii a 2 bytes.

68

Observaie: Operatorii relaionali i logici (&&, ||, !,...) produc totdeauna un rezultat care este fie 0, fie 1, pe cnd operatorii similari destinai prelucrrii biilor pot produce orice valoare arbitrar, n concordan cu operaia specific. Operatorii >> i << deplaseaz toi biii dintr-o variabil la dreapta, respectiv la stnga. Forma general a operaiilor de deplasare este: variabil >> numr_de_poziii_bit - pentru deplasare dreapta. variabil << numr_de_poziii_bit - pentru deplasare stnga. n poziiile rmase libere, dup deplasare, se introduc zerouri. Operaiile de deplasare pot fi utile cnd se decodific perifericele de intrare cum ar fi convertoarele D/A (digital/analogice) i cnd se citesc informaii de stare. Operatorii de deplasare se pot utiliza i pentru realizarea cu rapiditate a operaiilor de nmulire i mprire. Se tie c o deplasare stnga cu 1 bit realizeaz nmulirea cu 2, iar o deplasare dreapta cu 1 bit realizeaz o mprire cu 2. Exemplu:
x x x x x x = 7; << 1; << 3; << 2; >> 1; >> 2; 0 0 1 0 0 0 1 1 1 0 0 0 0 0 0 1 1 1 0 0 1 1 1 0 14 1 1 0 0 0 0 112 0 0 0 0 0 0 192 1 0 0 0 0 0 96 0 1 1 0 0 0 24 7

Urmtorul exemplu evideniaz 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");

69

printf("\n");}

Programul produce urmtoarea ieire:


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

Dei limbajul C nu conine un operator de rotire, se poate realiza o funcie care s efectueze aceast operaie. De exemplu rotirea la stnga cu o poziie a numrului 10101010 ne conduce la numrul 01010101 i se realizeaz dup schema:
1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1

O posibilitate de realizare a operaiei de rotire necesit utilizarea unei uniuni cu dou tipuri de date diferite. De exemplu utiliznd uniunea:
union rotate { char ch[1]; unsigned int i; } rot;

Urmtoarea funcie 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;}

Att ntregul i ct i cele dou caractere ch[0] i ch[1] partajeaz primii doi octei din cei 4 rezervai de uniune. Numrul de rotit se introduce (pe 8 bii) n ch[0]. Se rotete apoi ntregul i (deci se rotesc toi cei 4 octei care i corespund). Se testeaz MSB al lui ch[0] care se gsete n urma rotirii n poziia LSB din ch[1]. Dac este 1, atunci se seteaz la 1 LSB din ch[0], realizndu-se astfel operaia de rotaie. Un exemplu de program care s utilizeaze aceast funcie:
# include <stdio.h> union rotate { char ch[1];

70

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

Acest program realizeaz rotirea numrului 14710=100100112 cu 6 poziii


10010011 00100111 01001110 10011100 00111001 01110010 11100100

Programul de mai sus funcioneaz pentru numere reprezentabile pe un octet (mai mici de 255). Dac dorim s facem o rotire pe doi octei, 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++) {

71

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

4.2.6. Operatorul de atribuire


n C, operatorul de atribuire (asignare) este semnul egal (=). Valoarea expresiei din dreapta se atribuie variabilei din stnga 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 stnga:
x = y = z = 0 este echivalent cu (x=(y=(z=0)));

O expresie de atribuire de forma x = x + 5 n care variabila din stnga apare imediat dup operatorul = se poate scrie ntr-o form compact de tipul x += 5, unde operatorul += este tot un operator de atribuire. Majoritii operatorilor binari le corespund operatori de atribuire de forma "op = " unde op poate fi : +, -, *, %, <<, >>, &, ^ , Operatorii de atribuire (asignare) sunt:

72

= , += , -= , *= , /= , %= , <<= , >>= , &= , ^= , |= 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 numrul de octei necesar memorrii variabilei sau tipului datei care este operandul su. 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 numr ntreg, fr semn. Fiierul standard stddef.h definete tipul size_t al rezultatului oferit de operatorul sizeof. Dac sizeof se aplic unui tablou, rezultatul este numrul total de octei din tablou. Exemplul din programul urmtor prezint dimensiunea principalelor tipuri de date:
# include <stdio.h> # include <stddef.h> 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 execuiei acestui program, se va afia (rezultatele depind de tipul de procesor sau de compilator):
Tip Tip Tip Tip Tip Tip Tip caracter pe 1 octet short int pe 2 octeti int pe 4 octeti long int pe 4 octeti float pe 4 octeti double pe 8 octeti long double pe 8 octeti

4.2.8. Operatorul ternar ?


Operatorul " ? " poate fi utilizat pentru a nlocui instruciunea if / else avnd forma:

73

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 adevrat, 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 dect 9, y va primi valoarea 200. Acelai program scris cu if /else va fi:
x = 10; if (x > 9) y = 100; else y = 200;

n alctuirea expresiilor din declaraia operatorului ternar " ? " pot fi folosite i funcii: 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 afia " S-a introdus zero". Dac se introduce alt numr, atunci programul va executa att funcia f1(), ct i funcia f2().

4.2.9. Operatorul virgul


Operatorul virgul se utilizeaz ntr-un ir n care se introduc mai multe expresii. Astfel, instruciunea:
x = (y = 3, y+1),

are c efect atribuirea valorii 4 variabilei x.

74

Deci expresiile separate prin virgul sunt evaluate de la stnga la dreapta, prima expresie evaluat cptnd valoarea void. Dac se utilizeaz un operator de atribuire, valoarea atribuit variabilei din stnga operatorului de atribuire este valoarea ultimei expresii din dreapta, dup evaluare. Exemplu:
y = 10; x = (y = y - 5, 30 / y);

Variabila x va cpta valoarea 6. Observaie Deoarece operatorul virgul are o preceden mai mic dect operatorul de atribuire, pentru ca atribuirile s se fac corect, trebuie utilizate paranteze.

4.2.10. Operatorul de forare a tipului sau de conversie explicit (expresie cast)


Adesea se dorete specificarea conversiei valorii unui operand spre un tip dat. Acest lucru este posibil folosind o construcie de forma: (tip) operand Printr-o astfel de construcie valoarea operandului se convertete spre tipul indicat n paranteze. n construcia de mai sus (tip) se consider c este un operator unar. Acest operator este cunoscut sub numele de operator de forare 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 funcie oarecare f are un parametru de tip double. Pentru ca aceast funcie s poat fi apelat cu un parametru int n (n este un parametru de tip ntreg) acesta trebuie mai nti 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 forare a tipului fiind unar, are aceeai prioritate ca i ceilali operatori unari ai limbajului C.

4.2.11. Operatorii parantez


Parantezele rotunde se utilizeaz fie pentru a include o expresie, fie la apelul funciilor. O expresie inclus n paranteze rotunde formeaz un operand. n acest mod se poate impune o alt ordine n

75

efectuarea operaiilor, dect cea care rezult din prioritatea i asociativitatea operatorilor. Operanzii obinui 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 construciile:
(a-5+b)++ --(a+b) &(a*b)

sunt eronate. La apelul unei funcii, lista parametrilor efectivi se include ntre paranteze rotunde. n acest caz se obinuiete s se spun c parantezele rotunde sunt operatori de apel de funcie. Parantezele ptrate include expresii care reprezint indici. Ele se numesc operatori de indexare. Parantezele sunt operatori de prioritate maxim. Operatorii unari au prioritatea imediat mai mic dect 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 memorie alocat unei date. n forma cea mai simpl, acest operator se utilizeaz n construcii 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. Ali operatori ai limbajului C


n limbajul C se mai utilizez i operatorii: * , . i -> Operatorul * unar (a nu se confunda cu operatorul aritmetic binar de nmulire) se utilizeaz pentru a face acces la coninutul unei zone de memorie definit prin adresa ei de nceput. Se obinuiete s se spun c operatorul de adres & este operator de refereniere, iar operatorul * este operator de derefereniere. Operatorii . i -> se utilizeaz pentru a se accesa componentele unei structuri. Ei au prioritate maxim, avnd aceeai prioritate cu parantezele.

76

4.2.14. Regula conversiilor implicite i precedena operatorilor


Regula conversiilor implicite se aplic la evaluarea expresiilor. Ea acioneaz atunci cnd un operator binar se aplic la doi operanzi de tipuri diferite. n acest caz, operandul de tip inferior se convertete 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 acelai tip, atunci se execut operatorul respectiv, iar tipul rezultatului coincide cu tipul comun al operanzilor. Dac rezultatul aplicrii operatorului reprezint o valoare n afara limitelor tipului respectiv, atunci rezultatul este eronat (are loc o depire). Exemplu: Rezultatul mpriirii 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 execuia operatorului, conform algoritmului umtor: 1.Dac unul din operanzi este de tip long double, atunci cellalt operand se convertete spre tipul long double iar tipul rezultatului aplicrii operatorului este de asemenea de tip long double. 2.Altfel, dac unul din operanzi este de tip double atunci cellalt operand se convertete spre tipul double iar tipul rezultatului aplicrii operatorului este de asemenea de tip double. 3.Altfel, dac unul din operanzi este de tip float atunci cellalt operand se convertete spre tipul float iar tipul rezultatului aplicrii operatorului este de asemenea de tip float. 4.Altfel, dac unul din operanzi este de tip unsigned long atunci cellalt operand se convertete spre tipul unsigned long iar tipul rezultatului aplicrii operatorului este de asemenea de tip unsigned long. 5.Altfel, dac unul din operanzi este de tip long atunci cellalt operand se convertete spre tipul long iar tipul rezultatului aplicrii operatorului este de asemenea de tip long. 6.Altfel, unul din operanzi trebuie sa fie de tip unsigned, cellalt de tip int i acesta se convertete spre tipul unsigned, iar tipul rezultatului aplicrii operatorului este de tip unsigned.

77

Precedenele operatorilor C sunt prezentate n tabelul urmtor. Operatorii aflai pe aceeai linie au aceeai prioritate. Ei se asociaz de la stnga la dreapta, exceptnd operatorii unari, condiionali i de atribuire, care se asociaz de la dreapta la stnga. Precedena nalt Operatorul () [ ] -> . ! ~ ++ -- - (type) * & sizeof * / % + << >> < <= > >= == != & ^ | && || ?: = += -= *= /= ,

Sczut

Capitolul V INSTRUCIUNI

Limbajul C posed un set variat de instruciuni, set care i permite s realizeze principalele compuneri de operaii: secvenierea, repetiia cu test final, repetiia cu test iniial, repetiia cu numr cunoscut de pai, decizia i selecia, saltul necondiionat, ieirea prematur dintr-un ciclu. Instruciunile pot fi clasificate n: instruciuni etichetate, instruciuni expresie, instruciuni compuse, instruciuni de selecie, instruciuni repetitive, instruciuni de salt.

78

5.1. Instruciuni etichetate (instruciunea goto)


Instruciunile etichetate posed etichete ca prefixe i au forma:
etichet:instruciune

Eticheta format dintr-un identificator definete identificatorul ca destinaie pentru o instruciune de salt, singura utilizare a sa fiind ca destinaie a unei instruciuni goto. Etichetele nu pot fi redeclarate. Etichetele sunt locale n corpul funciei n care sunt definite. Instruciunea goto are urmtorul format:
goto etichet

La ntlnirea instruciunii goto, se realizeaz un salt la instruciunea prefixat de eticheta aflat dup instruciunea goto. Deoarece o etichet este local n corpul unei funcii rezult c ea este nedefinit n afara corpului funciei respective, deci, o instruciune goto nu poate face salt la o instruciune din afara corpului funciei n care este definit. Nu este recomandat utilizarea abuziv a acestei instruciuni deoarece programul devine mai puin lizibil i pot apare erori logice n program foarte greu de detectat. Instruciunea goto se utilizeaz n special pentru ieirea din mai multe cicluri imbricate. Exemplu: Urmtorul program utilizeaz instruciunea goto pentru a afia numerele de la 1 la 100:
#include <stdio.h> void main(void) { int nr=1; eticheta: printf(%d, nr++); if (nr<=100) goto eticheta; }

5.2. Instruciuni expresie


Cele mai multe instruciuni sunt instruciunile expresie, care au forma:

79

[ expresie ]; unde expresie este opional. Majoritatea instruciunilor expresie sunt atribuiri sau apeluri de funcii. Deci, o instruciune expresie const dintr-o expresie urmat de caracterul ";".
Exemplu: a = b * c + 3; printf ("FAC. DE AUTOMATICA");

Dac expresia expresie de mai sus lipsete, construcia ; se numete instruciune vid. Aceasta nu are nici un efect, dar este utilizat pentru a nlocui un corp vid al unei iteraii sau pentru a plasa o etichet.

5.3. Instruciuni compuse


O instruciune compus este o posibil list de declaraii i/sau instruciuni nchise ntre acolade. Exemplu:
{ a = b + 2; b++; }

O instruciune compus se numete bloc. Un bloc ne permite s tratm mai multe instruciuni ca pe una singur. Corpul unei funcii este o instruciune compus. Domeniul de vizibilitate al unui identificator declarat ntr-un bloc se ntinde din punctul declaraiei pn la sfritul blocului. Identificatorii utilizai ntr-un bloc pot fi ascuni prin declaraii de acelai nume n blocurile interioare blocului iniial. 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); {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); }

80

5.4. Instruciuni de selecie


5.4.1. Instruciunea if
O instruciune if cu care n C se implementeaz o structur de control de selecie sau o structur alternativ, are urmtorul 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 (condiie adevrat), atunci se execut instructiune1; altfel, dac valoarea expresiei este zero (condiie fals), se execut instructiune2. n ambele cazuri, dup executarea lui instructiune1 sau instructiune2, controlul este transferat la instruciunea ce urmeaz dup if. Aici, prin instructiune1 sau instructiune2 se nelege o instruciune simpl, o instruciune compus (un bloc) sau o instruciune vid. Poriunea else instructiune2; este opional, n acest fel putnduse obine o structur de selecie cu o ramur vid de forma:
if (conditie) instructiune;

Exemplu: Urmtorul program citete dou numere i afieaz 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 instruciune if este opional, apare o ambiguitate atunci cnd else este omis dintr-un if inclus (ncuibat). n C acest lucru se rezolv prin asocierea lui else cu cel mai apropiat if. De exemplu, n secvena:
if (x) if (y) else printf ("1"); printf ("2");

else este asociat cu instruciunea if(y). Dac dorim ca else s fie asociat cu if(x) trebuie s utilizm acolade, astfel:
if (x) { if (y) printf ("1");

81

} else printf ("2");

Secvena anterioar este echivalent cu:


if (x) { if (y) printf ("1"); else ;} else printf ("2");

5.4.2. Instruciuni de selecie multipl: if - else if


ntr-o instruciune if se poate include, pe o ramur, o alt instruciune if. n acest fel se creeaz posibilitatea de a codifica structuri de selecie multipl, folosindu-se perechi else if. O asemenea construcie este de forma:
if (conditie1) instructiune1; else if (conditie2) instructiune2; else if (conditie3) instructiune3; . . . . . . . . . . . . . . . . else if (conditieN) instructiuneN; else instructiuneN+1;

n acest caz, condiiile sunt testate n ordine. Dac una din ele este adevrat, atunci este executat instruciunea corespunztoare, dup care controlul este transferat la instruciunea urmtoare din program. Codul pentru fiecare alternativ poate fi format dintr-o instruciune simpl (inclusiv instruciunea vid) sau dintr-un bloc delimitat prin { i }. Dac nici una dintre expresii nu este adevrat, atunci se execut secvena corespunztoare ultimei alternative introdus prin else. Aceast ultim alternativ nu este obligatorie, structura putndu-se ncheia dup secvena notat cu instructiuneN. Exemplu: Considerm un program care realizeaz conversiile inch-cm i cm-inch. Presupunem c indicm unitatea intrrii 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);

82

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


ntr-o instruciune de selecie switch, se compar, pe rnd, o valoare cu constantele dintr-o mulime i n momentul gsirii unei coincidene se execut instruciunea sau blocul de instruciuni asociate acelei constante. Forma general a instruciunii 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 }

Instruciunea switch realizeaz transferul controlului la una din secvenele de instruciuni dac valoarea variabila ce trebuie s aib tipul ntreg coincide cu una din constantele de dupa case. Secvena de instruciuni se execut pna se ntlnete break, dup care se trece la instruciunea imediat urmtoare dup switch. Dac nu se gsete nici o coinciden, se execut secvena de instruciuni de dup default, iar dac default lipsete, deoarece prezena acesteia este opional, se trece la instruciunea urmtoare. Exemplu: Decizia din exemplul anterior poate fi realizat i astfel:
# include <stdio.h>

83

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

Observaie:Constantele case trebuie sa fie distincte. Pentru a iei din instruciunea switch se folosete instruciunea 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 : 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; } }

84

Rulnd acest program, vom obine:


Now is the time for all good men the time for all good men to to . . .

Instruciunea switch este foarte eficient n scrierea programelor care afieaz pe ecran o list de opiuni (un meniu) din care utilizatorul alege cte una i o execut. Instruciunile switch pot fi i incluse (ncuibate) una n alta.

5.5. Instruciuni repetitive


5.5.1. Instruciunea for
Forma general a instruciunii for este:
for (initializare; conditie; incrementare) instructiune;

unde:

- initializare este o instruciune de atribuire utilizat pentru iniializarea variabilei de control a ciclului. Nu exist nici o restricie privitoare la tipul su; - condiie este o expresie relaional care se testeaz naintea fiecrei iteraii: dac condiia este adevrat (diferit de 0), ciclul se continu; dac condiia este fals (egal cu 0), instruciunea for se ncheie; - incrementare se evalueaz dup fiecare iteraie specificnd astfel reiniializarea ciclului. Exemplu: Urmtorul program afieaz 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); }

Nu ntotdeauna ciclul for trebuie s se desfoare n sensul creterii variabilei de control. Putem crea cicluri for n care variabila de control se decrementeaz. Exemplu: Programul urmtor afieaz 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 restricii n incrementarea sau decrementarea variabilei de control a ciclului.

85

Exemplu: Urmtorul program afieaz 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 instruciune din declaraia ciclului for poate fi o instruciune simpl sau un bloc (un grup de instruciuni delimitate de acolade) care va fi executat repetitiv. Exemplu: Programul urmtor afieaz pe ecran numerele de la 0 la 99, precum i ptratul 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 numr: 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);

Instruciunile for pot fi incluse una n alta. Exemplu: Programul urmtor parcurge un ir de caractere de la stnga la dreapta, afind subirurile ce au ca baz primul caracter.
# include <stdio.h> # include <string.h> void main (void) { 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'); } }

86

Variante ale ciclului for: Limbajul C permite mai multe variante ale ciclului for care determin creterea flexibilitii acestuia n diferite situaii. Una din cele mai utilizate variante const n folosirea a mai multe variabile de control a ciclului. n exemplul urmtor, att x ct 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 tiprete numerele de la 0 la 98 din 2 n 2. Se observ c iniializrile i incrementrile celor dou variabile sunt separate prin virgul. Terminarea ciclului presupune testarea nu numai a variabilei de control cu anumite valori prestabilite, ci condiia de terminare a ciclului poate fi orice expresie C corect. Exemplu: Considerm un program pentru antrenarea unui copil n exerciiile de adunare. Dac copilul vrea s se opreasc se apesa tasta T, atunci cnd 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 */ } }

O alt caracteristic interesant a ciclului for este aceea c nu se impune definirea tuturor celor trei parametri ai ciclului for, oricare dintre ei putnd fi opionali. De exemplu, ciclul urmtor se va executa pn cnd de la tastatura se introduce numrul 123:
for (x = 0; x != 123;) scanf("%d", &x);

Deoarece instruciunea de incrementare a lui x lipsete, de fiecare dat cnd ciclul se repet, programul testeaz ca x s fie egal cu 123, dar

87

nu modific pe x n nici un fel. Dac de la tastatur se introduce 123, condiia buclei devine fals i ciclul se termin. Exemplu: O variant de calcul a lui n! ar fi urmtoarea:
# 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 utilizri ale ciclului for const n crearea de bucle infinite. Dac nici una din cele trei expresii care formeaz ciclul for nu sunt precizate, se obine o bucl fr sfrit, ca n exemplul urmtor n care se consider c elementul condiie are valoarea adevrat:
for (;;) printf ("Aceasta bucla va rula la nesfirsit. \n ");

Ieirea dintr-o bucla for: Pentru terminarea unei bucle for, chiar i a buclei for(; ;) se folosete instruciunea break care se plaseaz oriunde n corpul ciclului i determin ncheierea imediat a ciclului (la ntlnirea acesteia), programul continundu-se cu instruciunea ce urmeaz dup instruciunea for. Exemplu: Acest program va rula pn cnd 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 "); }

Utilizarea ciclurilor for fr corp (instruciune) : Pentru crearea unor ntrzieri de timp se pot folosi cicluri for cu corp vid de forma:
for (t = 0; t < O_ANUMITA_VALOARE; t++);

Observaie: De obicei, instruciunea for este legat de parcurgerea unor structuri de date de tip tablou.

88

5.5.2. Instruciunea while


Forma general a instruciunii repetitive while este:
while (conditie) instructiune;

unde instructiune poate fi o instruciune vid, o instruciune simpl sau un bloc de instruciuni ce vor fi executate repetitiv. n timpul execuiei se evalueaz mai nti condiia buclei a crei valoare trebuie s fie ntreag. Dac valoarea calculat este diferit de 0 (condiie adevrat), atunci instructiune se execut. Dac, dup o evaluare (inclusiv prima) rezult o valoare 0 (condiie fals), atunci controlul este transferat la instruciunea ce urmeaz dup while. Astfel, instruciunea asociat cu while se execut repetat, ct timp valoarea asociat condiiei este diferit de 0 sau condiia este adevrat. Exemplu: Programul urmtor 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 instruciunea while realizeaz testarea condiiei la nceputul instruciunii, aceasta instruciune este bun de utilizat n situaiile n care nu se dorete execuia buclei, evident dac condiia nu este adevrat. Exemplu: Programul urmtor 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");

89

gets (sir); centreaza (strlen (sir)); printf (sir); } /* Se calculeaz numrul de spaii 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 programm un ciclu infinit, atunci se poate gsi o expresie care ramne tot timpul adevrat. Un exemplu uzual este urmtorul: Ieirea din ciclu, n acest caz, se asigur prin mecanisme de tip break, goto sau return. Corpul ciclului while poate conine i numai instruciunea vid. De exemplu, este o bucl simpl care se execut pn cnd de la tastatur se va introduce caracterul "A". Observaie: Instruciunea while reprezint mecanismul sintactic de baz pentru a programa cicluri n C. Reamintim c instruciunea for se folosete dup urmtorul format general: for (initializare; conditie; incrementare) instructiune; care este echivalent semantic cu secvena: initializare; while (conditie) { instructiune; incrementare; }
while ((ch = getche ()) != 'A'); while (1) { Corpul ciclului }

5.5.3. Instruciunea do-while


Spre deosebire de ciclurile programate cu while sau for, unde condiia de ciclare este verificat la nceput, n cazul folosisii mecanismului do-while, condiia se evalueaz dup execuia secvenei de instruciuni ce reprezint corpul ciclului. Forma general a buclei do-while este: Semantic, do-while este echivalent cu secvena:
instructiune; do { instructiune; } while (conditie);

90

Dei acoladele nu sunt necesare cnd instructiune este o instruciune simpl, de obicei se utilizeaz pentru a evita confuzia cu while. Se remarc faptul c instructiune ce reprezint corpul ciclului (adic, o instruciune simpl, o instruciune compus sau o instruciune vid) este executat cel puin odat. Celelalte execuii sunt condiionate de valoarea ntreag rezultat din evaluarea condiiei. Dac aceast valoare este 0 (condiie fals), atunci controlul se transfer la urmtoarea instruciune din program; n caz contrar se execut corpul ciclului i se reevalueaz condiia. Exemplu: Urmtoarea 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); }

while (conditie) instructiune;

Un caz tipic de utilizare a instruciunii do-while este oferit de programele interactive n care selecia unei opiuni se face pe baza unui meniu afiat pe ecranul terminalului. Exemplu: Urmtorul 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': verifica_ortografia(); break; case '2': corecteaza_erorile(); break; case '3': afiseaza_erorile();

91

Dup afiarea opiunilor, programul va bucla pn cnd se va selecta o opiune 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);

break; } } while (ch != '1' && ch != '2' && ch != '3'); }

sau
i = 0; do { c[i] = a[i] + b[i]; i++; } while (i < 10);

5.5.4. Bucle ncuibate


Cnd o bucl este introdus n alt bucl, bucla interioar se spune a fi inclus (nested, ncuibat) n bucla exterioar. Exemplu: Programul urmtor afieaz 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 "); } }

Cnd se execut acest program se obin urmtoarele rezultate:


i i^2 i^3 i^4 1 1 1 1 2 4 8 16 3 9 27 81 . . . . . . . . . . . 9 81 729 6561

Alinierea rezultatelor se datorete utilizrii n printf() a unui format de afiare corespunztor (%9d) care precizeaz dimensiunea minim a cmpului specificat.

92

Un alt exemplu, puin mai complex, este un program de nmulire 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); b[i][j]=elem; } // Se calculeaza fiecare element al matricei produs for(i=0;i<=la-1;i++)

93

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


Instruciunea break are dou utilizri. Prima utilizare const n terminarea unui case n cadrul instruciunii switch. A doua utilizare const n terminarea imediat a unui ciclu scurtcircuitnd testul condiional normal al buclei. Dac ntr-o bucl se ntlnete o instruciune break, calculatorul termin (prsete) imediat bucla i controlul programului se transfer la instruciunea ce urmeaz instruciunii 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; }}

tiprete numerele pn la 10 i atunci se oprete deoarece break determin ieirea imediat din ciclu. n cazul buclelor incluse, este important de notat ca break determin ieirea imediat numai din bucla interioar (din bucla n care este introdus). De exemplu, programul:
# include <stdio.h> void main (void) { int t; for (t = 0; t < 100; ++t) { count = 1;

94

for (;;) { printf (" %d ", count); count++; if (count == 10) break; } }

va afia pe ecran numerele de la 1 la 10 de 100 de ori. Instruciunea break se poate utiliza i n cadrul ciclurilor programate cu while sau do-while, schema general de utilizare fiind urmtoarea: while (expresie) { ................. if (conditie) break; ................. } Dac la una din iteraii, condiia din if este ndeplinit, atunci ciclul se termin automat, altfel el poate continua pn cnd expresia din while are valoarea fals. Dac instruciunea break se execut n cadrul unei instruciuni switch, care la rndul ei este inclus ntr-un ciclu programat cu while, for sau do-while, atunci ea determin terminarea numai a instruciunii switch, nu i ieirea din ciclu.

5.5.6. Instruciunea continue


Instruciunea continue, executat ntr-un ciclu, determin oprirea iteraiei curente i asigur trecerea imediat la iteraia urmtoare. De exemplu, programul urmtor va afia 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 cnd se genereaz un numr impar se execut instruciunea continue ce va determina trecerea la iteraia urmtoare by-pasnd instruciunea printf(). n cazul instruciunilor while i do-while, o instruciune continue determin trecerea direct la testul condiional i prin urmare, continuarea procesului de buclare. n cazul unui for, se realizeaz mai nti operaia de incrementare a variabilei de control a ciclului, apoi testarea condiiei de continuare a buclei.

Capitolul VI
95

TIPURI DE DATE STRUCTURATE


n C exist dou categorii de tipuri de date structurate: tablourile i structurile. Un tablou este o colecie omogen de valori de acelai tip identificate printr-un indice, iar o structur este o colecie neomogen de valori identificate prin nume simbolice, denumite selectori.

6.1. Tablouri unidimensionale


Un tablou este o colecie de variabile de acelai tip care sunt referite printr-un nume comun. n C, un tablou const din locaii 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 utiliznd 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 urmtoarea form general: tip var_nume[size]; Aici, tip, declar tipul de baz al tabloului. Tipul de baz determin tipul de dat al fiecrui element al tabloului. var_nume este numele tabloului, iar size este numrul elementelor pe care le va conine 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 urmtor ncarc un tablou de ntregi cu numerele de la 0 la 9:
void int int for (t main (void) { x[10]; // se rezerva 10 elemente intregi t; = 0; t < 10; t++) x[t] = t; }

Pentru un tablou unidimensional, dimensiunea total, n bytes, a acestuia va fi:

96

Total bytes = sizeof (tip) * lungimea_tabloului Observaie: Limbajul C nu realizeaz verificarea dimensiunilor unui tablou: astfel, nu exist nimic care s ne opreasc s nu trecem peste sfritul tabloului. Dac se trece peste sfritul unui tablou ntr-o operaie de atribuire, atunci se vor atribui valori unor alte variabile sau chiar se vor distruge pri din program. Exemplu: Dei urmtorul 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, dei vectorul crash conine numai 10 elemente. Aceste verificri rmn n sarcina exclusiv a programatorului. Tablourile unidimensionale sunt, de fapt, liste de informaii de acelai 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) A B C D ch(4) E ch(5) ch(6) 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 conine cu un caracter mai mult dect numrul 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 numr 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, funcia strlen(s) din fiierul antet "string.h" ntoarce numrul caracterelor din ir fr terminatorul 0, adic: strlen(s) = sizeof(s) - 1. n interiorul unui ir se poate folosi convenia de notaie cu \. Aceasta face posibil reprezentarea caracterelor " i \ n interiorul unui ir. Cel mai frecvent caracter folosit este caracterul '\n' = new line (NL). De exemplu, instruciunea:

97

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 notaie. Este posibil s folosim caracterul null ntr-un ir, dar majoritatea programelor nu testeaz dac mai sunt caractere dup el.

6.1.2. Iniializarea vectorilor de caractere Citirea unui ir de la tastatur utiliznd funciile scanf() i gets(). Utilizarea funciei 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 funciei 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 afiat (n cazul de fa, valorile variabilelor nume i adresa). Funcia scanf() citete un ir de caractere din bufferul de intrare pn cnd ntlnete un spaiu, un caracter TAB, sau ajunge la sfritul acestuia. Astfel, dac se tasteaz, "ENE ALEXANDRU", atunci n variabila nume se va memora doar valoarea "ENE". Pentru a obine irul n ntregime este recomandat s se transmit numele sub forma: "ENE_ALEXANDRU".

Cea mai bun cale de a introduce un ir de la tastatur const n utilizarea funciei gets() din fiierul antet "stdio.h". Forma general a funciei gets() este: gets (nume_vector) Pentru a citi un ir se apeleaz gets() avnd ca argument numele vectorului, fr nici un index. Funcia gets() returneaz vectorul ce va 98

pstra irul de caractere introdus de la tastatur. gets() va continua s citeasc caractere pn la introducerea caracterului CR. Exemplu: Programul urmtor afieaz 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 funcia printf() poate primi ca argument un ir de caractere. Dac se introduce un ir mai lung dect dimensiunea tabloului, vectorul va fi suprascris. Folosind constantele ir, vectorii de caractere se iniializeaz sub forma: char nume_vector[size] = "sir_de_caractere" unde size = numrul 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); }

Iniializarea vectorilor de caractere utiliznd constantele ir

Vectorul nume va ocupa ncepnd de la adresa nume, 13 + 1 = 14 octei, iar cel de-al doilea vector va ocupa ncepnd de la adresa adresa, 23 + 1 = 24 locaii (bytes). Funcia puts() scrie pe stdout irul memorat n vectorul al crui nume apare ca parametru al funciei puts(), precum i caracterul "\n". De multe ori, n C se realizeaz iniializarea unor vectori de caractere a cror dimensiune nu este precizat. Dac dimensiunea vectorului nu este precizat, compilatorul C va crea un vector suficient de lung nct s permit iniializarea 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"; char e3[ ] = "cannot open file\n";

Cu aceast ultim iniializare, instruciunea

99

printf ("%s are lungimea %d\n", e2, sizeof (e2));

va tipari:

Iniializarea unui vector (tablou unidimensional) se poate face i cu


o list de iniializatori scrii ntre acolade. Dac vectorul are o lungime necunoscut, numrul de iniializatori determin mrimea tabloului, iar tipul devine complet. Dac tabloul are lungime fix, numrul de iniializatori nu poate depi numrul de membri din tablou. n cazul n care sunt mai puini iniializatori, membrii n plus sunt iniializai cu zero. Exemple: - Instruciunea urmtoare iniializeaz 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};

write error are lungimea 13

Rezult c: i[0] = 1, ... , i[9] = 10. - Instruciunea urmtoare declar i iniializeaz vectorul x ca un tablou unidimensional cu 3 membri:
int x[] = {1, 2, 3};

- Instruciunea urmtoare:
char sir[6] = { 'h', 'e', 'l', 'l', 'o', '\0' };

este echivalent cu:


char sir[6] = "hello";

6.1.3. Funcii pentru prelucrarea irurilor (fiierul antet string.h)


Funcia strcpy() Apelul funciei strcpy() are urmtoarea form general: strcpy (nume_sir, constanta_sir); Funcia strcpy() copiaz coninutul 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. Funcia strcat() Apelul funciei strcat() are forma: strcat (s1, s2);

100

Funcia strcat() concateneaz irul s2 la sfritul 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 afia "hellothere" pe ecran. Funcia strcmp() Se apeleaz sub forma: strcmp (s1, s2); Aceast funcie compar irurile s1 i s2 i returneaz valori negative, dac s1 < s2, 0, dac s1 = s2 i un numr pozitiv, dac s1 > s2. Exemplu: Aceast funcie 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; }

Funcia strlen() Funcia strlen() se apeleaz sub forma: strlen (s) unde s este un ir. Funcia strlen() returneaz lungimea irului s. Exemplu: Programul urmtor returneaz lungimea unui ir introdus de la tastatur.
# incude <stdio.h> # incude <string.h> void main (void) { char sir[80];

101

printf ("Introduceti un sir: "); gets (sir); printf ("Sirul %s contine %d strlen(sir)); }

caractere

",

sir,

Observaie: Funcia strlen() nu numr i caracterul NULL. Exemplu: Programul urmtor afieaz 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 urmtor realizeaz introducerea unor iruri, compararea lor, concatenarea lor i afiarea 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 afia:


Lungimi 10 10 Sirurile sunt egale AUTOMATICAAUTOMATICA

Dac irurile sunt egale, funcia strcmp() returneaz fals (0) i din aceast cauz n instruciunea if s-a folosit !strcmp(). Observaie: Caracterul NULL de terminare a vectorului de caractere poate fi utilizat n buclele for ca n exemplul urmtor, unde se convertete 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); }

102

Conversia caracterelor se face cu funcia toupper() care returneaz litera mare corespunztoare argumentului (literei mici). Ciclul funcioneaz pn cnd sir[i] devine caracterul null.

6.2. Tablouri cu dou dimensiuni (matrice)


Tablourile bidimensionale (matricele) sunt reprezentate ca vectori de vectori. De exemplu, declaraia: 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, declaraia: int v[2, 5]; conduce la eroare.

6.2.1. Iniializarea matricelor


Declaraia :
char v[2][5] = { 'a', 'b', 'c', 'd', 'e', '0', '1', '2', '3', '4' };

conduce la iniializarea 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: Secvena de instruciuni:


# 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 ncrcarea tabloului num[3][4]cu numerele de la 1 la 12. Astfel, num[0][0] = 1, ..., num[2][3] = 12.

103

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) Declaraia: float y[4][3] = {
{1,3,5}, {2,4,6}, {3,5,7},};

este o iniializare cu paranteze complete i are urmtorul efect: - numerele 1, 3, 5 iniializeaz prima linie a tabloului: y[0][0], y[0][1], y[0][2] sau y[0]; - numerele 2, 4, 6 iniializeaz pe y[1]; - numerele 3, 5, 7 iniializeaz pe y[2]. ntruct iniializatorul se termin cu virgul, elementele lui y[3] vor fi iniializate cu 0. Acelai efect ar fi putut fi realizat de:
float y[4][3]={1, 3, 5, 2, 4, 6, 3, 5, 7, };

Secvena:
float y[4][3] = { {1}, {2}, {3}, {0}, };

iniializeaz prima coloan a lui y, privit ca un tablou bidimensional, cu 1, 2, 3 i 0, restul tabloului fiind iniializat cu 0.

6.2.2. Tablouri bidimensionale de iruri


Pentru crearea unui tablou de iruri se folosete un tablou de caractere, bidimensional, n care mrimea indicelui din stnga determin numrul de iruri, iar indicele din drepta specific lungimea maxim a fiecrui ir. De exemplu, declaraia :
char sir_tablou[30][80];

definete un tablou de 30 de iruri, fiecare ir avnd maximum 80 de caractere. Accesul la un singur ir este foarte uor: se specific numai primul indice. De exemplu:
gets (sir_tablou[2])

ntoarce al treilea ir din tabloul sir_tablou. Funcional, instruciunea anterioar este echivalent cu:
gets (&sir_tablou[2][0]);

6.3. Tablouri multidimensionale


Forma general a declaraiei unui tablou multidimensional este:
tip nume[size1][size2]...[sizeN];

De exemplu, declaraia:

104

int

trei[4][10][3];

creeaz un tablou de 4*10*3 ntregi. Forma general de iniializare a tablourilor este urmtoarea: 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. Observaie: Limbajul C permite i iniializarea tablourilor multidimensionale fr dimensiune. Trebuie menionat c pentru aceasta este necesar precizarea indicelui celui mai din dreapta. Astfel, declaraia:
int sqrs[5][2] = {1, 1, 2, 4, 3, 9, 4, 16, 5, 25};

este echivalent cu declaraia:


int sqrs[ ][2] = {1, 1, 2, 4, 3, 9, 4, 16, 5, 25};

6.4. Structuri
O structur este o colecie de variabile (de tipuri diferite) referite sub un singur nume. Definiia unei structuri formeaz un aa numit ablon (template, tag) ce poate fi folosit la crearea variabilelor tip structur. Variabilele care formeaz structuri se numesc elementele structurii. De exemplu, fragmentul urmtor definete un ablon pentru o structur numit addr care definete la rndul su numele i adresa unei persoane necesare n cazul transmiterii unei scrisori:
struct addr { char name[30]; char street[40]; char city[20]; char state[3]; unsigned int zip; }; struct { char *name; char *street; char *city; char *state; unsigned int zip; } addr;

Pentru definirea unui ablon al unei structuri se folosete cuvntul cheie struct. Terminarea definiiei se face cu ; (este unul din puinele cazuri de utilizare a caracterului punct i virgul ; dup acolad). Precizm c numele addr identific structura particular definit anterior (ablonul) i este specificatorul su de tip. Programul anterior definete numai forma (tipul) datelor structurii, dar nu definete variabilele structur, deci trebuie fcut distincie dintre structura-

105

ablon i variabila-structur. O variabil de tip structur se declar cu ajutorul ablonului structurii. 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 pstra toate variabilele ce alctuiesc 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 Cnd se definete 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; Secvena anterioar definete o structur ablon numit addr i declar variabilele addr_info, binfo, cinfo de acelai 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 definete variabila-structur addr_info cu ablonul definit, dar fr nume Forma general de definire a unei structuri este :
struc nume_tip_structura { tip nume_variabile; tip nume_variabile; tip nume_variabile;

106

. . . . . . . . . . . . . . } variabile_structura;

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 cuvntul cheie struct se refer la ablonul structurii (tipul su) 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, instruciunea urmtoare va atribui elementului zip al variabilei structur addr_info valoarea 12345: addr_info.zip = 12345; Pentru accesarea elementelor unei structuri se folosete forma general : nume_structura.nume_element De exemplu, pentru afiarea variabilei zip se va scrie: printf ("%d", addr_info.zip); n aceeai 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 afiarea coninutului lui addr_info.name, caracter cu caracter, se folosete 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 nti se definete 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);

107

are ca efect afiarea codului zip din a treia structur. Se observ c, la fel orice variabil tablou, tablourile de structuri ncep cu indexul 0. Exemplu de program pentru actualizarea unei liste de corespondena - maillist Pentru a ilustra modul de utilizare a structurilor i tablourilor de structuri prezentm un exemplu de program pentru actualizarea unei liste de coresponden. Informaiile ce vor fi memorate se refer la name, street, city, state, zip. Pentru definirea structurii de baz addr care va conine aceste informaii 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 definete dup necesiti. Prima funcie necesar n program este main(), a crei structur este urmtoarea:
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(); }}}

Funcia init_list() pregtete tabloul structur pentru utilizare prin punerea unui caracter null n primul byte al cmpului "nume". Programul impune ca o variabil structur s nu fie utilizat dac cmpul nume este vid. Aceast iniializare are loc n memoria intern a calculatorului (nu n fiierul maillist de pe disc). Structura funciei de initializare init_list() ar putea fi urmtoarea:
/* Functia init_list() */ void init_list() { register int t;

108

for (t = 0; t < SIZE; t++) *addr_info[t].name = '\0';

Funcia de selectare a meniului menu() va afia mesajele opiunilor i va returna varianta aleas. Prin tastarea literei din paranteze, se va lansa n execuie 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); }

Observaie: Funcia strrchr(cs,c) din <string.h> ntoarce un pointer la ultima apariie a lui c n cs sau NULL dac nu apare. Funcia enter() are ca efect introducerea unor noi informaii n urmtoarea 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 informaii 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);}

109

Rutinele save() i load() se utilizeaz pentru actualizarea bazei de date. Aceste rutine lucreaz cu fiierul disc maillist. Cnd se apeleaz load(), atunci se copiaz n tabloul structur din memorie datele stocate n fiierul maillist. Funcia load() realizeaz operaiunea invers, de supranscriere a fiierului disc cu datele din memorie. Spre exemplu, dac dorim s adugm date la fiierul maillist care conine deja date introduse anterior, ordinea de lansare a comenzilor va fi: init_list(), load(), enter(), save(), exit(). Structura acestor rutine este urmtoarea:
/* 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"); }

Att save() ct i load() confirm un succes a unei operaii cu fiiere prin verificarea valorilor ntoarse de funciile fread() sau fwrite(). n plus, load() trebuie s caute i indicatorul de sfrit de fiier utiliznd funcia feof() deoarece fread() ntoarce aceeai valoare dac se ntlnete indicatorul de sfrit de fiier sau dac apare o eroare. Funcia display() afieaz pe ecran ntregul tablou structur din memorie care conine date valide:
/* Functia display() */ void display() { register int t; for (t=0;t<SIZE;t++) {

110

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

111

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

112

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

6.4.2. Introducerea structurilor n funcii


a) Introducerea elementelor unei structuri n funcii Cnd un element al unei variabile tip structur este transmis (pasat) unei funcii, 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 funcie este urmtoarea:


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

113

Pentru a transmite funciei 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] */ b) Transmiterea unei ntregi structuri unei funcii Cnd o structur este utilizat ca argument al unei funcii, limbajul C transmite funciei ntreaga structur utiliznd metoda standard a apelului prin valoare. Aceasta nseamn c orice modificare asupra coninutului structurii n interiorul funciei n care este apelat structura, nu afecteaz structura folosit ca argument. Ceea ce trebuie avut neaprat n vedere atunci cnd, 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 att argumentul arg al lui f1, ct i parametrul param ca avnd acelai tip de structur. Programul va afia 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;

114

char ch;}; void main() { struct struct_tip arg; /* se declara structura arg */ arg.a = 1000; f1(arg);} void f1(struct struct_tip param) functia f1() */ {printf ("%d\n",param.a); }

/*

se

declara

Pentru exemplificare, propunem urmtorul 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 cresctoare, avnd n vedere ca primele poziii s fie ocupate de numerele pare din ir sortate cresctor dup care s urmeze numerele impare din ir sortate tot cresctor. Pentru a realiza acest program, vom utiliza nu variabile de tip ir ci o structur care s conin ca prim element irul respectiv iar cel de-al doilea element s fie lungimea acestuia. Se vor construi funcii care s realizeze citirea irurilor de la tastatur, scrierea lor pe display, respectiv s le ordoneze i s le sorteze dup paritate. Toate aceste funcii 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();

115

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

116

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

Din funcia cit_sir() se poate iei prin tastarea oricarui caracter nenumeric. Se observ cum aceasta funcie lucreaz direct cu variabila global ir iar celelalte cu variabile locale proprii care apoi sunt asignate variabilei globale ir la returnare. Rulnd programul vom obine 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: Considerm 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] Cnd 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 definete structura "sal" cu 2 elemente: primul este o structur de tip "addr" care conine adresa salariatului; al doilea element este salariul sptmnal al acestuia. Instruciunea urmtoare va atribui codul 90178 variabilei "zip" din adresa muncitorului muncitor.adresa.zip = 90178;

117

Se observ c elementele fiecrei structuri sunt referite de la exterior ctre interior, respectiv de la stnga 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. Exemplu:
union tip_u { int i; char ch;};

O variabil de acest tip poate fi declarat fie prin plasarea numelui su la sfritul acestei definiii, fie utiliznd o instruciune de declarare separat. De exemplu, instruciunea : union tip_u exuniune; declar variabila-uniune "exuniune" n care att ntregul i, ct i caracterul ch ocup aceeai zon de memorie (cu toate ca int ocupa 4 octei, iar char numai un octet). Cnd 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 aceeai sintax folosit la structuri (operatorii punct i " -> "). Cnd un element al unei uniuni se adreseaz direct, se utilizeaz operatorul ".", iar cnd elementul se adreseaz printr-un pointer, se folosete operatorul "-> ". De exemplu, pentru a atribui elementul ntreg i al uniunii anterioare "exuniune" valoarea 10, se va scrie: exuniune.i = 10; n exemplul urmtor, programul transfer un pointer la "exuniune" unei funcii :
func1(union tip_u *un) { un -> i = 10; }

/* se atribuie valoarea 10 intregului i al uniunii exuniune */ In C, uniunile sunt des utilizate cnd sunt necesare conversii de tip. De exemplu, funcia standard putw() ce realizeaz scrierea binar a unui ntreg ntr-un fiier de pe disc, poate fi scris folosind o uniune. Pentru aceasta, mai nti se creaz o uniune ce cuprinde un ntreg i un vector de dou caractere, astfel:

118

union pw { int i; char ch[2];};

Atunci, structura lui putw(), utiliznd 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 }

6.6.Enumerri
O enumerare este o mulime de constante ntregi ce pot lua toate valorile unei variabile de un anumit tip. Enumerrile se definesc n acelai mod ca i structurile, utiliznd cuvntul cheie enum ce semnaleaz nceputul unui tip enumerare. Forma general de definire a unei enumerri este: enum nume_tip_enum { lista_enumeratori} lista_variabile; unde att nume_tip_enum, ct i lista_variabile sunt opionale. Exemplu: Urmtorul fragment definete o enumerare numit "bancnota" cu care apoi se declar o enumerare numit "bani" avnd acest tip:
enum bancnota {suta,douasute,cincisute,mie,cincimii,zecemii}; enum bancnota bani;

Dndu-se aceast definiie i declaraie, 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. Fr nici o alt iniializare, valoarea primului enumerator este 0, a celui de-al doilea este 1, s.a.m.d. De aceea, instruciunea: printf ("%d %d, suta, mie); va afisa pe ecran: 0 3 Se pot specifica valorile unuia sau mai multor simboluri folosind iniializatori. 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

119

cincimii = 1001 zecemii = 1002

Urmatorul fragment de program nu funcioneaz, deoarece "bani" este un ntreg i nu un ir :


bani = cincimii; printf ("%s", bani);

Nici acest program nu functioneaz:


gets (s); strcpy (bani, s);

Pentru a afia tipurile bancnotelor coninute 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 corespunztor, se poate declara un tablou de iruri i utiliza valoarea enumeratorului ca index. De exemplu, urmtorul fragment va afia irul corespunztor:
char name[ ][20] = { "suta", "douasute", "cincisute", "mie", "cincimii" "zecemii" }; . . . . . . printf ("%s", name[bani]);

Fragmentul anterior va funciona numai dac nu se realizeaz iniializarea simbolurilor, deoarece indexarea irurilor ncepe cu zero. Urmtorul program afieaz 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 urmtor i se aplic operatorul sizeof() vom gsi sizeof(y) = 8.

120

# include <stdio.h> union { char ch; int i; double f; } y; void main() {printf("%d\n",sizeof(y));}

Deci compilatorul va reine valoarea celei mai largi tipuri de date din uniune.

Capitolul VII POINTERI


Un pointer este o variabil care pstreaz adresa unui obiect de tip corespunztor. 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 definete tipul variabilelor spre care indic pointerul. Variabila pointer este o variabil de un tip special, aparte de tipurile char, int, float. Cuvntul cheie tip din declaraia 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 folosete, deci depinde n mare msur de tipul procesorului pentru care a fost proiectat compilatorul. O indicaie despre formatul n care se stocheaz o variabil pointer n memorie poate fi obinut prin tiprirea coninutului unei variabile pointer (o adres) utiliznd 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 &:

121

Operatorul & este un operator unar care ofer (returneaz) adresa unei variabile (adresa operandului su). 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 */ 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 considerm poriunea 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: Memoria Adresa
i j p
i = 123; p = &i;

1200 1202 1204

Dup primele dou atribuiri zona de stocare va arta astfel:


Adre sa i j p 1200 1202 1204 1200 Me oria m 123

122

Coninutul variabilei p (de tip pointer) va fi valoarea 1200, adic adresa variabilei i. Instruciunea j = *p; copiaz un ntreg scurt de la locaia 1200 n j, locaiile de memorie fiind acum ca cele din figur: Mem oria Adresa
i j p 1200 1202 1204 123 123 1200

7.1.1. Importana tipului de baz


Considerm declaraia: val = *count_addr; Se pune ntrebarea: care va fi numrul de bytes ce va fi transferat variabilei val de la adresa indicat prin *count_addr. Sau, mai general, de unde tie compilatorul ci bytes s transfere n cazul oricrei asignri care utilizeaz pointeri. Rspunsul la aceste ntrebri 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 (corespunztori reprezentrii unui ntreg scurt) i nu 4 bytes, corespunztori unui numr real n virgul mobil.

7.1.2. Expresii n care intervin pointeri


n general, expresiile n care intervin pointeri respect aceleai reguli ca orice alte expresii din limbajul C.

Atribuirea pointerilor Ca orice variabil, un pointer poate fi folosit n membrul drept al unei instruciuni de asignare (atribuire), pentru atribuirea valorii unui pointer unui alt pointer. Exemplu: 123

# 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 funcia printf() tiprirea se face cu formatul %p care specific faptul c variabilele din list vor fi afiate ca adrese pointer. Din cele de mai sus se observ c operaia 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', aa nct valoarea lui *p atribuit lui c2 este 'a'. Operaii aritmetice efectuate asupra pointerilor Utilizarea operatorilor de incrementare i decrementare Fie secvena:
int *p1; p1++; /* pointer la intreg */

De fiecare dat cnd se incrementeaz p1, acesta va indica spre urmtorul ntreg. Astfel, dac p1 = 2000, dup efectuarea instruciunii p1++, acesta va fi p1 = 2004 (va indica spre urmtorul ntreg). Dup fiecare incrementare a unui pointer, acesta va indica spre urmtorul element al tipului su de baz. Dup fiecare decrementare a unui pointer, acesta va indica spre elementul anterior. Valoarea pointerilor va fi crescut sau micorat n concordan cu lungimea tipului datelor spre care acetia indic, aa cum se poate vedea n exemplul urmtor:
char *ch = 3000; short int *i = 3000;

ch ch + 1 ch + 2

3000 3001 3002

i i+1

124

ch + 3 ch + 4 ch + 5 ch + 6

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 stnga unei operaii de atribuire. Verificai utilizarea pointerilor din programul urmtor:
# 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);

3003 3004 i+2 3005 3006 i+3 Memoria

n urma rulrii sale, pe calculatoarele mai moderne obinem rezultatul


i= 1,j= 2 pi= 0065FDE0,pj= 0065FDDC *pi= 1 *pj= 1 *pi= 1 *pj= 0 ++pj= 0065FDDE,++*pj= 1

La sau dintr-un pointer, se pot aduna sau scdea valori de tip ntreg. Rezultatul este un pointer de acelai tip cu cel iniial, indicnd spre un alt element din tablou. De exemplu, face ca p1 s indice spre al 9-lea element avnd tipul lui p1, considernd c elementul curent este indicat de p1. Evident c valoarea pointerului se va modifica corespunztor lungimii tipului datei indicat prin pointer.
Exemplu:
int *p1; /* Pointer la intreg */ p1 = p1 + 9; p1 = p1 + 9;

Utilizarea operatorilor de adunare i de scdere

125

Dac valoarea p1 = 3000, atunci p1 + 9 va avea valoarea:


(valoarea lui p1)+9*sizeof(int)=3000+9*4=3036

Aceleai considerente sunt valabile n cazul n care un ntreg este sczut dintr-un pointer. Dac doi pointeri de acelai tip sunt sczui, rezultatul este un numr 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. Dac p indic spre ultimul membru dintr-un tablou, atunci (p+1) are valoare nedeterminat. Observaii : Nu se pot aduna sau scdea valori de tip float sau double la/sau dintr-un pointer. Nu se pot efectua operaii de nmulire i mprire cu pointeri. Exemplu: Scderea 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); }

Compararea pointerilor

Doi pointeri de acelai tip se pot compara printr-o expresie relaional, astfel: dac p i q sunt doi pointeri, atunci instruciunile:
if (p < q) printf ( p indica spre o adresa mai mica decit q \n );

sunt corecte. Compararea pointerilor se utilizeaz cnd doi sau mai muli pointeri indic spre acelai obiect comun. Exemplu: Un exemplu interesant de utilizare a pointerilor const n examinarea coninutului locaiilor 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;

126

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); /*Afiseaza in hexazecimal continutul locatiei de memorie adresata cu *p*/ if (kbhit()) return;} // Stop cand se apasa orice tasta }

Programul introduce cuvntul cheie far care permite pointerului p s indice locaii de memorie care nu sunt n acelai segment de memorie n care este plasat programul. Formatul %lu din scanf() nseamn: "citete un unsigned long int". Formatul %X din printf() ordon calculatorului s afieze argumentul n hexazecimal cu litere mari (Formatul %x afieaz n hexazecimal cu litere mici). Programul folosete explicit un ablon de tip pentru transferul valorii ntregului fr semn, start, pe care l introducem de la tastatur, ntr-un pointer. Funcia kbhit() returneaz ADEVARAT dac se apas o tast, altfel returneaz FALS.

Utilizarea pointerilor ca parametri formali ai funciilor


n exemplele de pn acum, s-au folosit funcii C care atunci cnd erau apelate, parametrii acestor funcii erau (ntotdeauna) actualizai prin pasarea valorii fiecrui argument. Acest fapt ne ndreptete s numim C-ul ca un limbaj de apel prin valoare. Exist totui o excepie de la aceast regul atunci cnd argumentul este un tablou. Aceast excepie este explicat, pe scurt, prin faptul c valoarea unui nume al unui tablou (vector, matrice etc.) neindexate este adresa primului su element. Deci, n cazul unui argument de tip tablou, ceea ce se paseaz unei funcii este o anumit adres. Folosind variabile pointer se pot pasa adrese pentru orice tip de date. Spre exemplu, funcia scanf() accept un parametru de tip pointer (adres): scanf(%1f,&x); Ceea ce este important de evideniat este cum anume se poate scrie o funcie care s accepte ca parametri formali sau ca argumente pointeri ?.

127

Funcia care recepioneaz o adres ca argument va trebui s declare acest parametru ca o variabil pointer. De exemplu, funcia 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 */ 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 strns legtur 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 reine toi octeii corepunztori tuturor elementelor tabloului. Astfel, tabloul q declarat ca short q[100]; rezerv 2x100 octei de memorie iar int q[100] rezerv 4x100 octei de memorie. Pe de alt parte, pentru un pointer se aloc o zon de memorie redus la numai civa octei necesari pentru a reine 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 civa octei ( n exemplele anterioare - 4 octei). Considerm secvena:
char sir[80]; char *p1; p1 = sir;

Acest fragment face ca p1 s adreseze primul element al tabloului sir. n C numele unui tablou fr 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 fr indici reprezint adrese, pe cnd numele unui tablou cu indici se refer la valorile stocate n acea poziie n tablou.

128

Deoarece indicele inferior al oricrui 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. Pentru a vedea modul de utilizare a celor dou metode, considerm un program care tiprete 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 litere mari: \n"); gets (sir); printf ("Acesta este sirul in litere mici: \n"); for(i=0;sir[i];i++) printf("%c", tolower(str[i])); }

cu

Exemplu: Versiunea cu pointeri


void main (void) { char sir[80] , *p; printf ("Introduceti un sir de caractere scrise 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++)); } cu

Pointerii sunt rapizi i uor de utilizat cnd se dorete referirea elementelor unui tablou n ordine strict cresctoare sau strict descresctoare. Dac ns se dorete 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 declaraiile:
short q[100]; short *pq

129

atunci sunt permise i posibile urmtoarele declaraii:


Varianta cu tablou pq=&q[0] pq=q q[n] Varianta cu pointeri pq=&q[0] pq=q pq[n] *(pq+n) Descriere Pointerul pq indic adresa primului element al tabloului q pq[n] nseamn acelai 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, orice referire la pq[n] nseamn acelai lucru cu valoarea de la adresa (pq+n). Exemplu: Programul urmtor realizeaz tiprirea 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 pstreaz stiva ntr-un tablou, iar rutinele push() i pop() realizeaz accesul la elementele stivei utiliznd pointeri. Pentru memorarea vrfului stivei, utilizm variabila tos (top of stack). Considerm c folosim numai numere de tip ntreg. n programul main(), rutinele push() i pop() sunt utilizate astfel: push() citete numerele introduse de la tastatur i le memoreaz n stiv dac acestea sunt nenule. Cnd se introduce un zero, pop() extrage valoarea din stiv.

130

# 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); if (value != 0) push (value); else printf ("Nr. extras din stiva este %d 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); }

\n",

7.2.2. Pointeri i iruri


Deoarece numele unui tablou fr indici este un pointer la primul element al tabloului, pentru implementarea unor funcii care manipuleaz iruri, se pot utiliza pointeri. tim c funcia 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: Prezentm o variant de scriere a funciei 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 }

131

Reamintim c un ir n C se termin cu caracterul NULL. De aceea, instructiunea while(*s1) rmne adevrat pn cnd se ntlnete 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 urmtor afieaz pe ecran mesajul " Acest program funcioneaz ":
# include <stdio.h> void main (void) { char *s; s = " Acest program functioneaza "; printf (s); }

7.2.3. Preluarea adresei unui element al unui tablou


Pn acum s-a vzut c un pointer poate s adreseze primul element al unui tablou. Este posibil s se adreseze orice element al unui tablou aplicnd 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 eseniala const n gsirea unui subir ntr-un ir dat. Exemplu: Programul urmtor afieaz ultima parte a unui ir introdus de la tastatur, din punctul n care se ntlnete primul spaiu:
# 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 spaiu, programul va afia spaiul i apoi subirul rmas. Dac n irul introdus nu este nici un spaiu, p indic spre sfritul irului i atunci nu se va afia nimic. De exemplu, dac se introduce my friend, atunci printf() afieaz mai nti un spaiu i apoi friend.

132

7.2.4. Tablouri de pointeri


Putem construi tablouri de pointeri n aceeai 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 gsirea 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:


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

Funcia printf() este apelat din funcia selmes(). Aceasta va afia mesajul de eroare indexat prin numrul de eroare num, care este pasat ca argument funciei selmes(). De exemplu, dac se introduce 2, atunci se va afia mesajul: Write error Atentie !. Trebuie facut distincia 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 postfixai. Deoarece prioritatea operatorilor postfixai este mai mare dect cea a operatorilor prefixai, atunci cnd se dorete schimbarea prioritii, 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 semnificaiei lui.

133

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 conine valoarea dorit: Pointer Variabil Adres ---------> Valoare n cazul unui pointer la pointer, primul pointer conine adresa celui de-al doilea pointer, care indic spre variabila ce conine valoarea dorit: Pointer Pointer Variabil Adres ---------> Adres ---------> Valoare Declararea indirectrilor multiple se face sub forma:
/* cpp este un pointer la pointer la caracter */ char **cpp; /* newbalance este un pointer la pointer la float */ float **newbalance;

Pentru a avea acces la o valoare indirectat printr-un pointer la pointer este necesar, de asemenea, utilizarea operatorului * de dou ori, aa cum se vede n exemplul urmtor:
# 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. Iniializarea pointerilor


Secvena:
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 declaraie poate fi utilizat ca orice alt ir. Astfel, programul urmtor este corect:
# include char *p = void main register <stdio.h> " Hello world \n "; (void) { int t;

134

/*Se tipareste sirul in ordine directa si inversa*/ printf (p); for(t = strlen(p)-1; t > -1; t--) printf("%c",p[t]); }

Observaie: Neiniializarea pointerilor sau iniializarea greit a acestora, poate duce la erori care, n cazul programelor de dimensiuni mari, sunt foarte greu de depistat i pot avea urmri catastrofale. Exemplu: Considerm programul:
# include <stdio.h> void main(void) { int x, *p; x = 10; *p = x; printf ("%d\n", *p);

Acest program atribuie valoarea 10 anumitor locaii de memorie necunoscute. Programul nu va oferi niciodat o valoare pointerului p dar va tipri valoarea lui x. Exemplu: Considerm acum urmtorul program:
# include <stdio.h> void main (void) { int x, *p; x = 10; p = x; printf("%d",*p);

Funcia printf() nu va afia niciodat valoarea lui x (care este 10), dar va tipri o valoare necunoscut. Aceasta datorit atribuirii greite:
p = x;

Instruciunea 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 informaii n memoria principal a calculatorului.

Prima metod folosete variabilele globale i locale. n cazul variabilelor globale, memoria ce li se aloc este fix pe tot timpul execuiei programului. Pentru variabilele locale, programul aloc memorie n spaiul stivei, n timpul execuiei programului. Dei variabilele locale sunt eficient de implementat, n C, de multe ori, 135

utilizarea acestora, necesit cunoaterea n avans a cantitii de memorie necesare n fiecare situaie.
H h ig Siv t a M mrielib r e o e p nrua c re et lo a ( ep ha ) V ria ileg b le a b lo a (t t e saic ) Lo w P g m ro ra M m ria e o

funciilor de alocare dinamic malloc() i free(). Prin aceast metod, un program aloc memorie pentru diversele informaii n spaiul memoriei libere numit heap, plasat ntre programul util i memoria sa permanent i stiv. Se observ c stiva crete n jos, iar dimensiunea acesteia depinde de program. Un program cu multe funcii recursive va folosi mult mai intens stiva n comparaie cu un program ce nu utilizeaza funcii recursive, aceasta deoarece adresele de retur i variabilele locale corespunztoare acestor funcii sunt salvate n stiv. Funciile malloc() i free() Aceste funcii 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 vrful stivei, n scopul stabilirii i meninerii unei liste a variabilelor memorate. De fiecare dat cnd se face o cerere de memorie, funcia malloc() aloc o parte din memoria rmas liber. De fiecare dat cnd se face un apel de eliberare a memoriei, funcia free() elibereaz memorie sistemului. Declararea funciei malloc() se face sub forma:
void *malloc (int numar_de_bytes);

A doua metod de alocare a memoriei, const n utilizarea

Aceasta ntoarce un pointer de tip void, ceea ce nseamn c trebuie utilizat un ablon explicit de tip atunci cnd pointerul returnat de

136

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 eec i malloc() returneaz NULL. Pentru determinarea exact a numrului de bytes necesari fiecrui tip de date, se poate folosi operatorul sizeof(). Prin aceasta, programele pot deveni portabile pe o varietate de sisteme. Funcia free() returneaz sistemului memoria alocat anterior cu malloc(). Dup eliberarea memoriei cu free(), aceasta se poate reutiliza folosind un apel malloc(). Declararea funciei free() se realizeaz sub forma:
free(void *p);

Funcia free() elibereaz spaiul indicat de p i nu face nimic dac p este NULL. Parametrul actual p trebuie s fie un pointer la un spaiu alocat anterior cu malloc(), calloc() sau realloc(). Exemplu: Urmtorul program va aloca memorie pentru 40 de ntregi, va afia valoarea acestora, dup care elibereaz zona, utiliznd free():
# 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); } }

Funciile calloc() i realloc() Funcia calloc() aloc un bloc de n zone de memorie, fiecare de dim octei i seteaz la 0 zonele de memorie; funcia returneaz un pointer la acel bloc (adresa primului octet din bloc). Declararea funciei se face cu:
void *calloc(unsigned int n, unsigned int dim);

Funcia realloc() primete un pointer la un bloc de memorie alocat n prealabil (declarat pointer de tip void) i redimensioneaz

137

zona alocat la dim octei (dac este nevoie, se copiaz vechiul coninut ntr-o alt zon de memorie). Declararea funciei se face cu:
void *realloc(void *ptr, unsigned int dim);

7.2.8. Pointeri la structuri


Limbajul C recunoate pointerii la structuri n acelai mod n care se recunoate pointerii la orice alt tip de variabil. Declararea unui pointer la structur se face plasnd operatorul * n faa numelui unei variabile structur. De exemplu, pentru structura addr definit mai nainte, urmtoarea instruciune 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 funcie. Cnd unei funcii i se transmite ca argument un pointer la o structur, calculatorul va salva i va reface din stiv numai adresa structurii, conducnd astfel la cresterea vitezei de executare a programului. Pentru a gsi adresa unei variabile structur, se plaseaz operatorul & naintea numelui variabilei structur. De exemplu, dndu-se urmtorul 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 dect operatorul *, pentru o referire corect a elementelor unei structuri utiliznd pointerii sunt necesare paranteze. Actualmente, pentru referirea unui element al unei variabile structur dndu-se un pointer la acea variabil, exist dou metode: Prima metod utilizeaz referirea explicit a pointerului numestructur i este considerat o metoda nvechit (obsolete), iar a doua metod, modern, utilizeaz operatorul sgeat -> (minus urmat de mai mare).

138

Exemplu: Pentru a vedea cum se utilizeaz un pointer-struct, examinm urmtorul program care afieaz ora, minutul i secunda utiliznd 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; { (*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 ntrzierii se poate modifica valoarea contorului t al buclei. Se vede ca programul definete o structur global numit tm, dar nu declar variabilele. In interiorul funciei main() se declar structura "time" i se iniializeaz cu 00:00:00. Programul transmite adresa lui time funciilor actualizeaza() i afiseaza(). n ambele funcii, argumentul acestora este declarat a fi un pointer-structur de tip "tm".

139

Referirea fiecrui element se face prin intermediul acestui pointer. Elementele unei structuri pot fi referite utiliznd 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; 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 definete funcia delay() */ { long int t; for (t = 1;t<140000000;++t);}

140

7.2.9. Structuri dinamice liniare de tip list


Structura a fost introdus ca fiind o grup (colecie) ordonat de date care pot fi de tipuri diferite i care au anumite legturi logice ntre ele. Adesea, aceast grup de date se repet de mai multe ori. Se ajunge astfel la noiunea de tablou, ale crui elemente sunt fiecare cte 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 pn 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 uurin valorile componentelor dar este foarte dificil s mrim numrul lor peste limita maxim declarat nainte de compilare. Mai mult, prin tergerea unor elemente structur dintr-un tablou de structuri obinem goluri n tablou, pe care le putem umple numai printr-o gestiune foarte precis a poziiilor din tablou. Folosind pointeri la tabloul de structuri, este foarte posibil s indicm spre un element care a fost ters. Dac dorim o reprezentare contigu n memorie, va trebui s compactm (sau s defragmentm) tabloul la fiecare tergere a unui element de tip structur. Mai mult, dac dorim s schimbm ordinea n care s-au stocat elementele din tablou sau s inserm ntr-o poziie intermediar un element nou, aceaste operaii devin foarte anevoioase. ntr-un exemplu anterior am folosit secvena
# 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 aplicaie, chiar n timpul execuiei programului, constatm c avem nevoie de mai mult de 100 de rezervri de memorie, nu exist nici o posibilitate de a mri tabloul fr 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

141

succes. Acest lucru prezent dou inconveniente (vezi [Mocanu, 2001]): 1- Execuia i obinerea rezultatelor sunt amnate i produc ntrzieri pentru modificarea programului surs, recompilare i reintroducerea datelor care fuseser deja introduse pn n momentul n care s-a constatat necesitatea mririi 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. Cnd apare necesitatea introducerii unui element n list, se va aloca dinamic spaiu pentru respectivul element, se va crea elementul prin nscrierea informaiilor corespunztoare i se va lega n list. Cnd un element este scos din list spaiul de memorie ocupat de acesta va fi eliberat i se vor reface legturile. Structurile dinamice se construiesc prin legarea componentelor structurii, numite variabile dinamice. O variabil dinamic ce intr n componena unor structuri de date dinamice (nod) prezint n structura sa dou pri: 1. Partea de informaie (info) ce poate aparine unui tip simplu (int, char, float, double, etc.) conform cu necesitile problemei. 2. Partea de legtur (link) ce conine cel puin un pointer de legtur (next) la o alt variabil dinamic, de obicei de acelai tip, ce realizeaz nlnuirea 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 rndul lor, listele pot fi grupate n mai multe categorii, cele mai importante fiind listele simplu nlnuite, listele circulare i listele dublu legate. Principalele operaii care se pot efectua asupra unei liste sunt: crearea listei, adugare/tergere/modificare au unui element (nod), accesul la un element i tergerea n totalitate a listei. Lista simplu nlnuit este cel mai simplu tip de list din punctul de vedere al legrii elementelor: legtura ntre elemente este ntr-un singur sens, de la primul ctre ultimul. Exist un nod pentru

142

care pointerul spre nodul urmtor este NULL. Acesta este ultimul nod al listei simplu nlnuite (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 nlnuit poate fi identificat n mod unic prin primul element al listei. Determinarea ultimului element se poate face prin parcurgerea secvenial a listei pn la ntlnirea nodului cu pointerul spre nodul urmtor cu valoarea NULL.

info next

info next L i s t s i m p l n lnu i t

info NULL

Listele circulare sunt un alt tip de liste pentru care relaia de preceden nu mai este liniar ci ultimul element pointeaz ctre primul. Procedurile necesare pentru crearea i utilizarea unei liste circulare sunt extrem de asemntoare cu cele de la liste liniare, cu singura deosebire c ultimul element nu are adresa de pointer vid (NULL) ci adresa primului element.
info next info next List circular 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 nlnuite ce partajeaz acelai cmp comun info, una fiind imaginea n oglind a celeilalte.
in fo ne xt NU LL inf o nex t pre viou s Li st du blu le gat in fo NU LL pre vious

Pointerul next indic spre urmtorul nod, iar cmpul previous indic spre cmpul anterior.

143

Vom prezenta n continuare modul n care putem proiecta funciile principale care acioneaz 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. Particularizrile se vor face pe exemplul bazei de date construite anterior. Tipul unui nod se declar n felul urmtor:
General struct tip_nod { declaratii struct tip_nod *next; }; Particular struct addr { char name[20]; char street[30]; char city[15]; char state[10]; unsigned int zip; struct addr *next; };

Att la crearea unei liste ct i la inserarea unui nod se va apela funcia malloc() pentru a rezerva spaiu de memorie pentru un nod. Zona alocat prin intermediul funciei malloc() se poate elibera folosind funcia free(). Propunem conceperea unui program asemntor cu programul de exploatare a unei baze de date conceput anterior dar care s foloseasc n locul unui tablou static o list simplu nlnuit. Programul poate fi extins ulterior pentru structuri dinamice mai complexe, care s foloseasc liste dublu nlnuite sau circulare. Programul va avea urmtoarele faciliti: 1. Crearea unei liste simplu nlnuite n memorie (pentru prima oar). 2. Exploatarea unei liste simplu nlnuite n memorie: 2.1. Inserarea unor nregistrri (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 (adugare la coada listei) 2.2. tergerea unor nregistrri a) tergerea primului nod al listei b) tergerea unui nod intern al listei

144

c) tergerea ultimului nod al listei 3. Salvarea din memorie pe disc a unei liste simplu nlnuite 4. Citirea de pe disc n memorie a bazei de date salvate anterior 5. Afiarea pe ecran, nregistrare cu nregistrare, a bazei de date coninute n lista dinamic. Programul este prevzut cu o intefa simpl prin care utilizatorul poate alege dintre opiunile pe care le are la dispoziie. Interfaa este sub forma unui meniu din care, prin tastarea iniialelor comenzilor, acestea se lanseaz n execuie. Vom descrie pe rnd funciile care ndeplinesc sarcinile enumerate mai sus. Pentru o mai bun proiectare a programului, vom folosi att modularizarea intern prin construirea mai multor funcii ct i o modularizare extern prin izolarea programului principal de restul funciilor care se folosesc.
Baz de date cu list simplu nlnuit Interfa cu a utilizatorul Afiare pe ecran a nregistr rilor din baza de date

Comenzi pentru citire/scriere Comenzi pentru procesarea pe disc listei dinamice Citire de pe disc n lista Salvarea pe disc a listei Insera re

dinamic

dinamice din memorie

tergere

- prima nregistrare - ultima nregistrare - nregistrare intermediar

Exemplu: Programul principal bd_main.c


# include "local.h"

145

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

Fiierul 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]; 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 fiier header se realizeaz definirea ablonului structurii addr i apoi, prin comanda typedef, se definete un nou tip de date numit TNOD. First i last sunt pointeri la structuri de acest tip iar fp este pointer la fiier (file pointer). Cu extern se definesc prototipurile unor funcii care nu se gsesc n fiierul bd_main.c ci n fiierul bd_bib.c unde sunt colectate toate funciile pe care le folosim. Acest fiier are la rndul su un fiier header numit local1.h care conine:
# include <stdio.h> # include <ctype.h> # include <string.h> # include <stdlib.h> extern FILE *fp; typedef struct addr { char name[20]; char street[30];

146

char city[15]; char state[10]; unsigned int zip; struct addr *next;}TNOD; extern TNOD *first, *last;

Prin cele dou fiiere header cele dou fiiere surs bd_main.c i bd_bib.c se pot compila mpreun, rezultnd un singur fiier executabil. Interfaa cu utilizatorul Aceasta const ntr-un meniu principal, care permite accesarea funciilor principale, i din dou submeniuri pentru operaiile de tergere i inserare de nregistrri.
/* 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"); 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 {

147

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

ncrcarea bazei de date de pe disc Baza de date este nregistrat pe disc n fiierul maillist.dat care se creaz la prima salvare a listei dinamice din memorie. Funcia loadf_nod() citete o nregistrare (record) din fiier, returnnd 1 n caz de reuit, -1 dac se ajunge la sfritul fiierului 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;}} /* 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;}

Funcia 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 nregistrri (nod sau structur).

148

Salvarea bazei de date pe disc Reprezint operaiunea invers celei de citire. Lista simplu nlnuit din memorie se salveaz n ntregime pe disc n fiierul maillist.dat. Spre deosebire de funcia loadf_list() care apela la funcia loadf_nod(), funcia savef_list() nu face nici un apel la o alt funcie 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 nlnuite La nceput variabilele first i last au valoarea NULL, lista fiind vid. Crearea unei liste se face prin funcia create_list. Ea returneaz 0 pentru eroare i 1 dac operaia reuete. Prin aceast funcie se iniializeaz o list dinamic n memorie, fr a face o citire a unei baze de date create anterior. Aceasta este prima funcie care se apeleaz cnd 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");

149

free(p);}}

Afiarea listei dinamice din memorie Prin aceast operaie, realizat de funcia display_list(), se afieaz secvenial toate nregistrrile coninute n nodurile listei pornind de la prima i terminnd 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(); p=first; if (p!=NULL) do { disp_nod(p); p=p->next; getchar();} while (p!=NULL); else printf("Lista vida !\n");}

Funcia display_list() apeleaz la funcia disp_nod() care afiez o singur nregistrare. Dac este nevoie de afiarea unui cap de tabel (antet) se apeleaz la funcia disp_antet(). Inserarea unor noduri n lista dinamic Aceast operaie presupune introducerea unor noduri (nregistrri) fie la nceputul listei naintea primului nod, fie la sfritul su (adugare) dup ultimul nod, fie ntre dou noduri vecine nesituate la extremiti. Funciile listate mai jos realizeaz aceste sarcini.
// functia de inserare void insert() { char choice; for (; ;) { choice = menu_insert(); switch (choice) {

150

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

151

La inserarea unui nod, este nevoie ca acesta s fie legat de cele ntre care se introduce cu ajutorul pointerilor next. Dac se dorete ca nodul inserat s fie primul sau ultimul din list, este nevoie s modificm 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 extremiti. n acest caz, este nevoie s modificm pointerii first i last . n toate cazurile este nevoie s refacem legturile distruse dup dispariia prin tergere a unor noduri. tergerea nu este efectiv, ci numai se refac legturile i se elibereaz cu funcia free() zona de memorie alocat n prealabil (prin inserare sau creare) cu funcia malloc(). Mai mult, avem opiunea de a terge ntreaga list din memorie n scopul nlocuirii n totalitate a datelor coninute n nregistrri.
// funcia de tergere void erase() { char choice; for (; ;) { choice = menu_del(); switch (choice) { case 'e' : del_list(); break; 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

152

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

Capitolul VIII FUNCII


8.1. Forma general a unei funcii
Principalul mijloc prin care se pot modulariza programele C este oferit de conceptul de funcie (unele funcii standard au fost deja folosite pentru diverse operaii). n C orice funcie "ntoarce" (returneaz), dup apel, o valoare al crui tip trebuie cunoscut. n practic, ns, de multe ori valorile returnate de funcii sunt ignorate. Standardul limbajului C permite chiar declararea explicit a funciilor care nu returneaz valori ca fiind de tip void. n C o funcie poate fi definit, declarat i apelat. Definirea unei funcii C se realizeaz dup urmtorul format general:
tip nume_funcie (lista_parametri)

153

declaraii_parametri { declaraii _locale instruciuni }

sau, o form mai nou adoptat de ANSI-C n 1989:


tip nume_funcie (declaraii _parametri) { declaraii _locale instruciuni }

Tipul unei funcii corespunde tipului valorii pe care funcia o va returna utiliznd instruciunea return. Acesta poate fi un tip de baz (char, int, float, double etc.) sau un tip derivat (pointer, structur etc.). Dac pentru o funcie nu se specific nici un tip, atunci, implicit se consider c funcia 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 apelrii acesteia. Tipul acestor parametri este descris fie n paragraful declaraii_parametri, fie direct n lista parametrilor. Lista parametrilor este nchis ntre paranteze. Chiar dac o funcie nu are parametri, parantezele nu trebuie s lipseasc. De exemplu, funcia max(a, b), care returneaz cel mai mare dintre numerele ntregi a i b, se poate defini sub forma:
int max(a, b) int a, b; { if (a > b) return (a); else return (b); }

sau

int

max(int a, int b)

{ if (a > b) return (a); else return (b); }

n cazul n care tipul parametrilor formali ai unei funcii nu se declar, atunci ei sunt considerai implicit de tip int. n cazul compilatoarelor moderne, programul urmtor va genera dou avertismente. Exemplu:
# include <stdio.h> float max(); // Prototipul functiei max() float x; void main()

154

{ 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 compilrii 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 compilrii, la parcurgerea liniei de declarare a funciei:
float max(a,b)

parametrii formali a i b sunt considerai de tip ntreg. Programul, modificat ca mai jos, va duce la o compilare fr probleme: Exemplu:
# include <stdio.h> 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)

155

{ 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. Rentoarcerea dintr-o funcie


Mai inti precizm c instruciunea return are dou utilizri importante: return determin ieirea imediat din funcia n care se afl instruciunea i rentoarcerea n programul apelant; return poate fi folosit pentru a ntoarce o valoare. Rentoarcerea dintr-o funcie n programul apelant (funcia apelant) se poate face n dou moduri: a) Dup parcurgerea codului corespunztor funciei se revine n programul apelant la instruciunea imediat urmtoare. Exemplu: Aceasta funcie tiprete un ir n ordine invers:
# include <string.h> void afis_invers(char s[]); void main() { char s[10]; printf("Introduceti un sir de 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"); }

caracrere

de

la

b) Al doilea mod de ntoarcere dintr-o funcie se realizeaz utiliznd funcia return. Funcia return poate fi folosit fr nici o valoare asociat. Exemplu: Funcia urmtoare afieaz rezultatele ridicrii unui numr 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 */

156

i = 1; for (; exp; exp--) i = baza * i; printf (" Rezultatul este %d \n", i);

Dac exponentul exp este negativ, instruciunea return determin terminarea funciei nainte ca sistemul s ntlneasc }, dar nu returneaz nici o valoare. O funcie poate conine mai multe instruciuni return, care pot simplifica anumite algoritme.

8.3. Valori returnate


Toate funciile, cu excepia 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 instruciunea return. Dac o funcie este declar ta ca fiind de tip void, aceasta poate fi folosit n orice expresie C. O funcie nu poate fi membrul stng ntr-o expresie de atribuire. De exemplu, instruciunea: swap(x,y) = 100; este greit. Funciile care nu sunt de tip void se pot mpri n trei categorii: 1) Funcii "pure" sunt funciile care efectueaz operaii asupra argumentelor i returneaz o valoare de baz pe acea operaie. Exemplu: sqrt() i sin() returneaz respectiv radcina ptrat i sinusul argumentului. 2) A doua categorie de funcii sunt cele care manipuleaz informaii i ntorc o valoare care arat reuita sau eecul acestei manipulri. Un exemplu este fwrite() folosit pentru a scrie informaii pe disk. Dac scrierea se face cu succes, fwrite() ntoarce numrul de octei nscrii (cerui s se nscrie); orice alt valoare indic apariia unei erori. 3) A treia categorie de funcii sunt cele care nu trebuie s ntoarc o valoare explicit. De exemplu, funcia printf() ntoarce numrul de caractere tiprite, numr care, de obicei, nu are o utilizare ulterioar. Dac pentru o funcie care returneaz o valoare nu se specific o operaie de atribuire, calculatorul va ignora valoarea returnat. Exemplu: Considerm urmtorul program care utilizeaz funcia mul():
# include <stdio.h> mul(); void main (void){ int x, y, z;

157

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 funcii


n C, fiecare funcie este un bloc de instruciuni. Codul unei funcii este propriu acelei funcii i nu poate fi accesat (utilizat) prin nici o instruciune din orice alt funcie, cu excepia instruciunii de apel al acelei funcii. (De exemplu, nu putem utiliza goto pentru a realiza saltul dintr-o funcie n mijlocul unei alte funcii). Blocul de instruciuni care descrie corpul unei funcii 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 pri ale programului. Codul i datele care sunt definite n interiorul unei funcii nu pot interaciona cu codul i datele definite n alt funcie, deoarece cele dou funcii au scopuri diferite. n cadrul unei funcii se deosebesc trei tipuri de variabile, astfel: variabile locale, parametri formali i variabile globale. Domeniul unei funcii determin modul n care alte pri 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 funcii se numesc variabile locale. Variabilele locale pot fi referite numai prin instruciuni interioare blocului n care au fost daclarate aceste variabile. Variabilele locale nu sunt cunoscute n afara blocului n care au fost daclarate, domeniul lor limitndu-se numai la acest bloc. Mai exact, variabilele locale exist numai pe durata execuiei blocului de cod n care acestea au fost daclarate; deci o variabil local este creat la intrarea n blocul su i distrus la ieire. De obicei, blocurile de program n care se declar variabilele locale sunt funciile. Implicit, o

158

variabil local este auto, deci se stocheaz n memoria stiv. Ea poate fi declarat i register, caz n care se stocheaz n regitrii interni ai microprocesorului sau poate fi declarat static, caz n care se stocheaz n memoria de date sau static, valoarea sa pstrndu-se i la ieirea din funcie. 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 cruia a fost declarat. Limbajul C conine cuvntul cheie auto, care poate fi folosit pentru declararea de variabile locale. Cu toate acestea, ntruct C presupune c toate variabilele neglobale sunt prin definiie (implicit) variabile locale, deci au atributul auto, acest cuvnt cheie nu se utilizeaz. De obicei, variabilele locale utilizate n interiorul unei funcii se declar la nceputul blocului de cod al acestei funcii. Acest lucru nu este neaprat necesar, deoarece o variabil local poate fi declarat oriunde n interiorul blocului n care se utilizeaz, dar nainte de a fi folosit. Exemplu: Considerm urmtoarea funcie:
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 ieirea din acesta. Mai mult, s este cunoscut

159

numai n interiorul blocului if i nu poate fi referit din alt parte, chiar din alt parte a funciei func() care o conine. Deoarece calculatorul creeaz i distruge variabilele locale la fiecare intrare i ieire din blocul n care acestea sunt daclarate, coninutul lor este pierdut o dat ce calculatorul prseste blocul. Astfel, variabilele locale nu pot reine valorile lor dup ncheierea apelului funciei.

8.4.2. Parametri formali


Dac o funcie va folosi argumente, atunci aceasta trebuie s declare variabilele care vor accepta (primi) valorile argumentelor. Aceste variabile se numesc parametri formali ai funciei. Parametrii formali ai funciei se comport ca orice alt variabil local din interiorul funciei. Declararea parametrilor formali se face dup numele funciei i naintea corpului propriu-zis al funciei. Exemplu:
/* Funcia urmtoare ntoarce 1 dac caracterul c aparine 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); if (func(s, c)) printf("Caracterul se afla in sir\n"); else printf("Caracterul NU se afla in sir\n"); }

Funcia func() are doi parametri: s i c. Aceasta funcie ntoarce 1 dac caracterul c aparine irului i 0 dac c nu aparine irului. Precizm c argumentele cu care se va apela funcia trebuie s aib acelai tip cu parametrii formali declarai n funcie. Aceti parametri formali pot fi utilizai ca orice alt variabil local. Un al doilea mod (recomandat de ANSI-C n 1989) de a declara parametrii unei funcii const n declararea complet a fiecrui parametru n interiorul parantezelor asociate funciei. De exemplu, declararea parametrilor funciei func() de mai sus se poate face i sub forma:
func (char *s, char c) {

160

. . . . . . . . . . . }

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 pstra valorile lor pe durata execuiei complete a programului, deci se stocheaz n memoria static. Variabilele globale se creeaz prin declararea lor n afara oricrei funcii (inclusiv main()). Variabilele globale pot fi plasate n orice parte a programului, evident n afara oricrei funcii, i nainte de prima lor utilizare. De obicei, variabilele globale se plaseaz la nceputul unui program, mai exact naintea funciei 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 */ for (count = 1; count < 10; count ++) printf ("%2d\n" , count); }

Se observ c, dei nici funcia main() i nici funcia func1() nu au declarat variabila count, ambele o folosesc. Funcia func2() a declarat o variabil local count. Cnd 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 acelai nume, toate referirile la numele variabilei n interiorul funciei 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.

161

Variabilele globale sunt memorate ntr-o zon fix de memorie destinat special acestui scop (memoria static), rmnnd n aceast zon pe parcursul ntregii execuii 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 nelegerea lucrului cu pointeri este urmtorul: se declar o variabil global x, i apoi dou variabile locale cu acelai nume, x, care se vor ascunde una pe cealalt. Pentru a vedea i modul n care se stocheaz n memorie aceste variabile, vom afia i locaiile de memorie pentru fiecare tip de variabil x, precum i pentru pointerul p corespunztor. Reinem c, n general, dac:
p = &x => p = &x = &(*p) = (&*)p = p *p = *(&x) = (*&)x = x

Faptul c * respectiv & sunt operaiuni complementare (inverse una celeilalte) se observ din relaiile 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 egalitile de mai sus, pointer sau variabil). Adresa Mem oria .. .. .. .. .. .. .. .. .
&x= adresa x &p= adresa p &q= adresa q x .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. p= &x .. .. .. .. .. .. .. .. q= &p

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

162

{ 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 execuiei programului, obinem urmtorul rezultat:


x=34 x=1 x=2 q=0065FDF4 &x=00426A54 &x=0065FDE8 &x=0065FDE4 *q=0065FDE4 *p=34 *p=1 *p=2 **q=2 p=00426A54 &p=0065FDF4 p=0065FDE8 &p=0065FDF4 p=0065FDE4 &p=0065FDF4 &q=0065FDEC

Prin declaraia int *p = &x; variabila p este declarat variabil pointer ctre o dat de tip ntreg i este iniializat cu adresa variabilei spre puncteaz, anume x. Pointerul q este declarat ca un pointer la un alt pointer.
Denum. Tipul Variab. variabilei Caracteristici ale Caracteristici ale variabilei variabilei pointer ataate Adresa &x Valoare Adresa &p Valoarea p *p x 00426A54 34 0065FDF4 00426A54 34 0065FDF0 1 0065FDF4 0065FDF0 1 0065FDEC p = &x q = &p 2 *p = x *q = p &q= 0065FDEC q= 0065FDF4 **q =2 0065FDF4 0065FDEC 2

int x int x int x p q

global local local pointer local pointer local

Acelai lucru se face pentru celelalte dou variabile locale. Din interpretarea rezultatelor de mai sus putem trage urmtoarele concluzii. Spre exemplu,
printf(%d,x); este echivalent cu printf(%d,*p); scanf(%d,&x); este echivalent cu scanf(%d,p);

163

dac n prealabil s-a fcut atribuirea p = &x; Se mai observ cum pointerul p, care iniial indica spre variabila global x, este ncrcat cu adresa de memorie 4336176 (00426A54 H), pe cnd n cazurile cnd indica variabilele locale x se alocau adresele de memorie 6684140 (0065FDF0 H) i 6684144 (0065FDEC H), adrese adiacente, la un interval de 4 octei, att ct sunt alocai pentru variabila de tip ntreg. Se observ c variabila global se afl ntr-o alt zon de memorie dect variabilele locale. Modul de lucru cu pointerii este scos n eviden prin instruciunile:
q=&p; r=*q; // q retine adresa pointerului p // 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 iniializeaz 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 funcii. De exemplu, pentru scrierea unei funcii mul() care determin produsul a doi ntregi se pot utiliza dou metode, una general i una specific, astfel: General :
mul (x, y) int x, y { return (x * y);}

Specific :
int x, y; mul () { return (x * y);}

Cnd se dorete realizarea produsului a oricror doi ntregi x i y se utilizeaz varianta general a funciei, iar cnd se dorete 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); }

164

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

din

8.5. Apelul funciilor


Apelul unei funcii nseamn referirea funciei, mpreun cu valorile actuale ale parametrilor formali, precum i preluarea valorii returnate, dac este necesar. La apelul funciei, tipul argumentelor trebuie s fie acelai cu cel al tipului parametrilor formali ai funciei. Dac apar nepotriviri de tip (de exemplu, parametrul formal al funciei este de tip int, iar apelul funciei folosete un argument de tip float) de obicei, compilatorul C nu semnalizeaz eroare, dar rezultatul poate fi incorect. n C transmiterea argumentelor de la funcia apelant spre funcia apelat se face prin valori sau prin adrese. a) n cazul transmiterii argumentului prin valoare, se realizeaz copierea (atribuirea) valorilor fiecrui argument n (la) cte un parametru formal al funciei apelate. Exemplu: Se apeleaz o funcie ce calculeaza ptratul unui numr 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, schimbrile survenite asupra parametrului formal x nu afecteaz variabila utilizat pentru apelul funciei (schimbrile lui x nu modific n nici un fel pe t). b) Dac transmiterea argumentului se realizeaz prin adrese, atunci la apelul funciei n loc de valori se folosesc adrese, iar n definiie, parametrii formali se declar ca pointeri.

165

Exemplu: O funcie 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 funciei swap() sunt pointeri la float. Programul urmtor arat modul de apel al acestei funcii.
# 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 funciei swap() i nu valorile lui x i y. Un apel combinat, valoare-referin este prezentat n exemplul urmtor:
# 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 funciilor avnd ca argumente tablouri


Cnd se apeleaz o funcie avnd ca argument un tablou, acesteia i se va transmite un pointer la primul element al tabloului. Reamintim c n C numele unui tablou fr nici un indice este un pointer la primul element al tabloului. Deci, un argument de tipul T[ ]

166

(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 fr dimensiune:
display(int num[]) { int i; for(i = 0; i < 10; i++) printf ("%d",num[i]); }

Aceasta funcie 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 funciei. 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 declaraie deoarece putem indexa orice pointer utiliznd []. Toate cele trei metode de declarare a unui tablou ca parametru produc acelai rezultat: un pointer. Cu toate acestea, un element al unui tablou folosit ca argument al unei funcii va fi tratat ca orice alt variabil. Astfel, programul de mai sus poate fi rescris sub forma:
# include <stdio.h> void main (void) {

167

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 afiarea tuturor numerelor prime cuprinse ntre dou limite ntregi. Programul principal apeleaz dou funcii: nr_prim() returneaz 1 dac argumentul su ntreg este prim i 0 dac nu este prim; numerele prime sunt grupate ntr-un vector, care se afieaz ulterior cu funcia 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; 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 reinut c atunci cnd un tablou se utilizeaz ca argument al unei funcii, calculatorul transmite funciei adresa de nceput a tabloului. Acest lucru constituie o excepie a limbajului C n convenia de transmitere a parametrilor prin valoare. Astfel, codul funciei poate aciona asupra coninutului tabloului i l poate altera. Exemplu: Programul urmtor va modifica coninutul vectorului sir din funcia main() dup apelul funciei afis_litmari().
# include <stdio.h> # include <ctype.h> afis_litmari(); void main (void) { char sir[80];

168

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 rulrii programului va fi:


abcdefghijklmnoprstuvxyzw ABCDEFGHIJKLMNOPRSTUVXYZW ABCDEFGHIJKLMNOPRSTUVXYZW

Exemplu: Dac nu dorim s se ntmple 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) /* 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 rulrii va fi de aceast dat:


abcbdefghijklmnoprstuvxyzw ABCBDEFGHIJKLMNOPRSTUVXYZW abcbdefghijklmnoprstuvxyzw

n aceasta variant coninutul tabloului ramne nemodificat, deoarece programul nu-i schimb valoarea. Un exemplu clasic de transmitere a tablourilor ntr-o funcie l constituie funcia gets() din biblioteca C standard. Prezentm o variant simplificat a acestei funcii numit xgets().
xgets(s) char *s; { char ch; int t; for (t = 0; t < 80; ++t) { ch = getchar(); switch (ch) {

169

case '\n' : s[t] = '\0'; /* terminare sir return; case '\b': if (t > 0) t--; break; default: s[t] = ch; } } s[80] ='\0'; }

*/

Funcia xgets() trebuie apelat avnd ca argument un tablou de caractere, care, prin definiie, este un pointer la caracter. Numrul caracterelor introduse de la tastatur, prin funcia for este de 80. Dac se introduc mai mult de 80 de caractere, funcia se ncheie cu return. Dac se introduce un spaiu, contorul t este redus cu 1. Cnd se apas CR, xgets() introduce terminatorul de ir.

8.7. Argumentele argc i argv ale funciei main()


Singurele argumente pe care le poate avea funcia main() sunt argv i argc. Parametrul argc conine numrul argumentelor din linia de comand i este un ntreg. ntotdeauna acesta va fi cel puin 1, deoarece numele programului este codificat ca primul argument. 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: Urmtorul program arat modul de utilizare al argumentelor linie_comanda i va afia Hello urmat de numele dumneavoastr, dac v introducei 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 numete ARG_LC.C i numele dumneavoastr este DAN, atunci, pentru a executa programul, n linia de comand, vei tipri ARG_LC DAN. Ieirea programului va fi Hello DAN !.

170

Argumentele linie_comanda trebuie separate prin spaiu 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. Evitai folosirea sa fr paranteze, adic char
*argv.

Urmtorul program numit "nrinvers" numr invers de la o valoare specificat prin linia de comand i transmite un beep cnd ajunge la zero. Precizm c programul convertete primul argument, care conine numrul la un ntreg folosind funcia standard atoi(). Dac irul "display" apare ca al doilea argument_comanda, programul va afia, de asemenea, numrul 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"); 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 */ }

Observaie: Dac n linia de comand nu se specific nici un argument, programul va afia un mesaj de eroare.

8.8. Funcii care returneaz valori nentregi


Dac nu se declar explicit tipul funciei, compilatorul C o va declara implicit de tip int. Pentru ca funcia s ntoarc un tip diferit de int trebuie, pe de o parte, s se precizeze un specificator de tip al funciei i apoi s se identifice tipul funciei naintea apelului acesteia. O funcie C poate returna orice tip de dat din C. Declararea tipului este similar celei de la declararea tipului variabilei: specificatorul de tip ce precede funcia indic tipul datei ntoarse de

171

funcie. Pentru a nu se genera incertitudini datorate dimensiunii de reprezentare, nainte de utilizarea unei funcii ce ntoarce tipuri nentregi, tipul acestei funcii trebuie fcut cunoscut programului. Acest lucru este necesar deoarece compilatorul nu cunoate tipul datei ntoarse de funcie i acesta va genera un cod greit pentru apelul funciei. Pentru a preveni aceast greeal, la nceputul programului se plaseaz o form special de declaraie care s precizeze compilatorului ce tip de valoare va returna acea funcie. Aceast declaraie se numete prototipul funciei. 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 funciei are forma general:


specificator_de_tip nume_funcie();

Chiar dac funcia are argumente, n declaraia de tip acestea nu se precizeaz (cu excepia compilatoarelor mai vechi de 1989, care nu sunt adaptate la cerinele ANSI-C). Dac o funcie ce a fost declarat int ntoarce un caracter, calculatorul convertete valoarea caracter ntr-un ntreg. Deoarece conversiile caracter --> ntreg-caracter sunt fr probleme, o funcie ce ntoarce un caracter poate fi definit ca o funcie care ntoarce un ntreg.

8.9. Returnarea pointerilor


Dei funciile care ntorc pointeri se manipuleaz n acelai mod ca i celelalte tipuri de funcii, trebuie discutate cteva concepte importante. Pointerii la variabile nu sunt nici ntregi, nici ntregi fr semn. Pointerii sunt adrese de memorie a anumitor tipuri de date: int, char, float, double, struct etc. Motivul acestei distincii este legat de faptul c atunci cnd se prelucreaz un pointer aritmetic, aceast prelucrare este dependent de

172

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, cnd un pointer este incrementat sau decrementat, acesta va indica ctre elementul urmtor, respectiv anterior, din tabloul pe care l indirecteaz. De exemplu, dac funcia int f() returneaz un ntreg, atunci funcia 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 urmtorul element. Exemplu: Programul urmtor conine o funcie care ntoarce un pointer ntr-un ir n locul n care calculatorul gsete o coinciden de caractere.
char *match (char c, char *s) {int count; count = 0; while (c!=s[count] && s[count] != '\0') return (&s[count]); }

count ++;

Funcia match() va ncerca s ntoarc un pointer la locul (elementul) din ir unde calculatorul gsete prima coinciden cu caracterul c. Dac nu se gseste nici o coinciden, funcia va ntoarce un pointer la terminatorul de ir (NULL). Un scurt program ce ar utiliza funcia match() este urmtorul :
# include <stdio.h> # 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 citete mai nti un ir i apoi un caracter. n cazul n care caracterul este n ir, atunci se tipreste irul din punctul

173

unde se afl caracterul, altfel se tiprete "Nu exist nici o coinciden". Un caz interesant este oferit de funciile care returneaz pointeri ctre iruri de caractere. O astfel de funcie se declar sub forma:
char *f()

Exemplu: Programul urmtor arat modul n care se definete, se declar i se apeleaz o astfel de funcie.
# 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 funciei strcpy() din string.h , deci o funcie care copiaz caracterele din irul s2 n irul s1. Rezultatul se gsete 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() */
# 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 iniializeaz s0 cu s1. Bucla while atribuie valorile (caracterele) (*s2) n locaiile indicate de pointerul s1, incrementnd ambii pointeri simultan. Bucla se termin la ntlnirea caracterului null, care se copiaz i el. Valoarea returnat, s0, reine adresa de nceput a irului s1.

174

Un ultim exemplu l constituie un program de manipulare a unor matrici. Acest program realizeaz citirea unei matrici, transpunerea sa i respectiv afiarea rezultatului apelnd la funciile 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_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]); 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; }

[dim_lin

8.10. Funcii de tip void


Din punct de vedere sintactic, tipul void se comport ca un tip fundamental (de baz). Nu exist obiecte de tip void.

175

Tipul void este utilizat pentru declararea implicit a acelor funcii 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) void *pv /* functia f nu intoarce o valoare */ /* pointer la un obiect necunoscut */

Utiliznd void se impiedic folosirea funciilor ce nu ntorc o valoare n orice expresie, prevenind astfel o ntrebuinare greit a acestora. De exemplu, funcia afis_vertical() afieaz pe ecran argumentul su ir, vertical, i ntruct nu ntoarce nici o valoare, este declarat de tip void.
void afis_vertical (sir) char *sir; { while (*sir) printf ("%c \n", *sir ++);

naintea utilizrii acestei funcii sau oricrei alte funcii de tip void, aceasta trebuie declarat. Dac nu se declar, compilatorul C consider c aceasta ntoarce o valoare ntreag. Astfel, modul de utilizare al funciei afis_vertical() este urmtorul:
# 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 ++); }

8.11. Funcii prototip


Dup cum se tie, naintea folosirii unei funcii care ntoarce o alt valoare dect int, aceasta trebuie definit. Funciile prototip au fost adugate de comitetul ANSI-C standard. Declararea unei funcii prototip se face conform urmtorului format:
tip nume_funcie (tip_arg1, tip_arg2,...)

unde:

tip = tipul valorii ntoarse de funcie; tip_arg1, tip_arg2,... = tipurile argumentelor funciei. Exemplu: Programul urmtor va determina compilatorul s emit un mesaj de eroare sau de avertisment deoarece acesta ncearc s

176

apeleze funcia func() avnd al doilea argument de tip int, n loc de float, cum a fost declarat n funcia 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); }

Funciile prototip se folosesc pentru a ajuta compilatorul n prima faz n care funciile utilizate sunt definite dup programul principal. Acesta trebuie ntiinat asupra tipul datei returnat de o funcie pentru a aloca corect memoria. Dac funciile sunt declarate naintea liniei de program main(), funciile prototip nu mai sunt necesare, deoarece compilatorul extrage informaia despre funcii n momentul n care parcurge corpul definiiei lor. Spre exemplu, programul de mai sus se poate scrie i sub forma urmtoare, n care nu vom mai avea o declaraie de funcie 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 */

Utiliznd recomandrile 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 funcia prototip:


#include <stdio.h> void func(); /* Declarare prototip fara parametri formali ! */

177

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 evideniat o recomandare care simplific efortul de programare n sensul c n linia de declarare a prototipurilor funciilor folosite este necesar s definim tipul funciei nu i tipul parametrilor formali. Compilatorul se informeaz despre tipul parametrilor formali la parcurgerea corpului definiiei funciei. Din cele de mai sus se observ ca folosirea funciilor prototip ne ajut la verificarea corectitudinii programelor, deoarece nu este permis apelarea unei funcii cu alte tipuri de argumente, dect tipul celor declarate.

8.12. Funcii recursive


Funciile C pot fi recursive, adic se pot autoapela direct sau indirect. O funcie este recursiv dac o instruciune din corpul funciei este o instruciune de apel al aceleiai funcii. Uneori o funcie recursiv se numete i funcie circular. Un exemplu de o astfel de funcie este funcia factorial() care determin factorialul unui numr. Aceast funcie se poate organiza recursiv, tiind c: n! = n(n-1)!. Avnd n vedere 0!=1, aceast funcie se poate organiza astfel:
long factorial (int n) { if (n == 0) return (1); else return (n * factorial(n-1)); }

Programul de apel al acestei funcii 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)); }

178

Observaie: Atunci cnd o funcie se autoapeleaz recursiv, la fiecare apel al funciei se memoreaz pe stiv att valorile parametrilor actuali, ct i ntregul set de variabile dinamice definite n cadrul funciei. Din aceasta cauz stiva trebuie dimensionat corespunztor. O variant echivalent a funciei factorial() definit mai sus ar fi urmtoarea:
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 relaia de recuren: an = an-1+ an-2 , unde a0 = 0 i a1=1. Codul funciei 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 recursivitii poate s nu conduc la o reducere a memoriei necesare, att timp ct stiva este folosit intens pentru fiecare apel recursiv. De asemenea i execuia programului poate s nu fie mai rapid. Dar codul recursiv este mai compact i de multe ori mai uor de scris i neles dect echivalentul su recursiv. Recursivitatea este convenabil n mod deosebit pentru operaii pe structuri de date definite recursiv, cum sunt listele, arborii etc.

8.13. Clase de memorare (specificatori sau atribute)


Din punct de vedere al execuiei programelor C, memoria computerului este organizat n trei zone, cunoscute n mod tradiional ca segment de memorie text, segment de memorie static (sau de date) i segment de memorie dinamic (sau stiv).
Segment de memorie text Conine instruciunile programului, deci (memorie program) programul executabil Segment de memorie Conine variabilele a caror locaie rmne fix static

179

Segment de memorie dinamic (de tip stiv)

Conine variabilele de tip automatic, parametrii funciilor i apelurile i retururile de/din funcii

n tabelul urmtor se prezint caracteristicile claselor de memorie.


Specificator de memorie Domeniul de vizibilitate al variabilei Durata de via a variabilei Plasament n memoria dinamic (de tip stiv) n regitrii microproceso rului n memoria static In memoria static

Auto Local fiecrei funcii Temporar, numai (automatic) sau fiecrui bloc n care cnd se execut a fost declarat funcia n care este declarat Register Local fiecrei funcii Temporar, numai (registru) cnd se execut funcia n care este declarat Extern Global, de ctre toate Permanent, pe funciile dintr-un fiier parcursul rulrii surs sau din mai multe programului fiiere surs executabil Static Local sau global Permanent, ct timp este in memorie programul executabil

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 funcia care o declar este activat) sau permanent (exist pe toat durata de execuie 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 declaraia (n interiorul unei funcii sau naintea oricrei funcii). Variabilele cele mai folosite sunt cele care sunt declarate n blocurile aparinnd unei funcii. Aceste variabile sunt de dou feluri: - auto, aa cum sunt marea majoritate a variabilelor declarate numai prin tip. Acesta este un specificator implicit, deci nu este nevoie

180

s l invocm la declararea variabilelor. Variabilele auto sunt plasate n memoria stiv, iar domeniul de vizibilitate este local, numai pentru funcia n care variabila a fost declarat, iar din punctul de vedere al duratei de via sunt volatile, adic dispar din memoria stiv dup rentoarcerea din funcie. - static, declarate explicit. Variabilele static sunt plasate n memoria static, iar domeniul de vizibilitate este local, numai pentru funcia n care variabila a fost declarat, iar din punctul de vedere al duratei de via sunt permanente, adic nu dispar din memoria static dup rentoarcerea din funcie. - register, declarate explicit. Variabilele register sunt identice cu cele auto cu excepia faptului c stocarea nu are loc n memoria stiv ci n regitrii interni ai microprocesorului n scopul sporirii vitezei de execuie a programelor. - extern, declarate explicit. Din punct de vedere al modulrii unor programe, este preferabil s divizm 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 vzute de orice modul de program i de orice funcie component a unui modul program. Stocarea are loc n memoria static iar durata de via este permanent, pe toat perioada execuiei programului. Iniializarea unei variabile static difer de cea a unei variabile auto prin aceea c iniializarea este fcut o singur dat, la ncrcarea programului n memorie i lansarea sa n execuie. Dup prima iniializare, o variabil static nu mai poate fi reiniializat (de exemplu, la un nou apel al funciei n care este iniializat). Iat ilustrat acest lucru prin dou exemple simple. Se tiprete, cu ajutorul funciei receip(), un numr care este mai nti iniializat 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++;}

181

i se obine rezultatul:
First = 1 Second = 1

Dac se modific n funcia 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 obinem rezultatul
First = 1 Second = 2

Limbajul C suport patru specificatori ai claselor de memorare: auto, extern, static, register. Acetia precizeaz modul de memorare al variabilelor care i urmeaz. Specificatorii de memorare preced restul declaraiei unei variabile care capt forma general: specificator_de_memorare specificator_de_tip lista_de_variabile; Specificatorul auto Se folosete pentru a declar varibilele locale (obiectele dintr-un bloc). Totui, 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 (fiier) altor module de programe (fiiere) cu care se va lega primul pentru a alctui programul complet. Exemplu:
Modulul 1 int x, y; char ch; main() { . . . . . . . . . . . . } func1() { x = 123; } Modulul 2 extern int x, y; extern char ch; func22() { x = y / 10; } func23() { y = 10; }

Dac o variabil global este utilizat ntr-una sau mai multe funcii din modulul n care acestea au fost declarate nu este necesar

182

utilizarea opiunii extern. Dac compilatorul gsete o variabil ce n-a fost declarat, atunci acesta o va cuta 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 situaii ele i pstreaz valoarea la ieirea i intrarea, din sau n funcii. Variabile locale statice Cnd cuvntul cheie static se aplic unei variabile locale, compilatorul C creaz pentru aceasta o memorie permanent n acelai mod ca i pentru o variabil global. Diferena 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 funcie care necesit o astfel de variabil este un generator de numere care produce un nou numr 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 funciei serie() fr ca aceasta s fi fost declarat ca variabil global. Se observ de asemenea c funcia nu atribuie nici o valoare iniial variabilei numar_serie, ceea ce nseamn c valoarea inial a acesteia este 0. 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 fiiere). Exemplu:
static int numar_serie; //var. globala este cunoscuta numai in acest fisier serie() { numar_serie = numar_serie + 23;

183

return (numar_serie); } /* initializarea variabilei numar_serie */ serie_start(val_init) int val_init;{ numar_serie = val_init; }

Apelul funciei serie_start() cu o valoare intreag iniializeaz seria generatoare de numere, dup care apelul funciei serie() va genera urmtorul numr 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 funcie 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 att e ct i temp. De obicei utilizarea unei variabile registru conduce la micorarea timpului de execuie 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++); 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 gsi c timpul de execuie al buclei registru este aproximativ jumtate din timpul de execuie al variabilei non-registru.

184

8.14. Pointeri la funcii


ntr-un fel, un pointer funcie este un nou tip de dat. Chiar dac o funcie nu este o variabil, aceasta are o locaie fizic n memorie care poate fi atribuit unui pointer. Adresa atribuit pointerului este punctul de intrare al funciei. Acest pointer poate fi utilizat n locul numelui funciei. Pointerul permite de asemenea funciilor s fie pasate (trecute) ca argumente n alte funcii. Adresa unei funcii se obine utiliznd numele funciei fr 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 funcie. Deoarece n C nu exist o modalitate de a declara direct un pointer funcie, acesta se declar indirect folosind un pointer void care poate primi orice fel de pointer. Apelul funciei check() se face avnd ca parametri doi pointeri la caracter i un pointer funcie. Instruciunea : (*cmp)(a, b) realizeaz apelul funciei, n acest caz strcmp() iar a i b sunt argumentele acestuia. Exemplu:
# include <stdio.h>

185

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

Observaie: Funcia check() poate utiliza direct funcia 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"); }

Capitolul IX PREPROCESAREA

186

Un preprocesor C realizeaz substituirea macrodefiniiilor, o serie de calcule adiionale i incluziunea fiierelor. Liniile programului surs care ncep cu "#", precedat eventual de spaiu comunic cu preprocesorul. Sintaxa acestor linii este independent de restul limbajului; pot apare oriunde n program i pot avea efect care se menine (indiferent de domeniul n care apare) pn la sfritul unitatii de translatare. Preprocesorul C conine urmtoarele 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 cnd l ntlnete n textul surs. Forma general a directivei #define este : #define identificator ir Se observ c directiva #define nu conine "; ". n secvena de atomi lexicali "ir" nu trebuie s apar spaiu. Linia se termina cu CR. Exemplu: # define TRUE 1 # define FALSE 0 Cnd n program se ntlnesc numele TRUE i FALSE, acestea se vor nlocui cu 1, respectiv 0. Instruciunea:
printf ("%d %d %d", FALSE, TRUE, TRUE + 5);

va afia pe ecran 0 1 6. Dup definirea unui macro_name, acesta poate fi folosit pentru definirea altui macro_name. Exemplu:
# define ONE 1 /* Se definete macro_name ONE */ # define TWO ONE + ONE /* Se utilizeaz macro_name ONE */ # define THREE ONE + TWO

187

Deci aceast macrodefiniie realizeaz simpla nlocuire a unui identificator cu irul asociat. Dac, de exemplu, se dorete 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 cnd n program se ntlnete identificatorul E_MS. Exemplu: Programul urmtor nu va afia "this is a test", deoarece argumentul lui printf() nu este nchis ntre ghilimele.
# define XYZ this is a test . . . . . . . . . . . . . . . . . printf ("XYZ");

Se va afia 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 "

Observaie: 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 argumente. Exemplu :

directiva

#define

poate

avea

# 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, instruciunea printf() va arata astfel :


printf("Numarul mai mic este: %d",(x<y)?x:y);

Directiva #error Directiva #error foreaz compilatorul s stopeze operaia de compilare cnd aceast este intilnita n program. Este utilizata n primul rind pentru depanarea programelor. Forma general a directivei este: #error mesaj_de_eroare

188

Aceasta linie determin procesorul s scrie mesajul de eroare i s termine compilarea. Directiva # include Directiva # include comand compilatorului s includ n fiierul ce conine directiva #include un alt fiier surs al crui nume este specificat n directiv. Formele directivei sunt :
# include <nume_fisier> # include "nume_fisier"

Prima form se refer la fiiere header (cu extensia .h) care se gsesc n subdirectorul include din fiecare mediu de programare C, iar cea de-a doua la fiiere 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 condiionat


Limbajul C conine cteva directive care ne permit s compilm selectiv anumite poriuni 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 adevrat, compilatorul va compila fragmentul de cod cuprins ntre #if i #endif, iar dac expresie_constanta este fals, compilatorul va sri peste acest bloc. Exemplu:
#define MAX 100 void main() { #if MAX > 99 printf("Se compileaza pentru tablouri > 99\n"); #endif }

Observaie: Expresie_constanta se evalueaz n timpul compilrii. De aceea, aceasta trebuie s conin numai variabile constante definite anterior utilizrii lor. Expresie_constanta nu trebuie s conin operatorul sizeof. Directiva #else lucreaz similar cu instruciunea else determinnd o alternativ de compilare. Exemplu :
# define MAX 10

189

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 tipri mesajul : Se compileaz pentru tablouri < 99 Directiva #elif inlocuiete "else if" i este utilizat pentru realizarea opiunilor 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 adevrat se compileaz "Secventa_de_instructiuni" i nu se mai tasteaz nici o alt expresie #elif. Dac "expresie" este fals, compilatorul verific urmtoarele expresii n serie, compilndu-se "Secventa_de_instructiuni_i", corespunzatoare primei "expresie_i" adevrat, 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 condiionat utilizeaz directivele #ifdef i #ifndef, care nseamn "if defined" i "if not defined". Forma general a lui #ifdef este :
#ifdef macro_name Secventa_de_instructiuni

190

#endif

Dac anterior apariiei secvenei 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 prntr-o directiv #define, atunci se va compila blocul dintre #ifndef i #endif. Att #ifdef, ct 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 afia: Hello TOM ! i JERY not defined.

Dac nu s-a definit TOM, atunci programul va afia : Hello anyone !. Directiva #undef Se utilizeaz pentru a anula definiia 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 definete att LENGTH, ct i WIDTH pn se ntlnete directiva #undef. Principala utilizare a lui #undef este de a permite localizarea unui macro_name numai n anumite seciuni ale programului. Directiva #line O linie cu una din formele:
#line numar "nume_fiaier" #line numar

191

determin compilatorul s considere, din motive de diagnosticare a erorilor, c numrul de linie al urmatoarei linii din programul surs este dat de "numr", iar numele fiierului n care se afl programul surs este dat de "nume_fiier". Dac lipseste "nume_fiier", programul surs se afl n fiierul curent. Exemplu: Urmtoarea secven face ca numrul de linie s nceap cu 100.
# line 100 void main() /* linia 100 */ { /* linia 101 */ printf ("%d\n" , __LINE__); /* linia 102 */ }

Instructiunea printf() va afia valoarea 102 deoarece aceast reprezint a treia linie n program, dup instruciunea #line 100. Directiva #pragma O linie de control de forma:
#pragma nume

determin compilatorul s realizeze o aciune care depinde de modul de implementare al directivei #pragma. "nume" este numele aciunii #pragma dorite. Limbajul C definete dou instruciuni #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 conine i cod n limbajul de asamblare. Directiva vid O linie de forma: # nu are nici un efect. Macro_names (macrosimboluri) predefinite Limbajul C conine civa identificatori predefinii, care la compilare se expandeaz pentru a produce informaii speciale. Acetia sunt:

192

__LINE__ o constanta zecimal care conine numele liniei surs curente. __FILE__ un ir care conine numele fiierului care se compileaz. __DATA__ un ir care conine data compilrii sub forma luna/zi/an. __TIME__ un ir care conine ora compilrii sub form: hh:mm:ss __STDC__ constanta 1. Acest identificator este 1 numai n implementarile standard; dac constanta este orice alt numr, 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 fiiere surs unice, cu excepia fiierelor header. Un singur fiier surs este n general suficient n cazul programelor mici. Modularizarea intern este un principiu de baz al programrii n C i const n utilizarea pe scar larg a funciilor definite de utilizator. Scrierea programului principal (main) se concentreaz mai ales pe apelul acestor funcii. n cazul n care corpul de definiie al funciilor utilizator se afl dup corpul de definiie main, este necesar ca s declarm prototipul funciilor utilizate de main() pentru a informa corect compilatorul despre tipul variabilelor returnate de funcii. O alt modalitate este aceea de a defini funciile utilizator naintea funciei principale main(), caz n care nu mai sunt necesare prototipurile. Programul este modularizat cu ajutorul funciilor prin divizarea sa n nuclee funcionale. Acestea pot fi comparate cu nite mici piese de lego cu ajutorul crora se pot construi ulterior structuri (programe) foarte complexe. Pe scurt, modularizarea intern const n descompunerea sarcinii globale a unui program n funcii de prelucrare distincte. O funcie de uz general este o funcie care poate fi folosit ntro varietate de situaii i, probabil, de ctre mai muli utilizatori. Este de preferat ca aceste funcii de uz general s nu primeasc informaii prin intermediul unor variabile globale ci prin intermediul

193

parametrilor. Sporete astfel foarte mult flexibilitatea n folosirea acestor funcii. Modularizarea extern const n divizarea unui program foarte complex n mai multe subprograme. Astfel, un fiier surs mai mare se poate diviza n dou sau mai multe fiiere surs mai mici. Evident, aceste fiiere sunt strns legate ntre ele pentru a forma n final un tot unitar echivalent cu programul complex iniial (dinainte de divizare).

n figura de mai sus se prezint un ecran al Microsoft Visual C+ + din MSDN 6.0 Noiunea cea mai cuprinztoare este aceea de Workspace (spaiu de lucru) care cuprinde n esen o colecie 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 rndul su din mai multe fiiere, de acelai tip sau de tipuri diferite. Prezentarea exhaustiv a organizrii acestui mediu de dezvoltare a aplicaiilor C/C++ este un demers n afara prezentei lucrri. Ceea ce merit s subliniem este faptul c, n cadrul cel mai ntlnit, anume un workspace care include un singur project, acest proiect conine mai

194

ales fiiere surs i fiiere de tip header. Aceste fiiere se numesc module. Modulul principal este fiierul care conine funcia principal main(). Celelalte fiiere surs, dac exist, se numesc module secundare. De obicei, cu fiecare modul secundar se asociaz un fiier header propriu separat. Acest fiier header trebuie s conin toate directivele i declaraiile 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 conine un singur project, care va conine urmtoarele 4 fiiere: bd_main.c local.h
bd_bib.c local1.h bd_main.c (bd - baz de date) este modulul principal, cel care conine funcia main(). El are asociat fiierul header local.h. n mod asemntor, local1.h este fiierul header asociat cu modulul secundar bd_bib.c (bib - bibliotec) care conine toate definiiile

funciilor utilizator. Coninutul 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(); }}}

Fiierul local.h conine:


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

195

extern char menu(); extern void enter(),save(),load(); extern void display(),exit();

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;

196

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

Fiierul local1.h conine:


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

197

extern FILE *fp;

Se poate verifica cum fiecare modul n parte este compilabil fr erori, iar la link-editare nu se semnaleaz, de asemenea, erori.

Capitolul X INTRRI/IEIRI
10.1. Funcii de intrare i ieire - stdio.h
Limbajul C nu dispune de instruciuni de intrare/ieire. Aceste operaii se realizeaz prin intermediul unor funcii din biblioteca standard a limbajului C. Aceste funcii pot fi aplicate n mod eficient la o gam larg de aplicaii datorit multiplelor faciliti 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 fiierelor care s permit organizri de date, aa cum n alte limbaje exist fiiere cu organizare relativ sau indexat. n limbajul C toate fiierele sunt tratate ca o niruire de octei, neexistnd structuri de date specifice care s se aplice acestor fiiere. Programatorul poate s interpreteze datele dup cum dorete Prin urmare, prin scrierea/citirea datelor se scriu/citesc un numr de octei fr o interpretare specific. Funciile de intrare/ieire, tipurile i macrodefiniiile din "stdio.h" reprezint aproape o treime din bibliotec. n C, intrarea standard respectiv ieirea standard sunt n mod implicit reprezentate de terminalul de la care s-a lansat programul. Prin fiier nelegem o mulime ordonat de elemente pstrate pe diferite suporturi. Aceste elemente se numesc nregistrri. Suporturile cele mai des utilizate sunt cele magnetice (floppy sau harddiscuri). Ele se mai numesc suporturi reutilizabile deoarece zona utilizat pentru pstrarea nregistrrilor unui fiier poate fi ulterior reutilizat ulterior pentru pstrarea nregistrrilor unui alt fiier. n C un fiier reprezint o surs sau o destinaie de date, care poate fi asociat cu un disc sau cu alte periferice.

198

Biblioteca accept fiiere de tip text i binar, dei n anumite sisteme, de exemplu UNIX, acestea sunt identice. Un fiier de tip text este o succesiune de linii, fiecare linie avnd zero sau mai multe caractere terminate cu ' \n '. ntr-o alt reprezentare, anumite caractere pot fi convertite ntr-o succesiune de caractere, adic s nu existe o relaie unu la unu ntre caracterele scrise (citite) i aciunea perifericului. De exemplu, caracterul NL (new line), ' \n ', corespunde grupului CR (carriage return) i LF (line feed). n aceeai idee, se consider c datele introduse de la un terminal formeaz un fiier de intrare. nregistrarea se consider c este format de datele unui rnd tastate de la terminal (tastatur, keyboard), deci caracterul de rnd nou NL se consider ca fiind terminator de nregistrare. n mod analog, datele care se afieaz pe terminal (monitor, display) formeaz un fiier de ieire. i n acest caz nregistrarea este format din caracterele unui rnd. Ceea ce este important de subliniat este c fiierele text pot fi accesate la nivel de octet sau de caracter, ele putnd fi interpretate drept o colecie de caractere, motiv pentru care se i numesc fiiere text. Toate funciile de intrare/ ieire folosite pn acum se pot utiliza i pentru fiierele text. Un fiier de tip binar este o succesiune de octei neprelucrai care conin date interne, cu proprietatea c dac sunt scrise i citite pe acelai sistem, datele sunt egale. Aceste fiiere sunt organizate ca date binare, adic octeii nu sunt considerai ca fiind coduri de caractere. La fiierele binare nregistrarea se consider c este o colecie de date structurate numite articole. Structurile de date sunt pretabile pentru stocarea n astfel de fiiere Tratarea fiierelor se poate face la dou nivele, inferior i superior. Nivelul inferior de prelucrare a fiierelor ofer o tratare a fiierelor fr zone tampon (buffere), fcnd apel direct la sistemul de operare. Rezervarea de zone tampon este lsat pe seama utilizatorului. Fiierele de tip text se preteaz la o astfel de tratare. Nivelul superior de prelucrare a fiierelor se bazeaz pe utilizarea unor proceduri specializate n prelucrarea fiierelor care printre altele pot rezerva i gestiona automat zonele tampon necesare. Fiierele binare se pot manipula cu facilitate la acest nivel. Funciile

199

specializate de nivel superior au denumiri asemntoare cu cele de nivel inferior, doar prima liter a numelui este f. n practic operaiile de intrare/ieire (I/O) cu memoria extern (hard-disk sau floppy-disk) sunt mult mai lente dect cele cu memoria intern. Din aceast cauz, pentru a spori viteza de lucru, se ncearc s se reduc numrul de operaii de acces la disc. n acest scop se folosesc bufferele. Un buffer este o zon de memorie n care sistemul memoreaz o cantitate de informaie (numr de octei), n general mai mare dect cantitatea solicitat de o operaie de I/O. Dac un program efectueaz o operaie de citire a 2 octei dintr-un fiier, atunci sistemul citete ntr-un buffer ntreg sectorul de pe disc (512 octei) n care se gsesc i cei 2 octei solicitai, eventual chiar mai mult, n funcie de dimensiunea bufferului (zonei tampon). Dac n continuare se vor solicita nc 2 octei, acetia vor fi preluai din bufferul din memorie, fr a mai fi nevoie s mai accesm discul pe care se afl fiierul din care se face citirea. Operaiile de citire continu n acest mod pn la citirea tuturor octeilor din buffer, moment n care se va face o nou umplere a bufferului cu noi date prin citirea urmtorului sector de pe disc. Invers, dac un program efectueaz o operaie de scriere a unui numr de octei pe disc, acetia se vor nscrie de fapt secvenial n buffer i nu direct pe disc. Scrierea va continua astfel pn la umplerea bufferului, moment n care sistemul de operare efectueaz o operaie de scriere a unui secto de pe disc cu cei 512 octei din buffer (se golete bufferul prin scriere). n acest fel, reducnd numrul de operaii de acces la disc (pentru citire sau scriere) crete viteza de execuie a programelor i fiabilitatea dispozitivelor de I/O. Bufferele au o mrime implicit, dar ea poate fi modificat prin program. Dimensiunea trebuie aleas n funcie de aplicaie innd cont de faptul c prin mrirea bufferului crete viteza de execuie dar scade dimensiunea memoriei disponibile codului programului i invers, prin micorarea sa crete memoria cod disponibil dar scade viteza de lucru. Bufferul de tastatur are, spre exemplu, dimensiunea de 256 octei, din care 254 sunt pui la dispoziie. Orice fiier are o nregistrare care marcheaz sfritul de fiier. n cazul fiierelor de intrare ale cror date se introduc de la terminal, sfritul de fiier se genereaz n funcie 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.

200

Un fiier stocat pe suport magnetic se mai numete i fiier extern. Cnd se prelucreaz un astfel de fiier se creaz o imagine a acestuia n memoria intern (RAM) a calculatorului. Aceast imagine se mai numete i fiier intern. Un fiier intern este conectat la un fiier extern sau dispozitiv prin deschidere; conexiunea este ntrerupt prin nchidere. Deschiderea unui fiier ntoarce un pointer la un obiect de tip FILE, care conine toate datele necesare pentru controlul fiierului. Operaiile de deschidere i nchidere a fiierelor se poate realiza n C prin funcii specializate din biblioteca standard I/O a limbajului. Alte operaii care sunt executate frecvent n prelucrarea fiierelor sunt: Crearea unui fiier (acest fiier nu exist n format extern) Actualizarea unui fiier (deja existent) Adugarea de nregistrri unui fiier deja existent Consultarea unui fiier Poziionarea ntr-un fiier Redenumirea unui fiier tergerea unui fiier Ca i operaiile de deschidere i nchidere de fiiere, operaiile indicate mai sus pot fi realizate printr-un set de funcii aflate n biblioteca standard I/O a limbajului. Aceste funcii realizeaz aciuni similare sub diferite sisteme de operare, dar multe dintre ele pot depinde de implementare. n cele ce urmeaz se prezint funciile care au o utilizare comun pe diferite medii de programare i sunt cele mai frecvent utilizate.

10.2. Operaii cu fiiere


n acest subcapitol vom detalia principalele operaii efectuate asupra unor fiiere. n timpul lucrului cu fiierele, sistemul de operare pstreaz un indicator de fiier care indic poziia curent n fiier, poziie la care se va face urmtoarea operaie de scriere sau citire. De exemplu, la deschiderea unui fiier pentru citire indicatorul de fiier va indica la nceputul fiierului. Dac se va face o operaie de citire a 2 octei se vor citi octeii cu numrul de ordine 0 i 1 iar indicatorul va indica spre urmtorul octet, adic cel cu numrul de ordine 3.

201

Pentru o mai corect nelegere a acestor funcii le vom structura dup nivelul la care se utilizeaz: inferior sau superior. n momentul nceperii execuiei unui program, interfeele standard (cu ecranul, tastatura i porturile seriale i paralele) sunt deschise n mod text. Principalele funcii sunt grupate n tabelul de mai jos:
Descriere Deschidere Creare Citire Scriere nchidere Poziionare tergere Redenumire Nume funcie de nivel inferior _open _creat _read _write _close _lseek _unlink _rename Nume funcie de nivel superior fopen fcreate fread fwrite fclose fseek remove rename

n afara acestor funcii principale mai exist anumite funcii specializate, cum ar fi: - Funcii pentru prelucrarea pe caractere a unui fiier: putc (scriere caracter) i getc (citire caracter). - Funcii pentru Intrri/Ieiri cu format: fscanf i fprintf. - Funcii pentru Intrri/Ieiri de iruri de caractere: fgets i fputs. Pentru ca sistemul de operare s poat opera asupra fiierelor ca fluxuri (stream) de intrare/ieire trebuie s cunoasc anumite informaii despre ele. Acest lucru se realizeaz prin operaia de deschidere a fluxurilor (stream-urilor). Pointerul fiier n urma operaiei de deschidere se creaz n memorie o variabil de tip structur FILE care este o structur predefinit. n aceast variabil, care se numete bloc de control al fiierului, FCB (File Control Block) sistemul pstreaz informaii despre fiierul deschis, precum: Nume Dimensiune

202

Atribute fiier Descriptorul fiierului Un pointer-fiier este un pointer la informaiile care definesc diferitele aspecte ale unui fiier: nume, stare, poziie curent. Un pointer-fiier 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 abstracie de cazurile speciale de calculatoare tip VAX sau U3B, pe majoritatea implementrilor tipul FILE se definete prin urmtoarea 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 ctre suportul pentru exploatarea fiierelor n limbajul C. n urma deschiderii unui fiier, programul primete 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 operaiile care se fac pe acest stream se refer la fiierul 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 operaie de citire de la stream-ul stdin nseamn citire de la tastatur. Bufferul folosit are o dimensiune de 254 de caractere i bufferul se golete 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 ieire (ecranul). Orice operaie 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 fiierul stdout. FILE *stderr; care se refer la dispozitivul standard pentru afiarea 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.

203

FILE *stdaux; care se refer la primul port serial COM1. Este ne-bufferizat.

10.3. Nivelul inferior de prelucrare a fiierelor


La acest nivel operaiile de prelucrare a fiierelor se execut fr o gestiune automat a zonelor tampon, fcndu-se apel direct la sistemul de operare. Programatorul are n gestiune o zon declarat drept buffer i trebuie s in cont de faptul c aceast bufferizare este la nivel linie. Numele funciilor de nivel inferior, orientate pe text (transfer de octei) ncep de obicei cu _ (underline). Dac un fiier se deschide n modul text, atunci, n cazul citirii dintr-un fiier, secvena de octei CR-LF (0DH, 0AH) este translatat (nlocuit) cu un singur caracter LF, iar n cazul scrierii n fiier caracterul LF este expandat la secvena CR-LF. De asemenea, n cazul MS-DOS sau Windows CTRL/Z este interpretat n cazul citirii drept caracter de sfrit de fiier (EOF).

10.3.1. Deschiderea unui fiier


Orice fiier nainte de a fi prelucrat trebuie deschis, motiv pentru care operaia de deschidere a unui fiier este de mare importan. Deschiderea unui fiier existent se realizeaz prin intermediul funciei _open. La revenirea din ea se returneaz un aa numit descriptor de fiier. Acesta este un numr ntreg. El identific n continuare fiierul respectiv n toate operaiile realizate asupra lui. n forma cea mai simpl funcia _open se apeleaz printr-o expresie de atribuire de forma: df = _open(spf,mod) unde: df este un numr ntreg care reprezint descriptorul de fiier spf este specificatorul fiierului care se deschide mod definete modul de prelucrare a fiierului Specificatorul de fiier este fie un ir de caractere, fie un pointer spre un astfel de ir de caractere. Coninutul 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 fiierului care se opereaz. Fiierele deschise la acest nivel pot fi prelucrate n citire (consultare), scriere (adugare de nregistrri) sau citire/scriere (actualizare sau punere la zi).

204

Calea spre fiier trebuie s respecte conveniile sistemului de operare MS-DOS n general. n cea mai simpl form ea este un ir de caractere care definete numele fiierului, urmat de extensia fiierului. Aceasta presupune c fiierul se gsete n directorul curent. Dac fiierul nu se afl n fiierul curent, atunci numele este precedat de o construcie de forma:
litera:\nume_1\nume_2\\nume_k

unde: litera definete 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 fiierul BIO.C din directorul JOC de pe dscheta A se deschide n citire/scriere. n funcie de operaia dorit, mod poate avea valorile: 0 - pentru citire 1 - pentru scriere 2 - pentru citire/scriere Deschiderea unui fiier nu reuete dac unul dintre parametri este eronat. n acest caz funcia _open returneaz valoarea (-1). int _open( const char *filename, int oflag [, int pmode] ); este definiia general a funciei _open. Modul de acces mod se poate furniza n mod explicit printr-o variabil de tip ntreg (oflag) care poate avea valorile:
Variabila mod _O_RDONLY Modul de deschidere a fiierului Fiierul se deschide numai n citire (read-only) Nu se poate specifica mpreun cu _O_RDWR sau _O_WRONLY Fiierul se deschide numai n scriere (write-only) Nu se poate specifica mpreun cu _O_RDWR sau _O_RDONLY Fiierul se deschide n citire/scriere (read/write) Fiierul se deschide pentru adugarea de nregistrri la sfritul su.

_O_WRONLY _O_RDWR _O_APPEND

205

_O_CREAT _O_BINARY _O_TEXT

Creaz i deschide un nou fiier pentru scriere. Nu are nici un efect dac fiierul este deja existent. Fiierul se prelucreaz n mod binar Fiierul este de tip text, adic se prelucreaz pe caractere sau octei (implicit)

Menionm c n MSDN aceste variabile se mai numesc i oflag (open-flag) i sunt definite n fiierul header FCNTL.H. n cazul n care oflag este _O_CREAT, atunci este necesar specificarea constantelor opionale pmode, care se gsesc definite n SYS\STAT.H. Acestea sunt: _S_IREAD - este permis numai citirea fiierului _S_IWRITE - este permis i citirea (permite efectiv citirea/scrierea fiierului) _S_IREAD | _S_IWRITE - este permis i scrierea i citirea fiierului. Argumentul pmode este cerut numai cnd se specific _O_CREAT. Dac fiierul exist deja, pmode este ignorat. Altcumva, pmode specific setrile de permisiune asupra fierului care sunt activate cnd fiierul este nchis pentru prima oar. _open aplic masca curent de permisiune la fiier nainte de setarea accesului la fiier. Pentru a crea un fiier nou se va utiliza funcia _creat pentru a-l deschide. De fapt se deschide prin creare un fiier inexistent. Funcia este definit astfel: int _creat( const char *filename, int pmode ); n care parametrii au fost descrii mai sus. Protecia unui fiier este dependent de sistemul de operare. Spre exemplu, n UNIX protecia se definete prin 9 bii ataai oricrui fiier, grupai n 3 grupe de cte 3 bii. Fiecare bit controleaz o operaie de citire, scriere, execuie. Protecia operaiilor se exprim fa de proprietar, grup sau oricine altcineva. Numrul octal 0751 permite proprietarului toate cele 3 operaii indicate mai sus (7 = 1112), grupul la care aparine proprietarul poate citi i executa fiierul (5 = 1012) iar ali utilizatori pot numai executa fiierul (1 = 0012). Funcia _creat poate fi apelat i n cazul n care se deschide un fiier deja existent, caz n care se pierde coninutul vechi al fiierului respectiv i se creaz n locul lui unul nou cu acelai nume.

206

Fiecare din funciile _open sau _creat returneaz un specificator de fiier (handle) pentru fiierul deschis. Acest specificator este o valoare ntreag pozitiv. Implicit, stdin are specificatorul 0, stdout i stderr au specificatorii 1 respectiv 2 iar fiierele disc care sunt deschise primesc pe rnd valorile 3, 4,..etc. pn la numrul maxim admis de fiiere deschise. 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 fiier read-only sau modul de partajare a fiierului nu permite operaia specificat sau calea nu specific un nume de fiier ci de director. EEXIST (valoare 17) flagurile _O_CREAT i _O_EXCL sunt specificate, dar numele de fiier este al unui fiier deja existent. EINVAL (valoare 22) unul dintre argumentele oflag sau pmode sunt invalide. EMFILE (valoare 24) nu mai sunt disponibile specificatoare de fiier (prea multe fiiere deschise). ENOENT (valoare 2) fiierul sau calea nu au fost gsite. Variabila global errno pstreaz codurile de eroare folosite de funciile 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 funcie la nivel de sistem (la nivelul inferior). Deoarece errno pstreaz valoarea setat de ultimul apel, aceast valoare se poate modifica la fiecare apel de funcie 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 aplicaiile Windows pe 32 de bii sunt un subset al acestor valori UNIX. Valorile specificate mai sus sunt valabile pentru aplicaii Windows pe 32 de bii. La o eroare, errno nu este setat n mod necesar la aceeai valoare cu codul erorii de sistem. Numai pentru operaii de I/O se folosete _doserrno pentru a accesa codul erorilor sistemului de operare echivalent cu codurile semnalate de errno. Exemplu:

207

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> #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 );} if( fh2 == -1 ) perror( "Open failed on output file" ); else {printf( "Open succeeded on output file\n" ); _close( fh2 );}}

fh2=_open("OPEN.OUT",_O_WRONLY|_O_CREAT,_S_IREAD|_S_IWRITE);

Prin execuia acestui program se vor obine urmtoarele 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 fiier


Scrierea ntr-un fiier se realizeaz folosind funcia _write. Se presupune c fiierul respectiv a fost n prealabil deschis prin funciile _open sau _creat. Ea este asemntoare cu funcia _read, doar c se realizeaz transferul de date n sens invers i anume din memorie pe suportul fiierului. Funcia _write, ca i _read, se apeleaz printr-o atribuire de forma: nr = _read(df,zt,n) unde: nr este o variabil de tip ntreg creia i se atribuie numrul de octei scrii n fiier. df este descriptorul de fiier returnat de funcia _open la deschiderea sau _creat la crearea fiierului.

208

zt - este un pointer spre zona tampon definit de utilizator, zon din care se face scrierea. n este dimensiunea zonei tampon sau numrul de octei care se dorete s se scrie. Definiia funciei este: int _write( int handle, const void *buffer, unsigned int count ); Funcia _write scrie count octei din buffer n fiierul asociat cu descriptorul handle. Operaia de scriere ncepe la poziia curent a pointerului de fiier asociat cu fiierul dat. Dac fiierul este deschis cu indicatorul _O_APPEND, operaia de scriere ncepe la sfritul fiierului. Dup o operaie de scriere pointerul de fiier este incrementat cu numrul de bii scrii efectiv. Dac fiierul a fost deschis n mod text (implicit), atunci _write trateaz CTRL/Z drept un caracter ce indic sfritul logic al fiierului. Cnd se scrie ntr-un dispozitiv, _write trateaz CTRL/Z din buffer drept terminator al operaiei de ieire. n general trebuie ca la revenirea din funcia _write s avem nr=n, ceea ce semnific faptul c s-au scris pe disc exact numrul de bii din buffer. n caz contrar scrierea este eronat: aceasta semnific faptul c pe disc a rmas mai puin spaiu (n octei) dect numrul de octei ai bufferului. Dac valoarea returnat este -1, se semnalizeaz eecul operaiei de scriere. n acest caz variabila global errno poate avea una din valorile EBADF (care semnific un descriptor de fiier invalid sau c fiierul nu a fost deschis pentru scriere) sau ENOSPC (care semnific lipsa spaiului pe disc pentru operaia de scriere). Funcia _write poate fi utilizat pentru a scrie pe ieirile standard (display). Astfel, pentru a scrie pe ieirea standard identificat prin stdout se folosete descriptorul 1, iar pentru a scrie pe ieirea standard pentru erori, stderr, se folosete descriptorul de fiier 2. De asemenea, n acest caz nu este nevoie s apelm funcia _open sau _creat deoarece fiierele respective se deschid automat la lansarea programului. Exemplu:
/*Acest program deschide un fisier pentru scriere 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"; si

209

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) perror( "Write failed" ); else printf( "Wrote %u bytes to file\n", byteswritten ); _close( fh );}}

n urma execuiei programului, se va afia mesajul:


Wrote 36 bytes to file Press any key to continue

10.3.3. Citirea dintr-un fiier


Citirea dintr-un fiier deschis n prealabil cu funcia _open se realizeaz cu ajutorul funciei _read. Ea returneaz numrul efectiv al octeilor citii din fiier. Funcia _read se poate apela folosind o expresie de atribuire de forma: nr = _read(df,zt,n) cu definiia general: int _read( int handle, void *buffer, unsigned int count ); unde: nr este o variabil de tip ntreg creia i se atribuie numrul de octei citii din fiier. df este descriptorul de fiier returnat de funcia open la deschiderea sau creat la crearea fiierului. zt - este un pointer spre zona tampon definit de utilizator, zon n care se face citirea. n reprezint numrul de bii care se citesc Funcia _read citete maximum count octei n buffer din fiierul asociat cu descriptorul handle. Operaia de citire ncepe de pe poziia curent ndicat de pointerul de fiier asociat cu fiierul dat. Dup operaia de citire, pointerul de fiier indic spre urmtorul octet necitit din fiier. Dac fiierul a fost deschis n mod text, citirea se termin la ntlnirea caracterului CTRL/Z, care este interpretat drept indicator de sfrit de fiier. _read returneaz numrul de bii citii din fiier, care poate fi mai mic dect count dac sunt mai puini dect count octei rmai n fiier sau dac fiierul este deschis n mod text. n acest caz fiecare pereche CR-LF (carriage returnlinefeed) (CR-LF) este nlocuit cu

210

un singur caracter LF. Numai acest caracter LF se consider n valoarea returnat. nlocuirea nu afecteaz pointerul de fiier. Dac funcia ncearc s citeasc dup sfritul fiierului, se returneaz valoarea 0. Dac descriptorul de fiier (handle) este invalid sau dac fiierul nu este deschis pentru citire sau dac este blocat, funcia 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 citete un singur octet. De obicei, nu este eficient s se citeasc cte un octet dintr-un fiier, deoarece apelurile multiple ale funciei _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. Funcia _read citete maximum count bii n zona buffer din fiierul cu descriptorul handle. Operaia de citire ncepe de la poziia curent a pointerului de fiier asociat cu fiierul respectiv. Dup o operaie de citire, pointerul fiier indic spre urmtorul caracter (octet) necitit din fiier. Dac fiierul a fost deschis n mod text, _read se termin cnd se ntlnete indicatorul de fiier CTRL/Z. Funcia _read poate fi utilizat pentru a citi de la intrarea standard (tastatur). n acest caz descriptorul de fiier are valoarea 0. De asemenea, n acest caz nu este nevoie s apelm funcia _open deoarece fiierul 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> _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" ); /* Necesara numai pentru definirea

211

exit( 1 ); } /* Read in input: */ if((bytesread = _read(fh,buffer,nbytes)) <= 0) perror( "Problem reading file" ); else printf( "Read %u bytes from file\n", bytesread ); _close( fh );}

La execuia programului se va afia urmtorul mesaj:


Read 36 bytes from file Press any key to continue

10.3.4. nchiderea unui fiier


Dup terminarea prelucrrii unui fiier el trebuie nchis. Acest lucru se realizeaz automat dac programul se termin prin apelul funciei exit. Programatorul poate nchide un fiier folosind funcia _close. Se recomand ca programatorul s nchid orice fiier de ndat ce s-a terminat prelucrarea lui, deoarece numrul fiierelor ce pot fi deschise simultan este limitat ntre 15 i 25, n funcie de sistemul de operare. Menionm c fiierele corespunztoare intrrilor i ieirilor standard nu trebuie nchise de programator. Definiia funciei este:
int _close( int handle );

Funcia _close nchide fiierul asociat cu descriptorul handle. Funcia _close returneaz valoarea 0 la o nchidere reuit 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 funcie df este descriptorul de fiier (handle) al fiierului pe care dorim s-l nchidem.

10.3.5. Poziionarea ntr-un fiier


Operaiile de citire/scriere ntr-un fiier se execut secvenial, astfel nct: - fiecare apel al funciei _read citete nregistrarea din poziia urmtoare din fiier - fiecare apel al funciei _write scrie nregistrarea n poziia urmtoare din fiier. Acest mod de acces la fiier se numete secvenial i el este util cnd dorim s prelucrm fiecare nregistrare a fiierului. n practic

212

apar ns i situaii n care noi dorim s scriem i s citim nregistrri ntr-o ordine oarecare. n acest caz se spune c accesul la fiier este aleator. Pentru a realiza un acces aleator este nevoie s ne putem poziiona oriunde n fiierul respectiv O astfel de poziionare este posibil pe hard-uri i floppy-uri prin funcia _lseek. Definiia funciei este:
long _lseek( int handle, long offset, int origin );

Funcia _lseek mut pointerul de fiier asociat cu descriptorul handle (df) pe o nou locaie care este situat la offset octei de origin. Urmtoarea operaie de citire/scriere se va efectua de la noua locaie. Argumentul origin trebuie s fie una dintre urmtoarele constante, definite n STDIO.H: SEEK_SET nceputul fiierului (valoare 0) SEEK_CUR poziia curent a pointerului de fiier (valoare 1) SEEK_END sfritul fiierului (valoare implicit 2) Funcia _lseek returneaz valoarea 0 la poziionare corect i -1 la incident. Ea poate fi apelat prin:
v = _lseek(df, deplasament, origine)

unde: v este o variabil de tip ntreg creia i se atribuie valoarea returnat de ctre funcie (0 sau -1) df este descriptorul de fiier (handle) a crui valoare a fost definit la deschiderea sau crearea fiierului. deplasament este o variabil de tip long i conine numrul de octei peste care se va deplasa capul de citire/scriere al discului. origine are una din valorile 0 - deplasamentul se socotete de la nceputul fiierului; 1 - deplasamentul se socotete din poziia curent a capului de citire/scriere; 2 - deplasamentul se socotete de la sfritul fiierului. Menionm c prin apelul lui _lseek nu se realizeaz nici un fel de transfer de informaie ci numai poziionarea n fiier. Operaia urmtoare realizat prin apelul funciei _read sau _write se va realiza din aceast poziie. Spre exemplu, apelul:
v = _lseek(df, 0l, 2)

permite s se fac poziionarea la sfritul fiierului. n continuare se pot aduga articole folosind funcia _write. Poziionarea la nceputul fiierului se face prin apelul:
v = _lseek(df, 0l, 0)

Exemplu:

213

#include <io.h> #include <fcntl.h> #include <stdlib.h> #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 execuiei programului se va afia:


Pozitia pentru inceputul fisierului = 0 Pozitia curenta = 10 Pozitia ultima este = 36 Press any key to continue

10.3.6 tergerea unui fiier


Un fiier poate fi ters apelnd funcia _unlink astfel: v = _unlink(spf) unde v este o variabil de tip ntreg creia i se atribuie valoarea 0 pentru tergere reuit i (-1) pentru tergere nereuit. spf este specificatorul de fiier folosit la deschidere a fiierului. Definiia funciei este:

214

int _unlink( const char *filename ); Funcia _unlink terge de pe disc fiierul specificat prin filename. Exemplu:
/* 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 execuiei programului se afieaz:


S-a sters 'WRITE.O' Press any key to continue

10.3.7. Exemple de utilizare a funciilor de intrare/ieire de nivel inferior


1. S se scrie un program care copiaz intrarea standard la ieirea standard. Aceast problem se poate rezolva uor prin folosirea funciilor getchar i putchar. Acum o vom rezolva folosind funciile _read i _write.
# include <stdio.h> # include <io.h> void main() /* copiaza standard */ { char c[1]; while (_read(0,c,1)>0) _write(1,c,1);} intrarea standard la iesirea

Menionm c cel de-al doilea parametru al funciei _read sau _write trebuie s fie pointer spre caractere. Lucrul la nivelul inferior nu este chiar att de simplu pe ct pare. Vom ilustra n continuare responsabilitatea pe care o are programatorul n gestionarea zonelor tampon. S considerm exemplul anterior n care zona tampon o mrim 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);}

215

Citirea nu se va opri dup 3 caractere, ci funcia _read va continua s funcioneze pn la tastarea ENTER (CR+LF). Imediat funcia _read va tipri grupele de 3 caractere introduse, inclusiv grupul final CR+LF. Zona tampon definit este supranscris de fiecare dat cnd se introduc noi caractere. Dac de la tastatur vom introduce 123456<CR><LF> atunci se va tipri primul grup (prima nscriere a zonei tampon) 123, apoi a doua grup 456 i grupul <CR> i <LF> va supranscrie primele dou caractere ale bufferului, anume codurile ASCII ale lui 4 i 5 i se va tipri <CR><LF>6. 123456 123456 6 Primul grup 123456 este scris prin ecou de la tastatur, iar urmtoru se nscrie de ctre program. Dac n continuare vom introduce 1<ENTER> atunci se va tipri 1 urmat de dou rnduri noi deoarece fiecare CR sau LF sunt expandate de stdout n perechi <CR><LF>. Dac mrim la 5 dimensiunea bufferului i de la tastatur introducem 12<ENTER>, atunci se va tipri 12 12 deoarece cel de-al 5-lea octet al bufferului nu a fost alocat prin citire, avnd o valoare nedefinit. Problemele de mai sus legate de gestiunea bufferului n/din care se face citirea/scrierea pot fi depite cu o modificare simpl, prezentat mai jos. Prin scriere nu se vor trimite spre stdout dect numrul de caractere citit de la stdin.
# include <stdio.h> # include <io.h> # define LZT 10 // lungime zona tampon void main() /* copiaza intrarea standard standard */ { char zt[LZT]; int n; while ((n=_read(0,zt,LZT))>0) _write(1,zt,n);}

la

iesirea

216

Programatorul trebuie s in cont ns i de alte amnunte cum ar fi dimensiunea implicit a bufferului stdin, care este de 254 de caractere. 2. S se scrie un program care citete un ir de numere flotante de la intrarea standard i creaz 2 fiiere fis1.dat i fis2.dat, primul coninnd numerele de ordin impar citite de la intrarea standard (primul, al 3-lea, al 5-lea, etc.) iar cel de-al doilea pe cele de ordin par citite de la aceeai intrare. Apoi s se listeze, la ieirea standard, cele dou fiiere n ordinea fis1.dat, fis2.dat cte un numr pe un rnd n formatul numr de ordine:numr Vom scrie programul folosindu-ne de funcii definite de utilizator care s fac apel la funciile 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);

217

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) { 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 fiier fis.dat. Programul va diferi fa de cel anterior prin faptul c nregistrrile se stocheaz ntr-un singur fiier, deci funcia de listare se va modifica pentru citirea din 2 n 2 a nregistrrilor. Dup fiecare citire din fiier, se va face un salt cu o nregistrare pentru a poziiona capul de citire/scriere peste nregistrarea urmtoare.
# # # # # include include include include include <stdio.h> <io.h> <sys/types.h> <sys/stat.h> <fcntl.h>

218

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

219

// 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);} 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 atenia asupra modului n care lucreaz funciile de intrare/ieire pentru stdin i stdout fa de cele pentru disc. Dac intrrile i ieirile pentru perifericele standard le putem executa n formatul dorit cu ajutorul funciilor specializate scanf i printf, pentru lucrul cu discul variabila float este tratat sub forma unui grup de 4 octei care se scriu sau se citesc pe disc aa cum este reprezentarea lor intern. Exist funcii 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 fiiere ale funciilor printf() i scanf() sunt fprintf() i fscanf(). Echipamentele periferice pot fi considerate fiiere externe i deci funciile specializate pentru I/O cu fiiere pot fi folosite i pentru operaii de I/O cu echipamentele periferice. Funciile printf i scanf sunt proiectate pentru a lucra implicit cu fiierele stdout respectiv stdin, deci cu monitorul i tastatura.

10.4. Nivelul superior de prelucrare a fiierelor


Nivelul superior de prelucrare a fiierelor se refer la aa numitul format binar de reprezentare a informaiei n fiiere care la rndul su face apel la informaia structurat. Bufferul este alocat automat i gestionat de funcii C specializate.

10.4.1. Funcia fopen()


Funcia fopen se apeleaz printr-o expresie de atribuire de forma:

220

pf = fopen(spf,mod) unde: pf - este un pointer spre tipul FILE spf este specificatorul fiierului care se deschide mod este un ir de caractere care definete modul n care se deschide fiierul. Forma general de declarare a funciei fopen() este:
FILE *fopen(char *filename, char *mode);

Funcia deschide fiierul al crui nume este specificat prin "filename" (de obicei un fiier disc) i ntoarce un pointer la FILE pentru operaie reuit i NULL pentru operaie nereuit. Varibilele permise pentru modul "mode" sunt:
a a+ r r+ w w+ b t c n _O_WRONLY | _O_APPEND _O_CREAT | _O_APPEND) (usual _O_WRONLY |

_O_RDWR | _O_APPEND (usual _O_RDWR | _O_APPEND | _O_CREAT ) _O_RDONLY _O_RDWR _O_WRONLY(usual _O_TRUNC) _O_WRONLY | _O_CREAT |

_O_RDWR (usual _O_RDWR | _O_CREAT | _O_TRUNC) _O_BINARY _O_TEXT Nimic Nimic

Modul "a" nu terge markerul de sfrit d fiier EOF nainte de a aduga la sfritul fiierului. Dup ce s-a fcut o adugare, comanda MS-DOS TYPE tiprete datele pn la markerul original EOF i nu pn la ultima dat adugat. Modul "a+" terge identificatorul de sfrit de fiier EOF nainte de adugarea de nregistrri la sfritul fiierului. Dup adugare comanda MS-DOS TYPE va tipri toate datele coninute n fiier. Modul "a+" este cerut pentru adugarea la sfritul unui fiier care are marker terminator CTRL/Z = EOF.

221

Dac modul "mode" include "b" dup litera iniial, ca n "rb" sau "w+b" se indic un fiier binar. Numele fiierului conine cel mult FILENAME_MAX caractere. La un moment dat pot fi deschise cel mult FOPEN_MAX fiiere. Menionm c stdin, stdout i stderr sunt pointeri spre tipul FILE i permit ca funciile de nivel superior de prelucrare a fiierelor s poat trata intrarea standard, ieirea standard i ieirea standard pentru erori, la fel ca i restul fiierelor. Singura deosebire const n faptul c n acest caz programatorul nu trebuie s deschid sau s nchid fiierele 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 fiier se utilizeaz secvena:


if ((fp = fopen("test", "w")) == NULL) { puts ("Cannot open file\n"); exit(1); }

Dac pentru operaia de citire se ncearc deschiderea unui fiier inexistent, fopen() va returna o eroare.

10.4.2. Funcia fclose()


Forma general de declarare a funciei fclose() este:
int fclose(FILE *fp);

unde "fp" este pointerul la fiier returnat dup apelul funciei fopen(). Funcia fclose() se utilizeaz pentru a nchide un fiier deschis cu fopen(). Funcia fclose() scrie mai nti n fiier datele rmase n fiierul buffer apoi nchide fiierul. fclose() ntoarce zero (0) pentru nchidere reuit i EOF (-1) dac apare o eroare. La execuia funciei exit se nchid automat toate fiierele deschise. Cu toate acestea, se recomand ca programatorul s nchid un fiier de ndat ce s-a terminat prelucrarea lui, altfel se poate ajunge n situaia de a se depi numrul limit admis pentru fiierele 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;

222

/* 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 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 execuiei programului se obine:


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. Funciile rename() i remove()


Funcia rename() schimb numele vechi al fiierului, "oldname", cu numele nou, "newname". ntoarce o valoare diferit de zero dac incercarea nu reuseste.
int rename (char *oldname, char *newname);

Funcia remove()
int remove(char *filename);

Funcia remove() elimin fiierul cu numele specificat, astfel nct o incercare ulterioar de deschidere a fiierului va eua. ntoarce o valoare diferit de zero dac ncercarea reuete.

10.4.4. Funcii de tratare a erorilor


a) Funcia ferror() Aceasta funcie determin dac n urma unei operaii cu fiiere sa produs sau nu o eroare. Forma general de declarare este:
int ferror(FILE *fp)

unde "fp" este un pointer la fiier.

223

Funcia 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) Funcia feof()
int feof(FILE *fp)

Funcia feof() ntoarce o valoare diferit de zero dac indicatorul de sfrit de fiier este valid i o valoare zero dac indicatorul de sfrit de fiier nu este valid. c) Funcia perror()
void perror(const char *s)

Funcia 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. Funcii cu acces direct


a) Funcia fread() Permite citirea unui bloc de date. Forma general de declarare:
int fread(void *buffer,int num_bytes,int count,FILE *fp)

Funcia fread() citete din fiierul specificat prin "fp" cel mult "count" obiecte, fiecare obiect avnd lungimea egal cu "num_bytes" i i trimite n zona de memorie indirectat prin "buffer" . *fp este un pointer fiier la fiierul deschis anterior cu fopen(). Funcia ntoarce numrul de obiecte citite, acesta putnd fi mai mic dect cele cerute. Pentru a determina starea funciei se pot utiliza funciile feof(), ferror(). b) Funcia fwrite() Permite scrierea unui bloc de date. Forma general de declarare:
int fwrite(void *buffer,int num_bytes,int count, FILE *fp)

Funcia fwrite() scrie din zona (tabloul) "buffer" n fiierul indirectat prin "fp", "count" obiect de lungime "nr_bytes". Funcia ntoarce numrul de obiecte scrise, care, n caz de eroare este mai mic dect "count". Exemplu: Programul urmtor scrise un numr 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; }

224

fwrite (&f, sizeof (float), 1, fp); fclose (fp); }

Aa cum se vede din acest program, "buffer" poate fi o simpl variabil. Exemplu: Programul urmtor copiaz un tablou de numere reale "balance", n fiierul "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;} . . . . . . . . . . . . . . . . . fwrite (balance, sizeof (balance), 1, fp); . . . . . . . . . . . . . . . . . fclose (fp); }

Exemplu: Programul urmtor deschide fiierul FREAD.OUT i scrie n el 25 de caractere i apoi l redeschide i citete din nou caracterele din fiier dup care afieaz numrul caracterelor citite i coninutul.
#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 execuie programului se obine:


S-au scris 25 caractere

225

Nr. caracterelor citite = 25 Continutul bufferului = zyxwvutsrqponmlkjihgfedcb Press any key to continue

10.4.6. Funcii pentru poziionare


a) Funcia fseek() Determin poziionarea fiierului la citire sau scriere, ncepnd cu poziia selectat. Forma funciei:
int fseek(FILE *fp, long offset, int origin)

unde "fp" este un pointer-fiier returnat prin apelul funciei fopen(), "offset" este deplasamentul (numr octei) noii poziii fa de "origin", iar "origin" este una din urmtoarele macrodefiniii: SEEK_SET - nceput de fiier; SEEK_CUR - poziie curent; SEEK_END - sfrit de fiier. Funcia returneaz 0 dac se execut cu succes i o valoare nenul n caz de eroare. Dac nu s-a efectuat nici o operaie de I/O de la deschiderea fiierului n mod APPEND (adugare), atunci pointerul indic nceputul fiierului. Nu se recomanda utilizarea funciei fseek() pentru fiiere text; se sugereaz utilizarea acesteia numai pentru fiiere binare. Translaiile CR-LF efectuate n mod text pot cauza funcionarea defectoas a funciei fseek. Funcia fopen i toate celelalte funcii vor cuta s nlture caracterul CTRL/Z terminator de fiier (EOF). Singurele operaii garantate s funcioneze corect cnd se utilizeaz fseek asupra fiierelor deschise n mod text este poziionarea cu offset 0 relativ la orice poziie din fiier i poziionarea fa de nceputul fiierului cu un offset returnat de funcia ftell(). Funcia ftell() este definit astfel:
long ftell( FILE *stream );

Funcia returneaz valoarea curent a pointerului fiier. Poziia este exprimat prin offsetul fa de nceputul fiierului. n cazul fiierelor deschise n mod text, acest offset nu reflect ntotdeauna exact numrul de octei datorit translaiei CR-LF. Este preferat folosirea simultan a funciilor fseek i ftell pentru a opera asupra fiierelor text, dar se recomand folosirea lor n special asupra fiierelor binare. Exemplu: Pentru a citi cel de-al 235 byte din fiierul numit "test" se poate folosi urmtorul program:

226

func1() /* se declara funcia 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 */

Observaie: 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]; 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 execuie programului se obine:


Pointerul fisier este plasat la mijlocul primei linii. Acesta este fisierul 'fseek.out'. Press any key to continue

10.4.7. Ieiri cu format


Funciile de tip printf() asigur conversiile de ieire cu format. a) Funcia fprintf() Forma acestei funcii este:
int fprintf(FILE *fp, "format", lista_argumente)

Funcia fprintf() realizeaz conversia i scrierea la ieire n fiierul indirectat cu "fp" sub controlul formatului, "format". Valoarea ntoars de funcie este numrul de caractere scrise, sau orice valoare negativ, dac apare o eroare.

227

irul "format" conine dou tipuri de obiecte: caractere obinuite care sunt copiate n fiierul de ieire i descriptori de conversie, fiecare determinnd conversia i tiprirea 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 stnga a argumentului convertit n cmpul de reprezentare; "+" - precizeaz c numrul 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 cmpului; "#" - indic o form de ieire alternativ : pentru "0", prima cifra va fi zero; pentru "x" sau "X", la nceputul fiecrui numr nenul se va scrie "0x" sau "0X"; pentru "e", "E", "g", "G", "f" ieirea va avea ntotdeauna un punct zecimal; pentru "g" i "G" nu se vor elimina zerourile de la sfrit. 2) Un numr care indic lungimea minim a cmpului de reprezentare. Argumentul convertit va fi tiprit ntr-un cmp cu o lungime cel puin egal cu cea specificat, dac va fi nevoie i mai mare. Dac argumentul specificat are mai puine caractere, atunci cmpul va fi umplut la stnga sau la dreapta, funcie 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 cmpului de precizie. 4) Un numr, precizia, care indic numrul maxim de caracetre care se vor tipri dup virgul pentru "e", "E", sau "f", sau numrul de cifre semnificative pentru conversiile "g" sau "G", sau numrul maxim de caractere ce se vor tipri dintr-un ir. Lungimea, sau precizia, sau amndoua se pot specifica i prin "*". De exemplu: %10.4f - va afia un numr de cel puin 10 caractere cu 4 caractere dup virgul; %5.7s - va afia un ir de cel puin 5 caractere dar nu mai lung de 7 caractere; %-10.2f - va alinia la stnga un numr real cu 2 zecimale ntr-un cmp de reprezentare de cel puin 10 caractere. Descriptorii de conversie utilizai de C sunt:

228

%c - un singur caracter. %d, %i - notaie zecimala cu semn. %x, %X - notaie hexazecimal fr semn (fr 0x sau 0X). %u - notaie zecimal fr semn. %s - caracterele din ir sunt tiprite pn se ntlnete '\0' sau ct timp numrul de caractere tiprit precizia. %f - notaie zecimal de forma [-]mmm.ddd, unde numrul d-urilor este indicat de precizie; precizia implicit este 6, iar o precizie egal cu zero elimina punctul zecimal. %e, %E - notaie zecimal de forma: [-]m.dddddde+/-xx sau [-]m.ddddddE+/-XX unde numrul 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 dect -4, sau precizie, n caz contrar se utilizeaz %f. %p - afiseaza un pointer. %o - notaie octala fr semn (fr 0 la nceput). %% - nu se face conversie, se tiprete "%". %n - nu se realizeaz conversie de argument; numrul de caractere scrise pn n acel moment este scris n argument. Exist doi modificatori de format care permit funciei fprintf() s afieze ntregii long i short. Aceti modificatori se pot aplic caracterelor de conversie d, i, o, u i x, precedndu-i pe acetia (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 funciei fprintf() s afieze short int. Atunci %hu va preciza c data este de tip short unsigned int.] b) Funcia printf() Forma funciei :
int printf("format", lista-argumente)

Funcia printf() este echivalent cu :


fprintf(stdout, "format", lista_argumente)

Exemplu:
printf() ("%-5.2f", 123.456) ("%5.2f", 3.4565) ("%10s", "hello") ieire 123.45 3.45 hello

229

("%-10s", "hello") (%5.7s", "123456789")

hello 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"; 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. Intrri cu format


Funciile de tip scanf() realizeaz conversiile de intrare cu format a) Funcia fscanf() Forma acestei funcii este:
int fscanf(FILE *fp, "format", lista_argumente)

Funcia fscanf() citete din fiierul indirectat prin "fp" sub controlul formatului "format" i atribuie valorile citite argumentelor urmtoare, fiecare argument trebuind s fie un pointer. Funcia ntoarce EOF dac se detecteaz sfritul de fiier sau apare o alt eroare nainte de orice conversie. n caz contrar, funcia ntoarce numrul de elemente care au primit valori. irul "format" poate conine: - specificatori de conversie, constnd dintr-un caracter %, un caracter opional de suprimare a atribuirii; un numr opional care indic lungimea cmpului, un caracter opional h, l sau L, care indic lungimea argumentului i un caracter de conversie; - spaii sau caractere HT sau VT care se ignor; - caractere obinuite (diferite de %) care indic urmtorul caracter diferit de caracterele albe cu care ncepe fiierul de intrare.

230

Un cmp de intrare se definete ca un ir de caractere diferite de cele albe i se ntinde pn la urmtorul caracter alb (spaiu, tab-uri, CR, LF, VT, FF). Rezultatul conversiei unui cmp de intrare este plasat n variabil indicat de argumentul corespunztor din lista de argumente. Dac se indic suprimarea atributului prin "*" ca n %*s, cmpul de intrare este ignorat, fr a se face nici o atribuire. Descriptorii de conversie utilizai n C pentru citire sunt: %c - citete un singur caracter; caracterele urmtoare sunt plasate n tablourile indicate, respectndu-se numrul de caractere indicat de lungimea cmpului; implicit este 1. Nu se adaug '\0'. %d - citete un numr ntreg zecimal. %u - citete un numr ntreg zecimal fr semn. %i - citete un numr ntreg (intregul poate fi octal, cu 0 la nceput, sau hexazecimal, cu 0x sau 0X la nceput). %o - ntreg octal (cu sau fr zero la nceput). %x - ntreg hexazecimal (cu sau fr 0x sau 0X la nceput). %s - ir de caractere diferite de caracterele albe, indicnd 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 - citete valoarea pointerului. %n - se scrie n argument numerele de caractere citite pn n acel moment. Nu se citete nimic din intrare. %h - citete un ntreg scurt. Un caracter obinuit n irul "format" determin ca funcia 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 gsete 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) Funcia scanf() Forma funciei:
int scanf("format", lista-argumente)

Funcia scanf() este echivalenta cu:

231

fscanf(stdin, "format", lista-argumente)

Exemple:
scanf ("%d", &count); /* se citete un ntreg n variabil count */ scanf ("%s", address); /* se citete un ir de caractere n vectorul address */ scanf ("%d %d", &r, &c);/* se citesc doua numere separate prin spaiu, tab sau linie noua */

Un * plasat ntre % i caracterul de conversie, va suspenda atribuirea datei citite. Astfel, instruciunea :
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. Instruciunea :
scanf("%20s", sir);

citete nu mai mult de 20 caractere n variabil ir. Dac se introduce un ir de mai mult de 20 caractere, vor fi reinute numai primele 20, iar restul se pierd. Pentru caracterele rmase se poate apela din nou funcia scanf() sub forma :
scanf("%s", sir);

care va plasa restul caracterelor tot n "ir". Dac de la tastatura se introduce 10#20, instruciunea :
scanf("%s#%s", &x, &y);

va plasa 10 n x i 20 n y. Instruciunea :
scanf("%s ", name);

nu se ncheie dect dac dup ultimul caracter se introduce un spaiu. Exemplu de utilizare a funciilor 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' );

232

/* 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. Funcii de citire i scriere a caracterelor


a) Funcia fgetc()
int fgetc(FILE *fp)

Funcia fgetc() ntoarce urmtorul caracter al fiierului indirectat cu "fp", caracter de tip unsigned char (convertit la int) sau EOF dac s-a detectat sfritul de fiier sau a aprut o eroare. Exemplu de utilizare a funciei 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 "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 );}

in

b) Funcia getc()
int getc (FILE *fp)

233

Aceast funcie este identic cu fgetc() cu deosebirea c este o macrodefiniie, putnd s evalueze fiierul mai mult dect o dat. Observaie: "fp" este un pointer-fiier returnat de funcia fopen(). Exemplu: Pentru a citi un fiier text pn la ntlnirea indicatorului de sfrit de fiier se scrie:
ch = getch (fp); while (ch != EOF) { ch = getc (fp); }

c) Funcia getchar()
int getchar(void)

Funcia getchar() este echivalent cu getc (stdin) . Dezavantajul funciei getchar() este c aceast poate pstra n bufferul de intrare nu unul, ci mai multe caractere, primul caracter fiind preluat dup apasarea tastei CR. d) Funciile getche() i getch()
int int getche(void) getch(void)

Funciile introduc un caracter de la tastatur. Funciile asteapt pn se apas o tast i apoi ntorc valoarea acesteia. Funcia getche() preia un caracter cu "ecou" iar getch() preia caracterul fr ecou. e) Funcia gets()
char *gets(char *s)

Funcia gets() citete 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'. Funcia ntoarce vectorul s sau EOF, n caz de eroare. f) Funcia fgets()
char *fgets(char *s, int n, FILE *fp)

Funcia fgets() citete n tabloul s cel mult n-1 caractere, oprindu-se dac a detectat NL (New Line) care este inclus n tablou, nlocuit prin'\0'. Funcia ntoarce tabloul s, sau NULL, dac apare o eroare sau sfrit de fiier. Exemplu de folosire a funciei 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)

234

printf( "fgets error\n" ); else printf( "%s", line); fclose( stream ); }}

a') Funcia fputc()


int fputc(int ch, FILE *fp)

Funcia fputc() scrie caracterul "ch" convertit la unsigned char, n fiierul indirectat prin "fp". ntoarce valoarea caracterului scris, sau EOF, dac apare vreo eroare. Exemplu de utilizare a funcie 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. */ p = strptr1; while((*p != '\0') && fputc(*(p++), stdout)!=EOF); /* Identic cu _fputchar. (Aceasta functie nu compatibila ANSI */ p = strptr2; while((*p != '\0') && _fputchar(*(p++))!=EOF);}

este

n general, funciile care ncep cu _ (subliniere) dar i cu f (de la file) sunt destinate lucrului cu interfeele standard stdin i stdout. b') Funcia putc()
int putc(int ch, FILE *fp)

Funcia putc() este echivalenta cu fputc() cu excepia c este o macrodefinitie, putnd evalua fiierul mai mult dect o dat. c') Funcia putchar()
int putchar(int ch)

Funcia putchar(ch) este echivalenta cu putc (ch, stdout). d') Funcia de la punctul d) nu are un corespondent pentru ieire. e') Funcia puts()
int puts(const char *s)

Funcia puts() scrie irul "s" i NL n "stdout". ntoarce EOF, dac apare o eroare, sau o valoare nenegativ n caz contrar. f') Funcia fputs()
int fputs(const char *s,FILE *fp)

Funcia fputs() scrie irul "s" (care nu este neaprat necesar s conin '\n') n fiierul "fp". ntoarce EOF, n caz de eroare, o valoare nenegativ, n caz contrar. Spre exemplu,

235

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

Funcia ungetc()
int ungetc(int ch,FILE *fp)

Funcia ungetc() pune argumentul "ch" inpoi n fiier, de unde va fi preluat la urmtoarea citire. Se poate pune inapoi n fiier doar un singur caracter. Marcajul EOF nu poate fi pus napoi. Funcia ntoarce caracterul ce trebuie pus, sau EOF, n caz de eroare. Funciile getw() i putw() Aceste funcii se folosesc pentru a citi, respectiv a scrie ntregi dintr-un sau ntr-un fiier disc. Aceste funcii lucreaz exact c funciile getc() i putc(). Exemplu de utilizare a functiilor fprintf() i fscanf() : 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);

236

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

Capitolul XI UTILIZAREA ECRANULUI N MOD TEXT


Biblioteca standard a limbajului C i C++ conine funcii pentru gestiunea ecranului. Acesta poate fi gestionat n dou moduri: n mod text sau n mod grafic. Prezentm funciile standard mai importante utilizate la gestiunea ecranului n mod text. Toate funciile standard de gestiune a ecranului n mod text au prototipurile n fiierul antet <conio.h>. Modul text presupune c ecranul este compus dintr-un numr de linii i un numr 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. Poziia pe ecran a unui caracter se definete printr-un sistem de dou coordonate ntregi (x,y) unde: x este numrul coloanei n care este situat caracterul y este numrul liniei n care este situat caracterul

237

Colul din stnga sus al ecranului are coordonatele (1,1), iar colul din dreapta jos are coordonatele (80,25). n mod implicit, funciile de gestiune a ecranului n mod text au acces la tot ecranul. Accesul poate fi ns limitat la o parte din ecran utiliznd aa 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 urmtoarele: coordonatele x i y; culoarea caracterului; culoarea fondului pe care este afiat caracterul; clipirea caracterului.

11.1. Setarea ecranului n mod text


Se realizeaz cu ajutorul funciei textmode care are urmtorul prototip:
void textmode (int modtext)

unde modtext poate fi exprimat simbolic sau numeric n modul urmtor:


Modul text Caractere albe pe fond negru; 40 de coloane Color 40 de coloane Caractere albe pe fond negru; 80 de coloane Color 80 de coloane Monocrome 80 de coloane Color cu 50 de linii pentru placa VGA Modul precedent Constant simbolic BW40 C40 BW80 C80 MONO C4350 LASTMODE Valoare 0 1 2 3 7 64 -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

238

ori ns se dorete partajarea ecranului n zone care s poat fi gestionate n mod independent. Acest lucru poate fi realizat cu ajutorul ferestrelor. O fereastr se definete cu ajutorul funciei window care are urmtorul prototip:
void window (int x1, int y1, int x2, int y2);

unde: (x1,y1) reprezint coordonatele colului stnga sus ; (x2,y2) reprezint coordonatele colului dreapta jos. La un moment dat este activ o singur fereastr i anume acea definit la ultimul apel al funciei window. Dac parametri de la apelul funciei window sunt eronai, aceasta nu are nici un efect.

11.3. tergerea unei ferestre


O fereastr activ se terge utiliznd funcia clrscr care are urmtorul prototip: void clrscr(void) Dup apelul acestei funcii, fereastra activ (sau tot ecranul dac nu s-a definit n prealabil o fereastr cu funcia window) devine vid. Fondul ei are culoarea definit prin culoarea de fond curent. Funcia clrscr poziioneaz cursorul pe caracterul din stnga sus al ferestrei active, adic n poziia de coordonate (1,1).

11.4. Deplasarea cursorului


Cursorul poate fi plasat pe un caracter al ferestrei active folosind funcia gotoxy ce are urmtorul prototip:
void gotoxy (int x, int y);

unde (x, y) reprezint coordonatele caracterului pe care se plaseaz cursorul. Dac la apelul funciei coordonatele sunt definite n afara ferestrei active, funcia este ignorat. Poziia cursorului din fereastra activ poate fi determinat cu ajutorul funciilor wherex i wherey care returneaz numrul coloanei, respectiv liniei, din fereastra activ pe care se afl cursorul, i care au urmtoarele prototipuri:
int wherex(void); int wherey(void);

n cazul n care se dorete ascunderea cursorului (facerea invizibil a cursorului) se utilizeaz o secven ce utilizeaz funcia geninterrupt:

239

{ _AH=1; _CH=0x20; geninterrupt(0x10); } Cursorul poate fi rafiaat utiliznd urmtoarea secven: { _AH=1; _CH=6; _CL=7; geninterrupt(0x10);}

11.5. Setarea culorilor


Culoarea fondului se seteaz textbackground ce are urmtorul prototip:
void textbackground(int culoare);

cu

ajutorul

funciei

unde culoare este un ntreg n intervalul [0, 7] i are semnificaia din tabelul de mai sus. Pot fi setate ambele culori precum i clipirea caracterului folosind funcia textattr ce are urmtorul prototip:
void textattr (int atribut)

unde atribut se definete cu ajutorul relaiei:

atribut=16*culoare_fond+culoare_caracter+clipire;
Culoarea caracterelor se seteaz cu ajutorul funciei textcolor ce are urmtorul prototip:
void textcolor(int culoare);

unde culoare este un ntreg n intervalul [0,15] a crui semnificaie este explicat de tabelul urmtor:
Culoare Negru Albastru Verde Turcoaz Rou Purpuriu Maro Gri deschis Gri nchis Albastru deschis Verde deschis Turcoaz deschis Rou deschis Purpuriu deschis Constant simbolic BLACK BLUE GREEN CYAN RED MANGETA BROWN LIGHTGRAY DARKGRAY LIGHTBLUE LIGHTGREEN LIGHTCYAN LIGHTRED LIGHTMANGETA Valoare 0 1 2 3 4 5 6 7 8 9 10 11 12 13

240

Galben Alb Clipire

YELLOW WHITE BLINK

14 15 128

11.6. Funcii pentru gestiunea textelor


Pentru afiarea caracterelor se pot folosi funciile: int putch (int c); afieaz un singur caracter; int cputs (const char *str); afieaz un ir de caractere n mod similar funciei puts; int cprintf (const char *format); afieaz date sub controlul formatelor n mod similar funciei printf. void clreol (void); - terge sfritul liniei ncepnd cu poziia cursorului; void delline (void); - terge toat linia pe care este poziionat cursorul; int gettext (int left, int top, int right, int bottom, void *destination); - copiaz textul cuprins n dreptunghiul definit de coordonatele (left, top) stnga sus i (right, bottom) dreapta jos la adresa de memorie indicat de pointerul destination; int puttext( int left, int top, int right, int bottom, void *source ); - citete textul cuprins n dreptunghiul definit de coordonatele (left, top) stnga 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) stnga sus i (right, bottom) dreapta jos n dreptunghiul cu coordonatele colului din stnga sus (destleft, desttop); void insline (void); - insereaz o linie vid n fereastra activ; int getch (void); - citete un caracter fr ecou de la tastatur, adic dup ce este citit caracterul nu mai este afiat pe ecran; funcia returneaz codul ASCII al caracterului citit de la tastatur. int getche (void); - citete un caracter cu ecou de la tastatur, adic dup ce este citit caracterul este afiat automat pe ecran; funcia returneaz codul ASCII al caracterului citit de la tastatur.

241

int kbhit (void); - controleaz dac s-a tastat ceva la tastatur. Dac a fost apsat o tast se returneaz o valoare diferit de zero, altfel se returneaz valoarea 0. Exemplu: Urmtorul program deseneaz o fereastr i scrie un numr n aceasta.
#include #include #include #include #include <stdio.h> <stdlib.h> <conio.h> <alloc.h> <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; //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();

242

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

243

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

Capitolul XII UTILIZAREA ECRANULUI N MOD GRAFIC


Pentru aplicaiile grafice limbajul C pune la dispoziie peste 60 de funcii standard ce au prototipul n fiierul graphics.h. n continuare sunt prezentate cele mai importante funcii ce permit gestiunea ecranului n mod grafic.

12.1. Iniializarea modului grafic


244

Pentru a se putea lucra n mod grafic trebuie realizat o iniializare utiliznd funcia initgraph. Aceasta poate fi folosit singur sau mpreun cu o alt funcie numit detectgraph care determin parametrii adaptorului grafic. Prototipul ei este:
void far detectgraph(int far *gd, int far *gm);

unde: Pointerul gd pstreaz adresa uneia din valorile din tabelul urmtor (n funcie de adaptorul grafic utilizat):
Constant simbolic CGA MCGA EGA EGA64 EGAMONO IBM8514 HERCMONO ATT400 VGA PC3270 Valoare 1 2 3 4 5 6 7 8 9 10

Valorile spre care pointeaz gd definesc nite funcii standard corespunztoare adaptorului grafic. Aceste funcii se numesc drivere. Ele se afl n subdirectorului BGI. Funcia detectgraph detecteaz adaptorul grafic prezent la calculator i pstreaz valoarea corespunztoare acestuia n zona spre care pointeaz gd. Zona spre care pointeaz gm memoreaz una din valorile:
Adaptor CGA Constant simbolic Valoare CGAC0 0 CGAC1 1 CGAC2 2 CGAC3 3 CGAHI 4 EGALO 0 EGAHI 1 VGALO 0 VGAMED 1 Rezoluie 320*200 320*200 320*200 320*200 640*200 640*200 640*350 640*200 640*350

EGA VGA

245

VGAHI 2

640*480

Modul grafic se definete n aa fel nct el s fie cel mai performant pentru adaptorul grafic curent. Cele mai utilizate adaptoare sunt cele de tip EGA i VGA. Apelul funciei detectgraph trebuie s fie urmat de apelul funciei initgraph. Aceasta seteaz modul grafic n conformitate cu parametri stabilii de apelul prealabil al funciei detectgraph i are urmtorul prototip:
void far *cale); initgraph(int far *gd,int far *gm, int far

unde: gd i gm sunt pointeri ce au aceeai semnificaie ca i n cazul funciei detectgraph; cale este pointer spre irul de caractere care definete calea subdirectorului BGI care conine 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 urmtoarea secven de instruciuni:
int gd,gm; detectgraph(&gd,&gm); initgraph(&gd,&gm, C:\\BORLANDC\\BGI);

Doar dup apelul funciei initgraph pot fi utilizate i alte funcii de gestionare a ecranului n mod grafic. Utilizatorul poate defini el nsui parametri pentru iniializarea modului grafic. De exemplu, secvena urmtoare:
int gd=1,gm=0; initgraph(&gd,&gm, C:\\BORLANDC\\BGI);

seteaz modul grafic corespunztor unui adaptor grafic CGA cu rezoluia 320*200 de puncte. n afara acestor funcii mai pot fi utilizate i urmtoarele funcii: 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;

246

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 corespunztor adaptorului grafic curent; void far getmoderange(int grafdriv,int far *min, int far *max) definete valorile minimale i maximale ale modului grafic utilizat. void far closegraph(void) se utilizeaz pentru a iei din modul grafic.

12.2. Gestiunea culorilor


Adaptoarele grafice sunt prevzute cu o zon de memorie n care se pstreaz 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. Poziia pe ecran a unui pixel se definete prin dou valori ntregi: (x,y) unde: x definete coloana n care este afiat pixelul; y definete linia n care este afiat pixelul. Fiecrui pixel i corespunde o culoare ce este ptrat n memoria video. Numrul maxim de culori care pot fi afiate cu ajutorul unui adaptor EGA este 64.. Culorile se codific prin numere ntregi din intervalul [0, 63] i prin constante simbolice. Cele 64 de culori nu pot fi afiate simultan. n cazul adaptorului EGA pe ecran se pot afia cel mult 16 culori ce formeaz o palet. Paleta implicit este dat de tabelul urmtor:
Denumire simbolic BLACK BLUE GREEN CYAN RED MANGETA BROWN Valoare 0 1 2 3 4 5 6

247

LLIGHTGRAY DARKGRAY LIGHTBLUE LIGHTGREEN LIGHTCYAN LIGHTRED LIGHTMANGETA YELLOW WHITE

7 8 9 10 11 12 13 14 15

n mod implicit, culoarea fondului este ntotdeauna cea corespunztoare indicelui zero, iar culoarea pentru desenare este cea corespunztoare indicelui 15. Pentru controlul culorilor pot fi utilizate urmtoarele funcii: void far setbkcolor(int culoare) modific culoarea fundalului; int far getbkcolor(void) returneaz indexul din tabloul care definete paleta pentru culoarea fundalului; void far setcolor(int culoare) seteaz culoarea utilizat pentru desenare; int far getcolor(void) returneaz indexul din tabloul care definete 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; signed char colors[MAXCOLORS+1]; };

unde size este dimensiunea paletei; colors este un tablou ale crui elemente au ca valori codurile culorilor componente ale paletei care se definete. Modificarea paletei curente cu ajutorul funciei setpalette sau setallpalette conduce la schimbarea corespunztoare a culorilor afiate pe ecran n momentul apelului funciilor respective. void far getpalette(struct palettetype far* paleta) determin codurile culorilor componente ale paletei curente;

248

int far getmaxcolor(void) returneaz numrul maxim de culori diminuat cu 1; int far getpalettesize(void) returneaz numrul 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 afia m linii a n pixeli fiecare. Poziia unui pixel este dat de dou numere ntragi (x,y) numite coordonatele pixelului. Pixelul aflat n stnga sus are coordonatele (0,0). Coloanele se numeroteaz de la stnga la dreapta, iar liniile de sus n jos. Informaii referitoare la ecran pot fi obinute cu ajutorul urmtoarelor funcii: int far getmaxx(void) returneaz coordonta maxim pe orizontal; int far getmaxy(void) returneaz coordonta maxim pe vertical; int far getx(void) returneaz poziia pe orizontal a pixelului curent; int far gety(void) returneaz poziia pe vertical a pixelului curent.

12.4. Utilizarea textelor n mod grafic


Afiarea textelor n modul grafic presupune definirea unor parametri care pot fi controlai prin intermediul funciilor descrise n continuare: a) void far settextstyle(int font,int direcie,int charsize) unde: font definete setul de caractere i poate lua urmtoarele valori:
Constant simbolic DEFAULT_FONT TRIPLEX_FONT SMALL_FONT SANS_SERIF_FONT GOTHIC_FONT Valoare 0 1 2 3 4

direcie definete direcia de scris a textului, astfel:

249

- de la stnga la dreapta: HORIZ_DIR; - de jos n sus: VERT_DIR. charsize definete dimensiunea caracterului n pixeli, astfel:
Valoarea parametrului 1 2 3 . 10 Matricea utilizat pentru afiarea caracterului (n pixeli) 8*8 16*16 24*24 .. 80*80

b) void far settextjustify(int oriz, int vert) definete


cadrajul textului; oriz definete ncadrarea pe orizontal, astfel: - n stnga: LEFT_TEXT; - n centru: CENTER_TEXT; - n dreapta: RIGHT_TEXT. vert definete ncadrarea pe vertical, astfel: - marginea inferioar: BOTTOM_TEXT; - n centru: CENTER_TEXT; - marginea superioar: TOP_TEXT. Dup setarea acestor parametri pot fi afiate texte folosind funciile outtext i outtextxy care au urmtoarele prototipuri: void far outtext(char far* ir) , unde ir este un pointer spre zona de memorie n care se pstreaz caracterele de afiat, afieaz caracterele ncepnd cu poziia 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 pstreaz caracterele de afiat, x,y definete poziia de pe ecran unde se face afiarea. Dimensiunile n pixeli ale unui ir de caractere se pot determina utiliznd funciile textheight i textwidth: void far textheight(char far* ir) returneaz nlimea n pixeli a irului pstrat n zona spre care pointeaz ir, void far textwidth(char far* ir) returneaz llimea n pixeli a irului pstrat n zona spre care pointeaz ir.

12.5. Gestiunea imaginilor


250

n modul grafic, ecranul poate fi partajat n mai multe pri ce pot fi gestionate independent. Aceste pri se numesc ferestre grafice. Urmtoarele funcii sunt utilizate pentru prelucrarea ferestrelor grafice: void far setviewport(int st, int sus, int dr, int jos, int d) definete o fereastr grafic, unde: (st,sus) coordonatele colului stnga sus al ferestrei; (dr,jos) coordonatele colului dreapta jos al ferestrei; d indicator cu privire la decuparea desenului. Dac d are valoarea 1, atunci funciile de afiare a textelor i de desenare nu pot scrie sau desena n afara limitelor ferestrei. void far clearviewport(void) terge fereastra activ; dup apelul acestei funcii, toi pixelii ferestrei au aceeai culoare, i anume culoarea de fond, iar poziia curent a cursorului este punctul de coordonate relative (0,0); void far cleardevice(void) terge tot ecranul iar poziia curent a cursorului este colul din stnga sus al ecranului; void far getviewsettings(struct viewporttype far* fereastra) returneaz parametri ferestrei active. Imaginea ecranului se pstreaz n memoria video a adaptorului grafic i formeaz o pagin. Funciile urmtoare sunt utilizate pentru gestionarea paginilor void far setactivepage(int nrpag) activeaz o pagin al crei numr 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 dect cea activ utiliznd aceast funcie (aceast funcie poate fi util pentru animaie); 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 colului stnga sus a zonei de pe ecran ce se salveaz; (dr,jos) coordonatele colului dreapta jos a zonei de pe ecran ce se salveaz; zt pointer spre zona de memorie n care se salveaz imaginea de pe ecran.

251

unsigned far imagesize(int st, int sus, int dr, int jos) determin dimensiunea unei zone dreptunghiulare de pe ecran, unde: (st,sus) coordonatele colului stnga sus a zonei de pe ecran; (dr,jos) coordonatele colului dreapta jos a zonei de pe ecran. void far putimage(int st, int sus, int jos,void far* zt, int op) afieaz oriunde pe ecran o zon dreptunghiular salvat cu funcia getimage, unde: (st,sus) coordonatele colului stnga sus a zonei de pe ecran ce se salveaz; zt pointer spre zona de memorie n care se pstreaz imaginea ce se va afia pe ecran; op definete operaia 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 definete astfel:
Constant simbolic COPY_PUT XOR_PUT OR_PUT AND_PUT NOT_PUT Valoare 0 1 2 3 4 Aciune copiaz imaginea din memorie pe ecran sau exclusiv ntre datele de pe ecran i cele din memorie sau ntre datele de pe ecran i cele din memorie i ntre datele de pe ecran i cele din memorie copiaz imaginea din memorie pe ecran completnd datele aflate n memorie

12.6. Desenarea i colorarea figurilor geometrice


Biblioteca standard pune la dispoziia utilizatorului o serie de funcii care permit desenarea i colorarea unor figuri geometrice: void far putpixel(int x, int y, int culoare) afieaz un pixel pe ecran n punctul de coordonate (x,y) (relativ la fereastra activ) i avnd culoarea culoare; unsigned far getpixel(int x, int y) determin culoarea unui pixel aflat pe ecran n poziia (x,y); void far moveto(int x, int y) mut cursorul n dreptul pixelului de coordonate (x,y);

252

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 colurile diagonal opuse; void far drawpoly(int nr, int far* tabpct) traseaz o linie polignal, parametrul nr specificnd numrul de laturi iar tabpct este un pointer spre un tablou de ntregi ce definesc vrfurile liniei poligonale pstrate sub forma: abscisa_i,ordonata_i unde i are valorile 1,2,., nr+1; void far setlinestyle(int stil, unsigned ablon, int grosime) definete stilul utilizat pentru trasarea liniilor, unde: stil este un ntreg din intervalul [0,4] care definete stilul liniei conform urmtorului tabel:
Constant simbolic SOLID_LINE DOTTED_LINE CENTER_LINE DASHED_LINE USERBIT_LINE Valoare 0 1 2 3 4 Stil Linie continu Linie punctat Linie ntrerupt format din liniue de dou dimensiuni Linie ntrerupt format din liniue de aceeai dimensiune Stil definit de utilizator prin ablon

253

ablon definete stilul liniei i are sens doar cnd parametrul stil are valoarea 4; grosime definete limea 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 aceeai semnificaie cu funcia rectangle ns dreptunghiul este colorat; void far bar3d(int st, int sus, int dr, int jos, int profunzime, int ind) funcia 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 haura, int culoare) definete modul de colorare al figurilor, astfel: culoare definete culoarea utilizat pentru haurare; haura definete haura utilizat pentru colorare conform tabelului:
Constant simbolic EMPTY_FILL SOLID_FILL LINE_FILL LTSLASH_FILL SLASH_FILL BKSLASH_FILL LTBKSLASH_FILL HATCH_FILL XHATCH_FILL INTERLEAVE_FILL WIDE_DOT_FILL CLOSE_DOT_FILL USER_FILL Valoare 0 1 2 3 4 5 6 7 8 9 10 11 12

254

void far setfillpattern(char far *h_utilizator,int culoare) este utilizat pentru a defini o haur a utilizatorului, astfel: culoare definete culoarea de haurare; h_utilizator este un pointer spre o zon de memorie care definete haura 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 funcie utilizat pentru colorarea unui domeniu nchis, astfel: (x,y) reprezint coordonatele unui punct din interiorul domeniului nchis; culoare definete culoarea utilizat la trasarea conturului figurii (interiorul este colorat n conformitate cu setrile efectuate de funcia setfillstyle). Exemplu: Prezentm n acest exemplu un model de utilizare a modului grafic pentru trasarea graficelor unor funcii 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 cuprins intre -1 si 1:\n ");

fie

255

printf("a="); scanf("%f",&a); printf("\n"); 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;

256

p=0; while(p==0) { setbkcolor(1); p=1; printf("1 : sin(x)\n"); 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);

257

if (l==1){clrscr();cleardevice(); p=0;}} closegraph(); }

Capitolul XIII FUNCII MATEMATICE

Limbajul C conine mai multe funcii matematice care utilizeaz argumente de tip double i ntorc valori de tip double. Aceste funcii se mpart n urmtoarele categorii: - funcii trigonometrice; - funcii hiperbolice; - funcii exponeniale i logaritmice; - alte tipuri. Toate funciile matematice sunt incluse n fiierul antet "math.h". Acesta mai conine o serie de macrodefiniii cum ar fi EDOM, ERANGE i HUGE_VAL. Macrodefiniiile EDOM i ERANGE se gsesc n fiierul "errno.h" i sunt constante ntregi diferite de zero, utilizate pentru a semnala erorile de domeniu i de plaj ale funciei. HUGE_VAL (aflat tot n "errno.h") este o valoare pozitiv de tip double. Dac un argument al unei funcii matematice nu este n domeniul pentru care a fost definit funcia, atunci funcia ntoarce 0 i n domeniul de eroare, "errno" este modificat la EDOM. Dac o funcie produce un rezultat prea mare pentru a fi reprezentat printr-un double, apare o depire, funcia returnnd HUGE_VAL cu semnul adecvat iar "errno" este modificat la ERANGE. Dac se produce subdepire, funcia ntoarce zero, iar "errno" este modificat la ERANGE n funcie de implementare.

13.1 Funcii trigonometrice


- sin(x) , - cos(x) , x n radiani x n radiani - sinusul lui x; - cosinusul lui x.

258

- tan(x) ,

x n radiani

- tangenta lui x;

Exemplu: Programul urmtor afieaz 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; 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 Funcii 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 urmtor afieaz 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 Funcii hiperbolice


- sinh(x) , x R - cosh(x) , x R - tanh(x) , x R - sinus hipebolic de x; - cosinus hipebolic de x; - tangenta hipebolica de x.

259

13.4 Funcii exponeniale 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 urmtor afieaz 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)); val ++; } while (val < 11.0);}

- pow(x,y); funcia calculeaza xy. O eroare de domeniu apare dac x = 0 i y = 0 sau dac x < 0 i y nu este ntreg. Exemplu: Programul urmtor afieaza 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 aplicaii este necesar generarea de numere aleatoare. Pentru asemenea cazuri limbajul C dispune de dou funcii, rand i random, care returneaz numere ntregi aleatore. Funcia rand are urmtorul prototip:
int rand(void)

i returneaz un numr ntreg, aleator, cuprins n intervalul de la 0 la RAND_MAX (valoare definit n fiierul antet stdlib.h). Funcia random are prototipul:
int random(int val_maxima)

i returneaz un numr ntreg, aleator, cuprins n intervalul [0, val_maxima]. Pentru generarea de numere aleatoare n virgul mobil se mparte rezultatul funciei random la o valoare ntreag. Urmtorul program exemplific utilizarea acestor funcii:

260

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

13.6 Alte tipuri de funcii matematice


Nume funcie sqrt(x) ceil(x) floor(x) fabs(x) ldexp(x, n) Caracterizarea funciei - radicalul lui x, sqrt(x)=

- cel mai mic ntreg, mai mare c x, convertit la double (ex. ceil(1.05) va returna valoarea 2). - 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). - modulul numrului x;

- calculeaza x*2n , unde n este de tip int. fmod(x, y) - restul n virgul mobila a lui x/y, cu acelai semn ca x. modf(x, double *ip) - mparte pe x n parte ntreag i parte fracionar, fiecare cu acelai semn c x; memoreaz partea ntreag n "*ip" i ntoarce partea fracionar. modf(x, double *ip) - ntoarce x ntr-o funcie normalizat n intervalul [1/2, 1] i o putere a lui 2, care se memoreaza n "*exp"; dac x este 0, ambele pri ale rezultatului sunt 0.

Modul de utilizare al acestor funcii este similar cu al celorlalte funcii matematice descrise n acest capitol.

261

Capitolul XIV ELEMENTE DE PROGRAMARE AVANSAT


14.1 Gestionarea memoriei
Un calculator poate avea trei tipuri de memorie: convenional, extins i expandat. n programare memoria constituie un factor important ce influeneaz viteza de lucru a programelor. Fiecare tip de memorie are diferite viteze de acces, ceea ce afecteaz performana programelor. Volumul i tipul de memorie instalat poate fi determinat utiliznd comanda DOS: C:>MEM /CLASSIFY (pentru versiuni ale sistemului de operare DOS mai mari de varianta 5). Sistemul de operare DOS dispune de capaciti de gestionare a memoriei ce pot maximiza performanele calculatorului.

14.1.1 Memoria convenional


Primul PC compatibil IBM utiliza de obicei ntre 64Kb i 256Kb memorie RAM (Read Only Memory). Pe atunci aceast memorie era mai mult dect suficient. Astzi, memoria convenional a unui PC este format din primul 1Mb de RAM. Programele DOS

262

ruleaz, n mod obinuit, cu primii 640Kb de memorie convenional. 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 intrareieire de baz). Sistemul de operare Windows utilizeaz modelul de memorie virtual pentru a gestiona memoria, ceea ce nseamn c eliberarea memoriei convenionale nu are semnificaie sub acest sistem de operare. ns, memoria convenional este important cnd se ruleaz programe n cadrul unei ferestre DOS sub Windows. Structura memoriei convenionale a unui calculator personal este urmtoarea:
BIOS ROM Memorie rezervat Memorie video COMMAND.COM Memorie pentru programe Intrri CONFIG.SYS Nucleul DOS Zona de comunicaii BIOS Vectori de ntrerupere BIOS

PC-ul mparte memoria n blocuri de 64Kb numite segmente. n mod obinuit, programul utilizeaz un segment de cod (ce conine instruciunile 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 definete numrul de segmente pe care le poate folosi pentru fiecare. Modele sunt foarte importante deoarece, dac se utilizeaz un model de memorie necorespunztor, programul poate s nu dein suficient memorie pentru execuie. Compilatorul va alege un model de memorie suficient de mare pentru a rula programul, ns cu ct memoria utilizat este mai mare cu att viteza de execuie a programului scade. Din aceast cauz trebuie ales modelul cel mai mic

263

pentru necesitile programului. Majoritatea compilatoarelor accept urmtoarele 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 obinuit 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 funcii se fac utiliznd 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 numr mare de date); e) large aloc mai multe segmente att pentru date ct i pentru cod i este cel mai lent model de memorie din cele prezentate pn acum. El trebuie utilizat doar ca ultim resurs; f) huge este un model utilizat doar n cazul utilizrii unor matrici mai mari de 64Kb. Pentru a stoca o astfel de matrice programul trebuie s utilizeze cuvntul cheie huge pentru a crea un pointer, astfel:
int huge *matrice_uria

dup care programul trebuie s utilizeze funcia halloc pentru alocarea memoriei i funcia hfree pentru eliberarea acesteia. Exemplul urmtor aloc o matrice de 400000 octei: 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]);

264

hfree(matrice_uriasa);

} }

Pentru selectarea unui anumit model de memorie se include, de regul, o opiune n cadrul liniei de comand a compilatorului. Majoritatea compilatoarelor predefinesc o constant specific pentru a determina modelul curent de memorie. n tabelul urmtor sunt prezentate aceste constante definite de compilatoarele Microsoft C i Borland C:
Model de memorie Small Medium Compact Large Microsoft C M_I86SM M_I86MM M_I86CM M_I86LM Borland C _SMALL_ _MEDIUM_ _COMPACT_ _LARGE_

Programul poate verifica modelul de memorie utilizat folosind urmtoarea secven de instruciuni:
#ifndef _MEDIUM_ printf(Programul cere modelul de memorie medium\n); exit(1); #endif

Atunci cnd un program trebuie s aloce memorie n mod dinamic se utilizeaz fie funcia malloc pentru a aloca memorie din zona near (din segmentul curent), fie funcia 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 specificaie pentru memoria expandat, care combin software i o platform special de memorie expandat pentru a pcli PC-ul n scopul accesrii unor volume mari de memorie. Mai nti n zona de memorie superioar se aloc un bloc de 64Kb dup care acest bloc de memorie este mprit n patru seciuni de 16Kb, numite pagini n care se ncarc paginile logice ale programului. De exemplu un program de 128Kb este mprit n opt pagini de 16Kb fiecare care sunt ncrcate n funcie de necesitile programului n zona rezervat de 64Kb.

14.1.3 Memoria extins


Calculatoarele cu procesoare peste 386 utilizeaz adresarea pe 32 de bii ceea ce le d posibilitatea de accesare direct de pn la 4Gb

265

de memorie. Programatorii au numit memoria de peste 1Mb memorie extins. Pentru a accesa memoria extins, trebuie ncrcat 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 creia programele pstreaz temporar datele pe durata execuiei. De exemplu, atunci cnd programele transmit parametri ctre o funcie, C plaseaz aceti parametri n stiv. Cnd funcia i ncheie execuia acetia sunt scoi din stiv. Stiva este numit astfel deoarece ultimele valori depuse sunt primele extrase. n funcie de modelul de memorie utilizat, spaiul de memorie ocupat de stiv difer. Valoarea minim a stivei este 4Kb. n cazul modelelor compact sau large, C aloc pentru stiv un ntreg segment de 64Kb. Dac un program plaseaz n stiv mai multe informaii dect poate reine aceasta, va aprea o eroare de depire a stivei (stack-overflow). Dac programul a dezactivat testarea stivei, datele depuse n stiv pot fi suprapuse peste datele programului. Exemplul urmtor prezint modul de determinare a dimensiunii stivei utiliznd funcia _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


Aa cum am menionat n paragraful anterior, BIOS-ul reprezint serviciile de intrare-ieire de baz. Pe scurt, BIOS este un cip din cadrul calculatorului ce conine instruciunile 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, totui, majoritatea compilatoarelor

266

de C dispun de funciide bibliotec ce permit utilizarea acestor servicii fr a avea nevoie de limbaje de asamblare. DOS este un sistem de operare pentru calculatoarele compatibile IBM PC. Sistemul DOS permite rularea programelor i pstreaz informaia pe disc. n plus, sistemul DOS pune la dispoziie 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 funciilor. Muli programatori confund serviciile DOS cu serviciile BIOS. Tabelul urmtor prezint relaia dintre componenta HARD a calculatorului, serviciile BIOS, DOS i componenta SOFT.
Programe DOS BIOS HARDWARE Nivel nalt | | Nivelul cel mai jos

Aa 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 cte ori poate fi utilizat o funcie de bibliotec C n locul unui serviciu DOS sau BIOS, aceasta s fie utilizat pentru a mri 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 pn la urm serviciile BIOS penttru a accesa componentele hardware ale cal;culatorului.

14.2.1 Serviciile BIOS


Prezentm n continuare o serie de servicii BIOS ce pot fi accesate utiliznd funcii de bibliotec ale limbajului C. 1) accesul la imprimant

267

nainte ca un program s scrie ieirea la imprimant utiliznd indicatorul de fiier stdprn se poate face o verificare dac imprimanta este conectat i dac are hrtie utiliznd funcia biosprint din fiierul antet bios.h:
int biosprint(int comanda,int octet,int nr_port)

unde comanda specific una din urmtoarele operaii: 0 tiprete octetul specificat; 1 iniializeaz portul imprimantei; 2 citete starea imprimantei. Parametrul octet specific valoarea ASCII a caracterului ce se dorete a fi scris la imprimant iar nr_port specific portul imprimantei care poate fi 0 pentru LPT1, 1 pentru LPT2, .a.m.d. Funcia biosprint returneaz o valoarea nteag pe un octet ai crui bii au urmtoarea semnificaie: 0 dispozitiv n pauz; 3 eroare I/O; 4 imprimant selectat; 5 lips hrtie; 6 confirmare dispozitiv; 7 dispozitivul nu este ocupat. 2) operaii intrare/ieire Operaiile intrare/ieire de nivel jos pot fi realizate utiliznd funcia biodisk ce are urmtoarea sintax:
int biodisk(int operatie, int unitate, int head, int track, int sector, int nr_sector, void *buffer)

unde parametrul unitate precizeaz numrul unitii, care este 0 pentru A, 1 pentru B, i aa 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 funcia dorit astfel:
0 1 2 3 4 5 6 7 8 Iniializeaz sistemul de disc Returneaz starea ultimei operaii pe disc Citete numrul precizat de sectoare Scrie numrul precizat de sectoare Verific numrul precizat de sectoare Formateaz pista specificat Formateaz pista specificat i marcheaz sectoarele defecte Formateaz unitatea ncepnd cu pista specificat Returneaz parametrii unitii de disc

268

9 10 11 12 13 14 15 16 17 18 19 20

Iniializeaz unitatea de disc Execut o citire lung 512 octei de sector plus patru suplimentari Execut o scriere lung 512 octei de sector plus patru suplimentari Execut o poziionare pe disc Iniializarea alternativ a discului Citete bufferul sectorului Scrie bufferul sectorului Testeaz dac unitatea este pregtit Recalibreaz unitatea Execut diagnosticarea unitii de RAM Execut diagnosticarea unitii Execut diagnosticarea intern a controlerului

Dac se execut cu succes, funcia 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 dispoziie funcia _bios_keybrd ce are urmtoarea sintax:
unsigned _bios_keybrd(unsigned comanda)

unde parametrul comanda specific operaia dorit i poate avea una din urmtoarele valori:
_KEYBRD_READ _KEYBRD_READY

_KEYBRD_SHIFTSTATUS

_NKEYBRD_READ

_NKEYBRD_READY

Indic funciei s citeasc un caracter de la tastatur Determin dac este prezent un caracter la bufferul tastaturii. Dac funcia returneaz 0, nseamn c nici o intrare de la tastatur nu este prezent. Dac valoarea returnat este 0xFFFF, utilizatorul a apsat CTRL C 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 apsat Bit 2 CTRL este apsat Bit 1 SHIFT stnga este apsat Bit 0 SHIFT dreapta este apsat Indic funciei s citeasc un caracter de la tastatur, inclusiv tastele speciale, cum ar fi tastele cu sgei Determin dac este prezent un caracter la

269

_NKEYBRD_SHIFTSTATUS

bufferul tastaturii. Dac funcia returneaz 0, nseamn c nici o intrare de la tastatur nu este prezent. Dac valoarea returnat este 0xFFFF, utilizatorul a apsat CTRL C Funcia accept inclusiv tastele speciale, cum ar fi tastele cu sgei 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 apsat Bit 10 CTRL dreapta este apsat Bit 9 ALT stnga este apsat Bit 8 CTRL stnga este apsat

4) obinerea listei cu echipamente din BIOS Unele programe necesit determinarea caracteristicilor hardware ale calculatorului. Pentru aceasta se utilizeaz funcia _bios_equiplist care are urmtoarea sintax:
unsigned _bios_equiplist(void);

Funcia returneaz o valoare pe 16 bii a cror valoare are urmtoarea semnificaie:


15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0

15:14 numrul de imprimante paralele instalate (de la 0 la 3); 13 imprimanta serial; 12 adaptorul de jocuri; 11:10:9 numrul de porturi seriale COM (de la 0 la 7); 8 prezena DMA (Direct Memory Acces); bitul are valoarea 0 dac exist DMA i 1 dac nu exist; 7:6 numrul 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, 1164Kb; 1 prezena coprocesorului matematic; 0 prezena unitii de disc flexibile. 5) controlul intrrilor i ieirilor pentru portul serial Pentru a executa operaii intrare/ieire utiliznd portul serial se utilizeaz funcia bioscom ce are urmtoarea sintax:
unsigned bioscom(int comanda,int port,char octet);

270

Parametrul comanda specific operaia dorit i poate avea una din urmtoarele valori:
_COM_INIT _COM_RECEIV E _COM_SEND _COM_STATUS Stabilete valorile pentru comunicare ale portului Primete un octet de la port Trimite un octet la port Returneaz valorile portului

Parametrul port specific portul serial ce se dorete a fi utilizat, unde 0 corespunde lui COM1, 1 lui COM2 i aa mai departe. Parametrul octet specific fie octetul pentru ieire, fie valorile de comunicare dorite. 6) determinarea volumului de memorie convenional BIOS Pentru a determina memoria convenional ce poate fi utilizat de ctre un proggram se utilizeaz funcia biosmemory ce are urmtoarea sintax:
int biosmemory(void);

Valoarea returnat de aceast funcie nu cuprinde memoria extins, expandat sau superioar. 7) citirea cronometrului BIOS BIOS are incorporat un ceas intern ce bate de 18.2 ori pe secund. Acest cronometru este util pentru a genera punctul iniial al unui generator de numere aleatoare. Multe compilatoare de C pun la dispoziie dou funcii pentru accesul la cronometrul BIOS: biostime i _bios_timeofday. Sintaxa acestor funcii este urmtoarea:
long biostime(int operatie,long timp_nou);

Parametrul operaie poate lua dou valori: 0 dac se dorete ca funcia s citeasc valoarea curent a cronometrului; 1 pentru a fixa valoarea cronometrului la valoarea timp_nou.
long _bios_timeofday(int operatie,long *batai);

Aceast funcie poate fi, de asemenea, utilizat pentru a citi sau a fixa cronometrul BIOS.

14.2.2 Serviciile DOS


n acest paragraf prezentm o serie de servicii DOS ce pot fi accesate utiliznd funcii de bibliotec ale limbajului C. 1) suspendarea temporar a unui program Execuia unui program poate fi suspendat temporar utiliznd funcia sleep.h din fiierul antet dos.h:
void sleep(unsigned secunde);

271

parametrul secunde specificnd numrul de secunde pe care este suspendat programul. 2) utilizarea sunetelor Generarea de sunete ce utilizeaz difuzorul calculatorului se realizeaz utiliznd funciile sound i nosound:
void sound(unsigned frecventa)

genereaz un sunet cu frecvena frecventa;


void sound(unsigned frecventa)

deconecteaz difuzorul. Programul urmtor genereaz un sunet de siren dezactivat la apsarea 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); } } while(!kbhit()); nosound(); }

3) obinerea de informaii despre erori n DOS n cazul n care un serviciu al sistemului DOS eueaz, programele pot cere informaii suplimentare despre acea eroare folosind funcia dosexterr:
int dosexterr(struct DOSERROR *info_eroare);

unde structura DOSERROR are urmtoarele cmpuri:


struct DOSERROR{ int de_exterror; //eroare int de_class; //clasa erorii int de_action;//actiune recomandata int de_locus;//sursa erorii };

Dac funcia returneaz valoarea 0, apelul serviciului DOS nu a avut nici o eroare. Clasa erorii descrie categotia erorii, astfel: 01H Resurse depite 02H Eroare temporar 03H Eroare de autorizare 04H Eroare de sistem 05H Eroare hardware

272

06H Eroare de sistem nedatorat programului curent 07H Eroare de aplicaie 08H Articol nentlnit 09H Format nevalid 0AH Articol blocat 0BH Eroare de suport 0CH Articolul exist 0DH Eroare necunoscut Parametrul de_action indic programului cum s rspund erorii, astfel: Mai nti ncearc din nou, apoi cere intervenia utilizatorului 01H ncearc din nou, cu o ntrziere, apoi cere intervenia 02H
utilizatorului

03H Cere intervenia utilizatorului pentru soluie 04H Renun i elimin Renun, dar nu elimina 05H 06H Ignor eroarea 07H ncearc din nou dup intervenia utilizatorului Parametrul de_locus specific sursa erorii, astfel: Surs necunoscut 01 H 02H Eroare de dispozitiv bloc Eroare de reea 03 H Eroare de dispozitiv serial 04 H Eroare de memorie 05 H 4) citirea valorilor registrului segment Codul programului, datele i stiva sunt controlate de compilator utiliznd 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 funcia segread:
void segread(struct SREGS *segs);

Structura SREGS are urmtoarele cmpuri:


struct SREGS { unsigned int es; unsigned int cs; unsigned int ss;

273

unsigned int ds; }

5) accesul la valorile de port Pentrul controlul hardware de nivel inferior, compilatoarele de C pun la dispoziie urmtoarele funcii: - int inport (int adresa_port); - citete un cuvnt de la portul specificat de parametrul adresa_port; - int inportb (int adresa_port); - citete un octet de la portul specificat de parametrul adresa_port; - int outport (int adresa_port); - scrie un cuvnt 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 funcia delay, similar funciei sleep. Funcia 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 fiier pentru comenzi se utilizeaz funcia system:
int system(const char *comanda);

Parametrul comanda este un ir de caracter care conine numele comenzii DOS sau a fiierului de comenzi. Dac funcia reuete s execute comanda, se returneaz valoarea 0, altfel returneaz -1. Programul urmtor prezint utilizarea funciei 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 utiliznd funcia _dos_getvect n modul urmtor:
void interrupt(* _dos_getvect(unsigned nr_intr))();

Parametrul nr_intr specific numrul ntreruperii dorite ce poate avea valori de la 0 la 255. Programul urmtor va afia vectorii pentru toate ntreruperile calculatorului:

274

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 dorete crearea unui program de tratare a unei ntreruperi, vectorul de ntrerupere trebuie atribuit acestui program. Aceast atribuire se realizeaz cu ajutorul funciei _dos_setvect:
void _dos_setvect(unsigned nr_intr, void interrupt(* handler)());

Parametrul nr_intr specific ntreruperea al crui vector trebuie modificat. Pentru activarea i dezactivarea ntreruperilor se utilizeaz funciile:
void _disable(void); void _enable(void);

Dac se dorete reactivarea ntreruperii originare se utilizeaz funcia _chain_interrupt:


void chain_interrupt(void(interrupt far *handler)());

Generarea unei ntreruperi se realizeaz folosind funcia geninterrupt:


void geninterrupt(int intrerupere);

unde parametrul intrerupere specific ntreruperea generat.

14.3 Bibliotecile C
Dac se examineaz fiierele ce nsoesc un compilator C, se remarc multe fiiere cu extensia LIB. Aceste fiiere conin biblioteci obiect. Atunci cnd este compilat i link-editat un program, editorul de legturi examineaz fiierele LIB pentru a rezolva referinele la funcii. Cnd sunt create funcii utile ce sunt necesare i n alte programe, se pot construi biblioteci n care aceste funcii s fie pstrate.

14.3.1 Reutilizarea unui cod obiect


n cazul crerii unei funcii utile care se dorete reutilizat, se poate compila fiierul ce conine funcia respectiv pentru a crea codul obiect (de exemplu din fiierul funcie.c prin compilare se obine

275

fiierul obiect funcie.obj). Funcia definit n acest fiier obiect poate fi reutilizat n alt program utiliznd urmtoarea instruciune: C:\>bc fisier_nou.c funcie.obj Totui, acest mod de a reutiliza codul unor funcii este destul de dificil de utilizat n cazul n care se dorete reutilizarea unui numr mare de funcii aflate n fiiere obiect separate.

14.3.2 Lucrul cu fiiere bibliotec


Operaiile acceptate de fiierele bibliotec sunt urmtoarele: - crearea unei biblioteci; - adugarea unuia sau mai multor fiiere obiect la bibliotec; - nlocuirea unui fiier obiect cu altul; - tergerea unuia sau mai multor fiiere obiect din bibliotec; - listarea rutinelor pe care le conine biblioteca. n funcie de compilator, numele programului de bibliotec i opiunile liniei de comand pe care programul le accept vor diferi. n continuare prezentm operaiile ce pot fi realizate cu funciile bibliotec utiliznd programul TLIB al compilatorului Borland C. Presupunem c n urma compilrii am creat fiierul obiect funcie.obj ce conine o serie de funcii pe care dorim s le pstrm ntr-o bibliotec. Crearea unei bilioteci biblioteca.lib care s conin acest fiier obiect se realizeaz cu urmtoarea linie de comand:
C:\>tlib biblioteca.lib + functie.obj

Dup ce fiierul bibliotec a fost creat, funciile acestuia sunt disponibile pentru compilarea i legarea noilor programe. Funcia de biliotec TLIB a compilatorului Borland C are urmtoarea sintax:
tlib cale comand, fiier

unde: -

cale este un ir de caractere care specific calea pn la bilioteca asupra creia se efectueaz operaia; comand este format dintr-un simbol i numele unui fiier obiect. Simbol poate fi unul din caracterele: + (adaug un modul la bibliotec), - (elimin un modul din bibliotec), * (extrage un modul din bibliotec ntr-un fiier cu acelai nume, fr al elimina), -+ (nlocuiete un modul din bibliotec), -* (extrage i elimin un modul din bibliotec); fisier reprezint numele fiierul n care se scrie ieirea operaiei efectuate asupra bibliotecii.

276

14.3 Fiierele antet


Fiecare program folosete una sau mai multe instruciuni #include pentru a cere compilatorului de C s foloseasc instruciunile incluse ntr-un fiier antet. Cnd compilatorul ntlnete o instruciune #include n program, el compileaz codul din fiierul antet ca i cum ar fi scris n fiierul surs. Fiierele antet conin definiii frecvent utilizate i furnizeaz compilatorului informaii referitoare la funciile sale. Dac la compilarea programului se afieaz un mesaj de eroare, avertiznd c nu se poate deschide un anumit fiier antet, trebuie verificat subdirectorul care conine fiierele antet, pentru a vedea dac acel fiier exist sau nu. Dac se gsete fiierul respectiv, n linia de comand din sistemul de operare DOS trebuie scris urmtoarea instruciune:
C:\>SET INCLUDE=C:\BORLANDC\INCLUDE

BIBLIOGRAFIE
1. Plum T., Learning to program in C, Prentice Hall, 1983 Auslander D.,Tham C., Real-time software for control: program examples in C, Prentice Hall, 1990. Schild H., Using Turbo C, Borland, Osborne / McGraw Hill, 1988. Holzner S., Borland C++ Programming, Brady Books, New York, 1992.

2. 3. 4.

277

5. 6.

Somnea D., Turturea D., Introducere n C++, Programarea orientat pe obiecte, Ed. Tehnic, Bucureti, 1993. Marian Gh., Bdic C., Pdeanu L., Limbajul PASCAL, Indrumar de laborator, Reprografia Universitii din Craiova, 1993. Negrescu L., Introducere n MicroInformatica, Cluj Napoca, 1993. limbajul C, Editura

7. 8. 9. 10. 11. 12. 13.

Petrovici V., Goicea F., Programarea n limbajul C, Editura Tehnic, Bucureti, 1993. Marian Gh., Muatescu C., Lacu M., Iordache t., Limbajul C, Editura ROM TPT, Craiova, 1999. Mocanu M., Ghid de programare n limbajele C/C++, Editura SITECH, Craiova, 2001. Zaharia, M.D., Structuri de date i algoritmi. Exemple n limbajele C i C++, Ed. Albastr, Cluj Napoca, 2002. Kernighan, B.W., Ritchie, D.M., The C programming languages, Englewood. Cliffs, N.J. Prentice-Hall, 1978. Bulac, C., Iniiere n Turbo C++ i Borland C, Editura Teora, Bucureti, 1995.

278

S-ar putea să vă placă și