Documente Academic
Documente Profesional
Documente Cultură
1.1. Importan a limbajului C....................................................1 1.2 Arhitectura de baz a unui calculator ................................4 1.2.1 Microprocesorul .........................................................6 1.2.2 Memoria .....................................................................8 1.2.3 Echipamentele periferice..........................................10 1.3. Programarea calculatorului .............................................13 1.3.1. Sistemul de operare .................................................14 1.3.2. Tipuri de fiiere .......................................................17 1.3.3. Construirea fiierului executabil .............................18
Capitolul II REPREZENTAREA DATELOR N CALCULATOR................. 23 2.1. Reprezentarea intern/extern a numerelor...........................24 2.2. Reprezentarea extern a numerelor ......................................25
2.2.1. Reprezentarea extern a numerelor ntregi..............26 2.2.2. Reprezentarea extern a numerelor reale ................29 2.3 Reprezentarea intern a numerelor ..................................31 2.3.1. Reprezentarea intern a numerelor ntregi ..............31 2.3.2 Adunarea, scderea i nmul irea numerelor ntregi.33 2.3.3 Reprezentarea intern a numerelor reale..................35 Game de reprezentare pentru numerele reale....................46 2.3.5. Codificare BCD.......................................................47
Capitolul III ELEMENTELE DE BAZ ALE LIMABJULUI C...................... 49
3.1. Crearea i lansarea n execu ie a unui program C...........49 3.2. Structura unui program C ...............................................51 3.3. Mul imea caracterelor .....................................................53 I
3.3.1. Litere i numere.......................................................53 3.3.2. Caractere whitespace...............................................53 3.3.3. Caractere speciale i de punctua ie..........................54 3.3.4. Secven e escape.......................................................54 3.4. Identificatori ...................................................................56 3.5. Cuvintele cheie ale limbajului C.....................................56 3.6. Constante ........................................................................56 3.6.1. Constante caracter ...................................................57 3.6.2. Constante ntregi .....................................................57 3.6.3. Constante n virgul mobil ....................................57 3.6.4. Constante ir............................................................58 3.6.5. Constanta zero .........................................................59 3.6.6. Obiecte constante ....................................................59 3.6.7. Enumerri ................................................................60
Capitolul IV OPERANZI I OPERATORI N C................................................ 61
4.1. Operanzi..........................................................................61 4.2. Operatori .........................................................................61 4.2.1. Operatori aritmetici .................................................62 4.2.2. Operatori de incrementare i decrementare.............63 4.2.3. Operatori rela ionali ................................................63 4.2.4. Operatori logici .......................................................64 4.2.5. Operatori logici la nivel de bit.................................65 4.2.6. Operatorul de atribuire ............................................70 4.2.7. Operatorul sizeof .....................................................70 4.2.8. Operatorul ternar ? .................................................71 4.2.9. Operatorul virgul ...................................................72 4.2.10. Operatorul de for are a tipului sau de conversie explicit (expresie cast) ........................................72 4.2.11. Operatorii parantez ..............................................73 4.2.12. Operatorul adres ..................................................73 4.2.13. Al i operatori ai limbajului C ................................74 4.2.14. Regula conversiilor implicite i preceden a operatorilor ...........................................................74 II
5.1. Instruc iuni etichetate (instruc iunea goto) .....................76 5.2. Instruc iuni expresie........................................................77 5.3. Instruc iuni compuse.......................................................77 5.4. Instruc iuni de selec ie ....................................................78 5.4.1. Instruc iunea if.........................................................78 5.4.2. Instruc iuni de selec ie multipl: if - else if.............79 5.4.3. Instruc iunea switch.................................................80 5.5. Instruc iuni repetitive......................................................82 5.5.1. Instruc iunea for ......................................................82 5.5.2. Instruc iunea while ..................................................86 5.5.3. Instruc iunea do-while.............................................87 5.5.4. Bucle ncuibate........................................................89 5.5.5. Instruc iunea break ..................................................91 5.5.6. Instruc iunea continue .............................................92
Capitolul VI TIPURI DE DATE STRUCTURATE ............................................ 93
6.1. Tablouri unidimensionale ...............................................93 6.1.1. Constante ir............................................................94 6.1.2. Ini ializarea vectorilor de caractere .........................95 6.1.3. Func ii pentru prelucrarea irurilor (fiierul antet string.h).................................................................97 6.2. Tablouri cu dou dimensiuni (matrice).........................100 6.2.1. Ini ializarea matricelor ..........................................100 6.2.2. Tablouri bidimensionale de iruri .........................101 6.3. Tablouri multidimensionale..........................................101 6.4. Structuri ........................................................................102 6.4.1. Tablouri de structuri ..............................................104 6.4.2. Introducerea structurilor n func ii ........................110 6.4.3. Tablouri i structuri n structuri.............................114 6.5. Uniuni ...........................................................................114 6.6. Enumerri .....................................................................116
III
7.1. Operatori pointer...........................................................118 7.1.1. Importan a tipului de baz.....................................120 7.1.2. Expresii n care intervin pointeri...........................120 7.2. Pointeri i tablouri.........................................................125 7.2.1. Indexarea pointerilor .............................................126 7.2.2. Pointeri i iruri .....................................................128 7.2.3. Preluarea adresei unui element al unui tablou.......129 7.2.4. Tablouri de pointeri...............................................129 7.2.5. Pointeri la pointeri.................................................130 7.2.6. Ini ializarea pointerilor..........................................131 7.2.7. Alocarea dinamic a memoriei..............................132 7.2.8. Pointeri la structuri ................................................134 7.2.9. Structuri dinamice liniare de tip list ...................137
Capitolul VIII FUNC II ......................................................................................... 150
8.1. Forma general a unei func ii .......................................150 8.2. Rentoarcerea dintr-o func ie ........................................152 8.3. Valori returnate.............................................................153 8.4. Domeniul unei func ii ...................................................154 8.4.1. Variabile locale .....................................................155 8.4.2. Parametri formali...................................................156 8.4.3. Variabile globale ...................................................157 8.5. Apelul func iilor............................................................161 8.6. Apelul func iilor avnd ca argumente tablouri .............163 8.7. Argumentele argc i argv ale func iei main() ...............166 8.8. Func ii care returneaz valori nentregi ........................168 8.9. Returnarea pointerilor...................................................169 8.10. Func ii de tip void .......................................................172 8.11. Func ii prototip ...........................................................173 8.12. Func ii recursive .........................................................174 8.13. Clase de memorare (specificatori sau atribute)...........176 8.14. Pointeri la func ii ........................................................181 IV
9.1. Directive uzuale ............................................................183 9.2. Directive pentru compilare condi ionat ......................185 9.3. Modularizarea programelor .........................................189
Capitolul X INTRRI/IEIRI ........................................................................... 194
10.1. Func ii de intrare i ieire - stdio.h ...............................194 10.2. Opera ii cu fiiere .......................................................197 10.3. Nivelul inferior de prelucrare a fiierelor ...................199 10.3.1. Deschiderea unui fiier........................................200 10.3.2. Scrierea ntr-un fiier...........................................204 10.3.3. Citirea dintr-un fiier...........................................206 10.3.4. nchiderea unui fiier...........................................208 10.3.5. Pozi ionarea ntr-un fiier....................................208 10.3.6 tergerea unui fiier .............................................210 10.3.7. Exemple de utilizare a func iilor de intrare/ieire de nivel inferior .......................................................211 10.4. Nivelul superior de prelucrare a fiierelor ..................216 10.4.1. Func ia fopen() ....................................................216 10.4.2. Func ia fclose()....................................................218 10.4.3. Func iile rename() i remove()...........................219 10.4.4. Func ii de tratare a erorilor..................................219 10.4.5. Func ii cu acces direct .........................................220 10.4.6. Func ii pentru pozi ionare ...................................221 10.4.7. Ieiri cu format ....................................................223 10.4.8. Intrri cu format ..................................................226 10.4.9. Func ii de citire i scriere a caracterelor..............228
Capitolul XI UTILIZAREA ECRANULUI N MOD TEXT ........................... 233
11.1. Setarea ecranului n mod text .....................................233 11.2. Definirea unei ferestre ................................................234 11.3. tergerea unei ferestre ................................................234 11.4. Deplasarea cursorului .................................................235 V
12.1. Ini ializarea modului grafic.........................................240 12.2. Gestiunea culorilor......................................................242 12.3. Setarea ecranului.........................................................244 12.4. Utilizarea textelor n mod grafic.................................244 12.5. Gestiunea imaginilor...................................................246 12.6. Desenarea i colorarea figurilor geometrice ...............247
Capitolul XIII FUNC II MATEMATICE............................................................ 253
13.1 Func ii trigonometrice .................................................253 13.2 Func ii trigonometrice inverse.....................................254 13.3 Func ii hiperbolice .......................................................254 13.4 Func ii exponen iale i logaritmice..............................254 13.5 Generarea de numere aleatoare....................................255 13.6 Alte tipuri de func ii matematice .................................256
Capitolul XIV ELEMENTE DE PROGRAMARE AVANSAT ....................... 257
14.1 Gestionarea memoriei ..................................................257 14.1.1 Memoria conven ional........................................257 14.1.2 Memoria expandat ..............................................260 14.1.3 Memoria extins...................................................260 14.1.4 Stiva......................................................................260 14.2 Servicii DOS i BIOS ..................................................261 14.2.1 Serviciile BIOS ....................................................262 14.2.2 Serviciile DOS......................................................266 14.3 Bibliotecile C ...............................................................270 14.3.1 Reutilizarea unui cod obiect.................................270 14.3.2 Lucrul cu fiiere bibliotec...................................270 14.3 Fiierele antet...............................................................271
BIBLIOGRAFIE ............................................................................ 272
VI
- capacitatea de programare att la nivel nalt ct i la nivel sczut ceea ce d posibilitatea utilizatorului de a programa fie fr a sim i sistemul de operare i maina de calcul, fie la un nivel apropiat de sistemul de operare ceea ce permite un control foarte bun al eficien ei programului din punct de vedere vitez/memorie; - portabilitatea programelor ce permite utilizarea programelor scrise n C pe o mare varietate de calculatoare i sisteme de operare; - facilit ile de reprezentare i prelucrare a datelor materializate printr-un numr mare de operatori i func ii de bibliotec ce fac programarea mult mai uoar. Prin anii 80 interesul pentru programarea orientat pe obiecte a crescut, ceea ce a condus la apari ia de limbaje care s permit utilizarea ei n scrierea programelor. Limbajul C a fost dezvoltat i el n aceast direc ie i n anul 1980 a fost dat publicit ii limbajul C++, elaborat de Bjarne Stroustrup de la AT&T. La ora actual, majoritatea limbajelor de programare moderne au fost dezvoltate n direc ia 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 influen at 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 facilit ile 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 aplica ii Internet. Interfe ele utilizator au atins o mare dezvoltare datorit facilit ilor oferite de componentele hardware ale diferitelor calculatoare. n principiu, ele simplific interac iunea dintre programe
i utilizatorii acestora. Astfel, diferite comenzi, date de intrare sau rezultate pot fi exprimate simplu i natural utiliznd diferite standarde care con in ferestre, bare de meniuri, cutii de dialoguri, butoane, etc. Cu ajutorul mouse-ului toate acestea pot fi accesate extrem de rapid i 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 interfe e simple i vizuale, accesibile unui segment foarte larg de utilizatori. Implementarea interfe elor este mult simplificat prin utilizarea limbajelor orientate spre obiecte, aceasta mai ales datorit posibilit ii de a utiliza componente standardizate aflate n biblioteci specifice. Importan a aplicrii conceptului de reutilizare a codului rezult din faptul c interfe ele utilizator adesea ocup 40% din codul total al aplica iei. Firma Borland comercializeaz o bibliotec de componente standardizate care pot fi utilizate folosind limbajul C++, bibliotec cunoscut sub numele Turbo Vision. De obicei, interfe ele utilizator gestioneaz ecranul n mod grafic. O astfel de interfa utilizator se numete interfa utilizator grafic. Una din cele mai populare interfe e utilizator grafice pentru calculatoarele IBM PC este produsul Windows oferit de firma Microsoft. Windows este un mediu de programare ce amplific facilit ile oferite de sistemul de operare MS-DOS. Aplica iile Windows se pot dezvolta folosind diferite medii de dezvoltare ca: Turbo C++ pentru Windows, Pascal pentru Windows, Microsoft C++, Microsoft Visual Basic, Visual C i Visual C++. Componentele Visual permit specificarea n mod grafic a interfe ei utilizator, a unei aplica ii, folosind mouse-ul, iar aplica ia propriu-zis se programeaz ntr-un limbaj de tip Basic, C sau C++. Dac n ani 70 se considera c o persoan este rezonabil s se poat ocupa de o aplica ie de 4-5 mii de instruc iuni, n prezent, n condi iile folosirii limbajelor de programare orientate pe obiecte, aceast medie a ajuns la peste 25 de mii de instruc iuni. Un limbaj de programare trebuie privit nu doar la suprafa a sa sintax i mod de butonare a calculatorului pentru o implementare particular ci mai ales n profunzime, prin conceptele pe care se bazeaz, prin stilul de programare, prin modul de structurare a aplica iei i, implicit, a programului, prin filozofia de rezolvare a
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 n elegerea i utilizarea eficient a limbajelor de nivel nalt orientate pe obiecte i Visual.
Echipamente de intrare
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 pu in temporar, date i programe. dispozitive de stocare externe: Permit calculatoarelor s stocheze permanent programe i mari cantit i de date. Cele mai uzuale dispozitive de stocare extern sunt HDD (hard disk drives), FDD (floppy disk drive) i CD-ROM (Compact Disk-Read Only Memory) sau CD-R/W (Compact Disk-Read/Write). dispozitive de intrare : n mod uzual sunt reprezentate de tastatur (keyboard) i de mouse. Aceste dispozitive reprezint calea uzual de introducere a datelor i instruc iunilor care gestioneaz func ionarea unui calculator. dispozitive de ieire: Reprezint modalitatea prin care calculatorul transmite utilizatorului uman rezultatele execu iei programelor. Ecranul monitorului sau imprimanta sunt astfel de dispozitive uzuale. unitatea de procesare i control (UPC) : Este partea principal a unui calculator deoarece este componenta care execut instruc iunile. n mod uzual aceast unitate de procesare i control este reprezentat de un microprocesor care se plaseaz pe placa de baz (mainboard) a calculatorului mpreun cu memoria intern RAM. n plus fa de aceste componente orice calculator este prevzut cu o magistral (bus) prin care se gestioneaz modalitatea de transmitere a datelor ntre componentele de baz ale calculatorului. Magistrala reprezint o colec ie de trasee electrice care leag microprocesorul de dispozitivele de intrare/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 interac iunea dintre componentele HARD principale ale unui calculator. Calculatoarele pot fi n general clasificate dup dimensiuni sau putere de calcul. Nu se poate face ns la ora actual o distinc ie net ntre 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
sta ii 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 (rezolu ie mai mare). minicalculator (minicomputer): Un calculator multiutilizator (multi-user) capabil s lucreze simultan cu zeci sau chiar sute de utilizatori. mainframe: Un calculator multiutilizator capabil s lucreze simultan cu sute sau chiar mii de utilizatori. supercomputer: Un computer extrem de rapid care poate executa sute de milioane de opera ii ntr-o secund.
1.2.1 Microprocesorul
Microprocesorul este cea mai important i cea mai scump component a unui calculator de performan ele acesteia depinznd n mare msur rezultatele ntregului sistem. Din punct de vedere fizic, microprocesorul este un cip ce con ine un circuit integrat complex ce i permite s prelucreze informa ii prin executarea unor opera ii logice i matematice diverse (adunri, scderi, nmul iri, mpr iri, comparri de numere). El este compus din dou pr i importante: unitatea de execu ie (EU Execution Unit) i unitatea de interfa a magistralei de date (BIU Bus Interface Unit). Prima component realizeaz efectiv
opera iile, iar cea de-a doua are func ia de transfer a datelor de la i nspre microprocesor. Microprocesorul reprezint de fapt unitatea central a unui calculator i ndeplinete o serie de activit i specifice cum ar fi: execut opera ii aritmetice i logice, decodific instruc iuni speciale, transmite altor cipuri din sistem semnale de control. Toate aceste opera ii sunt executate cu ajutorul unor zone de memorie ale microprocesorului, numite registre. Orice microprocesor are un set finit de instruc iuni pe care le 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 solu ie 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 proiectan ii de la Intel au prsit firma, crend corpora ia ZILOG care a produs chipul Z80 (compatibil cu 8080 dar cu set de instruc iuni mai puternic i de dou ori mai rapid. Cipul Intel 4004 a fost primul procesor comercial, lansat la 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 opera ii pe secund. Aceast inven ie a contribuit la revolu ionarea domeniilor de aplica ii ale computerelor, dnd startul unui adevrat galop de inova ii 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 publica iei Communications of the ACM, Gordon Moore, co-fondator al companiei Intel, era optimist n privin a evolu iei PC-urilor i a microprocesoarelor: "complexitatea microprocesoarelor, care se msoar prin numrul de tranzistori pe cip, s-a dublat aproape constant la fiecare 18 luni, de la apari ia primului prototip 4004. Aceasta evolu ie exponen ial a determinat o continu cretere a performan elor 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 instruc iuni 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 informa iile 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 informa ia sub forma a dou niveluri de tensiune ce corespund valorilor 0 i 1 din sistemul de numera ie. Celulele de baz ale memoriei (ce pot avea valoarea 0 sau 1) se numesc bi i i ele reprezint particulele cele mai mici de informa ie din calculator. Pentru citirea informa iilor nu se folosesc bi i n mod individual ci acetia sunt grupa i ntr-o succesiune. Astfel o succesiune de 8 bi i formeaz un octet (sau un byte) aceasta reprezentnd unitatea de msur a capacit ii de memorie. Deoarece reprezentarea numerelor n calculator se face n baza 2 i nu n baza 10, aa cum suntem obinui i 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 ini ial calculatorul (sistemul de operare). Aceste memorii pot fi doar citite (con inutul lor nu poate fi modificat). nscrierea con inutului acestor memorii se face de ctre fabricant, iar opera iunea 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 con inut se terge la deconectarea calculatorului. n memoria RAM informa ia este stocat temporar. De exemplu, programele de aplica ii curente i datele asociate acestor aplica ii sunt 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 existen ei unor memorii externe, care s fie solicitate la nevoie. Rolul acestora l joac discurile i ele pot fi asemnate cu cr ile dintr-o bibliotec pe care le putem consulta ori de cte ori avem nevoie de anumite informa ii. Primele discuri aprute pentru PC-uri, numite i dischete, floppy disk-uri sau discuri flexibile, permiteau stocarea a maximum 160 KB de informa ie. Astzi mai exist doar dischete cu diametrul de 3.5 inch cu capacitatea de 1,44 MB. Existen a acestora pare a fi pus ns n pericol de apari ia CD-urilor reinscriptibile a cror capacitate de memorare depete 700 MB iar evolu ia tehnologic, din ce n ce mai rapid, d semne c lucrurile nu se vor opri aici. Dischetele folosesc metode magnetice de memorare a informa iei motiv pentru care ele se mai numesc i suporturi magnetice de informa ie. Principala component a unui calculator utilizat pentru memorarea programelor o reprezint hard discul. Acesta poate fi asemnat cu o dischet de mare capacitate, integrat ntr-o unitate
ncapsulat. Ini ial, pu ine PC-uri prezentau hard discuri, dar cum pre urile acestora au sczut considerabil, iar performan ele i capacit ile au crescut, n prezent toate calculatoarele prezint acest dispozitiv. n clipa de fa , capacitatea de memorare a unui hard disc a depit valoarea de 40 de GB.
10
nscrierea de caractere pe ecran. Tastele de micare se afl situate n partea dreapt a tastaturii i ele func ioneaz n dou moduri, care pot fi comutate prin ac ionarea altei taste, aflate deasupra lor, pe care scrie NumLock. Dac ledul corespunztor acestei taste este aprins, modul de lucru este numeric, n caz contrar fiind comutat pe semnifica ia direc ional. Tastele func ionale sunt un grup de 12 taste situate n partea de sus a tastaturii avnd pe ele litera F urmat de un numr ntre 1 i 12. Ac ionarea acestor taste determin efectuarea unor opera ii specifice de la program la program. Mouse-ul este tot un echipament de intrare mai uor de manevrat dect tastatura dar care poate efectua mai pu ine opera ii. Totui, foarte multe aplica ii (n special aplica iile grafice) nu mai pot fi concepute fr mouse. Un mouse are aspectul unei buc i de spun, uor manevrabil, avnd dedesubt o bil pozi ionabil, 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 ac iuni se face prin pozi ionarea cursorului n zona corespunztoare i apsarea unuia dintre butoanele aflate pe partea posterioar. Ini ial un mouse avea dou sau trei butoane. Acum exist mouse-uri cu 5 butoane i 2 roti e ce ndeplinesc o serie de func ii 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 situa ia n care se folosete numai tastatura. Utilitatea mouse-ului este i mai evident n cazul aplica iilor grafice. De altfel, WINDOWS este un sistem de operare creat special pentru lucrul cu mouse-ul. Scanner-ul reprezint dispozitive care se cupleaz la un PC i cu care, prin intermediul unui software adecvat, se pot capta imagini, fotografii, texte etc., n vederea unei prelucrri ulterioare. Astfel se pot manevra imagini foto, se pot crea efecte grafice speciale, care nu se pot ob ine prin metode tradi ionale. Dup captare, imaginea poate fi prelucrat, mutat, 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 pr i: un adaptor (plac) video i un monitor sau display. Adaptorul video reprezint dispozitivul care realizeaz legtura (interfa a) cu calculatorul i se afl n interiorul acestuia. El va fi corespunztor tipului de monitor video care i este ataat. Adaptorul video realizeaz o rezolu ie orizontal i una vertical. Rezolu ia 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 rezolu ie 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 mbunt ire adus de standardul VGA a fost rezolu ia 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 execu iei unor comenzi sau programe, fiind ncadrat n categoria echipamentelor periferice de ieire. Ca pies principal, monitorul con ine 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 bi i. Standardul VGA a introdus un nou tip de monitor care utilizeaz semnale analogice pentru transferul informa iilor privind culoarea de la adaptorul video la monitor. Dac semnalele digitale prezint niveluri care indic prezen a sau absen a unui bit, semnalele analogice pot prezenta orice valoare ntre una minim i una maxim. Imprimanta - Reprezint un dispozitiv care poate fi 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 performan e, ct i prin modalit ile tehnice de ralizare. Fiecare dintre ele prezint avantaje i dezavantaje, ideal fiind a o folosi pe cea care corespunde cel mai bine tipului de lucrri
12
executate. n func ie 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 func ioneaz 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 pre ul echipamentului.
13
comune marii majorit i a programelor de aplica ie: alocarea memoriei, afiarea caracterelor pe ecran i la imprimant, citirea caracterelor de la tastatur, accesul la informa iile stocate pe disc magnetic, etc.
14
Aplica ie
Tastatur
Imprimant
Sistemul de operare al unui calculator este partea de software necesar i suficient pentru execu ia oricror alte aplica ii dorite de utilizator. Un calculator nu poate func iona dect sub gestiunea unui sistem de operare. Orice aplica ie lansat n execu ie de ctre un utilizator apeleaz la resursele puse la dispozi ie de ctre sistemul de operare. Sistemul de operare interfa eaz 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 responsabilit i i capabilit i i mai mari. Ele ac ioneaz ca un gestionar al traficului de date i al execu iei programelor. n principal sistemul de operare asigur ca diferite programe i diferi i utilizatori s nu interfereze unele cu altele. Sistemul de operare este de asemenea responsabil cu securitatea, asigurnd inaccesibilitatea persoanelor neautorizate la resursele sistemului. Sistemele de operare se pot clasifica dup cum urmeaz: multi-user: Permit ca doi sau mai mul i utilizatori s ruleze n acelai timp programe (utilizatori concuren i). Anumite sisteme de operare permit sute sau chiar mii de utilizatori concuren i. multiprocesor: Permit execu ia unui program pe mai mult de un microprocesor. multitasking: Permit mai multor programe s ruleze n acelai timp (execu ie concurent).
15
multithreading: Permit diferitelor pr i 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 aplica ie, pot rula (pot fi executate). Programele de aplica ie trebuie s fie scrise pentru a rula pe baza unui anumit sistem de operare. Alegerea unui anumit sistem de operare determin n consecin mul imea aplica iilor care pot fi rulate pe calculatorul respectiv. Pentru PC-uri, cele mai populare sisteme de operare sunt DOS, OS/2 sau Windows, dar mai sunt disponibile i altele precum Linux. Ca utilizator se interac ioneaz cu sistemul de operare prin intermediul unor comenzi. Spre exemplu, sistemul de operare DOS accept comenzi precum COPY sau RENAME pentru a copia 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. Interfa ele grafice cu utilizatorul (GUI, Graphical user interfaces) permit introducerea unor comenzi prin selectarea i ac ionarea cu mouse-ul a unor obiecte grafice care apar pe ecran. Spre exemplu, sistemul de operare Windows are un desktop ca intefa garfic cu utilizatorul. Pe acest desktop (birou) se afl diferite simboluri grafice (icoane, icons) ataate diferitelor aplica ii disponibile pe calculatorul respectiv. Utilizatorul are multiple posibilit i de configurare a acestei intefe e grafice. Primul sistem de operare creat pentru calculatoare a fost CP/M (Control Program for Microcomputers), realizat pentru calculatoarele pe 8 bi i. O dat cu perfec ionarea componentelor HARD s-a impus i necesitatea 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 capabilit ilor hardware ale calculatoarelor, acesta s-a transformat, prin dezvoltri succesive, n Windows.
16
Indiferent de sistemul de operare utilizat, din punctul de vedere al utilizatorului, informa iile sunt scrise pe disc sub forma unor fiiere. Un fiier este o colec ie de informa ii 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 func ie 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 cr i, deoarece ele con in numele fiierelor i adresa de nceput a acestora. De asemenea, un director poate con ine la rndul su alte directoare crendu-se astfel o structur arborescent de directoare n care poate fi gsit foarte repede un anumit fiier.
17
fiiere care con in informa ii intermediare ntre cele n limbaj surs i cele executabile (extensiile OBJ, OVL); fiiere ce con in imagini (extensiile JPEG, GIF, BMP); fiiere ce con in sunete (extensiile WAV, MIDI, MP3) etc.
18
La ora actual un limbaj de programare este inclus ntr-un mediu de programare mai complex care include un editor de texte pentru introducerea instruc iunilor n limbajul de programare de nivel nalt, un compilator i un editor de legturi folosite pentru translatarea codului surs n cod main. n mod tipic, un programator scrie declara ii ntr-un limbaj precum Pascal, C sau MATLAB folosind un editor. Se creeaz astfel un fiier numit fiier cod surs ce con ine o colec ie de instruc iuni i declara ii scrise n limbajul respectiv. Primul pas este prelucrarea codului surs de ctre compilator, care translateaz instruc iunile de nivel nalt ntr-o serie de instruc iuni cod obiect. Cnd este lansat n execu ie compilatorul acesta, ntr-o prim etap, lanseaz un analizor sintactic, gramatical, numit parser. Acesta parcurge i analizeaz sintactic, secven ial, n ordinea n care au fost introduse, toate instruc iunile scrise n limbajul de nivel nalt. O instruc iune de nivel nalt se translateaz ntr-una sau mai multe instruc iuni specifice microprocesorului pentru care a fost conceput compilatorul. Aceste instruc iuni ale microprocesorului sunt nlocuite cu codurile lor binare, fiecare instruc iune a microprocesorului fiind codificat de ctre constructor. Codurile binare ale instruc iunilor 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 tradi ional cod obiect. Este foarte important ca referiri la alte module de cod s fie corect reprezentate n acest cod obiect. Pasul final n producerea programului executabil, dup ce compilatorul a produs codul obiect, este prelucrarea codului obiect de 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 ob ine codul main, salvat ntr-un fiier cu extensia .exe. Acest cod main poate fi executat secven ial, instruc iune cu instruc iune, de ctre microprocesor. Cu alte cuvinte, un program executabil (executable program aflat pe disc cu extensia .exe) se ob ine prin salvarea pe disc a codului main ob inut prin prelucrarea succesiv a fiierului cod surs de ctre compilator (compiler) i apoi de ctre link-editor (linker).
19
Procesul de ob inere a unui executabil este prezentat n figura de mai jos. Blocurile tridimensionale reprezint entit ile principale ale mediului de programare: editorul de texte, compilatorul (compiler) i editorul de legturi (linker). Blocurile dreptunghiulare reprezint fiierele rezultate n urma aplicrii celor trei utilitare de sistem: n urma utilizrii editorului de texte ob inem fiierul text surs cod cu numele generic nume. Dac folosim limbajul de programare C spre exemplu, se ob ine fiierul nume.c care se va salva pe disc. n urma lansrii n execu ie 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 ob ine un fiier cod obiect, salvat pe disc sub numele nume.obj n urma lansrii n execu ie a editorului de legturi, se preia fiierul cod obiect nume.obj i se leag cu toate modulele necesare (inclusiv func ii de bibliotec sau alte module externe), ob inndu-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 execu ie a programului fluxul de informa ie este complet controlat de ctre microprocesor, toate salturile de adres fiind fcute corespunztor. Interpretorul (interpreter) este un program care execut instruc iuni scrise ntr-un limbaj de nivel nalt. Numai anumite limbaje
20
de nivel nalt, spre exemplu BASIC, LISP sau MATLAB, sunt prevzute cu un interpretor. Exist dou modalit i de a executa un program scris n limbaj de nivel nalt. Cel mai comun mod este acela de a compila programul. Cealalt modalitate este pasarea programului unui interpretor. Un interpretor translateaz instruc iunile de nivel nalt ntr-o form intermediar care este apoi executat. Prin contrast, un compilator translateaz instruc iunile de nivel nalt direct n limbaj 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 execu ie mediul BASIC, apoi se deschide fiierul surs-BASIC corespunztor i se lanseaz interpretorul de BASIC pentru execu ia 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 por iuni de program pe care s le testeze rapid. De asemenea, interpretoarele permit o programare interactiv fiind des folosite n procesul de instruc ie. n mediul de programare MATLAB, mediu interpretor, orice comand utilizator se execut imediat. Se pot edita i fiiere script, care con in secven e de comenzi care se execut secven ial. Programele de descriere a paginii (Page Description Languages) ca PostScript spre exemplu folosesc un interpretor. Fiecare imprimant PostScript are incorporat un interpretor care execut instruc iuni PostScript. Asamblorul (assembler) este un program care face transla ia unui program scris n limbaj de asamblare (limbaj de nivel sczut, 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 con ine instruc iuni mai pu in
21
complexe dect cele de nivel nalt, asamblorul face practic o convertire biunivoc ntre mnemonicele limbajului de asamblare i codurile binare corespunztoare acestor mnemonice (instruc iuni).
I n s tru c iu n ile n lim b a ju l d e n iv e l n a lt s e in tr o d u c d e la ta s ta tu r .
T o t c e s e in tr o d u c e d e la ta s ta tu r e s te v izib il p e m o n ito r
E d it o r d e te x te ( e v e n tu a l in c o r p o r a t n m e d iu )
F i ie ru l s u r s c u n u m e le n u m e i e x te n s ia c o r e s p u n z to a r e s e s a lv e a z d in m e m o r ia R A M p e h a r d d is k
C o m p ila t o r S e c o m p ile a z fi ie ru l s u r s
S e o b in e fi ie ru l c o d o b ie c t : n u m e .o b j S e s a lv e a z p e h a rd d is k fi ie r u l n u m e .o b j L in k - e d it a re ( le g a r e a tu tu r o r m o d u le lo r n e c e s a re )
S e o b in e fi ie r u l c o d m a in ( e x e c u t a b il): n u m e .e x e
S e s a lv e a z p e h a r d d is k fi ie r u l n u m e .e x e
L a n s a r e a n e x e c u ie d e c tr e s is te m u l d e o p e r a r e a e x e c u ta b ilu lu i n u m e .e x e
22
L o w = 0 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 numera ie binar folosit pentru reprezentarea informa iei n calculatoare este un sistem de numera ie ponderal, ntocmai ca sistemul de numera ie zecimal. Reprezentarea natural a numerelor la nivelul percep iei umane este cea zecimal, pe cnd reprezentarea proprie mainilor de calcul este cea binar. De aici rezult necesitatea compatibilizrii sau interfa rii ntre aceste dou moduri de reprezentare a numerelor. Cum cele dou sisteme de numera ie sunt ponderale, o prim diferen este aceea c sistemul zecimal 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.
23
n alt ordine de idei, dac pentru reprezentarea extern sunt semnificative simbolurile de reprezentare (cifre, semnele + sau -, punct zecimal sau binar, mantis sau exponent), pentru reprezentarea intern sunt necesare conven ii de reprezentare: indiferent de tipul datelor, acestea vor fi colec ii sau iruri de cifre binare crora, prin conven ie, li se atribuie semnifica ii. ntr-o prim instan , este foarte important s facem o distinc ie ntre tipurile de date recunoscute de un calculator (sau mai bine zis de microprocesorul cu care este dotat calculatorul personal) i formatele de reprezentare ale acestor date ce reprezint conven ii pentru reprezentarea tipurilor de date, att la nivel intern (n memoria calculatorului) ct i la nivel extern, al percep iei umane. Din punctul de vedere al tipurilor de date care sunt implementate n limbajul C putem spune c distingem dou mari categorii, date de tip ntreg (integer) i date de tip real (float). Formatele de reprezentare intern/extern vor fi prezentate n cele ce urmeaz. Cel mai simplu de reprezentat sunt numerele naturale. Se face apoi trecerea la numerele ntregi negative i apoi la numerele reale care au o parte ntreag i una frac ionar.
2.1. Reprezentarea intern/extern a numerelor Reprezentarea intern a numerelor se refer la modul n care se stocheaz datele n memoria RAM a calculatorului sau n regitrii microprocesorului. n acest format se prelucreaz numerele pentru implementarea diverselor opera ii aritmetice. La nivelul calculatorului informa ia 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 numera ie pentru reprezentarea numerelor. La nivel de reprezentare extern se folosete semnul - n fa a unui numr n cazul n care acesta este negativ sau punctul care separ partea ntreag de cea frac ionar. 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
24
cum se reprezint intern un numr natural cum se reprezint extern un numr ntreg negativ 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
n general dorim s ob inem 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
25
nseamn c va trebui s specificm cte cifre se vor folosi la partea ntreag i cte la partea frac ionar sau dac dorim reprezentare tiin ific sau nu. De altfel i operatorul uman face aceleai conven ii 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 func ii de reprezentare cu format a datelor numerice sau alfanumerice prin care programatorul poate impune un format extern cu care se manipuleaz datele.
26
N b = (c n 1 b n 1 + c n 2 b n 2 + ... + c1 b1 + c 0 b 0 ) = c k 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 mpr iri n ordinea invers de cum au fost ob inute. a) Conversia din baza 10 n baza 2 i invers Fie de exemplu numrul zecimal 37. Reprezentarea sa binar va fi ob inut astfel: 3710 = 1001012
37 36 1 2 18 18 0 2 9 2 8 4 2 1 4 2 2 0 2 1 0
1001012
Cu aceste numere naturale putem face o serie de opera ii aritmetice. Adunarea numerelor naturale binare se face ntocmai ca la cele n reprezentare n baza 10, dup regula: 0+0=0
27
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 1 1 0 0 1 62 1 1 1 1 1 0 Se observ cum se ob ine rezultatul corect. nmul irea se face n mod asemntor, ca o adunare repetat. Spre exemplu, s calculm 37x25
37 25
925
11100111012 = 1x20 + 1x22 + 1x23 +1x24 +1x27 +1x28+1x29 = 1+4+8+16+128+256+512 = 92510 b) Conversia dintr-o baz oarecare 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 4 A11 n baza 10 i apoi numrul zecimal ob inut l vom trece n baza 13. Se observ cum un numr n baza 11 poate con ine i cifra A=10 iar un numr n baza 13 poate con ine cifrele A=10, B=11, C=12.
) ck bk k =m
n1
Se observ cum punctul delimiteaz partea ntreag (exprimat printr-o combina ie de puteri pozitive ale bazei b) i partea frac ionar (exprimat printr-o combina ie de puteri negative ale bazei b). Semnifica ie pentru programator i pentru 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 frac ionar. 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 semnifica ie asemntoare cu punctul zecimal, care face separarea ntre partea ntreag i cea frac ionar. Cifrele binare situate dup punctul binar vor corespunde puterilor negative ale lui 2. Astfel, n general, un numr real va avea reprezentarea binar:
12.2510 = 1100.01 = 2 3 + 2 2 + 2 2
29
Partea ntreag a unui numr real se reprezint binar precum numerele ntregi (cu sau fr semn). Pentru a determina partea frac ionar, se procedeaz n mod invers ca la partea ntreag. Astfel, dac partea frac ionar zecimal se reprezint binar, atunci aceasta se nmul ete 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 ob ine reprezentarea binar a lui 12.25. Partea ntreag este 12. Ea se reprezint binar prin mpr iri succesive la 2 i considerarea resturilor. Partea frac ionar este 0.25 Partea P.F. x 2 Noua Bitul frac ionar P.F. nscris P.F.
0.25 0.5 0 0.5 1 0 0 1
Ob inem 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 frac ionar 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..
Ob inem: 5.3710 = 101.010111101...2 Cu ct mai multe cifre binare vom re ine dup punctul binar, cu att vom fi mai aproape de valoarea exact 5.37. Ob inem 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
30
infinit de cifre se poate reprezenta ntr-o alt baz pe un numr finit 1 de cifre ( ex: = 0.3333...3...10 = 0.13 ). Cum orice reprezentare 3 binar intern este pe un numr finit de bi i, numrul poate s nu fie reprezentat exact n calculator, ci cu o anumit aproxima ie. Acest lucru este decisiv pentru a n elege importan a lungimii reprezentrii numerelor n calculator. Cu ct un numr binar se reprezint pe un numr mai mare de bi i, cu att precizia de reprezentare crete.
31
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 conven ie pentru reprezentarea intern a numerelor ntregi negative. Aceast conven ie prevede folosirea MSB pentru reprezentarea semnului numerelor ntregi. Dac numrul este pozitiv, se adaug n pozi ia MSB bitul de semn 0, iar dac numrul este negativ se utilizeaz n pozi ia 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 to i bi ii numrului astfel ob inut. Complementarea nseamn transformarea bitului 0 n bitul 1 i a bitului 1 n bitul 0. pas3. Numrul astfel ob inut se adun cu 1. De exemplu, s reprezentm numrul -37. pas1. |-37| = 37 3710 = 1001012 = [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 semnifica ia numrului negativ. Pentru a vedea ce numr negativ este reprezentat, putem repeta procedeul de mai sus i ob inem 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
32
33
n continuare vom pune n eviden importan a 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 bi i). Aceste numere sunt cuprinse n gama
[ 2 , 2 1] = [ 128, 127].
7 7
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 opera iile (117-12) i (117+12). Se observ c operanzii sunt n gama de reprezentare a numerelor cu semn pe 8 bi i. Prin prima scdere, ne ateptm s ob inem un rezultat, 105, n aceeai gam de reprezentare. 117-12=117+(-12) = 01110101+11110100 = 01101001 = 10510, rezultat corect. 117+12 = 01110101+00001100 = 10000001 = -12710, rezultat evident incorect. Incorectitudinea provine de la faptul c rezultatul a depit gama de reprezentare. Dac rezultatul este interpretat pe 9 bi i de exemplu, gama de reprezentare devine [ 256, 255] i rezultatul va fi 117+12 = 001110101+000001100 = 010000001 = 12910, rezultat corect. Ca o concluzie preliminar, re inem c pentru a ob ine rezultate corecte este necesar s precizm dac se lucreaz sau nu cu bit de semn i pe c i bi i se face reprezentarea, pentru c numai n acest context interpretarea rezultatelor este corect. n ceea ce privete nmul irea numerelor ntregi cu semn (cu bit de semn), aici problema nu mai are o rezolvare asemntoare, n sensul c nu putem trata bi ii de semn la fel cu cei de reprezentare ai valorii. Astfel, procesorul studiaz bi ii de semn i ia o decizie n privin a semnului rezultatului. De fapt, se realizeaz func ia logic XOR a bi ilor de semn. Numerele negative se vor lua n modul, iar opera iile de nmul ire se vor face numai cu numere pozitive. La final, func ie de semnul rezultatului, se ia decizia 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
34
Se analizeaz separat bi ii de semn i se ia decizia c rezultatul va fi negativ, deci, la final, se va reprezenta n complement fa de 2. Mai departe se va lucra cu 25, modulul numrului (-25), care se ob ine prin complementarea fa de 2 a numrului binar 1100111: 1100111 0011000+1=0011001 Se va re ine pentru procesare numai numrul (fr semn) 11001, care se va nmul i cu numrul (fr semn) 100101, ob innd, aa cum am artat mai sus, valoarea 1110011101. Mai departe, se adaug bitul de semn, 0 pentru numere pozitive, ob inndu-se 01110011101. Acest ultim numr se va complementa fa de 2, ob inndu-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 nmul irea 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.
35
Numerele reale se pot reprezenta identic cu cele ntregi cu semn, cu o precizare: nu se face o deosebire net ntre bi ii reprezentrii pr ii ntregi i cei ai reprezentrii pr ii frac ionare. Acest tratament nediferen iat provine de la reprezentarea tiin ific uzual cu mantis i exponent. Fie, spre exemplu, reprezentarea binar a numrului 12.25:
Calculatorul poate reprezenta irul de bi i 110001 i re ine faptul c punctul se pune dup primii 4 bi i 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 informa ie 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 apari ia pe pia a primelor calculatoare pentru neprofesioniti (aa numitele Home Computers) au fost procesoare capabile s prelucreze i s transmit n paralel 8 bi i, a fost natural gruparea a 8 bi i ntr-o entitate numit byte. 1B = 8b (adic un byte reprezint 8 bi i) Procesoarele au evoluat, ajungndu-se n prezent la procesoare pe 64 de bi i. Cum evolu ia lor s-a fcut trecndu-se succesiv prin multipli de 8 bi i, s-au impus i alte entit i de reprezentare a informa iei, pe care le vom prezenta sintetic n tabelul de mai jos.
Denumire
Dimensiune
Nr. byte Nr. biti
8b 16 b 32 b 64 b 80 b
Denumire echivalent
Nota ie
1B 2B 4B 8B 10B
B W DW QW TW
A determina reprezentarea intern nseamn s determinm lungimea reprezentrii (de obicei n multipli de octe i), modul de interpretare al bi ilor ce compun reprezentarea i gama de
36
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 ob ine 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 n elegem unit ile 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 con ine de fapt o ntreag colec ie 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 combina ie de bi i) care l identific n mod unic. Cum cu un octet putem codifica 28 = 256 caractere, octetul s-a dovedit o entitate suficient pentru codificarea caracterelor utilizate n informatic. n 256 de coduri distincte se pot include literele mari i mici ale alfabetului anglo-saxon (inclusiv litere specifice diverselor alfabete precum cel chirilic sau particularit i ale diferitelor ri: , , , , ... n romn, de exemplu). Se mai pot include caractere ce reprezint numere, semne de punctua ie sau alte caractere de control. Codul ASCII a standardizat aceast codificare, astfel 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:
37
b6
b5
b4
b3
b2
b1
b0
B it d e s e m n 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 con in 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 mul imea 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 men ionat 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). Reprezentarea unui numr natural n format hexazecimal se realizeaz cu metoda mpr irii succesive la 16 sau, mai simplu, pornind de la reprezentarea binar a numrului. Cum mul imea cifrelor hexa con ine 16 simboluri (09 i AF), 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 bi i
38
se nlocuiete fiecare grup de 4 bi i cu cifra hexazecimal pe care o codific. De exemplu, s presupunem c avem numrul 217.
21710 = 110110012 = 1101.10012 = D916 = 13 161 + 9 16 0 = 208 + 9 = 217
n acest mod, dac un numr are o reprezentare intern pe un numr de k octe i, 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 5 6 7 8 9 10 11 12 13 14 15
H 0 1 2 3 4 5 6 7 8 9 a b c d e f
Sym Null
LF CR
D 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
H 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f
Sym
D 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
H 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f
H 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f
39
D 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
H 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f
Sym D @ 80 A 81 B 82 C 83 D 84 E 85 F 86 G 87 H 88 I 89 J 90 K 91 L 92 M 93 N 94 O 95
H 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f
Sym P Q R S T U V W X Y Z [ \ ] ^ _
D 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
H 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f
Sym ` a b c d e f g h i j k L M n o
D 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
H 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f
Sym p q r s t u v 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.
M SB S b30 O ctetul 1 O ctetul 2 O ctetul 3 b0 LS B O ctetul 4
Tipul int este identic cu signed int i utilizeaz o reprezentare pe 4B a numerelor ntregi cu semn. Reprezentarea pe 4 octe i duce la posibilitatea mririi gamei de reprezentare astfel:
max = 2 31 1 3 3 ; 2 31 = 2 2 30 = 2 210 2 10 3 2 10 9 31 min = 2
( )
( )
40
[ 2.1475 10 ] [ 2 10 , 2 10 ]
9 9 9
unsigned int nu va mai lua n considerare bitul de semn, astfel nct reprezentarea intern este de forma din figura de mai jos. Evident,
3 3 max = 2 32 1 ; 2 32 = 4 2 30 = 4 210 4 10 3 4 10 9 min = 0
( )
( )
poate
schimba
cu
ajutorul
b0 LSB
( )
unsigned long int va considera numai numere ntregi pozitive n gama 0, 1.844 1019 . 2.3.3.2 Tipul float Acest tip de reprezentare este de tip real, fiind cunoscut i ca reprezentare n virgul mobil (floating point). Acest tip descrie mecanismul de baz prin care se manipuleaz datele reale. Conceptul fundamental este acela de nota ie tiin ific, prin care orice 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.
Se observ cum stocarea n calculator a unei date floating-point necesit trei pr i: - bitul de semn (sign) - mantisa, frac ia (significand) - exponent (exponent) Folosind formatul specific I80386, n limbajul C se disting trei tipuri de date reale: - float , cu reprezentare pe 4 octe i (32 bi i, double word) - double, cu reprezentare pe 8 octe i (64 bi i, quad word) - long double, cu reprezentare pe 10 octe i (80 bi i, ten word)
MSB b31 b30
b0 LSB
S 31 30 S Exponent biased 23 22 Exponent = 8b Bias = 7FH=127 Significand = 23b Significand
0 float
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
42
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 bi i aloca i pentru exponent i pentru mantis, precum i interpretarea bi ilor mantisei (significand). Semnul are alocat n toate formatele un singur bit: 0 pentru numere pozitive i 1 pentru numere negative. Mrimea cmpului exponent variaz cu formatul i valoarea sa determin c i bi i se mut la dreapta sau la stnga punctului binar. Cmpul significand este analogul mantisei n nota ia tiin ific. El con ine to ii bi ii semnificativi ai reprezentrii, deci bi ii semnificativi att ai pr ii ntregi ct i ai pr ii frac ionare cu singura restric ie ca aceti bi i s fie consecutivi. Deoarece punctul binar este mobil, cu ct sunt mai mul i bi i aloca i pr ii ntregi, cu att vor fi mai pu ini pentru partea frac ionar i invers. Cu ct formatul este mai larg, cu att se vor reprezenta mai precis numerele. Pentru a salva un spa iu pre ios de stocare, nici unul dintre cele trei formate float nu stocheaz zerouri nesemnificative. De exemplu, pentru numrul 0.0000101 = 0.101x 2 4 cmpul significand va stoca numrul 101, nu i cele 4 zerouri nesemnificative ale pr ii frac ionare. Pentru a salva i mai mult spa iu, pentru formatele float i double cmpul significand nu va con ine primul bit semnificativ care obligatoriu este 1. Ctignd acest bit (numit bit phantom), se dubleaz gama de reprezentare. Formatul long double va con ine 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 bi ilor pornind de la MSB ctre LSB, cel mai mare fiind cel care are 1 la 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
43
Exponent = 10000010 = 13010 Significand = 100100000 Valoarea real a exponentului va fi 130 - 127 = 3 Bi ii cmpului significand se ob in 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 octe i (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 . 5 10 = 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 . 5 10 = C 1480000 16 Dac numrul 12.5 se reprezint n formatul double, deci pe 8 octe i, atunci reprezentarea sa intern se va realiza astfel: - bitul de semn va fi 0
44
exponentul nu va mai fi pe 8 bi i ca la tipul float, ci pe 11 bi i, deci se va schimba i bias, care va fi 1023. Atunci:
exponent 1
45
.10101
5.25 = 101.01 = .10101 23 10 2 1 ; 1.510 = 1.12 = .11 2 3+1 5.25 1.5 = [(.10101) (.11)] 2
4
nmax = 2128 = 28 210 = 256 (1024)12 256 1036 = 2.56 1038 Valoarea maxim exact, calculat fr a aproxima ca mai sus:
210 = 1024 1000 = 103 este n max = 3.4028 10 38 exponent_ biasmin = 0 exponent_ realmin = 0 127 = 127
12 12 13 nmin = 2127 = 27 210 = 23 210 210 = 8 210 8 1039
( )12
( )
( )
( )
Valoarea pozitiv minim exact este n min = 5.8775 10 39 La tipul double vom ob ine: exponent _ biasmax = 2047 exponent _ realmax = 2047 1023 = 1024
102 nmax = 21024 = 24 210 = 16 (1024)306 16 10306 = 1.6 10307
( )
46
( )
Valoarea pozitiv minim exact este n min = 1.1125 10 308 Efectund aceleai considera ii i calcule pentru tipul long double, 4932 n max = 1.1 10 vom ob ine nmin = 3.4 10 4932
47
Ca o concluzie la acest capitol, decisiv pentru n elegerea 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 ob in pe monitor sau la imprimant rezultatele oferite de calculator. Reprezentarea intern a numerelor se refer la modul n care se stocheaz datele n memoria RAM a calculatorului i respectiv n regitrii interni ai microprocesorului. Aceast reprezentare intern este legat de no iunea de tip de dat. Tipul de dat ntreg (integer) se reprezint intern pe 2, 4 sau 8 octe i n complement fa de 2, cu cel mai semnificativ bit (MSB) bit de semn: 1 pentru numere ntregi negative i 0 pentru numere ntregi pozitive. Un caz particular de dat de tip ntreg este tipul character, interpretat ca ntreg pe un octet. Tipul de dat real (float) se reprezint intern pe 4, 8 sau 10 octe i i con ine 3 cmpuri de bi i 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.
48
se ac ioneaz 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 execu ie a programului executabil mesaj.exe tastnd <CTRL>-F9; pentru a vizualiza rezultatele execu iei programului se tasteaz <Alt>-F5; se revine n fereastra de editare a mediului ac ionnd 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.
49
La fel de simplu poate fi utilizat i varianta pentru sistemul de operare WINDOWS a compilatorului BORLANDC v3.1: se lanseaz n execu ie programul bcw.exe; se selecteaz din meniul File op iunea New, crendu-se fiierul noname00.cpp; n fereastra noname00.cpp se introduce codul programului; din meniul File se selecteaz op iunea Save As sau Save iar n csu a de dialog care apare se va salva fiierul program cu extensia .cpp; din meniul Compile se selecteaz op iunea Build All ce va afia caseta de dialog Compiling (compilare); dac opera ia 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 execu ie a fiierului executabil se poate realiza folosind op iunea Run din meniul Run sau combina ia de taste <CTRL>-F9. Fiecare limbaj de programare are un set de reguli, denumite reguli sintactice, reguli ce trebuie respectate la editarea unui cod surs. Dac este 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 con ine eroarea, precum i o scurt descriere a erorii. n exemplul urmtor programului i lipsete caracterul punct i virgul dup utilizarea func ie printf:
#include <stdio.h> void main (void) { printf("Primul program in C!") }
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 instruc iunea respectiv.
50
} unde: - tip reprezint tipul de dat returnat de func ie (n mod implicit o func ie returneaz tipul int); - nume_func ie reprezint numele sub care func ia este cunoscut n program; - param_1,...,param_n - parametrii cu care func ia este apelat i al cror tip poate fi declarat direct n aceast list, sau prin instruc iuni separate plasate imediat dup lista parametrilor. Corpul func iei este definit ca o secven de instruc iuni i/sau apeluri de func ii i este delimitat de restul func iei prin paranteze acolade. n limbajul C exist dou categorii de func ii. O prim categorie este format de func iile ce returneaz o valoare la revenirea din ele n punctul de apel, tipul acestei valori fiind definit de de tipul func iei. Cealalt categorie con ine func iile ce nu returneaz nici o valoare la revenirea din ele , pentru aceste func ii fiind utilizat cuvntul cheie void n calitate de tip. El semnific lipsa unei valori returnate la revenirea din func ie. Exemple:
1) void f(void) { }
51
2) double g(int x) { }
Func ia g are un parametru x de tipul int i returneaz la revenirea n programul principal o valoare flotant n dubl precizie. Func iile C sunt n general unit i independente, compilabile separat. Instruc iunile, la rndul lor, pot defini tipul unor date folosite n program, sau opera ii ce trebuie executate prin program. Din punct de vedere sintactic, orice instruc iune trebuie terminat cu caracterul ";", iar grupurile de instruc iuni pot fi delimitate prin caracterele { i } pentru a forma unit i sintactice noi de tip bloc. Func iile apelate vor primi valori pentru argumentele (parametrii) lor i pot returna ctre func ia apelant valori de un anumit tip. Cu aceste precizri generale, dac avem un program compus din dou func ii, i anume func ia principal i o func ie apelat f(), atunci structura acestuia va fi de forma:
tip main( ) { -------f( ); / * apelul -------} functiei f() * /
Functia
principala
___________
f( ) { }
Functia
f( )
Programul ncepe cu execu ia func iei main(). Aceasta func ie este folosit n general fr parametri. La rndul lor, func iile apelate pot fi scrise n limbaj C, sau realizate n alte limbaje de programare: asamblare, Fortran, Pascal etc.
52
53
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.
54
ac iuni precum carriage return i tab pe terminale i imprimante i pentru a furniza reprezentarea caracterelor care normal au n eles special, cum ar fi ghilimelele (). O secven escape const dintr-un backslash urmat de o liter sau combina ii de cifre. Setul complet de secven e escape cuprinde: \a caracterul BEL - activare sunet \b caracterul BS (backspace) - revenire cu un spa iu \f caracterul FF (form feed) - salt de pagin la imprimant \n caracterul LF (line feed) - 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. Secven ele \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
Numai cifrele octale (de la 0 la 7) pot apare ntr-o secven escape octal i trebuie s apar cel pu in o cifr. De exemplu, caracterul backspace poate fi scris ca \10 n loc de \010. Similar, o secven hexagesimal poate s con in cel pu in o cifr, iar a doua cifr poate fi omis. Totui, cnd se utilizeaz secven e escape n iruri, este indicat s se scrie toate cele trei cifre ale secven ei. Altfel, caracterul care urmeaz dup secven a escape ar putea fi interpretat ca o parte a secven ei, dac se ntmpl s fie o cifr octal sau hexagesial. De exemplu, secven a \0331 este interpretat drept ESC i 1. Dac am scrie \331, omi nd primul zero, atunci am avea o interpretare greit.
55
Secven ele escape permit caractere de control negrafice pentru a fi transmise ctre display. Caracterele negrafice trebuie totdeauna reprezentate ca secven e escape. Plasnd necorespunztor un caracter negrafic n programe C, el are rezultat imprevizibil.
3.4. Identificatori
Identificatorii sunt nume ce sunt date variabilelor, func iilor i etichetelor utilizate n program. Un nume este o succesiune de litere i eventual cifre, primul caracter fiind liter. n calitate de litere se pot utiliza literele mici i mari ale alfabetului englez, precum i caracterul subliniere (_). Numrul de caractere care intr n componen a unui nume nu este limitat. Numele sunt utilizate pentru a defini diferite variabile sau func ii ntr-un program C. n mod implicit, numai primele 32 de caractere dintr-un nume sunt luate n considerare, adic dou nume sunt diferite dac ele difer n primele 32 de caractere ale lor. Exemple de nume: a, b1, a1b2c3, Fs, _hG, Nume, nUME, Se recomand ca numele s fie sugestive, adic ele s sugereze pe ct posibil scopul alegerii lor sau a datei pe care o reprezint.
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
56
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 func ie.
57
Observa ie. n interiorul constantelor ntregi sau reale nu pot apare spa ii albe. De exemplu, 56.62 e - 17 nu este o constant n virgul mobil, ci sunt de fapt 4 atomi lexicali: 56.62, e, -, 17 i se va genera o eroare de sintax. Dac se 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 loca ie n memoria calculatorului. Caracterele dintr-un ir sunt stocate n memorie, iar valoarea numeric a constantei este adresa acestei memorii. n plus, compilatorul stocheaz caracterul null \0 la 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: 61 A Adresa+1 62 B Adresa+2 63 C Adresa+3 0 /0 Valoarea constantei ir ABC va fi Adresa, adic valoarea adresei loca iei n care se stocheaz primul caracter din ir. Ca o ultim remarc, vom face precizarea c din punctul de vedere al reprezentrii, constanta caracter A, spre exemplu, este
Adresa
58
diferit de consta ir A, care se stocheaz n memorie la o anumit adres i se termin cu caracterul null, deci are aloca i doi octe i. Fiecare constant ir con ine cu un caracter mai mult 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 func ia strlen() nu numr i terminatorul \0. n interiorul unui ir se poate folosi conven ia de nota ie cu \. Aceasta face posibil reprezentarea caracterului ghilimele (") i \ n interiorul unui ir. Cel mai frecvent caracter folosit este caracterul '\n'=newline (NL). De exemplu, instruc iunea:
printf ("beep at end of message\007\n");
determin scrierea unui mesaj, a caracterului BEL i a caracterului NL. O secven de forma \n ntr-un ir nu determin introducerea unui caracter NL n ir, ci este o simpl nota ie (\n este caracter 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 nota ia octal sau hexazecimal este recomandat s se foloseasc 3 cifre pentru numr. Exemplu:
char v1[] = "a\x0fah\0129";//'a' 'x0f' 'a' 'h' '\012' '9' char v2[] = "a\xfah\ 129"; /* 'a' 'xfa' 'h' '\12' '9' */ char v3[] = "a\xfad\127"; /* 'a' 'xfa' 'd' '\127' */
59
Deoarece nu i se poate atribui o valoare, o constant poate fi doar ini ializat. Declarnd ceva ca fiind constant, ne asigurm c valoarea sa nu se modific n domeniul su. Astfel instruc iunile:
model = 165; model++; /* eroare */ /* eroare */
vor determina apari ia unor mesaje de eroare corespunztoare. De notat c const modific un tip ceea ce nseamn c restric ioneaz felul n care se poate utiliza un obiect, i nu modul de alocare. Pentru o constant, compilatorul nu rezerv memorie deoarece i se cunoate valoarea (precizat la ini ializare). Mai mult, ini ializatorul 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;
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; }
determin compilatorul s ini ieze un avertisment deoarece sunt folosite numai dou din cele trei valori ale lui Key.
60
Exemple: 1. 6353 este o constant ntreag zecimal de tip int i reprezint un operand constant de tip int. 2. float x2 reprezint declara ia variabilei x2, iar numele x2 reprezint un operand de tipul float. 3. 0xa13d este o constant ntreag hexazecimal de tip unsigned i reprezint un operand de tipul unsigned. 4. produs(a,b) este un apel al func iei produs. Aceast func ie reprezint un operand al crui tip coincide cu tipul valori returnate de func ia produs.
4.2. Operatori
Operatorii pot fi unari sau binari n func ie 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
61
binar se aplic la operandul care l precede imediat i la care l urmeaz imediat. Operatorii limbajului C nu pot avea ca operanzi constante ir (iruri de caractere). C are mai multe clase generale de operatori: aritmetici, rela ionali i logici, operatori pentru prelucrare bi i, precum i c iva operatori speciali pentru sarcini particulare. La scrierea unei expresii se pot utiliza operatori din toate clasele. La evaluarea unei astfel de expresii este necesar s se in seama de priorit ile operatorilor care apar in diferitelor clase de operatori, de asociativitatea operatorilor de aceeai prioritate i de regula conversiilor implicite.
62
- expresia a=b=c=d-15
-15)));
- expresia a%-b*c
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). Preceden a tuturor operatorilor aritmetici este: nalt Sczut ++ -+ - (unari) * / % + - (binari)
63
egal diferit mai mic strict mai mic sau egal mai mare strict mai mare sau egal
Operatorii rela ionali au o preceden mai mic dect operatorii aritmetici, astfel o expresie de forma a < b + c este interpretat ca
a<(b+c).
Expresiile legate prin operatori logici binari sunt evaluate de la stnga la dreapta. Preceden a operatorilor logici i rela ionali 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 ()
64
Operatorii logici i rela ionali sunt utiliza i n formarea instruc iunilor repetitive precum i a instruc iunii if.
OR |
y 0 1
x 0 1 0 1 11
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 bi ilor corespunztori afla i n orice pozi ie. Din aceast cauz ei se mai numesc i operatori logici pe bit. Trebuie fcut distinc ia fa de operatorii logici, care folosesc nota ii dublate: &&, ||, sau !. Operatorii logici au aceleai denumiri, dar ei trateaz ntregul operator ca pe o singur valoare, adevrat sau fals. n scriere, se mai folosete i denumirea bit-and, bit-or, bit-negate sau exclusive-or. Ca exemplu, considerm opera ia bit-not. Fie numrul binar: N2 = 0000000000000111 = 0x0007 = 710 Negarea sa pe bit se realizeaz cu instruc iunea ~0x7 sau ~07 sau ~7 i valoarea sa va fi ~N2 = 1111111111111000 = 0xFFF8 sau 0177770 pe un computer cu ntreg pe 16 bi i sau 0xFFFFFFF8 pe un computer cu ntreg pe 32 de bi i. Exemplul 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
65
Deoarece limbajul C a fost gndit s nlocuiasc limbajul de asamblare n majoritatea opera iilor de programare, acesta trebuie s aib capacitatea s suporte to i (sau cel pu in mul i) operatorii utiliza i n asamblare. Operatorii pentru prelucrarea bi ilor se aplic bi ilor dintr-un byte sau 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 bi ilor utiliza i n C sunt: & | ^ ~ >> << AND OR exclusive OR (XOR) complement fa de unu (NOT) deplasare dreapta deplasare stnga
Opera iile pe bi i sunt utilizate de obicei n drivere, pentru testarea i mascarea anumitor bi i. De exemplu, opera ia AND poate fi folosit pentru tergerea unor bi i dintr-un byte sau dintr-un cuvnt, OR poate fi folosit pentru setarea unor bi i, iar XOR pentru complementarea unor bi i i testarea egalit ii a 2 bytes. Observa ie: Operatorii rela ionali i logici (&&, ||, !,...) produc totdeauna un rezultat care este fie 0, fie 1, pe cnd operatorii similari destina i prelucrrii bi ilor pot produce orice valoare arbitrar, n concordan cu opera ia specific. Operatorii >> i << deplaseaz to i bi ii dintr-o variabil la dreapta, respectiv la stnga. Forma general a opera iilor de deplasare este: variabil >> numr_de_pozi ii_bit - pentru deplasare dreapta. variabil << numr_de_pozi ii_bit - pentru deplasare stnga. n pozi iile rmase libere, dup deplasare, se introduc zerouri. Opera iile de deplasare pot fi utile cnd se decodific perifericele de intrare cum ar fi convertoarele D/A (digital/analogice) i cnd se citesc informa ii de stare. Operatorii de deplasare se pot utiliza i pentru realizarea cu rapiditate a opera iilor de nmul ire i mpr ire. Se tie c o deplasare stnga cu 1 bit realizeaz nmul irea cu 2, iar o deplasare dreapta cu 1 bit realizeaz o mpr ire cu 2. Exemplu:
x = 7; 0 0 0 0 0 1 1 1 7
66
x x x x x
1; 3; 2; 1; 2;
0 0 1 0 0
0 1 1 1 0
0 1 0 1 0
0 1 0 0 1
1 0 0 0 1
1 0 0 0 0
1 0 0 0 0
0 0 0 0 0
14 112 192 96 24
Dei limbajul C nu con ine un operator de rotire, se poate realiza o func ie care s efectueze aceast opera ie. De exemplu rotirea la stnga cu o pozi ie 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
67
O posibilitate de realizare a opera iei 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;
Att ntregul i ct i cele dou caractere ch[0] i ch[1] partajeaz primii doi octe i din cei 4 rezerva i de uniune. Numrul de rotit se introduce (pe 8 bi i) n ch[0]. Se rotete apoi ntregul i (deci se rotesc to i cei 4 octe i care i corespund). Se testeaz MSB al lui ch[0] care se gsete n urma rotirii n pozi ia LSB din ch[1]. Dac este 1, atunci se seteaz la 1 LSB din ch[0], realizndu-se astfel opera ia de rota ie. Un exemplu de program care s utilizeaze aceast func ie:
# include <stdio.h> union rotate { char ch[1]; unsigned int i; } rot; void disp_binary(); void rotate_bit(); void main() { register int t; rot.ch[0]=147; for (t=0;t<7;t++) { disp_binary(rot.i); rotate_bit(&rot);}} /* se defineste functia rotate_bit() */ void rotate_bit(union rotate *rot) {rot->ch[1]=0; rot->i=rot->i<<1; if (rot->ch[1]) rot->i=rot->i|1;} /* se defineste functia disp_binary() */ void disp_binary(int i) {register int t; for (t=128;t>0;t=t/2) if (i&t) printf("1"); else printf("0"); printf("\n");}
68
Programul de mai sus func ioneaz pentru numere reprezentabile pe un octet (mai mici de 255). Dac dorim s facem o rotire pe doi octe i, atunci se poate modifica programul de mai sus dup cum urmeaz:
# include <stdio.h> union rotate { char ch[3]; unsigned int i; } rot; void disp_binary(); void rotate_bit(); void main() { register int t; rot.i=17843; for (t=0;t<7;t++) { disp_binary(rot.i); rotate_bit(&rot);}} /* se defineste functia rotate_bit() */ void rotate_bit(union rotate *rot) {rot->ch[2]=0; rot->i=rot->i<<1; if (rot->ch[2]) rot->i=rot->i|1;} /* se defineste functia disp_binary() */ void disp_binary(int i) {register int t; for (t=32768;t>0;t=t/2) if (i&t) printf("1"); else printf("0"); printf("\n");}
Operatorul " ~ " realizeaz complementul fa de 1. O utilizare interesant a complementului fa de 1 este aceea c ne permite s vedem setul caracterelor extinse implementate n calculator:
# include <stdio.h> # include <conio.h> void main() {char ch; do {ch = getch(); printf ("%c %c\n", ch, ~ch);} while (ch != 'q');}
69
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. Majorit ii operatorilor binari le corespund operatori de atribuire de forma "op = " unde op poate fi : +, -, *, %, <<, >>, &, ^ , Operatorii de atribuire (asignare) sunt:
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.
70
void main(){ printf("\nTip printf("\nTip int)); printf("\nTip printf("\nTip printf("\nTip printf("\nTip printf("\nTip double));}
caracter pe %d octet",sizeof(char)); short int pe %d octeti",sizeof(short int pe %d octeti",sizeof(int)); long int pe %d octeti",sizeof(long int)); float pe %d octeti",sizeof(float)); double pe %d octeti",sizeof(double)); long double pe %d octeti\n", sizeof(long
n urma execu iei 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
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;
71
n alctuirea expresiilor din declara ia operatorului ternar " ? " pot fi folosite i func ii: Exemplu:
# include <stdio.h> f1(); f2(); // prototipurile functiilor f1() si f2() void main() { int t; printf (": "); scanf("%d",&t); // se introduce numarul intreg t t?f1()+f2(t): printf(" S-a introdus zero\n");} f1() {printf ("S-a introdus "); } f2(int n) {printf ("%d\n", n);}
Dac se introduce zero, atunci va fi apelat printf() i va afia " S-a introdus zero". Dac se introduce alt numr, atunci programul va executa att func ia f1(), ct i func ia f2().
are c efect atribuirea valorii 4 variabilei x. 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. Observa ie 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 for are 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 construc ie de forma: (tip) operand Printr-o astfel de construc ie valoarea operandului se convertete spre tipul indicat n paranteze. n construc ia de mai sus (tip) se consider c este un operator unar. Acest operator este
72
cunoscut sub numele de operator de for are a tipului sau de conversie explicit. De cele mai multe ori ns este utilizat denumirea englez a operatorului i anume expresie cast. Exemplu: Presupunem c o func ie oarecare f are un parametru de tip double. Pentru ca aceast func ie s poat fi apelat cu un parametru int n (n este un parametru de tip ntreg) acesta trebuie mai 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 for are a tipului fiind unar, are aceeai prioritate ca i ceilal i operatori unari ai limbajului C.
sunt eronate. La apelul unei func ii, lista parametrilor efectivi se include ntre paranteze rotunde. n acest caz se obinuiete s se spun c parantezele rotunde sunt operatori de apel de func ie. 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
73
memorie alocat unei date. n forma cea mai simpl, acest operator se utilizeaz n construc ii de forma: &nume
unde nume este numele unei variabile simple sau al unei structuri. n cazul n care nume este numele unui tablou, atunci acesta are ca valoare chiar adresa de nceput a zonei de memorie alocat tabloului respectiv i, n acest caz, nu se mai utilizeaz operatorul adres &.
74
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.
Preceden ele operatorilor C sunt prezentate n tabelul urmtor. Operatorii afla i pe aceeai linie au aceeai prioritate. Ei se asociaz de la stnga la dreapta, exceptnd operatorii unari, condi ionali i de atribuire, care se asociaz de la dreapta la stnga.
Preceden a nalt Operatorul () [ ] -> . ! ~ ++ -- - (type) * & sizeof * / % + << >> < <= > >= == != & ^ | && || ?: = += -= *= /= ,
Sczut
75
Limbajul C posed un set variat de instruc iuni, set care i permite s realizeze principalele compuneri de opera ii: secven ierea, repeti ia cu test final, repeti ia cu test ini ial, repeti ia cu numr cunoscut de pai, decizia i selec ia, saltul necondi ionat, ieirea prematur dintr-un ciclu. Instruc iunile pot fi clasificate n: instruc iuni etichetate, instruc iuni expresie, instruc iuni compuse, instruc iuni de selec ie, instruc iuni repetitive, instruc iuni de salt.
Eticheta format dintr-un identificator definete identificatorul ca destina ie pentru o instruc iune de salt, singura utilizare a sa fiind ca destina ie a unei instruc iuni goto. Etichetele nu pot fi redeclarate. Etichetele sunt locale n corpul func iei n care sunt definite. Instruc iunea goto are urmtorul format:
goto etichet
La ntlnirea instruc iunii goto, se realizeaz un salt la instruc iunea prefixat de eticheta aflat dup instruc iunea goto. Deoarece o etichet este local n corpul unei func ii rezult c ea este nedefinit n afara corpului func iei respective, deci, o instruc iune goto nu poate face salt la o instruc iune din afara corpului func iei n care este definit. Nu este recomandat utilizarea abuziv a acestei instruc iuni deoarece programul devine mai pu in lizibil i pot apare erori logice n program foarte greu de detectat. Instruc iunea goto se utilizeaz n special pentru ieirea din mai multe cicluri imbricate. Exemplu: Urmtorul program utilizeaz instruc iunea goto pentru a afia numerele de la 1 la 100:
76
#include <stdio.h> void main(void) { int nr=1; eticheta: printf(%d, nr++); if (nr<=100) goto eticheta; }
Dac expresia expresie de mai sus lipsete, construc ia ; se numete instruc iune vid. Aceasta nu are nici un efect, dar este utilizat pentru a nlocui un corp vid al unei itera ii sau pentru a plasa o etichet.
O instruc iune compus se numete bloc. Un bloc ne permite s tratm mai multe instruc iuni ca pe una singur. Corpul unei func ii este o instruc iune compus. Domeniul de vizibilitate al unui identificator declarat ntr-un bloc se ntinde din punctul declara iei pn la sfritul blocului. Identificatorii utiliza i ntr-un bloc pot fi ascuni prin declara ii de acelai nume n blocurile interioare blocului ini ial. Exemplu:
# include <stdio.h> int x = 34; /* x este global */ void main(void) { int *p = &x; /*p preia adresa variabilei globale*/ int x1, x2; printf("x = %d\n", x);
77
{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); }
unde conditie este orice expresie care prin evaluare conduce la o valoare ntreag. Dac valoarea expresiei este diferit de zero (condi ie adevrat), atunci se execut instructiune1; altfel, dac valoarea expresiei este zero (condi ie fals), se execut instructiune2. n ambele cazuri, dup executarea lui instructiune1 sau instructiune2, controlul este transferat la instruc iunea ce urmeaz dup if. Aici, prin instructiune1 sau instructiune2 se n elege o instruc iune simpl, o instruc iune compus (un bloc) sau o instruc iune vid. Por iunea else instructiune2; este op ional, n acest fel putnduse ob ine o structur de selec ie 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 instruc iune if este op ional, apare o ambiguitate atunci cnd else este omis dintr-un if inclus (ncuibat).
78
n C acest lucru se rezolv prin asocierea lui else cu cel mai apropiat if. De exemplu, n secven a:
if (x) if (y) else printf ("1"); printf ("2");
else este asociat cu instruc iunea if(y). Dac dorim ca else s fie asociat cu if(x) trebuie s utilizm acolade, astfel:
if (x) { if (y) printf ("1"); } else printf ("2");
n acest caz, condi iile sunt testate n ordine. Dac una din ele este adevrat, atunci este executat instruc iunea corespunztoare, dup care controlul este transferat la instruc iunea urmtoare din program. Codul pentru fiecare alternativ poate fi format dintr-o instruc iune simpl (inclusiv instruc iunea vid) sau dintr-un bloc delimitat prin { i }. Dac nici una dintre expresii nu este adevrat, atunci se execut secven a corespunztoare ultimei alternative introdus prin else. Aceast ultim alternativ nu este obligatorie, structura putndu-se ncheia dup secven a notat cu instructiuneN.
79
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); 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); }
80
Instruc iunea switch realizeaz transferul controlului la una din secven ele de instruc iuni dac valoarea variabila ce trebuie s aib tipul ntreg coincide cu una din constantele de dupa case. Secven a de instruc iuni se execut pna se ntlnete break, dup care se trece la instruc iunea imediat urmtoare dup switch. Dac nu se gsete nici o coinciden , se execut secven a de instruc iuni de dup default, iar dac default lipsete, deoarece prezen a acesteia este op ional, se trece la instruc iunea urmtoare.
Observa ie: Constantele case trebuie sa fie distincte. Pentru a iei din instruc iunea switch se folosete instruc iunea break. Exemplu:
# include <stdio.h> void main (void) { int t; for (t = 0; t < 10; t++) switch (t) { case 1 : printf ("Now"); break; case 2 : printf (" is "); break; case 3 :
81
case 4 : printf (" printf (" break; case 5 : case 6 : printf (" break; case 7 : case 8 : case 9 : printf (" break; }
to ");
. "); }
Instruc iunea switch este foarte eficient n scrierea programelor care afieaz pe ecran o list de op iuni (un meniu) din care utilizatorul alege cte una i o execut. Instruc iunile switch pot fi i incluse (ncuibate) una n alta.
- initializare este o instruc iune de atribuire utilizat pentru ini ializarea variabilei de control a ciclului. Nu exist nici o restric ie privitoare la tipul su; - condi ie este o expresie rela ional care se testeaz naintea fiecrei itera ii: dac condi ia este adevrat (diferit de 0), ciclul se continu; dac condi ia este fals (egal cu 0), instruc iunea for se ncheie; - incrementare se evalueaz dup fiecare itera ie specificnd astfel reini ializarea 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); }
unde:
82
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 restric ii n incrementarea sau decrementarea variabilei de control a ciclului. 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 instruc iune din declara ia ciclului for poate fi o instruc iune simpl sau un bloc (un grup de instruc iuni delimitate de acolade) care va fi executat repetitiv. Exemplu: Programul 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); } }
Instruc iunile 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)
83
int l, n, i; char sir[81]; puts ("Tastati un sir terminat cu <CR> : "); gets (sir); l = strlen (sir); for (n = 0; n <= l; ++n) { for (i = 0; i < n; ++i) putchar (sir[i]); putchar ('\n'); } }
Variante ale ciclului for: Limbajul C permite mai multe variante ale ciclului for care determin creterea flexibilit ii acestuia n diferite situa ii. Una din cele mai utilizate variante const n folosirea a mai multe variabile de control a ciclului. n exemplul 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 ini ializrile i incrementrile celor dou variabile sunt separate prin virgul. Terminarea ciclului presupune testarea nu numai a variabilei de control cu anumite valori prestabilite, ci condi ia de terminare a ciclului poate fi orice expresie C corect. Exemplu: Considerm un program pentru antrenarea unui copil n exerci iile 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 */ } }
84
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 op ionali. 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 instruc iunea de incrementare a lui x lipsete, de fiecare dat cnd ciclul se repet, programul testeaz ca x s fie egal cu 123, dar nu modific pe x n nici un fel. Dac de la tastatur se introduce 123, condi ia buclei devine fals i ciclul se termin. Exemplu: O variant de calcul a lui n! ar fi 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 ob ine o bucl fr sfrit, ca n exemplul urmtor n care se consider c elementul condi ie 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 instruc iunea break care se plaseaz oriunde n corpul ciclului i determin ncheierea imediat a ciclului (la ntlnirea acesteia), programul continundu-se cu instruc iunea ce urmeaz dup instruc iunea for.
85
Utilizarea ciclurilor for fr corp (instruc iune) : Pentru crearea unor ntrzieri de timp se pot folosi cicluri for cu corp vid de forma:
for (t = 0; t < O_ANUMITA_VALOARE; t++);
Observa ie: De obicei, instruc iunea for este legat de parcurgerea unor structuri de date de tip tablou.
unde instructiune poate fi o instruc iune vid, o instruc iune simpl sau un bloc de instruc iuni ce vor fi executate repetitiv. n timpul execu iei se evalueaz mai nti condi ia buclei a crei valoare trebuie s fie ntreag. Dac valoarea calculat este diferit de 0 (condi ie adevrat), atunci instructiune se execut. Dac, dup o evaluare (inclusiv prima) rezult o valoare 0 (condi ie fals), atunci controlul este transferat la instruc iunea ce urmeaz dup while. Astfel, instruc iunea asociat cu while se execut repetat, ct timp valoarea asociat condi iei este diferit de 0 sau condi ia 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 instruc iunea while realizeaz testarea condi iei la nceputul instruc iunii, aceasta instruc iune este bun de utilizat n situa iile n care nu se dorete execu ia buclei, evident dac condi ia nu este adevrat.
86
centrarea
Dac dorim s programm un ciclu infinit, atunci se poate gsi o expresie care ramne tot timpul adevrat. Un exemplu uzual este urmtorul:
while (1) { Corpul ciclului }
Ieirea din ciclu, n acest caz, se asigur prin mecanisme de tip break, goto sau return. Corpul ciclului while poate con ine i numai instruc iunea vid. De exemplu,
while ((ch = getche ()) != 'A');
este o bucl simpl care se execut pn cnd de la tastatur se va introduce caracterul "A". Observa ie: Instruc iunea while reprezint mecanismul sintactic de baz pentru a programa cicluri n C. Reamintim c instruc iunea for se folosete dup urmtorul format general: for (initializare; conditie; incrementare) instructiune; care este echivalent semantic cu secven a: initializare; while (conditie) { instructiune; incrementare; }
87
de instruc iuni ce reprezint corpul ciclului. Forma general a buclei do-while este:
do { instructiune; } while (conditie);
Dei acoladele nu sunt necesare cnd instructiune este o instruc iune simpl, de obicei se utilizeaz pentru a evita confuzia cu while. Se remarc faptul c instructiune ce reprezint corpul ciclului (adic, o instruc iune simpl, o instruc iune compus sau o instruc iune vid) este executat cel pu in odat. Celelalte execu ii sunt condi ionate de valoarea ntreag rezultat din evaluarea condi iei. Dac aceast valoare este 0 (condi ie fals), atunci controlul se transfer la urmtoarea instruc iune din program; n caz contrar se execut corpul ciclului i se reevalueaz condi ia. 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); }
Un caz tipic de utilizare a instruc iunii do-while este oferit de programele interactive n care selec ia unei op iuni se face pe baza unui meniu 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':
88
verifica_ortografia(); break; case '2': corecteaza_erorile(); break; case '3': afiseaza_erorile(); break; } } while (ch != '1' && ch != '2' && ch != '3'); }
Dup afiarea op iunilor, programul va bucla pn cnd se va selecta o op iune valid. Exemplu: Adunarea elementelor a doi vectori:
int a[10], b[10], c[10]; . . . . . . . . . . . . . . i = 0; do { c[i] = a[i] + b[i]; i = i + 1; } while (i < 10);
sau
i = 0; do { c[i] = a[i] + b[i]; i++; } while (i < 10);
89
Alinierea rezultatelor se datorete utilizrii n printf() a unui format de afiare corespunztor (%9d) care precizeaz dimensiunea minim a cmpului specificat. Un alt exemplu, pu in mai complex, este un program de nmul ire a dou matrice. Evident, n acest caz vom avea 3 bucle for incluse una n cealalt. // Program de inmultire a doua matrici
# include <stdio.h> float a[100][100],b[100][100],c[100][100]; float elem, s; int la, ca, lb, cb, lc, cc, i, j, k; void main(void) { la=101; ca=101; lb=ca+1; cb=ca; printf("Program de inmultire a doua matrici\nSe declara dimensiunile fiecarei matrici\n\n"); /* Introducem pe rand dimensiunile fiecarei matrici. Verificam sa nu se depaseasca dimensiunile maxime si verificam posibilitatea inmultirii matricilor */ while (ca!=lb){ printf("Se verifica daca dimensiunile declarate sunt compatibile pentru inmultire!\n\n"); while ((la>=100)||(ca>=100)) { printf("Intoduceti dimensiunile primei matrice"); printf("\nNr. linii matrice A = \n"); scanf("%d",&la); printf("Nr. coloane matrice A = \n"); scanf("%d",&ca); } while ((lb>=101)||(cb>=101)) { printf("Intoduceti dimens. celei de-a doua matrice"); printf("\nNr. linii matrice B = \n"); scanf("%d",&lb); printf("Nr. coloane matrice B = \n"); scanf("%d",&cb); } if(ca!=lb) { la=101;ca=101; lb=ca+1;cb=ca;} } /* Se introduc matricile */ for(i=0; i<=la-1; i++) for(j=0; j<=ca-1; j++) { printf("a(%d,%d) = ", i, j); scanf("%f",&elem); a[i][j] = elem; } for(i=0;i<=lb-1;i++) for(j=0;j<=cb-1;j++) { printf("b(%d,%d) = ",i,j); scanf("%f",&elem);
90
b[i][j]=elem; } // Se calculeaza fiecare element al matricei produs for(i=0;i<=la-1;i++) for(j=0;j<=cb-1;j++) { s=0; for(k=0;k<=ca-1;k++) s = s+a[i][k]*b[k][j]; c[i][j] = s; } // Se afisaza matricile printf("\n\nA = \n"); for(i=0;i<=la-1;i++) { printf("\n"); for(j=0;j<=ca-1;j++) printf("%6.3f ",a[i][j]); } printf("\n\nB = \n"); for(i=0;i<=lb-1;i++) { printf("\n"); for(j=0;j<=cb-1;j++) printf("%6.3f ",b[i][j]); } printf("\n\nC = A*B\n"); for(i=0;i<=la-1;i++) { printf("\n"); for(j=0;j<=cb-1;j++) printf("%6.3f ",c[i][j]); }}
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)
91
int t; for (t = 0; t < 100; ++t) { count = 1; for (;;) { printf (" %d ", count); count++; if (count == 10) break; } }
va afia pe ecran numerele de la 1 la 10 de 100 de ori. Instruc iunea break se poate utiliza i n cadrul ciclurilor programate cu while sau do-while, schema general de utilizare fiind urmtoarea: while (expresie) { ................. if (conditie) break; ................. } Dac la una din itera ii, condi ia din if este ndeplinit, atunci ciclul se termin automat, altfel el poate continua pn cnd expresia din while are valoarea fals. Dac instruc iunea break se execut n cadrul unei instruc iuni switch, care la rndul ei este inclus ntr-un ciclu programat cu while, for sau do-while, atunci ea determin terminarea numai a instruc iunii switch, nu i ieirea din ciclu.
Se observ c atunci cnd se genereaz un numr impar se execut instruc iunea continue ce va determina trecerea la itera ia urmtoare by-pasnd instruc iunea printf(). n cazul instruc iunilor while i do-while, o instruc iune continue determin trecerea direct la testul condi ional i prin urmare, continuarea procesului de buclare. n cazul unui for, se realizeaz mai nti opera ia de incrementare a variabilei de control a ciclului, apoi testarea condi iei de continuare a buclei.
92
93
Pentru un tablou unidimensional, dimensiunea total, n bytes, a acestuia va fi: Total bytes = sizeof (tip) * lungimea_tabloului
Observa ie: Limbajul C nu realizeaz verificarea dimensiunilor unui tablou: astfel, nu exist nimic care s ne opreasc s nu trecem peste sfritul tabloului. Dac se trece peste sfritul unui tablou ntr-o opera ie de atribuire, atunci se vor atribui valori unor alte variabile sau chiar se vor distruge pr i 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 con ine numai 10 elemente. Aceste verificri rmn n sarcina exclusiv a programatorului. Tablourile unidimensionale sunt, de fapt, liste de informa ii 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) ch(4) ch(5) ch(6) A B C D E F G
6.1.1. Constante ir
n C o constant ir este o secven de caractere nchis ntre ghilimele. Exemplu: "acesta este un sir". Fiecare constant ir con ine cu un caracter mai mult 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, func ia 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 conven ia de nota ie cu \. Aceasta face posibil reprezentarea caracterelor " i \ n interiorul unui
94
ir. Cel mai frecvent caracter folosit este caracterul '\n' = new line (NL). De exemplu, instruc iunea: printf("beep at end of message \007 \n "); determin scrierea unui mesaj, a caracterului BEL i a caracterului NL. Nu este permis continuarea irurilor de caractere de pe o linie pe alta.
Exemplu: "this is not a string but a syntax error".
O secven de forma \n ntr-un ir nu determin introducerea unui caracter NL n ir, ci este o simpl nota ie. Este posibil s folosim caracterul null ntr-un ir, dar majoritatea programelor nu testeaz dac mai sunt caractere dup el.
6.1.2. Ini ializarea vectorilor de caractere Citirea unui ir de la tastatur utiliznd func iile scanf() i gets(). o Utilizarea func iei scanf(). Exemplu:
# include <stdio.h> void main (void) { char nume[21], adresa[41]; printf ("\n Nume: "); scanf ("%s", nume); printf ("\n Adresa: "); scanf ("%s", adresa); printf ("%s\n%s\n", nume, adresa); }
S-au definit variabilele nume i adresa ca tip ir de caractere de maximum 20 i 40 de caractere. irul "%s" care apare n apelul func iei scanf() precizeaz c se vor citi n variabilele nume, respectiv adresa, valori de tip ir de caractere. n printf() descriptorul "%s" are rolul de a preciza cum trebuie convertite valorile datelor de afiat (n cazul de fa , valorile variabilelor nume i adresa). Func ia scanf() citete un ir de caractere din bufferul de intrare pn cnd ntlnete un spa iu, 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 ob ine irul n ntregime este recomandat s se transmit numele sub forma: "ENE_ALEXANDRU".
o Cea mai bun cale de a introduce un ir de la tastatur const n utilizarea func iei gets() din fiierul antet "stdio.h". Forma general a func iei gets() este: gets (nume_vector)
95
Pentru a citi un ir se apeleaz gets() avnd ca argument numele vectorului, fr nici un index. Func ia gets() returneaz vectorul ce va 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 func ia printf() poate primi ca argument un ir de caractere. Dac se introduce un ir mai lung dect dimensiunea tabloului, vectorul va fi suprascris.
Vectorul nume va ocupa ncepnd de la adresa nume, 13 + 1 = 14 octe i, iar cel de-al doilea vector va ocupa ncepnd de la adresa adresa, 23 + 1 = 24 loca ii (bytes). Func ia puts() scrie pe stdout irul memorat n vectorul al crui nume apare ca parametru al func iei puts(), precum i caracterul "\n". De multe ori, n C se realizeaz ini ializarea unor vectori de caractere a cror dimensiune nu este precizat. Dac dimensiunea vectorului nu este precizat, compilatorul C va crea un vector suficient de lung nct s permit ini ializarea dorit. Exemplu: n loc s scriem :
char e1[12] = "read error\n"; char e2[13] = "write error\n"; char e3[18] = "cannot open file\n";
putem scrie:
char e1[ ] = "read error\n"; char e2[ ] = "write error\n";
96
va tipari:
write error are lungimea 13
o Ini ializarea unui vector (tablou unidimensional) se poate face i cu o list de ini ializatori scrii ntre acolade. Dac vectorul are o lungime necunoscut, numrul de ini ializatori determin mrimea tabloului, iar tipul devine complet. Dac tabloul are lungime fix, numrul de ini ializatori nu poate depi numrul de membri din tablou. n cazul n care sunt mai pu ini ini ializatori, membrii n plus sunt ini ializa i cu zero. Exemple: - Instruc iunea urmtoare ini ializeaz un vector de 10 ntregi cu numerele de la 1 la 10:
int i[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
Rezult c: i[0] = 1, ... , i[9] = 10. - Instruc iunea urmtoare declar i ini ializeaz vectorul x ca un tablou unidimensional cu 3 membri:
int x[] = {1, 2, 3};
Acest program va copia "hello" n irul sir. Func ia strcat() Apelul func iei strcat() are forma:
97
strcat (s1, s2); Func ia 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. Func ia strcmp() Se apeleaz sub forma: strcmp (s1, s2); Aceast func ie compar irurile s1 i s2 i returneaz valori negative, dac s1 < s2, 0, dac s1 = s2 i un numr pozitiv, dac s1 > s2. Exemplu: Aceast func ie poate fi folosit ca o subrutin de verificare a parolei:
# include <stdio.h> # include <string.h> void main (void) { char s[80]; printf ("Introduceti parola: "); gets (s); if (strcmp (s, "pasword")) { printf (" Invalid pasword \n "); return 0;} return 1; }
Func ia strlen() Func ia strlen() se apeleaz sub forma: strlen (s) unde s este un ir. Func ia strlen() returneaz lungimea irului s. Exemplu: Programul urmtor returneaz lungimea unui ir introdus de la tastatur.
# incude <stdio.h> # incude <string.h> void main (void) { char sir[80];
98
printf ("Introduceti un sir: "); gets (sir); printf ("Sirul %s contine %d strlen(sir)); }
caractere
",
sir,
Observa ie: Func ia 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 irurile sunt egale, func ia strcmp() returneaz fals (0) i din aceast cauz n instruc iunea if s-a folosit !strcmp().
Observa ie: Caracterul NULL de terminare a vectorului de caractere poate fi utilizat n buclele for ca n exemplul 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); }
99
Conversia caracterelor se face cu func ia toupper() care returneaz litera mare corespunztoare argumentului (literei mici). Ciclul func ioneaz pn cnd sir[i] devine caracterul null.
conduce la ini ializarea primului vector cu primele 5 litere, iar a celui de-al doilea cu primele 5 cifre. Exemplu: Programul:
# include <stdio.h> void main (void) { char v[2][5] = { 'a', 'b', 'c', 'd', 'e', '0', '1', '2', '3', '4' }; int i, j; for (i = 0; i < 2; i++){ for(j = 0; j < 5; j++) printf ("v[%d][%d] = %c", i, j, v[i][j]); printf ("\n"); } }
va produce :
v[0][0]=a v[0][1]=b v[0][2]=c v[0][3]=d v[0][4]=e v[1][0]=0 v[1][1]=1 v[1][2]=2 v[1][3]=3 v[1][4]=4.
conduce la ncrcarea tabloului num[3][4]cu numerele de la 1 la 12. Astfel, num[0][0] = 1, ..., num[2][3] = 12.
100
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:
este o ini ializare cu paranteze complete i are urmtorul efect: - numerele 1, 3, 5 ini ializeaz prima linie a tabloului: y[0][0], y[0][1], y[0][2] sau y[0]; - numerele 2, 4, 6 ini ializeaz pe y[1]; - numerele 3, 5, 7 ini ializeaz pe y[2]. ntruct ini ializatorul se termin cu virgul, elementele lui y[3] vor fi ini ializate cu 0. Acelai efect ar fi putut fi realizat de:
float y[4][3]={1, 3, 5, 2, 4, 6, 3, 5, 7, };
Secven a:
float y[4][3] = { {1}, {2}, {3}, {0}, };
ini ializeaz prima coloan a lui y, privit ca un tablou bidimensional, cu 1, 2, 3 i 0, restul tabloului fiind ini ializat cu 0.
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. Func ional, instruc iunea anterioar este echivalent cu:
gets (&sir_tablou[2][0]);
101
creeaz un tablou de 4*10*3 ntregi. Forma general de ini ializare 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.
Observa ie: Limbajul C permite i ini ializarea tablourilor multidimensionale fr dimensiune. Trebuie men ionat c pentru aceasta este necesar precizarea indicelui celui mai din dreapta. Astfel, declara ia:
int sqrs[5][2] = {1, 1, 2, 4, 3, 9, 4, 16, 5, 25};
6.4. Structuri
O structur este o colec ie de variabile (de tipuri diferite) referite sub un singur nume. Defini ia unei structuri formeaz un 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 defini iei se face cu ; (este unul din pu inele 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 distinc ie dintre structuraablon i variabila-structur. O variabil de tip structur se declar cu ajutorul ablonului structurii.
102
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; Secven a 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; . . . . . . . . . . . . . . } variabile_structura;
103
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, instruc iunea 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 con inutului 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]);
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.
104
Exemplu de program pentru actualizarea unei liste de coresponden a - maillist Pentru a ilustra modul de utilizare a structurilor i tablourilor de structuri prezentm un exemplu de program pentru actualizarea unei liste de coresponden . Informa iile ce vor fi memorate se refer la name, street, city, state, zip. Pentru definirea structurii de baz addr care va con ine aceste informa ii vom scrie:
struct addr { char name[20]; char street[30]; char city[15]; char state[10]; unsigned int zip; } addr_info[SIZE];
Tabloul addr_info contine SIZE structuri de tip addr, unde SIZE se definete dup necesit i. Prima func ie 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(); }}}
Func ia 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 ini ializare are loc n memoria intern a calculatorului (nu n fiierul maillist de pe disc). Structura func iei de initializare init_list() ar putea fi urmtoarea:
/* Functia init_list() */ void init_list() { register int t; for (t = 0; t < SIZE; t++) *addr_info[t].name = '\0';
105
Func ia de selectare a meniului menu() va afia mesajele op iunilor i va returna varianta aleas. Prin tastarea literei din paranteze, se va lansa n execu ie o anumit procedur.
/* Functia menu() */ char menu() { char s[5],ch; do { printf ("(E)nter\n"); printf ("(D)isplay\n"); printf ("(L)oad\n"); printf ("(S)ave\n"); printf ("(Q)uit\n"); printf (" Alegeti optiunea: "); gets(s); ch=s[0]; } while (!strrchr("edlsq",ch)); return tolower(ch); }
Observa ie: Func ia strrchr(cs,c) din <string.h> ntoarce un pointer la ultima apari ie a lui c n cs sau NULL dac nu apare. Func ia enter() are ca efect introducerea unor noi informa ii n 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 informa ii culese de la tastatur.
/* Functia enter() */ void enter() { register int i; for (i=0; i < SIZE; i++) if (!*addr_info[i].name) break; if (i == SIZE) { printf ("addr_info full \n"); /* Lista plina */ return;} printf ("Name: "); gets (addr_info[i].name); printf ("Street: "); gets (addr_info[i].street); printf ("City: "); gets (addr_info[i].city); printf ("State: "); gets (addr_info[i].state); printf ("Zip: "); scanf ("%d",&addr_info[i].zip);}
Rutinele save() i load() se utilizeaz pentru actualizarea bazei de date. Aceste rutine lucreaz cu fiierul disc maillist. Cnd se apeleaz load(), atunci se copiaz n tabloul structur din memorie
106
datele stocate n fiierul maillist. Func ia load() realizeaz opera iunea invers, de supranscriere a fiierului disc cu datele din memorie. Spre exemplu, dac dorim s adugm date la fiierul maillist care con ine deja date introduse anterior, ordinea de lansare a comenzilor va fi: init_list(), load(), enter(), save(), exit(). Structura acestor rutine este 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 opera ii cu fiiere prin verificarea valorilor ntoarse de func iile fread() sau fwrite(). n plus, load() trebuie s caute i indicatorul de sfrit de fiier utiliznd func ia feof() deoarece fread() ntoarce aceeai valoare dac se ntlnete indicatorul de sfrit de fiier sau dac apare o eroare. Func ia display() afieaz pe ecran ntregul tablou structur din memorie care con ine date valide:
/* Functia display() */ void display() { register int t; for (t=0;t<SIZE;t++) { if (*addr_info[t].name!='\0') { printf("%s\n",addr_info[t].name); printf("%s\n",addr_info[t].street); printf("%s\n",addr_info[t].city);
107
/* 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);
108
ch=s[0]; } while (!strrchr("edlsq",ch)); return tolower(ch); } /* Functia enter() */ void enter() { register int i; for (i=0; i < SIZE; i++) if (!*addr_info[i].name) break; if (i == SIZE) { printf ("addr_info full \n"); /* Lista plina */ return;} printf ("Name: "); gets (addr_info[i].name); printf ("Street: "); gets (addr_info[i].street); printf ("City: "); gets (addr_info[i].city); printf ("State: "); gets (addr_info[i].state); printf ("Zip: "); scanf ("%d",&addr_info[i].zip);} /* Functia save() */ void save() { register int i; if ((fp = fopen("maillist", "wb")) == NULL) { printf (" Cannot open file\n "); return;} for (i = 0; i <= SIZE; i++) if(*addr_info[i].name) if(fwrite(&addr_info[i], sizeof(struct addr), 1,fp) !=1) printf (" File write error \n "); fclose (fp);} /* Functia load() */ void load() { register int i; if ((fp = fopen("maillist","rb")) == NULL) { printf("Cannot open file\n "); return;} for (i = 0; i < SIZE; i++) if(fread(&addr_info[i],sizeof(struct ddr),1,fp)==1); else if (feof(fp)) { fclose (fp); return;} else printf ("File read error\n"); } /* Functia display() */ void display() { register int t;
109
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();}}}
//se utilizeaza valoarea caracterului lui s[2] unde func(), func2(), func3(), func4() sunt numele unor func ii. Pentru a transmite func iei func() adresele elementelor din structura struct2, se scrie astfel: func (&struct2.x); /* se paseaza adresa caracterului x */ func (&struct2.s[2]); /* se paseaza adresa caracterului s[2] */
110
b) Transmiterea unei ntregi structuri unei func ii Cnd o structur este utilizat ca argument al unei func ii, limbajul C transmite func iei ntreaga structur utiliznd metoda standard a apelului prin valoare. Aceasta nseamn c orice modificare asupra con inutului structurii n interiorul func iei n care este apelat structura, nu afecteaz structura folosit ca argument. Ceea ce trebuie avut 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; char ch;}; void main() { struct struct_tip arg; /* se declara structura arg */ arg.a = 1000; f1(arg);}
111
param)
/*
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 pozi ii 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 con in ca prim element irul respectiv iar cel de-al doilea element s fie lungimea acestuia. Se vor construi func ii care s realizeze citirea irurilor de la tastatur, scrierea lor pe display, respectiv s le ordoneze i s le sorteze dup paritate. Toate aceste func ii comunic prin intermediul unei variabile globale de tip structur i a mai multor variabile locale de tip structur. Programul n C este prezentat n continuare:
# include <stdio.h> // definim prototipurile functiilor utilizate struct sir_lung cit_sir(); struct sir_lung concat_sir(); struct sir_lung ord_sir(); struct sir_lung par_sir(); struct sir_lung impar_sir(); void tip_sir(); /* se defineste structura sir+lungime si variabila globala sir */ struct sir_lung { int sir[80]; int lung;} sir; // programul principal void main(){ struct sir_lung sir_init,sir_ord,sir_par,sir_impar; sir_init=cit_sir(); getchar(); sir_ord=cit_sir(); sir_init=concat_sir(sir_init,sir_ord); sir_par=par_sir(sir_init); sir_par=ord_sir(sir_par); sir_impar=impar_sir(sir_init); sir_impar=ord_sir(sir_impar); sir_ord=concat_sir(sir_par,sir_impar);
112
tip_sir(sir_init); tip_sir(sir_ord);} // se definesc functiile struct sir_lung cit_sir() {int result=1, i=0; sir.lung=0; while (result) { result=scanf("%d",&sir.sir[i]); i++;} sir.lung=--i; return sir;} void tip_sir(struct sir_lung sirf) { int i; for (i=0;i<sirf.lung;i++) printf("%d ",sirf.sir[i]);printf("\n");} struct sir_lung concat_sir(struct sir_lung sir1, struct sir_lung sir2) { int i; struct sir_lung sir_concat; sir_concat=sir1; for (i=0;i<sir2.lung;i++) sir_concat.sir[sir1.lung+i]=sir2.sir[i]; sir_concat.lung=sir1.lung+sir2.lung; return sir=sir_concat;} struct sir_lung ord_sir(struct sir_lung sir1) {int i,j,temp; for (i=0;i<sir1.lung;i++) for (j=i+1;j<sir1.lung;j++) if (sir1.sir[i]>sir1.sir[j]) {temp=sir1.sir[i];sir1.sir[i]=sir1.sir[j]; sir1.sir[j]=temp;} return sir=sir1;} struct sir_lung par_sir(struct sir_lung sir1) { int i,j=0; struct sir_lung sir_par; for (i=0;i<sir1.lung;i++) if (!(sir1.sir[i]%2)) {sir_par.sir[j]=sir1.sir[i];j++;} sir_par.lung=j; return sir=sir_par;} struct sir_lung impar_sir(struct sir_lung sir1) { int i,j=0; struct sir_lung sir_impar; for (i=0;i<sir1.lung;i++) if (sir1.sir[i]%2) {sir_impar.sir[j]=sir1.sir[i];j++;} sir_impar.lung=j; return sir=sir_impar;}
113
Din func ia cit_sir() se poate iei prin tastarea oricarui caracter nenumeric. Se observ cum aceasta func ie lucreaz direct cu variabila global ir iar celelalte cu variabile locale proprii care apoi sunt asignate variabilei globale ir la returnare. Rulnd programul vom ob ine rezultatele:
1 2 17 -3 6 -4; -9 -2 31 2 -7; 1 2 17 -3 6 -4 -9 -2 31 2 -7 -4 -2 2 2 6 -9 -7 -3 1 17 31
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 con ine adresa salariatului; al doilea element este salariul sptmnal al acestuia. Instruc iunea urmtoare va atribui codul 90178 variabilei "zip" din adresa muncitorului muncitor.adresa.zip = 90178; 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.
114
Exemplu:
union tip_u { int i; char ch;};
O variabil de acest tip poate fi declarat fie prin plasarea numelui su la sfritul acestei defini ii, fie utiliznd o instruc iune de declarare separat. De exemplu, instruc iunea : 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 octe i, 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 func ii :
func1(union tip_u *un) { un -> i = 10; }
/* se atribuie valoarea 10 intregului i al uniunii exuniune */ In C, uniunile sunt des utilizate cnd sunt necesare conversii de tip. De exemplu, func ia 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:
union pw { int i; char ch[2];};
115
6.6. Enumerri
O enumerare este o mul ime 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 op ionale. 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;
Trebuie precizat c fiecare enumerator este caracterizat printr-o valoare ntreaga. Fr nici o alt ini ializare, valoarea primului enumerator este 0, a celui de-al doilea este 1, s.a.m.d. De aceea, instruc iunea: printf ("%d %d, suta, mie); va afisa pe ecran: 0 3 Se pot specifica valorile unuia sau mai multor simboluri folosind ini ializatori. De exemplu: enum bancnota {suta, douasute, cincisute, mie=1000, cincimii, zecemii}; face ca simbolurile din enumerarea bancnota s aib valorile:
suta = 0 douasute = 1 cincisute = 2 mie = 1000 cincimii = 1001 zecemii = 1002
116
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 func iona numai dac nu se realizeaz ini ializarea 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.
# include <stdio.h> union { char ch; int i; double f; } y; void main() {printf("%d\n",sizeof(y));}
Deci compilatorul va re ine valoarea celei mai largi tipuri de date din uniune.
117
118
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 por iunea de program: short i, j; // i si j sunt ambele intregi scurti short *p // p este pointer la tip intreg scurt i = 123; p = &i; j = *p; }
Memoria
Con inutul variabilei p (de tip pointer) va fi valoarea 1200, adic adresa variabilei i. Instruc iunea j = *p; copiaz un ntreg scurt de la loca ia 1200 n j, loca iile de memorie fiind acum ca cele din figur:
Adresa i j p 1200 1202 1204 Memoria 123 123 1200
119
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.
Se observ c n func ia printf() tiprirea se face cu formatul %p care specific faptul c variabilele din list vor fi afiate ca adrese pointer.
120
Din cele de mai sus se observ c opera ia fundamental care se execut asupra unui pointer este indirectarea, ceea ce presupune referirea la un obiect indicat de acel pointer. Exemplu:
char c1 = 'a'; char *p = &c1; /* p contine adresa lui c1 */ char c2 = *p; /*c2 preia valoarea de la adresa p*/ printf ("c1 = %c c2 = %c", c1, c2);
Deci variabila indicat de p este c1, iar valoarea memorat n c1 este 'a', aa nct valoarea lui *p atribuit lui c2 este 'a'. Opera ii aritmetice efectuate asupra pointerilor o Utilizarea operatorilor de incrementare i decrementare Fie secven a:
int *p1; p1++; /* pointer la intreg */
De fiecare dat cnd se incrementeaz p1, acesta va indica spre urmtorul ntreg. Astfel, dac p1 = 2000, dup efectuarea instruc iunii 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 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 opera ii de atribuire. Verifica i utilizarea pointerilor din programul urmtor:
121
# 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);
La sau dintr-un pointer, se pot aduna sau scdea valori de tip ntreg. Rezultatul este un pointer de acelai tip cu cel ini ial, indicnd spre un alt element din tablou. De exemplu,
p1 = p1 + 9;
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;
Aceleai considerente sunt valabile n cazul n care un ntreg este sczut dintr-un pointer. Dac doi pointeri de acelai tip sunt sczu i, 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.
122
Dac p indic spre ultimul membru dintr-un tablou, atunci (p+1) are valoare nedeterminat. Observa ii : Nu se pot aduna sau scdea valori de tip float sau double la/sau dintr-un pointer. Nu se pot efectua opera ii de nmul ire i mpr ire 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); }
Doi pointeri de acelai tip se pot compara printr-o expresie rela ional, astfel: dac p i q sunt doi pointeri, atunci instruc iunile:
if (p < q) printf ( p indica spre o adresa mai mica decit q \n );
o Compararea pointerilor
sunt corecte. Compararea pointerilor se utilizeaz cnd doi sau mai mul i pointeri indic spre acelai obiect comun. Exemplu: Un exemplu interesant de utilizare a pointerilor const n examinarea con inutului loca iilor de memorie ale calculatorului.
/* Programul afiseaza continutul locatiilor de memorie de la o adresa specificata */ # include <stdio.h> # include <stdlib.h> dump (start); void main (void) { /* start = adresa de inceput */ unsigned long int start; printf (Introduceti adresa de start: ); scanf ( %lu , &start); dump (start); /* Se apeleaza functia dump () */ } dump (start) /* Se defineste functia dump() */ unsigned long int start; {char far *p; int t; p = (char far *) start; /*Conversie la un pointer*/ for (t = 0; ; t++, p++) { if (!(t%16)) printf ("/n"); printf ("%2X ", *p);
123
/*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 loca ii 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. Func ia kbhit() returneaz ADEVARAT dac se apas o tast, altfel returneaz FALS. n exemplele de pn acum, s-au folosit func ii C care atunci cnd erau apelate, parametrii acestor func ii erau (ntotdeauna) actualiza i prin pasarea valorii fiecrui argument. Acest fapt ne ndrept ete s numim C-ul ca un limbaj de apel prin valoare. Exist totui o excep ie de la aceast regul atunci cnd argumentul este un tablou. Aceast excep ie este explicat, pe scurt, prin faptul c valoarea unui nume al unui tablou (vector, matrice etc.) neindexate este adresa primului su element. Deci, n cazul unui argument de tip tablou, ceea ce se paseaz unei func ii este o anumit adres. Folosind variabile pointer se pot pasa adrese pentru orice tip de date. Spre exemplu, func ia scanf() accept un parametru de tip pointer (adres): scanf(%1f,&x); Ceea ce este important de eviden iat este cum anume se poate scrie o func ie care s accepte ca parametri formali sau ca argumente pointeri ?. Func ia care recep ioneaz o adres ca argument va trebui s declare acest parametru ca o variabil pointer. De exemplu, func ia swap() care va interschimba valorile a doi ntregi poate fi declarat astfel:
# include <stdio.h> void swap(); /*Prototipul functiei swap()*/ void main(void) { int i,j; i=1; j=2; printf("i= %d j= %d\n", i, j); swap(&i,&j); /* Apelul functiei */
124
printf("i= %d j= %d\n", i, j); } void swap(int *pi, int *pj) { short t; t = *pi; *pi = *pj; *pj = t; }
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 pozi ie n tablou. 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.
125
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
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.
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,
126
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.
# 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);
127
if (value != 0) push (value); else printf ("Nr. extras din stiva este 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); }
%d
\n",
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 func ioneaz ":
# include <stdio.h> void main (void) { char *s; s = " Acest program functioneaza "; printf (s); }
128
plaseaz adresa celui de-al 3-lea element al vectorului x n pointerul p. Un domeniu n care aceast practic este esen iala const n 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 spa iu:
# include <stdio.h> void main (void) { char s[80]; char *p; int i; printf (" Introduceti un sir : \n "); gets (s); /* Gaseste primul spatiu sau sfarsitul sirului */ for (i = 0; s[i] && s[i] != ' '; i++) p = & s[i+1]; printf (p); }
Dac p indic spre un spa iu, programul va afia spa iul i apoi subirul rmas. Dac n irul introdus nu este nici un spa iu, 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 spa iu i apoi friend.
Pentru atribuirea unei variabile ntregi, var, celui de al treilea element al tabloului de pointeri *x[10], se va scrie:
x[2] = &var;
129
char *err[ ] = { " Cannot open file \n ", " Read error \n ", " Write error \n " }; selmes (int num) /* Selecteaza un mesaj */ { scanf("%d", &num); /* Se introduce un numar de la tastatura */ printf("%s", err[num]); }
Func ia printf() este apelat din func ia selmes(). Aceasta va afia mesajul de eroare indexat prin numrul de eroare num, care este pasat ca argument func iei selmes(). De exemplu, dac se introduce 2, atunci se va afia mesajul: Write error Atentie !. Trebuie facut distinc ia ntre:
int *v[10]; // Tablou de 10 pointeri la intregi int (*v)[10]; // Pointer la un tablou de 10 intregi
Pentru aceasta trebuie inut cont de faptul c * este un operator prefixat, iar [] i () sunt operatori postfixa i. Deoarece prioritatea operatorilor postfixa i este mai mare dect cea a operatorilor prefixa i, atunci cnd se dorete schimbarea priorit ii, trebuie folosite paranteze.
130
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*/
ntr-un program, p din ultima declara ie poate fi utilizat ca orice alt ir. Astfel, programul urmtor este corect:
# include <stdio.h> char *p = " Hello world \n "; void main (void) { register int t; /*Se tipareste sirul in ordine directa si inversa*/ printf (p); for(t = strlen(p)-1; t > -1; t--) printf("%c",p[t]); }
Observa ie: Neini ializarea pointerilor sau ini ializarea 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 loca ii de memorie necunoscute. Programul nu va oferi niciodat o valoare pointerului p dar va tipri valoarea lui x.
131
Func ia printf() nu va afia niciodat valoarea lui x (care este 10), dar va tipri o valoare necunoscut. Aceasta datorit atribuirii greite:
p = x;
Instruc iunea atribuie valoarea 10 pointerului p, care se presupune c reprezint o adres i nu o valoare. Pentru a corecta programul, trebuie scris: p = &x;
A doua metod de alocare a memoriei, const n utilizarea func iilor de alocare dinamic malloc() i free(). Prin aceast metod, un program aloc memorie pentru diversele informa ii n spa iul
132
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 func ii recursive va folosi mult mai intens stiva n compara ie cu un program ce nu utilizeaza func ii recursive, aceasta deoarece adresele de retur i variabilele locale corespunztoare acestor func ii sunt salvate n stiv. Func iile malloc() i free() Aceste func ii formeaz sistemul de alocare dinamic a memoriei n C i fac parte din fisierul antet <stdlib.h>. Acestea lucreaz mpreun i utilizeaz zona de memorie liber plasat ntre codul program i memoria sa permanent (fix) i vrful stivei, n scopul stabilirii i men inerii unei liste a variabilelor memorate. De fiecare dat cnd se face o cerere de memorie, func ia malloc() aloc o parte din memoria rmas liber. De fiecare dat cnd se face un apel de eliberare a memoriei, func ia free() elibereaz memorie sistemului. Declararea func iei malloc() se face sub forma:
void *malloc (int numar_de_bytes);
Aceasta ntoarce un pointer de tip void, ceea ce nseamn c trebuie utilizat un ablon explicit de tip atunci cnd pointerul returnat de malloc() se atribuie unui pointer de un alt tip. Dac apelul lui malloc() se execut cu succes, malloc() va returna un pointer la primul byte al zonei de memorie din heap ce a fost alocat. Dac nu este suficient memorie pentru a satisfce cererea malloc(), apare un 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. Func ia free() returneaz sistemului memoria alocat anterior cu malloc(). Dup eliberarea memoriei cu free(), aceasta se poate reutiliza folosind un apel malloc(). Declararea func iei free() se realizeaz sub forma:
free(void *p);
Func ia free() elibereaz spa iul indicat de p i nu face nimic dac p este NULL. Parametrul actual p trebuie s fie un pointer la un spa iu alocat anterior cu malloc(), calloc() sau realloc().
Exemplu: Urmtorul program va aloca memorie pentru 40 de ntregi, va afia valoarea acestora, dup care elibereaz zona, utiliznd free():
133
# include <stdio.h> # include <stdlib.h> void main(void) { int t, *p; p = (int *) malloc(40*sizeof(int)); if (!p) printf("Out of memory \n"); //Verificati neaparat daca p este un pointer corect else { for (t=0; t<40; ++t) *(p + t) = t; for (t=0; t < 40; ++t) printf("%d", *(p + t)); free(p); } }
Func iile calloc() i realloc() Func ia calloc() aloc un bloc de n zone de memorie, fiecare de dim octe i i seteaz la 0 zonele de memorie; func ia returneaz un pointer la acel bloc (adresa primului octet din bloc). Declararea func iei se face cu:
void *calloc(unsigned int n, unsigned int dim);
Func ia realloc() primete un pointer la un bloc de memorie alocat n prealabil (declarat pointer de tip void) i redimensioneaz zona alocat la dim octe i (dac este nevoie, se copiaz vechiul con inut ntr-o alt zon de memorie). Declararea func iei se face cu:
void *realloc(void *ptr, unsigned int dim);
O utilizare important a pointerilor la structur const n realizarea apelului prin adres ntr-o func ie. Cnd unei func ii 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.
134
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). 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; {
135
(*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 func iei main() se declar structura "time" i se ini ializeaz cu 00:00:00. Programul transmite adresa lui time func iilor actualizeaza() i afiseaza(). n ambele func ii, argumentul acestora este declarat a fi un pointer-structur de tip "tm". Referirea 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;
136
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 func ia delay() */ { long int t; for (t = 1;t<140000000;++t);}
137
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 pozi ie intermediar un element nou, aceaste opera ii devin foarte anevoioase. ntr-un exemplu anterior am folosit secven a
# define SIZE 100 struct addr { char name[20]; char street[30]; char city[15]; char state[10]; unsigned int zip; } addr_info[SIZE];
Rezult c am definit un tablou static cu 100 de elemente, cu numele addr_info, la care fiecare element este o structur cu ablonul addr. Dac n aceast aplica ie, chiar n timpul execu iei programului, 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 succes. Acest lucru prezent dou inconveniente (vezi [Mocanu, 2001]): 1- Execu ia i ob inerea 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 spa iu pentru respectivul element, se va crea elementul prin nscrierea informa iilor corespunztoare i se va lega n list. Cnd un element este scos din list spa iul 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 componen a unor structuri de date dinamice (nod) prezint n structura sa dou pr i:
138
1. Partea de informa ie (info) ce poate apar ine unui tip simplu (int, char, float, double, etc.) conform cu necesit ile problemei. 2. Partea de legtur (link) ce con ine cel pu in un pointer de legtur (next) la o alt variabil dinamic, de obicei de acelai tip, ce realizeaz nln uirea variabilelor dinamice n structuri de date dinamice. Dintre structurile de date dinamice, cele mai simple i mai utilizate sunt listele. Lista este o structur liniar, de tipul unui tablou unidimensional (vector), care are un prim element i un ultim element. Celelalte elemente au un predecesor i un succesor. Elementele unei liste se numesc noduri. La rndul lor, listele pot fi grupate n mai multe categorii, cele mai importante fiind listele simplu nln uite, listele circulare i listele dublu legate. Principalele opera ii 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 nln uit 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 care pointerul spre nodul urmtor este NULL. Acesta este ultimul nod al listei simplu nln uite (sau simplu legate). De asemenea, exist un nod spre care nu pointeaz nici un alt nod, acesta fiin primul nod al listei. O list simplu nln uit poate fi identificat n mod unic prin primul element al listei. Determinarea ultimului element se poate face prin parcurgerea secven ial a listei pn la ntlnirea nodului cu pointerul spre nodul urmtor cu valoarea NULL.
info next
info NULL
Listele circulare sunt un alt tip de liste pentru care rela ia de preceden nu mai este liniar ci ultimul element pointeaz ctre primul. Procedurile necesare pentru crearea i utilizarea unei liste circulare sunt extrem de asemntoare cu cele de la liste liniare, cu
139
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 nln uite ce partajeaz acelai cmp comun info, una fiind imaginea n oglind a celeilalte.
info next NULL info next previous List dublu legat info NULL previous
Pointerul next indic spre urmtorul nod, iar cmpul previous indic spre cmpul anterior. Vom prezenta n continuare modul n care putem proiecta func iile principale care ac ioneaz asupra unei structuri dinamice. Pentru aceasta vom utiliza dou variabile globale de tip pointer, una care pointeaz spre primul nod al listei iar cealalt spre ultimul nod al listei. Vom denumi aceste variabile first respectiv last. 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; };
140
Att la crearea unei liste ct i la inserarea unui nod se va apela func ia malloc() pentru a rezerva spa iu de memorie pentru un nod. Zona alocat prin intermediul func iei malloc() se poate elibera folosind func ia free(). Propunem conceperea unui program asemntor cu programul de exploatare a unei baze de date conceput anterior dar care s foloseasc n locul unui tablou static o list simplu nln uit. Programul poate fi extins ulterior pentru structuri dinamice mai complexe, care s foloseasc liste dublu nln uite sau circulare. Programul va avea urmtoarele facilit i: 1. Crearea unei liste simplu nln uite n memorie (pentru prima oar). 2. Exploatarea unei liste simplu nln uite 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 c) tergerea ultimului nod al listei 3. Salvarea din memorie pe disc a unei liste simplu nln uite 4. Citirea de pe disc n memorie a bazei de date salvate anterior 5. Afiarea pe ecran, nregistrare cu nregistrare, a bazei de date con inute n lista dinamic. Programul este prevzut cu o intefa simpl prin care utilizatorul poate alege dintre op iunile pe care le are la dispozi ie. Interfa a este sub forma unui meniu din care, prin tastarea ini ialelor comenzilor, acestea se lanseaz n execu ie. Vom descrie pe rnd func iile care ndeplinesc sarcinile enumerate mai sus. Pentru o mai bun proiectare a programului, vom folosi att modularizarea intern prin construirea mai multor func ii ct i o modularizare extern prin izolarea programului principal de restul func iilor care se folosesc.
141
Baz de date cu list simplu nln uit Afiare pe ecran a nregistrrilor din baza de date
Interfa a cu utilizatorul
Inserare
dinamic
tergere
142
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 func ii care nu se gsesc n fiierul bd_main.c ci n fiierul bd_bib.c unde sunt colectate toate func iile pe care le folosim. Acest fiier are la rndul su un fiier header numit local1.h care con ine:
# include <stdio.h> # include <ctype.h> # include <string.h> # include <stdlib.h> extern FILE *fp; typedef struct addr { char name[20]; char street[30]; char city[15]; char state[10]; unsigned int zip; struct addr *next;}TNOD; extern TNOD *first, *last;
Prin cele dou fiiere header cele dou fiiere surs bd_main.c i bd_bib.c se pot compila mpreun, rezultnd un singur fiier executabil. Interfa a cu utilizatorul Aceasta const ntr-un meniu principal, care permite accesarea func iilor principale, i din dou submeniuri pentru opera iile de tergere i inserare de 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");
143
printf ("(D)isplay list\n"); printf ("(I)nsert record\n"); printf ("(E)rase\n"); printf ("(Q)uit\n"); printf (" Alegeti optiunea: "); gets(s); ch=s[0]; } while (!strrchr("clsdieq",ch)); return tolower(ch); } //meniu functia de stergere char menu_del() { char s[5],ch; do { printf ("(E)ntire list delete\n"); printf ("(F)irst record delete\n"); printf ("(L)ast record delete\n"); printf ("(I)ntermediate delete\n"); printf ("(Q)uit\n"); printf (" Option ? "); gets(s);ch=s[0]; } while (!strrchr("efliq",ch)); return tolower(ch); } //meniu functia de inserare char menu_insert() { char s[5],ch; do { printf ("(F)irst record insert\n"); printf ("(L)ast record insert\n"); printf ("(I)ntermediate insert\n"); printf ("(Q)uit\n"); printf (" Option ? "); gets(s);ch=s[0]; } while (!strrchr("fliq",ch)); return tolower(ch); }
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. Func ia 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;}}
144
/* Functia loadf_list() from file */ int loadf_list() { int n=sizeof(TNOD),i; TNOD *p; first=last=NULL; if ((fp = fopen("maillist","rb")) == NULL) { printf("Cannot open file\n ");return 0;} while (((p=(TNOD *)malloc(n))!=NULL)&&((i=loadf_nod(p))==1)) if (first==NULL){ /* prima creare */ first=last=p; first->next=NULL;} else { last->next=p;last=p; last->next=NULL;} if (p==NULL){ printf("Memorie insuficienta pentru lista\n"); return 0;} free(p);return i;}
Func ia loadf_list() aloc fiecare nregistrare citit de pe disc unui nod al listei dinamice construite n memorie. n pointerii first i last se stocheaz n permanen adresele primei i ultimei nregistrri (nod sau structur). Salvarea bazei de date pe disc Reprezint opera iunea invers celei de citire. Lista simplu nln uit din memorie se salveaz n ntregime pe disc n fiierul maillist.dat. Spre deosebire de func ia loadf_list() care apela la func ia loadf_nod(), func ia savef_list() nu face nici un apel la o alt func ie definit de utilizator:
/* Functia savef_list() */ int savef_list() { TNOD *p; if ((fp = fopen("maillist", "wb")) == NULL) { printf (" Cannot open file\n ");return 0;} p=first; do { if(fwrite(p, sizeof(TNOD), 1,fp) !=1) {printf (" File write error \n "); fclose (fp);return 0;} p=p->next;} while (p!=NULL); fclose (fp);return 1;}
Crearea unei liste simple nln uite La nceput variabilele first i last au valoarea NULL, lista fiind vid. Crearea unei liste se face prin func ia create_list. Ea returneaz 0
145
pentru eroare i 1 dac opera ia reuete. Prin aceast func ie se ini ializeaz o list dinamic n memorie, fr a face o citire a unei baze de date create anterior. Aceasta este prima func ie 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"); free(p);}}
Afiarea listei dinamice din memorie Prin aceast opera ie, realizat de func ia display_list(), se afieaz secven ial toate nregistrrile con inute 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();
146
p=first; if (p!=NULL) do { disp_nod(p); p=p->next; getchar();} while (p!=NULL); else printf("Lista vida !\n");}
Func ia display_list() apeleaz la func ia disp_nod() care afiez o singur nregistrare. Dac este nevoie de afiarea unui cap de tabel (antet) se apeleaz la func ia disp_antet(). Inserarea unor noduri n lista dinamic Aceast opera ie presupune introducerea unor noduri (nregistrri) fie la nceputul listei naintea primului nod, fie la sfritul su (adugare) dup ultimul nod, fie ntre dou noduri vecine nesituate la extremit i. Func iile listate mai jos realizeaz aceste sarcini.
// functia de inserare void insert() { char choice; for (; ;) { choice = menu_insert(); switch (choice) { case 'f' : ins_first(); break; case 'l' : ins_last(); break; case 'i' : ins_int(); break; case 'q' : break;} break;}} /* Functia insert_last() */ void ins_last() { int n=sizeof(TNOD); char ch='c',s[2]; TNOD *p; /* ne pozitionam pe ultimul nod */ p=first; while (p->next!=NULL) p=p->next; // se creaza lista in memorie while (ch!='x') { p=(TNOD *)malloc(n); last->next=p; p=add_nod(p); p->next=NULL; last=p; printf("Exit? (x): "); gets(s);ch=s[0];}} /* Functia insert_first() */ void ins_first() { int n=sizeof(TNOD); char ch='c',s[2];
147
TNOD *p; while (ch!='x') { p=(TNOD *)malloc(n); p->next=first; p=add_nod(p); first=p; printf("Exit? (x): "); gets(s);ch=s[0];}} // inserare dupa inregistrarea afisata void ins_int() { TNOD *p, *pi; char ch='n', s[2]; disp_antet(); p=first; while ((p!=NULL)&&(ch!='y')) { disp_nod(p); printf("Here ? [y]: "); gets(s);ch=s[0]; if (ch!='y') p=p->next;} pi=(TNOD *)malloc(sizeof(TNOD)); pi=add_nod(pi); pi->next=p->next; p->next=pi;}
La inserarea unui nod, este nevoie ca acesta s fie legat de cele ntre care se introduce cu ajutorul pointerilor next. Dac se 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 extremit i. n acest caz, este nevoie s modificm pointerii first i last . n toate cazurile este nevoie s refacem legturile distruse dup dispari ia prin tergere a unor noduri. tergerea nu este efectiv, ci numai se refac legturile i se elibereaz cu func ia free() zona de memorie alocat n prealabil (prin inserare sau creare) cu func ia malloc(). Mai mult, avem op iunea de a terge ntreaga list din memorie n scopul nlocuirii n totalitate a datelor con inute n nregistrri.
// func ia de tergere void erase() { char choice; for (; ;) { choice = menu_del(); switch (choice) { case 'e' : del_list(); break;
148
case 'f' : del_first(); break; case 'l' : del_last(); break; case 'i' : del_int(); break; case 'q' : break;} break;}} // se sterge intreaga lista si se elibereaza memoria void del_list() { TNOD *p,*pu; p=first;pu=p->next; while (pu!=NULL) {free(p); p=pu;pu=pu->next;} first=NULL;last=NULL;} // sterge prima inregistrare void del_first() { int n=sizeof(TNOD); char ch='c',s[2]; TNOD *p,*pu; while (ch!='x') { p=first;pu=p->next; free(p); first=pu; printf("Exit? (x): "); gets(s);ch=s[0];}} // stergere ultima inregistrare void del_last() { int n=sizeof(TNOD); char ch='c',s[2]; TNOD *p; /* ne pozitionam pe penultimul nod */ while (ch!='x') { p=first; while (p->next!=last) p=p->next; free(p->next); p->next=NULL; last=p; printf("Deleted. Exit? (x): "); gets(s);ch=s[0];}} // stergere inregistrare intermediara void del_int() { TNOD *p, *pa, *pu; char ch='n', s[2]; disp_antet(); pa=first;p=pa->next;pu=p->next; while ((p!=last)&&(ch!='y')) { disp_nod(p); printf("Delete ? [y]: "); gets(s);ch=s[0]; if (ch='y') {pa->next=pu; free(p);} else {pa=p;p=pu;pu=pu->next;}}}
149
Tipul unei func ii corespunde tipului valorii pe care func ia o va returna utiliznd instruc iunea return. Acesta poate fi un tip de baz (char, int, float, double etc.) sau un tip derivat (pointer, structur etc.). Dac pentru o func ie nu se specific nici un tip, atunci, implicit se consider c func ia ntoarce o valoare ntreag. Lista parametrilor, lista_parametri, este o list de nume de variabile separate prin virgul care vor primi valorile argumentelor n momentul apelrii acesteia. Tipul acestor parametri este descris fie n paragraful declara ii_parametri, fie direct n lista parametrilor. Lista parametrilor este nchis ntre paranteze. Chiar dac o func ie nu are parametri, parantezele nu trebuie s lipseasc.
150
De exemplu, func ia max(a, b), care returneaz cel mai mare dintre numerele ntregi a i b, se poate defini sub forma:
int max(a, b) int a, b; { if (a > b) return (a); else return (b); }
sau
int
max(int a, int b)
n cazul n care tipul parametrilor formali ai unei func ii nu se declar, atunci ei sunt considera i implicit de tip int. n cazul compilatoarelor moderne, programul urmtor va genera dou avertismente. Exemplu:
# include <stdio.h> float max(); // Prototipul functiei max() float x; void main() { x = max(3, 4); printf("max= %d",x); } float max(a, b) float a, b; { if (a > b) return (a); else return (b); }
Aceste avertismente sunt generate deoarece, la primul pas al compilrii, la parcurgerea liniei de declarare a func iei:
float max(a,b)
parametrii formali a i b sunt considera i de tip ntreg. Programul, modificat ca mai jos, va duce la o compilare fr probleme: Exemplu:
# include <stdio.h>
151
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); }
Se observ c nu mai este nevoie de declararea explicit a parametrilor formali de tip ntreg a i b.
152
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 func ie se realizeaz utiliznd func ia return. Func ia return poate fi folosit fr nici o valoare asociat. Exemplu: Func ia 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 */ i = 1; for (; exp; exp--) i = baza * i; printf (" Rezultatul este %d \n", i); }
Dac exponentul exp este negativ, instruc iunea return determin terminarea func iei nainte ca sistemul s ntlneasc }, dar nu returneaz nici o valoare. O func ie poate con ine mai multe instruc iuni return, care pot simplifica anumite algoritme.
153
manipulri. Un exemplu este fwrite() folosit pentru a scrie informa ii pe disk. Dac scrierea se face cu succes, fwrite() ntoarce numrul de octe i nscrii (ceru i s se nscrie); orice alt valoare indic apari ia unei erori. 3) A treia categorie de func ii sunt cele care nu trebuie s ntoarc o valoare explicit. De exemplu, func ia printf() ntoarce numrul de caractere tiprite, numr care, de obicei, nu are o utilizare ulterioar. Dac pentru o func ie care returneaz o valoare nu se specific o opera ie de atribuire, calculatorul va ignora valoarea returnat.
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.
154
n cadrul unei func ii se deosebesc trei tipuri de variabile, astfel: variabile locale, parametri formali i variabile globale. Domeniul unei func ii determin modul n care alte pr i ale programului pot avea acces la aceste trei tipuri de variabile stabilind i durata de via a acestora.
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 con ine cuvntul cheie auto, care poate fi folosit pentru declararea de variabile locale. Cu toate acestea, ntruct C presupune c toate variabilele neglobale sunt prin defini ie (implicit) variabile locale, deci au atributul auto, acest cuvnt cheie nu se utilizeaz. De obicei, variabilele locale utilizate n interiorul unei func ii se declar la nceputul blocului de cod al acestei func ii. Acest lucru nu este neaprat necesar, deoarece o variabil local poate fi declarat
155
oriunde n interiorul blocului n care se utilizeaz, dar nainte de a fi folosit. Exemplu: Considerm urmtoarea func ie:
func (){ char ch; printf (" Continuam (y / n) ? : "); ch = getche(); //Se preia optiunea de la tastatura /* Daca raspunsul este yes */ if (ch == 'y') { char s[80]; /* s se creeaza numai dupa intrarea in acest bloc */ printf (" Introduceti numerele: \n "); gets (s); prelucreaza_nr (s); /* Se prelucreaza numerele */ } }
Aici, func() creeaz variabila local s la intrarea n blocul de cod a lui if i o distruge la ieirea din acesta. Mai mult, s este cunoscut numai n interiorul blocului if i nu poate fi referit din alt parte, chiar din alt parte a func iei func() care o con ine. Deoarece calculatorul creeaz i distruge variabilele locale la fiecare intrare i ieire din blocul n care acestea sunt daclarate, con inutul lor este pierdut o dat ce calculatorul prseste blocul. Astfel, variabilele locale nu pot re ine valorile lor dup ncheierea apelului func iei.
156
Func ia func() are doi parametri: s i c. Aceasta func ie ntoarce 1 dac caracterul c apar ine irului i 0 dac c nu apar ine irului. Precizm c argumentele cu care se va apela func ia trebuie s aib acelai tip cu parametrii formali declara i n func ie. Aceti parametri formali pot fi utiliza i ca orice alt variabil local. Un al doilea mod (recomandat de ANSI-C n 1989) de a declara parametrii unei func ii const n declararea complet a fiecrui parametru n interiorul parantezelor asociate func iei. De exemplu, declararea parametrilor func iei func() de mai sus se poate face i sub forma:
func (char *s, char c) { . . . . . . . . . . . }
157
for (count = 1; count < 10; count ++) printf ("%2d\n" , count); }
Se observ c, dei nici func ia main() i nici func ia func1() nu au declarat variabila count, ambele o folosesc. Func ia func2() a declarat o variabil local count. 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 func iei n care este declarat variabila local se vor efectua numai asupra variabilei locale i nu vor avea nici un efect asupra variabilei globale. Deci, o variabil local ascunde o variabil global. Variabilele globale sunt memorate ntr-o zon fix de memorie destinat special acestui scop (memoria static), rmnnd n aceast zon pe parcursul ntregii execu ii a programului. Variabilele declarate explicit extern sunt tot variabile globale, dar accesibile nu numai modulului program n care au fost declarate, ci i tuturor modulelor n care au fost declarate de tip extern. Un alt exemplu util i pentru n elegerea lucrului cu pointeri este 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 loca iile de memorie pentru fiecare tip de variabil x, precum i pentru pointerul p corespunztor. Re inem c, n general, dac:
p = &x => p = &x = &(*p) = (&*)p = p *p = *(&x) = (*&)x = x
Faptul c * respectiv & sunt opera iuni complementare (inverse una celeilalte) se observ din rela iile de mai sus, din care deducem c:
&* = *& = identitate &(*z)= z; // z este pointer *(&z) = z; // z este o variabila de un anume tip
(z este de alt tip n fiecare din egalit ile de mai sus, pointer sau variabil).
158
Adresa
&x=adresa x
&p=adresa p
&q=adresa q
# include <stdio.h> int x = 34; /* x este global */ void main(void) { int *p = &x, *r; /* p este o variabila pointer catre un intreg */ void **q; printf("x=%d &x=%p *p=%d p=%p &p=%p\n",x,&x, *p, p, &p); { int x; x = 1; /* Acest prim x este o variabila locala ce o ascunde pe cea globala */ p = &x; printf("x=%d &x=%p *p=%d p=%p &p=%p\n",x,&x, *p, p, &p);} { int x; /* Acest al doilea x ascunde prima variabila locala x */ x = 2; // Se atribuie valoarea 2 acestui x p = &x; /* Pointerul p retine adresa variabilei x */ printf("x=%d &x=%p *p=%d p=%p &p=%p\n",x, &x, *p, p, &p); q = &p; // q retine adresa pointerului p r = *q; // r retine valoarea de la adresa q /*Cum q = &p => r = *(&p) = p => *r = *p = x */ printf("q=%p *q=%p **q=%d &q=%p\n", q, *q, *r, &q); } }
Prin declara ia int *p = &x; variabila p este declarat variabil pointer ctre o dat de tip ntreg i este ini ializat cu adresa variabilei spre puncteaz, anume x. Pointerul q este declarat ca un pointer la un alt pointer.
159
Caracteristici ale Caracteristici ale variabilei variabilei pointer ataate Adresa &x Valoare Adresa &p Valoarea p *p x 00426A54 34 0065FDF4 00426A54 34 0065FDF0 0065FDEC p = &x q = &p 1 2 *p = x *q = p &q= 0065FDEC q= 0065FDF4 **q =2 0065FDF4 0065FDF4 0065FDF0 0065FDEC 1 2
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);
dac n prealabil s-a fcut atribuirea p = &x; Se mai observ cum pointerul p, care ini ial 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 octe i, att ct sunt aloca i 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 instruc iunile:
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 ini ializeaz pointerul q cu adresa pointerului p, apoi pointerul r va primi valoarea *q, adic valoarea p. Una din principalele caracteristici a limbajelor structurate o constituie compartimentarea codului i a datelor. n C, compartimentarea se realizeaz folosind variabile i func ii. De exemplu, pentru scrierea unei func ii mul() care determin produsul a doi ntregi se pot utiliza dou metode, una general i una specific, astfel:
160
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 func iei, 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); } 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
161
a) n cazul transmiterii argumentului prin valoare, se realizeaz copierea (atribuirea) valorilor fiecrui argument n (la) cte un parametru formal al func iei apelate. Exemplu: Se apeleaz o func ie 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 func iei (schimbrile lui x nu modific n nici un fel pe t). b) Dac transmiterea argumentului se realizeaz prin adrese, atunci la apelul func iei n loc de valori se folosesc adrese, iar n defini ie, parametrii formali se declar ca pointeri. Exemplu: O func ie swap() care schimb valorile a dou variabile reale se poate defini astfel:
void swap(float *x, float *y){ float temp; temp = x; /* temp preia valoarea de la adresa x */ *x = *y; /* valoarea de la adresa y este copiata la adresa x */ y = temp; /* la adresa y se copiaza valoarea lui temp */ }
Se observ c parametrii formali ai func iei swap() sunt pointeri la float. Programul urmtor arat modul de apel al acestei func ii.
# include <stdio.h> void swap(float *x,float *y); void main(void) { float x, y; // x si y sunt de tip float scanf("%f,%f",&x,&y);/*Se introduc de la tastatura doua numere reale separate prin virgula*/ printf ("x = %f, y = %f \n ",x,y); swap(&x,&y); /*Se apeleaza functia swap() avand ca argumente adresele lui x si y */ printf("x = %f, y = %f \n ",x,y); }
Prin &x i &y, programul transfer adresele lui x i y func iei swap() i nu valorile lui x i y.
162
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]); }
163
Aceasta func ie declar pe num ca fiind un vector de ntregi cu dimensiune necunoscut. Deoarece limbajul C nu verific dimensiunea vectorilor, dimensiunea actual a vectorului este irelevant ca parametru al func iei. i de aceasta dat, num va fi convertit la un pointer la ntreg. c) Ultima metod prin care se poate declara un parametru tablou este ca pointer, astfel:
display(int *num) { int i; for (i = 0; i < 10; i++) printf ("%d", num[i]); }
Limbajul C permite acest tip de declara ie deoarece putem indexa orice pointer 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 func ii va fi tratat ca orice alt variabil. Astfel, programul de mai sus poate fi rescris sub forma:
# include <stdio.h> void main (void) { int v[10], i; for (i = 0; i < 10; i++) v[i] = i; for (i = 0; i < 10; i++) display (v[i]); } display(int num) { printf ("%d" , num); }
De data aceasta, parametrul din display() este de tip int, deoarece programul utilizeaz numai valoarea elementului tabloului. Exemplu: Vom prezenta un program pentru afiarea tuturor numerelor prime cuprinse ntre dou limite ntregi. Programul principal apeleaz dou func ii: 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 func ia display().
# include <stdio.h> int nr_prim(); // Se declara prototipul void display(); void main (void) { int a,b,i,j,v[80]; printf("Introduceti limitele: "); scanf("%d %d", &a, &b); j = 0; for (i=a; i<=b; ++i) if (nr_prim(i)) {v[j]=i; ++j;} display(v,j);} int nr_prim(int i) { int j; // Decide daca i este prim
164
for (j=2; j<=i/2; j++) if (i%j==0) return 0; return 1; } void display(int *p, int j) /* Tipareste un vector de intregi */ { int i; for (i=0; i<j; ++i) printf("%d ", p[i]); }
Din cele de mai sus, trebuie re inut c atunci cnd un tablou se utilizeaz ca argument al unei func ii, calculatorul transmite func iei adresa de nceput a tabloului. Acest lucru constituie o excep ie a limbajului C n conven ia de transmitere a parametrilor prin valoare. Astfel, codul func iei poate ac iona asupra con inutului tabloului i l poate altera. Exemplu: Programul urmtor va modifica con inutul vectorului sir din func ia main() dup apelul func iei afis_litmari().
# include <stdio.h> # include <ctype.h> afis_litmari(); void main (void) { char sir[80]; gets(sir); afis_litmari(sir); printf("\n%s\n",sir);} // Se defineste functia afis_litmari() afis_litmari(char *s) { register int t; for (t = 0; s[t]; ++t) { // Se modifica continutul sirului sir s[t] = toupper(s[t]); printf("%c",s[t]);}}
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)
165
/* 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
n aceasta variant con inutul tabloului ramne nemodificat, deoarece programul nu-i schimb valoarea. Un exemplu clasic de transmitere a tablourilor ntr-o func ie l constituie func ia gets() din biblioteca C standard. Prezentm o variant simplificat a acestei func ii numit xgets().
xgets(s) char *s; { char ch; int t; for (t = 0; t < 80; ++t) { ch = getchar(); switch (ch) { case '\n' : s[t] = '\0'; /* terminare sir return; case '\b': if (t > 0) t--; break; default: s[t] = ch; } } s[80] ='\0'; }
*/
Func ia xgets() trebuie apelat avnd ca argument un tablou de caractere, care, prin defini ie, este un pointer la caracter. Numrul caracterelor introduse de la tastatur, prin func ia for este de 80. Dac se introduc mai mult de 80 de caractere, func ia se ncheie cu return. Dac se introduce un spa iu, contorul t este redus cu 1. Cnd se apas CR, xgets() introduce terminatorul de ir.
166
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 introduce i numele, imediat dup numele programului:
# include <stdio.h> void main (argc, argv) // Numele programului int argc; char *argv[]; {if (argc != 2) { printf (" Ati uitat sa va introduceti numele \n"); return; } printf ("Hello %s !", argv[1]); }
Dac acest program se numete ARG_LC.C i numele dumneavoastr este DAN, atunci, pentru a executa programul, n linia de comand, ve i tipri ARG_LC DAN. Ieirea programului va fi Hello DAN !. Argumentele linie_comanda trebuie separate prin spa iu sau TAB i nu prin virgul, sau;. Parametrul argv[] se declar, de obicei, sub forma char *argv[]; i reprezint un tablou de lungime nedeterminat, mai precis reprezint un tablou de pointeri. Accesul la elementele lui argv[] se realizeaz prin indexarea acestuia, astfel: argv[0] va indica spre primul ir, care este ntotdeauna numele programului; argv[1] va indica spre primul argument etc. Evita i folosirea sa 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 con ine numrul la un ntreg folosind func ia 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");
167
return; } if (argc==3 && !strcmp(argv[2], "display")) disp = 1; else disp = 0; for (count = atoi(argv[1]); count; --count) if (disp) printf("%d ",count); printf("%c",7); /* Se emite un beep */
Observa ie: Dac n linia de comand nu se specific nici un argument, programul va afia un mesaj de eroare.
Chiar dac func ia are argumente, n declara ia de tip acestea nu se precizeaz (cu excep ia compilatoarelor mai vechi de 1989, care nu sunt adaptate la cerin ele ANSI-C).
168
Dac o func ie ce a fost declarat int ntoarce un caracter, calculatorul convertete valoarea caracter ntr-un ntreg. Deoarece conversiile caracter --> ntreg-caracter sunt fr probleme, o func ie ce ntoarce un caracter poate fi definit ca o func ie care ntoarce un ntreg.
count ++;
Func ia 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 , func ia va ntoarce un pointer la terminatorul de ir (NULL). Un scurt program ce ar utiliza func ia match() este urmtorul :
# include <stdio.h>
169
# 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 unde se afl caracterul, altfel se tiprete "Nu exist nici o coinciden ". Un caz interesant este oferit de func iile care returneaz pointeri ctre iruri de caractere. O astfel de func ie se declar sub forma:
char *f()
Exemplu: Programul urmtor arat modul n care se definete, se declar i se apeleaz o astfel de func ie.
# include <stdio.h> void main(void) { int i; char *NumeLuna(); scanf("%d", &i); printf("%s \n ", NumeLuna(i)); } char *NumeLuna(nr) int nr; { char *luna[]= {"Eroare", "Ianuarie", "Februarie", "Martie", "Aprilie", "Mai", "Iunie","Iulie", "August", "Septembrie", "Octombrie", "Noiembrie", "Decembrie"}; return ((nr>=1) && (nr <= 12)?luna[nr]:luna[0]);
Un alt exemplu va fi reprezentat de o variant a func iei strcpy() din string.h , deci o func ie care copiaz caracterele din irul s2 n irul s1. Rezultatul se 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() */
170
# include <stdio.h> char *strcpy2(register char s1[],register char s2[]) { char *s0 = s1; // Echivalent: char *s0;s0 = s1; while ((*s1++ = *s2++) != '\0'); return s0; } void main() { char *sir1,*sir2; puts(Introduceti un sir de la tatstatura \n); gets(sir2); puts(strcpy2(sir1,sir2));}
Se observ cum se ini ializeaz s0 cu s1. Bucla while atribuie valorile (caracterele) (*s2) n loca iile indicate de pointerul s1, incrementnd ambii pointeri simultan. Bucla se termin la ntlnirea caracterului null, care se copiaz i el. Valoarea returnat, s0, re ine adresa de nceput a irului s1. Un ultim exemplu l constituie un program de manipulare a unor matrici. Acest program realizeaz citirea unei matrici, transpunerea sa i respectiv afiarea rezultatului apelnd la func iile cit_mat(), trans_mat() i tip_mat().
# include <stdio.h> # define DIM_MAX 10 void cit_mat(); void tip_mat(); int *trans_mat(); void main() { int a[DIM_MAX][DIM_MAX], dim_lin, dim_col, *p; printf("Introduceti dimensiunea matricei dim_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]);
[dim_lin
171
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; }
Utiliznd void se impiedic folosirea func iilor ce nu ntorc o valoare n orice expresie, prevenind astfel o ntrebuin are greit a acestora. De exemplu, func ia 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 func ii sau oricrei alte func ii de tip void, aceasta trebuie declarat. Dac nu se declar, compilatorul C consider c aceasta ntoarce o valoare ntreag. Astfel, modul de utilizare al func iei afis_vertical() este 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 ++); }
172
unde:
tip = tipul valorii ntoarse de func ie; tip_arg1, tip_arg2,... = tipurile argumentelor func iei. Exemplu: Programul urmtor va determina compilatorul s emit un mesaj de eroare sau de avertisment deoarece acesta ncearc s apeleze func ia func() avnd al doilea argument de tip int, n loc de float, cum a fost declarat n func ia func():
#include <stdio.h> void func(int, float);//Prototipul functiei func() void main (void) { int x, y; x = 10; y = 10; func (x, y); } /* Se afiseaza o nepotrivire */ void func (x, y) /* Parametrii functiei sunt: */ int x; /* x - intreg */ float y; /* y - real */ { printf ("%f", y/(float) x); }
Func iile prototip se folosesc pentru a ajuta compilatorul n prima faz n care func iile utilizate sunt definite dup programul principal. Acesta trebuie ntiin at asupra tipul datei returnat de o func ie pentru a aloca corect memoria. Dac func iile sunt declarate naintea liniei de program main(), func iile prototip nu mai sunt necesare, deoarece compilatorul extrage informa ia despre func ii n momentul n care parcurge corpul defini iei lor. Spre exemplu, programul de mai sus se poate scrie i sub forma urmtoare, n care nu vom mai avea o declara ie de func ie prototip:
#include <stdio.h> void func (x, y) /* Parametrii functiei sunt: */ int x; /* x - intreg */ float y; /* y - real */ { printf ("%f", y/(float) x); } void main (void) { int x, y; x = 10; y = 10; func (x, y); } /* Nu se afiseaza nepotrivire */
173
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
n ultimul program am eviden iat o recomandare care simplific efortul de programare n sensul c n linia de declarare a prototipurilor func iilor folosite este necesar s definim tipul func iei nu i tipul parametrilor formali. Compilatorul se informeaz despre tipul parametrilor formali la parcurgerea corpului defini iei func iei. Din cele de mai sus se observ ca folosirea func iilor prototip ne ajut la verificarea corectitudinii programelor, deoarece nu este permis apelarea unei func ii cu alte tipuri de argumente, dect tipul celor declarate.
174
Observa ie: Atunci cnd o func ie se autoapeleaz recursiv, la fiecare apel al func iei se memoreaz pe stiv att valorile parametrilor actuali, ct i ntregul set de variabile dinamice definite n cadrul func iei. Din aceasta cauz stiva trebuie dimensionat corespunztor.
O variant echivalent a func iei 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 rela ia de recuren : an = an-1+ an-2 , unde a0 = 0 i a1=1. Codul func iei poate fi scris sub forma:
long fib(int n) { if (n == 0) return (0); else if (n == 1) return (1); else return (fib(n-1)+fib(n-2)); }
Utilizarea recursivit ii poate s nu conduc la o reducere a memoriei necesare, att timp ct stiva este folosit intens pentru fiecare apel recursiv. De asemenea i execu ia programului poate s nu fie mai rapid. Dar codul recursiv este mai compact i de multe ori mai uor de scris i n eles dect echivalentul su recursiv. Recursivitatea este convenabil n mod deosebit pentru opera ii pe structuri de date definite recursiv, cum sunt listele, arborii etc.
175
atribute)
Din punct de vedere al execu iei programelor C, memoria computerului este organizat n trei zone, cunoscute n mod tradi ional ca segment de memorie text, segment de memorie static (sau de date) i segment de memorie dinamic (sau stiv).
Segment de memorie text Con ine instruc iunile programului, deci (memorie program) programul executabil Segment de memorie Con ine variabilele a caror loca ie rmne fix static Segment de memorie Con ine variabilele de tip automatic, parametrii func iilor i apelurile i retururile dinamic (de tip stiv) de/din func ii
Auto Local fiecrei func ii Temporar, numai (automatic) sau fiecrui bloc n care cnd se execut a fost declarat func ia n care este declarat Register Local fiecrei func ii Temporar, numai (registru) cnd se execut func ia n care este declarat Extern Global, de ctre toate Permanent, pe func iile 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 func ia care o
176
declar este activat) sau permanent (exist pe toat durata de execu ie a programului). Dac tipul se declar explicit n declaratorul variabilei, clasa de memorie se determin prin specificatorul de clas de memorie i prin locul unde se face declara ia (n interiorul unei func ii sau naintea oricrei func ii). Variabilele cele mai folosite sunt cele care sunt declarate n blocurile apar innd unei func ii. 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 s l invocm la declararea variabilelor. Variabilele auto sunt plasate n memoria stiv, iar domeniul de vizibilitate este local, numai pentru func ia n care variabila a fost declarat, iar din punctul de vedere al duratei de via sunt volatile, adic dispar din memoria stiv dup rentoarcerea din func ie. - static, declarate explicit. Variabilele static sunt plasate n memoria static, iar domeniul de vizibilitate este local, numai pentru func ia n care variabila a fost declarat, iar din punctul de vedere al duratei de via sunt permanente, adic nu dispar din memoria static dup rentoarcerea din func ie. - register, declarate explicit. Variabilele register sunt identice cu cele auto cu excep ia faptului c stocarea nu are loc n memoria stiv ci n regitrii interni ai microprocesorului n scopul sporirii vitezei de execu ie 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 func ie component a unui modul program. Stocarea are loc n memoria static iar durata de via este permanent, pe toat perioada execu iei programului. Ini ializarea unei variabile static difer de cea a unei variabile auto prin aceea c ini ializarea este fcut o singur dat, la ncrcarea programului n memorie i lansarea sa n execu ie. Dup prima ini ializare, o variabil static nu mai poate fi reini ializat (de exemplu, la un nou apel al func iei n care este ini ializat).
177
Iat ilustrat acest lucru prin dou exemple simple. Se tiprete, cu ajutorul func iei receip(), un numr care este mai nti ini ializat cu valoarea 1 i returnat incrementat cu o unitate. n cazul folosirii variabilelor implicite locale auto se ruleaz programul:
# include <stdio.h> short receip(); void main(){ printf("First = %d\n",receip()); printf("Second = %d\n",receip());} short receip() { short number = 1; return number++;}
i se ob ine rezultatul:
First = 1 Second = 1
Dac se modific n func ia receip() variabila number din auto n static, vom avea
# include <stdio.h> short receip(); void main(){ printf("First = %d\n",receip()); printf("Second = %d\n",receip());} short receip() { static short number = 1; return number++;}
i ob inem rezultatul
First = 1 Second = 2
Limbajul C suport patru specificatori ai claselor de memorare: auto, extern, static, register. Acetia precizeaz modul de memorare al variabilelor care i urmeaz. Specificatorii de memorare preced restul declara iei 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:
178
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 func ii din modulul n care acestea au fost declarate nu este necesar utilizarea op iunii 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 situa ii ele i pstreaz valoarea la ieirea i intrarea, din sau n func ii. 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. Diferen a dintre o variabil local static i o variabil global este c variabila local static este cunoscut numai n interiorul blocului n care a fost declarat. Un exemplu de func ie care necesit o astfel de variabil este un generator de numere care produce un nou 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 func iei serie() fr ca aceasta s fi fost declarat ca variabil global. Se observ de asemenea c func ia nu atribuie nici o valoare ini ial variabilei numar_serie, ceea ce nseamn c valoarea in ial a acesteia este 0.
179
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; return (numar_serie); } /* initializarea variabilei numar_serie */ serie_start(val_init) int val_init;{ numar_serie = val_init; }
Apelul func iei serie_start() cu o valoare intreag ini ializeaz seria generatoare de numere, dup care apelul func iei serie() va genera 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 func ie calculeaza me pentru ntregi :
int_putere (m, e) int m; register int e; { register int temp; temp = 1; for (; e; e--) temp * = m; return temp; }
n acest exemplu au fost declarate ca variabile registru att e ct i temp. De obicei utilizarea unei variabile registru conduce la micorarea timpului de execu ie al unui program. Exemplu :
unsigned int i; unsigned int delay; main() { register unsigned int j; long t; t = time ('\0'); for (delay = 0; delay < 10; delay++) for (i = 0; i < 64000; i++);
180
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 execu ie al buclei registru este aproximativ jumtate din timpul de execu ie al variabilei non-registru.
Declararea lui strcmp() n main() s-a facut din dou motive: 1) programul trebuie s tie ce tip de valoare returneaz strcmp(); 2) numele trebuie cunoscut de compilator ca i func ie. Deoarece n C nu exist o modalitate de a declara direct un pointer func ie, acesta se declar indirect folosind un pointer void care poate primi orice fel de pointer.
181
Apelul func iei check() se face avnd ca parametri doi pointeri la caracter i un pointer func ie. Instruc iunea : (*cmp)(a, b) realizeaz apelul func iei, n acest caz strcmp() iar a i b sunt argumentele acestuia. Exemplu:
# include <stdio.h> # include <ctype.h> int strcmp(); /* prototip functie */ void main() { char s1[80], s2[80]; int (*p)(); /* p este pointer la functie */ p = strcmp; gets (s1); gets (s2); printf (" Test de egalitate \n "); if (!(*p) (s1,s2)) printf ("Egal\n"); else printf("Neegal\n"); }
Observa ie: Func ia check() poate utiliza direct func ia strcmp() sub forma: check (s1, s2, strcmp); Exemplu:
# include <stdio.h> # include <ctype.h> void check (); int strcmp(); /* prototip functie */ void main() { char s1[80], s2[80]; gets (s1); gets (s2); check (s1, s2, strcmp); } void check (char *a, char *b, int (*cmp) ()) // se defineste functia check() /* cu int (*cmp) () se declara un pointer functie */ { printf (" Test de egalitate \n "); if (!(*cmp) (a,b)) printf ("Egal\n"); else printf ("Neegal\n"); }
182
Capitolul IX PREPROCESAREA
Un preprocesor C realizeaz substituirea macrodefini iilor, o serie de calcule adi ionale i incluziunea fiierelor. Liniile programului surs care ncep cu "#", precedat eventual de spa iu comunic cu preprocesorul. Sintaxa acestor linii este independent de restul limbajului; pot apare oriunde n program i pot avea efect care se men ine (indiferent de domeniul n care apare) pn la sfritul unitatii de translatare. Preprocesorul C con ine urmtoarele directive: #if #include #ifdef #define #ifndef #undef #else #line #elif #error #pragma
va afia pe ecran 0 1 6.
183
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
Deci aceast macrodefini ie 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);
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 "
Observa ie: De obicei macro_names sunt definite cu litere mari. Directiva #define poate fi folosit i pentru precizarea dimensiunii unui tablou, astfel:
# define MAX_SIZE 100 float balance [ MAX_SIZE ];
directiva #define
poate avea i
# define MIN (a ,b) a < b ? a : b void main() { int x, y; x = 10; y = 20; printf("Numarul mai mic este: %d ", MIN (x,y)); }
Dup substituirea lui MIN(a, b) n care a = x i b = y, instruc iunea printf() va arata astfel :
printf("Numarul mai mic este: %d",(x<y)?x:y);
184
Directiva #error Directiva #error for eaz compilatorul s stopeze opera ia 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 Aceasta linie determin procesorul s scrie mesajul de eroare i s termine compilarea. Directiva # include Directiva # include comand compilatorului s includ n fiierul ce con ine 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.
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 }
Observa ie: Expresie_constanta se evalueaz n timpul compilrii. De aceea, aceasta trebuie s con in numai variabile constante definite anterior utilizrii lor. Expresie_constanta nu trebuie s con in operatorul sizeof.
185
Directiva #else lucreaz similar cu instruc iunea determinnd o alternativ de compilare. Exemplu :
# define MAX 10 void main() { #if MAX > 99 printf("Se compileaza pentru tablouri > 99\n"); #else printf("Se compileaza pentru tablouri < 99\n"); #endif }
else
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 op iunilor multiple de tip if / else / if utilizate la compilare. Forma general a directivelor #if , #elif, #endif este:
#if expresie Secventa_de_instructiuni #elif expresie_1 Secventa_de_instructiuni_1 #elif expresie_2 Secventa_de_instructiuni_2 . . . . . . . . . . . . . . #elif expresie_N Secventa_de_instructiuni_N #endif
Dac "expresie" este 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:
MAX > 100 #if VERSIUNE_SERIALA int port = 198; #elif int port = 200; #endif #else char out_buffer[100]; #endif #if
Directivele #ifdef i #ifndef O alt metod de compilare condi ionat utilizeaz directivele #ifdef i #ifndef, care nseamn "if defined" i "if not defined".
186
Dac anterior apari iei secven ei de mai sus s-a definit un macro_name printr-o directiv #define, compilatorul va compila "Secventa_de_instructiuni" dintre #ifdef i #endif. Forma general a lui #ifndef este:
#ifndef macro_name Secventa_de_instructiuni #endif
Dac macro_name nu este definit 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.
Directiva #undef Se utilizeaz pentru a anula defini ia unui macro_name definit printr-o directiv #define. Exemplu:
#define LENGTH 100 #define WIDTH 100 char array[LENGTH][WIDTH]; #undef LENGTH #undef WIDTH
Acest program 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 sec iuni ale programului. Directiva #line O linie cu una din formele:
187
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 instruc iunea #line 100. Directiva #pragma O linie de control de forma:
#pragma nume
determin compilatorul s realizeze o ac iune care depinde de modul de implementare al directivei #pragma. "nume" este numele ac iunii #pragma dorite. Limbajul C definete dou instruc iuni #pragma: warn i inline. Directiva warn determin compilatorul s emit un mesaj de avertisment. Forma general a lui warn este :
#pragma warn mesaj
unde "mesaj" este unul din mesajele de avertisment definite n C. Forma general a directivei inline este :
#pragma inline
i avertizeaz compilatorul c programul surs con ine i cod n limbajul de asamblare. Directiva vid O linie de forma: # nu are nici un efect. Macro_names (macrosimboluri) predefinite Limbajul C con ine c iva identificatori predefini i, care la compilare se expandeaz pentru a produce informa ii speciale. Acetia sunt:
188
__LINE__ o constanta zecimal care con ine numele liniei surs curente. __FILE__ un ir care con ine numele fiierului care se compileaz. __DATA__ un ir care con ine data compilrii sub forma luna/zi/an. __TIME__ un ir care con ine 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.
189
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 ini ial (dinainte de divizare).
n figura de mai sus se prezint un ecran al Microsoft Visual C++ din MSDN 6.0 No iunea cea mai cuprinztoare este aceea de Workspace (spa iu de lucru) care cuprinde n esen o colec ie de proiecte corelate i prelucrabile mpreun. Un workspace cuprinde unul sau mai multe Projects (proiecte) dintre care numai unul este principal i restul sunt subordonate (subprojects). Fiecare proiect este compus la rndul su din mai multe fiiere, de acelai tip sau de tipuri diferite. Prezentarea exhaustiv a organizrii acestui mediu de dezvoltare a aplica iilor 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 con ine mai ales fiiere surs i fiiere de tip header. Aceste fiiere se numesc module. Modulul principal este fiierul care con ine func ia principal main(). Celelalte fiiere surs, dac exist, se numesc module
190
secundare. De obicei, cu fiecare modul secundar se asociaz un fiier header propriu separat. Acest fiier header trebuie s con in toate directivele i declara iile de variabile necesare pentru o corect compilare separat a modulului cu care se asociaz. Pentru a exemplifica cele de mai sus, vom modulariza un exemplu anterior, anume al unei baze de date simple. Workspace-ul va con ine un singur project, care va con ine 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 con ine func ia 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 con ine toate defini iile
func iilor utilizator. Con inutul lor este prezentat n continuare. Modulul bd_main.c este:
# include "local.h" void main() { char choice; init_list(); for (; ;) { choice = menu(); switch (choice) { case 'e' : enter(); break; case 'd' : display(); break; case 's' : save(); break; case 'l' : load(); break; case 'q' : exit(); }}}
191
/* Functia menu() */ char menu() { char s[5],ch; do { printf ("(E)nter\n"); printf ("(D)isplay\n"); printf ("(L)oad\n"); printf ("(S)ave\n"); printf ("(Q)uit\n"); printf (" Alegeti optiunea: "); gets(s); ch=s[0]; } while (!strrchr("edlsq",ch)); return tolower(ch); } /* Functia enter() */ void enter() { register int i; for (i=0; i < SIZE; i++) if (!*addr_info[i].name) break; if (i == SIZE) { printf ("addr_info full \n"); /* Lista plina */ return;} printf ("Name: "); gets (addr_info[i].name); printf ("Street: "); gets (addr_info[i].street); printf ("City: "); gets (addr_info[i].city); printf ("State: "); gets (addr_info[i].state); printf ("Zip: "); scanf ("%d",&addr_info[i].zip);} /* Functia save() */ void save() { register int i; if ((fp = fopen("maillist", "wb")) == NULL) { printf (" Cannot open file\n "); return;}
192
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();}}}
Se poate verifica cum fiecare modul n parte este compilabil fr erori, iar la link-editare nu se semnaleaz, de asemenea, erori.
193
Capitolul X INTRRI/IEIRI
10.1. Func ii de intrare i ieire - stdio.h
Limbajul C nu dispune de instruc iuni de intrare/ieire. Aceste opera ii se realizeaz prin intermediul unor func ii din biblioteca standard a limbajului C. Aceste func ii pot fi aplicate n mod eficient la o gam larg de aplica ii datorit multiplelor facilit i pe care le ofer. De asemenea, ele asigur o bun portabilitate a programelor, fiind implementate ntr-o form compatibil pe toate sistemele de operare. O alt caracteristic a limbajului C const n faptul c nu exist un sistem de gestionare a 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 octe i, 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 octe i fr o interpretare specific. Func iile de intrare/ieire, tipurile i macrodefini iile 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 n elegem o mul ime 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 destina ie de date, care poate fi asociat cu un disc sau cu alte periferice. 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
194
reprezentare, anumite caractere pot fi convertite ntr-o succesiune de caractere, adic s nu existe o rela ie unu la unu ntre caracterele scrise (citite) i ac iunea perifericului. De exemplu, caracterul NL (new line), ' \n ', corespunde grupului CR (carriage return) i LF (line feed). n 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 colec ie de caractere, motiv pentru care se i numesc fiiere text. Toate func iile de intrare/ ieire folosite pn acum se pot utiliza i pentru fiierele text. Un fiier de tip binar este o succesiune de octe i neprelucra i care con in date interne, cu proprietatea c dac sunt scrise i citite pe acelai sistem, datele sunt egale. Aceste fiiere sunt organizate ca date binare, adic octe ii nu sunt considera i ca fiind coduri de caractere. La fiierele binare nregistrarea se consider c este o colec ie 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. Func iile specializate de nivel superior au denumiri asemntoare cu cele de nivel inferior, doar prima liter a numelui este f. n practic opera iile 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
195
s se reduc numrul de opera ii de acces la disc. n acest scop se folosesc bufferele. Un buffer este o zon de memorie n care sistemul memoreaz o cantitate de informa ie (numr de octe i), n general mai mare dect cantitatea solicitat de o opera ie de I/O. Dac un program efectueaz o opera ie de citire a 2 octe i dintr-un fiier, atunci sistemul citete ntr-un buffer ntreg sectorul de pe disc (512 octe i) n care se gsesc i cei 2 octe i solicita i, eventual chiar mai mult, n func ie de dimensiunea bufferului (zonei tampon). Dac n continuare se vor solicita nc 2 octe i, acetia vor fi prelua i din bufferul din memorie, fr a mai fi nevoie s mai accesm discul pe care se afl fiierul din care se face citirea. Opera iile de citire continu n acest mod pn la citirea tuturor octe ilor 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 opera ie de scriere a unui numr de octe i pe disc, acetia se vor nscrie de fapt secven ial n buffer i nu direct pe disc. Scrierea va continua astfel pn la umplerea bufferului, moment n care sistemul de operare efectueaz o opera ie de scriere a unui secto de pe disc cu cei 512 octe i din buffer (se golete bufferul prin scriere). n acest fel, reducnd numrul de opera ii de acces la disc (pentru citire sau scriere) crete viteza de execu ie a programelor i fiabilitatea dispozitivelor de I/O. Bufferele au o mrime implicit, dar ea poate fi modificat prin program. Dimensiunea trebuie aleas n func ie de aplica ie innd cont de faptul c prin mrirea bufferului crete viteza de execu ie 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 octe i, din care 254 sunt pui la dispozi ie. 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 func ie de sistemul de operare considerat. Pentru sistemele de operare MS-DOS sau MIX i RSX11 se tasteaz CTRL/Z iar pentru UNIX se tasteaz CTRL/U. Un 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.
196
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 con ine toate datele necesare pentru controlul fiierului. Opera iile de deschidere i nchidere a fiierelor se poate realiza n C prin func ii specializate din biblioteca standard I/O a limbajului. Alte opera ii care sunt executate frecvent n prelucrarea 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 Pozi ionarea ntr-un fiier Redenumirea unui fiier tergerea unui fiier Ca i opera iile de deschidere i nchidere de fiiere, opera iile indicate mai sus pot fi realizate printr-un set de func ii aflate n biblioteca standard I/O a limbajului. Aceste func ii realizeaz ac iuni similare sub diferite sisteme de operare, dar multe dintre ele pot depinde de implementare. n cele ce urmeaz se prezint func iile care au o utilizare comun pe diferite medii de programare i sunt cele mai frecvent utilizate.
197
Descriere
Deschidere Creare Citire Scriere nchidere Pozi ionare
Nume func ie de nivel inferior _open _creat _read _write _close _lseek _unlink _rename
Nume func ie de nivel superior fopen fcreate fread fwrite fclose fseek remove rename
tergere Redenumire
n afara acestor func ii principale mai exist anumite func ii specializate, cum ar fi: - Func ii pentru prelucrarea pe caractere a unui fiier: putc (scriere caracter) i getc (citire caracter). - Func ii pentru Intrri/Ieiri cu format: fscanf i fprintf. - Func ii 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 informa ii despre ele. Acest lucru se realizeaz prin opera ia de deschidere a fluxurilor (stream-urilor). Pointerul fiier n urma opera iei de deschidere se creaz n memorie o variabil de tip structur FILE care este o structur predefinit. n aceast variabil, care se numete bloc de control al fiierului, FCB (File Control Block) sistemul pstreaz informa ii despre fiierul deschis, precum: Nume Dimensiune Atribute fiier Descriptorul fiierului Un pointer-fiier este un pointer la informa iile care definesc diferitele aspecte ale unui fiier: nume, stare, pozi ie 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 abstrac ie de cazurile speciale de calculatoare tip
198
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 opera iile 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 opera ie de citire de la stream-ul stdin nseamn citire de la tastatur. Bufferul folosit are o dimensiune de 254 de caractere i bufferul se 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 opera ie de scriere la stream-ul stdout nseamn scriere pe ecran. Spre deosebire de stdin, stdout este ne-bufferizat deoarece orice scriere pe ecran se face direct la scrierea unui caracter n 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. FILE *stdaux; care se refer la primul port serial COM1. Este ne-bufferizat.
199
drept buffer i trebuie s in cont de faptul c aceast bufferizare este la nivel linie. Numele func iilor de nivel inferior, orientate pe text (transfer de octe i) ncep de obicei cu _ (underline). Dac un fiier se deschide n modul text, atunci, n cazul citirii dintr-un fiier, secven a de octe i CR-LF (0DH, 0AH) este translatat (nlocuit) cu un singur caracter LF, iar n cazul scrierii n fiier caracterul LF este expandat la secven a CR-LF. De asemenea, n cazul MS-DOS sau Windows CTRL/Z este interpretat n cazul citirii drept caracter de sfrit de fiier (EOF).
unde:
200
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 func ie de opera ia 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 func ia _open returneaz valoarea (-1). int _open( const char *filename, int oflag [, int pmode] ); este defini ia general a func iei _open. Modul de acces mod se poate furniza n mod explicit printr-o variabil de tip ntreg (oflag) care poate avea valorile:
Variabila mod
_O_RDONLY
201
Men ionm 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 op ionale 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 func ia _creat pentru a-l deschide. De fapt se deschide prin creare un fiier inexistent. Func ia este definit astfel: int _creat( const char *filename, int pmode ); n care parametrii au fost descrii mai sus. Protec ia unui fiier este dependent de sistemul de operare. Spre exemplu, n UNIX protec ia se definete prin 9 bi i ataa i oricrui fiier, grupa i n 3 grupe de cte 3 bi i. Fiecare bit controleaz o opera ie de citire, scriere, execu ie. Protec ia opera iilor se exprim fa de proprietar, grup sau oricine altcineva. Numrul octal 0751 permite proprietarului toate cele 3 opera ii indicate mai sus (7 = 1112), grupul la care apar ine proprietarul poate citi i executa fiierul (5 = 1012) iar al i utilizatori pot numai executa fiierul (1 = 0012). Func ia _creat poate fi apelat i n cazul n care se deschide un fiier deja existent, caz n care se pierde con inutul vechi al fiierului respectiv i se creaz n locul lui unul nou cu acelai nume. Fiecare din func iile _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.
202
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 opera ia 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 func iile perror (print error) sau strerror (string error) pentru tratarea erorilor. Constantele manifest pentru aceste variabile sunt declarate n STDLIB.H dup cum urmeaz: extern int _doserrno; extern int errno; errno este setat de o eroare ntr-un apel de func ie la nivel de sistem (la nivelul inferior). Deoarece errno pstreaz valoarea setat de ultimul apel, aceast valoare se poate modifica la fiecare apel de func ie sistem. Din aceast cauz errno trebuie verificat imediat nainte i dup un apel care poate s-o modifice. Toate valorile errno, definite drept constante manifest n ERRNO.H, sunt compatibile UNIX. Valorile valide pentru aplica iile Windows pe 32 de bi i sunt un subset al acestor valori UNIX. Valorile specificate mai sus sunt valabile pentru aplica ii Windows pe 32 de bi i. La o eroare, errno nu este setat n mod necesar la aceeai valoare cu codul erorii de sistem. Numai pentru opera ii de I/O se folosete _doserrno pentru a accesa codul erorilor sistemului de operare echivalent cu codurile semnalate de errno. Exemplu: Acest program foloseste _open pentru a deschide un fisier numit OPEN.C pentru citire si un fisier numit OPEN.OUT scriere. Apoi fisierele sunt inchise
#include #include #include #include <fcntl.h> <sys/types.h> <sys/stat.h> <io.h>
203
#include <stdio.h> void main( void ) { int fh1, fh2; fh1 = _open( "OPEN.C", _O_RDONLY ); if( fh1 == -1 ) perror( "open failed on input file" ); else { printf( "open succeeded on input file\n" ); _close( fh1 );}
fh2=_open("OPEN.OUT",_O_WRONLY|_O_CREAT,_S_IREAD|_S_IWRITE);
if( fh2 == -1 ) perror( "Open failed on output file" ); else {printf( "Open succeeded on output file\n" ); _close( fh2 );}}
204
Func ia _write scrie count octe i din buffer n fiierul asociat cu descriptorul handle. Opera ia de scriere ncepe la pozi ia curent a pointerului de fiier asociat cu fiierul dat. Dac fiierul este deschis cu indicatorul _O_APPEND, opera ia de scriere ncepe la sfritul fiierului. Dup o opera ie de scriere pointerul de fiier este incrementat cu numrul de bi i 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 opera iei de ieire. n general trebuie ca la revenirea din func ia _write s avem nr=n, ceea ce semnific faptul c s-au scris pe disc exact numrul de bi i din buffer. n caz contrar scrierea este eronat: aceasta semnific faptul c pe disc a rmas mai pu in spa iu (n octe i) dect numrul de octe i ai bufferului. Dac valoarea returnat este -1, se semnalizeaz eecul opera iei 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 spa iului pe disc pentru opera ia de scriere). Func ia _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 func ia _open sau _creat deoarece fiierele respective se deschid automat la lansarea programului. Exemplu:
/*Acest program deschide un fisier pentru scriere si foloseste _write pentru a scrie octeti in fisier*/ #include <io.h> #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> char buffer[]="This is a test of '_write' function"; void main( void ) { int fh; unsigned byteswritten; if((fh=_open("write.o",_O_RDWR|_O_CREAT, _S_IREAD|_S_IWRITE))!=-1) { if((byteswritten = write(fh,buffer,sizeof(buffer)))== -1)
205
perror( "Write failed" ); else printf( "Wrote %u bytes to file\n", byteswritten ); _close( fh );}}
206
fiierul nu este deschis pentru citire sau dac este blocat, func ia returneaz valoarea negativ -1 i seteaz variabila errno la EBADF. Tipul erorii i depistarea ei este dependent de sistemul de operare utilizat. Dac n = 1, se citete un singur octet. De obicei, nu este eficient s se citeasc cte un octet dintr-un fiier, deoarece apelurile multiple ale func iei _read pot conduce la un consum de timp apreciabil. Dimensiunea maxim a lui n este dependent de sistemul de operare. O valoare utilizat frecvent este 512, valoare optim pentru MS-DOS sau pentru UNIX. Func ia _read citete maximum count bi i n zona buffer din fiierul cu descriptorul handle. Opera ia de citire ncepe de la pozi ia curent a pointerului de fiier asociat cu fiierul respectiv. Dup o opera ie 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. Func ia _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 func ia _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" ); exit( 1 ); } /* Read in input: */ if((bytesread = _read(fh,buffer,nbytes)) <= 0) perror( "Problem reading file" ); else Necesara numai pentru definirea
207
Func ia _close nchide fiierul asociat cu descriptorul handle. Func ia _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 func ie df este descriptorul de fiier (handle) al fiierului pe care dorim s-l nchidem.
208
pozi iona oriunde n fiierul respectiv O astfel de pozi ionare este posibil pe hard-uri i floppy-uri prin func ia _lseek. Defini ia func iei este:
long _lseek( int handle, long offset, int origin );
Func ia _lseek mut pointerul de fiier asociat cu descriptorul handle (df) pe o nou loca ie care este situat la offset octe i de origin. Urmtoarea opera ie de citire/scriere se va efectua de la noua loca ie. Argumentul origin trebuie s fie una dintre urmtoarele constante, definite n STDIO.H: SEEK_SET nceputul fiierului (valoare 0) SEEK_CUR pozi ia curent a pointerului de fiier (valoare 1) SEEK_END sfritul fiierului (valoare implicit 2) Func ia _lseek returneaz valoarea 0 la pozi ionare corect i -1 la incident. Ea poate fi apelat prin:
v = _lseek(df, deplasament, origine)
unde: v este o variabil de tip ntreg creia i se atribuie valoarea returnat de ctre func ie (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 con ine numrul de octe i 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 pozi ia curent a capului de citire/scriere; 2 - deplasamentul se socotete de la sfritul fiierului. Men ionm c prin apelul lui _lseek nu se realizeaz nici un fel de transfer de informa ie ci numai pozi ionarea n fiier. Opera ia urmtoare realizat prin apelul func iei _read sau _write se va realiza din aceast pozi ie. Spre exemplu, apelul:
v = _lseek(df, 0l, 2)
permite s se fac pozi ionarea la sfritul fiierului. n continuare se pot aduga articole folosind func ia _write. Pozi ionarea la nceputul fiierului se face prin apelul:
v = _lseek(df, 0l, 0)
Exemplu:
#include <io.h> #include <fcntl.h> #include <stdlib.h>
209
#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 );}
210
Men ionm c cel de-al doilea parametru al func iei _read sau _write trebuie s fie pointer spre caractere. Lucrul la nivelul inferior nu este chiar 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);}
Citirea nu se va opri dup 3 caractere, ci func ia _read va continua s func ioneze pn la tastarea ENTER (CR+LF). Imediat func ia _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.
211
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
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 con innd numerele de ordin impar citite de la intrarea standard
212
(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 func ii definite de utilizator care s fac apel la func iile de nivel inferior. Programul arat astfel:
# include <stdio.h> # include <io.h> # include <sys/types.h> # include <sys/stat.h> # include <fcntl.h> #include <stdlib.h> char nume1[]="fis1.dat"; char nume2[]="fis2.dat"; union unr { float nr; char tnr[sizeof(float)];}; union unr nrcit; int creare_fis(const char *nume) { int df; if ((df=_creat(nume,_S_IWRITE|_S_IREAD))==-1) { printf("%s: ",nume); printf("Nu se poate deschide fisierul in creare\n"); exit(1);} return df;} void scrie_fis(int df,char *nume) {if(_write(df,nrcit.tnr,sizeof(float))!=sizeof(float)) { printf("%s: ",nume); printf("Eroare la scrierea fisierului\n");exit(1);}} void date_fis(int df1,char *nume1,int df2,char *nume2) { int j=1,i; while ((i=scanf("%f",&nrcit.nr))==1) { if(j%2) scrie_fis(df1,nume1); else scrie_fis(df2,nume2); j++;}} void inchid_fis(int df, char *nume) { if (_close(df)<0) { printf("%s: ",nume); printf("eroare la inchiderea fisierului\n"); exit(1);}} int deschid_fis_cit(char *nume) { int df; if ((df=_open(nume,_O_RDONLY))==-1) {
213
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 func ia 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 pozi iona capul de citire/scriere peste nregistrarea urmtoare.
# include <stdio.h> # include <io.h> # include <sys/types.h> # include <sys/stat.h> # include <fcntl.h> #include <stdlib.h> char nume[]="fis.dat"; union unr { float nr; char tnr[sizeof(float)];}; union unr nrcit; int creare_fis(const char *nume) { int df; if ((df=_creat(nume,_S_IWRITE|_S_IREAD))==-1) { printf("%s: ",nume); printf("Nu se poate deschide fisierul in creare\n");
214
exit(1);} return df;} void scrie_fis(int df,char *nume) { if (_write(df,nrcit.tnr,sizeof(float))!=sizeof(float)) { printf("%s: ",nume); printf("Eroare la scrierea fisierului\n");exit(1);}} void date_fis(int df,char *nume) { while (scanf("%f",&nrcit.nr)==1) { scrie_fis(df,nume);}} void inchid_fis(int df, char *nume) { if (_close(df)<0) { printf("%s: ",nume); printf ("eroare la inchiderea fisierului\n"); exit(1);}} int deschid_fis_cit(char *nume) { int df; if ((df=_open(nume,_O_RDONLY))==-1) { printf("%s: ",nume); printf("Nu se poate deschide fisierul in citire\n"); exit(1);} return df;} void list_fis(int df,char *nume) { int j,i; j=1; while ((i=_read(df,nrcit.tnr,sizeof(float)))>0) { printf("%6d: %g\n",j,nrcit.nr); // avans peste o inregistrare if(_lseek(df,(long)sizeof(float),1)==-1l) break; j+=2;} if (i<0) { printf("%s: ",nume); printf("Eroare la citire din fisierul\n"); exit(1);} j=2; // pozitionare pe prima inregistrare _lseek(df,0l,0); // avans la inregistrarea a doua _lseek(df,(long)sizeof(float),1); while((i=_read(df,nrcit.tnr,sizeof(float)))>0) {printf("%6d: %g\n",j,nrcit.nr); // avans peste o inregistrare if(_lseek(df,(long)sizeof(float),1)==-1l) break; j+=2;} if (i<0) { printf("%s: ",nume); printf("Eroare la citire din fisierul\n"); exit(1);} _close(df);}
215
Atragem aten ia asupra modului n care lucreaz func iile 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 func iilor specializate scanf i printf, pentru lucrul cu discul variabila float este tratat sub forma unui grup de 4 octe i care se scriu sau se citesc pe disc aa cum este reprezentarea lor intern. Exist func ii specializate pentru scrierea/citirea pe disc cu format, dar care sunt de nivel superior. Ceea ce merit s subliniem este faptul c echivalentele de nivel superior pentru fiiere ale func iilor printf() i scanf() sunt fprintf() i fscanf(). Echipamentele periferice pot fi considerate fiiere externe i deci func iile specializate pentru I/O cu fiiere pot fi folosite i pentru opera ii de I/O cu echipamentele periferice. Func iile printf i scanf sunt proiectate pentru a lucra implicit cu fiierele stdout respectiv stdin, deci cu monitorul i tastatura.
216
Func ia deschide fiierul al crui nume este specificat prin "filename" (de obicei un fiier disc) i ntoarce un pointer la FILE pentru opera ie reuit i NULL pentru opera ie nereuit. Varibilele permise pentru modul "mode" sunt:
a a+ r r+ w w+ b t c n _O_WRONLY | _O_APPEND (usual _O_WRONLY | _O_CREAT | _O_APPEND) _O_RDWR | _O_APPEND (usual _O_RDWR | _O_APPEND | _O_CREAT ) _O_RDONLY _O_RDWR _O_WRONLY(usual _O_TRUNC) _O_WRONLY | _O_CREAT |
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 con inute n fiier. Modul "a+" este cerut pentru adugarea la sfritul unui fiier care are marker terminator CTRL/Z = EOF. Dac modul "mode" include "b" dup litera ini ial, ca n "rb" sau "w+b" se indic un fiier binar. Numele fiierului con ine cel mult FILENAME_MAX caractere. La un moment dat pot fi deschise cel mult FOPEN_MAX fiiere. Men ionm c stdin, stdout i stderr sunt pointeri spre tipul FILE i permit ca func iile 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
217
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 */
Dac pentru opera ia de citire se ncearc deschiderea unui fiier inexistent, fopen() va returna o eroare.
unde "fp" este pointerul la fiier returnat dup apelul func iei fopen(). Func ia fclose() se utilizeaz pentru a nchide un fiier deschis cu fopen(). Func ia 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 execu ia func iei 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 situa ia 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; /* 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
218
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 );}
Func ia remove()
int remove(char *filename);
Func ia 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.
unde "fp" este un pointer la fiier. Func ia ferror() ntoarce o valoare diferit de zero dac s-a detectat o eroare i o valoare 0 dac nu s-a detectat nici o eroare. b) Func ia feof()
int feof(FILE *fp)
Func ia feof() ntoarce o valoare diferit de zero dac indicatorul de sfrit de fiier este valid i o valoare zero dac indicatorul de sfrit de fiier nu este valid. c) Func ia perror()
void perror(const char *s)
219
Func ia perror() scrie s i un mesaj de eroare care depinde de implementare, corespunzator cu intregul din errno.h, astfel:
fprintf(stderr,"%s %s\n, s, "error message")
Func ia 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(). Func ia ntoarce numrul de obiecte citite, acesta putnd fi mai mic dect cele cerute. Pentru a determina starea func iei se pot utiliza func iile feof(), ferror(). b) Func ia fwrite() Permite scrierea unui bloc de date. Forma general de declarare:
int fwrite(void *buffer,int num_bytes,int count, FILE *fp)
Func ia fwrite() scrie din zona (tabloul) "buffer" n fiierul indirectat prin "fp", "count" obiect de lungime "nr_bytes". Func ia 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; } 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;}
220
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 con inutul.
#include <stdio.h> void main( void ) { FILE *stream; char list[30]; int i, numread, numwritten; /* Deschide fisierul in mod text: */ if( (stream = fopen( "fread.out", "w+t" )) != NULL ) { for ( i = 0; i < 25; i++ ) list[i] = (char)('z' - i); /* Scrie 25 caractere in fisier */ numwritten = fwrite(list,sizeof(char),25,stream ); printf( "S-au scris %d caractere\n", numwritten ); fclose( stream );} else printf( "Probleme cu deschiderea fisierului\n" ); if( (stream = fopen( "fread.out", "r+t" )) != NULL ) { /* Incearca sa citeasca 25 caractere */ numread = fread( list, sizeof( char ), 25, stream ); printf("Nr. caracterelor citite = %d\n", numread); printf( "Continutul bufferului = %.25s\n", list ); fclose( stream );} else printf( "Fisierul nu a putut fi deschis\n" );}
unde "fp" este un pointer-fiier returnat prin apelul func iei fopen(), "offset" este deplasamentul (numr octe i) noii pozi ii fa de "origin", iar "origin" este una din urmtoarele macrodefini ii: SEEK_SET - nceput de fiier;
221
SEEK_CUR - pozi ie curent; SEEK_END - sfrit de fiier. Func ia returneaz 0 dac se execut cu succes i o valoare nenul n caz de eroare. Dac nu s-a efectuat nici o opera ie de I/O de la deschiderea fiierului n mod APPEND (adugare), atunci pointerul indic nceputul fiierului. Nu se recomanda utilizarea func iei fseek() pentru fiiere text; se sugereaz utilizarea acesteia numai pentru fiiere binare. Transla iile CR-LF efectuate n mod text pot cauza func ionarea defectoas a func iei fseek. Func ia fopen i toate celelalte func ii vor cuta s nlture caracterul CTRL/Z terminator de fiier (EOF). Singurele opera ii garantate s func ioneze corect cnd se utilizeaz fseek asupra fiierelor deschise n mod text este pozi ionarea cu offset 0 relativ la orice pozi ie din fiier i pozi ionarea fa de nceputul fiierului cu un offset returnat de func ia ftell(). Func ia ftell() este definit astfel:
long ftell( FILE *stream );
Func ia returneaz valoarea curent a pointerului fiier. Pozi ia este exprimat prin offsetul fa de nceputul fiierului. n cazul fiierelor deschise n mod text, acest offset nu reflect ntotdeauna exact numrul de octe i datorit transla iei CR-LF. Este preferat folosirea simultan a func iilor 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:
func1() /* se declara func ia func1() */ { FILE *fp; if ((fp = fopen("test", "rb")) == NULL) { printf ("Cannot open file\n"); exit (1); } fseek(fp, 235L, 0); return getc(fp);} /* se citeste un caracter de la pozitia 235 */
222
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 );}}
Func ia fprintf() realizeaz conversia i scrierea la ieire n fiierul indirectat cu "fp" sub controlul formatului, "format". Valoarea ntoars de func ie este numrul de caractere scrise, sau orice valoare negativ, dac apare o eroare. irul "format" con ine 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;
223
"#" - 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 pu in egal cu cea specificat, dac va fi nevoie i mai mare. Dac argumentul specificat are mai pu ine caractere, atunci cmpul va fi umplut la stnga sau la dreapta, func ie de aliniere. Caracterul de umplere este de obicei spatiul, dar este 0 dac s-a ales aceast optiune (exemplu: %05d). 3) Un punct ce separ lungimea 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 pu in 10 caractere cu 4 caractere dup virgul; %5.7s - va afia un ir de cel pu in 5 caractere dar nu mai lung de 7 caractere; %-10.2f - va alinia la stnga un numr real cu 2 zecimale ntrun cmp de reprezentare de cel pu in 10 caractere. Descriptorii de conversie utiliza i de C sunt: %c - un singur caracter. %d, %i - nota ie zecimala cu semn. %x, %X - nota ie hexazecimal fr semn (fr 0x sau 0X). %u - nota ie zecimal fr semn. %s - caracterele din ir sunt tiprite pn se ntlnete '\0' sau ct timp numrul de caractere tiprit precizia. %f - nota ie 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 - nota ie zecimal de forma: [-]m.dddddde+/-xx sau [-]m.ddddddE+/-XX
224
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 - nota ie 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 func iei 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 func iei fprintf() s afieze short int. Atunci %hu va preciza c data este de tip short unsigned int.] b) Func ia printf() Forma func iei :
int printf("format", lista-argumente)
Exemplu:
printf() ("%-5.2f", 123.456) ("%5.2f", 3.4565) ("%10s", "hello") ("%-10s", "hello") (%5.7s", "123456789") ieire 123.45 3.45 hello hello 1234567
225
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" );}
Func ia fscanf() citete din fiierul indirectat prin "fp" sub controlul formatului "format" i atribuie valorile citite argumentelor urmtoare, fiecare argument trebuind s fie un pointer. Func ia ntoarce EOF dac se detecteaz sfritul de fiier sau apare o alt eroare nainte de orice conversie. n caz contrar, func ia ntoarce numrul de elemente care au primit valori. irul "format" poate con ine: - specificatori de conversie, constnd dintr-un caracter %, un caracter op ional de suprimare a atribuirii; un numr op ional care indic lungimea cmpului, un caracter op ional h, l sau L, care indic lungimea argumentului i un caracter de conversie; - spa ii sau caractere HT sau VT care se ignor; - caractere obinuite (diferite de %) care indic urmtorul caracter diferit de caracterele albe cu care ncepe fiierul de intrare. Un cmp de intrare se definete ca un ir de caractere diferite de cele albe i se ntinde pn la urmtorul caracter alb (spa iu, 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 utiliza i 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.
226
%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 func ia fscanf() s citeasc un caracter ce coincide cu cele din "format". De exemplu, "%d, %d" face c fscanf() s citeasc un ntreg, apoi caracterul "," i apoi un alt ntreg. Dac calculatorul nu 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) Func ia scanf() Forma func iei:
int scanf("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 spa iu, tab sau linie noua */
Un * plasat ntre % i caracterul de conversie, va suspenda atribuirea datei citite. Astfel, instruc iunea :
scanf("%d%*c%d", &x, &y);
face ca, dac de la tastatur se introduce 10/20, 10 s fie atribuit lui x, %*c este ignorat (nu este atribuit), iar 20 se atribuie lui y. Instruc iunea :
scanf("%20s", sir);
227
citete nu mai mult de 20 caractere n variabil ir. Dac se introduce un ir de mai mult de 20 caractere, vor fi re inute numai primele 20, iar restul se pierd. Pentru caracterele rmase se poate apela din nou func ia scanf() sub forma :
scanf("%s", sir);
care va plasa restul caracterelor tot n "ir". Dac de la tastatura se introduce 10#20, instruc iunea :
scanf("%s#%s", &x, &y);
nu se ncheie dect dac dup ultimul caracter se introduce un spa iu. Exemplu de utilizare a func iilor fscanf i fprintf.
#include <stdio.h> FILE *stream; void main( void ) { long l; float fp; char s[81]; char c; stream = fopen( "fscanf.out", "w+" ); if( stream == NULL ) printf( "Fisierul fscanf.out nu a fost deschis\n" ); else { fprintf( stream, "%s %ld %f%c", "a-string", 65000, 3.14159, 'x' ); /* Seteaza pointerul la inceputul fisierului: */ fseek( stream, 0L, SEEK_SET ); /* Citeste datele inapoi din fisierul disc: */ fscanf( stream, "%s", s ); fscanf( stream, "%ld", &l ); fscanf( stream, "%f", &fp ); fscanf( stream, "%c", &c ); /* Tipareste datele citite din fisier: */ printf( "%s\n", s ); printf( "%ld\n", l ); printf( "%f\n", fp ); printf( "%c\n", c ); fclose( stream ); }}
/* Acest program scrie date cu format cu printf intr-un fisier. Apoi foloseste fscanf pentru a citi datele din fisier */
228
Func ia 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 func iei fgetc().
/* Acest program foloseste getc pentru a citi 80 de caractere dintr-un fisier si apoi le plaseaza dintr-un buffer la intrarea standard */
#include <stdio.h> #include <stdlib.h> void main( void ) { FILE *stream; char buffer[81]; int i, ch; /* Deschide fisierul pentru a citi o inregistrare */ if( (stream = fopen( "fgetc.c", "r" )) == NULL ) exit( 0 ); /* Citeste primele 80 de caractere si le plaseaza in "buffer": */ ch = fgetc(stream); for(i=0;(i<80) && (feof(stream)==0); i++ ) { buffer[i] = (char)ch; ch = fgetc( stream ); } /* Adauga null la sfarsitul fisierului */ buffer[i] = '\0'; printf( "%s\n", buffer ); fclose( stream );}
b) Func ia getc()
int getc (FILE *fp)
Aceast func ie este identic cu fgetc() cu deosebirea c este o macrodefini ie, putnd s evalueze fiierul mai mult dect o dat. Observa ie: "fp" este un pointer-fiier returnat de func ia 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) Func ia getchar()
int getchar(void)
Func ia getchar() este echivalent cu getc (stdin) . Dezavantajul func iei 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) Func iile getche() i getch()
int getche(void)
229
int
getch(void)
Func iile introduc un caracter de la tastatur. Func iile asteapt pn se apas o tast i apoi ntorc valoarea acesteia. Func ia getche() preia un caracter cu "ecou" iar getch() preia caracterul fr ecou. e) Func ia gets()
char *gets(char *s)
Func ia 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'. Func ia ntoarce vectorul s sau EOF, n caz de eroare. f) Func ia fgets()
char *fgets(char *s, int n, FILE *fp)
Func ia fgets() 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'. Func ia ntoarce tabloul s, sau NULL, dac apare o eroare sau sfrit de fiier. Exemplu de folosire a func iei fgets.
/* Acest program utilizeaza fgets pentru afisarea unei linii dintr-un fisier la display */
#include <stdio.h> void main( void ) { FILE *stream; char line[100]; if( (stream = fopen( "fgets.c", "r" )) != NULL ) { if( fgets( line, 100, stream ) == NULL) printf( "fgets error\n" ); else printf( "%s", line); fclose( stream ); }}
a') Func ia
fputc()
Func ia 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 func ie fputc.
/* Acest program foloseste fputc si _fputchar pentru a trimite un sir de caractere la stdout. */
#include <stdio.h> void main( void ) { char strptr1[] = "Test pentru fputc !!\n"; char strptr2[] = "Test pentru _fputchar!!\n"; char *p; /* Tipareste linia folosind fputc. */
230
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, func iile care ncep cu _ (subliniere) dar i cu f (de la file) sunt destinate lucrului cu interfe ele standard stdin i stdout. b') Func ia putc()
int putc(int ch, FILE *fp)
Func ia putc() este echivalenta cu fputc() cu excep ia c este o macrodefinitie, putnd evalua fiierul mai mult dect o dat. c') Func ia putchar()
int putchar(int ch)
Func ia putchar(ch) este echivalenta cu putc (ch, stdout). d') Func ia de la punctul d) nu are un corespondent pentru ieire. e') Func ia puts()
int puts(const char *s)
Func ia puts() scrie irul "s" i NL n "stdout". ntoarce EOF, dac apare o eroare, sau o valoare nenegativ n caz contrar. f') Func ia fputs()
int fputs(const char *s,FILE *fp)
Func ia fputs() scrie irul "s" (care nu este neaprat necesar s con in '\n') n fiierul "fp". ntoarce EOF, n caz de eroare, o valoare nenegativ, n caz contrar. Spre exemplu,
/* Acest program foloseste fputs pentru a scrie o linie la terminalul standard */
#include <stdio.h> void main( void ) { fputs( "Hello world from fputs.\n", stdout );}
Func ia ungetc()
int ungetc(int ch,FILE *fp)
Func ia ungetc() pune argumentul "ch" inpoi n 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. Func ia ntoarce caracterul ce trebuie pus, sau EOF, n caz de eroare. Func iile getw() i putw() Aceste func ii se folosesc pentru a citi, respectiv a scrie ntregi dintr-un sau ntr-un fiier disc. Aceste func ii lucreaz exact c func iile getc() i putc(). Exemplu de utilizare a functiilor fprintf() i fscanf() :
231
232
233
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.
unde: (x1,y1) reprezint coordonatele col ului stnga sus ; (x2,y2) reprezint coordonatele col ului dreapta jos. La un moment dat este activ o singur fereastr i anume acea definit la ultimul apel al func iei window. Dac parametri de la apelul func iei window sunt erona i, aceasta nu are nici un efect.
234
Func ia clrscr pozi ioneaz cursorul pe caracterul din stnga sus al ferestrei active, adic n pozi ia de coordonate (1,1).
unde (x, y) reprezint coordonatele caracterului pe care se plaseaz cursorul. Dac la apelul func iei coordonatele sunt definite n afara ferestrei active, func ia este ignorat. Pozi ia cursorului din fereastra activ poate fi determinat cu ajutorul func iilor wherex i wherey care returneaz 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 func ia geninterrupt:
{ _AH=1; _CH=0x20; geninterrupt(0x10); } Cursorul poate fi rafiaat utiliznd urmtoarea secven : { _AH=1; _CH=6; _CL=7; geninterrupt(0x10);}
unde culoare este un ntreg n intervalul [0, 7] i are semnifica ia din tabelul de mai sus. Pot fi setate ambele culori precum i clipirea caracterului folosind func ia textattr ce are urmtorul prototip:
void textattr (int atribut)
atribut=16*culoare_fond+culoare_caracter+clipire; 235
Culoarea caracterelor se seteaz cu ajutorul func iei textcolor ce are urmtorul prototip:
void textcolor(int culoare);
unde culoare este un ntreg n intervalul [0,15] a crui semnifica ie 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 Galben Alb Clipire Constant simbolic BLACK BLUE GREEN CYAN RED MANGETA BROWN LIGHTGRAY DARKGRAY LIGHTBLUE LIGHTGREEN LIGHTCYAN LIGHTRED LIGHTMANGETA YELLOW WHITE BLINK Valoare
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 128
236
- 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 col ului 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; func ia 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; func ia returneaz codul ASCII al caracterului citit de la tastatur. - int kbhit (void); - controleaz dac s-a tastat ceva la tastatur. Dac a fost 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;
237
//memoreaza partea din ecran pe care se va afisa fereastra if(istiva==MAX){ printf("\nPrea multe ferestre!"); exit(1); } if ((stiva[istiva]=(ELEM *)farmalloc(sizeof(ELEM)))==0){ printf("\nMemorie insuficienta\n"); exit(1); } stiva[istiva]->x=st; stiva[istiva]->y=sus; stiva[istiva]->u=dr; stiva[istiva]->v=jos; if((gettext(st,sus,dr,jos,stiva[istiva]->zonfer))==0){ printf("\nEroare la memorarea ecranului!"); exit(1); } istiva++; //Activeaza fereastra si o afiseaza pe ecran window(st,sus,dr,jos); textattr(16*fond+culoare); clrscr(); //Trasare chenar if (chenar){ textcolor(WHITE); highvideo(); switch(chenar){ case SIMPLU: putch(218); break; case DUBLU: putch(201); break; } orizontal(dr-st-2,chenar); switch(chenar){ case SIMPLU: putch(191); break; case DUBLU: putch(187); break; } vertical(jos-sus,1,2,chenar); gotoxy(1,jos-sus+1); switch(chenar){ case SIMPLU: putch(192); break; case DUBLU: putch(200);
238
break; } orizontal(dr-st-2,chenar); vertical(jos-sus-1,dr-st,2,chenar); gotoxy(dr-st,jos-sus+1); switch(chenar){ case SIMPLU: putch(217); break; case DUBLU: putch(188); break; } normvideo(); textattr(16*fond+culoare); } gotoxy(3,3); cprintf("%d",n); //Ascunde cursorul _AH=1; _CH=0x20; geninterrupt(0x10); } void orizontal(int a,int chenar) { while(a--) switch(chenar){ case SIMPLU: putch(196); break; case DUBLU: putch(205); break; } } void vertical(int a,int col,int lin,int chenar) { while(a--) { gotoxy(col,lin++); switch(chenar){ case SIMPLU: putch(179); break; case DUBLU: putch(186); break; } } } void main(void) { clrscr(); fereastra(4,4,60,20,3,10,2,6); getch(); }
239
unde: Pointerul gd pstreaz adresa uneia din valorile din tabelul urmtor (n func ie 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 func ii standard corespunztoare adaptorului grafic. Aceste func ii se numesc drivere. Ele se afl n subdirectorului BGI. Func ia detectgraph detecteaz adaptorul grafic prezent la calculator i pstreaz valoarea corespunztoare acestuia n zona spre care pointeaz gd.
240
Rezolu ie
320*200 320*200 320*200 320*200 640*200 640*200 640*350 640*200 640*350 640*480
EGA VGA
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 func iei detectgraph trebuie s fie urmat de apelul func iei initgraph. Aceasta seteaz modul grafic n conformitate cu parametri stabili i de apelul prealabil al func iei detectgraph i are urmtorul prototip:
void far *cale); initgraph(int far *gd,int far *gm, int far
unde:
gd i gm sunt pointeri ce au aceeai semnifica ie ca i n cazul func iei detectgraph; cale este pointer spre irul de caractere care definete calea subdirectorului BGI care con ine driverele. De exemplu dac BGI este subdirector al directorului BORLANDC, atunci se utilizeaz irul de caractere:
C:\\BORLANDC\\BGI
Exemplu: Pentru setarea n mod implicit a modului grafic se poate utiliza urmtoarea secven de instruc iuni:
int gd,gm; detectgraph(&gd,&gm); initgraph(&gd,&gm, C:\\BORLANDC\\BGI);
Doar dup apelul func iei initgraph pot fi utilizate i alte func ii de gestionare a ecranului n mod grafic.
241
Utilizatorul poate defini el nsui parametri pentru ini ializarea modului grafic. De exemplu, secven a urmtoare:
int gd=1,gm=0; initgraph(&gd,&gm, C:\\BORLANDC\\BGI);
seteaz modul grafic corespunztor unui adaptor grafic CGA cu rezolu ia 320*200 de puncte. n afara acestor func ii mai pot fi utilizate i urmtoarele func ii: void far setgraphmode (int mode) utilizat pentru setarea modului grafic unde mode are valorile 0 4 pentru VGA, 0-1 pentru EGA, 0 2 pentru VGA; void far retorecrtmode(void) ce permite revenirea la modul precedent; void far graphdefaults(void) repune parametri grafici la valorile implicite; int far getgraphmode(void) returneaz codul modului grafic; char *far getmodename(int mod) returneaz pointerul spre numele modului grafic definit de codul numeric mod; char *far getdrivername(void) returneaz pointerul spre numele drieverului 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.
242
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 LLIGHTGRAY DARKGRAY LIGHTBLUE LIGHTGREEN LIGHTCYAN LIGHTRED LIGHTMANGETA YELLOW WHITE Valoare 0 1 2 3 4 5 6 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 func ii: 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;
243
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 func iei setpalette sau setallpalette conduce la schimbarea corespunztoare a culorilor afiate pe ecran n momentul apelului func iilor respective. void far getpalette(struct palettetype far* paleta) determin codurile culorilor componente ale paletei curente; int far getmaxcolor(void) returneaz numrul maxim de culori diminuat cu 1; int far getpalettesize(void) returneaz numrul culorilor componente ale paletei.
244
Valoare 0 1 2 3 4
direc ie definete direc ia de scris a textului, astfel: - 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 func iile 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 pozi ia curent de pe ecran; void far outtextxy(int x,int y,char far* ir) , unde ir este un pointer spre zona de memorie n care se pstreaz caracterele de afiat, x,y definete pozi ia de pe ecran unde se face afiarea. Dimensiunile n pixeli ale unui ir de caractere se pot determina utiliznd func iile textheight i textwidth:
245
void far textheight(char far* ir) returneaz nl imea n pixeli a irului pstrat n zona spre care pointeaz ir, void far textwidth(char far* ir) returneaz ll imea n pixeli a irului pstrat n zona spre care pointeaz ir.
246
(dr,jos) coordonatele col ului dreapta jos a zonei de pe ecran ce se salveaz; - zt pointer spre zona de memorie n care se salveaz imaginea de pe ecran. unsigned far imagesize(int st, int sus, int dr, int jos) determin dimensiunea unei zone dreptunghiulare de pe ecran, unde: - (st,sus) coordonatele col ului stnga sus a zonei de pe ecran; - (dr,jos) coordonatele col ului dreapta jos a zonei de pe ecran. void far putimage(int st, int sus, int jos,void far* zt, int op) afieaz oriunde pe ecran o zon dreptunghiular salvat cu func ia getimage, unde: - (st,sus) coordonatele col ului 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 opera ia ntre datele aflate n zona spre care pointeaz zt i cele existente pe ecran n zona dreptunghiular definit de parametri st, sus. Parametrul op se definete astfel:
Constant simbolic COPY_PUT XOR_PUT
OR_PUT AND_PUT NOT_PUT
Valoare
0 1 2 3 4
Ac iune
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
247
void far moverel(int dx, int dy) mut cursorul n dreptul pixelului de coordonate (x+dx,y+dy), unde (x,y) reprezint coordonatele pixelului curent; void far line(int xi, int yi, int xf, int yf) traseaz un segment de dreapt ntre punctele de coordonate (xi,yi) i (xf,yf); void far lineto(int x, int y) traseaz un segment de dreapt ntre punctul curent i punctul de coordonate (x,y); void far linerel(int dx, int dy) traseaz un segment de dreapt ntre punctul curent i punctul de coordonate (x+dx,y+dy), unde (x,y) sunt coordonatele punctului curent; void far arc(int xcentru, int ycentru, int unghistart, int unghifin,int raza) traseaz un arc de cerc, unghiurile fiind exprimate n grade sexagesimale; void far circle(int xcentru, int ycentru, int raza) traseaz un cerc, cu (xcentru,ycentru) coordonatele centrului i raza raza acestuia; void far ellipse(int xcentru, int ycentru, int unghistart, int unghifin,int semiaxamare, int semiaxamic) traseaz un arc de elips cu centrul n punctul de coordonate (xcentru,ycentru), semiaxa mare definit de parametrul semiaxamare iar semiaxa mic definit de parametrul semiaxamic; void far rectangle(int st, int sus, int dr, int jos) traseaz un dreptunghi definit de col urile diagonal opuse; void far drawpoly(int nr, int far* tabpct) traseaz o linie polignal, parametrul nr 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 liniu e de dou dimensiuni Linie ntrerupt format din liniu e de aceeai dimensiune Stil definit de utilizator prin ablon
248
ablon definete stilul liniei i are sens doar cnd parametrul stil are valoarea 4; grosime definete l imea liniei n pixeli, astfel: NORM_WIDTH valoarea 1 pixel i THICK_WIDTH valoarea 3 pixeli. void far getlinesettingstype(struct linesettingstype far* linieinfo) este utilizat pentru a determina stilul curent; void far bar(int st, int sus, int dr, int jos) are aceeai semnifica ie cu func ia rectangle ns dreptunghiul este colorat; void far bar3d(int st, int sus, int dr, int jos, int profunzime, int ind) func ia deseneaz o prism colorat pentru ind diferit de zero; pentru ind=0, nu se traseaz partea de sus a prismei; void far pieslice(int xcentru, int ycentru, int unghistart, int unghifin,int raza) deseneaz un sector de cerc colorat; void far fillpoly(int nr, int far* tabpct) deseneaz un poligon colorat; void far fillellipse(int xcentru, int ycentru, int semiaxamare, int semiaxamic) deseneaz o elips colorat; void far setfillstyle(int 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
void far setfillpattern(char far *h_utilizator,int culoare) este utilizat pentru a defini o haur a utilizatorului, astfel:
249
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 func ie 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 func ia setfillstyle). Exemplu: Prezentm n acest exemplu un model de utilizare a modului grafic pentru trasarea graficelor unor func ii matematice elementare.
#include <stdio.h> #include <math.h> #include <graphics.h> #include <conio.h> int x,y; float a,b; void desen(void) //functia care deseneaza axele si //coloreaza ecranul { cleardevice(); setbkcolor(14); setcolor(12); line(0,y,2*x,y); line(x,0,x,2*y); line(2*x-4,y-4,2*x,y); line(2*x-4,y+4,2*x,y); line(x,0,x-4,4); line(x,0,x+4,4); } void interval(int l1, int l2) //functia care verifica // daca intervalele pe care sunt definite functiile // trigonometrice, sunt respectate { while ((a<l1)||(b>l2)) { clrscr(); cleardevice(); setbkcolor(0); printf("reintroduce-ti intervalul astfel incat sa cuprins intre -1 si 1:\n "); printf("a="); scanf("%f",&a); printf("\n");
fie
250
printf("b="); scanf("%f",&b); printf("\n"); } desen();} void grafic(float (*trig)(float))//functia care traseaza // graficul functiilor trigonometrice { float ymax,i,i1,h,y0,y1,lx,ly; h=0.001*(b-a); if (abs(a)>abs(b)) lx=(x-25)/(abs(a)+1); else lx=(x-25)/(abs(b)+1); ymax=0; for(i=a;i<=b;i+=h) if (ymax<abs(trig(i))) ymax=abs(trig(i)); if (ymax>y/2) ymax=y-25; ly=(y-25)/(ymax+1); if (ly>lx) ly=lx; for(i=a;i<=b;i+=h) { y0=trig(i); i1=i*lx ; y1=y0*ly; putpixel(x+i1,y-y1,4);} } float sinx (float x) { return sin(x);} float cosx(float x) { return cos(x);} float tanx(float x) { return tan(x);} float ctanx(float x) { return 1/tan(x);} float acosx(float x) { return acos(x);} float asinx(float x) { return asin(x);} float atanx(float x) { return atan(x);} float actanx(float x) { return atan(1/x);} void main() { int p,l,t,dr=DETECT, modgr; initgraph(&dr,&modgr,"c:\\borlandc\\bgi"); setbkcolor(1); x=getmaxx()/2,y=getmaxy()/2; p=0; while(p==0) { setbkcolor(1); p=1; printf("1 : sin(x)\n");
251
printf("2 : cos(x)\n"); printf("3 : tg(x)\n"); printf("4 : ctg(x)\n"); printf("5 : arcsin(x)\n"); printf("6 : arccos(x)\n"); printf("7 : arctg(x)\n"); printf("8 : arcctg(x)\n"); printf("Alegeti nr. corespunzator functiei dorite: "); scanf("%d",&t); while(t<1 || t>8 ) { printf("Reintroducet t-ul cuprins intre 1 si 8\n "); printf("t="); scanf("%d",&t);} printf("Dati intervalul: \n"); do{ printf("a="); scanf("%f",&a); printf("\n"); printf("b="); scanf("%f",&b); printf("\n"); if (a>b) printf("Reintroduce-ti intervalul astfel incat a sa fie mai mic ca b:\n "); } while(a>b); desen(); switch(t) { case 1:grafic(sinx); break; case 2:grafic(cosx); break; case 3:grafic(tanx); break; case 4:grafic(ctanx); break; case 5:interval(-1,1); grafic(asinx); break; case 6:interval(-1,1); grafic(acosx); break; case 7:grafic(atanx); break; case 8:grafic(actanx); break; defalut: p=0 ; } getch(); clrscr(); cleardevice(); setbkcolor(0); printf("Doriti graficul altei functii? 1-DA 0-NU :"); scanf("%d", &l); if (l==1){clrscr();cleardevice(); p=0;}} closegraph(); }
252
Limbajul C con ine mai multe func ii matematice care utilizeaz argumente de tip double i ntorc valori de tip double. Aceste func ii se mpart n urmtoarele categorii: - func ii trigonometrice; - func ii hiperbolice; - func ii exponen iale i logaritmice; - alte tipuri. Toate func iile matematice sunt incluse n fiierul antet "math.h". Acesta mai con ine o serie de macrodefini ii cum ar fi EDOM, ERANGE i HUGE_VAL. Macrodefini iile EDOM i ERANGE se gsesc n fiierul "errno.h" i sunt constante ntregi diferite de zero, utilizate pentru a semnala erorile de domeniu i de plaj ale func iei. HUGE_VAL (aflat tot n "errno.h") este o valoare pozitiv de tip double. Dac un argument al unei func ii matematice nu este n domeniul pentru care a fost definit func ia, atunci func ia ntoarce 0 i n domeniul de eroare, "errno" este modificat la EDOM. Dac o func ie produce un rezultat prea mare pentru a fi reprezentat printr-un double, apare o depire, func ia returnnd HUGE_VAL cu semnul adecvat iar "errno" este modificat la ERANGE. Dac se produce subdepire, func ia ntoarce zero, iar "errno" este modificat la ERANGE n func ie de implementare.
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;
253
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); }
254
- pow(x,y); func ia calculeaza xy. O eroare de domeniu apare dac x = 0 i y = 0 sau dac x < 0 i y nu este ntreg. Exemplu: Programul 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); }
i returneaz un numr ntreg, aleator, cuprins n intervalul de la 0 la RAND_MAX (valoare definit n fiierul antet stdlib.h). Func ia 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 func iei random la o valoare ntreag. Urmtorul program exemplific utilizarea acestor func ii: Exemplu:
#include <stdio.h> #include <stdlib.h> void main(void) { int k; printf(Valorile furnizate de functia rand\n); for(k=0;k<100;k++) printf(%d ,rand()); printf(Valorile furnizate de functia random(100)\n); for(k=0;k<100;k++) printf(%d ,random(100)); printf(Valori reale intre 0 si 1\n); for(k=0;k<10;k++) printf(%f ,random(10)/10.0); printf(Valori intregi intre -10 si 10\n); for(k=0;k<10;k++) printf(%d ,10-random(20));}
255
- 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 frac ionar, fiecare cu acelai semn c x; memoreaz partea ntreag n "*ip" i ntoarce partea frac ionar. modf(x, double *ip) - ntoarce x ntr-o func ie normalizat n intervalul [1/2, 1] i o putere a lui 2, care se memoreaza n "*exp"; dac x este 0, ambele pr i ale rezultatului sunt 0.
Modul de utilizare al acestor func ii este similar cu al celorlalte func ii matematice descrise n acest capitol.
256
257
BIOS ROM Memorie rezervat Memorie video COMMAND.COM Memorie pentru programe Intrri CONFIG.SYS Nucleul DOS Zona de comunica ii BIOS Vectori de ntrerupere BIOS
PC-ul mparte memoria n blocuri de 64Kb numite segmente. n mod obinuit, programul utilizeaz un segment de cod (ce con ine instruc iunile programului) i un al doilea segment de memorie pentru date. Dac un program este foarte mare compilatorul va trebui s dispun de mai multe segmente de cod sau de date, sau de ambele. Modelul de memorie 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 de in suficient memorie pentru execu ie. 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 execu ie a programului scade. Din aceast cauz trebuie ales modelul cel mai mic pentru necesit ile 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 func ii 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);
258
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 func ia halloc pentru alocarea memoriei i func ia hfree pentru eliberarea acesteia. Exemplul urmtor aloc o matrice de 400000 octe i: Exemplu:
#include <stdio.h> #include <malloc.h> void main(void) { long int k; int huge *matrice_uriasa; if ((matrice_uriasa=(int huge*) halloc(100000L,sizeof (long int)))==NULL) printf(Eroare la alocarea matricii); else{ printf(Completeaza matricea\n); for(k=0;k<100000L;k++) matrice_uriasa[k]=k%32768; for(k=0;k<100000L;k++) printf(%d ,matrice_uriasa[k]); hfree(matrice_uriasa); } }
Pentru selectarea unui anumit model de memorie se include, de regul, o op iune n cadrul liniei de comand a compilatorului. Majoritatea compilatoarelor predefinesc o constant specific pentru a determina modelul curent de memorie. n tabelul 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 instruc iuni:
#ifndef _MEDIUM_ printf(Programul cere modelul de memorie medium\n);
259
exit(1); #endif
Atunci cnd un program trebuie s aloce memorie n mod dinamic se utilizeaz fie func ia malloc pentru a aloca memorie din zona near (din segmentul curent), fie func ia fmalloc pentru a aloca memorie far.
14.1.4 Stiva
Stiva este o regiune de memorie n cadrul creia programele pstreaz temporar datele pe durata execu iei. De exemplu, atunci cnd programele transmit parametri ctre o func ie, C plaseaz aceti parametri n stiv. Cnd func ia i ncheie execu ia acetia sunt scoi din stiv. Stiva este numit astfel deoarece ultimele valori depuse sunt primele extrase. n func ie de modelul de memorie utilizat, spa iul de memorie ocupat de stiv difer. Valoarea minim a stivei este 4Kb. n
260
cazul modelelor compact sau large, C aloc pentru stiv un ntreg segment de 64Kb. Dac un program plaseaz n stiv mai multe informa ii dect poate re ine 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 func ia _stklen. Exemplu:
#include <stdio.h> #include <dos.h> void main(void) { printf(Dimensiunea stivei este de %d octeti,_stklen); }
261
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 func ie 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.
unde comanda specific una din urmtoarele opera ii: 0 tiprete octetul specificat; 1 ini ializeaz 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. Func ia biosprint returneaz o valoarea nteag pe un octet ai crui bi i au urmtoarea semnifica ie: 0 dispozitiv n pauz; 3 eroare I/O; 4 imprimant selectat; 5 lips hrtie;
262
6 confirmare dispozitiv; 7 dispozitivul nu este ocupat. 2) opera ii intrare/ieire Opera iile intrare/ieire de nivel jos pot fi realizate utiliznd func ia 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 unit ii, 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 func ia dorit astfel:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Ini ializeaz sistemul de disc Returneaz starea ultimei opera ii 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 unit ii de disc Ini ializeaz unitatea de disc Execut o citire lung 512 octe i de sector plus patru suplimentari Execut o scriere lung 512 octe i de sector plus patru suplimentari Execut o pozi ionare pe disc Ini ializarea alternativ a discului Citete bufferul sectorului Scrie bufferul sectorului Testeaz dac unitatea este pregtit Recalibreaz unitatea Execut diagnosticarea unit ii de RAM Execut diagnosticarea unit ii Execut diagnosticarea intern a controlerului
Dac se execut cu succes, func ia returneaz valoarea 0. Dac apare o eroare, valoarea returnat precizeaz eroarea. 3) servicii de tastatur din BIOS Pentru accesul la serviciile de tastatur din BIOS, C-ul pune la dispozi ie func ia _bios_keybrd ce are urmtoarea sintax:
unsigned _bios_keybrd(unsigned comanda)
263
unde parametrul comanda specific opera ia dorit i poate avea una din urmtoarele valori:
_KEYBRD_READ _KEYBRD_READY
_KEYBRD_SHIFTSTATUS
_NKEYBRD_READ
_NKEYBRD_READY
_NKEYBRD_SHIFTSTATUS
Indic func iei s citeasc un caracter de la tastatur Determin dac este prezent un caracter la bufferul tastaturii. Dac func ia returneaz 0, nseamn c nici o intrare de la tastatur nu este prezent. Dac valoarea returnat este 0xFFFF, utilizatorul a 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 func iei s citeasc un caracter de la tastatur, inclusiv tastele speciale, cum ar fi tastele cu sge i Determin dac este prezent un caracter la bufferul tastaturii. Dac func ia returneaz 0, nseamn c nici o intrare de la tastatur nu este prezent. Dac valoarea returnat este 0xFFFF, utilizatorul a apsat CTRL C Func ia accept inclusiv tastele speciale, cum ar fi tastele cu sge i 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) ob inerea listei cu echipamente din BIOS Unele programe necesit determinarea caracteristicilor hardware ale calculatorului. Pentru aceasta se utilizeaz func ia _bios_equiplist care are urmtoarea sintax:
264
unsigned _bios_equiplist(void);
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 prezen a 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 prezen a coprocesorului matematic; 0 prezen a unit ii de disc flexibile. 5) controlul intrrilor i ieirilor pentru portul serial Pentru a executa opera ii intrare/ieire utiliznd portul serial se utilizeaz func ia bioscom ce are urmtoarea sintax:
unsigned bioscom(int comanda,int port,char octet);
Parametrul comanda specific opera ia dorit i poate avea una din urmtoarele valori:
_COM_INIT _COM_RECEIVE _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 conven ional BIOS Pentru a determina memoria conven ional ce poate fi utilizat de ctre un proggram se utilizeaz func ia biosmemory ce are urmtoarea sintax:
int biosmemory(void);
Valoarea returnat de aceast func ie nu cuprinde memoria extins, expandat sau superioar. 7) citirea cronometrului BIOS
265
BIOS are incorporat un ceas intern ce bate de 18.2 ori pe secund. Acest cronometru este util pentru a genera punctul ini ial al unui generator de numere aleatoare. Multe compilatoare de C pun la dispozi ie dou func ii pentru accesul la cronometrul BIOS: biostime i _bios_timeofday. Sintaxa acestor func ii este urmtoarea:
long biostime(int operatie,long timp_nou);
Parametrul opera ie poate lua dou valori: 0 dac se dorete ca func ia s citeasc valoarea curent a cronometrului; 1 pentru a fixa valoarea cronometrului la valoarea timp_nou.
long _bios_timeofday(int operatie,long *batai);
Aceast func ie poate fi, de asemenea, utilizat pentru a citi sau a fixa cronometrul BIOS.
parametrul secunde specificnd numrul de secunde pe care este suspendat programul. 2) utilizarea sunetelor Generarea de sunete ce utilizeaz difuzorul calculatorului se realizeaz utiliznd func iile sound i nosound:
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); } }
266
while(!kbhit()); nosound(); }
3) ob inerea de informa ii despre erori n DOS n cazul n care un serviciu al sistemului DOS eueaz, programele pot cere informa ii suplimentare despre acea eroare folosind func ia dosexterr:
int dosexterr(struct DOSERROR *info_eroare);
Dac func ia 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 06H Eroare de sistem nedatorat programului curent 07H Eroare de aplica ie 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 interven ia utilizatorului 01H ncearc din nou, cu o ntrziere, apoi cere interven ia 02H
utilizatorului Cere interven ia utilizatorului pentru solu ie 03H Renun i elimin 04H Renun , dar nu elimina 05H Ignor eroarea 06H ncearc din nou dup interven ia utilizatorului 07H Parametrul de_locus specific sursa erorii, astfel:
267
01H Surs necunoscut 02H Eroare de dispozitiv bloc 03H Eroare de re ea 04H Eroare de dispozitiv serial 05H Eroare de memorie 4) citirea valorilor registrului segment Codul programului, datele i stiva sunt controlate de compilator 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 func ia segread:
void segread(struct SREGS *segs);
5) accesul la valorile de port Pentrul controlul hardware de nivel inferior, compilatoarele de C pun la dispozi ie urmtoarele func ii: - 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 func ia delay, similar func iei sleep. Func ia delay are ns ca parametru o constant exprimat n milisecunde:
void delay(unsigned milisecunde);
7) apelarea unei comenzi interne DOS Pentru apelarea unei comenzi DOS sau a unui fiier pentru comenzi se utilizeaz func ia system:
int system(const char *comanda);
Parametrul comanda este un ir de caracter care con ine numele comenzii DOS sau a fiierului de comenzi. Dac func ia reuete s execute comanda, se returneaz valoarea 0, altfel returneaz -1.
268
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 func ia _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: 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 func iei _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 func iile:
void _disable(void); void _enable(void);
269
14.3 Bibliotecile C
Dac se examineaz fiierele ce nso esc un compilator C, se remarc multe fiiere cu extensia LIB. Aceste fiiere con in biblioteci obiect. Atunci cnd este compilat i link-editat un program, editorul de legturi examineaz fiierele LIB pentru a rezolva referin ele la func ii. Cnd sunt create func ii utile ce sunt necesare i n alte programe, se pot construi biblioteci n care aceste func ii s fie pstrate.
Dup ce fiierul bibliotec a fost creat, func iile acestuia sunt disponibile pentru compilarea i legarea noilor programe.
270
unde: -
cale este un ir de caractere care specific calea pn la bilioteca asupra creia se efectueaz opera ia; 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 opera iei efectuate asupra bibliotecii.
271
BIBLIOGRAFIE
1. 2. 3. 4. 5. 6. 7. 8. 9. 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. 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 Universit ii din Craiova, 1993. Negrescu L., Introducere n limbajul MicroInformatica, Cluj Napoca, 1993. C, Editura
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.
10. Mocanu M., Ghid de programare n limbajele C/C++, Editura SITECH, Craiova, 2001. 11. Zaharia, M.D., Structuri de date i algoritmi. Exemple n limbajele C i C++, Ed. Albastr, Cluj Napoca, 2002. 12. Kernighan, B.W., Ritchie, D.M., The C programming languages, Englewood. Cliffs, N.J. Prentice-Hall, 1978. 13. Bulac, C., Ini iere n Turbo C++ i Borland C, Editura Teora, Bucureti, 1995.
272