SlideShare a Scribd company logo
CUPRINS

CUPRINS............................................................................................I
Capitolul I...........................................................................................1
INTRODUCERE ÎN..........................................................................1
ARHITECURA SISTEMELOR DE CALCUL...............................1
   1.1. Importanţa limbajului C....................................................1
   1.2 Arhitectura de bază a unui calculator.................................4
      1.2.1 Microprocesorul.........................................................7
      1.2.2 Memoria.....................................................................8
      1.2.3 Echipamentele periferice..........................................10
   1.3. Programarea calculatorului.............................................13
      1.3.1. Sistemul de operare.................................................14
      1.3.2. Tipuri de fişiere.......................................................18
      1.3.3. Construirea fişierului executabil.............................19
Capitolul II.......................................................................................24
REPREZENTAREA DATELOR ÎN CALCULATOR.................25
   2.1. Reprezentarea internă/externă a numerelor....................26
   2.2. Reprezentarea externă a numerelor.................................27
      2.2.1. Reprezentarea externă a numerelor întregi..............28
      2.2.2. Reprezentarea externă a numerelor reale................31
   2.3 Reprezentarea internă a numerelor..................................33
      2.3.1. Reprezentarea internă a numerelor întregi..............33
      2.3.2 Adunarea, scăderea şi înmulţirea numerelor întregi.35
      2.3.3 Reprezentarea internă a numerelor reale..................37
      Game de reprezentare pentru numerele reale....................49
      2.3.5. Codificare BCD.......................................................50
Capitolul III......................................................................................52
ELEMENTELE DE BAZĂ ALE LIMABJULUI C.....................52
   3.1. Crearea şi lansarea în execuţie a unui program C...........52
   3.2. Structura unui program C................................................54
   3.3. Mulţimea caracterelor ....................................................56
      3.3.1. Litere şi numere.......................................................57
                                                  I
3.3.2. Caractere whitespace...............................................57
       3.3.3. Caractere speciale şi de punctuaţie..........................57
       3.3.4. Secvenţe escape.......................................................58
    3.4. Identificatori....................................................................59
    3.5. Cuvintele cheie ale limbajului C.....................................60
    3.6. Constante.........................................................................60
       3.6.1. Constante caracter...................................................61
       3.6.2. Constante întregi.....................................................61
       3.6.3. Constante în virgulă mobilă....................................61
       3.6.4. Constante şir............................................................62
       3.6.5. Constanta zero.........................................................63
       3.6.6. Obiecte constante....................................................63
       3.6.7. Enumerări ...............................................................64
Capitolul IV......................................................................................64
..........................................................................................................64
Operanzi şi operatori în C...............................................................64
    4.1. Operanzi .........................................................................65
    4.2. Operatori ........................................................................65
       4.2.1. Operatori aritmetici.................................................66
       4.2.2. Operatori de incrementare şi decrementare.............67
       4.2.3. Operatori relaţionali................................................67
       4.2.4. Operatori logici.......................................................68
       4.2.5. Operatori logici la nivel de bit.................................69
       4.2.6. Operatorul de atribuire............................................74
       4.2.7. Operatorul sizeof.....................................................74
       4.2.8. Operatorul ternar ?.................................................75
       4.2.9. Operatorul virgulă...................................................76
       4.2.10. Operatorul de forţare a tipului sau de conversie
               explicită (expresie cast)........................................77
       4.2.11. Operatorii paranteză..............................................77
       4.2.12. Operatorul adresă..................................................78
       4.2.13. Alţi operatori ai limbajului C................................78
       4.2.14. Regula conversiilor implicite şi precedenţa
               operatorilor...........................................................78
Capitolul V.......................................................................................80
..........................................................................................................80
                                                    II
Instrucţiuni.......................................................................................80
    5.1. Instrucţiuni etichetate (instrucţiunea goto).....................80
    5.2. Instrucţiuni expresie........................................................81
    5.3. Instrucţiuni compuse.......................................................82
    5.4. Instrucţiuni de selecţie....................................................82
       5.4.1. Instrucţiunea if.........................................................82
       5.4.2. Instrucţiuni de selecţie multiplă: if - else if.............84
       5.4.3. Instrucţiunea switch.................................................85
    5.5. Instrucţiuni repetitive......................................................87
       5.5.1. Instrucţiunea for......................................................87
       5.5.2. Instrucţiunea while..................................................90
       5.5.3. Instrucţiunea do-while.............................................92
       5.5.4. Bucle încuibate........................................................94
       5.5.5. Instrucţiunea break..................................................96
       5.5.6. Instrucţiunea continue.............................................97
Capitolul VI......................................................................................97
..........................................................................................................97
TIPURI DE DATE STRUCTURATE ...........................................97
    6.1. Tablouri unidimensionale...............................................98
       6.1.1. Constante şir............................................................99
       6.1.2. Iniţializarea vectorilor de caractere.......................100
       6.1.3. Funcţii pentru prelucrarea şirurilor (fişierul antet
                string.h)...............................................................102
    6.2. Tablouri cu două dimensiuni (matrice).........................104
       6.2.1. Iniţializarea matricelor..........................................105
       6.2.2. Tablouri bidimensionale de şiruri.........................106
    6.3. Tablouri multidimensionale..........................................106
    6.4. Structuri.........................................................................107
       6.4.1. Tablouri de structuri..............................................109
       6.4.2. Introducerea structurilor în funcţii........................115
       6.4.3. Tablouri şi structuri în structuri.............................119
    6.5. Uniuni...........................................................................119
    6.6. Enumerări......................................................................121
Capitolul VII..................................................................................123
........................................................................................................123
POINTERI.....................................................................................123

                                                   III
7.1. Operatori pointer...........................................................123
      7.1.1. Importanţa tipului de bază.....................................125
      7.1.2. Expresii în care intervin pointeri...........................125
   7.2. Pointeri şi tablouri.........................................................130
      7.2.1. Indexarea pointerilor.............................................131
      7.2.2. Pointeri şi şiruri ....................................................133
      7.2.3. Preluarea adresei unui element al unui tablou.......134
      7.2.4. Tablouri de pointeri...............................................134
      7.2.5. Pointeri la pointeri.................................................135
      7.2.6. Iniţializarea pointerilor..........................................136
      7.2.7. Alocarea dinamică a memoriei..............................137
      7.2.8. Pointeri la structuri................................................139
      7.2.9. Structuri dinamice liniare de tip listă...................142
Capitolul VIII.................................................................................155
FUNCŢII.......................................................................................155
   8.1. Forma generală a unei funcţii.......................................155
   8.2. Reîntoarcerea dintr-o funcţie........................................158
   8.3. Valori returnate.............................................................159
   8.4. Domeniul unei funcţii...................................................160
      8.4.1. Variabile locale.....................................................160
      8.4.2. Parametri formali...................................................162
      8.4.3. Variabile globale...................................................163
   8.5. Apelul funcţiilor............................................................167
   8.6. Apelul funcţiilor având ca argumente tablouri.............168
   8.7. Argumentele argc şi argv ale funcţiei main()...............172
   8.8. Funcţii care returnează valori neîntregi........................173
   8.9. Returnarea pointerilor...................................................174
   8.10. Funcţii de tip void.......................................................177
   8.11. Funcţii prototip............................................................178
   8.12. Funcţii recursive..........................................................180
   8.13. Clase de memorare (specificatori sau atribute)...........181
   8.14. Pointeri la funcţii.........................................................187
Capitolul IX....................................................................................188
PREPROCESAREA.....................................................................189
   9.1. Directive uzuale............................................................189
   9.2. Directive pentru compilare condiţionată.......................191
                                               IV
9.3. Modularizarea programelor.........................................195
Capitolul X.....................................................................................200
 ........................................................................................................200
INTRĂRI/IEŞIRI...........................................................................200
    10.1. Funcţii de intrare şi ieşire - stdio.h..............................200
    10.2. Operaţii cu fişiere........................................................203
    10.3. Nivelul inferior de prelucrare a fişierelor...................206
       10.3.1. Deschiderea unui fişier........................................206
       10.3.2. Scrierea într-un fişier...........................................210
       10.3.3. Citirea dintr-un fişier...........................................212
       10.3.4. Închiderea unui fişier...........................................214
       10.3.5. Poziţionarea într-un fişier....................................214
       10.3.6 Ştergerea unui fişier.............................................216
       10.3.7. Exemple de utilizare a funcţiilor de intrare/ieşire de
                nivel inferior.......................................................217
    10.4. Nivelul superior de prelucrare a fişierelor..................222
       10.4.1. Funcţia fopen()....................................................223
       10.4.2. Funcţia fclose()....................................................224
       10.4.3. Funcţiile rename() şi remove()...........................225
       10.4.4. Funcţii de tratare a erorilor..................................225
       10.4.5. Funcţii cu acces direct.........................................226
       10.4.6. Funcţii pentru poziţionare...................................228
       10.4.7. Ieşiri cu format....................................................229
       10.4.8. Intrări cu format..................................................232
       10.4.9. Funcţii de citire şi scriere a caracterelor..............235
Capitolul XI....................................................................................239
........................................................................................................239
Utilizarea ecranului.......................................................................239
în mod text.....................................................................................239
    11.1. Setarea ecranului în mod text......................................240
    11.2. Definirea unei ferestre.................................................241
    11.3. Ştergerea unei ferestre.................................................241
    11.4. Deplasarea cursorului..................................................241
    11.5. Setarea culorilor..........................................................242
    11.6. Funcţii pentru gestiunea textelor.................................243
Capitolul XII..................................................................................246

                                                    V
........................................................................................................246
Utilizarea ecranului.......................................................................246
în mod GRAFIC............................................................................246
    12.1. Iniţializarea modului grafic.........................................247
    12.2. Gestiunea culorilor......................................................249
    12.3. Setarea ecranului.........................................................251
    12.4. Utilizarea textelor în mod grafic.................................251
    12.5. Gestiunea imaginilor...................................................253
    12.6. Desenarea şi colorarea figurilor geometrice...............254
Capitolul XIII.................................................................................260
Funcţii matematice .......................................................................260
    13.1 Funcţii trigonometrice..................................................261
    13.2 Funcţii trigonometrice inverse.....................................261
    13.3 Funcţii hiperbolice.......................................................261
    13.4 Funcţii exponenţiale şi logaritmice..............................262
    13.5 Generarea de numere aleatoare....................................262
    13.6 Alte tipuri de funcţii matematice.................................263
Capitolul XIV.................................................................................264
ELEMENTE DE PROGRAMARE AVANSATĂ.......................264
    14.1 Gestionarea memoriei..................................................264
       14.1.1 Memoria convenţională........................................264
       14.1.2 Memoria expandată..............................................267
       14.1.3 Memoria extinsă...................................................268
       14.1.4 Stiva......................................................................268
    14.2 Servicii DOS şi BIOS..................................................269
       14.2.1 Serviciile BIOS....................................................270
       14.2.2 Serviciile DOS......................................................274
    14.3 Bibliotecile C...............................................................277
       14.3.1 Reutilizarea unui cod obiect.................................278
       14.3.2 Lucrul cu fişiere bibliotecă...................................278
    14.3 Fişierele antet...............................................................279
BIBLIOGRAFIE............................................................................279




                                                   VI
Capitolul I

         INTRODUCERE ÎN
 ARHITECURA SISTEMELOR DE CALCUL



1.1. Importanţa limbajului C

        Creat în anul 1972 de programatorii de sistem Dennis M.
Ritchie şi Brian W. Kernighan de la Bell Laboratories cu scopul de a
asigura implementarea portabilă a sistemului de operare UNIX, C-ul
este astăzi unul din cele mai cunoscute şi puternice limbaje de
programare. Eficient, economic şi portabil, C-ul este o alegere bună
pentru realizarea oricărui tip de programe, de la editoare de texte,
jocuri cu facilităţi grafice, programe de gestiune şi pentru calcule
ştiinţifice, până la programe de sistem, constituind unul dintre cele
mai puternice instrumente de programare.
        Adesea referit ca limbaj portabil, C-ul permite transferul
programelor între calculatoare cu diferite procesoare şi în acelaşi timp
facilitează utilizarea caracteristicilor specifice ale maşinilor
particulare, programele scrise în C fiind considerate cele mai
portabile.
        Dacă evoluţia limbajelor de programare a adus în prim plan
nume ca FORTRAN, LISP, COBOL, ALGOL-60 sau PASCAL, unele
cu răspândire mai mult ”academică” – fiind folosite pentru a prezenta
conceptele de bază sau conceptele avansate de programare – ca de
pildă ALGOL-60 sau PASCAL, altele cu răspândire industrială
masivă – ca de pildă FORTRAN şi COBOL – limbajul C a pătruns
mai lent, dar foarte sigur. Realitatea arată clar că, în momentul de faţă,
piaţa producătorilor de programe este dominată net de C şi de
variantele evoluate ale acestuia.
        Elementele principale care au contribuit la succesul C-ului sunt
următoarele:
          - modularizarea programelor – ce dă posibilitatea unui
              singur programator să stăpânească relativ uşor programe
              de zeci de mii de linii de sursă;
                                    1
-     capacitatea de programare atât la nivel înalt cât şi la
              nivel scăzut – ceea ce dă posibilitatea utilizatorului de a
              programa fie fără a ”simţi” sistemul de operare şi maşina
              de calcul, fie la un nivel apropiat de sistemul de operare
              ceea ce permite un control foarte bun al eficienţei
              programului din punct de vedere viteză/memorie;
          - portabilitatea programelor – ce permite utilizarea
              programelor scrise în C pe o mare varietate de
              calculatoare şi sisteme de operare;
          - facilităţile de reprezentare şi prelucrare a datelor –
              materializate printr-un număr mare de operatori şi funcţii
              de bibliotecă ce fac programarea mult mai uşoară.
        Prin anii ’80 interesul pentru programarea orientată pe obiecte a
crescut, ceea ce a condus la apariţia de limbaje care să permită
utilizarea ei în scrierea programelor. Limbajul C a fost dezvoltat şi el
în această direcţie şi în anul 1980 a fost dat publicităţii limbajul C++,
elaborat de Bjarne Stroustrup de la AT&T. La ora actuală, majoritatea
limbajelor de programare moderne au fost dezvoltate în direcţia
programării orientate pe obiecte.
        Limbajul C++, ca şi limbajul C, se bucură de o portabilitate
mare şi este implementat pe o gamă largă de calculatoare începând cu
microcalculatoare şi până la cele mai mari supercalculatoare.
        Limbajul C++ a fost implementat pe microcalculatoarele
compatibile IBM PC în mai multe variante. Cele mai importante
implementări ale limbajelor C++ pe aceste calculatoare sunt cele
realizate de firmele Microsoft şi Borland.
        Conceptele programării orientate pe obiecte au influenţat în
mare măsură dezvoltarea limbajelor de programare în ultimul deceniu.
De obicei, multe limbaje au fost extinse astfel încât ele să admită
conceptele mai importante ale programării orientate pe obiecte. Uneori
s-au făcut mai multe extensii ale aceluiaşi limbaj. De exemplu,
limbajul C++ are ca extensii limbajul E ce permite crearea şi gestiunea
obiectelor persistente, lucru deosebit de important pentru sistemele de
gestiune a bazelor de date, limbajul O ce încearcă să îmbine facilităţile
de nivel înalt cu cele ale programării de sistem, limbajul Avalon/C++
destinat calculului distribuit, şi nu în ultimul rând binecunoscutul de
acum limbaj Java, specializat în aplicaţii Internet.
        Interfeţele utilizator au atins o mare dezvoltare datorită
facilităţilor oferite de componentele hardware ale diferitelor
                                   2
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 utilizând diferite standarde
care conţin ferestre, bare de meniuri, cutii de dialoguri, butoane, etc.
Cu ajutorul mouse-ului toate acestea pot fi accesate extrem de rapid şi
uşor fără a mai fi nevoie să cunoşti şi să memorezi o serie întreagă
comenzi ale sistemului de operare sau ale limbajului de programare.
Toate acestea conduc la interfeţe simple şi vizuale, accesibile unui
segment foarte larg de utilizatori.
       Implementarea interfeţelor este mult simplificată prin utilizarea
limbajelor orientate spre obiecte, aceasta mai ales datorită posibilităţii
de a utiliza componente standardizate aflate în biblioteci specifice.
Importanţa aplicării conceptului de reutilizare a codului rezultă din
faptul că interfeţele utilizator adesea ocupă 40% din codul total al
aplicaţiei.
       Firma Borland comercializează o bibliotecă de componente
standardizate care pot fi utilizate folosind limbajul C++, bibliotecă
cunoscută sub numele Turbo Vision.
       De obicei, interfeţele utilizator gestionează ecranul în mod
grafic. O astfel de interfaţă utilizator se numeşte interfaţă utilizator
grafică. Una din cele mai populare interfeţe utilizator grafice pentru
calculatoarele IBM PC este produsul Windows oferit de firma
Microsoft.
       Windows este un mediu de programare ce amplifică facilităţile
oferite de sistemul de operare MS-DOS. Aplicaţiile Windows se pot
dezvolta folosind diferite medii de dezvoltare ca: Turbo C++ pentru
Windows, Pascal pentru Windows, Microsoft C++, Microsoft Visual
Basic, Visual C şi Visual C++.
       Componentele Visual permit specificarea în mod grafic a
interfeţei utilizator, a unei aplicaţii, folosind mouse-ul, iar aplicaţia
propriu-zisă se programează într-un limbaj de tip Basic, C sau C++.
       Dacă în ani ’70 se considera că o persoană este rezonabil să se
poată ocupa de o aplicaţie de 4-5 mii de instrucţiuni, în prezent, în
condiţiile folosirii limbajelor de programare orientate pe obiecte,
această medie a ajuns la peste 25 de mii de instrucţiuni.
       Un limbaj de programare trebuie privit nu doar la suprafaţa sa –
sintaxă şi mod de butonare a calculatorului pentru o implementare
particulară – ci mai ales în profunzime, prin conceptele pe care se
bazează, prin stilul de programare, prin modul de structurare a
                                    3
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 C-
ul este, şi mai mult, o necesitate vitală, acesta fiind piatra de temelie
pentru înţelegerea şi utilizarea eficientă a limbajelor de nivel înalt
orientate pe obiecte şi Visual.

1.2 Arhitectura de bază a unui calculator
      Calculatoarele de tip PC (calculatoare personale) reprezintă cele
mai răspândite şi mai utilizate dintre calculatoare, datorită gradului de
accesibilitate şi preţului relativ scăzut. Indiferent de tipul
calculatorului, modul general de concepţie, de alcătuire şi funcţionare
este acelaşi. Calculatorul este o maşină programabilă. Două dintre
principalele caracteristici ale unui calculator sunt:
 1.     Răspunde la un set specific de instrucţiuni într-o manieră bine
        definită.
 2.     Calculatorul poate executa o listă preînregistrată de
        instrucţiuni, numită program.
       Calculatoarele moderne sunt electronice şi numerice.
     Partea de circuite electrice şi electronice precum şi conexiunile
        fizice dintre ele se numeşte hardware.
     Totalitatea programelor precum şi datele aferente acestor
        programe poartă denumirea de software.




                                   4
Echipamente de ieşire




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




              UPC
     Unitatea de procesare
           şi control                       Echipamente de intrare


                      Fig.1.1 Configuraţia standard pentru utilizator
     Partea hardware a unui calculator este formată din totalitatea
componentelor sale fizice. Toate calculatoarele de uz general necesită
următoarele componente hardware:
     memorie: Permite calculatorului să stocheze, cel puţin
        temporar, date şi programe.
     dispozitive de stocare externe: Permit calculatoarelor să
        stocheze permanent programe şi mari cantităţi de date. Cele
        mai uzuale dispozitive de stocare externă sunt HDD (hard
        disk drives), FDD (floppy disk drive) şi CD-ROM (Compact
        Disk-Read Only Memory) sau CD-R/W (Compact Disk-
        Read/Write).
     dispozitive de intrare : În mod uzual sunt reprezentate de
        tastatură (keyboard) şi de mouse. Aceste dispozitive
        reprezintă calea uzuală de introducere a datelor şi
        instrucţiunilor care gestionează funcţionarea unui calculator.
     dispozitive de ieşire: Reprezintă modalitatea prin care
        calculatorul transmite utilizatorului uman rezultatele
        execuţiei programelor. Ecranul monitorului sau imprimanta
        sunt astfel de dispozitive uzuale.
     unitatea de procesare şi control (UPC) : Este partea principală
        a unui calculator deoarece este componenta care execută
        instrucţiunile. În mod uzual această unitate de procesare şi
        control este reprezentată de un microprocesor care se
                                             5
plasează pe placa de bază (mainboard) a calculatorului
         împreună cu memoria internă RAM. În plus faţă de aceste
         componente orice calculator este prevăzut cu o magistrală
         (bus) prin care se gestionează modalitatea de transmitere a
         datelor între componentele de bază ale calculatorului.
         Magistrala reprezintă o colecţie de trasee electrice care leagă
         microprocesorul de dispozitivele de intrare/ieşire şi de
         dispozitivele interne/externe de stocare a datelor. Putem
         distinge magistrala de date, magistrala de adrese şi magistrala
         de comandă şi control.
       În figura 1.2 este prezentată interacţiunea dintre componentele
HARD principale ale unui calculator.
       Calculatoarele pot fi în general clasificate după dimensiuni sau
putere de calcul. Nu se poate face însă la ora actuală o distincţie netă
între următoarele categorii de calculatoare:
PC (Personal Computer): Un calculator de dimensiuni mici,
monoutilizator (single-user), bazat pe un microprocesor. În plus
acesta este dotat standard cu tastatură, mouse, monitor şi
dispozitive periferice de stocare a datelor.

                                     Memoria
                                     secundară


                  Echipament       UNITATEA          Echipament
                   de intrare      CENTRALĂ           de ieşire


                                     Memoria
                                     principală


               Fig. 1.2 Arhitectura minimală a unui sistem de calcul

       staţii de lucru (workstation): Un calculator monoutilizator de
        mare putere. Aceasta este asemănător unui PC dar are un
        microprocesor mai puternic şi un monitor de înaltă calitate
        (rezoluţie mai mare).



                                    6
    minicalculator (minicomputer): Un calculator multiutilizator
         (multi-user) capabil să lucreze simultan cu zeci sau chiar sute
         de utilizatori.
        mainframe: Un calculator multiutilizator capabil să lucreze
         simultan cu sute sau chiar mii de utilizatori.
        supercomputer: Un computer extrem de rapid care poate
         executa sute de milioane de operaţii într-o secundă.
1.2.1 Microprocesorul
       Microprocesorul este cea mai importantă şi cea mai scumpă
componentă a unui calculator de performanţele acesteia depinzând în
mare măsură rezultatele întregului sistem.
        Din punct de vedere fizic, microprocesorul este un cip ce
 conţine un circuit integrat complex ce îi permite să prelucreze
 informaţii prin executarea unor operaţii logice şi matematice diverse
 (adunări, scăderi, înmulţiri, împărţiri, comparări de numere).
       El este compus din două părţi importante: unitatea de execuţie
(EU – Execution Unit) şi unitatea de interfaţă a magistralei de date
(BIU – Bus Interface Unit). Prima componentă realizează efectiv
operaţiile, iar cea de-a doua are funcţia de transfer a datelor de la şi
înspre microprocesor.
       Microprocesorul reprezintă de fapt unitatea centrală a unui
calculator şi îndeplineşte o serie de activităţi specifice cum ar fi:
execută operaţii aritmetice şi logice, decodifică instrucţiuni speciale,
transmite altor cipuri din sistem semnale de control. Toate aceste
operaţii sunt executate cu ajutorul unor zone de memorie ale
microprocesorului, numite registre. Orice microprocesor are un set
finit de instrucţiuni pe care le recunoaşte şi pe care le poate executa.
       Calculatoarele IBM PC folosesc procesoare INTEL sau
compatibile, realizate de alte companii cum ar fi: AMD, NexGen,
CYRIX. Numele microprocesorului este folosit la identificarea
calculatorului. Se folosesc frecvent expresii de tipul calculator 386,
calculator 486, calculator Pentium II, etc.
       În 1971 firma Intel a fost abordata de o companie Japoneza,
acum dispărută, pentru a construi un circuit dedicat pentru un nou
calculator. Designerul Ted Hoff a propus o soluţie programabilă, de uz
general, şi astfel s-a născut circuitul Intel 4004. Au urmat la scurt timp
chipurile 4040 si 8008 dar lor le lipseau multe din caracteristicile
microprocesoarelor aşa cum le ştim noi azi. În 1974 Intel a prezentat

                                    7
pentru prima oară circuitul Intel 8080 care a fost folosit in sistemele
Altair şi IMSAI. Curând după aceea au apărut procesoarele Motorola
6800 şi 6502 de la MOS Technology. Doi dintre proiectanţii de la
Intel au părăsit firma, creând corporaţia ZILOG care a produs chipul
Z80 (compatibil cu 8080 dar cu set de instrucţiuni mai puternic şi de
două ori mai rapid.
       Cipul Intel 4004 a fost primul procesor comercial, lansat la
sfârşitul anului 1971. La un preţ de circa 200$ şi înglobând 2300 de
tranzistori, cipul 4004 dezvolta mai multă putere de calcul decât
ENIAC, primul calculator electronic, cu 25 de ani în urma. Faţă de
cele 18.000 de tuburi cu vacuum ce ocupau 900 metri cubi, procesorul
4004 putea dezvolta 60.000 de operaţii pe secundă. Această invenţie a
contribuit la revoluţionarea domeniilor de aplicaţii ale computerelor,
dând startul unui adevărat galop de inovaţii tehnologice. Următorul
pas a fost în 1980, când IBM a inclus un procesor Intel în arhitectura
primului PC.
       Astăzi PC-urile sunt pretutindeni în jurul nostru. Un copil care
lucrează la o maşina ce incorporează un procesor Pentium Pro
beneficiază de mult mai multă putere de calcul decât dispunea
guvernul SUA în perioada lansării primelor echipaje umane către
Lună.
       Într-un număr aniversar al publicaţiei Communications of the
ACM, Gordon Moore, co-fondator al companiei Intel, era optimist în
privinţa evoluţiei PC-urilor şi a microprocesoarelor: "complexitatea
microprocesoarelor, care se măsoară prin numărul de tranzistori pe
cip, s-a dublat aproape constant la fiecare 18 luni, de la apariţia
primului prototip 4004. Aceasta evoluţie exponenţială a determinat o
continuă creştere a performanţelor PC-urilor şi o scădere a costului
procesului de calcul. Pe când în 1991 un PC bazat pe procesorul Intel
486 costa aproape 225$ pentru o performanţă de un milion de
instrucţiuni pe secundă (MIPS), astăzi, un sistem desktop ce utilizează
un cip Pentium Pro este evaluat la circa 7$ pe MIPS. Nu se întrevede
nici o dificultate care să frâneze această rată de dezvoltare".
1.2.2 Memoria
       Microprocesorul are capacitatea de a memora date care urmează
a fi prelucrate, cât şi rezultatele intermediare. Se observă că rolul său
principal este de a prelucra şi transmite informaţiile şi rezultatele şi
deci capacitatea sa de memorare este mică neputând stoca programe.

                                   8
De aceea, un calculator necesită şi o memorie care să găzduiască date
şi programe.
        Memoria este formată din punct de vedere fizic din cipuri ce
stochează informaţia sub forma a două niveluri de tensiune ce
corespund valorilor 0 şi 1 din sistemul de numeraţie. Celulele de bază
ale memoriei (ce pot avea valoarea 0 sau 1) se numesc biţi şi ele
reprezintă particulele cele mai mici de informaţie din calculator.
Pentru citirea informaţiilor nu se folosesc biţi în mod individual ci
aceştia sunt grupaţi într-o succesiune. Astfel o succesiune de 8 biţi
formează un octet (sau un byte) aceasta reprezentând unitatea de
măsură a capacităţii de memorie. Deoarece reprezentarea numerelor în
calculator se face în baza 2 şi nu în baza 10, aşa cum suntem obişnuiţi
în mod normal să lucrăm, şi multiplii unui byte vor fi puteri ale lui 2,
astfel:
        1 KB=210B=1024 B
        1 MB=210KB=1 048 576 B
        1 GB=210MB=230 B
        Abrevierile K (kilo), M (mega), G (giga) se scriu de obicei cu
litere mari şi reprezintă mii, milioane şi, respectiv, miliarde.
        Memoria unui calculator are două componente: memoria
principală (internă) şi memoria secundară (externă). Memoria internă
este memoria ce poate fi accesată în mod direct de către microprocesor
şi în care sunt încărcate programele înainte de a fi executate de către
microprocesor. Dacă primele procesoare puteau accesa doar 1 MB de
memorie astăzi un procesor Pentium poate accesa peste 256 MB.
Memoria principală este formată din două tipuri de circuite: cip-uri
ROM şi cip-uri RAM.
        Circuitele de tip ROM (Read Only Memory) au memorate
programele care controlează iniţial calculatorul (sistemul de operare).
Aceste memorii pot fi doar citite (conţinutul lor nu poate fi modificat).
Înscrierea conţinutului acestor memorii se face de către fabricant, iar
operaţiunea de înscriere cu programe se mai numeşte „arderea
memoriilor”.
        Circuitele de tip RAM (Random Acces Memory ) sunt memorii
la care utilizatorul are acces şi al căror conţinut se şterge la
deconectarea calculatorului. În memoria RAM informaţia este stocată
temporar. De exemplu, programele de aplicaţii curente şi datele
asociate acestor aplicaţii sunt încărcate în memoria RAM înainte de a
fi prelucrate de către microprocesor.
                                   9
Deoarece capacitatea de memorie a unui calculator nu poate fi
atât de mare încât să poată păstra toate programele pe vrem să le
executăm, a apărut necesitatea existenţei unor memorii externe, care
să fie solicitate la nevoie. Rolul acestora îl joacă discurile şi ele pot fi
asemănate cu cărţile dintr-o bibliotecă pe care le putem consulta ori de
câte ori avem nevoie de anumite informaţii.
       Primele discuri apărute pentru PC-uri, numite şi dischete,
floppy disk-uri sau discuri flexibile, permiteau stocarea a maximum
160 KB de informaţie. Astăzi mai există doar dischete cu diametrul de
3.5 inch cu capacitatea de 1,44 MB. Existenţa acestora pare a fi pusă
însă în pericol de apariţia CD-urilor reinscriptibile a căror capacitate
de memorare depăşeşte 700 MB iar evoluţia tehnologică, din ce în ce
mai rapidă, dă semne că lucrurile nu se vor opri aici.
       Dischetele folosesc metode magnetice de memorare a
informaţiei motiv pentru care ele se mai numesc şi suporturi
magnetice de informaţie.
       Principala componentă a unui calculator utilizată pentru
memorarea programelor o reprezintă hard discul. Acesta poate fi
asemănat cu o dischetă de mare capacitate, integrată într-o unitate
încapsulată. Iniţial, puţine PC-uri prezentau hard discuri, dar cum
preţurile acestora au scăzut considerabil, iar performanţele şi
capacităţile au crescut, în prezent toate calculatoarele prezintă acest
dispozitiv. În clipa de faţă, capacitatea de memorare a unui hard disc a
depăşit valoarea de 40 de GB.
1.2.3 Echipamentele periferice
       Comunicarea om-maşină se realizează cu ajutorul
echipamentelor periferice prin intermediul cărora utilizatorul poate
programa sau da anumite comenzi calculatorului sau poate vizualiza
rezultatele obţinute de către anumite programe. Principalele
echipamente periferice ale unui calculator sunt următoarele: tastatura,
mouse-ul, scanner-ul, monitorul şi imprimanta. Ele pot fi grupate în
echipamente de intrare – cele prin care calculatorul primeşte
informaţii sau comenzi (tastatură, mouse, scanner) - şi echipamente de
ieşire – cele prin care calculatorul transmite informaţii în exterior
(monitor, imprimantă). În continuare sunt prezentate câteva
caracteristici ale fiecărui echipament.
       Tastatura – este principalul dispozitiv de intrare al
calculatorului prin intermediul căruia se transmit comenzi către

                                    10
unitatea centrală. Cuplarea la calculator a tastaturii se face prin
intermediul unui cablu de conectare.
       Din punct de vedere al dispunerii tastelor, tastatura se aseamănă
destul de mult cu cea a unei maşini de scris dar are şi părţi care o
individualizează.
       Primele tastaturi au avut 83/84 de taste, pentru ca, ulterior, ele
să fie îmbogăţite prin dublarea tastelor existente sau adăugarea altora
noi, ajungându-se la 101/102 taste.
       Din punct de vedere al funcţionalităţii lor ele pot fi împărţite în
patru categorii:
                     - taste alfanumerice;
                     - taste cu scopuri speciale;
                     - taste direcţionale şi numerice;
                     - taste funcţionale.
       Tastele alfanumerice conţin literele, cifrele şi semnele de
punctuaţie şi ocupă partea centrală a tastaturii. Acţionarea unei astfel
de taste determină apariţia caracterului corespunzător pe ecranul
calculatorului. Tastele cu scopuri speciale sunt aşezate în acelaşi bloc
cu tastele alfanumerice şi determină efectuarea anumitor acţiuni fără
înscrierea de caractere pe ecran. Tastele de mişcare se află situate în
partea dreaptă a tastaturii şi ele funcţionează în două moduri, care pot
fi comutate prin acţionarea altei taste, aflate deasupra lor, pe care scrie
NumLock. Dacă ledul corespunzător acestei taste este aprins, modul
de lucru este numeric, în caz contrar fiind comutat pe semnificaţia
direcţională. Tastele funcţionale sunt un grup de 12 taste situate în
partea de sus a tastaturii având pe ele litera F urmată de un număr între
1 şi 12. Acţionarea acestor taste determină efectuarea unor operaţii
specifice de la program la program.
       Mouse-ul – este tot un echipament de intrare mai uşor de
manevrat decât tastatura dar care poate efectua mai puţine operaţii.
Totuşi, foarte multe aplicaţii (în special aplicaţiile grafice) nu mai pot
fi concepute fără mouse. Un mouse are aspectul unei bucăţi de săpun,
uşor manevrabil, având dedesubt o bilă poziţionabilă, cu sensibilitate
şi viteză reglabile.
       Mişcarea maouse-ului pe o suprafaţă plană este corelată cu
deplasarea pe ecran a unui cursor cu o formă deosebită: cruciuliţă,
săgeată, etc. Declanşarea unei acţiuni se face prin poziţionarea
cursorului în zona corespunzătoare şi apăsarea unuia dintre butoanele
aflate pe partea posterioară. Iniţial un mouse avea două sau trei
                                    11
butoane. Acum există mouse-uri cu 5 butoane şi 2 rotiţe ce îndeplinesc
o serie de funcţii corespunzătoare unor taste speciale.
       Folosirea mouse-ului uşurează mult munca utilizatorilor,
nemaifiind necesar ca aceştia să memoreze numărul relativ mare de
comenzi corespunzător fiecărui produs, ca în situaţia în care se
foloseşte numai tastatura. Utilitatea mouse-ului este şi mai evidentă în
cazul aplicaţiilor grafice. De altfel, WINDOWS este un sistem de
operare creat special pentru lucrul cu mouse-ul.
       Scanner-ul – reprezintă dispozitive care se cuplează la un PC şi
cu care, prin intermediul unui software adecvat, se pot capta imagini,
fotografii, texte etc., în vederea unei prelucrări ulterioare. Astfel se pot
manevra imagini foto, se pot crea efecte grafice speciale, care nu se
pot obţine prin metode tradiţionale. După captare, imaginea poate fi
prelucrată, mutată, mărită, micşorată, rotită, colorată, umbrită,
suprapusă cu altă imagine etc.
       Cu un software de recunoaştere optică a caracterelor datele sau
documentele tipărite pe coli de hârtie pot fi transformate în fişiere,
putându-se realiza chiar o stocare a lor sub formă de arhivă.

       Monitorul - Sistemul video este format din două părţi: un
adaptor (placă) video şi un monitor sau display. Adaptorul video
reprezintă dispozitivul care realizează legătura (interfaţa) cu
calculatorul şi se află în interiorul acestuia. El va fi corespunzător
tipului de monitor video care îi este ataşat. Adaptorul video realizează
o rezoluţie orizontală şi una verticală. Rezoluţia reprezintă numărul de
elemente, în cazul de faţă puncte – pixeli – care pot fi afişate pe ecran.
De exemplu, un monitor VGA, în mod video, are o rezoluţie de 640 x
480 pixeli.
       Standardul VGA (Video Graphics Array) a fost introdus de
către IBM o dată cu calculatoarele PS/2, iar modurile video VGA
reprezintă un superset al standardelor video anterioare, CGA (Color
Graphics Adapter) şi EGA (Enhanced Graphics Adapter). Cea mai
importantă îmbunătăţire adusă de standardul VGA a fost rezoluţia
superioară a caracterelor în modul text, precum şi posibilitatea de a
afişa 256 de culori la un moment dat.
       Monitorul, denumit uneori şi display, permite vizualizarea
datelor introduse de la tastatură sau rezultate în urma execuţiei unor
comenzi sau programe, fiind încadrat în categoria echipamentelor
periferice de ieşire. Ca piesă principală, monitorul conţine un tub de
                                    12
vacuum, similar cu cel de la televizor şi trei tunuri de electroni
(corespunzătoare celor trei culori fundamentale).
        Standardele video MDA, CGA şi EGA folosesc monitoare
digitale. Datele care descriu culorile pixelilor sunt trimise de adaptorul
video la monitor sub forma unor serii de semnale digitale care sunt
echivalente unor serii de biţi.
        Standardul VGA a introdus un nou tip de monitor care
utilizează semnale analogice pentru transferul informaţiilor privind
culoarea de la adaptorul video la monitor. Dacă semnalele digitale
prezintă niveluri care indică prezenţa sau absenţa unui bit, semnalele
analogice pot prezenta orice valoare între una minimă şi una maximă.
        Imprimanta - Reprezintă un dispozitiv care poate fi ataşat unui
calculator, cu scopul tipăririi de texte şi grafică, putând fi considerată
un fel de maşină de scris automată. Până în prezent au fost realizate un
număr destul de mare de tipuri de imprimante pentru PC-uri, ele
diferind atât prin performanţe, cât şi prin modalităţile tehnice de
ralizare. Fiecare dintre ele prezintă avantaje şi dezavantaje, ideal fiind
a o folosi pe cea care corespunde cel mai bine tipului de lucrări
executate. În funcţie de modul în care este realizată imprimarea se
disting următoarele tipuri de imprimante:
      -           imprimante matriceale cu 9, 18 sau 24 de ace –
                  realizează imprimarea prin impactul acelor peste o
                  bandă de hârtie;
      -           imprimante cu jet de cerneală – funcţionează prin
                  pulverizarea fină a unor picături de cerneală pe hârtia
                  de imprimat;
      -           imprimante laser – ce utilizează o rază laser sau mici
                  diode luminiscente care încarcă electrostatic un
                  tambur de imprimare, corespunzător caracterului care
                  urmează a fi imprimat;
      -           imprimante rapide de linii – ce imprimă mai multe
                  linii odată, fiind folosite mai mult la sisteme de calcul
                  de dimensiuni mari.
        Calitatea imprimării creşte de la primul la ultimul tip prezentat,
dar în mod corespunzător şi preţul echipamentului.

1.3. Programarea calculatorului


                                    13
Programele de calculator, cunoscute sub numele de software,
sunt constituite dintr-o serie de instrucţiuni pe care le execută
calculatorul. Când se creează un program, trebuie specificate
instrucţiunile pe care calculatorul trebuie să le execute pentru a realiza
operaţiile dorite. Procesul de definire a instrucţiunilor pe care le
execută calculatorul se numeşte programare.
       Programele executate pe un calculator pot fi împărţite în trei
categorii:
       • programe de aplicaţie – sunt acele programe care
            interacţionează direct cu utilizatorul, specializate în
            realizarea unei categorii de prelucrări. Editoarele de texte,
            programele pentru gestiunea bazelor de date, programele
            de tehnoredactare asistată de calculator, de grafică etc. sunt
            programe de aplicaţie.
       • utilitare – programe, care la fel ca programele de aplicaţie,
            interacţionează direct cu utilizatorul, dar, spre deosebire de
            acestea, realizează prelucrări de uz general. Utilitarele
            realizează o serie de operaţii de „gospodărie” cum ar fi:
            copierea fişierelor, pregătirea discurilor magnetice pentru
            utilizare, crearea de copii de salvare, testarea
            echipamentului, etc.
       • programe de sistem – realizează legătura între
            componentele electronice ale calculatorului şi programele
            de aplicaţie şi utilitare. Rolul programului de sistem este
            acela de a uşura sarcina programatorului, simplificând
            îndeplinirea acelor sarcini care sunt comune marii
            majorităţi a programelor de aplicaţie: alocarea memoriei,
            afişarea caracterelor pe ecran şi la imprimantă, citirea
            caracterelor de la tastatură, accesul la informaţiile stocate
            pe disc magnetic, etc.
1.3.1. Sistemul de operare
        Sistemul de operare este o parte componentă a software-ului
unui calculator, care mai cuprinde un număr variabil de programe
utilitare selectate conform cu necesităţile programatorilor.
       Sistemul de operare este un program cu funcţii de coordonare şi
control asupra resurselor fizice ale calculatorului şi care intermediază
dialogul om-calculator. Sistemul de operare permite rularea
programelor şi păstrarea informaţiilor pe disc. În plus, fiecare sistem
                                   14
de operare pune la dispoziţia aplicaţiilor o serie de servicii care permit
programelor să aloce memorie, să acceseze diferite echipamente
periferice, cum ar fi imprimanta, şi să gestioneze alte resurse ale
calculatorului.
       Un sistem de operare trebuie să aibă capacitatea de a se adapta
rapid la modificările tehnologice, rămânând în acelaşi timp compatibil
cu hardware-ul anterior. Lanţul de comunicare utilizator – calculator
este prezentat în Figura 1.3:
      Sistemul de operare este cel mai important program care rulează
pe un calculator. Orice calculator de uz general este dotat cu un sistem
de operare care permite execuţia altor programe. Sistemele de operare
execută operaţiuni de bază precum: recunoaşterea unei intrări de la
tastatură (preluare caracter), trimiterea unui caracter pentru afişare pe
ecranul monitorului, gestionarea fişierelor şi a directoarelor pe disc
(floppy-disk sau hard-disk), controlul fluxului de date cu
echipamentele periferice ca drivere de disc sau imprimante.



                                CALCULATOR


                                  SISTEM DE
                                  OPERARE

                                  APLICAŢII

                                 UTILIZATOR


                    Fig. 1.3. Comunicarea utilizator - calculator




                                     15
Aplicaţie



                                                         Disk-drive

                                          Sistem de
                                           operare
                                                               Mouse
                    Monitor




                              Tastaturã               Imprimantã

                        Fig. 1.4 Rolul sistemului de operare
       Sistemul de operare al unui calculator este partea de software
necesară şi suficientă pentru execuţia oricăror alte aplicaţii dorite de
utilizator. Un calculator nu poate funcţiona decât sub gestiunea unui
sistem de operare. Orice aplicaţie lansată în execuţie de către un
utilizator apelează la resursele puse la dispoziţie de către sistemul de
operare. Sistemul de operare interfaţează calculatorul cu operatorul
uman de o manieră cât mai transparentă cu putinţă astfel încât
utilizatorul nu trebuie să facă eforturi mari de adaptare dacă lucrează
cu arhitecturi hardware diferite.
       Pentru sisteme mai mari, sistemele de operare au
responsabilităţi şi capabilităţi şi mai mari. Ele acţionează ca un
gestionar al traficului de date şi al execuţiei programelor. În principal
sistemul de operare asigură ca diferite programe şi diferiţi utilizatori
să nu interfereze unele cu altele. Sistemul de operare este de asemenea
responsabil cu securitatea, asigurând inaccesibilitatea persoanelor
neautorizate la resursele sistemului.
       Sistemele de operare se pot clasifica după cum urmează:
       multi-user: Permit ca doi sau mai mulţi utilizatori să ruleze în
          acelaşi timp programe (utilizatori concurenţi). Anumite
          sisteme de operare permit sute sau chiar mii de utilizatori
          concurenţi.
       multiprocesor: Permit execuţia unui program pe mai mult de
          un microprocesor.


                                          16
     multitasking: Permit mai multor programe să ruleze în acelaşi
          timp (execuţie concurentă).
      multithreading: Permit diferitelor părţi ale unui program să
          fie executate concurent.
      timp real (real time): Răspund instantaneu la diferite intrări.
          Sistemele de operare de uz general, ca DOS sau UNIX nu
          sunt sisteme de operare de timp real.
       Sistemele de operare furnizează o platformă software pe baza
căreia alte programe, numite programe de aplicaţie, pot rula (pot fi
executate). Programele de aplicaţie trebuie să fie scrise pentru a rula
pe baza unui anumit sistem de operare. Alegerea unui anumit sistem
de operare determină în consecinţă mulţimea aplicaţiilor care pot fi
rulate pe calculatorul respectiv. Pentru PC-uri, cele mai populare
sisteme de operare sunt DOS, OS/2 sau Windows, dar mai sunt
disponibile şi altele precum Linux.
       Ca utilizator se interacţionează cu sistemul de operare prin
intermediul unor comenzi. Spre exemplu, sistemul de operare DOS
acceptă comenzi precum COPY sau RENAME pentru a copia fişiere
sau pentru a le redenumi. Aceste comenzi sunt acceptate şi executate
de o parte a sistemului de operare numită procesor de comenzi sau
interpretor de linie de comandă.
       Interfaţele grafice cu utilizatorul (GUI, Graphical user
interfaces) permit introducerea unor comenzi prin selectarea şi
acţionarea cu mouse-ul a unor obiecte grafice care apar pe ecran. Spre
exemplu, sistemul de operare Windows are un desktop ca intefaţă
garfică cu utilizatorul. Pe acest desktop (birou) se află diferite
simboluri grafice (icoane, icons) ataşate diferitelor aplicaţii
disponibile pe calculatorul respectiv. Utilizatorul are multiple
posibilităţi de configurare a acestei intefeţe grafice.
       Primul sistem de operare creat pentru calculatoare a fost CP/M
(Control Program for Microcomputers), realizat pentru calculatoarele
pe 8 biţi. O dată cu perfecţionarea componentelor HARD s-a impus şi
necesitatea dezvoltării unui SOFT adecvat. Astfel, în 1981, a apărut
prima versiune a sistemului de operare MS-DOS. Sistemul de operare
MS–DOS (MicroSoft Disk Operating System) este destinat
gestionării resurselor software si hardware ale microcalculatoarelor cu
o arhitectura de tip IBM – PC sau compatibilă cu aceasta şi echipate
cu procesoare 8086 sau 80x86, Pentium. Odată cu creşterea

                                  17
capabilităţilor hardware ale calculatoarelor, acesta s-a transformat,
prin dezvoltări succesive, în Windows.
        Indiferent de sistemul de operare utilizat, din punctul de vedere
al utilizatorului, informaţiile sunt scrise pe disc sub forma unor fişiere.
Un fişier este o colecţie de informaţii grupate sub acelaşi nume. Un
fişier poate fi un program executabil, un text, o imagine, un grup de
comenzi sau orice altceva.
        Un fişier este identificat prin numele său. Numele unui fişier
este format dintr-un şir de caractere (care în funcţie de sistemul de
operare este limitat la un anumit număr maxim de caractere), urmate
eventual de semnul punct (.) şi de încă maximum 4 caractere, numite
extensie, ca de exemplu: nume.ext.
        Pentru a putea avea acces rapid la fişiere, sistemul de operare
creează nişte fişiere speciale, numite directoare, care pot fi asemănate
cu cuprinsul unei cărţi, deoarece ele conţin numele fişierelor şi adresa
de început a acestora. De asemenea, un director poate conţine la
rândul său alte directoare creându-se astfel o structură arborescentă de
directoare în care poate fi găsit foarte repede un anumit fişier.
1.3.2. Tipuri de fişiere
       Fişierele se pot împărţi în două categorii – executabile şi
neexecutabile. În prima categorie intră acele fişiere al căror nume scris
în dreptul prompterului (în cazul sistemului de operare DOS)
determină executarea unor activităţi de către sistemul de operare. O
parte dintre fişierele executabile sunt programe şi sunt recunoscute
prin extensia lor care poate fi EXE sau COM, altele fiind constituite
în fişiere de comenzi proprii sistemului de operare, a căror extensie
este BAT.
       Fişierele COM, numite adesea şi comenzi, conţin informaţii în
formatul imagine de memorie. Ele sunt mai compacte şi mai rapide
decât fişierele EXE, dar lungimea lor nu poate să depăşească 64 K.
Fişierele EXE pot să ajungă la dimensiuni mai mari prin segmentarea
programului în fragmente a căror dimensiune să fie de maximum 64K.
       Dintre fişierele neexecutabile vom aminti câteva mai
importante:
   • fişiere text ;
   • fişiere cu extensia SYS sau DRV, cunoscute sub numele de
        driver-e şi care conţin instrucţiuni despre modul în care

                                    18
sistemul de operare trebuie să controleze diferite componente
       hardware;
   •   surse de programe scrise în diferite limbaje (cu extensiile PAS
       – limbajul Pascal, C – limbajul C, CPP – limbajul C++, etc.);
   •   fişiere care conţin informaţii intermediare între cele în limbaj
       sursă şi cele executabile (extensiile OBJ, OVL);
   •   fişiere ce conţin imagini (extensiile JPEG, GIF, BMP);
   •   fişiere ce conţin sunete (extensiile WAV, MIDI, MP3) etc.
1.3.3. Construirea fişierului executabil
       Instrucţiunile pe care le execută un calculator sunt de fapt
grupuri de 1 ş 0 (cifre binare) care reprezintă semnale electronice
produse în interiorul calculatorului. Pentru a programa primele
calculatoare (în anii 1940-1950), programatorii trebuiau să înţeleagă
modul în care calculatorul interpreta diferitele combinaţii de 0 şi 1,
deoarece programatorii scriau toate programele folosind cifre binare.
Cum programele deveneau din ce în ce mai mari, acest mod de lucru a
devenit foarte incomod pentru programatori. De aceea au fost create
limbaje de programare care permit exprimarea instrucţiunilor
calculatorului într-o formă mai accesibilă programatorului. După ce
programatorul scrie instrucţiunile într-un fişier - numit fişier sursă, un
al doilea program – numit compilator, converteşte instrucţiunile
limbajului de programare în şirurile 1 şi 0 – cunoscute sub numele de
cod maşină.
       Pentru a obţine un program executabil, orice program sursă
trebuie eventual translatat (tradus) în limbaj cod maşină sau cod
obiect pe care îl poate înţelege microprocesorul. În urma acestui
proces, alături de fişierul sursă apare şi fişierul cod obiect (object file.)
Această translatare sau traducere este efectuată de către compilatoare,
interpretoare sau asambloare.
       Compilatorul este folosit pentru transformarea codului sursă,
adică a programului scris într-un limbaj de programare de nivel înalt,
în cod obiect (object code). Acest cod obiect va fi transformat în faza
de editare de legături în cod maşină executabil de microprocesorul
sistemului de calcul.
       Programatorii scriu programe într-o formă numită cod sursă.
Acest cod sursă parcurge apoi câţiva paşi înainte de a deveni program
executabil.

                                     19
Pe scurt, un compilator este un program special care procesează
instrucţiuni scrise într-un limbaj de programare particular şi le
transformă în limbaj maşină sau cod maşină pe care îl poate executa
microprocesorul.
        La ora actuală un limbaj de programare este inclus într-un
mediu de programare mai complex care include un editor de texte
pentru introducerea instrucţiunilor în limbajul de programare de nivel
înalt, un compilator şi un editor de legături folosite pentru translatarea
codului sursă în cod maşină.
        În mod tipic, un programator scrie declaraţii într-un limbaj
precum Pascal, C sau MATLAB folosind un editor. Se creează astfel
un fişier numit fişier cod sursă ce conţine o colecţie de instrucţiuni şi
declaraţii scrise în limbajul respectiv.
Primul pas este prelucrarea codului sursă de către compilator, care
translatează instrucţiunile de nivel înalt într-o serie de instrucţiuni cod
obiect. Când este lansat în execuţie compilatorul acesta, într-o primă
etapă, lansează un analizor sintactic, gramatical, numit parser. Acesta
parcurge şi analizează sintactic, secvenţial, în ordinea în care au fost
introduse, toate instrucţiunile scrise în limbajul de nivel înalt. O
instrucţiune de nivel înalt se translatează într-una sau mai multe
instrucţiuni specifice microprocesorului pentru care a fost conceput
compilatorul. Aceste instrucţiuni ale microprocesorului sunt înlocuite
cu codurile lor binare, fiecare instrucţiune a microprocesorului fiind
codificată de către constructor. Codurile binare ale instrucţiunilor
microprocesorului împreună cu reprezentările interne ale datelor
manipulate formează codul obiect.
       Deci în unul sau mai multe faze (parserul este una dintre faze)
din codul sursă de intrare se produce un cod de ieşire, numit în mod
tradiţional cod obiect. Este foarte important ca referiri la alte module
de cod să fie corect reprezentate în acest cod obiect.
       Pasul final în producerea programului executabil, după ce
compilatorul a produs codul obiect, este prelucrarea codului obiect de
către un editor de legături (link-editor sau linker). Acest linker
combină diferitele module (le leagă) şi dă valori reale, efective, tuturor
adreselor simbolice existente în codul obiect. În urma acestei
prelucrări se obţine codul maşină, salvat într-un fişier cu extensia .exe.
Acest cod maşină poate fi executat secvenţial, instrucţiune cu
instrucţiune, de către microprocesor.

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




              Fig. 1.5 Procesul de elaborare a unui program executabil

       Procesul de obţinere a unui executabil este prezentat în figura
de mai jos. Blocurile tridimensionale reprezintă entităţile principale
ale mediului de programare: editorul de texte, compilatorul (compiler)
şi editorul de legături (linker). Blocurile dreptunghiulare reprezintă
fişierele rezultate în urma aplicării celor trei utilitare de sistem:
      în urma utilizării editorului de texte obţinem fişierul text
          sursă cod cu numele generic “nume”. Dacă folosim limbajul
          de programare C spre exemplu, se obţine fişierul nume.c care
          se va salva pe disc.
      în urma lansării în execuţie a compilatorului, acesta preia
          fişierul sursă şi îl prelucrează corespunzător, semnalizându-se
          toate erorile fatale pentru program sau avertismente utile
          programatorului în procesul de depanare. În cazul în care
          compilarea se efectuează cu succes, se obţine un fişier cod
          obiect, salvat pe disc sub numele nume.obj
      în urma lansării în execuţie a editorului de legături, se preia
          fişierul cod obiect nume.obj şi se leagă cu toate modulele
          necesare (inclusiv funcţii de bibliotecă sau alte module
          externe), obţinându-se un program executabil (cod maşină) cu

                                    21
numele nume.exe la care adresele nu mai sunt simbolice ci
         absolute relativ la adresa de început a programului. La
         lansarea în execuţie a programului fluxul de informaţie este
         complet controlat de către microprocesor, toate salturile de
         adresă fiind făcute corespunzător.
       Interpretorul (interpreter) este un program care execută
instrucţiuni scrise într-un limbaj de nivel înalt. Numai anumite limbaje
de nivel înalt, spre exemplu BASIC, LISP sau MATLAB, sunt
prevăzute cu un interpretor.
       Există două modalităţi de a executa un program scris în limbaj
de nivel înalt. Cel mai comun mod este acela de a compila programul.
Cealaltă modalitate este “pasarea” programului unui interpretor.
                Un interpretor translatează instrucţiunile de nivel înalt
                într-o formă intermediară care este apoi executată. Prin
                contrast, un compilator translatează instrucţiunile de
                nivel înalt direct în limbaj maşină (cod maşină).
                Programele compilate rulează în general mai rapid
                decât cele interpretate. Un alt avantaj al programelor
                compilate este acela al desprinderii din context în
                sensul că programele executabile generate în urma
                procesului de compilare pot fi executate direct sub
                sistemul de operare al calculatorului. Un program
                interpretat se execută sub mediul în care a fost creat.
       Spre exemplu, pentru a rula un program scris în limbajul
BASIC se lansează în execuţie mediul BASIC, apoi se deschide
fişierul sursă-BASIC corespunzător şi se lansează interpretorul de
BASIC pentru execuţia sa.
       Avantajul unui interpretor este acela al evitării procesului de
compilare consumator de timp în cazul în care avem programe de mari
dimensiuni. Interpretorul poate executa imediat programele sursă.
Pentru acest motiv interpretoarele se folosesc mai ales în procesul de
dezvoltare al programelor, când programatorul doreşte adăugarea unor
mici porţiuni de program pe care să le testeze rapid. De asemenea,
interpretoarele permit o programare interactivă fiind des folosite în
procesul de instrucţie.
       În mediul de programare MATLAB, mediu interpretor, orice
comandă utilizator se execută imediat. Se pot edita şi fişiere script,
care conţin secvenţe de comenzi care se execută secvenţial.

                                   22
Programele de descriere a paginii (Page Description Languages)
ca PostScript spre exemplu folosesc un interpretor. Fiecare
imprimantă PostScript are incorporat un interpretor care execută
instrucţiuni PostScript.
       Asamblorul (assembler) este un program care face translaţia
unui program scris în limbaj de asamblare (limbaj de nivel scăzut,
corespunzător microprocesorului sistemului de calcul) în limbaj cod
maşină. Putem spune că asamblorul reprezintă pentru limbajul de
asamblare ceea ce reprezintă compilatorul pentru limbajele de nivel
înalt. Cum limbajul de asamblare conţine instrucţiuni mai puţin
complexe decât cele de nivel înalt, asamblorul face practic o
convertire biunivocă între mnemonicele limbajului de asamblare şi
codurile binare corespunzătoare acestor mnemonice (instrucţiuni).




                                 23
Instrucţiunile în limbajul
de nivel înalt se introduc
de la tastaturã.




                                         Tot ce se introduce de la
                                         tastaturã este vizibil pe monitor



          Editor de texte
  (eventual incorporat în mediu)



 Fişier text (fişier sursã)
 cu extensia adecvatã:
       nume.pas (limbaj Pascal)
           nume.c (limbaj C)
        nume.cpp (limbaj C++)
    nume.bas (limbaj BASIC), etc.          Fişierul sursã cu numele
                                           “nume” şi extensia
                                           corespunzãtoare se salveazã
                                           din memoria RAM pe harddisk
           Compilator
   Se compileazã fişierul sursã


   Se obţine fişierul cod obiect:
                nume.obj
                                           Se salveazã pe harddisk fişierul
                                                      nume.obj

             Link-editare
(legarea tuturor modulelor necesare)



     Se obţine fişierul cod maşinã
             (executabil):
               nume.exe                      Se salveazã pe harddisk fişierul
                                                        nume.exe



          Lansarea în execuţie de cãtre
              sistemul de operare
           a executabilului nume.exe

     Fig. 1.6 Detalierea procesului de generare a unui executabil
                         Capitolul II

                                    24
REPREZENTAREA DATELOR ÎN
               CALCULATOR


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

    High=’1’



     Low=’0’

                                                                   timp
       Ca urmare a acestei asocieri spunem, prin abuz de limbaj, că un
calculator numeric prelucrează numere binare. Ca şi un număr
zecimal, un număr binar are mai multe cifre binare. Sistemul de
numeraţie binar folosit pentru reprezentarea informaţiei în calculatoare
este un sistem de numeraţie ponderal, întocmai ca sistemul de
numeraţie zecimal.
       Reprezentarea naturală a numerelor la nivelul percepţiei umane
este cea zecimală, pe când reprezentarea proprie maşinilor de calcul
este cea binară. De aici rezultă necesitatea compatibilizării sau
interfaţării între aceste două moduri de reprezentare a numerelor. Cum
cele două sisteme de numeraţie sunt ponderale, o primă diferenţă este
aceea că sistemul zecimal foloseşte ca ponderi puterile întregi
(pozitive sau negative) ale lui 10 (zece) iar sistemul binar va folosi
puterile întregi (pozitive sau negative) ale lui 2.
       Î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

                                   25
internă sunt necesare convenţii de reprezentare: indiferent de tipul
datelor, acestea vor fi colecţii sau şiruri de cifre binare cărora, prin
convenţie, li se atribuie semnificaţii.
        Într-o primă instanţă, este foarte important să facem o distincţie
între tipurile de date recunoscute de un calculator (sau mai bine zis de
microprocesorul cu care este dotat calculatorul personal) şi formatele
de reprezentare ale acestor date ce reprezintă convenţii pentru
reprezentarea tipurilor de date, atât la nivel intern (în memoria
calculatorului) cât şi la nivel extern, al percepţiei umane.
        Din punctul de vedere al tipurilor de date care sunt
implementate în limbajul C putem spune că distingem două mari
categorii, date de tip întreg (integer) şi date de tip real (float).
Formatele de reprezentare internă/externă vor fi prezentate în cele ce
urmează. Cel mai simplu de reprezentat sunt numerele naturale. Se
face apoi trecerea la numerele întregi negative şi apoi la numerele
reale care au o parte întreagă şi una fracţionară.

2.1. Reprezentarea internă/externă a numerelor

       Reprezentarea internă a numerelor se referă la modul în care se
stochează datele în memoria RAM a calculatorului sau în regiştrii
microprocesorului. În acest format se prelucrează numerele pentru
implementarea diverselor operaţii aritmetice. La nivelul calculatorului
informaţia nu poate fi decât binară. În această reprezentare putem scrie
numere întregi pozitive sau negative sau numere reale.
       Există un standard IEEE care reglementează modul de
reprezentare internă a datelor.
       Reprezentarea externă este reprezentarea numerelor la nivelul
utilizatorului uman, deci în principiu se poate folosi orice bază de
numeraţie pentru reprezentarea numerelor. La nivel de reprezentare
externă se foloseşte semnul “-” în faţa unui număr în cazul în care
acesta este negativ sau punctul care separă partea întreagă de cea
fracţionară. De asemenea, numerele întregi interpretate fără semn se
pot afişa şi în format binar, octal sau hexazecimal, deci în bazele 2, 8
sau 16.
       În cele ce urmează ne vom pune următoarele probleme:
     - cum se reprezintă extern un număr natural
     - cum se reprezintă intern un număr natural
     - cum se reprezintă extern un număr întreg negativ
                                   26
-    cum se reprezintă intern un număr întreg negativ
    -    cum se face conversia de la reprezentarea externă la cea
         internă
    -    cum se face conversia de la reprezentarea internă la cea
         externă

2.2. Reprezentarea externă a numerelor

       În ceea ce priveşte reprezentarea externă, nu sunt nici un fel de
dificultăţi deoarece fiecare este familiarizat cu reprezentarea zecimală
a numerelor naturale sau reale. Trebuie menţionat de la început că
orice tip de reprezentare pe care o vom folosi este ponderală în sensul
că poziţia cifrelor în număr nu este întâmplătoare ci conformă cu o
pondere corespunzătoare unei puteri a bazei de numeraţie.
       O caracteristică a reprezentărilor externe este folosirea unor
convenţii de format unanim acceptate şi de altfel foarte naturale pentru
un utilizator uman. Spre exemplu, pentru a exprima numere negative
se foloseşte semnul “-” iar pentru reprezentarea numerelor reale se
foloseşte punctul “.” pentru delimitarea părţii întregi de cea
fracţionară. De asemenea, suntem familiarizaţi şi cu notaţia ştiinţifică
în care intervine mantisa şi exponentul (în virgulă mobilă).
       Reprezentarea zecimală este cea mai naturală pentru utilizatorul
uman. Vom oferi în continuare câteva exemple de reprezentări
zecimale externe:
        Număr           Reprezentare             Reprezentare
                          normală                  ştiinţifică
             37               37                    0.37x102
            -37              -37                   -0.37x102
           0.375            0.375                  0.375x100
          -0.375           -0.375                 -0.375x100
        0.00375           0.00375                  0.375x10-2
        -0.00375         -0.00375                 -0.375x10-2
         12.375            12.375                0.12375x102
         -12.375          -12.375               -0.12375x102
      În general dorim să obţinem rezultatele numerice ale
programelor pe care le concepem într-o formă de reprezentare
accesibilă. Totuşi, calculatorul trebuie informat asupra formatului de
reprezentare în care dorim să se afişeze datele necesare. Aceasta
înseamnă că va trebui să specificăm câte cifre se vor folosi la partea

                                  27
întreagă şi câte la partea fracţionară sau dacă dorim reprezentare
ştiinţifică sau nu. De altfel şi operatorul uman face aceleaşi convenţii
                                                     1
de reprezentare. Spre exemplu ştim că numărul          nu poate fi exact
                                                     3
reprezentat ca un număr zecimal, deci fixăm un format de
reprezentare. Dacă formatul ale se limitează la 4 cifre zecimale, atunci
            1
vom scrie     ≅ 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.
2.2.1. Reprezentarea externă a numerelor întregi

       Numerele naturale se pot reprezenta fie în baza de numeraţie
10, fie în orice altă bază.
       În general, un număr întreg în baza b se poate reprezenta cu un
număr predeterminat de cifre ci ∈ B = { 0,1,2,....., b − 2, b − 1} .
Mulţimea B reprezintă mulţimea cifrelor sau simbolurilor de
reprezentare. Spre exemplu:
        b = 2 ⇒ B = { 0,1}
        b = 7 ⇒ B = { 0,1,2,3,4,5,6}
        b = 10 ⇒ B = { 0,1,2,3,4,5,6,7,8,9}
       Noi suntem obişnuiţi să folosim mulţimea cifrelor zecimale.
Dacă totuşi se foloseşte o bază de reprezentare mai mare decât 10,
atunci mulţimea cifrelor zecimale nu mai este suficientă pentru
reprezentarea numerelor în acea bază. Spre exemplu să considerăm
baza b = 16 care va folosi 16 cifre hexazecimale (sau mai simplu
hexa). Prin convenţie, cele 16 cifre hexazecimale vor fi:
                    Cifra Simbol Cifra Simbol
                     0       0         8         8
                     1       1         9         9
                     2       2         10        A
                     3       3         11        B
                     4       4         12        C
                     5       5         13        D
                     6       6         14        E
                     7       7         15        F


                                  28
Forma generală de reprezentare externă a numerelor întregi este
de forma:
                      N b = ±c n −1c n − 2 ......c 2 c1c 0
                     
                     c k ∈ B = { 0,1,2,....., b − 2, b − 1}
Valoarea numerică zecimală a numărului N b va fi:

         (                                                         ) ∑ ck ⋅ b k
                                                                            n −1
N b = ± c n−1 ⋅ b n −1 + c n − 2 ⋅ b n− 2 + ... + c1 ⋅ b1 + c 0 ⋅ b 0 = ±
                                                                     k =0
     În continuare vom studia următoarele probleme:
    - cum se face conversia unui număr 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 număr natural din baza 10 în baza 2, se
împarte succesiv numărul la 2 şi se utilizează resturile la aceste
împărţiri în ordinea inversă de cum au fost obţinute.
    a) Conversia din baza 10 în baza 2 şi invers
    Fie de exemplu numărul zecimal 37. Reprezentarea sa binară va fi
obţinută astfel:
                             3710 = 1001012
                          37     2
                          36     18    2
                           1     18    9 2
                                  0    8 4 2
                                       1 4 2 2
                                         0 2 1
                                           0

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

                25 24 23 22 21 20
 1001012      = 1 0 0 1 0 1 = 1x25 + 1x22 + 1x20=37
       Cu aceste numere naturale putem face o serie de operaţii
aritmetice. Adunarea numerelor naturale binare se face întocmai ca la
cele în reprezentare în baza 10, după regula:
         0+0=0

                                           29
0+1=1
         1+0=1
         1+1=0, transport 1 spre rangul următor
Astfel, să facem adunarea 37+25 în binar:
                     37          1 0 0 1 0 1+
                     25            11001
                     62          111110
Se observă cum se obţine rezultatul corect.
      Înmulţirea se face în mod asemănător, ca o adunare repetată.
Spre exemplu, să calculăm 37x25

                    37             1 0 0 1 0 1x
                    25               11001
                                    100101
                                100101
                               100101
                    925        1110 011101
11100111012 = 1x20 + 1x22 + 1x23 +1x24 +1x27 +1x28+1x29 =
1+4+8+16+128+256+512 = 92510
     b) Conversia dintr-o bază oarecare b1 într-o altă bază b2 .
Fie spre exemplu numărul 4911 care se doreşte scris în baza 13.
Pentru a realiza această conversie, vom folosi baza intermediară 10.
Vom converti mai întâi 4A11 în baza 10 şi apoi numărul zecimal
obţinut îl vom trece în baza 13. Se observă cum un număr în baza 11
poate conţine şi cifra A=10 iar un număr în baza 13 poate conţine
cifrele A=10, B=11, C=12.
             4 A11 = 10 ⋅110 + 4 ⋅111 = 44 + 10 = 5410
                          54    13
                          52    4     13
                          2     0     0
                                 4
                           5310 = 4213
                           4 A11 = 4213


                                 30
2.2.2. Reprezentarea externă a numerelor reale

       Semnificativă pentru utilizatorul uman este reprezentarea
zecimală (în baza b=10) a numerelor reale, cu care suntem obişnuiţi.
Faţă de reprezentarea numerelor întregi, la numerele reale intervine
simbolul punct “.” care delimitează partea întreagă de partea
fracţionară. Cu alte cuvinte, cu ajutorul numerelor reale putem
reprezenta şi numere care nu sunt întregi. Forma generală a unui
număr real reprezentat într-o bază oarecare b este:
                   N b = ±c n −1c n − 2 ...c1c 0 • c −1c − 2 ...c − m +1c − m
                  
                  c k ∈ B = { 0,1,2,..., b − 2, b − 1}
          Valoarea zecimală a numărului de mai sus va fi:
         (                                                                                                     ) ∑ ck ⋅ b k
                                                                                                                       n− 1
N10 = ± cn − 1b n − 1 + cn − 2b n − 2 + c1b1 + c0b 0 + c− 1b − 1 + c− 2 ⋅ b − 2 + c− m + 1b − m + 1 + c− m b − m = ±
                                                                                                                 k= −m
       Se observă cum punctul delimitează partea întreagă (exprimată
printr-o combinaţie de puteri pozitive ale bazei b) şi partea fracţionară
(exprimată printr-o combinaţie de puteri negative ale bazei b).
       Semnificaţie pentru programator şi pentru producătorii de
software sau microprocesoare au bazele de reprezentare 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 număr real
cu n cifre pentru partea întreagă şi m cifre pentru partea fracţionară.
       Aşa cum în sistemul zecimal reprezentăm cu un număr finit de
cifre zecimale numerele reale, acelaşi lucru se va întâmpla şi în
sistemul binar. Punctul binar va avea o semnificaţie asemănătoare cu
punctul zecimal, care face separarea între partea întreagă şi cea
fracţionară. Cifrele binare situate după punctul binar vor corespunde
puterilor negative ale lui 2.
Astfel, în general, un număr real va avea reprezentarea binară:
             (
 N 2 = ± bm bm− 1...b1b0 .b− 1b− 2 ...b− n = bm 2 m + bm− 1 2 m− 1 + b1 21 + b0 20 + b− 1 2 − 1 + b− 2 2 − 2 + ... + b− n 2 − n   )
Spre exemplu, numărul 12.25 va avea reprezentarea binară:
                              12.2510 = 1100.01 = 2 3 + 2 2 + 2 −2


                                                              31
Partea întreagă a unui număr real se reprezintă binar precum
numerele întregi (cu sau fără semn). Pentru a determina partea
fracţionară, se procedează în mod invers ca la partea întreagă.
       Astfel, dacă partea fracţionară zecimală se reprezintă binar,
atunci aceasta se înmulţeşte succesiv cu 2. Dacă rezultatul depăşeşte
valoarea 1, atunci se înscrie un bit 1. Se continuă mai departe cu
dublarea valorii care depăşeşte 1. Dacă rezultatul nu depăşeşte
valoarea 1, atunci se înscrie un bit 0 şi se continuă multiplicarea cu 2.
Spre exemplificare, vom vedea cum se obţine reprezentarea binară a
lui 12.25. Partea întreagă este 12. Ea se reprezintă binar prin împărţiri
succesive la 2 şi considerarea resturilor. Partea fracţionară este 0.25
                 Partea         P.F. x 2 Noua           Bitul
               fracţionară                    P.F.     înscris
                   P.F.
                  0.25            0.5                   0
                   0.5             1          0         1
                    0
Obţinem exact rezultatul căutat: 12.25 = 1100.01
Să mai considerăm un alt exemplu. Să reprezentăm numărul 5.37
Partea întreagă are reprezentarea 510 =1012
                 Partea         P.F. x 2    Noua       Bitul
            fracţionară P.F.                P.F.      înscris
                  0.37           0.74       0.74         0
                  0.74           1.48       0.48         1
                  0.48           0.96       0.96         0
                  0.96           1.92       0.92         1
                  0.92           1.84       0.84         1
                  0.84           1.68       0.68         1
                  0.68           1.36       0.36         1
                  0.36           0.72       0.72         0
                  0.72           1.44       0.44         1
                  Etc..                                Etc..
      Obţinem: 5.3710 = 101.010111guatda.com/cmx.p101...2
Cu cât mai multe cifre binare vom reţine după punctul binar, cu atât
vom fi mai aproape de valoarea exactă 5.37.
      Obţinem un rezultat foarte important: Deşi un număr zecimal
poate avea un număr finit de cifre zecimale după punctul zecimal,
reprezentarea sa binară internă poate avea un număr infinit de cifre
binare. Este valabilă şi reciproca: un număr real zecimal cu un

                                   32
număr infinit de cifre se poate reprezenta într-o altă bază pe un
                             1
număr finit de cifre ( ex:      = 0.3guatda.com/cmx.p333...3...10 = 0.13 ). Cum orice
                             3
reprezentare binară internă este pe un număr finit de biţi, numărul
poate să nu fie reprezentat exact în calculator, ci cu o anumită
aproximaţie. Acest lucru este decisiv pentru a înţelege importanţa
lungimii reprezentării numerelor în calculator. Cu cât un număr binar
se reprezintă pe un număr mai mare de biţi, cu atât precizia de
reprezentare creşte.

2.3 Reprezentarea internă a numerelor
       Deoarece semnalul intern purtător de informaţie într-un
calculator este de tip binar, un număr zecimal (întreg sau real) se va
reprezenta intern în baza 2 cu ajutorul unui număr binar. O cifră binară
se numeşte bit (Binary Digit) şi poate fi fie 0 fie 1.
       În reprezentarea externă a numerelor am văzut că se poate folosi
orice bază de numeraţie (cu cifrele corespunzătoare). De asemenea,
numerele pot fi prefixate cu un simbol de semn ± şi pot include în
reprezentare şi punctul de separaţie între partea întreagă şi cea
fracţionară.
       În reprezentarea internă acest lucru nu mai este posibil deoarece
semnele plus (+), minus (-) sau punct (.) nu au nici o semnificaţie
pentru calculator. Orice număr (orice tip de dată) este reprezentat la
nivel intern de un număr prestabilit de biţi. Specialiştii din industria
software au ajuns la un consens de reprezentare concretizat prin
standardul IEEE 754 de reprezentare a internă a numerelor reale în
computere.
       Reprezentarea internă a numerelor a impus în limbajul C
definirea aşa-numitelor tipuri de date.
       Tipul unei date reprezintă modul în care microprocesorul
stochează în memorie şi prelucrează cu ajutorul regiştrilor interni o
dată. Tipul unei date se referă la lungimea sa de reprezentare (pe câţi
biţi se reprezintă data) precum şi ce semnificaţie au anumite câmpuri
de biţi din cadrul reprezentării.
2.3.1. Reprezentarea internă a numerelor întregi


                                  33
Un număr binar este o colecţie de cifre binare ponderate fiecare
cu o putere a lui 2. Bitul corespunzător ponderii celei mai mari, situat
cel mai în stânga, se numeşte MSB (Most Significand Bit) iar cel
corespunzător ponderii celei mai mici, situat cel mai în dreapta, se
numeşte LSB (Less Significand Bit). În cazul reprezentării binare a
numerelor naturale, reprezentarea externă (cea percepută de operatorul
uman) şi cea internă (cea prelucrată de procesorul calculatorului) sunt
asemănătoare. Cum pentru operatorul uman operatorii ‘+’ sau ‘-‘
semnifică faptul că un număr este pozitiv sau negativ, este necesară o
convenţie pentru reprezentarea internă a numerelor întregi negative.
       Această convenţie prevede folosirea MSB pentru reprezentarea
semnului numerelor întregi. Dacă numărul este pozitiv, se adaugă în
poziţia MSB bitul de semn ‘0’, iar dacă numărul este negativ se
utilizează în poziţia MSB bitul de semn ‘1’. Mai mult, numerele
negative se reprezintă în aşa numitul complement faţă de 2.
  Reprezentarea numerelor întregi negative în complement faţă de 2
      Această formă de reprezentare a numerelor negative necesită
parcurgerea următorilor paşi:
   pas1. Se reprezintă modulul numărului negativ, folosind bit de
semn (egal cu 0, evident)
   pas2.    Se complementează toţi biţii numărului astfel obţinut.
            Complementarea înseamnă transformarea bitului 0 în
            bitul 1 şi a bitului 1 în bitul 0.
   pas3. Numărul astfel obţinut se adună cu 1.
De exemplu, să reprezentăm numărul -37.
                               3710 = 1001012 = [ 0] 100101
   pas1.         |-37| = 37                     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 numărului
negativ. Pentru a vedea ce număr negativ este reprezentat, putem
repeta procedeul de mai sus şi obţinem reprezentarea numărului
pozitiv dat de modulul său.
       O modalitate mai simplă este alocarea ponderii corespunzătoare
bitului de semn dar pe care o considerăm că reprezintă un număr
negativ. Astfel:
                                  34
10110112 = -1x26 + 1x24 + 1x23 + 1x21 + 1x20 = -64 + 27 = -37

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

        Aceste operaţii se execută folosind reprezentarea în
complement faţă de 2 a numerelor întregi, sau, mai bine zis, se execută
folosind în algoritmi bitul de semn ca pe un bit obişnuit.
De exemplu, dorim să calculăm:
                                    37-25
                                    25-37
                                  (-25)x37
                                (-25)x(-37)
        Pentru efectuarea acestor calcule, vom scrie reprezentările cu
bit de semn ale numerelor implicate:
                       2510 = 110012 = 011001
                       − 25 = 100111
                            10           2
                       
                       3710 = 1001012 = 0100101
                       − 3710 = 1011011
                       
        Se observă că 25 şi (-25) se reprezintă pe 6 biţi iar 37 şi (-37) pe
7 biţi.
        Deoarece am observat că biţii unui întreg cu semn nu au toţi
aceeaşi semnificaţie, este nevoie să reprezentăm numerele cu care
lucrăm pe un acelaşi număr de biţi. La adunări sau scăderi, biţii de
semn se vor afla în aceeaşi poziţie (vor avea aceeaşi pondere) şi vom
obţine astfel rezultate corecte. Pentru a avea o scriere pe un acelaşi
număr de biţi, se adaugă (completează) la stânga bitul de semn de un
număr corespunzător de ori. Astfel:
                               37 − 25 = 37 + (−25) = 0100101 + 1100111
                               0100101 +
− 2510 = 1001112 = 1100111
                              1100111
25 = 0110012 = 0011001        −−−−−−
                               0001100 = 1210




                                    35
25 − 37 = 25 + (−37) = 0011001 + 1011011
                             0011001 +
− 37 = 1011011
                            1011011
25 = 0110012 = 0011001
                              −−−−−−
                              1110100 = −64 + 52 = −12
       În continuare vom pune în evidenţă importanţa gamei de
reprezentare, adică a domeniului de valori ale datelor. Să considerăm,
spre exemplu, adunarea a două numere cu semn reprezentate pe un
octet (8 biţi). Aceste numere sunt cuprinse în gama
             [− 2 , 2 − 1]
                7   7
                           = [ − 128, 127] .
       Dacă vom dori să adunăm două numere din acest domeniu şi să
reprezentăm rezultatul tot pe un octet, putem avea surprize. De
exemplu, să considerăm operaţiile (117-12) şi (117+12). Se observă că
operanzii sunt în gama de reprezentare a numerelor cu semn pe 8 biţi.
Prin prima scădere, ne aşteptăm să obţinem un rezultat, 105, în aceeaşi
gamă de reprezentare.
117-12=117+(-12) = 01110101+11110100 = 01101001 = 10510,
rezultat corect.
117+12 = 01110101+00001100 = 10000001 = -12710,
rezultat evident incorect.
       Incorectitudinea provine de la faptul că rezultatul a depăşit
gama de reprezentare. Dacă rezultatul este interpretat pe 9 biţi de
exemplu, gama de reprezentare devine [ − 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ă precizăm dacă se lucrează sau nu cu
bit de semn şi pe câţi biţi se face reprezentarea, pentru că numai în
acest context interpretarea rezultatelor este corectă.
       În ceea ce priveşte înmulţirea numerelor întregi cu semn (cu bit
de semn), aici problema nu mai are o rezolvare asemănătoare, în
sensul că nu putem trata biţii de semn la fel cu cei de reprezentare ai
valorii. Astfel, procesorul studiază biţii de semn şi ia o decizie în
privinţa semnului rezultatului. De fapt, se realizează funcţia logică
XOR a biţilor de semn. Numerele negative se vor lua în modul, iar
operaţiile de înmulţire se vor face numai cu numere pozitive. La final,

                                  36
funcţie de semnul rezultatului, se ia decizia reprezentării corecte a
rezultatului.
       Spre exemplu, să calculăm (-25)x37. Pentru aceasta, procesorul
va primi pentru procesare următoarele două numere:
                    37 x(−25) = [ 0]100101 × [1]100111
Se analizează separat biţii de semn şi se ia decizia că rezultatul va fi
negativ, deci, la final, se va reprezenta în complement faţă de 2. Mai
departe se va lucra cu 25, modulul numărului (-25), care se obţine prin
complementarea faţă de 2 a numărului binar 1100111:
                     11001110011000+1=0011001
Se va reţine pentru procesare numai numărul (fără semn) 11001, care
se va înmulţi cu numărul (fără semn) 100101, obţinând, aşa cum am
arătat mai sus, valoarea 1110011101. Mai departe, se adaugă bitul de
semn, 0 pentru numere pozitive, obţinându-se 01110011101. Acest
ultim număr se va complementa faţă de 2, obţinându-se
10001100010+1=[1]0001100011, adică valoarea -1024+99 = -925,
valoarea corectă.
       Ca o concluzie, pentru a furniza rezultate corecte, procesorul va
trebui informat în permanenţă despre ce fel de numere prelucrează (cu
sau fără semn) şi care este lungimea lor de reprezentare (toate trebuie
să aibă aceeaşi lungime).
       Reprezentarea în complement faţă de 2 se poate folosi şi pentru
numerele reale negative, bitul de semn fiind MSB de la partea
întreagă. Astfel, -12.25 poate avea reprezentarea:
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 rămân valabile considerentele
                     de la numere întregi.
       În cazul de mai sus, problema reprezentării numărului negativ a
fost rezolvată cu ajutorul bitului de semn dar problema reprezentării
punctului binar va avea altă rezolvare.
2.3.3 Reprezentarea internă a numerelor reale

      Din considerentele de la reprezentarea externă a datelor putem
trage alte concluzii importante din punct de vedere al reprezentării

                                  37
interne. Numerele binare întregi fără semn au aceeaşi reprezentare
atât externă cât şi internă.
       Numerele întregi cu semn (care în reprezentare externă sunt
prefixate cu ± ) au ca reprezentare internă un bit de semn, dar care se
tratează deosebit de ceilalţi biţi ai reprezentării. Toţi întregii cu semn,
care au MSB=1, sunt reprezentaţi intern în complement faţă de 2.
       Numerele reale se pot reprezenta identic cu cele întregi cu semn,
cu o precizare: nu se face o deosebire netă între biţii reprezentării
părţii întregi şi cei ai reprezentării părţii fracţionare. Acest tratament
nediferenţiat provine de la reprezentarea ştiinţifică uzuală cu mantisă
şi exponent. Fie, spre exemplu, reprezentarea binară a numărului
12.25:      12.2510 = 1100.01 = 0.110001 x 2 4
        Calculatorul poate reprezenta şirul de biţi 110001 şi reţine
faptul că punctul se pune după primii 4 biţi ai reprezentării. Acest
lucru se întâmplă şi în realitate. Deci, singura deosebire între
reprezentarea numerelor reale şi a celor întregi constă în faptul că
numerele reale necesită o informaţie suplimentară despre aşa numitul
exponent, în cazul nostru numărul pozitiv 4.
        În cele ce urmează, vom prezenta tipurile de bază pe care le pot
avea datele în reprezentarea internă.
        Tipul unei date determină modul în care procesorul stochează şi
prelucrează data respectivă. Cum primele procesoare care au condus la
apariţia pe piaţă a primelor calculatoare pentru neprofesionişti (aşa
numitele Home Computers) au fost procesoare capabile să prelucreze
şi să transmită în paralel 8 biţi, a fost naturală gruparea a 8 biţi într-o
entitate numită byte.
                 1B = 8b (adică un byte reprezintă 8 biţi)
        Procesoarele au evoluat, ajungându-se în prezent la procesoare
pe 64 de biţi. Cum evoluţia lor s-a făcut trecându-se succesiv prin
multipli de 8 biţi, s-au impus şi alte entităţi de reprezentare a
informaţiei, pe care le vom prezenta sintetic în tabelul de mai jos.

   Denumire          Dimensiune       Denumire                 Notaţie
                                      echivalentă
                   Nr.      Nr.
                   byte     biti
Byte               1B       8b        octet                    B
Word               2B       16 b      cuvânt                   W

                                    38
Denumire         Dimensiune       Denumire                 Notaţie
                                     echivalentă
                   Nr.      Nr.
                   byte     biti
Double_Words       4B       32 b     Cuvânt dublu             DW
Quad_Words         8B       64 b     Cuvânt cvadruplu         QW
Ten_Words          10B      80 b                              TW
      A determina reprezentarea internă înseamnă să determinăm
lungimea reprezentării (de obicei în multipli de octeţi), modul de
interpretare al biţilor ce compun reprezentarea şi gama de
reprezentare, adică să determinăm magnitudinea (valorile minime şi
maxime pozitive şi negative) ce pot fi reprezentate în formatul
respectiv.
      În limbajul C, există două tipuri de reprezentare pe care le
putem numi principale: tipul întreg şi tipul real, fiecare având şi
anumite particularizări. Astfel, tipul întreg (int) include şi tipul
caracter (char) iar tipul real (float) include şi tipul real extins
(double).
      Tipurile de date le vom reprezenta de la simplu la complex, în
ordinea char, int, float, double.
      Tipurile de bază sunt char, int, float, double şi cu ajutorul
modificatorilor de tip putem obţine diverse particularizări.
Modificatorii pot fi signed, unsigned, short, long.
      Ca o generalitate, numerele sunt reprezentate intern luându-se
în considerare bitul de semn, deci implicit numerele întregi sau reale
au MSB bit de semn. Dacă se specifică explicit, prin modificatorul
unsigned, nu se mai consideră (interpretează) bitul de semn.
      2.3.3.1 Tipul char

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

        S      b6      b5      b4       b3     b2     b1       b0


    Bit de semn
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ă aceeaşi valoare reprezentată în format
hexazecimal (baza 16) iar în coloana Sym se reprezintă simbolul afişat
pe monitoarele PC.
       Întregul alfabet al limbajului C se regăseşte în mulţimea
primelor 128 de caractere ASCII. Restul de 128 de caractere se mai
numeşte şi set de caractere extins ASCII şi poate fi vizualizat printr-un
program simplu.
       Trebuie menţionat faptul că reprezentarea datelor în format
hexazecimal este foarte răspândită în tehnica programării
calculatoarelor. Avantajul reprezentării interne a datelor în format

                                     40
hexazecimal constă în folosirea unui număr mai mic de cifre (de 4 ori
mai mic decât numărul de cifre binare).
       Reprezentarea unui număr natural în format hexazecimal se
realizează cu metoda împărţirii succesive la 16 sau, mai simplu,
pornind de la reprezentarea binară a numărului.
       Cum mulţimea cifrelor hexa conţine 16 simboluri (0…9 şi A…
F), pentru codificarea celor 16 cifre avem nevoie de 4 cifre binare (
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 câte 4 biţi
     - se înlocuieşte fiecare grup de 4 biţi cu cifra hexazecimală pe
         care o codifică.
De exemplu, să presupunem că avem numărul 217.
21710 = 110110012 = 1101.10012 = D916 = 13 ⋅161 + 9 ⋅16 0 = 208 + 9 = 217
      În acest mod, dacă un număr are o reprezentare internă pe un
număr de k octeţi, se poate reprezenta simplu cu ajutorul a 2k cifre
hexazecimale.
       În tabelele de mai jos se prezintă codificarea ASCII a
caracterelor.
       Codurile corespunzătoare simbolurilor alfanumerice din tabel
sunt exact semnalele binare care se transmit în reprezentarea internă.
Cu alte cuvinte, dacă la tastatură se tastează simbolul “a”, atunci
circuitele corespunzătoare transmit spre calculator semnale binare
corespunzătoare codului 1010 0001, adică 61H sau 97 în zecimal.
       La fel se întâmplă când se lucrează cu procesoare de text sau
când se tipăreşte un document la imprimantă. Sistemul de calcul
manevrează codurile ASCII corespunzătoare literelor şi cifrelor pe
care utilizatorul le poate interpreta.

  D    H Sym D          H     Sym D       H    Sym D        H    Sym
  0    0 Null 1         1     ►   3       2        4        3    0
              6         0         2       0        8        0
  1    1 ☺    1         1     ◄   3       2    !   4        3    1
              7         1         3       1        9        1
  2    2 ☻    1         1     ↕   3       2    "   5        3    2
              8         2         4       2        0        2
  3    3 ♥    1         1     ‼   3       2    #   5        3    3
              9         3         5       3        1        3
                                   41
4       4    ♦        2   1    ¶     3    2    $        5    3    4
                          0   4          6    4             2    4
    5       5    ♣        2   1    §     3    2    %        5    3    5
                          1   5          7    5             3    5
    6       6    ♠        2   1    ▬     3    2    &        5    3    6
                          2   6          8    6             4    6
    7       7             2   1    ↨     3    2    '        5    3    7
                          3   7          9    7             5    7
    8       8             2   1    ↑     4    2    (        5    3    8
                          4   8          0    8             6    8
    9       9             2   1    ↓     4    2    )        5    3    9
                          5   9          1    9             7    9
    1       a    LF       2   1a   →     4    2a   *        5    3a   :
    0                     6              2                  8
    1       b    ♂        2   1    ←     4    2    +        5    3    ;
    1                     7   b          3    b             9    b
    1       c    ♀        2   1c   ∟     4    2c   ,        6    3c   <
    2                     8              4                  0
    1       d    CR       2   1    ↔     4    2    -        6    3    =
    3                     9   d          5    d             1    d
    1       e    ♫        3   1e   ▲     4    2e   .        6    3e   >
    4                     0              6                  2
    1       f    ☼        3   1f   ▼     4    2f   /        6    3f   ?
    5                     1              7                  3


D       H       Sy    D       H    Sy    D    H        Sy   D     H       Sym
                m                  m                   m
6       4       @       8 5        P     96   60       `    11    7       p
4       0             0   0                                 2     0
6       4       A       8 5        Q     97   61       a    11    7       q
5       1             1   1                                 3     1
6       4       B       8 5        R     98   62       b    11    7       r
6       2             2   2                                 4     2
6       4       C       8 5        S     99   63       c    11    7       s
7       3             3   3                                 5     3
6       4       D       8 5        T     10   64       d    11    7       t
8       4             4   4              0                  6     4

                                        42
6    4     E           8 5    U    10    65    e      11       7    u
9    5               5   5         1                  7        5
7    4     F           8 5    V    10    66    f      11       7    v
0    6               6   6         2                  8        6
7    4     G           8 5    W    10    67    g      11       7    w
1    7               7   7         3                  9        7
7    4     H           8 5    X    10    68    h      12       7    x
2    8               8   8         4                  0        8
7    4     I           8 5    Y    10    69    i      12       7    y
3    9               9   9         5                  1        9
7    4a    J           9 5a   Z    10    6a    j      12       7a   z
4                    0             6                  2
7    4     K           9 5    [    10    6b    k      12       7    {
5    b               1   b         7                  3        b
7    4c    L           9 5c       10    6c    L      12       7c   |
6                    2             8                  4
7    4     M           9 5    ]    10    6d    M      12       7    }
7    d               3   d         9                  5        d
7    4e    N           9 5e   ^    11    6e    n      12       7e   ~
8                    4             0                  6
7    4f    O           9 5f   _    11    6f    o      12       7f   ⌂
9                    5             1                  7

       2.3.3.2     Tipul int
       Acest tip se foloseşte pentru reprezentarea numerelor întregi cu
sau fără semn. Odată cu standardizarea ANSI C din 1989, s-a trecut la
modul de reprezentare a întregilor impus de noul procesor Intel 80386
dotat şi cu coprocesorul matematic Intel 80387.
          MSB

          S    b30                                 Octetul 1
                                                   Octetul 2
                                                   Octetul 3
                                         b0        Octetul 4

                                         LSB



                                  43
Tipul int este identic cu signed int şi utilizează o reprezentare pe
4B a numerelor întregi cu semn. Reprezentarea pe 4 octeţi duce la
posibilitatea măririi gamei de reprezentare astfel:
    max = 231 −1
    
                                         ( )
                                          3         3
                                                          ( )
                 ; 231 = 2 ⋅ 230 = 2 ⋅ 210 ≅ 2 ⋅ 103 ≅ 2 ⋅109
    min = −231
    
Rezultă că putem reprezenta numere întregi în gama:
               [± 2.1475 ⋅10 ] ≅ [− 2 ⋅10 , 2 ⋅10 ]
                               9           9          9

unsigned int nu va mai lua în considerare bitul de semn, astfel încât
reprezentarea internă este de forma din figura de mai jos. Evident,
   
   max = 232 −1 32
   
   min = 0
                                       3
                                          ( )    3
                ; 2 = 4 ⋅ 230 = 4 ⋅ 210 ≅ 4 ⋅ 103 ≅ 4 ⋅109( )
   
      Gama de reprezentare          se   poate   schimba        cu   ajutorul
modificatorilor short sau long.
                    MSB

                     S   b14
                                                 b0

                                                 LSB
short int se va reprezenta pe 2B, sub forma
max = 215 − 1

             ; 215 = 2 5 ⋅ 210 = 32 ⋅ 210 ⇒ [ − 32768, 32767] .
min = −215

unsigned short int va schimba gama de reprezentare în [ 0, 65535]
      long int se va reprezenta pe 8B şi va conduce la o gamă imensă
de reprezentare a numerelor întregi, lucru dovedit de
                         ( ) 6
          ± 2 63 = ±2 3 ⋅ 210 ≅ ±8 ⋅1018 = ±9.2234 ⋅1018
      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 număr se
                                   44
poate exprima ca un număr zecimal (deci, cu punct zecimal)
multiplicat cu o putere a lui zece sau ca un număr real binar (cu punct
binar) multiplicat cu o putere a lui 2.
         5.2510 = 101.01 = 1.0101 x 2 2 = 0.10101 x 2 3
       Se observă cum stocarea în calculator a unei date floating-point
necesită trei părţi:
    - bitul de semn (sign)
    - mantisa, fracţia (significand)
    - exponent (exponent)
    Folosind formatul specific I80386, în limbajul C se disting trei
tipuri de date reale:
    - float , cu reprezentare pe 4 octeţi (32 biţi, double word)
    - double, cu reprezentare pe 8 octeţi (64 biţi, quad word)
    - long double, cu reprezentare pe 10 octeţi (80 biţi, ten word)
               MSB

                 b31 b30



                                                b0

                                               LSB




                                  45
Exponent                 Significand
   S
            biased

   31 30                 23 22                              0
           Exponent = 8b             Significand = 23b             float
   S
           Bias = 7FH=127

    63 62               52 51                                0
         Exponent = 11b              Significand = 52b              double
    S
         Bias = 3FFH=1023

    79 78                   64 63                            0
         Exponent = 15b              Significand = 52b              long
    S
         Bias = 3FFFH=16383                                         double
       Tipurile float şi double sunt formate pentru numere reale ce
există numai în memorie. Când un astfel de număr este încărcat de
procesor în stiva pentru numere reale (flotante) pentru prelucrare sau
ca rezultat al prelucrării, el este automat convertit la formatul long
double (sau extended).
       În cazul în care acest număr se stochează în memorie, el se
converteşte la tipul float sau double. Toate cele trei subtipuri reale au
un format comun, care va fi prezentat în continuare. Ceea ce le
deosebeşte este numărul de biţi alocaţi pentru exponent şi pentru
mantisă, precum şi interpretarea biţilor mantisei (significand).
       Semnul are alocat în toate formatele un singur bit: 0 pentru
numere pozitive şi 1 pentru numere negative.
       Mărimea câmpului exponent variază cu formatul şi valoarea sa
determină câţi biţi se mută la dreapta sau la stânga punctului binar.
       Câmpul significand este analogul mantisei în notaţia ştiinţifică.
El conţine toţii biţii semnificativi ai reprezentării, deci biţii
semnificativi atât ai părţii întregi cât şi ai părţii fracţionare cu singura
restricţie ca aceşti biţi să fie consecutivi. Deoarece punctul binar este
mobil, cu cât sunt mai mulţi biţi alocaţi părţii întregi, cu atât vor fi mai
puţini pentru partea fracţionară şi invers. Cu cât formatul este mai
larg, cu atât se vor reprezenta mai precis numerele.
       Pentru a salva un spaţiu preţios de stocare, nici unul dintre cele
trei formate float nu stochează zerouri nesemnificative. De exemplu,
pentru numărul 0.0000101 = 0.101x 2 −4 câmpul significand va stoca
                                    46
numărul 101, nu şi cele 4 zerouri nesemnificative ale părţii
fracţionare. Pentru a salva şi mai mult spaţiu, pentru formatele float şi
double câmpul significand nu va conţine primul bit semnificativ care
obligatoriu este 1. Câştigând acest bit (numit bit phantom), se
dublează gama de reprezentare. Formatul long double va conţine
totuşi bitul de semn 1 cel mai semnificativ. Punctul binar se pune
exact înaintea primului bit din câmpul significand, adică după bitul 1
implicit (phantom). În cazul long double, se aplică după primul bit 1.
       Pentru a uşura operarea cu aceste numere, câmpul exponent nu
este stocat ca un număr întreg cu semn, ci este decalat (normalizat, cu
bias) pentru a reprezenta numai numere pozitive (deci exponentul este
interpretat ca număr natural fără semn). Biasul adăugat se scade pentru
a afla exponentul exact. Avantajul exponentului decalat constă, pe
lângă faptul că nu mai are nevoie de bit de semn, în faptul că pentru a
compara două numere reale putem începe prin compararea biţilor
pornind de la MSB către LSB, cel mai mare fiind cel care are 1 la
primul bit diferit. Se decide astfel foarte rapid care număr este cel mai
mare. Ca exemplu, să considerăm un format float în care se stochează:
       Sign = 0
       Exponent = 10000010 = 13010
       Significand = 1001000…00
Valoarea reală a exponentului va fi 130 - 127 = 3
Biţii câmpului significand se obţin adăugând MSB phantom, deci
aceştia vor fi 11001guatda.com/cmx.p000...00
Numărul real care s-a stocat este:
                    0.110guatda.com/cmx.p010...00 x 24 = 1100.1 =12.5
Reprezentarea internă a numărului 12.5, pe 4 octeţi (float), este
următoarea:
               Semn

                0    1    0    0    0    0    0    1
                0    1    0    0    1    0    0    0
                0    0    0    0    0    0    0    0
                0    0    0    0    0    0    0    0

                                               LSB
Cu alte cuvinte, putem spune că reprezentarea internă a numărului real
12.5 este (în format hexazecimal):
                                   47
12.510 = 4148000016
În cazul în care dorim să reprezentăm numărul negativ –12.5, singurul
bit care se va modifica va fi bitul de semn, care devine 1. Astfel,
reprezentarea internă în format float a numărului negativ real –12.5
este:
                 Semn

                     1       1       0   0   0    0   0        1
                     0       1       0   0   1    0   0        0
                     0       0       0   0   0    0   0        0
                     0       0       0   0   0    0   0        0

                                                           LSB
                       − 12.510 = C148000016
      Dacă numărul 12.5 se reprezintă în formatul double, deci pe 8
octeţi, atunci reprezentarea sa internă se va realiza astfel:
    - bitul de semn va fi 0
    - 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:
     3      + 1023 = 1026 = 1024 + 2 = 10000000010
exponent −1   bias
    -   significand va fi acelaşi ca la tipul float, dar reprezentat pe 52
        de biţi
                      Semn

                         0       1   0   0   0    0   0    0
                         0       0   1   0   1    0   0    1
                         0       0   0   0   0    0   0    0
                         0       0   0   0   0    0   0    0
                         0       0   0   0   0    0   0    0
                         0       0   0   0   0    0   0    0
                         0       0   0   0   0    0   0    0
                         0       0   0   0   0    0   0    0

                                                          LSB

                     12.510 = 402900000000000016


                                             48
Reţinem că la numere reale numai bitul de semn indică dacă
numărul este pozitiv sau negativ, mantisa şi exponentul se reprezintă
ca numere naturale fără bit de semn. Formatele prezentate mai sus
respectă standardul IEEE 754 de reprezentare a internă a numerelor
reale în computere.
      Se poate pune o întrebare legitimă: de ce bias-ul în cazul float
spre exemplu este 127? Pentru a răspunde la această întrebare, putem
face următorul raţionament:
    - exponentul cu semn este reprezentat pe 8 biţi, deci este în
         gama de reprezentare [ − 128, + 127] .
    - pentru a obţine un exponent pozitiv, adăugăm numărul 128.
    - deoarece bitul phantom nu este reprezentat, exponentul
         trebuie micşorat cu o unitate pentru a indica unde anume se
         poziţionează exact punctul binar.
    - Exponent pozitiv = exponent +128 – 1 = exponent + bias
de unde rezultă evident faptul că bias = 127 în cazul tipului float.
       În final să analizăm un exemplu de procesare a produsului a
două numere reale. Vrem să calculăm valoarea 5.25 x 1.5. Pentru
aceasta, vom scrie cei doi factori ai produsului în forma:
                                                 .10101 ×
                                                     .11
5.2510 = 101.012 = .10101 × 23
                                               −−−−−
                        1
1.510 = 1.12 = .11 × 2                    ;       10101

5.25 × 1.5 = [ ( .10101) × ( .11) ] × 2
                                        3+1      10101
                                               −−−−−
                                                .0111111
             ⇒ 5.25 × 1.5 = .0111111 × 24 = 111.111 = 7.875
      Se observă cum câmpurile exponent şi significand sunt
procesate separat, în final corelându-se forma de reprezentare internă.
Game de reprezentare pentru numerele reale

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

                                  49
La tipul float, avem
exponent _ biasmax = 255 ⇒ exponent _ realmax = 255 − 127 = 128

                     ( )12
nmax = 2128 = 28 ⋅ 210 = 256 ⋅ (1024 )12 ≅ 256 ⋅1036 = 2.56 ⋅1038
Valoarea maximă exactă, calculată fără a aproxima ca mai sus:
                                                    38
210 = 1024 ≅ 1000 = 103 este n max = 3.4028 ⋅10
exponent _ biasmin = 0 ⇒ exponent _ realmin = 0 − 127 = −127

                      ( )
nmin = 2−127 = 2− 7 ⋅ 210
                          −12
                                            ( )
                              = 23 ⋅ 2−10 ⋅ 210
                                                −12
                                                          ( )
                                                    = 8 ⋅ 210
                                                              −13
                                                                  ≅ 8 ⋅ 10− 39
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

                     ( )
nmax = 21024 = 24 ⋅ 210
                        102
                            = 16 ⋅ (1024 ) 306 ≅ 16 ⋅ 10306 = 1.6 ⋅ 10307
Valoarea maximă exactă este n max = 1.7 ⋅10 308
  exponent _ biasmin = 0 ⇒ exponent _ realmin = 0 − 1023 = −1023

  nmin = 2−1023 = 2−3 ⋅ 210( )
                            −102
                                 ≅ .125 ⋅ 10−306
Valoarea pozitivă minimă exactă este n min = 1.1125 ⋅ 10 −308
Efectuând aceleaşi consideraţii şi calcule pentru tipul long double,
                 n
                  max = 1.1 ⋅ 10
                                   4932
vom obţine       
                 n min = 3.4 ⋅ 10 −4932
                 
2.3.5. Codificare BCD

      Procesorul I80386 este considerat primul procesor care are
capacitatea de a procesa operaţii aritmetice asupra unor numere
reprezentate în zecimal codificat binar (BCD, binary-coded decimal)
în locul formatelor binare standard. Reprezentarea numerelor în cod
BCD este folosită pentru a face numerele binare mai accesibile
operatorului uman. Neajunsul acestei reprezentări este faptul că
numerele BCD ocupă spaţiu de stocare mai mare decât numerele
binare. Ele sunt mai uşor de interpretat de către programatorul uman,
pentru computer neavând nici un fel de relevanţă. Procesorul 80386

                                     50
poate manevra două tipuri de formate BCD: împachetat şi
neîmpachetat (packed BCD şi unpacked BCD). În formatul unpacked
BCD, o cifră zecimală se stochează pe un octet. Spre exemplu, cifra
zecimală 5 va fi reprezentată intern sub forma 00001001. Formatul
packed BCD stochează două cifre zecimale pe un octet, crescând
capacitatea de stocare internă precum şi gama de reprezentare pe un
acelaşi număr de octeţi. Ambele codificări folosesc reprezentarea pe 4
biţi a cifrelor zecimale. Spre exemplu, numărul 9817 se stochează pe 4
octeţi în format unpacked BCD şi pe 2 octeţi în format packed BCD:
        unpacked BCD: 9817 = 0000 1001 0000 1000 0000 0001
                      0000 0111
        packed BCD: 9817 = 1001 1000 0001 0111
        Se observă cum valoarea maximă care se poate stoca pe un
octet este 9 pentru unpacked BCD, 99 pentru packed BCD şi 255
pentru codificarea binară fără semn standard.
        Toate formatele reale prezentate se conformează standardului
IEEE 754 pentru reprezentarea numerelor în virgulă mobilă în format
binar.
       Ca o concluzie la acest capitol, decisiv pentru înţelegerea
dezvoltărilor ulterioare, putem sintetiza următoarele:
      Reprezentarea externă a numerelor se referă la modul în care
          operatorul uman acceptă schimbul de date cu calculatorul.
          Acest schimb de date are dublu sens: de la operatorul uman
          către calculator şi invers.
Reprezentarea externă este de obicei zecimală şi are un format
aproape identic cu formatul matematic uzual: simbol de semn prefixat,
punct zecimal, mantisă sau exponent. Numerele naturale se mai pot
reprezenta şi în format octal sau hexazecimal. În format extern se
introduc datele de la tastatură pentru prelucrare şi se obţin pe monitor
sau la imprimantă rezultatele oferite de calculator.
      Reprezentarea internă a numerelor se referă la modul în care
          se stochează datele în memoria RAM a calculatorului şi
          respectiv în regiştrii interni ai microprocesorului. Această
          reprezentare internă este legată de noţiunea de tip de dată.
      Tipul de dată întreg (integer) se reprezintă intern pe 2, 4 sau 8
          octeţi în complement faţă de 2, cu cel mai semnificativ bit
          (MSB) bit de semn: 1 pentru numere întregi negative şi 0
          pentru numere întregi pozitive. Un caz particular de dată de
          tip întreg este tipul character, interpretat ca întreg pe un octet.
                                     51
   Tipul de dată real (float) se reprezintă intern pe 4, 8 sau 10
        octeţi şi conţine 3 câmpuri de biţi distincte: bit de semn, câmp
        mantisă şi câmp exponent, de lungimi corespunzătoare.
Dacă se specifică explicit, toate numerele se pot defini fără semn
(unsigned), caz în care calculatorul nu mai interpretează bitul de semn
(MSB) diferit ci îl include în câmpul de reprezentare al mărimii,
crescând gama de reprezentare.




                        Capitolul III

            ELEMENTELE DE BAZĂ ALE
                LIMABJULUI C


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

      Prezentăm câteva comenzi simple pentru a lansa în execuţie un
program C folosind compilatorul BORLANDC v3.1 (versiunea
pentru sistemul de operare DOS):
      • după setarea pe directorul corespunzător se tastează
          - bc – pentru a intra în mediul BorlandC;
          - <Alt>-F – pentru a selecta meniul File;
          - N – pentru a deschide un fişier nou.
      • se editează programul sursă folosind editorul mediului
           BorlandC;
Exemplu:

                                  52
#include <stdio.h>
    void main (void)
     {
         printf("Primul program in C!");
     }
      •   se acţionează tasta F2 şi se indică numele fişierului (cu
          extensia .c sau .cpp) pentru salvarea programului sursă pe
          disc – de exemplu mesaj.c - (se recomandă salvarea pe disc
          după efectuarea oricărei modificări în programul sursă
          pentru evitarea pierderii accidentale a acesteia);
      • se realizează compilarea, link-editarea (realizarea
          legăturilor) şi lansarea în execuţie a programului executabil
          mesaj.exe tastând <CTRL>-F9;
      • pentru a vizualiza rezultatele execuţiei programului se
          tastează <Alt>-F5;
      • se revine în fereastra de editare a mediului acţionând o tastă
          oarecare;
      • pentru a închide un fişier sursă se tastează <Alt>-F3 iar
          pentru a ieşi din program în mediul de operare se tastează
          <Alt>-X.
      La fel de simplu poate fi utilizată şi varianta pentru sistemul de
operare WINDOWS a compilatorului BORLANDC v3.1:
      • se lansează în execuţie programul bcw.exe;
      • se selectează din meniul File opţiunea New, creându-se
          fişierul noname00.cpp;
      • în fereastra noname00.cpp se introduce codul programului;
      • din meniul File se selectează opţiunea Save As… sau Save
          iar în căsuţa de dialog care apare se va salva fişierul
          program cu extensia .cpp;
      • din meniul Compile se selectează opţiunea Build All ce va
          afişa caseta de dialog Compiling (compilare);
      • dacă operaţia de compilare se încheie cu succes (nu există
          erori de sintaxă în program) compilatorul va afişa mesajul
          Press any key, caz în care compilatorul va crea fişierul
          executabil;
      • lansarea în execuţie a fişierului executabil se poate realiza
          folosind opţiunea Run din meniul Run sau combinaţia de
          taste <CTRL>-F9.
                                  53
Fiecare limbaj de programare are un set de reguli, denumite
reguli sintactice, reguli ce trebuie respectate la editarea unui cod
sursă. Dacă este încălcată o regulă sintactică, programul nu va fi
compilat cu succes. În acest caz, pe ecran va fi afişat un mesaj de
eroare ce specifică linia ce conţine eroarea, precum şi o scurtă
descriere a erorii. În exemplul următor programului îi lipseşte
caracterul punct şi virgulă după utilizarea funcţie printf:

#include <stdio.h>
  void main (void)
  {
    printf("Primul program in C!")
  }
         La compilare pe ecran vor apare următoarele mesaje de eroare:
Compiling NONAME00.CPP:
Error NONAME00.CPP 5: Statement missing ;
Error NONAME00.CPP 5: Compound statement missing }
      Cu toate că în codul sursă există doar o eroare, compilatorul de
C va afişa două mesaje de eroare. Lipsa caracterului punct şi virgulă
provoacă o serie de erori în cascadă. Pentru a corecta erorile sintactice
se merge cu ajutorul cursorului în linia indicată de către mesajul de
eroare şi se corectează instrucţiunea respectivă.

3.2. Structura unui program C
      Conceptul de bază folosit în structurarea programelor scrise în
limbajul C este funcţia. Astfel, un program în C este compus din cel
puţin o funcţie şi anume funcţia main() sau funcţia principală. La
rândul ei, funcţia main() poate apela alte funcţii definite de utilizator
sau existente în bibliotecile ce însoţesc orice mediu de dezvoltare de
programare în C. Structura generală a unei funcţii C este de forma:
      tip nume_funcţie (param_1, param_2, ...,param_n)
         − − − − − − − −
                        
         − − − − − − − −   Instrucţiuni declarare tip parametri
         − − − − − − − −
                        
         {
− − − − − − −
             
− − − − − − −   Corp funcţie=secvenţă de instrucţiuni sau apel de funcţii
− − − − − − −
             


                                        54
}
unde: - tip reprezintă tipul de dată returnat de funcţie (în mod implicit
o funcţie returnează tipul int);
         - nume_funcţie reprezintă numele sub care funcţia este
cunoscută în program;
        - param_1,...,param_n - parametrii cu care funcţia este apelată
şi al căror tip poate fi declarat direct în această listă, sau prin
instrucţiuni separate plasate imediat după lista parametrilor.
       Corpul funcţiei este definit ca o secvenţă de instrucţiuni şi/sau
apeluri de funcţii şi este delimitat de restul funcţiei prin paranteze
acolade.
       În limbajul C există două categorii de funcţii. O primă categorie
este formată de funcţiile ce returnează o valoare la revenirea din ele în
punctul de apel, tipul acestei valori fiind definit de de tipul funcţiei.
Cealaltă categorie conţine funcţiile ce nu returnează nici o valoare la
revenirea din ele , pentru aceste funcţii fiind utilizat cuvântul cheie
void în calitate de tip. El semnifică lipsa unei valori returnate la
revenirea din funcţie.
       Exemple:
1) void f(void)
{
  …………
}
      Funcţia f nu are parametri şi nu returnează nici o valoare.
2) double g(int x)
{
  …………
}
      Funcţia g are un parametru x de tipul int şi returnează la
revenirea în programul principal o valoare flotantă în dublă precizie.
      Funcţiile C sunt în general unităţi independente, compilabile
separat. Instrucţiunile, la rândul lor, pot defini tipul unor date folosite
în program, sau operaţii ce trebuie executate prin program.
            Din punct de vedere sintactic, orice instrucţiune trebuie
terminată cu caracterul ";", iar grupurile de instrucţiuni pot fi
delimitate prin caracterele { şi } pentru a forma unităţi sintactice noi
de tip bloc. Funcţiile apelate vor primi valori pentru argumentele
(parametrii) lor şi pot returna către funcţia apelantă valori de un
anumit tip.


                                    55
Cu aceste precizări generale, dacă avem un program
compus din două funcţii, şi anume funcţia principală şi o funcţie
apelată f(), atunci structura acestuia va fi de forma:
tip main( )                          
{                                    
                                     
--------                             
                                     
                                             Functia principala
f( );    / * apelul functiei f() * / 
--------                             
                                     
}                                    
                                     
                 ___________
                  f( )       
                  {          
                             
                             
                  − − − − − −     Functia f( )
                  − − − − − −
                             
                  }          
                             

       Programul începe cu execuţia funcţiei main(). Aceasta funcţie
este folosită în general fără parametri. La rândul lor, funcţiile apelate
pot fi scrise în limbaj C, sau realizate în alte limbaje de programare:
asamblare, Fortran, Pascal etc.

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

       Mulţimea caracterelor C include literele mari şi mici ale
alfabetului englez şi cifrele zecimale din sistemul de numere arabe.
       Literele mari şi mici ale alfabetului englez sunt următoarele:
          ABCDEFGHIJKLMNOPRSTUVWXYZ
       abcdefghijklmnoprstuvwxyz
       iar cifrele zecimale:
       0 1 2 3 4 5 6 7 8 9.
       Aceste litere şi cifre pot fi folosite pentru a forma constante,
identificatori şi cuvinte cheie. Compilatorul C prelucrează litere mari
şi mici în mod distinct.
3.3.2. Caractere whitespace

            Spaţiul, tab-ul, linefeed (linie nouă), carriage return
(revenire la capătul rândului), form feed, tab-ul vertical şi newline
sunt numite caractere whitespace deoarece servesc pentru spaţiere
între cuvinte, aliniere la o nouă coloană, salt la linie nouă. Aceste
caractere separă instrucţiuni definite de utilizator, constante şi
identificatori, de celelalte instrucţiuni dintr-un program. Compilatorul
C ignoră caracterele whitespace dacă nu sunt folosite ca separatori sau
drept componente de constante, sau ca şiruri de caractere. Caracterele
whitespace sunt utilizate pentru a face programele mai lizibile.
Comentariile sunt de asemenea tratate ca whitespace.
3.3.3. Caractere speciale şi de punctuaţie

             Caracterele speciale şi de punctuaţie din mulţimea
caracterelor C sunt folosite pentru mai multe scopuri. Tabelul următor
prezintă aceste caractere.
      Aceste caractere au o semnificaţie specială pentru compilatorul
de C. Caracterele de punctuaţie din setul de caractere reprezentabile C
care nu apar în acest tabel pot fi utilizate numai în şiruri, constante
caracter şi comentarii.
        Caracte            Nume              Caracte          Nume

                                  57
r                                    r
       ,           Virgulă                 !          Semnul
                                                      exclamării
       .          Punct                    |          Bară verticală
       ;          Punct şi virgulă         /          Slash
       :          Două puncte                        Backslash
       ?          Semnul                   ~          Tilda
                  întrebării
       ’          Apostrof                 _          Underscore
       ”          Ghilimele                #          Diez
       (          Paranteză                %          Procent
                  stânga
       )          Paranteză                &          Ampersand
                  dreapta
       [          Paranteză                ^          Săgeată sus
                  dreaptă stânga
       ]          Paranteză                *          Asterisc
                  dreaptă dreapta
       {          Acoladă stânga           -          Minus
       }          Acoladă dreapta          =          Egal
       >          Mai mare                 +          Plus
       <          Mai mic
3.3.4. Secvenţe escape

            Secvenţele escape sunt combinaţii speciale de caractere
formate din whitespace şi caractere negrafice constituite în şiruri şi
constante caracter. Ele sunt în mod tipic utilizate pentru a specifica
acţiuni precum carriage return şi tab pe terminale şi imprimante şi
pentru a furniza reprezentarea caracterelor care normal au înţeles
special, cum ar fi ghilimelele (”). O secvenţă escape constă dintr-un
backslash urmat de o literă sau combinaţii de cifre. Setul complet de
secvenţe escape cuprinde:
      a caracterul    BEL              - activare sunet
      b caracterul    BS (backspace) - revenire cu un spaţiu
      f caracterul    FF (form feed) - salt de pagină la imprimantă
      n caracterul LF (line feed)             - rând nou
      r caracterul    CR (carriage return) - revenire la coloana 1
      t caracterul    HT (horizontal tab)     - tab orizontal
      v caracterul VT (vertical tab)           - tab vertical

                                 58
caracterul       (backslash)
      " caracterul      " (double qoute)         - ghilimele
      ' caracterul      ' (single qoute)         - apostrof
      0 caracterul      NULL
      ooo                                  - constantă octală
      xhh                                  - constantă hexazecimală

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

Exemplu: '6'                  'x6'              6 ASCII
         '60'                 'x30'             48 ASCII
         '137'                'x5f'             95 ASCII
            Numai cifrele octale (de la 0 la 7) pot apare într-o secvenţă
escape octală şi trebuie să apară cel puţin o cifră. De exemplu,
caracterul backspace poate fi scris ca „10” în loc de „010”.
       Similar, o secvenţă hexagesimală poate să conţină cel puţin o
cifră, iar a doua cifră poate fi omisă. Totuşi, când se utilizează
secvenţe escape în şiruri, este indicat să se scrie toate cele trei cifre ale
secvenţei. Altfel, caracterul care urmează după secvenţa escape ar
putea fi interpretat ca o parte a secvenţei, dacă se întâmplă să fie o
cifră octală sau hexagesială. De exemplu, secvenţa 0331 este
interpretată drept ESC şi 1. Dacă am scrie 331, omiţând primul zero,
atunci am avea o interpretare greşită.
       Secvenţele escape permit caractere de control negrafice pentru a
fi transmise către display. Caracterele negrafice trebuie totdeauna
reprezentate ca secvenţe escape. Plasând necorespunzător un caracter
negrafic în programe C, el are rezultat imprevizibil.

3.4. Identificatori
       Identificatorii sunt nume ce sunt date variabilelor, funcţiilor şi
etichetelor utilizate în program. Un nume este o succesiune de litere şi
eventual cifre, primul caracter fiind literă. În calitate de litere se pot
utiliza literele mici şi mari ale alfabetului englez, precum şi caracterul

                                     59
subliniere (_). Numărul de caractere care intră în componenţa unui
nume nu este limitat. Numele sunt utilizate pentru a defini diferite
variabile sau funcţii într-un program C.
       În mod implicit, numai primele 32 de caractere dintr-un nume
sunt luate în considerare, adică două nume sunt diferite dacă ele diferă
în primele 32 de caractere ale lor.
Exemple de nume: a, b1, a1b2c3, Fs, _hG, Nume, nUME, …
       Se recomandă ca numele să fie sugestive, adică ele să sugereze
pe cât posibil scopul alegerii lor sau a datei pe care o reprezintă.

3.5. Cuvintele cheie ale limbajului C
      În limbajul C există un număr de cuvinte care au o utilizare
predefinită, numite cuvinte cheie. Utilizatorul nu poate să utilizeze
aceste cuvinte pentru a denumi variabile sau funcţii într-un program.
Tabelul următor prezintă cuvintele cheie ale limbajului C:
Cuvintele cheie ale limbajului C
auto       default     float       register    struct      volatile
break      do          for         return      switch      while
case       double      goto        short       typedef     char
else       if          signed      union       const       enum
int        sizeof      unsigned    continue    extern      long
static     void


3.6. Constante
       În C, constantele se referă la valori fixe pe care programul nu le
poate modifica. Constantele pot fi: întregi, în virgulă mobilă sau reale,
constante-caracter, constante-şir sau enumerări. Zero poate fi folosit
ca o constantă pentru tipurile pointer, iar şirurile de caractere sunt de
fapt constante de tip char[]. Este posibil, de asemenea, să se specifice
constante simbolice. O constantă simbolică este un nume a cărui
valoare nu poate fi modificată în domeniul său.
       În C există trei feluri de constante simbolice:
           1. orice valoare de orice tip poate fi folosită ca şi
               constantă prin adaugarea cuvântului cheie const la
               definirea sa;
           2. un set de constante întregi definite ca o enumerare;
           3. orice nume de vector sau funcţie.

                                   60
3.6.1. Constante caracter
      O constantă caracter este un caracter inclus între apostrofuri. De
exemplu, 'a', 'A' şi '%' sunt constante caracter. Valoarea unei constante
caracter este chiar valoarea numerică corespunzătoare caracterului dat
în setul de caractere al maşinii. De pildă, dacă pe un calculator
caracterele se reprezintă în cod ASCII, atunci constanta '1' are
valoarea 0618, 4910 sau 3116. Constantele caracter pot fi folosite în
operaţii de calcul exact ca şi întregii.
      Caracterele pot fi reprezentate şi prin secvenţe escape (de
exemplu, prin constanta 'n' se introduce caracterul newline).
3.6.2. Constante întregi
        Constantele întregi se reprezintă în 4 forme: zecimale, octale,
hexazecimale şi constante caracter. Constantele zecimale sunt cel mai
frecvent folosite şi se reprezintă ca şiruri de cifre zecimale. O
constantă care începe cu zero urmat de x (0x) este un număr
hexazecimal, iar o constantă care începe cu zero este un număr octal.
Pentru a reprezenta cifrele hexazecimale 10,...,15 se folosesc literele
a,...,f sau literele mari corespunzătoare. Notaţiile octale şi
hexazecimale sunt utile în exprimarea succesiunilor de biţi.
Exemplu:
     int     hex = 0xFF;       /* numărul 255 în zecimal */
     int     oct = 011 ;       /* numărul 9 în zecimal */

3.6.3. Constante în virgulă mobilă
        O constantă în virgulă mobilă are tipul float, double sau long
double. Compilatorul, ca şi în cazul constantelor întregi, trebuie să
semnaleze eroare în cazul în care constantele sunt prea mari pentru a
putea fi reprezentate.
        Exemple de constante în virgulă mobilă:
            123.23      .23 0.23 1.0 1.         1.2e10      1.256-15
        Observaţie. În interiorul constantelor întregi sau reale nu pot
apare spaţii albe. De exemplu, 56.62 e - 17 nu este o constantă în
virgulă mobilă, ci sunt de fapt 4 atomi lexicali: 56.62, e, -, 17 şi se va
genera o eroare de sintaxă.
        Dacă se doreşte o constantă de tip float, aceasta se poate defini
astfel:     const float       pi8 = 3.14159265;



                                   61
3.6.4. Constante şir
        Constantele şir constau din caractere cuprinse între ghilimele,
ca în faimosul “Hello, worldn“. Constantele şir, spre deosebire de
altele, au o locaţie în memoria calculatorului. Caracterele dintr-un şir
sunt stocate în memorie, iar valoarea numerică a constantei este
adresa acestei memorii. În plus, compilatorul stochează caracterul
null ‘0’ la sfârşitul şirului, marcând astfel sfârşitul său.
        În cazul setului de caractere ASCII, constanta şir “0“ arată
astfel în memorie:

           3000                      48 (30H)       0
                                     ‘0’            ‘0’

iar valoarea constantei este adresa sa din memorie (în exemplul de mai
sus valoarea 3000), pe când valoarea caracterului 0 este 48 sau 30H.
       Cea mai scurtă constantă şir este şirul null scris drept “ “ şi este
stocat în memorie ca un singur caracter null ‘0’. De exemplu, dacă
avem constanta şir “ABC“ atunci, la o anumită adresă de memorie
vom avea:

                       Adresa              61
                                          ‘A’
                        Adresa+1           62
                                           ‘B’
                        Adresa+2           63
                                           ‘C’
                        Adresa+3            0
                                          ‘/0’
       Valoarea constantei şir “ABC“ va fi Adresa, adică valoarea
adresei locaţiei în care se stochează primul caracter din şir.
       Ca o ultimă remarcă, vom face precizarea că din punctul de
vedere al reprezentării, constanta caracter ‘A’, spre exemplu, este
diferită de consta şir “A“, care se stochează în memorie la o anumită
adresă şi se termină cu caracterul null, deci are alocaţi doi octeţi.
       Fiecare constantă şir conţine cu un caracter mai mult decât
numărul de caractere din şir deoarece aceasta se termină totdeauna cu
caracterul 0 care are valoarea 0.
       De exemplu, sizeof("asaf") va fi 5.

                                    62
Tipul unui şir este vector de un număr de caractere a.i. "asaf"
are tipul char[5]. Şirul vid se notează prin " " şi are tipul char[1]. De
notat că, pentru fiecare şir s, strlen(s) == sizeof(s) - 1, deoarece funcţia
strlen() nu numără şi terminatorul 0.
       În interiorul unui şir se poate folosi convenţia de notaţie cu .
Aceasta face posibilă reprezentarea caracterului ghilimele (") şi  în
interiorul unui şir. Cel mai frecvent caracter folosit este caracterul
'n'=newline (NL).
       De exemplu, instrucţiunea:
printf ("beep at end of message007n");
determină scrierea unui mesaj, a caracterului BEL şi a caracterului
NL. O secvenţă de forma n într-un şir nu determină introducerea unui
caracter NL în şir, ci este o simplă notaţie (n este caracter neafişabil).
       Nu este permisă continuarea şirurilor de caractere de pe o linie
pe alta.
       Atunci când se include o constantă numerică într-un şir de
caractere utilizând notaţia octală sau hexazecimală este recomandat să
se folosească 3 cifre pentru număr.
Exemplu:
char v1[] = "ax0fah0129";//'a' 'x0f' 'a' 'h' '012' '9'
char v2[] = "axfah 129"; /* 'a' 'xfa' 'h' '12' '9' */
char v3[] = "axfad127"; /* 'a' 'xfa' 'd' '127' */

3.6.5. Constanta zero
      Zero poate fi utilizat ca o constantă pentru tipurile întregi, în
virgulă mobilă sau pointer. Nu se recomandă alocarea unui obiect la
adresa zero. Tipul lui zero va fi determinat de context.
3.6.6. Obiecte constante

      Cuvântul cheie const poate fi inclus într-o declaraţie a unui
obiect pentru a determina ca tipul acestui obiect să fie constant şi nu
variabil.
Exemplu :         const        int   model = 145;
                  const        int   v[ ] = {1, 2, 3, 4};
             Deoarece nu i se poate atribui o valoare, o constantă poate
fi doar iniţializată. Declarând ceva ca fiind constant, ne asigurăm că
valoarea sa nu se modifică în domeniul său. Astfel instrucţiunile:
            model = 165;      /* eroare */
            model++;          /* eroare */
      vor determina apariţia unor mesaje de eroare corespunzătoare.
                                     63
De notat că const modifică un tip ceea ce înseamnă că
restricţionează felul în care se poate utiliza un obiect, şi nu modul de
alocare. Pentru o constantă, compilatorul nu rezervă memorie
deoarece i se cunoaşte valoarea (precizată la iniţializare). Mai mult,
iniţializatorul pentru o expresie constantă este, de obicei (dar nu
întotdeauna), o expresie constantă. Dacă este aşa, aceasta poate fi
evaluată în timpul compilării.
3.6.7. Enumerări

      Folosirea cuvântului cheie enum este o metodă alternativă
pentru definirea constantelor întregi, ceea ce este uneori mult mai util
decât utilizarea lui const. De exemplu,
      enum {ASM , AUTO , BREAK };
defineşte 3 constante întregi denumite enumeratori şi le atribuie valori.
      Deoarece valorile enumeratorilor sunt atribuite implicit,
începând cu 0, aceasta este echivalentă cu:
           const       ASM = 0;
           const       AUTO = 1;
           const       BREAK = 2;
      O enumerare poate avea nume. De exemplu,
           enum Keyword {ASM , AUTO , BREAK };
defineşte o enumerare cu numele Keyword. Numele enumerării devine
sinonim cu int şi nu cu un nou tip. Declararea unei variabile Keyword
în loc de int poate oferi atât utilizatorului, cât şi compilatorului, o
sugestie asupra modului de utilizare. De exemplu,
enum Keyword Key; //declara var. Key de tip enum Keyword
switch (Key) {
case ASM:
     ...........
     break;
     case BREAK:
     ...........
     break; }
determină compilatorul să iniţieze un avertisment deoarece sunt
folosite numai două din cele trei valori ale lui Key.
                         Capitolul IV

        OPERANZI ŞI OPERATORI ÎN C

                                   64
4.1. Operanzi

      O expresie, în limbajul C, este formată dintr-un operand sau mai
mulţi legaţi prin operatori. Un operand poate fi:
    - o constantă;
    - o constantă simbolică;
    - numele unei variabile;
    - numele unui tablou;
    - numele unei structuri;
    - numele unui tip;
    - numele unei funcţii;
    - elementele unui tablou;
    - elementele unei structuri;
    - o expresie inclusă între paranteze rotunde.
       Unui operand îi corespunde un tip şi o valoare. Dacă tipul
operandului este bine precizat la compilare, valoarea operandului se
determină fie la compilare, fie la execuţie.

      Exemple:
         1. 6353 – este o constantă întreagă zecimală de tip int şi
             reprezintă un operand constant de tip int.
         2.    float x2 – reprezintă declaraţia variabilei x2, iar
             numele x2 reprezintă un operand de tipul float.
         3. 0xa13d – este o constantă întreagă hexazecimală de tip
             unsigned şi reprezintă un operand de tipul unsigned.
         4. produs(a,b) – este un apel al funcţiei produs. Această
             funcţie reprezintă un operand al cărui tip coincide cu
             tipul valori returnate de funcţia produs.

4.2. Operatori
       Operatorii pot fi unari sau binari în funcţie de numărul de
operanzi cărora li se aplică. Un operator unar se aplică unui singur
operand, iar un operator binar se aplică la doi operanzi. Operatorul
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:

                                 65
aritmetici, relaţionali şi logici, operatori pentru prelucrare biţi, precum
şi câţiva operatori speciali pentru sarcini particulare.
       La scrierea unei expresii se pot utiliza operatori din toate
clasele. La evaluarea unei astfel de expresii este necesar să se ţină
seama de priorităţile operatorilor care aparţin diferitelor clase de
operatori, de asociativitatea operatorilor de aceeaşi prioritate şi de
regula conversiilor implicite.
4.2.1. Operatori aritmetici
      Lista operatorilor aritmetici este următoarea:

 +    reprezintă operatorul plus unar sau binar, în funcţie de context
 -    reprezintă operatorul minus unar sau binar, în funcţie de context
 *    reprezintă operatorul de înmulţire (binar)
 /    reprezintă operatorul de împărţire (binar)
 %    reprezintă operatorul modulo (binar)
       Operandul operatorului unar plus trebuie să fie de tip aritmetic
sau pointer, iar rezultatul este valoarea operandului. Un operand întreg
presupune o promovare a întregilor.
       Operandul operatorului unar minus trebuie să fie de tip
aritmetic, iar rezultatul este numărul negativ corespunzător. Un
operand întreg presupune promovarea întregilor.
       Operanzii operatorilor * şi / trebuie să fie de tip aritmetic, iar ai
lui % trebuie să fie de tip întreg. Operatorul binar / reprezintă câtul, iar
% oferă restul împărţirii primului operand la al doilea. Dacă al doilea
operand al operatorului / sau % este zero, rezultatul este nedefinit.
Pentru operanzi de tip întreg este adevărată egalitatea:
                             (a / b) * b + a % b = a
       În expresii operatorii binari + şi - au aceeaşi precedenţă, care
însă este mai mică decât a grupului *, / şi %. Precedenţa ultimului
grup este mai mică decât cea a operatorilor unari + şi -. Folosirea
parantezelor în expresii poate schimba precedenţa între operatori în
timpul evaluării acestora.
Exemplu: Dacă a, b, c, d sunt variabile de tip int, atunci:
       - expresia d * b % a           este echivalentă cu (d * b) % a;
       - expresia -a / d              este echivalentă cu (-a) / d;
      - expresia a=b=c=d-15 este echivalentă cu a=(b=(c=(d
-15)));
       - expresia a%-b*c este echivalentă cu (a%(-b))*c;
                                    66
4.2.2. Operatori de incrementare şi decrementare
       În C, operaţiile de forma i = i+1 şi j = j-1 pot fi programate
folosind doi operatori unari specifici şi anume ++ pentru incrementare
cu 1 şi -- pentru decrementare cu 1. Aceşti operatori pot fi folosiţi atât
ca prefix pentru variabile (de exemplu, ++i, --j) sau ca sufix (i++, j--).
Între aceste moduri de utilizare există diferenţe. Astfel, în expresia +
+i, variabila i este incrementată înainte de a-i folosi valoarea, în timp
ce în expresia i++, variabila i este incrementată după întrebuinţarea
valorii acesteia.
Exemplu: Considerăm secvenţa:
                  x = 10;
                  y = ++x;
      Dacă se afişează y, atunci vom găsi y = 11 deoarece mai întâi se
incrementează x şi apoi se atribuie valoarea lui y.
Dacă scriem:     x = 10;
                  y = x++;
      vom găsi y=10 (mai întâi se face atribuirea lui x la y şi apoi
incrementarea lui x).
           Precedenţa tuturor operatorilor aritmetici este:
                Înaltă           ++ --
                                 + - (unari)
                                 * / %
                Scăzută          + - (binari)
      Operatorii de aceeaşi precedenţă sunt evaluaţi de al stânga la
dreapta.
4.2.3. Operatori relaţionali
       Operatorii relaţionali permit compararea a două valori şi luarea
unei decizii după cum rezultatul comparării este adevărat sau fals.
Dacă rezultatul operaţiei este fals, atunci valoarea returnată este zero,
iar dacă este adevărat, valoarea returnată este 1.
       Operatorii relaţionali folosiţi în C sunt:




                                   67
==         egal
                     !=         diferit
                     <          mai mic strict
                     <=         mai mic sau egal
                     >          mai mare strict
                     >=         mai mare sau egal
      Operatorii relaţionali au o precedenţă mai mică decât operatorii
aritmetici, astfel o expresie de forma a < b + c este interpretată ca
a<(b+c).

4.2.4. Operatori logici
       Operatorii logici binari && (ŞI, AND) şi || (SAU, OR) precum
şi operatorul logic unar de negare “!“ (NOT), atunci când sunt aplicaţi
unor expresii, conduc la valori întregi 0 şi 1, cu semnificaţia fals şi
adevărat. Semantica acestor operatori se deduce din tabelul următor,
unde e1 şi e2 sunt două expresii:
         e1                 e2            e1&&e2      e1||e2      ! e1
       zero               zero              0            0         1
       zero          diferit de zero        0            1         1
  diferit de zero         zero              0            1         0
  diferit de zero    diferit de zero        1            1         0

      Expresiile legate prin operatori logici binari sunt evaluate de la
stânga la dreapta.
      Precedenţa operatorilor logici şi relaţionali este următoarea:
                       Înaltă              !
                                           > >= < <=
                                           == !=
                                           &&
                      Scăzută              ||

Astfel, expresia: 10>5 && !(10<9) || 3<4          este adevarată;
       expresia: 1 && !0 || 1                     este adevarată;
       expresia; 1 && ! (0 ||1)                   este falsă.
Programul următor tipăreşte numerele pare cuprinse între 0 şi 100.
 # include <stdio.h>
 main ()

                                   68
{ int i;
   for (i = 0; i <= 100; i++)
   if (! (i%2)) printf ("%d" , i); }
       Operatorii logici şi relaţionali sunt utilizaţi în formarea
instrucţiunilor repetitive precum şi a instrucţiunii if.
4.2.5. Operatori logici la nivel de bit
       Ne reîntoarcem la cei trei operatori de tip booleean & (AND,
§I), | (OR, SAU) şi ~ (NOT) precum şi la un al patrulea operator,
denumit SAU-EXCLUSIV ^ (EXCLUSIVE-OR). Aceşti operatori se
aplică la nivel de bit sau grupuri de biţi, după tabelele:
AND    x        OR     x       NOT            EXCLUSIVE-OR          x
 &     0 1       |     0 1      ~                  ^                0 1

 y 0 0 0        y 0 0 1         y 0     1        y   0              0 1
   1 0 1          1 1
                   1              1     0            1              1 0


        În C, aceşti operatori se aplică în paralel biţilor corespunzători
aflaţi în orice poziţie. Din această cauză ei se mai numesc şi operatori
logici pe bit. Trebuie făcută distincţia faţă de operatorii logici, care
folosesc notaţii dublate: &&, ||, sau !. Operatorii logici au aceleaşi
denumiri, dar ei tratează întregul operator ca pe o singură valoare,
adevărată sau falsă. În scriere, se mai foloseşte şi denumirea bit-and,
bit-or, bit-negate sau exclusive-or.
        Ca exemplu, considerăm operaţia bit-not. Fie numărul binar:
                    N2 = 0000000000000111 = 0x0007 = 710
        Negarea sa pe bit se realizează cu instrucţiunea
                              ~0x7 sau ~07 sau ~7
        şi valoarea sa va fi
               ~N2 = 1111111111111000 = 0xFFF8 sau 0177770
        pe un computer cu întreg pe 16 biţi sau 0xFFFFFFF8 pe un
computer cu întreg pe 32 de biţi.
      Exemplul următor realizează un SAU şi un ŞI pentru două
caractere:
      ‘a’ | ’c’ = 0110 0001 | 0110 0011 = 0110 0011 = ‘c’
      ‘a’ & ’c’ = 0110 0001 & 0110 0011 = 0110 0010 = ‘a’


                                   69
Deoarece limbajul C a fost gândit să înlocuiască limbajul de
asamblare în majoritatea operaţiilor de programare, acesta trebuie să
aibă capacitatea să suporte toţi (sau cel puţin mulţi) operatorii utilizaţi
în asamblare.
        Operatorii pentru prelucrarea biţilor se aplică biţilor dintr-un
byte sau cuvânt, ambele variabile de tip char şi short int. Aceşti
operatori nu se aplică tipurilor float, double, long double, void sau
altor tipuri mai complexe.
        Operatorii pentru prelucrarea biţilor utilizaţi în C sunt:

                 &        AND
                 |         OR
                 ^        exclusive OR (XOR)
                 ~        complement faţă de unu (NOT)
                 >>        deplasare dreapta
                 <<        deplasare stânga

       Operaţiile pe biţi sunt utilizate de obicei în drivere, pentru
testarea şi mascarea anumitor biţi. De exemplu, operaţia AND poate fi
folosită pentru ştergerea unor biţi dintr-un byte sau dintr-un cuvânt,
OR poate fi folosită pentru setarea unor biţi, iar XOR pentru
complementarea unor biţi şi testarea egalităţii a 2 bytes.
       Observaţie: Operatorii relaţionali şi logici (&&, ||, !,...) produc
totdeauna un rezultat care este fie 0, fie 1, pe când operatorii similari
destinaţi prelucrării biţilor pot produce orice valoare arbitrară, în
concordanţă cu operaţia specifică.
       Operatorii >> şi << deplasează toţi biţii dintr-o variabilă la
dreapta, respectiv la stânga. Forma generală a operaţiilor de deplasare
este:
    variabilă >> număr_de_poziţii_bit - pentru deplasare dreapta.
    variabilă << număr_de_poziţii_bit - pentru deplasare stânga.
       În poziţiile rămase libere, după deplasare, se introduc zerouri.
Operaţiile de deplasare pot fi utile când se decodifică perifericele de
intrare cum ar fi convertoarele D/A (digital/analogice) şi când se
citesc informaţii de stare. Operatorii de deplasare se pot utiliza şi
pentru realizarea cu rapiditate a operaţiilor de înmulţire şi împărţire.
Se ştie că o deplasare stânga cu 1 bit realizează înmulţirea cu 2, iar o
deplasare dreapta cu 1 bit realizează o împărţire cu 2.
       Exemplu:
                                    70
x   = 7;                     0 0 0 0 0 1 1 1      7
           x   << 1;           0   0   0 0 1 1 1 0      14
           x   << 3;           0   1   1 1 0 0 0 0    112
           x   << 2;           1   1   0 0 0 0 0 0    192
           x   >> 1;           0   1   1 0 0 0 0 0      96
           x   >> 2;           0   0   0 1 1 0 0 0      24
Următorul exemplu evidenţiază efectul operatorilor de deplasare:
# include <stdio.h>
void disp_binary();
/* prototipul functiei disp_binary() */
void main() {
int i = 1, t;
for (t=0;t<8;t++) {
disp_binary(i);
i=i<<1;}
printf (" n");
for (t=0;t<8;t++) {
i=i>>1;
disp_binary(i);}}

void disp_binary(int i)
/* se defineste functia disp_binary() */
/* care afiseaza bitii dintr-un byte */
{register int t;
for (t=128;t>0;t=t/2)
if (i&t) printf("1");
else printf("0");
printf("n");}
      Programul produce următoarea ieşire:
           0   0   0   0   0   0   0   1
           0   0   0   0   0   0   1   0
           .   .   .   .   .   .   .   . . . . .
           1   0   0   0   0   0   0   0
           1   0   0   0   0   0   0   0
           0   1   0   0   0   0   0   0
           .   .   .   .   .   .   .   . . . . .
           0   0   0   0   0   0   0   1
       Deşi limbajul C nu conţine un operator de rotire, se poate
realiza o funcţie care să efectueze această operaţie. De exemplu rotirea
la stânga cu o poziţie a numărului 10101010 ne conduce la numărul
01010101 şi se realizează după schema:




                                            71
1   0    1   0    1   0   1   0

                      0   1    0   1    0   1   0   1
       O posibilitate de realizare a operaţiei de rotire necesită
utilizarea unei uniuni cu două tipuri de date diferite. De exemplu
utilizând uniunea:
           union rotate {
           char ch[1];
           unsigned int i;
           } rot;
      Următoarea funcţie realizează o rotire cu 1 bit.
void rotate_bit(union rotate *rot)
{rot->ch[1]=0;
rot->i=rot->i<<1;
if (rot->ch[1]) rot->i=rot->i|1;}
       Atât întregul i cât şi cele două caractere ch[0] şi ch[1]
partajează primii doi octeţi din cei 4 rezervaţi de uniune.
       Numărul de rotit se introduce (pe 8 biţi) în ch[0]. Se roteşte
apoi întregul i (deci se rotesc toţi cei 4 octeţi care îi corespund). Se
testează MSB al lui ch[0] care se găseşte în urma rotirii în poziţia LSB
din ch[1]. Dacă este 1, atunci se setează la 1 LSB din ch[0],
realizându-se astfel operaţia de rotaţie.
       Un exemplu de program care să utilizeaze această funcţie:
# include <stdio.h>
union rotate {
char ch[1];
unsigned int i;
} rot;
void disp_binary();
void rotate_bit();
void main() {
register int t;
rot.ch[0]=147;
for (t=0;t<7;t++) {
disp_binary(rot.i);
rotate_bit(&rot);}}
/* se defineste functia rotate_bit() */
void rotate_bit(union rotate *rot)
{rot->ch[1]=0;
rot->i=rot->i<<1;
if (rot->ch[1]) rot->i=rot->i|1;}

                                   72
/* se defineste functia disp_binary() */
void disp_binary(int i)
{register int t;
for (t=128;t>0;t=t/2)
if (i&t) printf("1");
else printf("0");
printf("n");}
      Acest program realizează rotirea numărului 14710=100100112
cu 6 poziţii
            10010011
            00100111
            01001110
            10011100
            00111001
            01110010
            11100100
       Programul de mai sus funcţionează pentru numere
reprezentabile pe un octet (mai mici de 255). Dacă dorim să facem o
rotire pe doi octeţi, atunci se poate modifica programul de mai sus
după cum urmează:
# include <stdio.h>
union rotate {
char ch[3];
unsigned int i;
} rot;
void disp_binary();
void rotate_bit();
void main() {
register int t;
rot.i=17843;
for (t=0;t<7;t++) {
disp_binary(rot.i);
rotate_bit(&rot);}}
/* se defineste functia rotate_bit() */
void rotate_bit(union rotate *rot)
{rot->ch[2]=0;
rot->i=rot->i<<1;
if (rot->ch[2]) rot->i=rot->i|1;}
/* se defineste functia disp_binary() */
void disp_binary(int i)
{register int t;
for (t=32768;t>0;t=t/2)
if (i&t) printf("1");
else printf("0");
printf("n");}


                                73
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 %cn", ch, ~ch);} while (ch != 'q');}

4.2.6. Operatorul de atribuire

      În C, operatorul de atribuire (asignare) este semnul egal (=).
Valoarea expresiei din dreapta se atribuie variabilei din stânga
operatorului "=". În C, forma:
                        suma = a + b + c;
trebuie privită ca o nouă expresie, numită expresie de asignare.
Valoarea ei este chiar valoarea expresiei din dreapta operatorului de
atribuire. Dacă într-o expresie se fac mai multe atribuiri, atunci
evaluarea se face de la dreapta la stânga:
      x = y = z = 0 este echivalentă cu (x=(y=(z=0)));
       O expresie de atribuire de forma x = x + 5 în care variabila
din stânga apare imediat după operatorul = se poate scrie într-o formă
compactă de tipul x += 5, unde operatorul += este tot un operator de
atribuire. Majorităţii operatorilor binari le corespund operatori de
atribuire de forma "op = " unde op poate fi : +, -, *, %, <<, >>, &, ^ ,

      Operatorii de atribuire (asignare) sunt:
             = , += , -= , *= , /= , %= , <<= , >>= , &= , ^= , |=

      Deci, o expresie de asignare de forma :
           var    =   (var)   op    (expr)
unde var este o variabilă şi expr este o expresie, admite o reprezentare
compactă de forma:
           var    op=    expresie
Într-o formă compactă, ca mai sus, var este evaluată o singură dată.
4.2.7. Operatorul sizeof
    Operatorul sizeof returnează numărul de octeţi necesar
memorării variabilei sau tipului datei care este operandul său. Dacă

                                    74
sizeof operează asupra unui tip de date, atunci tipul trebuie să apară
între paranteze. De exemplu, sizeof(char) va fi 1, sizeof(int)
va fi 4 etc., deci rezultatul este un număr întreg, fără semn. Fişierul
standard stddef.h defineşte tipul size_t al rezultatului oferit de
operatorul sizeof. Dacă sizeof se aplică unui tablou, rezultatul este
numărul total de octeţi din tablou. Exemplul din programul următor
prezintă dimensiunea principalelor tipuri de date:
# include <stdio.h>
# include <stddef.h>
void main(){
 printf("nTip caracter pe %d octet",sizeof(char));
  printf("nTip short int pe %d octeti",sizeof(short
int));
 printf("nTip int pe %d octeti",sizeof(int));
 printf("nTip long int pe %d octeti",sizeof(long int));
 printf("nTip float pe %d octeti",sizeof(float));
 printf("nTip double pe %d octeti",sizeof(double));
 printf("nTip long double pe %d octetin", sizeof(long
double));}
      În urma execuţiei acestui program, se va afişa (rezultatele
depind de tipul de procesor sau de compilator):
      Tip   caracter pe 1 octet
      Tip   short int pe 2 octeti
      Tip   int pe 4 octeti
      Tip   long int pe 4 octeti
      Tip   float pe 4 octeti
      Tip   double pe 8 octeti
      Tip   long double pe 8 octeti

4.2.8. Operatorul ternar ?
        Operatorul " ? " poate fi utilizat pentru a înlocui instrucţiunea if
/ else având forma:
             if (conditie)
            expresie1
             else
            expresie2
      Operatorul ternar " ? " necesită trei operanzi şi are forma
generală:
Expr1 ?     Expr2 : Expr3
      unde Expr1, Expr2 şi Expr3 sunt expresii.
      Se evaluează expresia Expr1. Dacă este adevărată, se evaluează
Expr2, care devine valoarea întregii expresii. Dacă Expr1 este falsă, se


                                    75
evaluează Expr3, iar valoarea acesteia devine valoarea întregii
expresii:
Exemplu:
          x = 10;
            y = x > 9 ? 100 : 200;
       Cum 10 > 9, valoarea lui y va fi 100. Dacă x ar fi mai mic decât
9, y va primi valoarea 200.
            Acelaşi program scris cu if /else va fi:
            x = 10;
            if (x > 9) y = 100;
            else y = 200;
        În alcătuirea expresiilor din declaraţia operatorului ternar " ? "
pot fi folosite şi funcţii:
Exemplu:
# include <stdio.h>
f1();
f2(); // prototipurile functiilor f1() si f2()
void main() {
int t;
printf (": ");
scanf("%d",&t); // se introduce numarul intreg t
t?f1()+f2(t): printf(" S-a introdus zeron");}
f1() {printf ("S-a introdus "); }
f2(int n) {printf ("%dn", n);}
      Dacă se introduce zero, atunci va fi apelată printf() şi va afişa "
S-a introdus zero". Dacă se introduce alt număr, atunci programul va
executa atât funcţia f1(), cât şi funcţia f2().
4.2.9. Operatorul virgulă
     Operatorul virgulă se utilizează într-un şir în care se introduc
mai multe expresii. Astfel, instrucţiunea:
            x = (y = 3, y+1),
are că efect atribuirea valorii 4 variabilei x.
       Deci expresiile separate prin virgulă sunt evaluate de la stânga
la dreapta, prima expresie evaluată căpătând valoarea void. Dacă se
utilizează un operator de atribuire, valoarea atribuită variabilei din
stânga operatorului de atribuire este valoarea ultimei expresii din
dreapta, după evaluare. Exemplu:
            y = 10;
            x = (y = y - 5, 30 / y);
      Variabila x va căpăta valoarea 6.


                                     76
Observaţie Deoarece operatorul virgulă are o precedenţă mai
mică decât operatorul de atribuire, pentru ca atribuirile să se facă
corect, trebuie utilizate paranteze.
4.2.10. Operatorul de forţare a tipului sau de conversie
explicită (expresie cast)
       Adesea se doreşte specificarea conversiei valorii unui operand
spre un tip dat. Acest lucru este posibil folosind o construcţie de
forma: (tip) operand
       Printr-o astfel de construcţie valoarea operandului se
converteşte spre tipul indicat în paranteze. În construcţia de mai sus
(tip) se consideră că este un operator unar. Acest operator este
cunoscut sub numele de operator de forţare a tipului sau de conversie
explicită. De cele mai multe ori însă este utilizată denumirea engleză a
operatorului şi anume expresie cast.
       Exemplu:
       Presupunem că o funcţie oarecare f are un parametru de tip
double. Pentru ca această funcţie să poată fi apelată cu un parametru
int n (n este un parametru de tip întreg) acesta trebuie mai întâi
convertit la tipul double. Acest lucru se poate realiza printr-o atribuire:
double x
f(x=n)
       Un alt mod mai simplu de conversie a parametrului întreg spre
tipul double este utilizarea unei expresii cast:
       f((double)n)
       Operatorul de forţare a tipului fiind unar, are aceeaşi prioritate
ca şi ceilalţi operatori unari ai limbajului C.
4.2.11. Operatorii paranteză
      Parantezele rotunde se utilizează fie pentru a include o expresie,
fie la apelul funcţiilor. O expresie inclusă în paranteze rotunde
formează un operand. În acest mod se poate impune o altă ordine în
efectuarea operaţiilor, decât cea care rezultă din prioritatea şi
asociativitatea operatorilor.
      Operanzii obţinuţi prin includerea unei expresii între paranteze
impun anumite limite asupra operatorilor. De exemplu, la un astfel de
operand nu se pot aplica operanzii de incrementare şi decrementare
sau operatorul adresă. Astfel construcţiile:
(a-5+b)++      --(a+b)     &(a*b)
      sunt eronate.
                                    77
La apelul unei funcţii, lista parametrilor efectivi se include între
paranteze rotunde. În acest caz se obişnuieşte să se spună că
parantezele rotunde sunt operatori de apel de funcţie.
       Parantezele pătrate include expresii care reprezintă indici. Ele
se numesc operatori de indexare.
       Parantezele sunt operatori de prioritate maximă. Operatorii
unari au prioritatea imediat mai mică decât parantezele.
4.2.12. Operatorul adresă
      Operatorul adresă este unar şi se notează prin caracterul
&. El se aplică pentru a determina adresa de început a zonei de
memorie alocată unei date. În forma cea mai simplă, acest
operator se utilizează în construcţii de forma: &nume
unde nume este numele unei variabile simple sau al unei structuri. În
cazul în care nume este numele unui tablou, atunci acesta are ca
valoare chiar adresa de început a zonei de memorie alocată tabloului
respectiv şi, în acest caz, nu se mai utilizează operatorul adresă &.
4.2.13. Alţi operatori ai limbajului C
       În limbajul C se mai utilizeză şi operatorii: „ * ” , „ . ” şi „->”
       Operatorul „ * ” unar (a nu se confunda cu operatorul aritmetic
binar de înmulţire) se utilizează pentru a face acces la conţinutul unei
zone de memorie definită prin adresa ei de început. Se obişnuieşte să
se spună că operatorul de adresă & este operator de referenţiere, iar
operatorul „ * ” este operator de dereferenţiere.
       Operatorii „ . ” şi „ -> ” se utilizează pentru a se accesa
componentele unei structuri. Ei au prioritate maximă, având aceeaşi
prioritate cu parantezele.
4.2.14. Regula conversiilor implicite şi precedenţa operatorilor
       Regula conversiilor implicite se aplică la evaluarea expresiilor.
Ea acţionează atunci când un operator binar se aplică la doi operanzi
de tipuri diferite. În acest caz, operandul de tip inferior se converteşte
spre tipul superior al celuilalt operand şi rezultatul este de tip superior.
Înainte de toate se convertesc operanzii de tip char şi enum în tipul int.
       Dacă operatorul curent se aplică la operanzi de acelaşi tip,
atunci se execută operatorul respectiv, iar tipul rezultatului coincide cu
tipul comun al operanzilor. Dacă rezultatul aplicării operatorului


                                    78
reprezintă o valoare în afara limitelor tipului respectiv, atunci
rezultatul este eronat (are loc o „depăşire”).
       Exemplu: Rezultatul împărţiirii 7/3 este 2 şi nu 2.5 deoarece
cei doi operanzi sunt de tip întreg şi prin urmare rezultatul (care este
de tip real) este şi el convertit la tipul întreg.
       Dacă operatorul binar se aplică la operanzi de tipuri diferite,
atunci se face o conversie înainte de execuţia operatorului, conform
algoritmului umător:
     1. Dacă unul din operanzi este de tip long double, atunci celălalt
          operand se converteşte spre tipul long double iar tipul
          rezultatului aplicării operatorului este de asemenea de tip
          long double.
     2. Altfel, dacă unul din operanzi este de tip double atunci
          celălalt operand se converteşte spre tipul double iar tipul
          rezultatului aplicării operatorului este de asemenea de tip
          double.
     3. Altfel, dacă unul din operanzi este de tip float atunci celălalt
          operand se converteşte spre tipul float iar tipul rezultatului
          aplicării operatorului este de asemenea de tip float.
     4. Altfel, dacă unul din operanzi este de tip unsigned long atunci
          celălalt operand se converteşte spre tipul unsigned long iar
          tipul rezultatului aplicării operatorului este de asemenea de
          tip unsigned long.
     5. Altfel, dacă unul din operanzi este de tip long atunci celălalt
          operand se converteşte spre tipul long iar tipul rezultatului
          aplicării operatorului este de asemenea de tip long.
     6. Altfel, unul din operanzi trebuie sa fie de tip unsigned,
          celălalt de tip int şi acesta se converteşte spre tipul unsigned,
          iar tipul rezultatului aplicării operatorului este de tip
          unsigned.

       Precedenţele operatorilor C sunt prezentate în tabelul următor.
Operatorii aflaţi pe aceeaşi linie au aceeaşi prioritate. Ei se asociază de
la stânga la dreapta, exceptând operatorii unari, condiţionali şi de
atribuire, care se asociază de la dreapta la stânga.

          Precedenţa                     Operatorul


                                    79
Înaltă    () [ ] -> .
                         ! ~ ++ -- - (type) * & sizeof
                         * / %
                          + -
                          << >>
                          < <= > >=
                         == !=
                         &
                         ^
                         |
                         &&
                         ||
                         ?:
             Scăzută     = += -= *= /=
                         ,


                           Capitolul V

                        INSTRUCŢIUNI


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

5.1. Instrucţiuni etichetate (instrucţiunea goto)
      Instrucţiunile etichetate posedă etichete ca prefixe şi au forma:

             etichetă:         instrucţiune



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

      goto etichetă

       La întâlnirea instrucţiunii goto, se realizează un salt la
instrucţiunea prefixată de eticheta aflată după instrucţiunea goto.
       Deoarece o etichetă este locală în corpul unei funcţii rezultă că
ea este nedefinită în afara corpului funcţiei respective, deci, o
instrucţiune goto nu poate face salt la o instrucţiune din afara corpului
funcţiei în care este definită.
       Nu este recomandată utilizarea abuzivă a acestei instrucţiuni
deoarece programul devine mai puţin lizibil şi pot apare erori logice în
program foarte greu de detectat. Instrucţiunea goto se utilizează în
special pentru ieşirea din mai multe cicluri imbricate.
       Exemplu: Următorul program utilizează instrucţiunea goto
pentru a afişa numerele de la 1 la 100:

#include <stdio.h>
void main(void)
     {
         int nr=1;
         eticheta: printf(”%d”, nr++);
         if (nr<=100)
             goto eticheta;
     }


5.2. Instrucţiuni expresie
       Cele mai multe instrucţiuni sunt instrucţiunile expresie, care au
forma:
                               [ expresie ];
unde expresie este opţională.
       Majoritatea instrucţiunilor expresie sunt atribuiri sau apeluri de
funcţii. Deci, o instrucţiune expresie constă dintr-o expresie urmată de
caracterul ";".
Exemplu: a = b * c + 3;
         printf ("FAC. DE AUTOMATICA");

                                    81
Dacă expresia expresie de mai sus lipseşte, construcţia “;“ se
numeşte instrucţiune vidă. Aceasta nu are nici un efect, dar este
utilizată pentru a înlocui un corp vid al unei iteraţii sau pentru a plasa
o etichetă.

5.3. Instrucţiuni compuse
         O instrucţiune compusă este o posibilă listă de declaraţii şi/sau
instrucţiuni închise între acolade. Exemplu:
      { a = b + 2; b++; }
       O instrucţiune compusă se numeşte bloc. Un bloc ne permite să
tratăm mai multe instrucţiuni ca pe una singură. Corpul unei funcţii
este o instrucţiune compusă.
      Domeniul de vizibilitate al unui identificator declarat într-un
bloc se întinde din punctul declaraţiei până la sfârşitul blocului.
Identificatorii utilizaţi într-un bloc pot fi ascunşi prin declaraţii de
acelaşi nume în blocurile interioare blocului iniţial.
Exemplu:
# include <stdio.h>
  int      x = 34;   /* x este global */
  void main(void) {
      int *p = &x;   /*p preia adresa variabilei globale*/
      int x1, x2;
      printf("x = %dn", x);
{int x; /*x este local si il ascunde pe cel global */
x = 1;    /* atribuirea se face la cel local */
x1 = x;
printf("x = %dn", x1);}
{ int x;     /* se ascunde prima variabila locala */
x = 2;     /* se atribuie valoarea 2 acestui x */
x2 = x;
printf("x = %dn",x2); }
printf("x = %d %d %d n",x,x1,x2); }


5.4. Instrucţiuni de selecţie
5.4.1. Instrucţiunea if
      O instrucţiune if cu care în C se implementează o structură de
control de selecţie sau o structură alternativă, are următorul format
general:
            if (conditie) instructiune1;
            else instructiune2;

                                   82
unde conditie este orice expresie care prin evaluare conduce la o
valoare întreagă. Dacă valoarea expresiei este diferită de zero
(condiţie adevărată), atunci se execută instructiune1; altfel, dacă
valoarea expresiei este zero (condiţie falsă), se execută instructiune2.
În ambele cazuri, după executarea lui instructiune1 sau instructiune2,
controlul este transferat la instrucţiunea ce urmează după if. Aici, prin
instructiune1 sau instructiune2 se înţelege o instrucţiune simplă, o
instrucţiune compusă (un bloc) sau o instrucţiune vidă.
      Porţiunea else instructiune2; este opţională, în acest fel putându-
se obţine o structură de selecţie cu o ramură vidă de forma:
                 if (conditie)      instructiune;
Exemplu: Următorul program citeşte două numere şi afişează pe cel
mai mare dintre ele.
# include <stdio.h>
void main (void) {
  int x, y;
  printf("Introduceti doua numere intregi: n");
  scanf ("%d %d", &x, &y);
  if (x > y)
   printf ("Cel mai mare este : %dn",x);
  else
   printf("Cel mai mare este : %dn", y);
}
       Deoarece partea else dintr-o instrucţiune if este opţională, apare
o ambiguitate atunci când else este omis dintr-un if inclus (încuibat).
În C acest lucru se rezolvă prin asocierea lui else cu cel mai apropiat
if. De exemplu, în secvenţa:
           if (x)
              if (y)     printf ("1");
              else       printf ("2");
else este asociat cu instrucţiunea if(y). Dacă dorim ca else să fie
asociat cu if(x) trebuie să utilizăm acolade, astfel:
           if (x)
             { if (y) printf ("1");
             }
           else printf ("2");
Secvenţa anterioară este echivalentă cu:
           if (x) { if (y) printf ("1");
                    else ;}
           else printf ("2");




                                   83
5.4.2. Instrucţiuni de selecţie multiplă: if - else if
      Într-o instrucţiune if se poate include, pe o ramură, o altă
instrucţiune if. În acest fel se creează posibilitatea de a codifica
structuri de selecţie multiplă, folosindu-se perechi else if. O asemenea
construcţie este de forma:
                 if (conditie1)
                      instructiune1;
                 else if (conditie2)
                      instructiune2;
                 else if (conditie3)
                      instructiune3;
                 . . . . . . . . . . . . . . . .
                 else if (conditieN)
                      instructiuneN;
                 else
                      instructiuneN+1;
      În acest caz, condiţiile sunt testate în ordine. Dacă una din ele
este adevărată, atunci este executată instrucţiunea corespunzătoare,
după care controlul este transferat la instrucţiunea următoare din
program. Codul pentru fiecare alternativă poate fi format dintr-o
instrucţiune simplă (inclusiv instrucţiunea vidă) sau dintr-un bloc
delimitat prin { şi }. Dacă nici una dintre expresii nu este adevărată,
atunci se execută secvenţa corespunzătoare ultimei alternative
introdusă prin else. Această ultimă alternativă nu este obligatorie,
structura putându-se încheia după secvenţa notată cu instructiuneN.
Exemplu: Considerăm un program care realizează conversiile inch-cm
şi cm-inch. Presupunem că indicăm unitatea intrării cu i pentru inch şi
c pentru centimetru:
# include <stdio.h>
# include <conio.h>
void main(void) {
  const float fact = 2.54;
  float x,in,cm;
  char ch = 0;
  printf ("nIntroduceti numarul: n");
  scanf("%f",&x);
  printf("nIntroduceti unitatea: n");
  ch = getche(); /* se introduce un caracter de la
               tastatura care se afiseaza pe ecran */
  if (ch == 'i') {
     in = x;
     cm = x * fact;}
  else if(ch == 'c') {

                                  84
in = x/fact;
     cm = x; }
  else in = cm = 0;
  printf("n%5.2f in = %5.2f cm n",in,cm);               }

5.4.3. Instrucţiunea switch
      Într-o instrucţiune de selecţie switch, se compară, pe rând, o
valoare cu constantele dintr-o mulţime şi în momentul găsirii unei
coincidenţe se execută instrucţiunea sau blocul de instrucţiuni asociate
acelei constante. Forma generală a instrucţiunii switch este:
           switch (variabila) {
                case constanta1 :
                     secventa_instructiuni_1
                break;
                case constanta2 :
                     secventa_instructiuni_2
                break;
                case constanta3 :
                     secventa_instructiuni_3
                break;
                . . . . . . . . . . . . . . . . . . .
                case constantaN :
                     secventa_instructiuni_N
                break;
                default :
                     secventa_instructiuni_N+1
           }
       Instrucţiunea switch realizează transferul controlului la una din
secvenţele de instrucţiuni dacă valoarea variabila ce trebuie să aibă
tipul întreg coincide cu una din constantele de dupa case. Secvenţa de
instrucţiuni se execută pâna se întâlneşte break, după care se trece la
instrucţiunea imediat următoare după switch. Dacă nu se găseşte nici o
coincidenţă, se execută secvenţa de instrucţiuni de după default, iar
dacă default lipseşte, deoarece prezenţa acesteia este opţională, se
trece la instrucţiunea următoare.
Exemplu: Decizia din exemplul anterior poate fi realizată şi astfel:
 # include <stdio.h>
 # include <conio.h>
 void main(void) {
     const float fact = 2.54;
     float x, in, cm;
     char ch = 0;
     printf ("nIntroduceti numarul: n");
     scanf("%f", &x);

                                  85
printf("nIntroduceti unitatea: n");
     ch = getche();
     switch(ch) {
          case 'i': in = x;
                    cm = x * fact;
          break;
          case 'c': in = x/fact;
                    cm = x;
          break;
          default: in = cm = 0;
          break; }
     printf("n%5.2f in = %5.2f cm n",in,cm);
 }
Observaţie:        Constantele case trebuie sa fie distincte.
Pentru a ieşi din instrucţiunea switch se foloseşte instrucţiunea break.
Exemplu:
 # include <stdio.h>
 void main (void) {
     int t;
     for (t = 0; t < 10; t++)
     switch (t) {
          case 1 :
          printf ("Now");
          break;
          case 2 :
          printf (" is ");
          break;
          case 3 :
          case 4 :
          printf (" the ");
          printf (" time for all good men n");
          break;
          case 5 :
          case 6 :
          printf (" to ");
          break;
          case 7 :
          case 8 :
          case 9 :
          printf (" . ");
          break; } }
Rulând acest program, vom obţine:
           Now is the time for all good men
           the time for all good men
           to to . . .
      Instrucţiunea switch este foarte eficientă în scrierea
programelor care afişează pe ecran o listă de opţiuni (un meniu) din
                                  86
care utilizatorul alege câte una şi o execută. Instrucţiunile switch pot
fi şi incluse (încuibate) una în alta.

5.5. Instrucţiuni repetitive
5.5.1. Instrucţiunea for
        Forma generală a instrucţiunii for este:
for (initializare; conditie; incrementare)
      instructiune;
unde:
           - initializare este o instrucţiune de atribuire utilizată pentru
iniţializarea variabilei de control a ciclului. Nu există nici o restricţie
privitoare la tipul său;
        - condiţie este o expresie relaţională care se testează înaintea
fiecărei iteraţii: dacă condiţia este adevărată (diferită de 0), ciclul se
continuă; dacă condiţia este falsă (egală cu 0), instrucţiunea for se
încheie;
        - incrementare se evaluează după fiecare iteraţie specificând
astfel reiniţializarea ciclului.
Exemplu: Următorul program afişează pe ecran numerele de la 1 la
100.
 # include <stdio.h>
 void main (void) {     int x;
for (x = 1; x <= 100; x++) printf("%d ", x);               }
      Nu întotdeauna ciclul for trebuie să se desfăşoare în sensul
creşterii variabilei de control. Putem crea cicluri for în care variabila
de control se decrementează.
Exemplu: Programul următor afişează numerele de la 100 la 1.
 # include <stdio.h>
 void main (void) {
     int x;
     for (x =100; x > 0; x--) printf("%d", x);                 }
      Nu există restricţii în incrementarea sau decrementarea
variabilei de control a ciclului.
Exemplu: Următorul program afişează pe ecran numerele de la 0 la
100 din 5 în 5:
 # include <stdio.h>
 void main (void) {
  int x;
  for (x = 0; x <= 100; x = x + 5) printf ("%d", x); }


                                    87
Instructiunea instrucţiune din declaraţia ciclului for poate fi o
instrucţiune simplă sau un bloc (un grup de instrucţiuni delimitate de
acolade) care va fi executat repetitiv.
Exemplu: Programul următor afişează pe ecran numerele de la 0 la
99, precum şi pătratul acestora:
 # include <stdio.h>
 void main (void) {
   int i;
   for (i = 0; i < 100; i++) {
   printf (" Acesta este i : %3d", i);
   printf (" si i patrat : %5d n", i*i); } }
Exemplu: Calculul factorialului unui număr: n! = 123...n
 # include <stdio.h>
 void main (void)
 { int n, i;
   long int factorial;
   printf ("Introduceti n : ");
   scanf ("%d", &n);
   factorial = 1;
   for (i = 1; i <= n; i++)
      factorial *= i;
   printf (" %d ! = %ld n", n, factorial);              }
      Instrucţiunile for pot fi incluse una în alta.
Exemplu: Programul următor parcurge un şir de caractere de la stânga
la dreapta, afişând subşirurile ce au ca bază primul caracter.
 # include <stdio.h>
 # include <string.h>
 void main (void)
 {   int l, n, i;
     char sir[81];
     puts ("Tastati un sir terminat cu <CR> : ");
     gets (sir);
     l = strlen (sir);
     for (n = 0; n <= l; ++n) {
          for (i = 0; i < n; ++i)
               putchar (sir[i]);
          putchar ('n'); } }

       Variante ale ciclului for: Limbajul C permite mai multe
variante ale ciclului for care determină creşterea flexibilităţii acestuia
în diferite situaţii. Una din cele mai utilizate variante constă în
folosirea a mai multe variabile de control a ciclului. În exemplul
următor, atât x cât şi y sunt variabile de control a ciclului:


                                   88
# include <stdio.h>
 void main (void)
 {   int x, y;
     for (x = 0, y = 0; x + y < l00; x++, y++)
          printf ("%d ", x + y);
 }
Acest program tipăreşte numerele de la 0 la 98 din 2 în 2. Se observă
că iniţializările şi incrementările celor două variabile sunt separate
prin virgulă.
       Terminarea ciclului presupune testarea nu numai a variabilei de
control cu anumite valori prestabilite, ci condiţia de terminare a
ciclului poate fi orice expresie C corectă.
Exemplu: Considerăm un program pentru antrenarea unui copil în
exerciţiile de adunare. Dacă copilul vrea să se oprească se apesa tasta
T, atunci când calculatorul îl întreabă dacă să continue.

 # include <stdio.h>
 # include <conio.h>
 void main (void)
 { int i, j, raspuns;
   char terminare = ' ';
   for (i=1; i<100; i++) {
     for (j=1; j<100 && terminare !='t';j++) {
          printf ("nCit este %d + %d ? ",i,j);
          scanf("%d",&raspuns);
          if (raspuns != i+j) printf("nGresit !");
          else printf("nCorect !");
          printf(" Continuam ? ");
terminare = getchar(); }
/* Pentru terminare se apasa tasta t */ } }
       O altă caracteristică interesantă a ciclului for este aceea că nu se
impune definirea tuturor celor trei parametri ai ciclului for, oricare
dintre ei putând fi opţionali. De exemplu, ciclul următor se va executa
până când de la tastatura se introduce numărul 123:
      for (x = 0; x != 123;) scanf("%d", &x);
Deoarece instrucţiunea de incrementare a lui x lipseşte, de fiecare dată
când ciclul se repetă, programul testează ca x să fie egal cu 123, dar
nu modifică pe x în nici un fel. Dacă de la tastatură se introduce 123,
condiţia buclei devine falsă şi ciclul se termină.
Exemplu: O variantă de calcul a lui n! ar fi următoarea:
 # include <stdio.h>
 void main (void)
 {   int n, i;
     long int factorial;
                                    89
printf ("Introduceti n : ");
     scanf ("%d", &n);
     factorial = 1;
     for (i = 1; i <= n;) {
          factorial *= i ++;
          printf (" %d ! = %ld n", n, factorial);               } }
      Bucle infinite: Una din cele mai interesante utilizări ale ciclului
for constă în crearea de bucle infinite. Dacă nici una din cele trei
expresii care formează ciclul for nu sunt precizate, se obţine o buclă
fără sfârşit, ca în exemplul următor în care se consideră că elementul
condiţie are valoarea adevărat:
     for (;;)
      printf ("Aceasta bucla va rula la nesfirsit. n ");

      Ieşirea dintr-o bucla for:  Pentru terminarea unei bucle for,
chiar şi a buclei for(; ;) se foloseşte instrucţiunea break care se
plasează oriunde în corpul ciclului şi determină încheierea imediată a
ciclului (la întâlnirea acesteia), programul continuându-se cu
instrucţiunea ce urmează după instrucţiunea for.

Exemplu: Acest program va rula până când de la tastatura se apasă
tasta A:
     # include <stdio.h>
     # include <ctype.h>
     void main (void)
     {    for (; ;) { ch = getche ();
               if (ch == 'a') break; }
          printf (" Ai apasat tasta A "); }

      Utilizarea ciclurilor for fără corp (instrucţiune) : Pentru
crearea unor întârzieri de timp se pot folosi cicluri for cu corp vid de
forma:
     for (t = 0; t < O_ANUMITA_VALOARE; t++);
Observaţie: De obicei, instrucţiunea for este legată de parcurgerea
unor structuri de date de tip tablou.
5.5.2. Instrucţiunea while
     Forma generală a instrucţiunii repetitive while este:

         while (conditie)
             instructiune;
unde instructiune poate fi o instrucţiune vidă, o instrucţiune simplă
sau un bloc de instrucţiuni ce vor fi executate repetitiv. În timpul
                                   90
execuţiei se evaluează mai întâi condiţia buclei a cărei valoare trebuie
să fie întreagă. Dacă valoarea calculată este diferită de 0 (condiţie
adevărată), atunci instructiune se execută. Dacă, după o evaluare
(inclusiv prima) rezultă o valoare 0 (condiţie falsă), atunci controlul
este transferat la instrucţiunea ce urmează după while. Astfel,
instrucţiunea asociată cu while se execută repetat, cât timp valoarea
asociată condiţiei este diferită de 0 sau condiţia este adevărată.
Exemplu: Programul următor calculează c.m.m.d.c. pentru o pereche
x, y de numere întregi pozitive.
 # include <stdio.h>
 void main (void) {
   int    xi, yi, x, y;
   printf (" Introduceti doua numere pozitive: n");
   scanf ("%d %d", &xi, &yi);
   x = xi; y = yi;
   while (x != y)
 if (x > y) x -= y;
      else   y -= x;
   printf (" C.m.m.d.c. (%d, %d) = %d", xi, yi, x); }
       Metoda de calcul se bazează pe faptul că:
♦ daca x > y, atunci cmmdc (x, y) = cmmdc (x-y, x);
♦ daca x < y, atunci cmmdc (x, y) = cmmdc (x, y-x);
♦ daca x = y, atunci cmmdc (x, y) = x =y .
De exemplu, cmmdc (14, 21) = 7.
       Deoarece instrucţiunea while realizează testarea condiţiei la
începutul instrucţiunii, aceasta instrucţiune este bună de utilizat în
situaţiile în care nu se doreşte execuţia buclei, evident dacă condiţia
nu este adevărată.
Exemplu: Programul următor realizează centrarea unui text pe ecran:
 # include <stdio.h>
 # include <ctype.h>
 void main (void) {
     char sir[255];
printf(" Introduceti un sir de caractere: n");
     gets (sir);
     centreaza (strlen (sir));
     printf (sir); }
   /* Se calculează numărul de spaţii pentru centrarea
unui şir de
      caractere cu lungimea lung */
 centreaza (lung)
     int lung;
 {   lung = (80 - lung)/2;

                                  91
while (lung > 0) {
          printf (" "); lung--;          } }
      Dacă dorim să programăm un ciclu infinit, atunci se poate găsi
o expresie care ramâne tot timpul adevărată. Un exemplu uzual este
următorul:
                 while (1) { Corpul ciclului }
Ieşirea din ciclu, în acest caz, se asigură prin mecanisme de tip break,
goto sau return.
     Corpul ciclului while poate conţine şi numai instrucţiunea vidă.
De exemplu,
                while ((ch = getche ()) != 'A');
este o buclă simplă care se execută până când de la tastatură se va
introduce caracterul "A".
Observaţie: Instrucţiunea while reprezintă mecanismul sintactic de
bază pentru a programa cicluri în C.
      Reamintim că instrucţiunea for se foloseşte după următorul
format general:
         for (initializare; conditie; incrementare) instructiune;
care este echivalentă semantic cu secvenţa:
            initializare;
            while (conditie) {
                   instructiune;
                   incrementare; }
5.5.3. Instrucţiunea do-while
      Spre deosebire de ciclurile programate cu while sau for, unde
condiţia de ciclare este verificată la început, în cazul folosisii
mecanismului do-while, condiţia se evaluează după execuţia secvenţei
de instrucţiuni ce reprezintă corpul ciclului. Forma generală a buclei
do-while este:
                 do {     instructiune;
                 } while (conditie);
Semantic, do-while este echivalentă cu secvenţa:
                 instructiune;
                 while (conditie)
                      instructiune;
         Deşi acoladele nu sunt necesare când instructiune este o
instrucţiune simplă, de obicei se utilizează pentru a evita confuzia cu
while. Se remarcă faptul că instructiune ce reprezintă corpul ciclului
(adică, o instrucţiune simplă, o instrucţiune compusă sau o
instrucţiune vidă) este executată cel puţin odată. Celelalte execuţii
sunt condiţionate de valoarea întreagă rezultată din evaluarea
                                  92
condiţiei. Dacă această valoare este 0 (condiţie falsă), atunci controlul
se transferă la următoarea instrucţiune din program; în caz contrar se
execută corpul ciclului şi se reevaluează condiţia.
Exemplu: Următoarea secvenţă asigură preluarea corectă a unei valori
întregi între 1 şi 10:
 # include <stdio.h>
 void main (void) {
   int num;
   do {
   printf("nnIntrod. un intreg între 1 si 10: ");
   scanf ("%d", &num);
   printf (" Numarul introdus este : %d ", num);
   } while (num < 1 || num > 10);   }
      Un caz tipic de utilizare a instrucţiunii do-while este oferit de
programele interactive în care selecţia unei opţiuni se face pe baza
unui meniu afişat pe ecranul terminalului.
Exemplu: Următorul program implementează o versiune a unui meniu
de verificare a corectitudinii ortografice într-un text:
 # include <stdio.h>
 # include <ctype.h>
 void main (void) {
 char     ch;
 printf ("1. Verificarea ortografiei n ");
 printf ("2. Corectarea erorilor de ortografie n");
 printf ("3. Afisarea erorilor de ortografie n ");
do {
printf ("n Introduceti optiunea dumneavoastra: ");
ch=getche(); // Se citeste optiunea de la tastatura
switch (ch) {
     case '1':
          verifica_ortografia();
          break;
     case '2':
          corecteaza_erorile();
          break;
     case '3':
          afiseaza_erorile();
          break;   }
  } while (ch != '1' && ch != '2' && ch != '3'); }
       După afişarea opţiunilor, programul va bucla până când se va
selecta o opţiune validă.
Exemplu: Adunarea elementelor a doi vectori:
           int a[10], b[10], c[10];
           . . . . . . . . . . . . . .
           i = 0;
                                   93
do { c[i] = a[i] + b[i];
                 i = i + 1;
            } while (i < 10);
sau
            i = 0;
            do { c[i] = a[i] + b[i]; i++;
            } while (i < 10);

5.5.4. Bucle încuibate
      Când o buclă este introdusă în altă buclă, bucla interioară se
spune a fi inclusă (nested, încuibată) în bucla exterioară.
Exemplu: Programul următor afişează primele 4 puteri ale numerelor
cuprinse între 1 şi 9:
  # include <stdio.h>
  void main (void)
  { int i, j, k, p;
     printf (" i    i^2 i^3 i^4 n ");
     for (i = 1; i < 10; i++) {
          for (j = 1; i < 5; j++) {
               p = 1;
               for (k = 1; i < j; k++)
                    p = p * i;
               printf (" %9d ", p); }
          printf (" n "); }    }
Când se execută acest program se obţin următoarele rezultate:
            i    i^2  i^3     i^4
            1     1     1      1
            2     4     8      16
            3     9   27       81
            . . . . . . . . . . .
            9    81    729     6561
Alinierea rezultatelor se datoreşte utilizării în printf() a unui format de
afişare corespunzător (%9d) care precizează dimensiunea minimă a
câmpului specificat.
       Un alt exemplu, puţin mai complex, este un program de
înmulţire a două matrice. Evident, în acest caz vom avea 3 bucle for
incluse una în cealaltă.
 // Program de inmultire a doua matrici
 # include <stdio.h>
 float a[100][100],b[100][100],c[100][100];
 float elem, s;
 int la, ca, lb, cb, lc, cc, i, j, k;
 void main(void) {
  la=101; ca=101;
  lb=ca+1; cb=ca;

                                    94
printf("Program de inmultire a doua matricinSe declara
dimensiunile fiecarei matricinn");
/* 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!nn");
   while ((la>=100)||(ca>=100)) {
   printf("Intoduceti dimensiunile primei matrice");
   printf("nNr. linii matrice A = n");
   scanf("%d",&la);
   printf("Nr. coloane matrice A = n");
   scanf("%d",&ca);     }
   while ((lb>=101)||(cb>=101)) {
printf("Intoduceti dimens. celei de-a doua matrice");
   printf("nNr. linii matrice B = n");
   scanf("%d",&lb);
   printf("Nr. coloane matrice B = n");
   scanf("%d",&cb);     }
  if(ca!=lb) {
  la=101;ca=101;
  lb=ca+1;cb=ca;}    }
 /* Se introduc matricile */
 for(i=0; i<=la-1; i++)
   for(j=0; j<=ca-1; j++) {
printf("a(%d,%d) = ", i, j);
scanf("%f",&elem);
a[i][j] = elem; }
 for(i=0;i<=lb-1;i++)
   for(j=0;j<=cb-1;j++) {
printf("b(%d,%d) = ",i,j);
scanf("%f",&elem);
b[i][j]=elem; }
 // Se calculeaza fiecare element al matricei produs
 for(i=0;i<=la-1;i++)
   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("nnA = n");
 for(i=0;i<=la-1;i++)
 { printf("n");
   for(j=0;j<=ca-1;j++)
     printf("%6.3f ",a[i][j]); }
 printf("nnB = n");

                           95
for(i=0;i<=lb-1;i++)
 { printf("n");
   for(j=0;j<=cb-1;j++)
printf("%6.3f ",b[i][j]); }
 printf("nnC = A*Bn");
 for(i=0;i<=la-1;i++)
 { printf("n");
   for(j=0;j<=cb-1;j++)
printf("%6.3f ",c[i][j]); }}

5.5.5. Instrucţiunea break
      Instrucţiunea break are două utilizări. Prima utilizare constă în
terminarea unui case în cadrul instrucţiunii switch. A doua utilizare
constă în terminarea imediată a unui ciclu scurtcircuitând testul
condiţional normal al buclei. Dacă într-o buclă se întâlneşte o
instrucţiune break, calculatorul termină (părăseşte) imediat bucla şi
controlul programului se transferă la instrucţiunea ce urmează
instrucţiunii de buclare. De exemplu, programul:
  # include <stdio.h>
  void main (void)
  { int t;
     for (t = 0; t < 100; t++) {
          printf (" %3d ", t);
          if (t == 10) break; }}
tipăreşte numerele până la 10 şi atunci se opreşte deoarece break
determină ieşirea imediată din ciclu.
      În cazul buclelor incluse, este important de notat ca break
determină ieşirea imediată numai din bucla interioară (din bucla în
care este introdus). De exemplu, programul:
     # include <stdio.h>
     void      main (void)
     {    int t;
          for (t = 0; t < 100; ++t) {
               count = 1;
               for (;;) {
                    printf (" %d ", count);
                    count++;
                    if (count == 10) break; } }
va afişa pe ecran numerele de la 1 la 10 de 100 de ori.
      Instrucţiunea break se poate utiliza şi în cadrul ciclurilor
programate cu while sau do-while, schema generală de utilizare fiind
următoarea:
                  while (expresie) {
                       .................

                                  96
if (conditie) break;
                       .................
                   }
      Dacă la una din iteraţii, condiţia din if este îndeplinită, atunci
ciclul se termină automat, altfel el poate continua până când expresia
din while are valoarea fals.
      Dacă instrucţiunea break se execută în cadrul unei instrucţiuni
switch, care la rândul ei este inclusă într-un ciclu programat cu while,
for sau do-while, atunci ea determină terminarea numai a instrucţiunii
switch, nu şi ieşirea din ciclu.
5.5.6. Instrucţiunea continue
      Instrucţiunea continue, executată într-un ciclu, determină
oprirea iteraţiei curente şi asigură trecerea imediată la iteraţia
următoare. De exemplu, programul următor va afişa pe ecran numai
numerele pare.
     # include <stdio.h>
     void main (void)
     {    int x;
          for (x = 0; t < 100; x++) {
               if (x % 2) continue;
               printf (" %d ", x);    } }
      Se observă că atunci când se generează un număr impar se
execută instrucţiunea continue ce va determina trecerea la iteraţia
următoare by-pasând instrucţiunea printf().
      În cazul instrucţiunilor while şi do-while, o instrucţiune
continue determină trecerea direct la testul condiţional şi prin urmare,
continuarea procesului de buclare. În cazul unui for, se realizează mai
întâi operaţia de incrementare a variabilei de control a ciclului, apoi
testarea condiţiei de continuare a buclei.
                         Capitolul VI

      TIPURI DE DATE STRUCTURATE

        În C există două categorii de tipuri de date structurate:
tablourile şi structurile. Un tablou este o colecţie omogenă de valori
de acelaşi tip identificate printr-un indice, iar o structură este o
colecţie neomogenă de valori identificate prin nume simbolice,
denumite selectori.

                                  97
6.1. Tablouri unidimensionale
         Un tablou este o colecţie de variabile de acelaşi tip care sunt
referite printr-un nume comun. În C, un tablou constă din locaţii de
memorie contigue. Adresa cea mai mică corespunde primului element,
iar adresa cea mai mare corespunde ultimului element. Un tablou
poate avea de la una la mai multe dimensiuni. Accesul la un element
specific al tabloului se face utilizând un index. Cel mai utilizat tablou
este tabloul de caractere. Şirurile de caractere pot fi definite prin
conceptele: vector de caractere şi pointer-caracter.
         Declararea unui tablou cu o singură dimensiune are
următoarea formă generală:
                          tip var_nume[size];
Aici, tip, declară tipul de bază al tabloului. Tipul de bază determină
tipul de dată al fiecărui element al tabloului. var_nume este numele
tabloului, iar size este numărul elementelor pe care le va conţine
tabloul. Exemple:
  int a[10];            // vectorul a contine 10 intregi
  float v[3];           // vectorul v contine 3 reali
     În C toate tablourile folosesc pe zero ca index al primului lor
element. Elementele tabloului a[10] sunt a[0],...,a[9].
Exemplu: Programul următor încarcă un tablou de întregi cu numerele
de la 0 la 9:
  void     main (void) {
  int      x[10];    // se rezerva 10 elemente intregi
  int      t;
  for (t   = 0; t < 10; t++) x[t] = t;   }
      Pentru un tablou unidimensional, dimensiunea totală, în bytes, a
acestuia va fi:
            Total bytes = sizeof (tip) * lungimea_tabloului
Observaţie: Limbajul C nu realizează verificarea dimensiunilor unui
tablou: astfel, nu există nimic care să ne oprească să nu trecem peste
sfârşitul tabloului. Dacă se trece peste sfârşitul unui tablou într-o
operaţie de atribuire, atunci se vor atribui valori unor alte variabile sau
chiar se vor distruge părţi din program.
Exemplu: Deşi următorul program este incorect, compilatorul C nu
semnalează nici o eroare:
  void      main (void) {

                                    98
int crash[10], i;
      for (i = 0; i < 100; i++)           crash[i] = i;        }
Se observă că bucla se iterează de 100 de ori, deşi vectorul crash
conţine numai 10 elemente. Aceste verificări rămân în sarcina
exclusivă a programatorului.
      Tablourile unidimensionale sunt, de fapt, liste de informaţii de
acelaşi tip. De exemplu, prin rularea programului:
  char ch[7];
  void    main (void)
  { int i;
     for (i = 0; i < 7; i++) ch[i] = 'A' + i; }
vectorul “ch“ arată astfel:
           ch(0) ch(1) ch(2) ch(3)       ch(4)   ch(5) ch(6)
           A       B     C     D           E       F     G

6.1.1. Constante şir
      În C o constantă şir este o secvenţă de caractere închisă între
ghilimele. Exemplu: "acesta este un sir". Fiecare constantă şir conţine
cu un caracter mai mult decât numărul de caractere din şir, deoarece
aceasta se termină totdeauna cu caracterul NULL '0' care are valoarea
0. De exemplu, sizeof ("asaf") = 5.
      Tipul unui şir este "vector de un număr de caractere"; astfel
"asaf" are tipul char[5]. §irul vid este descris prin " " şi are tipul
char[1]. De notat că, pentru fiecare şir s, funcţia strlen(s) din fişierul
antet "string.h" întoarce numărul caracterelor din şir fără terminatorul
0, adică: strlen(s) = sizeof(s) - 1.
      În interiorul unui şir se poate folosi convenţia de notaţie cu .
Aceasta face posibilă reprezentarea caracterelor " şi  în interiorul unui
ş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


                                   99
caracterul null într-un şir, dar majoritatea programelor nu testează
dacă mai sunt caractere după el.
6.1.2. Iniţializarea vectorilor de caractere
• Citirea unui şir de la tastatură utilizând funcţiile scanf() şi gets().
 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 ("%sn%sn", nume, adresa); }
S-au definit variabilele nume şi adresa ca tip şir de caractere de
maximum 20 şi 40 de caractere. Şirul "%s" care apare în apelul
funcţiei scanf() precizează că se vor citi în variabilele nume, respectiv
adresa, valori de tip şir de caractere. În printf() descriptorul "%s" are
rolul de a preciza cum trebuie convertite valorile datelor de afişat (în
cazul de faţă, valorile variabilelor nume şi adresa).
       Funcţia scanf() citeşte un şir de caractere din bufferul de intrare
până când întâlneşte un spaţiu, un caracter TAB, sau ajunge la sfârşitul
acestuia. Astfel, dacă se tastează, "ENE ALEXANDRU", atunci în
variabila nume se va memora doar valoarea "ENE". Pentru a obţine
şirul în întregime este recomandat să se transmită numele sub forma:
"ENE_ALEXANDRU".
 Cea mai bună cale de a introduce un şir de la tastatură constă în
utilizarea funcţiei gets() din fişierul antet "stdio.h". Forma generală a
funcţiei gets() este:
                   gets (nume_vector)
       Pentru a citi un şir se apelează gets() având ca argument numele
vectorului, fără nici un index. Funcţia gets() returnează vectorul ce va
păstra şirul de caractere introdus de la tastatură. gets() va continua să
citească caractere până la introducerea caracterului CR.
Exemplu: Programul următor afişează şirul de caractere introdus de la
tastatură.
  # include <stdio.h>
 void main    (void) {
   char sir[80];
   gets (sir); /* citeste un sir de la tastatura */
   printf ("%s", sir); }

                                   100
Se observă că funcţia printf() poate primi ca argument un şir de
caractere. Dacă se introduce un şir mai lung decât dimensiunea
tabloului, vectorul va fi suprascris.
• Iniţializarea vectorilor de caractere utilizând constantele şir
     Folosind constantele şir, vectorii de caractere se iniţializează sub
forma:
            char nume_vector[size] = "sir_de_caractere"
unde size = numărul caracterelor din şir plus 1. Exemplu:
  # include <stdio.h>
  void main   (void) {
char nume[14] = "ENE ALEXANDRU";
char adresa[24] = "Str. A. I. Cuza, nr.13";
puts (nume);
puts (adresa); }
      Vectorul nume va ocupa începând de la adresa nume, 13 + 1 =
14 octeţi, iar cel de-al doilea vector va ocupa începând de la adresa
adresa, 23 + 1 = 24 locaţii (bytes).
     Funcţia puts() scrie pe stdout şirul memorat în vectorul al cărui
nume apare ca parametru al funcţiei puts(), precum şi caracterul "n".
      De multe ori, în C se realizează iniţializarea unor vectori de
caractere a căror dimensiune nu este precizată. Dacă dimensiunea
vectorului nu este precizată, compilatorul C va crea un vector suficient
de lung încât să permită iniţializarea dorită.
Exemplu: În loc să scriem :
             char e1[12] = "read errorn";
             char e2[13] = "write errorn";
             char e3[18] = "cannot open filen";
putem scrie:
             char e1[ ] = "read errorn";
             char e2[ ] = "write errorn";
             char e3[ ] = "cannot open filen";
Cu această ultimă iniţializare, instrucţiunea
       printf ("%s are lungimea %dn", e2, sizeof (e2));
va tipari:
      write error
      are lungimea 13
 Iniţializarea unui vector (tablou unidimensional) se poate face şi cu
o listă de iniţializatori scrişi între acolade. Dacă vectorul are o
lungime necunoscută, numărul de iniţializatori determină mărimea
tabloului, iar tipul devine complet. Dacă tabloul are lungime fixă,
numărul de iniţializatori nu poate depăşi numărul de membri din
                                  101
tablou. În cazul în care sunt mai puţini iniţializatori, membrii în plus
sunt iniţializaţi cu zero. Exemple:
       - Instrucţiunea următoare iniţializează un vector de 10 întregi cu
numerele de la 1 la 10:
      int i[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
Rezultă că: i[0] = 1, ... , i[9] = 10.
      - Instrucţiunea următoare declară şi iniţializează vectorul x ca
un tablou unidimensional cu 3 membri:
                       int   x[] = {1, 2, 3};
      - Instrucţiunea următoare:
      char sir[6] = { 'h', 'e', 'l', 'l', 'o', '0' };
este echivalentă cu:
            char sir[6] = "hello";

6.1.3. Funcţii pentru prelucrarea şirurilor (fişierul antet
string.h)
•   Funcţia strcpy()
     Apelul funcţiei strcpy() are următoarea formă generală:
                    strcpy (nume_sir, constanta_sir);
Funcţia strcpy() copiază conţinutul constantei_sir (inclusiv caracterul
terminator 'n') în nume_sir.
Exemplu:
              # include <string.h>
               void main(void) {
               char sir[80];
               strcpy (sir, "hello");
               printf("%s", sir);   }
      Acest program va copia "hello" în şirul sir.
•    Funcţia strcat()
      Apelul funcţiei strcat() are forma:
                             strcat (s1, s2);
Funcţia strcat() concatenează şirul s2 la sfârşitul şirului s1 şi întoarce
şirul s1. Şirul s2 nu se modifică. Ambele şiruri trebuie să aibă
caracterul terminator NULL, iar rezultatul va avea de asemenea
caracterul terminator NULL.
Exemplu:
      # include <stdio.h>
      # include <string.h>
           void main(void) {
              char first[20], second[10];
              strcpy (first, "hello");

                                   102
strcpy (second, "there");
               strcat (first, second);
               printf ("%s", first); }
Acest program va afişa "hellothere" pe ecran.
•   Funcţia strcmp()
      Se apelează sub forma:
                            strcmp (s1, s2);
Această funcţie compară şirurile s1 şi s2 şi returnează valori negative,
dacă s1 < s2, 0, dacă s1 = s2 şi un număr pozitiv, dacă s1 > s2.
Exemplu: Această funcţie poate fi folosită ca o subrutină de verificare
a parolei:

     # include <stdio.h>
     # include <string.h>
     void main (void) {
        char s[80];
        printf ("Introduceti parola: ");
        gets (s);
        if (strcmp (s, "pasword")) {
             printf (" Invalid pasword n ");
             return 0;}
        return 1; }

•    Funcţia strlen()
       Funcţia strlen() se apelează sub forma:
                                 strlen (s)
unde s este un şir. Funcţia strlen() returnează lungimea şirului s.
Exemplu: Programul următor returnează lungimea unui şir introdus de
la tastatură.
  # incude <stdio.h>
  # incude <string.h>
  void main (void) {
     char sir[80];
     printf ("Introduceti un sir: ");
     gets (sir);
     printf ("Sirul %s contine %d               caractere     ",   sir,
strlen(sir));    }
Observaţie: Funcţia strlen() nu numără şi caracterul NULL.
Exemplu: Programul următor afişează inversul unui şir de caractere
introduse de la tastatură.
    # include <stdio.h>
    # include <string.h>
     void main (void) {
     char sir[80];

                                 103
int i;
  gets(sir);
  for(i=strlen(sir)-1;i>=0;i--) printf("%c",sir[i]);              }
Exemplu: Programul următor realizează introducerea unor şiruri,
compararea lor, concatenarea lor şi afişarea rezultatului.
 # include <stdio.h>
 # include <string.h>
 void main (void) {
  char s1[80], s2[80];
  gets(s1); gets(s2);
  printf("Lungimi: %d %d n",strlen(s1),strlen(s2));
  if (!strcmp (s1, s2))
     printf ("Sirurile sunt egalen");
  strcat (s1, s2);
  printf ("%sn", s1);   }
Dacă se rulează acest program şi se introduc şirurile s1 =
"AUTOMATICA" şi s2 = "AUTOMATICA", se va afişa:
           Lungimi 10 10
           Sirurile sunt egale
           AUTOMATICAAUTOMATICA

      Dacă şirurile sunt egale, funcţia strcmp() returnează fals (0) şi
din această cauză în instrucţiunea if s-a folosit !strcmp().
Observaţie: Caracterul NULL de terminare a vectorului de caractere
poate fi utilizat în buclele for ca în exemplul următor, unde se
converteşte un şir de caractere scris cu litere mici la litere mari.
 # include <stdio.h>
 # include <string.h>
 void main (void) {
  char sir[80];
  int i;
  strcpy (sir, "acesta este un test");
  for(i = 0; sir[i]; i++) sir[i] = toupper (sir[i]);
  printf("%s", sir); }
       Conversia caracterelor se face cu funcţia toupper() care
returnează litera mare corespunzătoare argumentului (literei mici).
Ciclul funcţionează până când sir[i] devine caracterul null.

6.2. Tablouri cu două dimensiuni (matrice)
     Tablourile bidimensionale (matricele) sunt reprezentate ca
vectori de vectori. De exemplu, declaraţia:
                            int v[2][5];
                                 104
declară un vector cu 2 elemente, fiecare element fiind un vector de tip
int[5]. Se observă că fiecare dimensiune a tabloului este separată
(închisă) între paranteze, iar dimensiunile nu sunt separate prin
virgulă. Astfel, declaraţia: int v[2, 5]; conduce la eroare.
6.2.1. Iniţializarea matricelor
Declaraţia :
char v[2][5] = { 'a', 'b', 'c', 'd', 'e',
                '0', '1', '2', '3', '4' };
conduce la iniţializarea primului vector cu primele 5 litere, iar a celui
de-al doilea cu primele 5 cifre.
Exemplu: Programul:
  # include <stdio.h>
  void main (void) {
  char v[2][5] = { 'a', 'b', 'c', 'd', 'e',
                   '0', '1', '2', '3', '4' };
  int i, j;
  for (i = 0; i < 2; i++){
     for(j = 0; j < 5; j++)
        printf ("v[%d][%d] = %c", i, j, v[i][j]);
     printf ("n"); }    }
va produce :
v[0][0]=a v[0][1]=b v[0][2]=c v[0][3]=d v[0][4]=e
v[1][0]=0 v[1][1]=1 v[1][2]=2 v[1][3]=3 v[1][4]=4.
Exemplu: Secvenţa de instrucţiuni:
  # include <stdio.h>
  void main (void) {
     int l, c, num[3][4];
for (l = 0; l < 3; ++l)
  for (c = 0; c < 4; ++c)
          num[l][c] = (l * 4) + c + 1;              }
conduce la încărcarea tabloului num[3][4]cu numerele de la 1 la 12.
Astfel, num[0][0] = 1, ..., num[2][3] = 12.
      Se observă că limbajul C memoreză tablourile bidimensionale
într-o matrice linii-coloane, unde primul indice se referă la linie şi al
doilea indice se referă la coloană. Cantitatea de memorie alocată
permanent pentru un tablou, exprimată în bytes, este:
               nr_linii * nr_coloane * sizeof(tipul_datei)
Declaraţia: float y[4][3] = { {1,3,5},
                                    {2,4,6},
                                    {3,5,7},};
este o iniţializare cu paranteze complete şi are următorul efect:

                                  105
- numerele 1, 3, 5 iniţializează prima linie a tabloului: y[0][0],
y[0][1], y[0][2] sau y[0];
       - numerele 2, 4, 6 iniţializează pe y[1];
       - numerele 3, 5, 7 iniţializează pe y[2].
Întrucât iniţializatorul se termină cu virgulă, elementele lui y[3] vor fi
iniţializate cu 0. Acelaşi efect ar fi putut fi realizat de:
      float y[4][3]={1, 3, 5, 2, 4, 6, 3, 5, 7, };
Secvenţa:
      float y[4][3] = { {1}, {2}, {3}, {0}, };
iniţializează prima coloană a lui y, privit ca un tablou bidimensional,
cu 1, 2, 3 şi 0, restul tabloului fiind iniţializat cu 0.
6.2.2. Tablouri bidimensionale de şiruri
      Pentru crearea unui tablou de şiruri se foloseşte un tablou de
caractere, bidimensional, în care mărimea indicelui din stânga
determină numărul de şiruri, iar indicele din drepta specifică lungimea
maximă a fiecărui şir. De exemplu, declaraţia :
            char sir_tablou[30][80];
defineşte un tablou de 30 de şiruri, fiecare şir având maximum 80 de
caractere. Accesul la un singur şir este foarte uşor: se specifică numai
primul indice. De exemplu:
            gets (sir_tablou[2])
întoarce al treilea şir din tabloul sir_tablou. Funcţional, instrucţiunea
anterioară este echivalentă cu:
            gets (&sir_tablou[2][0]);

6.3. Tablouri multidimensionale
      Forma generală a declaraţiei unui tablou multidimensional este:
               tip   nume[size1][size2]...[sizeN];
De exemplu, declaraţia:
            int   trei[4][10][3];
creează un tablou de 4*10*3 întregi.
     Forma generală de iniţializare a tablourilor este următoarea:
specificator_tip nume_tablou[size1][size2]...[sizeN]={lista_valori};
unde lista_valori este o listă de constante separate prin virgulă,
compatibile cu tipul de bază al tabloului.
Observaţie: Limbajul C permite şi iniţializarea tablourilor
multidimensionale fără dimensiune. Trebuie menţionat că pentru
                                  106
aceasta este necesară precizarea indicelui celui mai din dreapta. Astfel,
declaraţia:
    int sqrs[5][2] = {1, 1, 2, 4, 3, 9, 4, 16, 5, 25};
este echivalentă cu declaraţia:
    int sqrs[ ][2] = {1, 1, 2, 4, 3, 9, 4, 16, 5, 25};


6.4. Structuri
       O structură este o colecţie de variabile (de tipuri diferite)
referite sub un singur nume. Definiţia unei structuri formează un aşa
numit şablon (template, tag) ce poate fi folosit la crearea variabilelor
tip structură. Variabilele care formează structuri se numesc elementele
structurii.
       De exemplu, fragmentul următor defineşte un şablon pentru o
structură numită addr care defineşte la rândul său numele şi adresa
unei persoane necesare în cazul transmiterii unei scrisori:
      struct addr {                      struct {
      char name[30];                     char *name;
      char street[40];                   char *street;
      char city[20];                     char *city;
      char state[3];                     char *state;
      unsigned int zip;                  unsigned int zip;
      };                                     } addr;
      Pentru definirea unui şablon al unei structuri se foloseşte
cuvântul cheie struct. Terminarea definiţiei se face cu ”;” (este unul
din puţinele cazuri de utilizare a caracterului punct şi virgulă ”;” după
acoladă).
      Precizăm că numele addr identifică structura particulară definită
anterior (şablonul) şi este specificatorul său de tip. Programul anterior
defineşte numai forma (tipul) datelor structurii, dar nu defineşte
variabilele structură, deci trebuie făcută distincţie dintre structura-
şablon şi variabila-structură. O variabilă de tip structură se declară cu
ajutorul şablonului structurii.
Pentru a declara o variabilă actuală cu această structură vom scrie
                      struct addr addr_info;
       Această linie declară variabila addr_info ca variabilă structură
de tip addr.
      Limbajul C alocă suficientă memorie pentru a păstra toate
variabilele ce alcătuiesc o structură. De exemplu, memoria alocată
pentru structura addr_info va fi :

                                  107
Name              30 bytes
                  Street            40 bytes
                  City              20 bytes
                  State             3 bytes
                  Zip               4 bytes
      Când se defineşte o structură şablon, se pot declara una sau mai
multe variabile-structuri, astfel :
                  struct addr {
                  char name[30];
                  char street[40];
                  char city[20];
                  char state[3];
                  unsigned int zip;
                  } addr_info, binfo, cinfo;
      Secvenţa anterioară defineşte o structură şablon numită addr şi
declară variabilele addr_info, binfo, cinfo de acelaşi tip.
      Pentru declararea unei singure structuri numite addr_info, nu
mai este necesară includerea numelui addr al structurii, astfel:
           struct {
           char name[30];
           char street[40];
           char city[20];
           char state[3];
           unsigned int zip;
           } addr_info;
În cazul de mai sus se defineşte variabila-structură addr_info cu
şablonul definit, dar fără nume
     Forma generală de definire a unei structuri este :
           struc nume_tip_structura {
           tip nume_variabile;
           tip nume_variabile;
           tip nume_variabile;
           . . . . . . . . . . . . . .
           } variabile_structura;

unde pot fi omise fie numele tipului structurii nume_tip_struct, fie
variabile_structura, dar nu ambele. După cum se observă,
nume_tip_structura după cuvântul cheie struct se referă la şablonul
structurii (tipul său) iar variabile_structura se referă la lista de
variabile de acest tip (cu această structură)


                                108
Referirea individuală a elementelor unei structuri se face cu
operatorul punct ".". De exemplu, instrucţiunea următoare va atribui
elementului zip al variabilei structură addr_info valoarea 12345:
                  addr_info.zip = 12345;
Pentru accesarea elementelor unei structuri se foloseşte forma
generală :
       nume_structura.nume_element
      De exemplu, pentru afişarea variabilei zip se va scrie:
       printf ("%d", addr_info.zip);
În aceeaşi formă, se pot referi celelalte elemente ale structurii
addr_info. De exemplu apelul:
       gets (addr_info.name);
are ca efect trecerea la un pointer-caracter la primul caracter al
elementului nume. Pentru a avea acces la fiecare caracter al
elementului addr_info.name, se poate indexa name. De exemplu,
pentru afişarea conţinutului lui addr_info.name, caracter cu caracter,
se foloseşte programul:
      register int t;
      for (t = 0; addr_info.name[t]; ++t)
           putchar (addr_info.name[t]);

6.4.1. Tablouri de structuri
      Cel mai uzual mod de folosire a structurilor este în tablouri de
structuri. Pentru declararea unui tablou de structuri, mai întâi se
defineşte o structură şi apoi se declară un tablou de variabile de acel
tip. De exemplu, pentru declararea unui tablou cu 100 de structuri
addr definite anterior, se va scrie:
                  struct addr addr_info[100];
      Pentru a avea acces la o structură oarecare din cele 100 se va
indexa numele structurii (în cazul acesta addr_info).
      De exemplu:
                printf ("%d", addr_info[2].zip);
are ca efect afişarea codului zip din a treia structură.
       Se observă că, la fel orice variabilă tablou, tablourile de structuri
încep cu indexul 0.
       Exemplu de program pentru actualizarea unei liste de
corespondenţa - maillist
       Pentru a ilustra modul de utilizare a structurilor şi tablourilor de
structuri prezentăm un exemplu de program pentru actualizarea unei
liste de corespondenţă.
                                   109
Informaţiile ce vor fi memorate se referă la name, street, city, state,
zip. Pentru definirea structurii de bază addr care va conţine aceste
informaţii vom scrie:
      struct addr {
      char name[20];
      char street[30];
      char city[15];
      char state[10];
      unsigned int zip;
      } addr_info[SIZE];

       Tabloul addr_info contine SIZE structuri de tip addr, unde SIZE
se defineşte după necesităţi.
      Prima funcţie necesară în program este main(), a cărei structură
este următoarea:
void main() {
     char choice;
     init_list();
     for (;;) {
          choice = menu();
          switch (choice) {
          case 'e' : enter(); break;
          case 'd' : display(); break;
          case 's' : save(); break;
          case 'l' : load(); break;
          case 'q' : exit(); }}}
      Funcţia init_list() pregăteşte tabloul structură pentru utilizare
prin punerea unui caracter null în primul byte al câmpului "nume".
Programul impune ca o variabilă structură să nu fie utilizată dacă
câmpul nume este vid. Această iniţializare are loc în memoria internă
a calculatorului (nu în fişierul maillist de pe disc).
      Structura funcţiei de initializare init_list() ar putea fi următoarea:
      /* Functia init_list() */
      void init_list() {
      register int t;
      for (t = 0; t < SIZE; t++)
      *addr_info[t].name = '0';           }
      Funcţia de selectare a meniului menu() va afişa mesajele
opţiunilor şi va returna varianta aleasă. Prin tastarea literei din
paranteze, se va lansa în execuţie o anumită procedură.
/* Functia menu() */
char menu() {
     char s[5],ch;
     do {

                                   110
printf ("(E)ntern");
         printf ("(D)isplayn");
         printf ("(L)oadn");
         printf ("(S)aven");
         printf ("(Q)uitn");
         printf (" Alegeti optiunea: ");
         gets(s);
         ch=s[0];
   } while (!strrchr("edlsq",ch));
   return tolower(ch); }
Observaţie: Funcţia strrchr(cs,c) din <string.h> întoarce un pointer la
ultima apariţie a lui c în cs sau NULL dacă nu apare.
      Funcţia enter() are ca efect introducerea unor noi informaţii în
următoarea structură liberă a listei addr_info[SIZE]. Această
introducere se efectuează prin determinarea primei structuri libere din
memorie (cu addr_info.name setată la 0) şi prin completarea sa cu
informaţii culese de la tastatură.
      /* Functia enter() */
      void enter() {
      register int i;
      for (i=0; i < SIZE; i++)
      if (!*addr_info[i].name) break;
      if (i == SIZE) {
      printf ("addr_info full n"); /* Lista plina */
      return;}
      printf ("Name: ");
      gets (addr_info[i].name);
      printf ("Street: ");
      gets (addr_info[i].street);
      printf ("City: ");
      gets (addr_info[i].city);
      printf ("State: ");
      gets (addr_info[i].state);
      printf ("Zip: ");
      scanf ("%d",&addr_info[i].zip);}
       Rutinele save() şi load() se utilizează pentru actualizarea bazei
de date. Aceste rutine lucrează cu fişierul disc maillist. Când se
apelează load(), atunci se copiază în tabloul structură din memorie
datele stocate în fişierul maillist. Funcţia load() realizează operaţiunea
inversă, de supraînscriere a fişierului disc cu datele din memorie. Spre
exemplu, dacă dorim să adăugăm date la fişierul maillist care conţine
deja date introduse anterior, ordinea de lansare a comenzilor va fi:
init_list(), load(), enter(), save(), exit(). Structura acestor rutine este
următoarea:

                                   111
/* Functia save() */
     void save() {
     register int i;
     if ((fp = fopen("maillist", "wb")) == NULL) {
     printf (" Cannot open filen ");
     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 filen ");
     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 errorn"); }
       Atât save() cât şi load() confirmă un succes a unei operaţii cu
fişiere prin verificarea valorilor întoarse de funcţiile fread() sau
fwrite(). În plus, load() trebuie să caute şi indicatorul de sfârşit de
fişier utilizând funcţia feof() deoarece fread() întoarce aceeaşi valoare
dacă se întâlneşte indicatorul de sfârşit de fişier sau dacă apare o
eroare.
       Funcţia display() afişează pe ecran întregul tablou structură din
memorie care conţine date valide:
      /* Functia display() */
      void display()    {
      register int t;
      for (t=0;t<SIZE;t++) {
      if (*addr_info[t].name!='0') {
      printf("%sn",addr_info[t].name);
      printf("%sn",addr_info[t].street);
      printf("%sn",addr_info[t].city);
      printf("%sn",addr_info[t].state);
      printf("%dnn",addr_info[t].zip);
      getchar();}}}
Listingul complet al programului va fi:
# include <stdio.h>
# include <ctype.h>
# include <string.h>

                                  112
# define SIZE 100
struct addr {
     char name[20];
     char street[30];
     char city[15];
     char state[10];
     unsigned int zip;
     } addr_info[SIZE];
FILE *fp;
void init_list(),enter(),save(),load();
void display(),exit();
char menu();

void main() {
     char choice;
     init_list();
     for (;;) {
          choice = menu();
          switch (choice) {
          case 'e' : enter(); break;
          case 'd' : display(); break;
          case 's' : save(); break;
          case 'l' : load(); break;
          case 'q' : exit(); }}}

/* Functia init_list() */
void init_list() {
register int t;
for (t = 0; t < SIZE; t++)
*addr_info[t].name = '0';    }

/* Functia menu() */
char menu() {
     char s[5],ch;
     do {
          printf ("(E)ntern");
          printf ("(D)isplayn");
          printf ("(L)oadn");
          printf ("(S)aven");
          printf ("(Q)uitn");
          printf (" Alegeti optiunea: ");
          gets(s);
          ch=s[0];
   } while (!strrchr("edlsq",ch));
   return tolower(ch); }

/* Functia enter() */
void enter() {

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

/* Functia display() */
void display(){
register int t;
printf("n%20s","Name");
printf("%30s","Street");
printf("%15s","City");
printf("%10s","State");

                           114
printf("%5sn","Zip");
for (t=0;t<SIZE;t++) {
if (*addr_info[t].name!='0') {
printf("%20s",addr_info[t].name);
printf("%30s",addr_info[t].street);
printf("%15s",addr_info[t].city);
printf("%10s",addr_info[t].state);
printf("%5d",addr_info[t].zip);
getchar();}}}

6.4.2. Introducerea structurilor în funcţii
      a) Introducerea elementelor unei structuri în funcţii
      Când un element al unei variabile tip structură este transmis
(pasat) unei funcţii, de fapt este transmisă valoarea acelui element.
Exemplu:
           struct struct1{
           char x;
           int y;
           float z;
           char s[10];
           } struct2;
     Modalitatea de a introduce fiecare element într-o funcţie este
următoarea:
      func (struct2.x);
      /* se paseaza valoarea caracterului x */
      func2 (struct2.y);
      /* se paseaza valoarea intregului y */
      func3 (struct2.z);
      /* se paseaza valoarea reala a lui z */
      func4 (struct2.s);
      /* se utilizeaza adresa sirului s */
      func (struct2.s[2]);
       //se utilizeaza valoarea caracterului lui s[2]
unde func(), func2(), func3(), func4() sunt numele unor funcţii.
Pentru a transmite funcţiei func() adresele elementelor din structura
struct2, se scrie astfel:
       func (&struct2.x);     /* se paseaza adresa caracterului x */
       func (&struct2.s[2]); /* se paseaza adresa caracterului s[2] */


      b) Transmiterea unei întregi structuri unei funcţii
     Când o structură este utilizată ca argument al unei funcţii,
limbajul C transmite funcţiei întreaga structură utilizând metoda
                                 115
standard a apelului prin valoare. Aceasta înseamnă că orice modificare
asupra conţinutului structurii în interiorul funcţiei în care este apelată
structura, nu afectează structura folosită ca argument.
      Ceea ce trebuie avut neapărat în vedere atunci când, ca
parametru, se utilizează o structură este ca tipul argumentului să fie
identic cu tipul parametrului.
Exemplu:
# include <stdio.h>
void f1();
     void main() {
     struct {
     int a,b;
     char ch;
     } arg;
      arg.a = 1000;
      f1(arg); /* se apeleaza functia f1() */   }
      void f1(param) /* se declara functia f1 */
          struct {
          int x, y;
          char ch;
          } param;
        {printf ("%dn", param.x); }
      Acest program declară atât argumentul arg al lui f1, cât şi
parametrul param ca având acelaşi tip de structură. Programul va afişa
pe ecran valoarea 1000.
      Pentru a face programele mai compacte se procedează la
definirea structurii ca variabilă globală şi apoi la utilizarea numelui
acesteia pentru a declara diversele variabile structură.
Exemplu:
     # include <stdio.h>
     void f1();
     /* Se defineste un tip structura */
     struct struct_tip {
     int a, b;
     char ch;};
     void main() {
     struct struct_tip arg;
     /* se declara structura arg */
     arg.a = 1000;
     f1(arg);}
     void f1(struct struct_tip param)                 /*   se    declara
functia f1() */
     {printf ("%dn",param.a); }



                                  116
Pentru exemplificare, propunem următorul program:
     -    se preia de la tastatura un prim şir de numere întregi
     -    se preia de la tastatura un al doilea şir de numere întregi
     -    se concatenează cele două şiruri
     -    şirul rezultat se sortează în ordine crescătoare, având în
          vedere ca primele poziţii să fie ocupate de numerele pare din
          şir sortate crescător după care să urmeze numerele impare din
          şir sortate tot crescător.
        Pentru a realiza acest program, vom utiliza nu variabile de tip
şir ci o structură care să conţină ca prim element şirul respectiv iar cel
de-al doilea element să fie lungimea acestuia. Se vor construi funcţii
care să realizeze citirea şirurilor de la tastatură, scrierea lor pe display,
respectiv să le ordoneze şi să le sorteze după paritate. Toate aceste
funcţii comunică prin intermediul unei variabile globale de tip
structură şi a mai multor variabile locale de tip structură.
        Programul în C este prezentat în continuare:
# include <stdio.h>
// definim prototipurile functiilor utilizate
struct sir_lung cit_sir();
struct sir_lung concat_sir();
struct sir_lung ord_sir();
struct sir_lung par_sir();
struct sir_lung impar_sir();
void tip_sir();
/* se defineste structura sir+lungime si variabila
globala sir */
struct sir_lung {
         int sir[80];
         int lung;} sir;
// programul principal
void main(){
struct sir_lung sir_init,sir_ord,sir_par,sir_impar;
sir_init=cit_sir();
getchar();
sir_ord=cit_sir();
sir_init=concat_sir(sir_init,sir_ord);
sir_par=par_sir(sir_init);
sir_par=ord_sir(sir_par);
sir_impar=impar_sir(sir_init);
sir_impar=ord_sir(sir_impar);
sir_ord=concat_sir(sir_par,sir_impar);
tip_sir(sir_init);
tip_sir(sir_ord);}
// se definesc functiile

                                    117
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;}
    Din funcţia cit_sir() se poate ieşi prin tastarea oricarui caracter
nenumeric. Se observă cum aceasta funcţie lucrează direct cu variabila
                                 118
globală şir iar celelalte cu variabile locale proprii care apoi sunt
asignate variabilei globale şir la returnare.
Rulând programul vom obţine rezultatele:
      1 2 17 -3 6 -4;
      -9 -2 31 2 -7;
      1 2 17 -3 6 -4 -9 -2 31 2 -7
      -4 -2 2 2 6 -9 -7 -3 1 17 31

6.4.3. Tablouri şi structuri în structuri
    Elementele unei structuri pot fi simple (int, char etc.) sau
complexe (tablouri de elemente de diferite tipuri, structuri).
Exemplu: Considerăm structura :
      struct x {
      int a[10][10]; /* tablou de 10x10 intregi */
      float b;} y;
De exemplu, referirea întregului a[3][7] se face prin:
                       y.a[3][7]
Când o structură este un element al altei structuri, structurile se
numesc încuibate (nested, incluse).
Exemplu:
                  struct sal {
                  struct addr adresa;
                  float salariu;
                  } muncitor;
      Se observă că elementele variabilei-structură "adresa" sunt
incluse în structura "sal". Aici "addr" este structura definită anterior.
Exemplul anterior defineşte structura "sal" cu 2 elemente: primul este
o structură de tip "addr" care conţine adresa salariatului; al doilea
element este salariul săptămânal al acestuia.
Instrucţiunea următoare va atribui codul 90178 variabilei "zip" din
adresa muncitorului
                      muncitor.adresa.zip = 90178;
      Se observă că elementele fiecărei structuri sunt referite de la
exterior către interior, respectiv de la stânga la dreapta.

6.5. Uniuni
         În C o uniune este o zonă de memorie utilizată de mai multe
variabile ce pot fi diferite ca tip. Definitia uniunilor este similară celei
a structurilor.
 Exemplu:
                                     119
union tip_u {
                 int i;
                 char ch;};
      O variabilă de acest tip poate fi declarată fie prin plasarea
numelui său la sfîrşitul acestei definiţii, fie utilizând o instrucţiune de
declarare separată. De exemplu, instrucţiunea :
                       union tip_u        exuniune;
declară variabila-uniune "exuniune" în care atât întregul i, cât şi
caracterul ch ocupă aceeaşi zonă de memorie (cu toate ca int ocupa 4
octeţi, iar char numai un octet).
       Când se declară o variabilă union compilatorul C rezervă o zonă
de memorie suficient de lungă capabilă să preia variabila cu lungimea
cea mai mare.
      Pentru a accesa elementele unei uniuni se utilizează aceeaşi
sintaxă folosită la structuri (operatorii punct şi " -> ").
      Când un element al unei uniuni se adresează direct, se utilizează
operatorul ".", iar când elementul se adresează printr-un pointer, se
foloseşte operatorul "-> ".
      De exemplu, pentru a atribui elementul întreg i al uniunii
anterioare "exuniune" valoarea 10, se va scrie:
                             exuniune.i = 10;
      În exemplul următor, programul transferă un pointer la
"exuniune" unei funcţii :
            func1(union tip_u *un)
            {    un -> i = 10; }
/* se atribuie valoarea 10 intregului i al uniunii exuniune */
      In C, uniunile sunt des utilizate când sunt necesare conversii de
tip. De exemplu, funcţia standard putw() ce realizează scrierea binară
a unui întreg într-un fişier de pe disc, poate fi scrisă folosind o uniune.
Pentru aceasta, mai întâi se crează o uniune ce cuprinde un întreg şi un
vector de două caractere, astfel:
            union pw {
                 int i;
                 char ch[2];};
      Atunci, structura lui putw(), utilizând aceasta uniune este :
     putw(word, fp)
     union pw word; /* se declara uniunea word */
     FILE *fp {
putc(word ->ch[0]); // se scrie primul caracter
putc(word->ch[1]); // se scrie al doilea caracter }



                                   120
6.6. Enumerări
      O enumerare este o mulţime de constante întregi ce pot lua toate
valorile unei variabile de un anumit tip.
      Enumerările se definesc în acelaşi mod ca şi structurile, utilizând
cuvântul cheie enum ce semnalează începutul unui tip enumerare.
Forma generală de definire a unei enumerări este:
    enum nume_tip_enum { lista_enumeratori} lista_variabile;
unde atât nume_tip_enum, cât şi lista_variabile sunt opţionale.
       Exemplu: Următorul fragment defineşte o enumerare numită
"bancnota" cu care apoi se declară o enumerare numită "bani" având
acest tip:
enum bancnota {suta,douasute,cincisute,mie,cincimii,zecemii};
enum bancnota bani;
Dându-se această definiţie şi declaraţie, sunt valabile urmatoarele
instructiuni:
     bani = mie;
if (5*bani == cincimii) printf("Sunt 5000 lei.n");
        Trebuie precizat că fiecare enumerator este caracterizat printr-o
valoare întreaga. Fără nici o altă iniţializare, valoarea primului
enumerator este 0, a celui de-al doilea este 1, s.a.m.d. De aceea,
instrucţiunea:     printf ("%d %d, suta, mie);
va afisa pe ecran:        0 3
       Se pot specifica valorile unuia sau mai multor simboluri folosind
iniţializatori. De exemplu:
enum bancnota {suta, douasute, cincisute, mie=1000, cincimii,
zecemii};
face ca simbolurile din enumerarea bancnota să aibă valorile:
           suta = 0
           douasute = 1
           cincisute = 2
           mie = 1000
           cincimii = 1001
           zecemii = 1002
Urmatorul fragment de program nu funcţionează, deoarece "bani" este
un întreg şi nu un şir :
           bani = cincimii;
           printf ("%s", bani);
Nici acest program nu functionează:
           gets (s);
           strcpy (bani, s);

                                  121
Pentru a afişa tipurile bancnotelor conţinute în enumerarea "bani", se
va scrie:
     switch (bani) {
          case suta: printf (" suta "); break;
          case douasute: printf (" douasute "); break;
     . . . . . . . . . . . . . . . . . . . . . . . . . .
          case zecemii: printf (" zecemii "); }
       Uneori pentru a translata valoarea unui enumerator în şirul
corespunzător, se poate declara un tablou de şiruri şi utiliza valoarea
enumeratorului ca index. De exemplu, următorul fragment va afişa
şirul corespunzător:
     char        name[ ][20] = {
                 "suta",
                 "douasute",
                 "cincisute",
                 "mie",
                 "cincimii"
                 "zecemii"
                 };
                 . . . . . .
                 printf ("%s", name[bani]);
       Fragmentul anterior va funcţiona numai dacă nu se realizează
iniţializarea simbolurilor, deoarece indexarea şirurilor începe cu zero.
Următorul program afişează numele bancnotelor:
# include <stdio.h>
enum bancnota { suta, douasute, cincisute,mie,
cincimii,zecemii,cincizecimii,sutamii,cincisutemii};
char name[][20]=
{"suta","douasute","cincisute","mie","douamii","cincimii"
,"zecemii","cincizecimii" "sutamii","cincisutemii"};
     void main() {
     enum bancnota bani;
for (bani = suta; bani <= cincisutemii; bani ++)
     printf ("%sn", name[bani]);}
Dacă variabilei uniune y din exemplul următor i se aplică operatorul
sizeof() vom găsi sizeof(y) = 8.
# include <stdio.h>
     union {
     char ch;
     int i;
     double f;
     } y;
    void main() {printf("%dn",sizeof(y));}
      Deci compilatorul va reţine valoarea celei mai largi tipuri de
date din uniune.

                                 122
Capitolul VII

                           POINTERI

       Un pointer este o variabilă care păstrează adresa unui obiect de
tip corespunzător. Forma generală pentru declararea unei variabile
pointer este:
                         tip * nume_variabila;
unde tip poate fi oricare din tipurile de bază admise în C, iar
nume_variabila este numele variabilei pointer. Tipul de bază al
pointerului defineşte tipul variabilelor spre care indică pointerul.
       Variabila pointer este o variabilă de un tip special, aparte de
tipurile char, int, float. Cuvântul cheie tip din declaraţia unui pointer
se referă la tipul de dată spre care indică pointerul, nu la formatul în
care se stochează efectiv o variabilă pointer în memorie. Formatul în
care se stochează o variabilă pointer în memorie depinde de tipul de
compilator care se foloseşte, deci depinde în mare măsură de tipul
procesorului pentru care a fost proiectat compilatorul. O indicaţie
despre formatul în care se stochează o variabilă pointer în memorie
poate fi obţinută prin tipărirea conţinutului unei variabile pointer (o
adresă) utilizând printf() cu formatul %p.
Exemplu:
char *p;                /* pointer la caracter */
int *temps, *start;     /* pointeri la intregi */
char *const q; /* pointer constant la caracter */

7.1. Operatori pointer
   Există doi operatori pointer speciali * şi &:
• Operatorul & este un operator unar care oferă (returnează) adresa
   unei variabile (adresa operandului său).
• Operatorul * este complementarul lui &. Este un operator unar
   care returnează valoarea variabilei plasată la adresa care urmează
   după acest operator.
Exemplu:
 # include <stdio.h>
 void main (void) {
  int *count_addr, count, val;
  count = 100;     /* int count are valoarea 100 */
                                  123
count_addr = &count; /*count_addr indica spre count.
Aceasta instructiune plaseaza in count_addr adresa din
memorie a variabilei count, adresa care nu are nici o
legatura cu valoarea lui count */
  val = count_addr;     /* val preia valoarea de la adresa
count_addr. Aceasta instructiune plaseaza valoarea lui
count aflata la adresa count_addr în variabila val */

  printf ("%d", val); /*Se va tipari numarul 100 */                  }
Spre exemplu, să considerăm porţiunea de program:
  short i, j; // i si j sunt ambele intregi scurti
  short *p    // p este pointer la tip intreg scurt
  i = 123;
  p = &i;
  j = *p;
Să presupunem că zona de stocare a celor trei variabile arată astfel:
                         Ad s
                            re a      Me om ria
                       i    12 0
                              0
                       j    12 2
                              0
                       p    12 4
                              0

După primele două atribuiri
  i = 123;
  p = &i;
zona de stocare va arăta astfel:
                           Ad s
                             re a         M m ria
                                           e o
                       i   12 0
                             0           123
                       j   12 2
                             0
                       p   12 4
                             0           12 0
                                           0

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ă:
                            Ad s
                               re a      M m ria
                                           e o
                     i      12 0
                              0        12 3
                   j       12 2
                             0           123
                   p       12 4
                             0           12 0
                                           0


                                   124
7.1.1. Importanţa tipului de bază
      Considerăm declaraţia: val = *count_addr;
      Se pune întrebarea: care va fi numărul de bytes ce va fi transferat
variabilei val de la adresa indicată prin *count_addr. Sau, mai general,
de unde ştie compilatorul câţi bytes să transfere în cazul oricărei
asignări care utilizează pointeri.
      Răspunsul la aceste întrebări este acela că, tipul de bază al
pointerului determină tipul datei spre care indică pointerul.
Exemplu:
/* Acest program nu lucreaza corect */
  # include <stdio.h>
  void main (void) {
     float x = 10.12, y;
short int *p; /* pointer la intreg */
p = &x;       /* p preia adresa lui x */
y = *p;   /* y preia valoarea de la adresa p */
printf ("x = %f y = %f",x,y);    }
       Acest program nu va atribui valoarea lui x lui y, deoarece în
program se declară p ca fiind pointer la întreg scurt şi compilatorul va
transfera în y numai 2 bytes (corespunzători reprezentării unui întreg
scurt) şi nu 4 bytes, corespunzători unui număr real în virgulă mobilă.
7.1.2. Expresii în care intervin pointeri
      În general, expresiile în care intervin pointeri respectă aceleaşi
reguli ca orice alte expresii din limbajul C.
       • Atribuirea pointerilor
Ca orice variabilă, un pointer poate fi folosit în membrul drept al unei
instrucţiuni de asignare (atribuire), pentru atribuirea valorii unui
pointer unui alt pointer.
Exemplu:
  # include <stdio.h>
  void main (void) {
int x;
int *p1,*p2; /* pointeri la intregi */
p1 = &x;      /* p1 indica spre x */
p2 = p1       /* p2 indica tot spre x */
printf ("p1 = %p p2 = %p", p1, p2); }
/* Se afiseaza valoarea hexa a adresei lui x, nu valoarea
lui x */
Se observă că în funcţia printf() tipărirea se face cu formatul %p care
specifică faptul că variabilele din listă vor fi afişate ca adrese pointer.

                                   125
Din cele de mai sus se observă că operaţia fundamentală care se
execută asupra unui pointer este indirectarea, ceea ce presupune
referirea la un obiect indicat de acel pointer.
Exemplu:
  char c1 = 'a';
  char *p = &c1;   /* p contine adresa lui c1 */
  char c2 = *p; /*c2 preia valoarea de la adresa p*/
  printf ("c1 = %c c2 = %c", c1, c2);
        Deci variabila indicată de p este c1, iar valoarea memorată în c1
este 'a', aşa încât valoarea lui *p atribuită lui c2 este 'a'.
        • Operaţii aritmetice efectuate asupra pointerilor
 Utilizarea operatorilor de incrementare şi decrementare
       Fie secvenţa:
           int *p1;             /* pointer la intreg */
           p1++;
De fiecare dată când se incrementează p1, acesta va indica spre
următorul întreg. Astfel, dacă p1 = 2000, după efectuarea instrucţiunii
p1++, acesta va fi p1 = 2004 (va indica spre următorul întreg).
    După fiecare incrementare a unui pointer, acesta va indica
       spre următorul element al tipului său de bază.
    După fiecare decrementare a unui pointer, acesta va indica
       spre elementul anterior.
    Valoarea pointerilor va fi crescută sau micşorată în concordanţă
cu lungimea tipului datelor spre care aceştia indică, aşa cum se poate
vedea în exemplul următor:
char *ch = 3000;
short int *i = 3000;

                       ch          3000     i
                       ch + 1      3001
                       ch + 2      3002     i+1
                       ch + 3      3003
                       ch + 4      3004     i+2
                       ch + 5      3005
                       ch + 6      3006     i+3
                                  Memoria
      Cum valoarea indirectată de un pointer este o l-valoare, ea poate
fi asignată şi incrementată ca orice altă variabilă. O l-valoare (left
value) este un operand care poate fi plasat în stânga unei operaţii de
atribuire. Verificaţi utilizarea pointerilor din programul următor:
 # include <stdio.h>
                                   126
void main(void) {
short *pi, *pj, t;
long *pl;
double *pd;
short i, j;
i=1; j=2; t=3;
printf("i= %d, j= %dn", i, j);
pi=&i; pj=&j;
printf("pi= %p, pj= %pn", pi, pj);
*pj /= *pi+1;
printf("*pi= %d *pj= %dn", *pi, *pj);
*pj /= *pi+2;
printf("*pi= %d *pj= %dn", *pi, *pj);
printf("++pj= %p, ++*pj= %dn",++pj,++*pj);              }
       În urma rulării sale, pe calculatoarele mai moderne obţinem
rezultatul
      i= 1,j= 2
      pi= 0065FDE0,pj= 0065FDDC
      *pi= 1 *pj= 1
      *pi= 1 *pj= 0
      ++pj= 0065FDDE,++*pj= 1

 Utilizarea operatorilor de adunare şi de scădere
      La sau dintr-un pointer, se pot aduna sau scădea valori de tip
întreg. Rezultatul este un pointer de acelaşi tip cu cel iniţial, indicând
spre un alt element din tablou. De exemplu,
            p1 = p1 + 9;
face ca p1 să indice spre al 9-lea element având tipul lui p1,
considerând că elementul curent este indicat de p1. Evident că
valoarea pointerului se va modifica corespunzător lungimii tipului
datei indicată prin pointer.
Exemplu:
      int *p1;      /* Pointer la intreg */
           p1 = p1 + 9;
Dacă valoarea p1 = 3000, atunci p1 + 9 va avea valoarea:
      (valoarea lui p1)+9*sizeof(int)=3000+9*4=3036
        Aceleaşi considerente sunt valabile în cazul în care un întreg
este scăzut dintr-un pointer. Dacă doi pointeri de acelaşi tip sunt
scăzuţi, rezultatul este un număr întreg cu semn care reprezintă
deplasamentul dintre cei doi pointeri (pointerii la obiecte vecine diferă
cu 1).
       În cazul tablourilor, dacă pointerul rezultat indică în afara
tabloului, rezultatul este nedefinit.


                                  127
Dacă p indică spre ultimul membru dintr-un tablou, atunci (p+1)
are valoare nedeterminată.
Observaţii :
 Nu se pot aduna sau scădea valori de tip float sau double la/sau
    dintr-un pointer.
 Nu se pot efectua operaţii de înmulţire şi împărţire cu pointeri.
Exemplu: Scăderea a doi pointeri este exemplificată în programul:
       # include <stdio.h>
       void main(){
        int i=4, j;
        float x[] = {1,2,3,4,5,6,7,8,9}, *px;
       j = &x[i]-&x[i-2];
       px = &x[4]+i;
       printf("%d %f %p %pn",j,*px,&x[4],px);
        }
 Compararea pointerilor
       Doi pointeri de acelaşi tip se pot compara printr-o expresie
relaţională, astfel: dacă p şi q sunt doi pointeri, atunci instrucţiunile:
if (p < q)
printf (“ p indica spre o adresa mai mica decit q n “);
sunt corecte.
      Compararea pointerilor se utilizează când doi sau mai mulţi
pointeri indică spre acelaşi obiect comun.
Exemplu: Un exemplu interesant de utilizare a pointerilor constă în
examinarea conţinutului locaţiilor de memorie ale calculatorului.
/* Programul afiseaza continutul locatiilor de memorie de
la o adresa specificata */
  # include <stdio.h>
  # include <stdlib.h>
  dump (start);
  void main (void) {
     /* start = adresa de inceput */
     unsigned long int start;
     printf (“Introduceti adresa de start: “);
scanf (“ %lu “, &start);
dump (start); /* Se apeleaza functia dump () */   }
dump (start)       /* Se defineste functia dump() */
unsigned long int start;
{char far *p;
 int t;
 p = (char far *) start; /*Conversie la un pointer*/
 for (t = 0; ; t++, p++) {
     if (!(t%16)) printf ("/n");
printf ("%2X ", *p);

                                  128
/*Afiseaza in hexazecimal continutul locatiei
  de memorie adresata cu *p*/
if (kbhit()) return;} // Stop cand se apasa orice tasta }
       Programul introduce cuvântul cheie far care permite pointerului
p să indice locaţii de memorie care nu sunt în acelaşi segment de
memorie în care este plasat programul. Formatul %lu din scanf()
înseamnă: "citeşte un unsigned long int". Formatul %X din printf()
ordonă calculatorului să afişeze argumentul în hexazecimal cu litere
mari (Formatul %x afişează în hexazecimal cu litere mici).
       Programul foloseşte explicit un şablon de tip pentru transferul
valorii întregului fără semn, start, pe care îl introducem de la tastatură,
într-un pointer. Funcţia kbhit() returnează ADEVARAT dacă se apasă
o tastă, altfel returnează FALS.
 Utilizarea pointerilor ca parametri formali ai funcţiilor
        În exemplele de până acum, s-au folosit funcţii C care atunci
când erau apelate, parametrii acestor funcţii erau (întotdeauna)
actualizaţi prin pasarea valorii fiecărui argument. Acest fapt ne
îndreptăţeşte să numim C-ul ca un limbaj de apel prin valoare. Există
totuşi o excepţie de la această regulă atunci când argumentul este un
tablou. Această excepţie este explicată, pe scurt, prin faptul că
valoarea unui nume al unui tablou (vector, matrice etc.) neindexate
este adresa primului său element.
        Deci, în cazul unui argument de tip tablou, ceea ce se pasează
unei funcţii este o anumită adresă.
        Folosind variabile pointer se pot pasa adrese pentru orice tip de
date. Spre exemplu, funcţia scanf() acceptă un parametru de tip
pointer (adresă): scanf(“%1f“,&x);
        Ceea ce este important de evidenţiat este cum anume se poate
scrie o funcţie care să accepte ca parametri formali sau ca argumente
pointeri ?.
       Funcţia care recepţionează o adresă ca argument va trebui să
declare acest parametru ca o variabilă pointer. De exemplu, funcţia
swap() care va interschimba valorile a doi întregi poate fi declarată
astfel:
  # include <stdio.h>
  void swap();/*Prototipul functiei swap()*/
  void main(void)
  { int i,j;
  i=1; j=2;
  printf("i= %d j= %dn", i, j);
  swap(&i,&j);     /* Apelul functiei */
                                   129
printf("i= %d j= %dn", i, j);
  }
  void swap(int *pi, int *pj)
  {    short t;
  t = *pi; *pi = *pj; *pj = t; }

7.2. Pointeri şi tablouri
         Între pointeri şi tablouri există o strânsă legătură în limbajul C.
Există însă o mare deosebire între tablouri şi pointeri pe care trebuie
să o avem mereu în vedere. Un tablou constă întotdeauna dintr-o mare
cantitate de memorie, îndeajuns de mare pentru a reţine toţi octeţii
corepunzători tuturor elementelor tabloului. Astfel, tabloul q declarat
ca short q[100]; rezervă 2x100 octeţi de memorie iar int q[100] rezervă
4x100 octeţi de memorie.
      Pe de altă parte, pentru un pointer se alocă o zonă de memorie
redusă la numai câţiva octeţi necesari pentru a reţine o adresă de
memorie. De fapt, zona de memorie alocată unui pointer depinde de
tipul pointerului (tipul datelor spre care punctează acesata) şi va fi de
numai câţiva octeţi ( în exemplele anterioare - 4 octeţi).
Considerăm secvenţa:
            char sir[80];
            char *p1;
            p1 = sir;
Acest fragment face ca p1 să adreseze primul element al tabloului sir.
       În C numele unui tablou fără indici este adresa de start a
tabloului. De fapt, numele tabloului este un pointer la tablou.
       Ca o concluzie, un pointer declarat sau numele unui tablou fără
indici reprezintă adrese, pe când numele unui tablou cu indici se referă
la valorile stocate în acea poziţie în tablou.
       Deoarece indicele inferior al oricărui vector este zero, pentru a
referi al 5-lea element al şirului sir, putem scrie sir[4] sau *(p1+4) sau
p1[4]. Deci pointerul p1 indică spre primul element al lui sir.
      Pentru a avea acces la elementul unui tablou, în limbajul C, se
folosesc 2 metode :
1. utilizarea indicilor tabloului;
2. utilizarea pointerilor .
      Deoarece a doua metodă este mai rapidă, în programarea în C,
de regulă, se utilizează această metodă.


                                   130
Pentru a vedea modul de utilizare a celor două metode,
considerăm un program care tipăreşte cu litere mici un şir de caractere
introdus de la tastatură cu litere mari:
Exemplu: Versiunea cu indici
void main (void) {
  char sir[80];
  int i;
   printf ("Introduceti un sir de caractere scrise cu
litere mari: n");
  gets (sir);
  printf ("Acesta este sirul in litere mici: n");
  for(i=0;sir[i];i++)
printf("%c", tolower(str[i])); }
Exemplu: Versiunea cu pointeri
void main (void) {
  char sir[80] , *p;
   printf ("Introduceti un sir de caractere scrise cu
litere mari: n");
  gets (sir);
  printf (" Acesta este sirul in litere mici:  n");
  p = sir; /* p preia adresa de inceput a sirului */
  while (*p) printf (" %c ", tolower(*p++));   }
Pointerii sunt rapizi şi uşor de utilizat când se doreşte referirea
elementelor unui tablou în ordine strict crescătoare sau strict
descrescătoare. Dacă însă se doreşte referirea aleatoare a elementelor
unui tablou, atunci indexarea tabloului este cel mai simplu şi sigur de
utilizat.
7.2.1. Indexarea pointerilor
      În C, dacă p este un pointer, iar i este întreg, p[i] este identic cu
*(p+i). Dacă avem declaraţiile:
      short q[100];
      short *pq
atunci sunt permise şi posibile următoarele declaraţii:
   Varianta cu          Varianta cu                 Descriere
      tablou              pointeri
 pq=&q[0]            pq=&q[0]             Pointerul pq indică adresa
 pq=q                pq=q                 primului element al tabloului q
 q[n]                pq[n]                pq[n] înseamnă acelaşi lucru cu
                     *(pq+n)              *(pq+n)
      În C, dacă se pune un index unui pointer, ca în pq[n], atunci se
consideră această utilizare echivalentă cu *(pq+n). Cu alte cuvinte,

                                   131
orice referire la pq[n] înseamnă acelaşi lucru cu valoarea de la adresa
(pq+n).
       Exemplu: Programul următor realizează tipărirea pe ecran a
numerelor cuprinse între 1 şi 10.
void main (void) {
     int v[10]={1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
     int *p, i;
     p = v;         /* p indica spre v */
     for (i=0;i<10;i++) printf ("%d", p[i]);   }
      Utilizarea constantelor şir în locul poinetrilor la caractere este
posibilă dar nu este uzuală.
Exemplu:
# include <stdio.h>
void main(){
char *sir = "To be or not to be", *altsir;
printf("%sn", "That don't impress me much"+5);
printf("%cn",*("12345"+3));
printf("%cn","12345"[1]);
puts("stringn");
altsir = "American pie";
printf("sir = %snaltsir = %sn",sir,altsir); }
       Exemplu de utilizare a stivei. Stiva este o listă cu acces tip
LIFO (last in - first out). Pentru a avea acces la elementele stivei, se
utilizează doua rutine: push() şi pop(). Calculatorul păstrează stiva
într-un tablou, iar rutinele push() şi pop() realizează accesul la
elementele stivei utilizând pointeri. Pentru memorarea vârfului stivei,
utilizăm variabila tos (top of stack).
       Considerăm că folosim numai numere de tip întreg. În
programul main(), rutinele push() şi pop() sunt utilizate astfel: push()
citeşte numerele introduse de la tastatură şi le memorează în stivă dacă
acestea sunt nenule. Când se introduce un zero, pop() extrage valoarea
din stivă.
# include <stdlib.h>
# include <stdio.h>
void push(); /* Prototipul functiei push() */
int pop();    /* Prototipul functiei pop() */
// Se rezerva pentru stiva 50x4 = 200 locatii
int stack[50];
int *p1, *tos;
void main(void) {
  int value;
  p1 = stack; /* p1 preia adresa bazei stivei */
  tos = p1;   /* tos va contine varful stivei */
  do { scanf ("%d", &value);
                                 132
if (value != 0) push (value);
     else printf ("Nr. extras din stiva este                    %d   n",
pop()); } while (value != -1);
}
void push(int i)   /* Functia push() */
{ p1++;
  if (p1==(tos+50)) {
     printf("n Stiva este plina.");
     exit (0);}
  *p1 = i;
  printf("introdus in stivan");
}
int pop()
{ if (p1 == tos) {
     printf ("n Stiva este complet goala.");
     exit (0); }
  p1 --;
  return *(p1+1); }

7.2.2. Pointeri şi şiruri
      Deoarece numele unui tablou fără indici este un pointer la
primul element al tabloului, pentru implementarea unor funcţii care
manipulează şiruri, se pot utiliza pointeri. §tim că funcţia strcmp(s1,
s2) realizează compararea şirurilor s1 şi s2 şi întoarce 0 dacă s1 = s2, o
valoare negativă, dacă s1 < s2 şi o valoare pozitivă, dacă s1 > s2.
Exemplu: Prezentăm o variantă de scriere a funcţiei strcmp(s1,s2)
char *s1, *s2;
{ while (*s1)
     if (*s1 - *s2) /* Daca valoarea punctata de s1
        este diferita de valoarea punctata de s2, */
  return *s1-*s2; /* Returneaza diferenta */
else {s1++; s2++;}
  return '0'; //Se returneaza 0 in caz de egalitate }
      Reamintim că un şir în C se termină cu caracterul NULL. De
aceea, instructiunea while(*s1) rămâne adevărată până când se
întâlneşte caracterul NULL, care este o valoare falsă.
      Dacă într-o expresie se utilizează un şir constant, calculatorul
tratează constanta ca pointer la primul caracter al şirului.
Exemplu: Programul următor afişează pe ecran mesajul " Acest
program funcţionează ":
      # include <stdio.h>
      void main (void) {
           char *s;
           s = " Acest program functioneaza ";
           printf (s);   }

                                  133
7.2.3. Preluarea adresei unui element al unui tablou
      Până acum s-a văzut că un pointer poate să adreseze primul
element al unui tablou. Este posibil să se adreseze orice element al
unui tablou aplicând operatorul & unui tablou indexat. De exemplu,
                                 p = &x[2];
plasează adresa celui de-al 3-lea element al vectorului x în pointerul p.
Un domeniu în care această practică este esenţiala constă în găsirea
unui subşir într-un şir dat.
Exemplu: Programul următor afişează ultima parte a unui şir introdus
de la tastatură, din punctul în care se întâlneşte primul spaţiu:
  # include <stdio.h>
  void main (void) {
     char s[80];
     char *p;
     int i;
     printf (" Introduceti un sir : n ");
     gets (s);
  /* Gaseste primul spatiu sau sfarsitul sirului */
     for (i = 0; s[i] && s[i] != ' '; i++)
        p = & s[i+1];
    printf (p); }
       Dacă p indică spre un spaţiu, programul va afişa spaţiul şi apoi
subşirul rămas. Dacă în şirul introdus nu este nici un spaţiu, p indică
spre sfârşitul şirului şi atunci nu se va afişa nimic. De exemplu, dacă
se introduce “my friend“, atunci printf() afişează mai întâi un spaţiu şi
apoi “friend“.
7.2.4. Tablouri de pointeri
      Putem construi tablouri de pointeri în aceeaşi manieră în care se
definesc alte tipuri de date.
Exemplu:
  int *x[10]; // Vector de 10 pointeri la intregi
  char *p[20]; // Vector de 20 pointeri la caracter
     Pentru atribuirea unei variabile întregi, var, celui de al treilea
element al tabloului de pointeri *x[10], se va scrie:
            x[2] = &var;
Pentru găsirea valorii lui var, se va scrie:
  y = *x[2]; //Valoarea lui var este atribuita lui y
Exemplu: Tablourile de pointeri pot fi utilizate în construirea
mesajelor de eroare, astfel:


                                    134
char *err[ ] = {
          " Cannot open file n ",
          " Read error n ",
          " Write error n " };
selmes (int num)      /* Selecteaza un mesaj */
{    scanf("%d", &num); /* Se introduce un
    numar de la tastatura */
          printf("%s", err[num]); }

      Funcţia printf() este apelată din funcţia selmes(). Aceasta va
afişa mesajul de eroare indexat prin numărul de eroare num, care este
pasat ca argument funcţiei selmes(). De exemplu, dacă se introduce 2,
atunci se va afişa mesajul: Write error
      Atentie !. Trebuie facută distincţia între:
 int *v[10]; // Tablou de 10 pointeri la intregi
 int (*v)[10]; // Pointer la un tablou de 10 intregi
Pentru aceasta trebuie ţinut cont de faptul că * este un operator
prefixat, iar [] şi () sunt operatori postfixaţi. Deoarece prioritatea
operatorilor postfixaţi este mai mare decât cea a operatorilor
prefixaţi, atunci când se doreşte schimbarea priorităţii, trebuie
folosite paranteze.
7.2.5. Pointeri la pointeri
      Un tablou de pointeri este ceea ce numim pointeri la pointeri.
Conceptul de tablou de pointeri este simplu, deoarece indexarea
tabloului conduce la clarificarea semnificaţiei lui.
      Un pointer la un pointer este o formă de indirectare multiplă sau
un lanţ de pointeri.
      În cazul unei indirectari simple, valoarea pointerului este adresa
variabilei care conţine valoarea dorită:
            Pointer                                Variabilă
             Adresă            --------->            Valoare

      În cazul unui pointer la pointer, primul pointer conţine adresa
celui de-al doilea pointer, care indică spre variabila ce conţine
valoarea dorită:
         Pointer              Pointer             Variabilă
          Adresă --------- Adresă --------- Valoare
                       >                    >

Declararea indirectărilor multiple se face sub forma:
                                 135
/* cpp este un pointer la pointer la caracter */
char **cpp;
/* newbalance este un pointer la pointer la float */
float **newbalance;
       Pentru a avea acces la o valoare indirectată printr-un pointer la
pointer este necesară, de asemenea, utilizarea operatorului * de două
ori, aşa cum se vede în exemplul următor:
 # include <stdio.h>
 void main (void) {
  int x, *p, **q;
  x = 10;
  p = &x;          /* p preia adresa lui x */
  q = &p;          /* q preia adresa lui p */
  printf(" %d ", **q);/*Se afiseaza valoarea lui x*/                   }

7.2.6. Iniţializarea pointerilor
      Secvenţa:
            char *p;
            char s[] = "Hello world n ";
            p = s;    /* p indica spre s */
este echivalentă cu:
            char *p = "Hello world n";
Într-un program, p din ultima declaraţie poate fi utilizat ca orice alt şir.
Astfel, programul următor este corect:
 # include <stdio.h>
 char *p = " Hello world n ";
 void main (void) {
  register int t;
 /*Se tipareste sirul in ordine directa si inversa*/
  printf (p);
  for(t = strlen(p)-1; t > -1; t--)
printf("%c",p[t]); }
       Observaţie: Neiniţializarea pointerilor sau iniţializarea greşită a
acestora, poate duce la erori care, în cazul programelor de dimensiuni
mari, sunt foarte greu de depistat şi pot avea urmări catastrofale.
Exemplu: Considerăm programul:
  # include <stdio.h>
  void main(void) {
     int x, *p;
     x = 10;
     *p = x;
     printf ("%dn", *p);                }



                                   136
Acest program atribuie valoarea 10 anumitor locaţii de memorie
necunoscute. Programul nu va oferi niciodată o valoare pointerului p
dar va tipări valoarea lui x.
Exemplu: Considerăm acum următorul program:
  # include <stdio.h>
  void main (void) {
     int x, *p;
     x = 10; p = x;
     printf("%d",*p);        }
       Funcţia printf() nu va afişa niciodată valoarea lui x (care este
10), dar va tipări o valoare necunoscută. Aceasta datorită atribuirii
greşite:
           p = x;
Instrucţiunea atribuie valoarea 10 pointerului p, care se presupune că
reprezintă o adresă şi nu o valoare. Pentru a corecta programul, trebuie
scris:      p = &x;
7.2.7. Alocarea dinamică a memoriei
    Există două metode principale prin care un program C poate
memora informaţii în memoria principală a calculatorului.
     • Prima metodă foloseşte variabilele globale şi locale. În cazul
variabilelor globale, memoria ce li se alocă este fixă pe tot timpul
execuţiei programului. Pentru variabilele locale, programul alocă
memorie în spaţiul stivei, în timpul execuţiei programului. Deşi
variabilele locale sunt eficient de implementat, în C, de multe ori,
utilizarea acestora, necesită cunoaşterea în avans a cantităţii de
memorie necesare în fiecare situaţie.

                    H h
                     ig           Stiva

                                  Memorie liberá
                                  pentru alocare
                                  (heap)

                                  Variabile globale
                                  (statice)

                    Low           Program
                                       M m ria
                                        e o
                                 137
• 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
memoriei libere numită heap, plasată între programul util şi memoria
sa permanentă şi stivă. Se observă că stiva creşte în jos, iar
dimensiunea acesteia depinde de program.
      Un program cu multe funcţii recursive va folosi mult mai intens
stiva în comparaţie cu un program ce nu utilizeaza funcţii recursive,
aceasta deoarece adresele de retur şi variabilele locale corespunzătoare
acestor funcţii sunt salvate în stivă.

       Funcţiile malloc() şi free()
      Aceste funcţii formează sistemul de alocare dinamică a
memoriei în C şi fac parte din fisierul antet <stdlib.h>. Acestea
lucrează împreună şi utilizează zona de memorie liberă plasată între
codul program şi memoria sa permanentă (fixă) şi vârful stivei, în
scopul stabilirii şi menţinerii unei liste a variabilelor memorate. De
fiecare dată când se face o cerere de memorie, funcţia malloc() alocă o
parte din memoria rămasă liberă. De fiecare dată când se face un apel
de eliberare a memoriei, funcţia free() eliberează memorie sistemului.
     Declararea funcţiei malloc() se face sub forma:
           void *malloc (int numar_de_bytes);
Aceasta întoarce un pointer de tip void, ceea ce înseamnă că trebuie
utilizat un şablon explicit de tip atunci când pointerul returnat de
malloc() se atribuie unui pointer de un alt tip. Dacă apelul lui malloc()
se execută cu succes, malloc() va returna un pointer la primul byte al
zonei de memorie din heap ce a fost alocată. Dacă nu este suficientă
memorie pentru a satisfce cererea malloc(), apare un eşec şi malloc()
returnează NULL. Pentru determinarea exactă a numărului de bytes
necesari fiecărui tip de date, se poate folosi operatorul sizeof(). Prin
aceasta, programele pot deveni portabile pe o varietate de sisteme.
       Funcţia free() returnează sistemului memoria alocată anterior cu
malloc(). După eliberarea memoriei cu free(), aceasta se poate
reutiliza folosind un apel malloc().
       Declararea funcţiei free() se realizează sub forma:
                 free(void *p);



                                  138
Funcţia free() eliberează spaţiul indicat de p şi nu face nimic dacă p
este NULL. Parametrul actual p trebuie să fie un pointer la un spaţiu
alocat anterior cu malloc(), calloc() sau realloc().
Exemplu: Următorul program va aloca memorie pentru 40 de întregi,
va afişa valoarea acestora, după care eliberează zona, utilizând free():
 # include <stdio.h>
 # include <stdlib.h>
 void main(void) {
    int t, *p;
  p = (int *) malloc(40*sizeof(int));
  if (!p) printf("Out of memory n");
//Verificati neaparat daca p este un pointer corect
  else {
for (t=0; t<40; ++t) *(p + t) = t;
     for (t=0; t < 40; ++t)
printf("%d", *(p + t));
     free(p);
  }   }

       Funcţiile calloc() şi realloc()
      Funcţia calloc() alocă un bloc de n zone de memorie, fiecare de
dim octeţi şi setează la 0 zonele de memorie; funcţia returnează un
pointer la acel bloc (adresa primului octet din bloc). Declararea
funcţiei se face cu:
      void *calloc(unsigned int n, unsigned int dim);
      Funcţia realloc() primeşte un pointer la un bloc de memorie
alocat în prealabil (declarat pointer de tip void) şi redimensionează
zona alocată la dim octeţi (dacă este nevoie, se copiază vechiul
conţinut într-o altă zonă de memorie). Declararea funcţiei se face cu:
      void *realloc(void *ptr, unsigned int dim);

7.2.8. Pointeri la structuri
      Limbajul C recunoaşte pointerii la structuri în acelaşi mod în
care se recunoaşte pointerii la orice alt tip de variabilă.
      Declararea unui pointer la structură se face plasând operatorul *
în faţa numelui unei variabile structură. De exemplu, pentru structura
addr definită mai înainte, următoarea instrucţiune declară pe
addr_pointer ca pointer la o dată de acest tip :
                   struct addr *addr_pointer;
      O utilizare importantă a pointerilor la structură constă în
realizarea apelului prin adresă într-o funcţie.
                                 139
Când unei funcţii i se transmite ca argument un pointer la o
structură, calculatorul va salva şi va reface din stivă numai adresa
structurii, conducând astfel la cresterea vitezei de executare a
programului.
      Pentru a găsi adresa unei variabile structură, se plasează
operatorul & înaintea numelui variabilei structură. De exemplu,
dându-se următorul fragment :
     struct balanta{
     float balance;
     char name[80];
     } person;
     struct balanta *p;
/* se declara un pointer la structura */
atunci:
                            p = &person;
plasează adresa lui person în pointerul p. Pentru a referi elementul
balance, se va scrie:
                            (*p).balance
       Deoarece operatorul punct are prioritate mai mare decât
operatorul *, pentru o referire corectă a elementelor unei structuri
utilizând pointerii sunt necesare paranteze.
       Actualmente, pentru referirea unui element al unei variabile
structură dându-se un pointer la acea variabilă, există două metode:
Prima metodă utilizează referirea explicită a pointerului nume-
structură şi este considerată o metoda învechită (obsolete), iar a doua
metodă, modernă, utilizează operatorul săgeată -> (minus urmat de
mai mare).
Exemplu: Pentru a vedea cum se utilizează un pointer-struct,
examinăm următorul program care afişează ora, minutul şi secunda
utilizând un timer software.
# include <stdio.h>
void actualizeaza();
void afiseaza(), delay();
struct tm { /* se defineste structura tm */
int ore;
int minute;
int secunde;};
void main()
{struct tm time; // Structura time de tip tm
time.ore = 0;
time.minute = 0;
time.secunde = 0;
for (;;) {

                                 140
actualizeaza (&time);
afiseaza (&time); }}
void actualizeaza(t)
/*Versiunea 1- referirea explicita prin pointeri */
struct tm *t; {
(*t).secunde ++;
if ((*t).secunde == 60) {
     (*t).secunde = 0;
     (*t).minute ++;    }
if ((*t).minute == 60) {
     (*t).minute = 0;
     (*t).ore ++;}
if ((*t).ore == 24) (*t).ore = 0;
     delay();}
void afiseaza(t) // Se defineste functia afiseaza()
struct tm *t; {
printf ("%d : ", (*t).ore);
printf ("%d : ", (*t).minute);
printf ("%d ", (*t).secunde);
printf ("n");}
void delay() /* Se defineste functia delay() */
{    long int t;
     for (t = 1;t<140000000;++t);}
       Pentru ajustarea duratei întârzierii se poate modifica valoarea
contorului t al buclei.
      Se vede ca programul defineşte o structură globală numită tm,
dar nu declară variabilele. In interiorul funcţiei main() se declară
structura "time" şi se iniţializează cu 00:00:00. Programul transmite
adresa lui time funcţiilor actualizeaza() şi afiseaza(). În ambele
funcţii, argumentul acestora este declarat a fi un pointer-structură de
tip "tm". Referirea fiecărui element se face prin intermediul acestui
pointer. Elementele unei structuri pot fi referite utilizând în locul
operatorului ".", operatorul "->". De exemplu :
                        (*t).ore
este echivalent cu
                        t -> ore
Atunci programul de mai sus se poate rescrie sub forma:
# include <stdio.h>
void actualizeaza();
void afiseaza(), delay();
struct tm { /* se defineste structura tm */
int ore;
int minute;
int secunde;};


                                 141
void main()
{struct tm time; // Declara structura time de tip tm
time.ore = 0;
time.minute = 0;
time.secunde = 0;
for (;;) {
actualizeaza (&time);
afiseaza (&time); }}

void actualizeaza(t)
/*Versiunea 1- referirea explicita prin pointeri */
struct tm *t; {
t->secunde ++;
if (t->secunde == 60) {
     t->secunde = 0;
     t->minute ++; }
if (t->minute == 60) {
     t->minute = 0;
     t->ore ++;}
if (t->ore == 24) t->ore = 0;
     delay();}

void afiseaza(t) // Se defineste functia afiseaza()
struct tm *t; {
printf ("%d : ", t->ore);
printf ("%d : ", t->minute);
printf ("%d ", t->secunde);
printf ("n");}

void delay() /* Se defineşte funcţia delay() */
{    long int t;
     for (t = 1;t<140000000;++t);}

7.2.9. Structuri dinamice liniare de tip listă
      Structura a fost introdusă ca fiind o grupă (colecţie) ordonată de
date care pot fi de tipuri diferite şi care au anumite legături logice între
ele. Adesea, această grupă de date se repetă de mai multe ori. Se
ajunge astfel la noţiunea de tablou, ale cărui elemente sunt fiecare câte
o structură. Tabloul de date definit în acest fel este şi el de acest tip
nou, pe care îl mai numim şi tip structurat.
       După cum s-a remarcat, în exemplele de până acum am folosit
structuri de tip static. Static se referă la faptul că tablourile de structuri
au dimensiuni predefinite. Termenul structuri de date statice exprimă
ideea că putem modifica cu uşurinţă valorile componentelor dar este
foarte dificil să mărim numărul lor peste limita maximă declarată

                                    142
înainte de compilare. Mai mult, prin ştergerea unor elemente structură
dintr-un tablou de structuri obţinem goluri în tablou, pe care le putem
umple numai printr-o gestiune foarte precisă a poziţiilor din tablou.
Folosind pointeri la tabloul de structuri, este foarte posibil să indicăm
spre un element care a fost şters. Dacă dorim o reprezentare contiguă
în memorie, va trebui să compactăm (sau să defragmentăm) tabloul la
fiecare ştergere a unui element de tip structură. Mai mult, dacă dorim
să schimbăm ordinea în care s-au stocat elementele din tablou sau să
inserăm într-o poziţie intermediară un element nou, aceaste operaţii
devin foarte anevoioase.
      Într-un exemplu anterior am folosit secvenţa
# define SIZE 100
struct addr {
     char name[20];
     char street[30];
     char city[15];
     char state[10];
     unsigned int zip;
     } addr_info[SIZE];
Rezultă că am definit un tablou static cu 100 de elemente, cu numele
addr_info, la care fiecare element este o structură cu şablonul addr.
Dacă în această aplicaţie, chiar în timpul execuţiei programului,
constatăm că avem nevoie de mai mult de 100 de rezervări de
memorie, nu există nici o posibilitate de a mări tabloul fără a modifică
şi apoi recompila sursa programului. Tabloul trebuie redeclarat cu o
dimansiune mai mare (în cazul nostru prin #define SIZE 200, de
exemplu), apoi se recompilează programul şi abia apoi se execută cu
succes. Acest lucru prezentă două inconveniente (vezi [Mocanu,
2001]):
    1- Execuţia şi obţinerea rezultatelor sunt amânate şi produc
        întârzieri pentru modificarea programului sursă, recompilare
        şi reintroducerea datelor care fuseseră deja introduse până în
        momentul în care s-a constatat necesitatea măririi tabloului.
    2- Este posibil ca programul sursă să nu fie disponibil.
      Eliminarea neajunsurilor prezentate mai sus se face prin
implementarea listelor cu ajutorul unor structuri de date dinamice.
      Când apare necesitatea introducerii unui element în listă, se va
aloca dinamic spaţiu pentru respectivul element, se va crea elementul
prin înscrierea informaţiilor corespunzătoare şi se va lega în listă.


                                  143
Când un element este scos din listă spaţiul de memorie ocupat de
acesta va fi eliberat şi se vor reface legăturile.
        Structurile dinamice se construiesc prin legarea componentelor
structurii, numite variabile dinamice. O variabilă dinamică ce intră în
componenţa unor structuri de date dinamice (nod) prezintă în structura
sa două părţi:
          1.        Partea de informaţie (info) ce poate aparţine unui tip
                    simplu (int, char, float, double, etc.) conform cu
                    necesităţile problemei.
          2.        Partea de legătură (link) ce conţine cel puţin un
                    pointer de legătură (next) la o altă variabilă
                    dinamică, de obicei de acelaşi tip, ce realizează
                    înlănţuirea variabilelor dinamice în structuri de date
                    dinamice.
        Dintre structurile de date dinamice, cele mai simple şi mai
utilizate sunt listele. Lista este o structură liniară, de tipul unui tablou
unidimensional (vector), care are un prim element şi un ultim element.
Celelalte elemente au un predecesor şi un succesor. Elementele unei
liste se numesc noduri.
        La rândul lor, listele pot fi grupate în mai multe categorii, cele
mai importante fiind listele simplu înlănţuite, listele circulare şi listele
dublu legate.
        Principalele operaţii care se pot efectua asupra unei liste sunt:
crearea listei, adăugare/ştergere/modificare au unui element (nod),
accesul la un element şi ştergerea în totalitate a listei.
        Lista simplu înlănţuită este cel mai simplu tip de listă din
punctul de vedere al legării elementelor: legătura între elemente este
într-un singur sens, de la primul către ultimul. Există un nod pentru
care pointerul spre nodul următor este NULL. Acesta este ultimul nod
al listei simplu înlănţuite (sau simplu legate). De asemenea, există un
nod spre care nu pointează nici un alt nod, acesta fiin primul nod al
listei. O listă simplu înlănţuită poate fi identificată în mod unic prin
primul element al listei. Determinarea ultimului element se poate face
prin parcurgerea secvenţială a listei până la întâlnirea nodului cu
pointerul spre nodul următor cu valoarea NULL.




                                   144
info               info                                    info
   next               next                                    NULL
                     Listă simplă înlănţuită

      Listele circulare sunt un alt tip de liste pentru care relaţia de
precedenţă nu mai este liniară ci ultimul element pointează către
primul. Procedurile necesare pentru crearea şi utilizarea unei liste
circulare sunt extrem de asemănătoare cu cele de la liste liniare, cu
singura deosebire că ultimul element nu are adresa de pointer vid
(NULL) ci adresa primului element.

       info              info                               info
       next              next                               next


                          Listă    circulară

       Listele dublu legate sunt utile în rezolvarea unor probleme care
necesită parcurgeri frecvente ale structurilor dinamice în ambele
sensuri. Ele pot fi privite ca structuri dinamice ce combină două liste
liniare simplu înlănţuite ce partajează acelaşi câmp comun info, una
fiind imaginea în oglindă a celeilalte.

    info              info                                   info
    next              next                                   NULL
    NULL            previous                              previous
                        Listă dublu legată
       Pointerul next indică spre următorul nod, iar câmpul previous
indică spre câmpul anterior.
       Vom prezenta în continuare modul în care putem proiecta
funcţiile principale care acţionează asupra unei structuri dinamice.
Pentru aceasta vom utiliza două variabile globale de tip pointer, una
care pointează spre primul nod al listei iar cealaltă spre ultimul nod al
listei. Vom denumi aceste variabile first respectiv last.
Particularizările se vor face pe exemplul bazei de date construite
anterior.
       Tipul unui nod se declară în felul următor:
                                  145
General                           Particular
         struct tip_nod {                     struct addr {
         declaratii                          char name[20];
 struct tip_nod *next;                       char street[30];
         };                                  char city[15];
                                             char state[10];
                                             unsigned int zip;
                                             struct addr *next;
                                             };


      Atât la crearea unei liste cât şi la inserarea unui nod se va apela
funcţia malloc() pentru a rezerva spaţiu de memorie pentru un nod.
      Zona alocată prin intermediul funcţiei malloc() se poate elibera
folosind funcţia free().
      Propunem conceperea unui program asemănător cu programul
de exploatare a unei baze de date conceput anterior dar care să
folosească în locul unui tablou static o listă simplu înlănţuită.
Programul poate fi extins ulterior pentru structuri dinamice mai
complexe, care să folosească liste dublu înlănţuite sau circulare.
      Programul va avea următoarele facilităţi:
    1. Crearea unei liste simplu înlănţuite în memorie (pentru prima
         oară).
    2. Exploatarea unei liste simplu înlănţuite în memorie:
       2.1. Inserarea unor înregistrări (noduri):
              a) Inserarea unui nod înaintea primului nod al listei
              b) Inserarea unui nod înainte/după un nod intern al listei
              c) Inserarea unui nod după ultimul nod al listei
                  (adăugare la coada listei)
       2.2. Ştergerea unor înregistrări
              a) Ştergerea primului nod al listei
              b) Ştergerea unui nod intern al listei
              c) Ştergerea ultimului nod al listei
       3.           Salvarea din memorie pe disc a unei liste simplu
                 înlănţuite
       4.          Citirea de pe disc în memorie a bazei de date salvate
                 anterior
       5.          Afişarea pe ecran, înregistrare cu înregistrare, a bazei
                 de date conţinute în lista dinamică.
                                   146
Programul este prevăzut cu o intefaţă simplă prin care
utilizatorul poate alege dintre opţiunile pe care le are la dispoziţie.
Interfaţa este sub forma unui meniu din care, prin tastarea iniţialelor
comenzilor, acestea se lansează în execuţie.
       Vom descrie pe rând funcţiile care îndeplinesc sarcinile
enumerate mai sus. Pentru o mai bună proiectare a programului, vom
folosi atât modularizarea internă prin construirea mai multor funcţii
cât şi o modularizare externă prin izolarea programului principal de
restul funcţiilor care se folosesc.


                        Bază de date cu listă
                           simplu înlănţuită
                                                     Afişare pe ecran
                                                     a înregistrărilor
                              Interfaţa cu
                                                     din baza de date
                              utilizatorul



   Comenzi pentru citire/scriere
                                         Comenzi pentru procesarea
   pe disc
                                         listei dinamice

    Citire de      Salvarea pe
    pe disc în     disc a listei     Inserare              Ştergere
    lista          dinamice din
    dinamică       memorie
                                             - prima înregistrare
                                             - ultima înregistrare
                                             - înregistrare
                                               intermediară




Exemplu:
    Programul principal bd_main.c
# include "local.h"
void main() {
     char choice;
     for (; ;) { choice = menu();
     switch (choice) {
     case 'c' : create_list(); break;
     case 'l' : loadf_list(); break;
     case 's' : savef_list(); break;

                                   147
case   'd'   :   display_list(); break;
     case   'i'   :   insert(); break;
     case   'e'   :   erase(); break;
     case   'q'   :   exit(0); break;}}}
Fişierul header local.h este:
# include <stdio.h>
# include <ctype.h>
# include <string.h>
# include <stdlib.h>
typedef struct addr {
     char name[20];
     char street[30];
     char city[15];
     char state[10];
     unsigned int zip;
     struct addr *next;}TNOD;
TNOD *first, *last;
FILE *fp;
extern int create_list();
extern char menu();
extern void display_list(), insert(), erase();
extern int loadf_list(), savef_list();
extern TNOD *add_nod();
       Cu ajutorul acestui fişier header se realizează definirea
şablonului structurii addr şi apoi, prin comanda typedef, se defineşte
un nou tip de date numit TNOD. First şi last sunt pointeri la structuri
de acest tip iar fp este pointer la fişier (file pointer).
Cu extern se definesc prototipurile unor funcţii care nu se găsesc în
fişierul bd_main.c ci în fişierul bd_bib.c unde sunt colectate toate
funcţiile pe care le folosim. Acest fişier are la rândul său un fişier
header numit local1.h care conţine:
     # include <stdio.h>
     # include <ctype.h>
     # include <string.h>
     # include <stdlib.h>
     extern FILE *fp;
     typedef struct addr {
     char name[20];
     char street[30];
     char city[15];
     char state[10];
     unsigned int zip;
     struct addr *next;}TNOD;
     extern TNOD *first, *last;



                                 148
Prin cele două fişiere header cele două fişiere sursă bd_main.c
şi bd_bib.c se pot compila împreună, rezultând un singur fişier
executabil.

      Interfaţa cu utilizatorul
      Aceasta constă într-un meniu principal, care permite accesarea
funcţiilor principale, şi din două submeniuri pentru operaţiile de
ştergere şi inserare de înregistrări.
/* Functia menu() principala */
char menu() {
char s[5],ch;
do {
printf ("n(C)reate new listn");
printf ("(L)oad list from filen");
printf ("(S)ave to filen");
printf ("(D)isplay listn");
printf ("(I)nsert recordn");
printf ("(E)rasen");
printf ("(Q)uitn");
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 deleten");
printf ("(F)irst record deleten");
printf ("(L)ast record deleten");
printf ("(I)ntermediate deleten");
printf ("(Q)uitn");
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 insertn");
printf ("(L)ast record insertn");
printf ("(I)ntermediate insertn");
printf ("(Q)uitn");
printf (" Option ? ");

                                149
gets(s);ch=s[0];
} while (!strrchr("fliq",ch));
return tolower(ch); }

      Încărcarea bazei de date de pe disc
      Baza de date este înregistrată pe disc în fişierul maillist.dat care
se crează la prima salvare a listei dinamice din memorie. Funcţia
loadf_nod() citeşte o înregistrare (record) din fişier, returnând 1 în caz
de reuşită, -1 dacă se ajunge la sfârşitul fişierului şi 0 în cazul în care
există o eroare de citire de pe disc.
/* Functia loadf_nod() from file*/
int loadf_nod(TNOD *p)
{if (fread(p,sizeof(TNOD),1,fp)==1) return 1;
else if (feof(fp)) {fclose (fp); return -1;}
else {printf ("File read errorn"); return 0;}}

/* Functia loadf_list() from file */
int loadf_list() {
int n=sizeof(TNOD),i;
TNOD *p;
first=last=NULL;
if ((fp = fopen("maillist","rb")) == NULL) {
printf("Cannot open filen ");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
listan");
return 0;}
free(p);return i;}
       Funcţia loadf_list() alocă fiecare înregistrare citită de pe disc
unui nod al listei dinamice construite în memorie. În pointerii first şi
last se stochează în permanenţă adresele primei şi ultimei înregistrări
(nod sau structură).

      Salvarea bazei de date pe disc
      Reprezintă operaţiunea inversă celei de citire. Lista simplu
înlănţuită din memorie se salvează în întregime pe disc în fişierul
maillist.dat. Spre deosebire de funcţia loadf_list() care apela la funcţia

                                   150
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 filen ");return 0;}
p=first;
do {
if(fwrite(p, sizeof(TNOD), 1,fp) !=1)
{printf (" File write error n ");
fclose (fp);return 0;}
p=p->next;} while (p!=NULL);
fclose (fp);return 1;}

       Crearea unei liste simple înlănţuite
       La început variabilele first şi last au valoarea NULL, lista fiind
vidă. Crearea unei liste se face prin funcţia create_list. Ea returnează 0
pentru eroare şi 1 dacă operaţia reuşeşte. Prin această funcţie se
iniţializează o listă dinamică în memorie, fără a face o citire a unei
baze de date create anterior. Aceasta este prima funcţie care se
apelează când construim o bază de date nouă.
/* Functia create_list() new */
void create_list() {
int n=sizeof(TNOD);
char ch='c';
TNOD *p;
first=last=NULL;
while ((p=(TNOD *)malloc(n))!=NULL)
{p=add_nod(p);
if (first==NULL){ /* prima creare */
first=last=p;
first->next=NULL;}
else {
last->next=p;
last=p;
last->next=NULL;}
printf ("Exit? (x): ");
if ((ch=getchar())=='x') break;}
if (p==NULL){
printf("Memorie insuficienta pentru listan");
free(p);}}
      Afişarea listei dinamice din memorie



                                  151
Prin această operaţie, realizată de funcţia display_list(), se
afişează secvenţial toate înregistrările conţinute în nodurile listei
pornind de la prima şi terminând cu ultima.
// functie de afisare antet
void disp_antet() {
printf("n%20s","Name");
printf("%30s","Street");
printf("%15s","City");
printf("%10s","State");
printf("%5sn","Zip");}
// afisare o singura inregistrare (nod)
void disp_nod(TNOD *p) {
printf("%20s",p->name);
printf("%30s",p->street);
printf("%15s",p->city);
printf("%10s",p->state);
printf("%5d",p->zip);}
/* Functia display_list() */
void display_list()     {
TNOD *p;
disp_antet();
p=first;
if (p!=NULL)
do {
disp_nod(p);
p=p->next;
getchar();} while (p!=NULL);
else printf("Lista vida !n");}
       Funcţia display_list() apelează la funcţia disp_nod() care afişeză
o singură înregistrare. Dacă este nevoie de afişarea unui cap de tabel
(antet) se apelează la funcţia disp_antet().

       Inserarea unor noduri în lista dinamică
       Această operaţie presupune introducerea unor noduri
(înregistrări) fie la începutul listei înaintea primului nod, fie la sfârşitul
său (adăugare) după ultimul nod, fie între două noduri vecine nesituate
la extremităţi. Funcţiile listate mai jos realizează aceste sarcini.
// functia de inserare
void insert() {
char choice;
for (; ;) {
choice = menu_insert();
switch (choice) {
case 'f' : ins_first(); break;
case 'l' : ins_last(); break;

                                    152
case 'i' : ins_int(); break;
case 'q' : break;} break;}}
/* Functia insert_last() */
void ins_last() {
int n=sizeof(TNOD);
char ch='c',s[2];
TNOD *p;
/* ne pozitionam pe ultimul nod */
p=first;
while (p->next!=NULL) p=p->next;
// se creaza lista in memorie
while (ch!='x') {
p=(TNOD *)malloc(n);
last->next=p;
p=add_nod(p);
p->next=NULL;
last=p;
printf("Exit? (x): ");
gets(s);ch=s[0];}}
/* Functia insert_first() */
void ins_first() {
int n=sizeof(TNOD);
char ch='c',s[2];
TNOD *p;
while (ch!='x') {
p=(TNOD *)malloc(n);
p->next=first;
p=add_nod(p);
first=p;
printf("Exit? (x): ");
gets(s);ch=s[0];}}
// inserare dupa inregistrarea afisata
void ins_int() {
TNOD *p, *pi;
char ch='n', s[2];
disp_antet();
p=first;
while ((p!=NULL)&&(ch!='y'))
{ disp_nod(p);
printf("Here ? [y]: ");
gets(s);ch=s[0];
if (ch!='y') p=p->next;}
pi=(TNOD *)malloc(sizeof(TNOD));
pi=add_nod(pi);
pi->next=p->next;
p->next=pi;}
       La inserarea unui nod, este nevoie ca acesta să fie legat de cele
între care se introduce cu ajutorul pointerilor next. Dacă se doreşte ca
                                 153
nodul inserat să fie primul sau ultimul din listă, este nevoie să
modificăm pointerii globali first şi last.

       Ştergerea unor noduri din lista dinamică
       Nodurile pe care dorim să le ştergem pot fi interioare sau se pot
afla la extremităţi. În acest caz, este nevoie să modificăm pointerii
first şi last . În toate cazurile este nevoie să refacem legăturile distruse
după dispariţia prin ştergere a unor noduri. §tergerea nu este efectivă,
ci numai se refac legăturile şi se eliberează cu funcţia free() zona de
memorie alocată în prealabil (prin inserare sau creare) cu funcţia
malloc().
       Mai mult, avem opţiunea de a şterge întreaga listă din memorie
în scopul înlocuirii în totalitate a datelor conţinute în înregistrări.
// funcţia de ştergere
void erase() {
char choice;
for (; ;) {
choice = menu_del();
switch (choice) {
case 'e' : del_list(); break;
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);

                                   154
char ch='c',s[2];
TNOD *p;
/* ne pozitionam pe penultimul nod */
while (ch!='x') { p=first;
while (p->next!=last) p=p->next;
free(p->next);
p->next=NULL;
last=p;
printf("Deleted. Exit? (x): ");
gets(s);ch=s[0];}}
// stergere inregistrare intermediara
void del_int() {
TNOD *p, *pa, *pu;
char ch='n', s[2];
disp_antet();
pa=first;p=pa->next;pu=p->next;
while ((p!=last)&&(ch!='y'))
{ disp_nod(p);
printf("Delete ? [y]: ");
gets(s);ch=s[0];
if (ch='y') {pa->next=pu;
free(p);}
else {pa=p;p=pu;pu=pu->next;}}}
                       Capitolul VIII

                             FUNCŢII

8.1. Forma generală a unei funcţii
        Principalul mijloc prin care se pot modulariza programele C
este oferit de conceptul de funcţie (unele funcţii standard au fost deja
folosite pentru diverse operaţii). În C orice funcţie "întoarce"
(returnează), după apel, o valoare al cărui tip trebuie cunoscut. În
practică, însă, de multe ori valorile returnate de funcţii sunt ignorate.
Standardul limbajului C permite chiar declararea explicită a funcţiilor
care nu returnează valori ca fiind de tip void.
      În C o funcţie poate fi definită, declarată şi apelată.
Definirea unei funcţii C se realizează după următorul format general:

           tip   nume_funcţie (lista_parametri)
                 declaraţii_parametri
                 {
                                  155
declaraţii _locale
              instrucţiuni
}
sau, o formă mai nouă adoptată de ANSI-C în 1989:
            tip   nume_funcţie (declaraţii _parametri)
                  {
                  declaraţii _locale
                  instrucţiuni
                  }
       Tipul unei funcţii corespunde tipului valorii pe care funcţia o va
returna utilizând instrucţiunea return. Acesta poate fi un tip de bază
(char, int, float, double etc.) sau un tip derivat (pointer, structură
etc.).
       Dacă pentru o funcţie nu se specifică nici un tip, atunci, implicit
se consideră că funcţia întoarce o valoare întreagă.
       Lista parametrilor, lista_parametri, este o listă de nume de
variabile separate prin virgulă care vor primi valorile argumentelor în
momentul apelării acesteia. Tipul acestor parametri este descris fie în
paragraful declaraţii_parametri, fie direct în lista parametrilor. Lista
parametrilor este închisă între paranteze. Chiar dacă o funcţie nu are
parametri, parantezele nu trebuie să lipsească.
       De exemplu, funcţia max(a, b), care returnează cel mai mare
dintre numerele întregi a şi b, se poate defini sub forma:

 int max(a, b)         sau          int   max(int a, int b)
 int a, b;
 {                                  {
 if (a > b)                         if (a > b)
   return (a);                        return (a);
 else                               else
   return (b);                        return (b);
 }                                  }
       În cazul în care tipul parametrilor formali ai unei funcţii nu se
declară, atunci ei sunt consideraţi implicit de tip int. În cazul
compilatoarelor moderne, programul următor va genera două
avertismente.
Exemplu:
  # include <stdio.h>
  float max();     // Prototipul functiei max()
  float x;
  void main()
  { x = max(3, 4);
printf("max= %d",x);

                                  156
}
  float max(a, b)
  float a, b;
  { if (a > b)
    return (a);
else
  return (b);   }
     În urma compilării va rezulta:
Compiling...
test.c
C:cpp_examplestest.c(11):   warning   C4244:   'return':
conversion from 'int' to 'float', possible loss of data
C:cpp_examplestest.c(13):   warning   C4244:   'return':
conversion from 'int' to 'float', possible loss of data
test.obj - 0 error(s), 2 warning(s)
     Aceste avertismente sunt generate deoarece, la primul pas al
compilării, la parcurgerea liniei de declarare a funcţiei:
     float max(a,b)
parametrii formali a şi b sunt consideraţi de tip întreg.
      Programul, modificat ca mai jos, va duce la o compilare fără
probleme:
Exemplu:
       # include <stdio.h>
       float max();
       float x;
       void main()
       { x = max(3.2, 4.1);
     printf("max= %dn",x);
       }
       float max(float a, float b)
       { if (a > b)
         return (a);
     else
         return (b);  }
     Tot corect va rula şi programul:

  # include <stdio.h>
  int max();
  int x;
  void main()
  { x = max(-3,4);
printf("max= %dn",x);
  }
  int max(a,b)
  { if (a > b)
    return (a);

                                157
else
  return (b);       }
     Se observă că nu mai este nevoie de declararea explicită a
parametrilor formali de tip întreg a şi b.

8.2. Reîntoarcerea dintr-o funcţie
       Mai intâi precizăm că instrucţiunea return are două utilizări
importante:
♦ return determină ieşirea imediată din funcţia în care se află
   instrucţiunea şi reîntoarcerea în programul apelant;
♦ return poate fi folosită pentru a întoarce o valoare.
      Reîntoarcerea dintr-o funcţie în programul apelant (funcţia
apelantă) se poate face în două moduri:
     a) După parcurgerea codului corespunzător funcţiei se revine în
programul apelant la instrucţiunea imediat următoare. Exemplu:
Aceasta funcţie tipăreşte un şir în ordine inversă:
  # include <string.h>
  void afis_invers(char s[]);
  void main() {
    char s[10];
        printf("Introduceti un sir de            caracrere    de   la
tastatura (max 10)n");
    scanf("%s",s);
    afis_invers(s);    }
  void afis_invers(char s[])
  { register int t;
    for (t = strlen(s)-1; t >= 0; t--)
       printf("%c", s[t]);
    printf("n");   }
       b) Al doilea mod de întoarcere dintr-o funcţie se realizează
utilizând funcţia return. Funcţia return poate fi folosită fără nici o
valoare asociată.
Exemplu: Funcţia următoare afişează rezultatele ridicării unui număr
întreg la o putere întreagă pozitivă:
  power (baza, exp){
int baza, exp, i;
scanf("%d %d", &baza, &exp);
if (exp < 0) return; /* Functia se termina
       daca exp e negativ */
i = 1;
for (; exp; exp--)      i = baza * i;

                                158
printf (" Rezultatul este         %d n", i);       }
      Dacă exponentul exp este negativ, instrucţiunea return
determină terminarea funcţiei înainte ca sistemul să întâlnească }, dar
nu returnează nici o valoare. O funcţie poate conţine mai multe
instrucţiuni return, care pot simplifica anumite algoritme.

8.3. Valori returnate
       Toate funcţiile, cu excepţia celor daclarate a fi de tip void,
returnează o valoare. Această valoare este fie explicit specificată prin
return, fie este zero dacă nu se utilizează instrucţiunea return. Dacă o
funcţie este declară ta ca fiind de tip void, aceasta poate fi folosită în
orice expresie C.
       O funcţie nu poate fi membrul stâng într-o expresie de atribuire.
De exemplu, instrucţiunea: swap(x,y) = 100; este greşită.
Funcţiile care nu sunt de tip void se pot împărţi în trei categorii:
      1) Funcţii "pure" sunt funcţiile care efectuează operaţii asupra
argumentelor şi returnează o valoare de bază pe acea operaţie.
Exemplu: sqrt() şi sin() returnează respectiv radăcina pătrată şi sinusul
argumentului.
      2) A doua categorie de funcţii sunt cele care manipulează
informaţii şi întorc o valoare care arată reuşita sau eşecul acestei
manipulări. Un exemplu este fwrite() folosită pentru a scrie informaţii
pe disk. Dacă scrierea se face cu succes, fwrite() întoarce numărul de
octeţi înscrişi (ceruţi să se înscrie); orice altă valoare indică apariţia
unei erori.
      3) A treia categorie de funcţii sunt cele care nu trebuie să
întoarcă o valoare explicită. De exemplu, funcţia printf() întoarce
numărul de caractere tipărite, număr care, de obicei, nu are o utilizare
ulterioară.
       Dacă pentru o funcţie care returnează o valoare nu se specifică
o operaţie de atribuire, calculatorul va ignora valoarea returnată.
Exemplu: Considerăm următorul program care utilizează funcţia
mul():
  # include <stdio.h>
  mul();
  void main (void){
    int x, y, z;
    x = 10; y = 20;
z = mul(x, y); //- primul apel al lui mul()

                                  159
printf("%dn", mul(x,y)); /- al doilea apel al lui mul()
    mul(x,y);   //- al treilea apel al lui mul()   }
  mul(a,b)      // Se defineste functia mul()
  { return a*b; }
       Linia a atribuie valoarea returnată de mul() lui z. În linia b,
valoarea returnată nu este atribuită, dar aceasta este utilizată de
printf(). In linia c valoarea returnată este pierdută, deoarece nu se
atribuie nici unei variabile ce va fi utilizată în altă parte a programului.

8.4. Domeniul unei funcţii
      În C, fiecare funcţie este un bloc de instrucţiuni. Codul unei
funcţii este propriu acelei funcţii şi nu poate fi accesat (utilizat) prin
nici o instrucţiune din orice altă funcţie, cu excepţia instrucţiunii de
apel al acelei funcţii. (De exemplu, nu putem utiliza goto pentru a
realiza saltul dintr-o funcţie în mijlocul unei alte funcţii). Blocul de
instrucţiuni care descrie corpul unei funcţii este separat de restul
programului şi dacă acesta nu utilizează variabile globale sau date, el
nici nu poate afecta, nici nu va fi afectat de alte părţi ale programului.
      Codul şi datele care sunt definite în interiorul unei funcţii nu pot
interacţiona cu codul şi datele definite în altă funcţie, deoarece cele
două funcţii au scopuri diferite.
       În cadrul unei funcţii se deosebesc trei tipuri de variabile, astfel:
variabile locale, parametri formali şi variabile globale. Domeniul
unei funcţii determină modul în care alte părţi ale programului pot
avea acces la aceste trei tipuri de variabile stabilind şi durata de viaţă a
acestora.
8.4.1. Variabile locale
      Variabilele declarate în interiorul unei funcţii se numesc
variabile locale. Variabilele locale pot fi referite numai prin
instrucţiuni interioare blocului în care au fost daclarate aceste
variabile. Variabilele locale nu sunt cunoscute în afara blocului în care
au fost daclarate, domeniul lor limitându-se numai la acest bloc. Mai
exact, variabilele locale există numai pe durata execuţiei blocului de
cod în care acestea au fost daclarate; deci o variabilă locală este creată
la intrarea în blocul său şi distrusă la ieşire. De obicei, blocurile de
program în care se declară variabilele locale sunt funcţiile. Implicit, o
variabilă locală este auto, deci se stochează în memoria stivă. Ea

                                   160
poate fi declarată şi register, caz în care se stochează în regiştrii
interni ai microprocesorului sau poate fi declarată static, caz în care se
stochează în memoria de date sau statică, valoarea sa păstrându-se şi
la ieşirea din funcţie.
Exemplu:
         func1() {
           int x;
           x = 10; }
           func2() {
           int x;
           x = -199; }
       Aici variabila întreagă x este declarată de două ori, o dată în
func1() şi o dată în func2(). x din func1() nu are nici o legatură cu x
din func2(), deoarece fiecare x este cunoscut numai în blocul în
interiorul căruia a fost declarat.
       Limbajul C conţine cuvântul cheie auto, care poate fi folosit
pentru declararea de variabile locale. Cu toate acestea, întrucât C
presupune că toate variabilele neglobale sunt prin definiţie (implicit)
variabile locale, deci au atributul auto, acest cuvânt cheie nu se
utilizează.
       De obicei, variabilele locale utilizate în interiorul unei funcţii se
declară la începutul blocului de cod al acestei funcţii. Acest lucru nu
este neapărat necesar, deoarece o variabilă locală poate fi declarată
oriunde în interiorul blocului în care se utilizează, dar înainte de a fi
folosită.
Exemplu: Considerăm următoarea funcţie:
func (){
  char ch;
  printf (" Continuam (y / n) ? : ");
  ch = getche(); //Se preia optiunea de la tastatura
/* Daca raspunsul este yes */
  if (ch == 'y') {
  char s[80];
/* s se creeaza numai dupa intrarea in acest bloc */
  printf (" Introduceti numerele: n ");
  gets (s);
  prelucreaza_nr (s); /* Se prelucreaza numerele */
  } }
      Aici, func() creează variabila locală s la intrarea în blocul de cod
a lui if şi o distruge la ieşirea din acesta. Mai mult, s este cunoscută
numai în interiorul blocului if şi nu poate fi referită din altă parte,
chiar din altă parte a funcţiei func() care o conţine.

                                   161
Deoarece calculatorul creează şi distruge variabilele locale la
fiecare intrare şi ieşire din blocul în care acestea sunt daclarate,
conţinutul lor este pierdut o dată ce calculatorul părăseste blocul.
Astfel, variabilele locale nu pot reţine valorile lor după încheierea
apelului funcţiei.
8.4.2. Parametri formali
      Dacă o funcţie va folosi argumente, atunci aceasta trebuie să
declare variabilele care vor accepta (primi) valorile argumentelor.
Aceste variabile se numesc parametri formali ai funcţiei. Parametrii
formali ai funcţiei se comportă ca orice altă variabilă locală din
interiorul funcţiei.    Declararea parametrilor formali se face după
numele funcţiei şi înaintea corpului propriu-zis al funcţiei.
Exemplu:
/* Funcţia următoare întoarce 1 dacă caracterul                       c
aparţine şirului s altfel întoarce 0 */
  # include <stdio.h>
  int func (char s[10],char c) {
while (*s)
  if (*s == c) return 1;
  else s++;
return 0; }
  void main() {
     char s[10], c;
     scanf("%c %s", &c, &s);
     if (func(s, c))
   printf("Caracterul se afla in sirn");
     else printf("Caracterul NU se afla in sirn"); }
      Funcţia func() are doi parametri: s şi c. Aceasta funcţie întoarce
1 dacă caracterul c aparţine şirului şi 0 dacă c nu aparţine şirului.
      Precizăm că argumentele cu care se va apela funcţia trebuie să
aibă acelaşi tip cu parametrii formali declaraţi în funcţie. Aceşti
parametri formali pot fi utilizaţi ca orice altă variabilă locală.
      Un al doilea mod (recomandat de ANSI-C în 1989) de a declara
parametrii unei funcţii constă în declararea completă a fiecărui
parametru în interiorul parantezelor asociate funcţiei. De exemplu,
declararea parametrilor funcţiei func() de mai sus se poate face şi sub
forma:
     func (char *s, char c)
        {
     . . . . . . . . . . .
        }


                                 162
8.4.3. Variabile globale
      Spre deosebire de variabilele locale, variabilele globale sunt
cunoscute întregului modul program şi pot fi utilizate de orice parte a
programului. De asemenea, variabilele globale vor păstra valorile lor
pe durata execuţiei complete a programului, deci se stochează în
memoria statică.
      Variabilele globale se creează prin declararea lor în afara
oricărei funcţii (inclusiv main()). Variabilele globale pot fi plasate în
orice parte a programului, evident în afara oricărei funcţii, şi înainte de
prima lor utilizare. De obicei, variabilele globale se plasează la
începutul unui program, mai exact înaintea funcţiei main().
Exemplu:
  int count;    /* count este global */
  void main (void) {
     count = 100;
     func1(); }
  func1()      /* Se defineste functia func1() */
  { int temp;
  /* temp preia variabila globala count */
    temp = count;
    func2();
    printf ("count is %d",count); // Se va afisa 100
  }
  func2()      /* se defineste functia func2() */
  { int count;      /* count este local */
    for (count = 1; count < 10; count ++)
    printf ("%2dn" , count);    }
      Se observă că, deşi nici funcţia main() şi nici funcţia func1() nu
au declarat variabila count, ambele o folosesc. Funcţia func2() a
declarat o variabilă locală count. Când se referă la count, func2() se va
referi numai la variabila locală count şi nu la variabila globală count
declarată la începutul programului.
      Reamintim că, dacă o variabilă globală şi o variabilă locală au
acelaşi nume, toate referirile la numele variabilei în interiorul funcţiei
în care este declarată variabila locală se vor efectua numai asupra
variabilei locale şi nu vor avea nici un efect asupra variabilei globale.
Deci, o variabilă locală ascunde o variabilă globală.
      Variabilele globale sunt memorate într-o zonă fixă de memorie
destinată special acestui scop (memoria statică), rămânând în această
zonă pe parcursul întregii execuţii a programului.



                                   163
Variabilele declarate explicit extern sunt tot variabile globale,
dar accesibile nu numai modulului program în care au fost declarate,
ci şi tuturor modulelor în care au fost declarate de tip extern.
       Un alt exemplu util şi pentru înţelegerea lucrului cu pointeri este
următorul: se declară o variabilă globală x, şi apoi două variabile
locale cu acelaşi nume, x, care se vor “ascunde“ una pe cealaltă.
Pentru a vedea şi modul în care se stochează în memorie aceste
variabile, vom afişa şi locaţiile de memorie pentru fiecare tip de
variabilă x, precum şi pentru pointerul p corespunzător.
Reţinem că, în general, dacă:
      p = &x => p = &x = &(*p) = (&*)p = p
      *p = *(&x) = (*&)x = x
Faptul că * respectiv & sunt operaţiuni complementare (inverse una
celeilalte) se observă din relaţiile de mai sus, din care deducem că:
      &* = *& = identitate
      &(*z)= z;     // z este pointer
      *(&z) = z; // z este o variabila de un anume tip
(z este de alt tip în fiecare din egalităţile de mai sus, pointer sau
variabilă).
                     Adresa            Memoria
                                   .. .. .. .. .. .. .. .. .

                   &x=a re ax
                       d s                     x
                                     .. .. .. .. .. .. .. ..
                                     .. .. .. .. .. .. .. ..
                   &p d s p
                     =a re a                p =&x
                                     .. .. .. .. .. .. .. ..

                   &q d s q
                     =a re a               q=&p

     # include <stdio.h>
     int x = 34;        /* x este global */
     void main(void) {
     int *p = &x, *r;
     /* p este o variabila pointer catre un intreg */
     void **q;
printf("x=%d &x=%p *p=%d p=%p &p=%pn",x,&x, *p, p, &p);
     { int x;
       x = 1;
     /* Acest prim x este o variabila locala ce o ascunde
pe cea globala */

                                  164
p = &x;
printf("x=%d &x=%p *p=%d p=%p &p=%pn",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=%pn",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=%pn", q, *q, *r, &q); } }
În urma execuţiei programului, obţinem următorul rezultat:
x=34          &x=00426A54        *p=34      p=00426A54 &p=0065FDF4
x=1           &x=0065FDE8        *p=1       p=0065FDE8 &p=0065FDF4
x=2           &x=0065FDE4        *p=2       p=0065FDE4 &p=0065FDF4
q=0065FDF4    *q=0065FDE4        **q=2      &q=0065FDEC

      Prin declaraţia int *p = &x; variabila p este declarată
variabilă pointer către o dată de tip întreg şi este iniţializată cu adresa
variabilei spre punctează, anume x. Pointerul q este declarat ca un
pointer la un alt pointer.

 Denum. Tipul          Caracteristici ale   Caracteristici ale variabilei
 Variab. variabilei       variabilei              pointer ataşate
                      Adresa &x Valoare Adresa &p Valoarea p            *p
                                       x
  int x   globală     00426A54        34  0065FDF4       00426A54        34
  int x    locală     0065FDF0         1  0065FDF4      0065FDF0          1
  int x    locală     0065FDEC        2     0065FDF4     0065FDEC        2
    p      pointer      p = &x     *p = x
            local
    q      pointer      q = &p     *q = p      &q=           q=        **q
            local                           0065FDEC      0065FDF4     =2
       Acelaşi lucru se face pentru celelalte două variabile locale. Din
interpretarea rezultatelor de mai sus putem trage următoarele
concluzii. Spre exemplu,
   printf(“%d”,x); este echivalentă cu printf(“%d”,*p);
   scanf(“%d”,&x); este echivalentă cu scanf(“%d”,p);
dacă în prealabil s-a făcut atribuirea p = &x;
      Se mai observă cum pointerul p, care iniţial indica spre
variabila globală x, este încărcat cu adresa de memorie 4336176

                                    165
(00426A54 H), pe când în cazurile când indica variabilele locale x se
alocau adresele de memorie 6684140 (0065FDF0 H) şi 6684144
(0065FDEC H), adrese adiacente, la un interval de 4 octeţi, atât cât sunt
alocaţi pentru variabila de tip întreg. Se observă că variabila globală se
află într-o altă zonă de memorie decât variabilele locale.
       Modul de lucru cu pointerii este scos în evidenţă prin
instrucţiunile:
  q=&p;  // q retine adresa pointerului p
  r=*q;  // r retine valoarea de la adresa q
         // q=&p => r = *(&p) = p => *r = *p = x
  printf("q=%p *q=%p **q=%d &q=%pn",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:

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

      Când se doreşte realizarea produsului a oricăror doi întregi x şi y
se utilizează varianta generală a funcţiei, iar când se doreşte produsul
numai al variabilelor globale x şi y se utilizează varianta specifică.
Exemplu:
  # include <stdio.h>
  # include <string.h>
  int count; // count este global intregului program
  play();     // Prototipul pentru functia play()
  void main(void) {
char sir[80];
     // sir este variabila locala a functiei main()
printf("Introduceti un sir : n");
     gets(sir);
     play(sir);
  }
  play(char *p)         // Se declara functia play()
  { // p este local functiei play()
    if (!strcmp(p, "add")) {


                                  166
int a,b; /* a si b sunt locale blocului if                        din
interiorul functiei play()*/
       scanf ("%d %d", &a, &b);
       printf ("%d n", a+b);
    }
     // int a, b nu sunt cunoscute sau evidente aici
     else if(!strcmp(p,"beep")) printf("%c",7);   }

8.5. Apelul funcţiilor
      Apelul unei funcţii înseamnă referirea funcţiei, împreună cu
valorile actuale ale parametrilor formali, precum şi preluarea valorii
returnate, dacă este necesar. La apelul funcţiei, tipul argumentelor
trebuie să fie acelaşi cu cel al tipului parametrilor formali ai funcţiei.
      Dacă apar nepotriviri de tip (de exemplu, parametrul formal al
funcţiei este de tip int, iar apelul funcţiei foloseşte un argument de tip
float) de obicei, compilatorul C nu semnalizează eroare, dar rezultatul
poate fi incorect.
      În C transmiterea argumentelor de la funcţia apelantă spre
funcţia apelată se face prin valori sau prin adrese.
      a) În cazul transmiterii argumentului prin valoare, se realizează
copierea (atribuirea) valorilor fiecărui argument în (la) câte un
parametru formal al funcţiei apelate.
Exemplu: Se apelează o funcţie ce calculeaza pătratul unui număr
întreg.
  # include <stdio.h>
  square();        // Prototipul functiei sqrt()
  void main(void) {
     int t = 10;
     printf("%d %dn", t, square(t)); }
  square(x)        // Se declara functia sqrt()
     int x;
  { x = x*x; return(x); }
       Se observă că prin această metodă, schimbările survenite asupra
parametrului formal x nu afectează variabila utilizată pentru apelul
funcţiei (schimbările lui x nu modifică în nici un fel pe t).
      b) Dacă transmiterea argumentului se realizează prin adrese,
atunci la apelul funcţiei în loc de valori se folosesc adrese, iar în
definiţie, parametrii formali se declară ca pointeri.
      Exemplu: O funcţie swap() care schimbă valorile a două
variabile reale se poate defini astfel:

 void swap(float *x, float *y){
                                  167
float temp;
  temp = x; /* temp preia valoarea de la adresa x */
  *x = *y; /* valoarea de la adresa y este copiata
      la adresa x */
  y = temp;   /* la adresa y se copiaza valoarea
                 lui temp */
 }
       Se observă că parametrii formali ai funcţiei swap() sunt pointeri
la float. Programul următor arată modul de apel al acestei funcţii.
 # include <stdio.h>
 void swap(float *x,float *y);
 void main(void) {
   float x, y;      // x si y sunt de tip float
   scanf("%f,%f",&x,&y);/*Se introduc de la tastatura
            doua numere reale separate prin virgula*/
   printf ("x = %f, y = %f n ",x,y);
   swap(&x,&y); /*Se apeleaza functia swap() avand ca
                  argumente adresele lui x si y */
   printf("x = %f, y = %f n ",x,y);
 }
      Prin &x şi &y, programul transferă adresele lui x şi y funcţiei
swap() şi nu valorile lui x şi y.
      Un apel combinat, valoare-referinţă este prezentat în exemplul
următor:
  # include <stdio.h>
  void f();
  void main (void) {
     int x = 1, y = 1;
     printf("x = %d, y = %d n", x, y);
     f(x,&y);}
  void f(int val, int *ref) {
     val++;
     (*ref)++;
     printf("x = %d, y = %d n",val,*ref); }

8.6. Apelul funcţiilor având ca argumente tablouri
      Când se apelează o funcţie având ca argument un tablou,
acesteia i se va transmite un pointer la primul element al tabloului.
Reamintim că în C numele unui tablou fără nici un indice este un
pointer la primul element al tabloului. Deci, un argument de tipul T[ ]
(vector de tipul T) va fi convertit la T * (pointer de tipul T). Rezultă că
vectorii, ca şi tablourile multidimensionale, nu pot fi transmise prin
valoare. Aceasta înseamnă că declararea parametrului formal trebuie

                                   168
să fie compatibilă tipului pointer. Există trei moduri de a declara un
parametru care va primi un pointer la un tablou (vector).
      a) Parametrul formal poate fi declarat ca un tablou, astfel:
  # include <stdio.h>
  display();       // Prototipul functiei display()
  void main(void) {
int v[10], i;
     for (i = 0; i < 10; ++i) v[i] = i;
     display(v);}
  display(num)     // Se defineste functia display()
     int num[10];
  { int i;
     for (i = 0; i < 10; i++) printf ("%d", num[i]);                  }
Chiar dacă acest program declară parametrul num ca pe un vector de
10 întregi, compilatorul C va converti automat pe num la un pointer la
întreg, deoarece parametrul nu poate primi întregul tablou (vector).
      b) O a doua cale de a declara un parametru vector (tablou),
constă în a specifica parametrul ca pe un vector fără dimensiune:
  display(int num[])
{ int i;
  for(i = 0; i < 10; i++) printf ("%d",num[i]); }
Aceasta funcţie declară pe num ca fiind un vector de întregi cu
dimensiune necunoscută. Deoarece limbajul C nu verifică
dimensiunea vectorilor, dimensiunea actuală a vectorului este
irelevantă ca parametru al funcţiei. §i de aceasta dată, num va fi
convertit la un pointer la întreg.
      c) Ultima metodă prin care se poate declara un parametru tablou
este ca pointer, astfel:
  display(int *num)
{ int i;
  for (i = 0; i < 10; i++)          printf ("%d", num[i]); }
Limbajul C permite acest tip de declaraţie deoarece putem indexa
orice pointer utilizând [].
      Toate cele trei metode de declarare a unui tablou ca parametru
produc acelaşi rezultat: un pointer. Cu toate acestea, un element al
unui tablou folosit ca argument al unei funcţii va fi tratat ca orice altă
variabilă. Astfel, programul de mai sus poate fi rescris sub forma:
  # include <stdio.h>
  void main (void) {
int v[10], i;
for (i = 0; i < 10; i++) v[i] = i;
     for (i = 0; i < 10; i++) display (v[i]);               }

                                  169
display(int num)           { printf ("%d" , num); }
     De data aceasta, parametrul din display() este de tip int,
deoarece programul utilizează numai valoarea elementului tabloului.
Exemplu: Vom prezenta un program pentru afişarea tuturor numerelor
prime cuprinse între două limite întregi. Programul principal apelează
două funcţii: nr_prim() returnează 1 dacă argumentul său întreg este
prim şi 0 dacă nu este prim; numerele prime sunt grupate într-un
vector, care se afişează ulterior cu funcţia display().
  # include <stdio.h>
  int nr_prim();        // Se declara prototipul
  void display();
  void main (void) {
int a,b,i,j,v[80];
printf("Introduceti limitele: ");
scanf("%d %d", &a, &b);
j = 0;
for (i=a; i<=b; ++i)
if (nr_prim(i)) {v[j]=i; ++j;}
display(v,j);}

  int nr_prim(int i)    // Decide daca i este prim
  { int j;
for (j=2; j<=i/2; j++)
     if (i%j==0) return 0;
     return 1; }
  void display(int *p, int j) /* Tipareste un vector
     de intregi */
  { int i;
for (i=0; i<j; ++i) printf("%d ", p[i]); }
       Din cele de mai sus, trebuie reţinut că atunci când un tablou se
utilizează ca argument al unei funcţii, calculatorul transmite funcţiei
adresa de început a tabloului. Acest lucru constituie o excepţie a
limbajului C în convenţia de transmitere a parametrilor prin valoare.
Astfel, codul funcţiei poate acţiona asupra conţinutului tabloului şi îl
poate altera.
Exemplu: Programul următor va modifica conţinutul vectorului sir
din funcţia main() după apelul funcţiei afis_litmari().
  # include <stdio.h>
  # include <ctype.h>
  afis_litmari();
  void main (void) {
     char sir[80];
     gets(sir);
     afis_litmari(sir);
printf("n%sn",sir);}
                                 170
// Se defineste functia afis_litmari()
  afis_litmari(char *s)
  { register int t;
     for (t = 0; s[t]; ++t) {
  // Se modifica continutul sirului sir
     s[t] = toupper(s[t]);
     printf("%c",s[t]);}}
Rezultatul rulării programului va fi:
                     abcdefghijklmnoprstuvxyzw
                     ABCDEFGHIJKLMNOPRSTUVXYZW
                     ABCDEFGHIJKLMNOPRSTUVXYZW
Exemplu: Dacă nu dorim să se întâmple acest lucru, programul de mai
sus se poate rescrie sub forma:
  # include <stdio.h>
  # include <ctype.h>
  afis_litmari();
  void main (void) {
     char sir[80];
     gets(sir);
     afis_litmari(sir);
printf("n%sn",sir);}
  afis_litmari(char *s)
  /* Se defineste functia afis_litmari() */
{ register int t;
  for (t = 0; s[t]; ++t) printf("%c",toupper(s[t])); } //
Nu se modifica continutul sirului sir
Rezultatul rulării va fi de această dată:
                    abcbdefghijklmnoprstuvxyzw
                    ABCBDEFGHIJKLMNOPRSTUVXYZW
                    abcbdefghijklmnoprstuvxyzw
      În aceasta variantă conţinutul tabloului ramâne nemodificat,
deoarece programul nu-i schimbă valoarea.
       Un exemplu clasic de transmitere a tablourilor într-o funcţie îl
constituie funcţia gets() din biblioteca C standard. Prezentăm o
variantă simplificată a acestei funcţii numită xgets().
  xgets(s)
     char *s; {
     char ch;
     int t;
     for (t = 0; t < 80; ++t) {
       ch = getchar();
       switch (ch) {
          case 'n' :
               s[t] = '0'; /* terminare sir            */
               return;

                                   171
case 'b':
                if (t > 0) t--;
                break;
           default:
                s[t] = ch; } }
       s[80] ='0';    }
      Funcţia xgets() trebuie apelată având ca argument un tablou de
caractere, care, prin definiţie, este un pointer la caracter. Numărul
caracterelor introduse de la tastatură, prin funcţia for este de 80. Dacă
se introduc mai mult de 80 de caractere, funcţia se încheie cu return.
Dacă se introduce un spaţiu, contorul t este redus cu 1. Când se apasă
CR, xgets() introduce terminatorul de şir.

8.7. Argumentele argc şi argv ale funcţiei main()
       Singurele argumente pe care le poate avea funcţia main() sunt
argv şi argc.
       Parametrul argc conţine numărul argumentelor din linia de
comandă şi este un întreg. Întotdeauna acesta va fi cel puţin 1,
deoarece numele programului este codificat ca primul argument.
       Parametrul argv este un pointer la un tablou de pointeri la
caractere. Fiecare element din acest tablou indică spre un argument
linie_comanda. Toate argumentele linie_comanda sunt şiruri.
Exemplu: Următorul program arată modul de utilizare al
argumentelor linie_comanda şi va afişa Hello urmat de numele
dumneavoastră, dacă vă introduceţi numele, imediat după numele
programului:
 # include <stdio.h>
 void main (argc, argv)     // Numele programului
  int argc;
  char *argv[];
 {if (argc != 2) {
  printf (" Ati uitat sa va introduceti numele n");
  return; }
  printf ("Hello %s !", argv[1]); }
      Dacă acest program se numeşte ARG_LC.C şi numele
dumneavoastră este DAN, atunci, pentru a executa programul, în linia
de comandă, veţi tipări ARG_LC DAN. Ieşirea programului va fi
Hello DAN !.
      Argumentele linie_comanda trebuie separate prin spaţiu sau
TAB şi nu prin virgulă, sau;.

                                  172
Parametrul argv[] se declară, de obicei, sub forma char
*argv[]; şi reprezintă un tablou de lungime nedeterminată, mai precis
reprezintă un tablou de pointeri. Accesul la elementele lui argv[] se
realizează prin indexarea acestuia, astfel: argv[0] va indica spre primul
şir, care este întotdeauna numele programului; argv[1] va indica spre
primul argument etc. Evitaţi folosirea sa fără paranteze, adică char
*argv.
      Următorul program numit "nrinvers" numără invers de la o
valoare specificată prin linia de comandă şi transmite un beep când
ajunge la zero. Precizăm că programul converteşte primul argument,
care conţine numărul la un întreg folosind funcţia standard atoi().
Dacă şirul "display" apare ca al doilea argument_comanda, programul
va afişa, de asemenea, numărul introdus pe ecran.
  # include <stdio.h>
  # include <string.h>
  # include <stdlib.h>
  void main(int argc, char *argv[])   /* nrinvers */
  { int disp, count;
    if (argc < 2) {
      printf ("Trebuie introdusa lungimea numarului
  in linia de comandan");
    return; }
  if (argc==3 && !strcmp(argv[2], "display"))
     disp = 1;
  else disp = 0;
  for (count = atoi(argv[1]); count; --count)
if (disp) printf("%d ",count);
  printf("%c",7);       /* Se emite un beep */   }
Observaţie: Dacă în linia de comandă nu se specifică nici un
argument, programul va afişa un mesaj de eroare.

8.8. Funcţii care returnează valori neîntregi
       Dacă nu se declară explicit tipul funcţiei, compilatorul C o va
declara implicit de tip int. Pentru ca funcţia să întoarcă un tip diferit
de int trebuie, pe de o parte, să se precizeze un specificator de tip al
funcţiei şi apoi să se identifice tipul funcţiei înaintea apelului acesteia.
      O funcţie C poate returna orice tip de dată din C. Declararea
tipului este similară celei de la declararea tipului variabilei:
specificatorul de tip ce precede funcţia indică tipul datei întoarse de
funcţie. Pentru a nu se genera incertitudini datorate dimensiunii de
reprezentare, înainte de utilizarea unei funcţii ce întoarce tipuri
                                   173
neîntregi, tipul acestei funcţii trebuie făcut cunoscut programului.
Acest lucru este necesar deoarece compilatorul nu cunoaşte tipul datei
întoarse de funcţie şi acesta va genera un cod greşit pentru apelul
funcţiei. Pentru a preveni această greşeală, la începutul programului se
plasează o formă specială de declaraţie care să precizeze
compilatorului ce tip de valoare va returna acea funcţie. Această
declaraţie se numeşte prototipul funcţiei.
Exemplu:
  # include <stdio.h>
  float sum();//Prototipul functiei (fara parametri)
  void main(void) {
     float first = 123.23, second = 99.09;
     printf("%fn", sum(first, second)); }
  float sum(float a, float b)    // Definitie sum()
  //Se returnează o valoare de tip float
 { return a+b; }

      Instructiunea de declarare a tipului funcţiei are forma generală:
              specificator_de_tip        nume_funcţie();
      Chiar dacă funcţia are argumente, în declaraţia de tip acestea nu
se precizează (cu excepţia compilatoarelor mai vechi de 1989, care nu
sunt adaptate la cerinţele ANSI-C).
       Dacă o funcţie ce a fost declarată int întoarce un caracter,
calculatorul converteşte valoarea caracter într-un întreg. Deoarece
conversiile caracter --> întreg-caracter sunt fără probleme, o funcţie ce
întoarce un caracter poate fi definită ca o funcţie care întoarce un
întreg.

8.9. Returnarea pointerilor
       Deşi funcţiile care întorc pointeri se manipulează în acelaşi mod
ca şi celelalte tipuri de funcţii, trebuie discutate câteva concepte
importante.
       Pointerii la variabile nu sunt nici întregi, nici întregi fără semn.
Pointerii sunt adrese de memorie a anumitor tipuri de date: int, char,
float, double, struct etc.
       Motivul acestei distincţii este legat de faptul că atunci când se
prelucrează un pointer aritmetic, această prelucrare este dependentă de
tipul datei indirectate: de exemplu, dacă este increment un pointer la


                                   174
int, noua valoare (a adresei) va fi cu 4 mai mare faţă de valoarea
anterioară.
      În general, când un pointer este incrementat sau decrementat,
acesta va indica către elementul următor, respectiv anterior, din
tabloul pe care îl indirectează. De exemplu, dacă funcţia int f()
returnează un întreg, atunci funcţia int *f(), returnează un pointer la o
dată de tip int.
      Deoarece fiecare tip de date poate avea lungimi diferite,
compilatorul trebuie "să ştie" ce tip de dată este indirectată de pointer,
pentru a-l face să indice corect spre următorul element.
Exemplu: Programul următor conţine o funcţie care întoarce un
pointer într-un şir în locul în care calculatorul găseşte o coincidenţă de
caractere.
 char *match (char c, char *s)
 {int count;
count = 0;
while (c!=s[count] && s[count] != '0')                count ++;
return (&s[count]);     }
       Funcţia match() va încerca să întoarcă un pointer la locul
(elementul) din şir unde calculatorul găseşte prima coincidenţă cu
caracterul c. Dacă nu se găseste nici o coincidenţă, funcţia va întoarce
un pointer la terminatorul de şir (NULL). Un scurt program ce ar
utiliza funcţia match() este următorul :
  # include <stdio.h>
  # 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 %sn",p);}
 else
    printf("Nu exista nici o coincidenta"); }
       Acest program citeşte mai întâi un şir şi apoi un caracter. În
cazul în care caracterul este în şir, atunci se tipăreste şirul din punctul
unde se află caracterul, altfel se tipăreşte "Nu există nici o
coincidenţă".
                                   175
Un caz interesant este oferit de funcţiile care returnează pointeri
către şiruri de caractere. O astfel de funcţie se declară sub forma:
                                 char *f()
Exemplu: Programul următor arată modul în care se defineşte, se
declară şi se apelează o astfel de funcţie.
  # include <stdio.h>
  void main(void) {
  int i;
  char *NumeLuna();
  scanf("%d", &i);
  printf("%s n ", NumeLuna(i)); }
  char *NumeLuna(nr)
  int nr;
  { char *luna[]=
    {"Eroare", "Ianuarie", "Februarie", "Martie",
     "Aprilie", "Mai", "Iunie","Iulie", "August",
     "Septembrie", "Octombrie", "Noiembrie",
     "Decembrie"};
 return ((nr>=1) && (nr <= 12)?luna[nr]:luna[0]);                  }
       Un alt exemplu va fi reprezentat de o variantă a funcţiei strcpy()
din string.h , deci o funcţie care copiază caracterele din şirul s2 în şirul
s1. Rezultatul se găseşte în s1.
/* Vom incepe cu definirea functiei strcpy2() si apoi vom declara
programul principal main(). In acest fel nu mai este necesara declararea
prototipului functiei strcpy2() */
  # include <stdio.h>
char *strcpy2(register char s1[],register char s2[])
{ char *s0 = s1;   // Echivalent: char *s0;s0 = s1;
  while ((*s1++ = *s2++) != '0');
  return s0; }
void main()
{ char *sir1,*sir2;
  puts(“Introduceti un sir de la tatstatura n”);
  gets(sir2);
  puts(strcpy2(sir1,sir2));}
       Se observă cum se iniţializează s0 cu s1. Bucla while atribuie
valorile (caracterele) (*s2) în locaţiile indicate de pointerul s1,
incrementând ambii pointeri simultan. Bucla se termină la întâlnirea
caracterului null, care se copiază şi el. Valoarea returnată, s0, reţine
adresa de început a şirului s1.
       Un ultim exemplu îl constituie un program de manipulare a
unor matrici. Acest program realizează citirea unei matrici,


                                   176
transpunerea sa şi respectiv afişarea rezultatului apelând la funcţiile
cit_mat(), trans_mat() şi tip_mat().
  # include <stdio.h>
  # define DIM_MAX 10
  void cit_mat();
  void tip_mat();
  int *trans_mat();
void main()
{ int a[DIM_MAX][DIM_MAX], dim_lin, dim_col, *p;
     printf("Introduceti dimensiunea matricei                [dim_lin
dim_col]: ");
  scanf("%d %d", &dim_lin, &dim_col);
  cit_mat(a, dim_lin, dim_col);
  tip_mat(a, dim_lin, dim_col);
  p = trans_mat(a, dim_lin, dim_col);
  tip_mat(a, dim_col, dim_lin); }

void cit_mat(int p[][DIM_MAX], int lin, int col)
{ int i, j;
  for (i=0; i<lin; i++)
for (j=0; j<col; j++)
{    printf("x[%d][%d] = ", i, j);
     scanf("%d", &p[i][j]); } }

void tip_mat(int p[][DIM_MAX], int lin, int col)
{ int i, j;
  for (i=0; i<lin; i++)
  { for (j=0; j<col; j++)
     printf("%d ",p[i][j]);
printf("n"); }
  printf("n"); }

int *trans_mat(int p[][DIM_MAX], int lin, int col)
{ int t, i, j;
  for (i=0; i<lin; i++)
for (j=i; j<col; j++)
{t = p[i][j], p[i][j] = p[j][i], p[j][i] = t;}
  return p; }


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



                                 177
Tipul void este utilizat pentru declararea implicită a acelor
funcţii care nu întorc o valoare. void se utilizează şi ca tip de bază
pentru pointeri la un obiect de tip necunoscut.
Exemplu:
void f(void)      /* functia f nu intoarce o valoare */
void *pv          /* pointer la un obiect necunoscut */
      Utilizând void se impiedică folosirea funcţiilor ce nu întorc o
valoare în orice expresie, prevenind astfel o întrebuinţare greşită a
acestora. De exemplu, funcţia afis_vertical() afişează pe ecran
argumentul său şir, vertical, şi întrucât nu întoarce nici o valoare, este
declarată de tip void.
  void afis_vertical (sir)
     char *sir;
  { while (*sir)
     printf ("%c n", *sir ++);           }
       Înaintea utilizării acestei funcţii sau oricărei alte funcţii de tip
void, aceasta trebuie declarată. Dacă nu se declară, compilatorul C
consideră că aceasta întoarce o valoare întreagă. Astfel, modul de
utilizare al funcţiei afis_vertical() este următorul:
  # include <stdio.h>
  void afis_vertical(); // Se declara prototipul
  void main (void) {
afis_vertical ("Hello "); }
  void afis_vertical (sir)
char *sir;
  { while (*sir)
    printf ("%c n", *sir ++); }


8.11. Funcţii prototip
      După cum se ştie, înaintea folosirii unei funcţii care întoarce o
altă valoare decât int, aceasta trebuie definită.
Funcţiile prototip au fost adăugate de comitetul ANSI-C standard.
Declararea unei funcţii prototip se face conform următorului format:
            tip   nume_funcţie (tip_arg1, tip_arg2,...)
unde:      tip = tipul valorii întoarse de funcţie;
     tip_arg1, tip_arg2,... = tipurile argumentelor funcţiei.
Exemplu: Programul următor va determina compilatorul să emită un
mesaj de eroare sau de avertisment deoarece acesta încearcă să


                                   178
apeleze funcţia func() având al doilea argument de tip int, în loc de
float, cum a fost declarat în funcţia func():
  #include <stdio.h>
  void func(int, float);//Prototipul functiei func()
  void main (void) {
  int x, y;
  x = 10; y = 10;
  func (x, y); }   /* Se afiseaza o nepotrivire */
  void func (x, y)   /* Parametrii functiei sunt: */
  int x;      /* x - intreg */
  float y;    /* y - real */
  { printf ("%f", y/(float) x); }

      Funcţiile prototip se folosesc pentru a ajuta compilatorul în
prima fază în care funcţiile utilizate sunt definite după programul
principal. Acesta trebuie înştiinţat asupra tipul datei returnat de o
funcţie pentru a aloca corect memoria. Dacă funcţiile sunt declarate
înaintea liniei de program main(), funcţiile prototip nu mai sunt
necesare, deoarece compilatorul extrage informaţia despre funcţii în
momentul în care parcurge corpul definiţiei lor.
       Spre exemplu, programul de mai sus se poate scrie şi sub forma
următoare, în care nu vom mai avea o declaraţie de funcţie prototip:
  #include <stdio.h>
  void func (x, y) /* Parametrii functiei sunt: */
    int x;              /* x - intreg */
    float y;            /* y - real */
  { printf ("%f", y/(float) x); }
  void main (void) {
    int x, y;
    x = 10; y = 10;
    func (x, y); } /* Nu se afiseaza nepotrivire */
       Utilizând recomandările ANSI-C din 1989, programul de mai
sus se poate scrie mai compact:
  #include <stdio.h>
  void func (int x, float y) /* Parametrii formali
         includ tipul */
  { printf ("%f", y/(float) x); }
  void main (void) {
    int x, y;
    x = 10; y = 10;
func (x, y); }//afisare avertisment de conversie
sau, folosind funcţia prototip:
  #include <stdio.h>
  void func();          /* Declarare prototip fara
        parametri formali ! */

                                  179
void main (void) {
    int x, y;
    x = 10; y = 10;
    func (x, y); }
  void func (int x, float y) /* Parametrii formali
    includ tipul */
  { printf ("%f", y/(float) x); }
       În ultimul program am evidenţiat o recomandare care simplifică
efortul de programare în sensul că în linia de declarare a
prototipurilor funcţiilor folosite este necesar să definim tipul funcţiei
nu şi tipul parametrilor formali. Compilatorul se informează despre
tipul parametrilor formali la parcurgerea corpului definiţiei funcţiei.
       Din cele de mai sus se observă ca folosirea funcţiilor prototip
ne ajută la verificarea corectitudinii programelor, deoarece nu este
permisă apelarea unei funcţii cu alte tipuri de argumente, decât tipul
celor declarate.

8.12. Funcţii recursive
      Funcţiile C pot fi recursive, adică se pot autoapela direct sau
indirect. O funcţie este recursivă dacă o instrucţiune din corpul
funcţiei este o instrucţiune de apel al aceleiaşi funcţii. Uneori o funcţie
recursivă se numeşte şi funcţie circulară.
      Un exemplu de o astfel de funcţie este funcţia factorial() care
determină factorialul unui număr. Această funcţie se poate organiza
recursiv, ştiind că: n! = n(n-1)!. Având în vedere 0!=1, această funcţie
se poate organiza astfel:
      long factorial (int n) {
           if (n == 0) return (1);
           else
           return (n * factorial(n-1)); }
Programul de apel al acestei funcţii se scrie sub forma:
  # include <stdio.h>
  void main (void) {
    int n;
    printf("Introduceti un numar intreg : n");
    scanf ("%d, &n);
    printf ("(%d) ! = %ld",n,factorial(n)); }
  long factorial (int n) {
    if (n == 0) return (1);
    else
    return (n * factorial(n-1)); }


                                   180
Observaţie: Atunci când o funcţie se autoapelează recursiv, la fiecare
apel al funcţiei se memorează pe stivă atât valorile parametrilor
actuali, cât şi întregul set de variabile dinamice definite în cadrul
funcţiei. Din aceasta cauză stiva trebuie dimensionată corespunzător.
O variantă echivalentă a funcţiei factorial() definită mai sus ar fi
următoarea:
     long factorial(int n) {
          if (!n) return (1);
          else
          return (n * factorial (n-1)); }
Un alt exemplu interesant este dat de şirul lui Fibonacci, în care
termenul general an este dat de relaţia de recurenţă: an = an-1+ an-2 ,
unde a0 = 0 şi a1=1. Codul funcţiei poate fi scris sub forma:
     long fib(int n) {
          if (n == 0)
          return (0);
          else if (n == 1)
               return (1);
          else return (fib(n-1)+fib(n-2)); }
      Utilizarea recursivităţii poate să nu conducă la o reducere a
memoriei necesare, atât timp cât stiva este folosită intens pentru
fiecare apel recursiv. De asemenea şi execuţia programului poate să nu
fie mai rapidă. Dar codul recursiv este mai compact şi de multe ori
mai uşor de scris şi înţeles decât echivalentul său recursiv.
Recursivitatea este convenabilă în mod deosebit pentru operaţii pe
structuri de date definite recursiv, cum sunt listele, arborii etc.

8.13. Clase de memorare (specificatori sau
atribute)
      Din punct de vedere al execuţiei programelor C, memoria
computerului este organizată în trei zone, cunoscute în mod tradiţional
ca segment de memorie text, segment de memorie statică (sau de date)
şi segment de memorie dinamică (sau stivă).

Segment de memorie text Conţine instrucţiunile programului, deci
(memorie program)       programul executabil
Segment de memorie Conţine variabilele a caror locaţie rămâne fixă
statică

                                 181
Segment de memorie            Conţine variabilele de tip automatic,
dinamică (de tip stivă)       parametrii funcţiilor şi apelurile şi retururile
                              de/din funcţii
În tabelul următor se prezintă caracteristicile claselor de memorie.

Specificator   Domeniul de                  Durata de viaţă    Plasament
de memorie     vizibilitate al variabilei   a variabilei

 Auto        Local fiecărei funcţii    Temporară, numai        În memoria
 (automatic) sau fiecărui bloc în care când se execută         dinamică (de
             a fost declarată          funcţia în care este    tip stivă)
                                       declarată
 Register    Local fiecărei funcţii    Temporară, numai        În regiştrii
 (registru)                            când se execută         microproceso
                                       funcţia în care este    rului
                                       declarată
 Extern      Global, de către toate    Permanentă, pe          În memoria
             funcţiile dintr-un fişier parcursul rulării       statică
             sursă sau din mai multe programului
             fişiere sursă             executabil
 Static      Local sau global          Permanentă, cât         In memoria
                                       timp este in            statică
                                       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
declară este activată) sau permanentă (există pe toată durata de
execuţie a programului).
       Dacă tipul se declară explicit în declaratorul variabilei, clasa de
memorie se determină prin specificatorul de clasă de memorie şi prin
locul unde se face declaraţia (în interiorul unei funcţii sau înaintea
oricărei funcţii).
       Variabilele cele mai folosite sunt cele care sunt declarate în
blocurile aparţinând unei funcţii. Aceste variabile sunt de două feluri:
       - auto, aşa cum sunt marea majoritate a variabilelor declarate
            numai prin tip. Acesta este un specificator implicit, deci nu

                                      182
este nevoie să îl invocăm la declararea variabilelor.
             Variabilele auto sunt plasate în memoria stivă, iar domeniul
             de vizibilitate este local, numai pentru funcţia în care
             variabila a fost declarată, iar din punctul de vedere al
             duratei de viaţă sunt volatile, adică dispar din memoria stivă
             după reîntoarcerea din funcţie.
        - static, declarate explicit. Variabilele static sunt plasate în
             memoria statică, iar domeniul de vizibilitate este local,
             numai pentru funcţia în care variabila a fost declarată, iar
             din punctul de vedere al duratei de viaţă sunt permanente,
             adică nu dispar din memoria statică după reîntoarcerea din
             funcţie.
        - register, declarate explicit. Variabilele register sunt identice
             cu cele auto cu excepţia faptului că stocarea nu are loc în
             memoria stivă ci în regiştrii interni ai microprocesorului în
             scopul sporirii vitezei de execuţie a programelor.
        - extern, declarate explicit. Din punct de vedere al modulării
             unor programe, este preferabil să divizăm un program
             complex în mai multe module program care se leagă în faza
             de link-editare. O variabilă declarată extern într-un modul
             program semnalează compilatorului faptul că această
             variabilă a fost declarată într-un alt modul. Aceste variabile
             sunt globale, adică sunt văzute de orice modul de program
             şi de orice funcţie componentă a unui modul program.
             Stocarea are loc în memoria statică iar durata de viaţă este
             permanentă, pe toată perioada execuţiei programului.
        Iniţializarea unei variabile static diferă de cea a unei variabile
auto prin aceea că iniţializarea este făcută o singură dată, la încărcarea
programului în memorie şi lansarea sa în execuţie. După prima
iniţializare, o variabilă static nu mai poate fi reiniţializată (de
exemplu, la un nou apel al funcţiei în care este iniţializată).
        Iată ilustrat acest lucru prin două exemple simple. Se tipăreşte,
cu ajutorul funcţiei receip(), un număr care este mai întâi iniţializat cu
valoarea 1 şi returnat incrementat cu o unitate. În cazul folosirii
variabilelor implicite locale auto se rulează programul:
      # include <stdio.h>
      short receip();
      void main(){
      printf("First = %dn",receip());
      printf("Second = %dn",receip());}
                                   183
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 = %dn",receip());
       printf("Second = %dn",receip());}
       short receip()
       {   static short number = 1;
           return number++;}
şi obţinem rezultatul
       First = 1
       Second = 2
       Limbajul C suportă patru specificatori ai claselor de memorare:
auto, extern, static, register. Aceştia precizează modul de memorare
al variabilelor care îi urmează. Specificatorii de memorare preced
restul declaraţiei unei variabile care capătă forma generală:
  specificator_de_memorare specificator_de_tip lista_de_variabile;
       Specificatorul auto
       Se foloseşte pentru a declară varibilele locale (obiectele dintr-un
bloc). Totuşi, utilizarea acestuia este foarte rară, deoarece, implicit,
variabilele locale au clasa de memorare automată (auto).
       Specificatorul extern
       Se utilizează pentru a face cunoscute anumite variabile globale
declarare într-un modul de program (fişier) altor module de programe
(fişiere) cu care se va lega primul pentru a alcătui programul complet.
Exemplu:

       Modulul 1                    Modulul 2
       int x, y;                    extern int x, y;
       char ch;                     extern char ch;
       main()                       func22()
          {                         {
        . . . . . .                 x = y / 10;
        . . . . . . }               }
                                    func23()
       func1()                      {
       { x = 123;          }         y = 10;      }
                                  184
Dacă o variabilă globală este utilizată într-una sau mai multe funcţii
din modulul în care acestea au fost declarate nu este necesară
utilizarea opţiunii extern. Dacă compilatorul găseşte o variabilă ce n-a
fost declarată, atunci acesta o va căuta automat printre variabilele
globale.
Exemplu:
int first, last; /* variabile globale */
main(    ) {
extern int first;}//folosire optionala declaratie extern

      Variabile statice
      Obiectele statice pot fi locale unui bloc sau externe tuturor
blocurilor, dar în ambele situaţii ele îşi păstrează valoarea la ieşirea şi
intrarea, din sau în funcţii.
      Variabile locale statice
        Când cuvântul cheie static se aplică unei variabile locale,
compilatorul C crează pentru aceasta o memorie permanentă în acelaşi
mod ca şi pentru o variabilă globală. Diferenţa dintre o variabilă locală
statică şi o variabilă globală este că variabila locală statică este
cunoscută numai în interiorul blocului în care a fost declarată.
      Un exemplu de funcţie care necesită o astfel de variabilă este un
generator de numere care produce un nou număr pe baza celui
anterior.
      serie()
      {static int numar_serie;
      numar_serie = numar_serie + 23;
      return (numar_serie);   }
       Se observă că variabila numar_serie continuă să existe între
două apeluri ale funcţiei serie() fără ca aceasta să fi fost declarată ca
variabilă globală. Se observă de asemenea că funcţia nu atribuie nici o
valoare iniţială variabilei numar_serie, ceea ce înseamnă că valoarea
inţială a acesteia este 0.

      Variabile globale statice
      O variabilă globală cu atributul static este o variabilă globală
cunoscută numai în modulul în care a fost declarată. Deci o variabilă
globală statică nu poate fi cunoscută şi nici modificată din alte module
de program (alte fişiere).
Exemplu:
static int numar_serie;
//var. globala este cunoscuta numai in acest fisier
                                   185
serie() {
     numar_serie = numar_serie + 23;
     return (numar_serie); }
/* initializarea variabilei numar_serie */
     serie_start(val_init)
     int val_init;{
     numar_serie = val_init; }
      Apelul funcţiei serie_start() cu o valoare intreagă iniţializează
seria generatoare de numere, după care apelul funcţiei serie() va
genera următorul număr din serie.

     Specificatorul register
     Acest modificator se aplică numai variabilei de tip int şi char.
Acest specificator precizează faptul ca variabilele declarate cu acest
modificator sunt des utilizate şi se pastrează de obicei în registrele
CPU. Specificatorul register nu se aplica variabilelor globale.
Exemplu: Aceasta funcţie calculeaza me pentru întregi :
           int_putere (m, e)
           int m;
           register int e; {
           register int temp;
           temp = 1;
           for (; e; e--) temp * = m;
           return temp; }
      În acest exemplu au fost declarate ca variabile registru atât e cât
şi temp.   De obicei utilizarea unei variabile registru conduce la
micşorarea timpului de execuţie al unui program.
Exemplu :
     unsigned int i;
     unsigned int delay;
     main() {
     register unsigned int j;
     long t;
     t = time ('0');
     for (delay = 0; delay < 10; delay++)
for (i = 0; i < 64000; i++);
printf("Timpul pentru bucla non-registru: %ldn" ,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);}




                                  186
Dacă se execută acest program se va găsi că timpul de execuţie
al buclei registru este aproximativ jumătate din timpul de execuţie al
variabilei non-registru.

8.14. Pointeri la funcţii
         Într-un fel, un pointer funcţie este un nou tip de dată. Chiar
dacă o funcţie nu este o variabilă, aceasta are o locaţie fizică în
memorie care poate fi atribuită unui pointer. Adresa atribuită
pointerului este punctul de intrare al funcţiei. Acest pointer poate fi
utilizat în locul numelui funcţiei. Pointerul permite de asemenea
funcţiilor să fie pasate (trecute) ca argumente în alte funcţii.
       Adresa unei funcţii se obţine utilizând numele funcţiei fără nici o
paranteză sau argumente (ca în cazul tablourilor).
Exemplu:
# include <stdio.h>
# include <ctype.h>
void check();
int strcmp();      /* prototip functie */
void main() {
char s1[80], s2[80];
void *p; /* p preia adresa de intrare a functiei */
p = strcmp;
gets(s1);
gets(s2);
check(s1,s2,p); }

void check (char *a, char *b, int (*cmp) ())
/* cu int (*cmp) () se declara un pointer functie */
        { printf (" Test de egalitate n ");
          if (!(*cmp) (a,b)) printf ("Egaln");
          else printf ("Neegaln"); }
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.
Apelul funcţiei check() se face având ca parametri doi pointeri la
caracter şi un pointer funcţie.
      Instrucţiunea : (*cmp)(a, b)

                                  187
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 ("Egaln");
          else printf("Neegaln"); }
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 ("Egaln");
          else     printf ("Neegaln"); }




                         Capitolul IX

                                 188
PREPROCESAREA

      Un preprocesor C realizează substituirea macrodefiniţiilor, o
serie de calcule adiţionale şi incluziunea fişierelor. Liniile programului
sursă care încep cu "#", precedat eventual de spaţiu comunică cu
preprocesorul. Sintaxa acestor linii este independentă de restul
limbajului; pot apare oriunde în program şi pot avea efect care se
menţine (indiferent de domeniul în care apare) până la sfârşitul unitatii
de translatare.
      Preprocesorul C conţine următoarele directive:
                  #if                #include
                  #ifdef             #define
                  #ifndef            #undef
                  #else              #line
                  #elif              #error
                  #pragma

9.1. Directive uzuale
       Directiva #define se utilizează pentru a defini un identificator
şi un şir (o secvenţă) pe care compilatorul îl va atribui identificatorului
de fiecare dată când îl întâlneşte în textul sursă.
      Forma generală a directivei #define este :
                   #define identificator şir
Se observă că directiva #define nu conţine "; ".
      În secvenţa de atomi lexicali "şir" nu trebuie să apară spaţiu.
Linia se termina cu CR.
Exemplu:
             # define TRUE           1
             # define FALSE          0
Când în program se întâlnesc numele TRUE şi FALSE, acestea se vor
înlocui cu 1, respectiv 0.
      Instrucţiunea:
            printf ("%d %d %d", FALSE, TRUE, TRUE + 5);
va afişa pe ecran 0 1 6.
      După definirea unui macro_name, acesta poate fi folosit pentru
definirea altui macro_name.

                                   189
Exemplu:
# define ONE 1          /* Se defineşte macro_name ONE */
# define TWO ONE + ONE /* Se utilizează macro_name ONE */
# define THREE     ONE + TWO
      Deci această macrodefiniţie realizează simpla înlocuire a unui
identificator cu şirul asociat. Dacă, de exemplu, se doreşte definirea
unui mesaj standard de eroare, se poate scrie:
      # define E_MS "standard error on input n"
      . . . . . . . . . .
      printf (E_MS);
      Ultima linie este echivalentă cu :
      printf ("standard error on inputn");
atunci când în program se întâlneşte identificatorul E_MS.
Exemplu: Programul următor nu va afişa "this is a test", deoarece
argumentul lui printf() nu este închis între ghilimele.
      # define XYZ this is a test
      . . . . . . . . . . . . . . . . .
      printf ("XYZ");
Se va afişa XYZ şi nu "this is a test".
      Dacă şirul este prea lung şi nu încape pe o linie, acesta se scrie
sub forma:
      # define LONG_STRING " this is a very long 
        string that is used as an example "
Observaţie: De obicei macro_names sunt definite cu litere mari.
     Directiva #define poate fi folosită şi pentru precizarea
dimensiunii unui tablou, astfel:
            # define MAX_SIZE 100
            float balance [ MAX_SIZE ];
     Macro_nameul dintr-o          directiva   #define   poate   avea   şi
argumente. Exemplu :
      # define MIN (a ,b) a < b ? a : b
      void main() {
      int x, y;
      x = 10; y = 20;
      printf("Numarul mai mic este: %d ", MIN (x,y)); }
După substituirea lui
MIN(a, b) în care a = x şi b = y,
instrucţiunea printf() va arata astfel :
       printf("Numarul mai mic este: %d",(x<y)?x:y);


      Directiva #error


                                    190
Directiva #error forţează compilatorul să stopeze operaţia de
compilare când această este intilnita în program. Este utilizata în
primul rind pentru depanarea programelor. Forma generală a directivei
este: #error mesaj_de_eroare
      Aceasta linie determină procesorul să scrie mesajul de eroare şi
să termine compilarea.

       Directiva # include
       Directiva # include comandă compilatorului să includă în
fişierul ce conţine directiva #include un alt fişier sursă al cărui nume
este specificat în directivă. Formele directivei sunt :
            # include <nume_fisier>
            # include "nume_fisier"
       Prima formă se referă la fişiere header (cu extensia .h) care se
găsesc în subdirectorul include din fiecare mediu de programare C, iar
cea de-a doua la fişiere header create în directorul de lucru al
utilizatorului (directorul curent). Directivele # include pot fi folosite şi
una în interiorul celeilalte.

9.2. Directive pentru compilare condiţionată
      Limbajul C conţine câteva directive care ne permit să compilăm
selectiv anumite porţiuni de program.

      Directivele #if, #else, #elif şi #endif
      Forma generală a lui #if este:
            #if expresie_constanta
            secventa de instructiuni
            #endif
     Dacă expresie_constanta este adevărată, compilatorul va
compila fragmentul de cod cuprins între #if şi #endif, iar dacă
expresie_constanta este falsă, compilatorul va sări peste acest bloc.
Exemplu:
      #define MAX 100
      void main() {
      #if MAX > 99
      printf("Se compileaza pentru tablouri > 99n");
      #endif }
Observaţie: Expresie_constanta se evaluează în timpul compilării. De
aceea, aceasta trebuie să conţină numai variabile constante definite

                                   191
anterior utilizării lor. Expresie_constanta nu trebuie să conţină
operatorul sizeof.
      Directiva #else lucrează similar cu instrucţiunea else
determinând o alternativă de compilare. Exemplu :
      # define MAX 10
      void main() {
      #if MAX > 99
      printf("Se compileaza pentru tablouri > 99n");
      #else
      printf("Se compileaza pentru tablouri < 99n");
      #endif }
      Deoarece MAX = 10, compilatorul va compila numai codul
cuprins între #else şi #endif, deci va tipări mesajul :
                  Se compilează pentru tablouri < 99
      Directiva #elif inlocuieşte "else if" şi este utilizată pentru
realizarea opţiunilor multiple de tip if / else / if utilizate la compilare.
Forma generală a directivelor #if , #elif, #endif este:
            #if expresie
                 Secventa_de_instructiuni
            #elif expresie_1
                 Secventa_de_instructiuni_1
            #elif expresie_2
                 Secventa_de_instructiuni_2
            . . . . . . . . . . . . . .
            #elif expresie_N
                 Secventa_de_instructiuni_N
            #endif
      Dacă      "expresie"       este     adevărată     se      compilează
"Secventa_de_instructiuni" şi nu se mai tastează nici o altă expresie
#elif. Dacă "expresie" este falsă, compilatorul verifică următoarele
expresii în serie, compilându-se "Secventa_de_instructiuni_i",
corespunzatoare primei "expresie_i" adevărată, i = 1, 2, . . . , N.
      Directivele #if şi #elif se pot include unele pe altele. Exemplu:
      #if  MAX > 100
           #if VERSIUNE_SERIALA
           int port = 198;
      #elif
           int port = 200;
           #endif
      #else
           char out_buffer[100];
      #endif

      Directivele #ifdef şi #ifndef
                                   192
O altă metodă de compilare condiţionată utilizează directivele
#ifdef şi #ifndef, care înseamnă "if defined" şi "if not defined".
      Forma generală a lui #ifdef este :
           #ifdef macro_name
           Secventa_de_instructiuni
           #endif
     Dacă anterior apariţiei secvenţei de mai sus s-a definit un
macro_name printr-o directivă #define, compilatorul va compila
"Secventa_de_instructiuni" dintre #ifdef şi #endif.
     Forma generală a lui #ifndef este:
           #ifndef macro_name
                Secventa_de_instructiuni
           #endif
      Dacă macro_name nu este definit prîntr-o directivă #define,
atunci se va compila blocul dintre #ifndef şi #endif.
      Atât #ifdef, cât şi #ifndef pot utiliza un #else, dar nu #elif.
Exemplu:
     # define TOM 10
     void main() {
           #ifdef TOM
                printf("Hello TOM !n");
           #else
                printf("Hello anyone !n");
           #endif
           #ifndef JERY
                printf ("Jery not defined n");
           #endif }
      Programul va afişa: Hello TOM !   şi JERY not defined.
Dacă nu s-a definit TOM, atunci programul va afişa : Hello anyone !.

      Directiva #undef
      Se utilizează pentru a anula definiţia unui macro_name definit
printr-o directivă #define.
      Exemplu:
           #define LENGTH 100
           #define WIDTH 100
           char array[LENGTH][WIDTH];
           #undef LENGTH
           #undef WIDTH
       Acest program defineşte atât LENGTH, cât şi WIDTH până se
întâlneşte directiva #undef.
      Principala utilizare a lui #undef este de a permite localizarea
unui macro_name numai în anumite secţiuni ale programului.
                                193
Directiva #line
     O linie cu una din formele:
           #line numar "nume_fiaier"
           #line numar
determină compilatorul să considere, din motive de diagnosticare a
erorilor, că numărul de linie al urmatoarei linii din programul sursă
este dat de "număr", iar numele fişierului în care se află programul
sursă este dat de "nume_fişier". Dacă lipseste "nume_fişier",
programul sursă se află în fişierul curent.
Exemplu:
Următoarea secvenţă face ca numărul de linie să înceapă cu 100.
     # line 100
     void main()    /* linia 100 */
          {    /* linia 101 */
     printf ("%dn" , __LINE__); /* linia 102 */
          }
      Instructiunea printf() va afişa valoarea 102 deoarece această
reprezintă a treia linie în program, după instrucţiunea #line 100.

     Directiva #pragma
     O linie de control de forma:
                 #pragma    nume
determină compilatorul să realizeze o acţiune care depinde de modul
de implementare al directivei #pragma. "nume" este numele acţiunii
#pragma dorite.
Limbajul C defineşte două instrucţiuni #pragma: warn şi inline.
       Directiva warn determină compilatorul să emită un mesaj de
avertisment. Forma generală a lui warn este :
                 #pragma warn       mesaj
unde "mesaj" este unul din mesajele de avertisment definite în C.
     Forma generală a directivei inline este :
                 #pragma inline
şi avertizează compilatorul că programul sursă conţine şi cod în
limbajul de asamblare.
      Directiva vidă
      O linie de forma:
            #
nu are nici un efect.

     Macro_names (macrosimboluri) predefinite
                                   194
Limbajul C conţine câţiva identificatori predefiniţi, care la
compilare se expandează pentru a produce informaţii speciale.
      Aceştia sunt:
      __LINE__          o constanta zecimală care conţine numele liniei
sursă curente.
      __FILE__ un şir care conţine numele fişierului care se
compilează.
      __DATA__          un şir care conţine data compilării sub forma
luna/zi/an.
      __TIME__          un şir care conţine ora compilării sub form:
hh:mm:ss
      __STDC__          constanta 1. Acest identificator este 1 numai în
implementarile standard; dacă constanta este orice alt număr, atunci
implementarea este diferită de cea standard.
      Aceste macrosimboluri, împreună cu simbolurile definite cu
#define nu pot fi redefinite.

9.3. Modularizarea programelor
       De obicei (vezi [Mocanu, 2001] programele C constau din
fişiere sursă unice, cu excepţia fişierelor header. Un singur fişier sursă
este în general suficient în cazul programelor mici.
       Modularizarea internă este un principiu de bază al programării
în C şi constă în utilizarea pe scară largă a funcţiilor definite de
utilizator. Scrierea programului principal (main) se concentrează mai
ales pe apelul acestor funcţii. În cazul în care corpul de definiţie al
funcţiilor utilizator se află după corpul de definiţie main, este necesar
ca să declarăm prototipul funcţiilor utilizate de main() pentru a
informa corect compilatorul despre tipul variabilelor returnate de
funcţii. O altă modalitate este aceea de a defini funcţiile utilizator
înaintea funcţiei principale main(), caz în care nu mai sunt necesare
prototipurile. Programul este modularizat cu ajutorul funcţiilor prin
divizarea sa în nuclee funcţionale. Acestea pot fi comparate cu nişte
mici piese de lego cu ajutorul cărora se pot construi ulterior structuri
(programe) foarte complexe.
       Pe scurt, modularizarea internă constă în descompunerea
sarcinii globale a unui program în funcţii de prelucrare distincte.
       O funcţie de uz general este o funcţie care poate fi folosită într-
o varietate de situaţii şi, probabil, de către mai mulţi utilizatori. Este
                                  195
de preferat ca aceste funcţii de uz general să nu primească informaţii
prin intermediul unor variabile globale ci prin intermediul
parametrilor. Sporeşte astfel foarte mult flexibilitatea în folosirea
acestor funcţii.
      Modularizarea externă constă în divizarea unui program foarte
complex în mai multe subprograme. Astfel, un fişier sursă mai mare se
poate diviza în două sau mai multe fişiere sursă mai mici. Evident,
aceste fişiere sunt strâns legate între ele pentru a forma în final un tot
unitar echivalent cu programul complex iniţial (dinainte de divizare).




      În figura de mai sus se prezintă un ecran al Microsoft Visual C+
+ din MSDN 6.0
      Noţiunea cea mai cuprinzătoare este aceea de Workspace (spaţiu
de lucru) care cuprinde în esenţă o colecţie de proiecte corelate şi
prelucrabile împreună. Un workspace cuprinde unul sau mai multe
Projects (proiecte) dintre care numai unul este principal şi restul sunt
subordonate (subprojects). Fiecare proiect este compus la rândul său
din mai multe fişiere, de acelaşi tip sau de tipuri diferite.
      Prezentarea exhaustivă a organizării acestui mediu de dezvoltare
a aplicaţiilor C/C++ este un demers în afara prezentei lucrări. Ceea ce

                                  196
merită să subliniem este faptul că, în cadrul cel mai întâlnit, anume un
workspace care include un singur project, acest proiect conţine mai
ales fişiere sursă şi fişiere de tip header. Aceste fişiere se numesc
module. Modulul principal este fişierul care conţine funcţia principală
main(). Celelalte fişiere sursă, dacă există, se numesc module
secundare. De obicei, cu fiecare modul secundar se asociază un fişier
header propriu separat. Acest fişier header trebuie să conţină toate
directivele şi declaraţiile de variabile necesare pentru o corectă
compilare separată a modulului cu care se asociază.
      Pentru a exemplifica cele de mai sus, vom modulariza un
exemplu anterior, anume al unei baze de date simple.
      Workspace-ul va conţine un singur project, care va conţine
următoarele 4 fişiere:          bd_main.c       local.h
                                  bd_bib.c         local1.h
bd_main.c (bd - bază de date) este modulul principal, cel care conţine
funcţia main(). El are asociat fişierul header local.h.
În mod asemănător, local1.h este fişierul header asociat cu modulul
secundar bd_bib.c (bib - bibliotecă) care conţine toate definiţiile
funcţiilor utilizator. Conţinutul lor este prezentat în continuare.
       Modulul bd_main.c este:
# include "local.h"
void main() {
char choice;
init_list();
for (; ;) {
     choice = menu();
     switch (choice) {
     case 'e' : enter();     break;
     case 'd' : display(); break;
     case 's' : save(); break;
     case 'l' : load(); break;
     case 'q' : exit(); }}}
      Fişierul local.h conţine:
# include <stdio.h>
# include <ctype.h>
# include <string.h>
# define SIZE 100
struct addr {
     char name[20];
     char street[30];
     char city[15];
     char state[10];
     unsigned int zip;

                                   197
} addr_info[SIZE];
FILE *fp;
extern void init_list();
extern char menu();
extern void enter(),save(),load();
extern void display(),exit();
    Modulul bd_bib.c este:
# include "local1.h"
/* Functia init_list() */
void init_list() {
register int t;
for (t = 0; t < SIZE; t++)
*addr_info[t].name = '0';    }

/* Functia menu() */
char menu() {
     char s[5],ch;
     do {
          printf ("(E)ntern");
          printf ("(D)isplayn");
          printf ("(L)oadn");
          printf ("(S)aven");
          printf ("(Q)uitn");
          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);}


                             198
/* Functia save() */
void save() {
register int i;
if ((fp = fopen("maillist", "wb")) == NULL) {
printf (" Cannot open filen ");
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 filen ");
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 errorn"); }

/* Functia display() */
void display(){
register int t;
printf("n%20s","Name");
printf("%30s","Street");
printf("%15s","City");
printf("%10s","State");
printf("%5sn","Zip");
for (t=0;t<SIZE;t++) {
if (*addr_info[t].name!='0') {
printf("%20s",addr_info[t].name);
printf("%30s",addr_info[t].street);
printf("%15s",addr_info[t].city);
printf("%10s",addr_info[t].state);
printf("%5d",addr_info[t].zip);
getchar();}}}
    Fişierul local1.h conţine:
# include <stdio.h>
# include <ctype.h>
# include <string.h>
# define SIZE 100
extern struct addr {
     char name[20];
     char street[30];
     char city[15];

                                 199
char state[10];
     unsigned int zip;
     } addr_info[SIZE];
extern FILE *fp;
       Se poate verifica cum fiecare modul în parte este compilabil fără
erori, iar la link-editare nu se semnalează, de asemenea, erori.
                          Capitolul X

                     INTRĂRI/IEŞIRI

10.1. Funcţii de intrare şi ieşire - stdio.h
       Limbajul C nu dispune de instrucţiuni de intrare/ieşire. Aceste
operaţii se realizează prin intermediul unor funcţii din biblioteca
standard a limbajului C. Aceste funcţii pot fi aplicate în mod eficient
la o gamă largă de aplicaţii datorită multiplelor facilităţi pe care le
oferă. De asemenea, ele asigură o bună portabilitate a programelor,
fiind implementate într-o formă compatibilă pe toate sistemele de
operare.
       O altă caracteristică a limbajului C constă în faptul că nu există
un sistem de gestionare a fişierelor care să permită organizări de date,
aşa cum în alte limbaje există fişiere cu organizare relativă sau
indexată. În limbajul C toate fişierele sunt tratate ca o înşiruire de
octeţi, neexistând structuri de date specifice care să se aplice acestor
fişiere. Programatorul poate să interpreteze datele după cum doreşte
Prin urmare, prin scrierea/citirea datelor se scriu/citesc un număr de
octeţi fără o interpretare specifică.
       Funcţiile de intrare/ieşire, tipurile şi macrodefiniţiile din
"stdio.h" reprezintă aproape o treime din bibliotecă.
       În C, intrarea standard respectiv ieşirea standard sunt în mod
implicit reprezentate de terminalul de la care s-a lansat programul.
       Prin fişier înţelegem o mulţime ordonată de elemente păstrate
pe diferite suporturi. Aceste elemente se numesc înregistrări.
Suporturile cele mai des utilizate sunt cele magnetice (floppy sau
harddiscuri). Ele se mai numesc suporturi reutilizabile deoarece zona
utilizată pentru păstrarea înregistrărilor unui fişier poate fi ulterior
reutilizată ulterior pentru păstrarea înregistrărilor unui alt fişier.În C

                                  200
un fişier reprezintă o sursă sau o destinaţie de date, care poate fi
asociată cu un disc sau cu alte periferice.
         Biblioteca acceptă fişiere de tip text şi binar, deşi în anumite
sisteme, de exemplu UNIX, acestea sunt identice.
         Un fişier de tip text este o succesiune de linii, fiecare linie
având zero sau mai multe caractere terminate cu ' n '. Într-o altă
reprezentare, anumite caractere pot fi convertite într-o succesiune de
caractere, adică să nu existe o relaţie unu la unu între caracterele scrise
(citite) şi acţiunea perifericului. De exemplu, caracterul NL (new line),
' n ', corespunde grupului CR (carriage return) şi LF (line feed).
         În aceeaşi idee, se consideră că datele introduse de la un
terminal formează un fişier de intrare. Înregistrarea se consideră că
este formată de datele unui rând tastate de la terminal (tastatură,
keyboard), deci caracterul de rând nou NL se consideră ca fiind
terminator de înregistrare. În mod analog, datele care se afişează pe
terminal (monitor, display) formează un fişier de ieşire. Şi în acest caz
înregistrarea este formată din caracterele unui rând.
         Ceea ce este important de subliniat este că fişierele text pot fi
accesate la nivel de octet sau de caracter, ele putând fi interpretate
drept o colecţie de caractere, motiv pentru care se şi numesc fişiere
text. Toate funcţiile de intrare/ ieşire folosite până acum se pot utiliza
şi pentru fişierele text.
        Un fişier de tip binar este o succesiune de octeţi neprelucraţi
care conţin date interne, cu proprietatea că dacă sunt scrise şi citite pe
acelaşi sistem, datele sunt egale.
        Aceste fişiere sunt organizate ca date binare, adică octeţii nu
sunt consideraţi ca fiind coduri de caractere. La fişierele binare
înregistrarea se consideră că este o colecţie de date structurate numite
articole. Structurile de date sunt pretabile pentru stocarea în astfel de
fişiere
        Tratarea fişierelor se poate face la două nivele, inferior şi
superior.
         Nivelul inferior de prelucrare a fişierelor oferă o tratare a
fişierelor fără zone tampon (buffere), făcând apel direct la sistemul de
operare. Rezervarea de zone tampon este lăsată pe seama
utilizatorului. Fişierele de tip text se pretează la o astfel de tratare.
        Nivelul superior de prelucrare a fişierelor se bazează pe
utilizarea unor proceduri specializate în prelucrarea fişierelor care
printre altele pot rezerva şi gestiona automat zonele tampon necesare.
                                   201
Fişierele binare se pot manipula cu facilitate la acest nivel. Funcţiile
specializate de nivel superior au denumiri asemănătoare cu cele de
nivel inferior, doar prima literă a numelui este f.
       În practică operaţiile de intrare/ieşire (I/O) cu memoria externă
(hard-disk sau floppy-disk) sunt mult mai lente decât cele cu memoria
internă. Din această cauză, pentru a spori viteza de lucru, se încearcă
să se reducă numărul de operaţii de acces la disc. În acest scop se
folosesc bufferele.
       Un buffer este o zonă de memorie în care sistemul memorează o
cantitate de informaţie (număr de octeţi), în general mai mare decât
cantitatea solicitată de o operaţie de I/O. Dacă un program efectuează
o operaţie de citire a 2 octeţi dintr-un fişier, atunci sistemul citeşte
într-un buffer întreg sectorul de pe disc (512 octeţi) în care se găsesc şi
cei 2 octeţi solicitaţi, eventual chiar mai mult, în funcţie de
dimensiunea bufferului (zonei tampon). Dacă în continuare se vor
solicita încâ 2 octeţi, aceştia vor fi preluaţi din bufferul din memorie,
fără a mai fi nevoie să mai accesăm discul pe care se află fişierul din
care se face citirea. Operaţiile de citire continuă în acest mod până la
citirea tuturor octeţilor din buffer, moment în care se va face o nouă
umplere a bufferului cu noi date prin citirea următorului sector de pe
disc. Invers, dacă un program efectuează o operaţie de scriere a unui
număr de octeţi pe disc, aceştia se vor înscrie de fapt secvenţial în
buffer şi nu direct pe disc. Scrierea va continua astfel până la umplerea
bufferului, moment în care sistemul de operare efectuează o operaţie
de scriere a unui secto de pe disc cu cei 512 octeţi din buffer (se
goleşte bufferul prin scriere). În acest fel, reducând numărul de
operaţii de acces la disc (pentru citire sau scriere) creşte viteza de
execuţie a programelor şi fiabilitatea dispozitivelor de I/O.
       Bufferele au o mărime implicită, dar ea poate fi modificată prin
program. Dimensiunea trebuie aleasă în funcţie de aplicaţie ţinând
cont de faptul că prin mărirea bufferului creşte viteza de execuţie dar
scade dimensiunea memoriei disponibile codului programului şi
invers, prin micşorarea sa creşte memoria cod disponibilă dar scade
viteza de lucru. Bufferul de tastatură are, spre exemplu, dimensiunea
de 256 octeţi, din care 254 sunt puşi la dispoziţie.
       Orice fişier are o înregistrare care marchează sfârşitul de fişier.
În cazul fişierelor de intrare ale căror date se introduc de la terminal,
sfârşitul de fişier se generează în funcţie de sistemul de operare

                                   202
considerat. Pentru sistemele de operare MS-DOS sau MIX şi RSX11
se tastează CTRL/Z iar pentru UNIX se tastează CTRL/U.
      Un fişier stocat pe suport magnetic se mai numeşte şi fişier
extern. Când se prelucrează un astfel de fişier se crează o imagine a
acestuia în memoria internă (RAM) a calculatorului. Această imagine
se mai numeşte şi fişier intern.
      Un fişier intern este conectat la un fişier extern sau dispozitiv
prin deschidere; conexiunea este întreruptă prin închidere.
Deschiderea unui fişier întoarce un pointer la un obiect de tip FILE,
care conţine toate datele necesare pentru controlul fişierului.
Operaţiile de deschidere şi închidere a fişierelor se poate realiza în C
prin funcţii specializate din biblioteca standard I/O a limbajului. Alte
operaţii care sunt executate frecvent în prelucrarea fişierelor sunt:
• Crearea unui fişier (acest fişier nu există în format extern)
• Actualizarea unui fişier (deja existent)
• Adăugarea de înregistrări unui fişier deja existent
• Consultarea unui fişier
• Poziţionarea într-un fişier
• Redenumirea unui fişier
• Ştergerea unui fişier
       Ca şi operaţiile de deschidere şi închidere de fişiere, operaţiile
indicate mai sus pot fi realizate printr-un set de funcţii aflate în
biblioteca standard I/O a limbajului. Aceste funcţii realizează acţiuni
similare sub diferite sisteme de operare, dar multe dintre ele pot
depinde de implementare. În cele ce urmează se prezintă funcţiile care
au o utilizare comună pe diferite medii de programare şi sunt cele mai
frecvent utilizate.


10.2. Operaţii cu fişiere
      În acest subcapitol vom detalia principalele operaţii efectuate
asupra unor fişiere.
      În timpul lucrului cu fişierele, sistemul de operare păstrează un
indicator de fişier care indică poziţia curentă în fişier, poziţie la care se
va face următoarea operaţie de scriere sau citire. De exemplu, la
deschiderea unui fişier pentru citire indicatorul de fişier va indica la
începutul fişierului. Dacă se va face o operaţie de citire a 2 octeţi se


                                    203
vor citi octeţii cu numărul de ordine 0 şi 1 iar indicatorul va indica
spre următorul octet, adică cel cu numărul de ordine 3.
       Pentru o mai corectă înţelegere a acestor funcţii le vom
structura după nivelul la care se utilizează: inferior sau superior.
      În momentul începerii execuţiei unui program, interfeţele
standard (cu ecranul, tastatura şi porturile seriale şi paralele) sunt
deschise în mod text.
      Principalele funcţii sunt grupate în tabelul de mai jos:

         Descriere        Nume funcţie de    Nume funcţie de
                          nivel inferior     nivel superior
         Deschidere             _open               fopen
         Creare                 _creat             fcreate
         Citire                 _read               fread
         Scriere                _write              fwrite
         Închidere             _close             fclose
         Poziţionare           _lseek              fseek
         Ştergere              _unlink            remove
         Redenumire           _rename             rename

      În afara acestor funcţii principale mai există anumite funcţii
specializate, cum ar fi:
       - Funcţii pentru prelucrarea pe caractere a unui fişier: putc
           (scriere caracter) şi getc (citire caracter).
       - Funcţii pentru Intrări/Ieşiri cu format: fscanf şi fprintf.
       - Funcţii pentru Intrări/Ieşiri de şiruri de caractere: fgets şi
           fputs.
       Pentru ca sistemul de operare să poată opera asupra fişierelor ca
fluxuri (stream) de intrare/ieşire trebuie să cunoască anumite
informaţii despre ele. Acest lucru se realizează prin operaţia de
deschidere a fluxurilor (stream-urilor).
      Pointerul fişier
      În urma operaţiei de deschidere se crează în memorie o variabilă
de tip structură FILE care este o structură predefinită. În această
variabilă, care se numeşte bloc de control al fişierului, FCB (File
Control Block) sistemul păstrează informaţii despre fişierul deschis,
precum:
    • Nume
                                 204
•    Dimensiune
    •    Atribute fişier
    •    Descriptorul fişierului
       Un pointer-fişier este un pointer la informaţiile care definesc
diferitele aspecte ale unui fişier: nume, stare, poziţie curentă. Un
pointer-fişier este o variabilă pointer de tip FILE, definită în "stdio.h".
       Tipul FILE este un tip structurat care depinde de sistemul de
operare. Dacă facem abstracţie de cazurile speciale de calculatoare tip
VAX sau U3B, pe majoritatea implementărilor tipul FILE se defineşte
prin următoarea structură:
        typedef struct {
        unsigned char *_ptr;
        int _cnt;
        unsigned char *_base;
        char _flag;
        char _file;
        } FILE;
       Variabila de tip FILE este creată şi gestionată de către suportul
pentru exploatarea fişierelor în limbajul C. În urma deschiderii unui
fişier, programul primeşte un pointer la variabila creată, deci un
pointer la o structură de tip FILE. Se spune că s-a deschis un stream
(flux de date). Toate operaţiile care se fac pe acest stream se referă la
fişierul asociat stream-ului.
       În limbajul C există 5 stream-uri standard, definite în <stdio.h>:
       FILE *stdin;
care se referă la dispozitivul standard de intrare (tastatura). Orice
operaţie de citire de la stream-ul stdin înseamnă citire de la tastatură.
Bufferul folosit are o dimensiune de 254 de caractere şi bufferul se
goleşte la tastarea NL (‘n’). Se mai spune că stdin este cu buffer la
nivel de linie.
       FILE *stdout;
care se referă la dispozitivul standard de ieşire (ecranul). Orice
operaţie de scriere la stream-ul stdout înseamnă scriere pe ecran. Spre
deosebire de stdin, stdout este ne-bufferizat deoarece orice scriere pe
ecran se face direct la scrierea unui caracter în fişierul stdout.
       FILE *stderr;
care se referă la dispozitivul standard pentru afişarea mesajelor de
eroare (ecranul). Este ne-bufferizat.
       FILE *stdprn;


                                   205
care se referă la primul port paralel PRN la care se conectează de
obicei imprimanta (LPT). Este bufferizat la nivel linie.
      FILE *stdaux;
care se referă la primul port serial COM1. Este ne-bufferizat.

10.3. Nivelul inferior de prelucrare a fişierelor
       La acest nivel operaţiile de prelucrare a fişierelor se execută fără
o gestiune automată a zonelor tampon, făcându-se apel direct la
sistemul de operare. Programatorul are în gestiune o zonă declarată
drept buffer şi trebuie să ţină cont de faptul că această bufferizare este
la nivel linie. Numele funcţiilor de nivel inferior, orientate pe text
(transfer de octeţi) încep de obicei cu _ (underline). Dacă un fişier se
deschide în modul text, atunci, în cazul citirii dintr-un fişier, secvenţa
de octeţi CR-LF (0DH, 0AH) este translatată (înlocuită) cu un singur
caracter LF, iar în cazul scrierii în fişier caracterul LF este expandat la
secvenţa CR-LF. De asemenea, în cazul MS-DOS sau Windows
CTRL/Z este interpretat în cazul citirii drept caracter de sfârşit de
fişier (EOF).
10.3.1. Deschiderea unui fişier
        Orice fişier înainte de a fi prelucrat trebuie deschis, motiv
pentru care operaţia de deschidere a unui fişier este de mare
importanţă. Deschiderea unui fişier existent se realizează prin
intermediul funcţiei _open. La revenirea din ea se returnează un aşa
numit descriptor de fişier. Acesta este un număr întreg. El identifică în
continuare fişierul respectiv în toate operaţiile realizate asupra lui.
        În forma cea mai simplă funcţia _open se apelează printr-o
expresie de atribuire de forma:
        df = _open(spf,mod)
unde:
df – este un număr întreg care reprezintă descriptorul de fişier
spf – este specificatorul fişierului care se deschide
mod – defineşte modul de prelucrare a fişierului
       Specificatorul de fişier este fie un şir de caractere, fie un pointer
spre un astfel de şir de caractere. Conţinutul şirului de caractere
depinde de sistemul de operare folosit. În cea mai simplă formă el este
un nume sau mai general o cale care indică plasamentul pe disc al
fişierului care se operează. Fişierele deschise la acest nivel pot fi
                                   206
prelucrate în citire (consultare), scriere (adăugare de înregistrări) sau
citire/scriere (actualizare sau punere la zi).
       Calea spre fişier trebuie să respecte convenţiile sistemului de
operare MS-DOS în general. În cea mai simplă formă ea este un şir de
caractere care defineşte numele fişierului, urmat de extensia fişierului.
Aceasta presupune că fişierul se găseşte în directorul curent. Dacă
fişierul nu se află în fişierul curent, atunci numele este precedat de o
construcţie de forma:
     litera:nume_1nume_2…nume_k
unde:
litera – defineşte discul (în general A, B pentru floppy-disk şi C, D,..
         pentru hard-disk)
nume_i – este un nume de subdirector.
      Deoarece calea se include între ghilimele, caracterul ‘’ se
dublează. Spre exemplu, putem folosi o comandă de deschidere de
forma:
      int d;
      d=_open(“A:JOCBIO.C“,O_RDWR);
caz în care fişierul BIO.C din directorul JOC de pe dscheta A se
deschide în citire/scriere.
        În funcţie de operaţia dorită, mod poate avea valorile:
        0 - pentru citire
        1 - pentru scriere
        2 - pentru citire/scriere
      Deschiderea unui fişier nu reuşeşte dacă unul dintre parametri
este eronat. În acest caz funcţia _open returnează valoarea (-1).
       int _open( const char *filename, int oflag [, int pmode] );
este definiţia generală a funcţiei _open.
             Modul de acces mod se poate furniza în mod explicit
printr-o variabilă de tip întreg (oflag) care poate avea valorile:
 Variabila mod               Modul de deschidere a fişierului
 _O_RDONLY         Fişierul se deschide numai în citire (read-only)
                   Nu se poate specifica împreună cu _O_RDWR sau
                   _O_WRONLY
 _O_WRONLY         Fişierul se deschide numai în scriere (write-only) Nu se
                   poate specifica împreună cu _O_RDWR sau
                   _O_RDONLY
 _O_RDWR           Fişierul se deschide în citire/scriere (read/write)


                                  207
_O_APPEND          Fişierul se deschide pentru adăugarea de înregistrări la
                    sfârşitul său.
 _O_CREAT           Crează şi deschide un nou fişier pentru scriere. Nu are
                    nici un efect dacă fişierul este deja existent.
 _O_BINARY          Fişierul se prelucrează în mod binar

 _O_TEXT            Fişierul este de tip text, adică se prelucrează pe
                    caractere sau octeţi (implicit)
             Menţionăm că în MSDN aceste variabile se mai numesc şi
oflag (open-flag) şi sunt definite în fişierul header FCNTL.H.
             În cazul în care oflag este _O_CREAT, atunci este
necesară specificarea constantelor opţionale pmode, care se găsesc
definite în SYSSTAT.H. Acestea sunt:
       _S_IREAD - este permisă numai citirea fişierului
       _S_IWRITE - este permisă şi citirea (permite efectiv
citirea/scrierea fişierului)
       _S_IREAD | _S_IWRITE - este permisă şi scrierea şi citirea
fişierului.
       Argumentul pmode este cerut numai când se specifică
_O_CREAT. Dacă fişierul există deja, pmode este ignorat. Altcumva,
pmode specifică setările de permisiune asupra fişerului care sunt
activate când fişierul este închis pentru prima oară.
       _open aplică masca curentă de permisiune la fişier înainte de
setarea accesului la fişier.
       Pentru a crea un fişier nou se va utiliza funcţia _creat pentru a-l
deschide. De fapt se deschide prin creare un fişier inexistent. Funcţia
este definită astfel:
       int _creat( const char *filename, int pmode );
în care parametrii au fost descrişi mai sus.
       Protecţia unui fişier este dependentă de sistemul de operare.
Spre exemplu, în UNIX protecţia se defineşte prin 9 biţi ataşaţi
oricărui fişier, grupaţi în 3 grupe de câte 3 biţi. Fiecare bit controlează
o operaţie de citire, scriere, execuţie. Protecţia operaţiilor se exprimă
faţă de proprietar, grup sau oricine altcineva. Numărul octal 0751
permite proprietarului toate cele 3 operaţii indicate mai sus (7 = 1112),
grupul la care aparţine proprietarul poate citi şi executa fişierul (5 =
1012) iar alţi utilizatori pot numai executa fişierul (1 = 0012). Funcţia
_creat poate fi apelată şi în cazul în care se deschide un fişier deja

                                   208
existent, caz în care se pierde conţinutul vechi al fişierului respectiv şi
se crează în locul lui unul nou cu acelaşi nume.
       Fiecare din funcţiile _open sau _creat               returnează un
specificator de fişier (handle) pentru fişierul deschis. Acest
specificator este o valoare întreagă pozitivă. Implicit, stdin are
specificatorul 0, stdout şi stderr au specificatorii 1 respectiv 2 iar
fişierele disc care sunt deschise primesc pe rând valorile 3, 4,..etc.
până la numărul maxim admis de fişiere deschise.
       Valoarea returnată -1 indică o eroare de deschidere, în care caz
variabila errno este setată la una din valorile:
       EACCES – (valoare 13) s-a încercat deschiderea pentru scriere a
unui fişier read-only sau modul de partajare a fişierului nu permite
operaţia specificată sau calea nu specifică un nume de fişier ci de
director.
       EEXIST – (valoare 17) flagurile _O_CREAT şi _O_EXCL sunt
specificate, dar numele de fişier este al unui fişier deja existent.
       EINVAL – (valoare 22) unul dintre argumentele oflag sau
pmode sunt invalide.
       EMFILE – (valoare 24) nu mai sunt disponibile specificatoare
de fişier (prea multe fişiere deschise).
       ENOENT – (valoare 2) fişierul sau calea nu au fost găsite.
Variabila globală errno păstrează codurile de eroare folosite de
funcţiile perror (print error) sau strerror (string error) pentru tratarea
erorilor. Constantele manifest pentru aceste variabile sunt declarate în
STDLIB.H după cum urmează:
       extern int _doserrno;
       extern int errno;
       errno este setată de o eroare într-un apel de funcţie la nivel de
sistem (la nivelul inferior). Deoarece errno păstrează valoarea setată
de ultimul apel, această valoare se poate modifica la fiecare apel de
funcţie sistem. Din această cauză errno trebuie verificată imediat
înainte şi după un apel care poate s-o modifice. Toate valorile errno,
definite drept constante manifest în ERRNO.H, sunt compatibile
UNIX. Valorile valide pentru aplicaţiile Windows pe 32 de biţi sunt
un subset al acestor valori UNIX. Valorile specificate mai sus sunt
valabile pentru aplicaţii Windows pe 32 de biţi.
       La o eroare, errno nu este setată în mod necesar la aceeaşi
valoare cu codul erorii de sistem. Numai pentru operaţii de I/O se

                                   209
foloseşte _doserrno pentru a accesa codul erorilor sistemului de
operare echivalent cu codurile semnalate de errno. Exemplu:
Acest program foloseste _open pentru a deschide un fisier numit
OPEN.C pentru citire si un fisier numit OPEN.OUT scriere. Apoi
fisierele sunt inchise
      #include <fcntl.h>
      #include <sys/types.h>
      #include <sys/stat.h>
      #include <io.h>
      #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 filen" );
      _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 filen" );
      _close( fh2 );}}
      Prin execuţia acestui program se vor obţine următoarele mesaje
pe display:
   open failed on input file: No such file or directory
   Open succeeded on output file
Press any key to continue


10.3.2. Scrierea într-un fişier
        Scrierea într-un fişier se realizează folosind funcţia _write. Se
presupune că fişierul respectiv a fost în prealabil deschis prin funcţiile
_open sau _creat. Ea este asemănătoare cu funcţia _read, doar că se
realizează transferul de date în sens invers şi anume din memorie pe
suportul fiierului. Funcţia _write, ca şi _read, se apelează printr-o
atribuire de forma:
        nr = _read(df,zt,n)
unde:
nr – este o variabilă de tip întreg căreia i se atribuie numărul de octeţi
scrişi în fişier.
                                  210
df – este descriptorul de fişier returnat de funcţia _open la deschiderea
sau _creat la crearea fişierului.
zt - este un pointer spre zona tampon definită de utilizator, zonă din
care se face scrierea.
n – este dimensiunea zonei tampon sau numărul de octeţi care se
doreşte să se scrie.
       Definiţia funcţiei este:
int _write( int handle, const void *buffer, unsigned int count );
       Funcţia _write scrie count octeţi din buffer în fişierul asociat cu
descriptorul handle. Operaţia de scriere începe la poziţia curentă a
pointerului de fişier asociat cu fişierul dat. Dacă fişierul este deschis
cu indicatorul _O_APPEND, operaţia de scriere începe la sfârşitul
fişierului. După o operaţie de scriere pointerul de fişier este
incrementat cu numărul de biţi scrişi efectiv.
       Dacă fişierul a fost deschis în mod text (implicit), atunci _write
tratează CTRL/Z drept un caracter ce indică sfârşitul logic al
fişierului. Când se scrie într-un dispozitiv, _write tratează CTRL/Z din
buffer drept terminator al operaţiei de ieşire.
       În general trebuie ca la revenirea din funcţia _write să avem
nr=n, ceea ce semnifică faptul că s-au scris pe disc exact numărul de
biţi din buffer. În caz contrar scrierea este eronată: aceasta semnifică
faptul că pe disc a rămas mai puţin spaţiu (în octeţi) decât numărul de
octeţi ai bufferului. Dacă valoarea returnată este -1, se semnalizează
eşecul operaţiei de scriere. În acest caz variabila globală errno poate
avea una din valorile EBADF (care semnifică un descriptor de fişier
invalid sau că fişierul nu a fost deschis pentru scriere) sau ENOSPC
(care semnifică lipsa spaţiului pe disc pentru operaţia de scriere).
       Funcţia _write poate fi utilizată pentru a scrie pe ieşirile standard
(display). Astfel, pentru a scrie pe ieşirea standard identificată prin
stdout se foloseşte descriptorul 1, iar pentru a scrie pe ieşirea standard
pentru erori, stderr, se foloseşte descriptorul de fişier 2. De asemenea,
în acest caz nu este nevoie să apelăm funcţia _open sau _creat
deoarece fişierele respective se deschid automat la lansarea
programului. Exemplu:
/*Acest program deschide un fisier pentru scriere                        si
foloseste _write pentru a scrie octeti in fisier*/
#include <io.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>

                                   211
#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)
   perror( "Write failed" );
      else
printf( "Wrote %u bytes to filen", byteswritten );
_close( fh );}}
      În urma execuţiei programului, se va afişa mesajul:
Wrote 36 bytes to file
Press any key to continue

10.3.3. Citirea dintr-un fişier
        Citirea dintr-un fişier deschis în prealabil cu funcţia _open se
realizează cu ajutorul funcţiei _read. Ea returnează numărul efectiv al
octeţilor citiţi din fişier. Funcţia _read se poate apela folosind o
expresie de atribuire de forma:
        nr = _read(df,zt,n)
cu definiţia generală:
        int _read( int handle, void *buffer, unsigned int count );
unde:
nr – este o variabilă de tip întreg căreia i se atribuie numărul de octeţi
citiţi din fişier.
df – este descriptorul de fişier returnat de funcţia open la deschiderea
sau creat la crearea fişierului.
zt - este un pointer spre zona tampon definită de utilizator, zonă în
care se face citirea.
n – reprezintă numărul de biţi care se citesc
Funcţia _read citeşte maximum count octeţi în buffer din fişierul
asociat cu descriptorul handle. Operaţia de citire începe de pe poziţia
curentă îndicată de pointerul de fişier asociat cu fişierul dat. După
operaţia de citire, pointerul de fişier indică spre următorul octet necitit
din fişier. Dacă fişierul a fost deschis în mod text, citirea se termină la
întâlnirea caracterului CTRL/Z, care este interpretat drept indicator de
sfârşit de fişier.


                                   212
_read returnează numărul de biţi citiţi din fişier, care poate fi
mai mic decât count dacă sunt mai puţini decât count octeţi rămaşi în
fişier sau dacă fişierul este deschis în mod text. În acest caz fiecare
pereche CR-LF (carriage return–linefeed) (CR-LF) este înlocuită cu
un singur caracter LF. Numai acest caracter LF se consideră în
valoarea returnată. Înlocuirea nu afectează pointerul de fişier. Dacă
funcţia încearcă să citească după sfârşitul fişierului, se returnează
valoarea 0. Dacă descriptorul de fişier (handle) este invalid sau dacă
fişierul nu este deschis pentru citire sau dacă este blocat, funcţia
returnează valoarea negativă -1 şi setează variabila errno la EBADF.
Tipul erorii şi depistarea ei este dependentă de sistemul de operare
utilizat. Dacă n = 1, se citeşte un singur octet. De obicei, nu este
eficient să se citească câte un octet dintr-un fişier, deoarece apelurile
multiple ale funcţiei _read pot conduce la un consum de timp
apreciabil. Dimensiunea maximă a lui n este dependentă de sistemul
de operare. O valoare utilizată frecvent este 512, valoare optimă
pentru MS-DOS sau pentru UNIX.
       Funcţia _read citeşte maximum count biţi în zona buffer din
fişierul cu descriptorul handle. Operaţia de citire începe de la poziţia
curentă a pointerului de fişier asociat cu fişierul respectiv. După o
operaţie de citire, pointerul fişier indică spre următorul caracter (octet)
necitit din fişier. Dacă fişierul a fost deschis în mod text, _read se
termină când se întâlnete indicatorul de fişier CTRL/Z.
       Funcţia _read poate fi utilizată pentru a citi de la intrarea
standard (tastatură). În acest caz descriptorul de fişier are valoarea 0.
De asemenea, în acest caz nu este nevoie să apelăm funcţia _open
deoarece fişierul se deschide automat la lansarea programului.
Exemplu:
/* Acest program deschide fisierul WRITE.O creat anterior
si incearca sa citeasca 60000 octeti din fisier folosind
_read. Apoi va afisa numarul de octeti cititi */

#include <fcntl.h> /*          Necesara     numai    pentru    definirea
_O_RDWR */
#include <io.h>
#include <stdlib.h>
#include <stdio.h>

char buffer[60000];

void main( void )
{   int fh;
                                   213
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
printf( "Read %u bytes from filen", bytesread );
_close( fh );}
      La execuţia programului se va afişa următorul mesaj:
Read 36 bytes from file
Press any key to continue

10.3.4. Închiderea unui fişier
        După terminarea prelucrării unui fişier el trebuie închis. Acest
lucru se realizează automat dacă programul se termină prin apelul
funcţiei exit. Programatorul poate închide un fişier folosind funcţia
_close. Se recomandă ca programatorul să închidă orice fişier de
îndată ce s-a terminat prelucrarea lui, deoarece numărul fişierelor ce
pot fi deschise simultan este limitat între 15 şi 25, în funcţie de
sistemul de operare. Menţionăm că fişierele corespunzătoare intrărilor
şi ieşirilor standard nu trebuie închise de programator.
       Definiţia funcţiei este:
            int _close( int handle );
       Funcţia _close închide fişierul asociat cu descriptorul handle.
       Funcţia _close returnează valoarea 0 la o închidere reuşită şi -1
în caz de incident. Apelul ei se realizează printr-o expresie de atribuire
de forma:
            v =_ close(df)
unde:
v – este variabila de tip întreg ce preia valoarea returnată de funcţie
df – este descriptorul de fişier (handle) al fişierului pe care dorim să-l
închidem.
10.3.5. Poziţionarea într-un fişier
        Operaţiile de citire/scriere într-un fişier se execută secvenţial,
astfel încât:
        - fiecare apel al funcţiei _read citeşte înregistrarea din poziţia
           următoare din fişier

                                  214
-    fiecare apel al funcţiei _write scrie înregistrarea în poziţia
           următoare din fişier.
       Acest mod de acces la fişier se numeşte secvenţial şi el este util
când dorim să prelucrăm fiecare înregistrare a fişierului. În practică
apar însă şi situaţii în care noi dorim să scriem şi să citim înregistrări
într-o ordine oarecare. În acest caz se spune că accesul la fişier este
aleator. Pentru a realiza un acces aleator este nevoie să ne putem
poziţiona oriunde în fişierul respectiv O astfel de poziţionare este
posibilă pe hard-uri şi floppy-uri prin funcţia _lseek.
       Definiţia funcţiei este:
long _lseek( int handle, long offset, int origin );
       Funcţia _lseek mută pointerul de fişier asociat cu descriptorul
handle (df) pe o nouă locaţie care este situată la offset octeţi de origin.
Următoarea operaţie de citire/scriere se va efectua de la noua locaţie.
Argumentul origin trebuie să fie una dintre următoarele constante,
definite în STDIO.H:
       SEEK_SET – începutul fişierului (valoare 0)
       SEEK_CUR – poziţia curentă a pointerului de fişier (valoare 1)
       SEEK_END – sfârşitul fişierului (valoare implicită 2)
       Funcţia _lseek returnează valoarea 0 la poziţionare corectă şi -1
la incident. Ea poate fi apelată prin:
      v = _lseek(df, deplasament, origine)
unde:
v – este o variabilă de tip întreg căreia i se atribuie valoarea returnată
de către funcţie (0 sau -1)
df – este descriptorul de fişier (handle) a cărui valoare a fost definită la
deschiderea sau crearea fişierului.
deplasament – este o variabilă de tip long şi conţine numărul de octeţi
peste care se va deplasa capul de citire/scriere al discului.
origine – are una din valorile
0 - deplasamentul se socoteşte de la începutul fişierului;
1 - deplasamentul se socoteşte din poziţia curentă a capului de citire/
    scriere;
2 - deplasamentul se socoteşte de la sfârşitul fişierului.
       Menţionăm că prin apelul lui _lseek nu se realizează nici un fel
de transfer de informaţie ci numai poziţionarea în fişier. Operaţia
următoare realizată prin apelul funcţiei _read sau _write se va realiza
din această poziţie. Spre exemplu, apelul:
      v = _lseek(df, 0l, 2)


                                   215
permite să se facă poziţionarea la sfârşitul fişierului. În continuare se
pot adăuga articole folosind funcţia _write.
      Poziţionarea la începutul fişierului se face prin apelul:
           v = _lseek(df, 0l, 0)
Exemplu:
#include <io.h>
#include <fcntl.h>
#include <stdlib.h>
#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 = %ldn",
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 = %ldn", 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 = %ldn", pos );
   _close( fh );}
     În urma execuţiei programului se va afişa:
Pozitia pentru inceputul fisierului = 0
Pozitia curenta = 10
Pozitia ultima este = 36
Press any key to continue

10.3.6 Ştergerea unui fişier
      Un fişier poate fi şters apelând funcţia _unlink astfel:

                                  216
v = _unlink(spf)
unde v este o variabilă de tip întreg căreia i se atribuie valoarea 0
pentru ştergere reuşită şi (-1) pentru ştergere nereuşită.
      spf este specificatorul de fişier folosit la deschidere a fişierului.
      Definiţia funcţiei este:
       int _unlink( const char *filename );
Funcţia _unlink şterge de pe disc fişierul specificat prin filename.
Exemplu:
/* Acest program sterge fisierul WRITE.O creat si prelucrat anterior. */

#include <stdio.h>
void main( void )
{ if( _unlink( "write.o" ) == -1 )
   perror( "Nu se poate sterge 'WRITE.O'" );
   else
   printf( "S-a sters 'WRITE.O'n" );}
În urma execuţiei programului se afişează:
      S-a sters 'WRITE.O'
      Press any key to continue

10.3.7. Exemple de utilizare a funcţiilor de intrare/ieşire de
nivel inferior
1. Să se scrie un program care copiază intrarea standard la ieşirea
    standard.
       Această problemă se poate rezolva uşor prin folosirea funcţiilor
getchar şi putchar. Acum o vom rezolva folosind funcţiile _read şi
_write.
# include <stdio.h>
# include <io.h>
void main() /* copiaza            intrarea     standard     la    iesirea
standard */
{ char c[1];
while (_read(0,c,1)>0)
_write(1,c,1);}
      Menţionăm că cel de-al doilea parametru al funcţiei _read sau
_write trebuie să fie pointer spre caractere.
      Lucrul la nivelul inferior nu este chiar atât de simplu pe cât
pare. Vom ilustra în continuare responsabilitatea pe care o are
programatorul în gestionarea zonelor tampon. Să considerăm
exemplul anterior în care zona tampon o mărim la 3 caractere, deci
programul arată astfel:
# include <stdio.h>

                                   217
# include <io.h>
void main()
{ char c[3];
while (_read(0,c,3)>0)
_write(1,c,3);}
        Citirea nu se va opri după 3 caractere, ci funcţia _read va
continua să funcţioneze până la tastarea ENTER (CR+LF). Imediat
funcţia _read va tipări grupele de 3 caractere introduse, inclusiv
grupul final CR+LF. Zona tampon definită este supraînscrisă de
fiecare dată când se introduc noi caractere.
        Dacă de la tastatură vom introduce
        123456<CR><LF>
atunci se va tipări primul grup (prima înscriere a zonei tampon) 123,
apoi a doua grupă 456 şi grupul <CR> şi <LF> va supraînscrie
primele două caractere ale bufferului, anume codurile ASCII ale lui 4
şi 5 şi se va tipări <CR><LF>6.
               123456
               123456
               6
        Primul grup 123456 este scris prin ecou de la tastatură, iar
următoru se înscrie de către program. Dacă în continuare vom
introduce 1<ENTER> atunci se va tipări 1 urmat de două rânduri noi
deoarece fiecare CR sau LF sunt expandate de stdout în perechi
<CR><LF>.
        Dacă mărim la 5 dimensiunea bufferului şi de la tastatură
introducem 12<ENTER>, atunci se va tipări
               12
               12

            ¦
deoarece cel de-al 5-lea octet al bufferului nu a fost alocat prin citire,
având o valoare nedefinită.
     Problemele de mai sus legate de gestiunea bufferului în/din care
se face citirea/scrierea pot fi depăşite cu o modificare simplă,
prezentată mai jos. Prin scriere nu se vor trimite spre stdout decât
numărul de caractere citit de la stdin.
# include <stdio.h>
# include <io.h>
# define LZT 10 // lungime zona tampon
void main() /* copiaza intrarea standard                   la    iesirea
standard */

                                  218
{ char zt[LZT];
int n;
while ((n=_read(0,zt,LZT))>0)
_write(1,zt,n);}
       Programatorul trebuie să ţină cont însă şi de alte amănunte cum
ar fi dimensiunea implicită a bufferului stdin, care este de 254 de
caractere.

2. Să se scrie un program care citeşte un şir de numere flotante de la
     intrarea standard şi crează 2 fişiere fis1.dat şi fis2.dat, primul
     conţinând numerele de ordin impar citite de la intrarea standard
     (primul, al 3-lea, al 5-lea, etc.) iar cel de-al doilea pe cele de
     ordin par citite de la aceeaşi intrare. Apoi să se listeze, la ieşirea
     standard, cele două fişiere în ordinea fis1.dat, fis2.dat câte un
     număr pe un rând în formatul
                      număr de ordine:        număr
       Vom scrie programul folosindu-ne de funcţii definite de
utilizator care să facă apel la funcţiile de nivel inferior. Programul
arată astfel:

# include <stdio.h>
# include <io.h>
# include <sys/types.h>
# include <sys/stat.h>
# include <fcntl.h>
#include <stdlib.h>
char nume1[]="fis1.dat";
char nume2[]="fis2.dat";
union unr {
     float nr;
     char tnr[sizeof(float)];};
union unr nrcit;
int creare_fis(const char *nume)
{ int df;
     if ((df=_creat(nume,_S_IWRITE|_S_IREAD))==-1)
     { printf("%s: ",nume);
printf("Nu se poate deschide fisierul in crearen");
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 fisieruluin");exit(1);}}


                                   219
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 fisieruluin");
     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 citiren");
     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: %gn",j,nrcit.nr);j+=2;}
if (i<0) {
     printf("%s: ",nume);
printf("Eroare la citire din fisieruln"); exit(1);}
_close(df);}
void main() {
int df1,df2;
df1=creare_fis(nume1);
df2=creare_fis(nume2);
date_fis(df1,nume1,df2,nume2);
inchid_fis(df1,nume1);inchid_fis(df2,nume2);
df1=deschid_fis_cit(nume1);
df2=deschid_fis_cit(nume2);
list_fis(df1,nume1,1);list_fis(df2,nume2,2);}

3. Să se realizeze programul de mai sus folosind un singur fişier
     fis.dat.
        Programul va diferi faţă de cel anterior prin faptul că
înregistrările se stochează într-un singur fişier, deci funcţia de listare
se va modifica pentru citirea din 2 în 2 a înregistrărilor. După fiecare
citire din fişier, se va face un salt cu o înregistrare pentru a poziţiona
capul de citire/scriere peste înregistrarea următoare.

# include <stdio.h>

                                  220
# 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 crearen");
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 fisieruluin");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 fisieruluin");
     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 citiren");
     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: %gn",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 fisieruln");

                           221
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: %gn",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 fisieruln");
exit(1);}
_close(df);}
void main() {
int df;
df=creare_fis(nume);
date_fis(df,nume);
inchid_fis(df,nume);
df=deschid_fis_cit(nume);
list_fis(df,nume);}
         Atragem atenţia asupra modului în care lucrează funcţiile de
intrare/ieşire pentru stdin şi stdout faţă de cele pentru disc. Dacă
intrările şi ieşirile pentru perifericele standard le putem executa în
formatul dorit cu ajutorul funcţiilor specializate scanf şi printf, pentru
lucrul cu discul variabila float este tratată sub forma unui grup de 4
octeţi care se scriu sau se citesc pe disc aşa cum este reprezentarea lor
internă. Există funcţii specializate pentru scrierea/citirea pe disc cu
format, dar care sunt de nivel superior.
       Ceea ce merită să subliniem este faptul că echivalentele de nivel
superior pentru fişiere ale funcţiilor printf() şi scanf() sunt fprintf() şi
fscanf(). Echipamentele periferice pot fi considerate fişiere externe şi
deci funcţiile specializate pentru I/O cu fişiere pot fi folosite şi pentru
operaţii de I/O cu echipamentele periferice. Funcţiile printf şi scanf
sunt proiectate pentru a lucra implicit cu fişierele stdout respectiv
stdin, deci cu monitorul şi tastatura.
10.4. Nivelul superior de prelucrare a fişierelor
       Nivelul superior de prelucrare a fişierelor se referă la aşa
numitul format binar de reprezentare a informaţiei în fişiere care la
rândul său face apel la informaţia structurată. Bufferul este alocat
automat şi gestionat de funcţii C specializate.
                                   222
10.4.1. Funcţia fopen()
Funcţia fopen se apelează printr-o expresie de atribuire de forma:
      pf = fopen(spf,mod)
unde:
      pf - este un pointer spre tipul FILE
      spf – este specificatorul fişierului care se deschide
      mod – este un şir de caractere care defineşte modul în care se
deschide fişierul.
      Forma generală de declarare a funcţiei fopen() este:
           FILE *fopen(char *filename, char *mode);
      Funcţia deschide fişierul al cărui nume este specificat prin
"filename" (de obicei un fişier disc) şi întoarce un pointer la FILE
pentru operaţie reuşită şi NULL pentru operaţie nereuşită.
      Varibilele permise pentru modul "mode" sunt:

   a     _O_WRONLY | _O_APPEND (usual                _O_WRONLY |
         _O_CREAT | _O_APPEND)
   a+    _O_RDWR | _O_APPEND (usual _O_RDWR | _O_APPEND |
         _O_CREAT )
   r     _O_RDONLY
   r+    _O_RDWR
   w     _O_WRONLY(usual        _O_WRONLY        |    _O_CREAT     |
         _O_TRUNC)
   w+    _O_RDWR (usual _O_RDWR | _O_CREAT | _O_TRUNC)
   b     _O_BINARY
   t     _O_TEXT
   c     Nimic
   n     Nimic


       Modul "a" nu şterge markerul de sfârşit d fişier EOF înainte de
a adăuga la sfârşitul fişierului. După ce s-a făcut o adăugare, comanda
MS-DOS TYPE tipăreşte datele până la markerul original EOF şi nu
până la ultima dată adăugată. Modul "a+" şterge identificatorul de
sfârşit de fişier EOF înainte de adăugarea de înregistrări la sfârşitul
fişierului. După adăugare comanda MS-DOS TYPE va tipări toate

                                 223
datele conţinute în fiier. Modul "a+" este cerut pentru adăugarea la
sfârşitul unui fişier care are marker terminator CTRL/Z = EOF.
       Dacă modul "mode" include "b" după litera iniţială, ca în "rb"
sau "w+b" se indică un fişier binar. Numele fişierului conţine cel mult
FILENAME_MAX caractere. La un moment dat pot fi deschise cel
mult FOPEN_MAX fişiere.
       Menţionăm că stdin, stdout şi stderr sunt pointeri spre tipul
FILE şi permit ca funcţiile de nivel superior de prelucrare a fişierelor
să poată trata intrarea standard, ieşirea standard şi ieşirea standard
pentru erori, la fel ca şi restul fişierelor. Singura deosebire constă în
faptul că în acest caz programatorul nu trebuie să deschidă sau să
închidă fişierele respective. Exemplu:
FILE *fp, *fopen();       /* se declara pointerii de tip file *fp si *fopen() */
fp = fopen("test","w"); /* se deschide fisierul " test " pentru screiere */
Pentru detectarea unei erori la deschiderea unui fişier se utilizează
secvenţa:
      if ((fp = fopen("test", "w")) == NULL) {
           puts ("Cannot open filen");
           exit(1); }
      Dacă pentru operaţia de citire se încearcă deschiderea unui fişier
inexistent, fopen() va returna o eroare.
10.4.2. Funcţia fclose()
      Forma generală de declarare a funcţiei fclose() este:
                    int fclose(FILE *fp);
unde "fp" este pointerul la fişier returnat după apelul funcţiei fopen().
       Funcţia fclose() se utilizează pentru a închide un fişier deschis cu
fopen(). Funcţia fclose() scrie mai întâi în fişier datele rămase în
fişierul buffer apoi închide fişierul. fclose() întoarce zero (0) pentru
închidere reuşită şi EOF (-1) dacă apare o eroare.
       La execuţia funcţiei exit se închid automat toate fişierele
deschise. Cu toate acestea, se recomandă ca programatorul să închidă
un fişier de îndată ce s-a terminat prelucrarea lui, altfel se poate ajunge
în situaţia de a se depăşi numărul limită admis pentru fişierele care pot
fi simultan deschise într-un program.
Exemplu:
/* Acest program deschide fisierele numite "test1.c"
si "test2.c".Apoi foloseste fclose pentru a inchide "test1.c" si _fcloseall
pentru a inchide restul fisierelor deschise */
#include <stdio.h>

                                       224
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 deschisn" );
else
printf( "Fisierul 'test1.c' a fost deschisn" );
   /* Deschidere pentru scriere */
if( (stream2 = fopen( "test2.c", "w+" )) == NULL )
printf( "Fisierul 'test2.c' nu a fost deschisn" );
else
printf( "Fisierul 'test2.c' a fost deschisn" );
   /* Inchide fisierul cu pointerul stream1 */
if( fclose( stream1 ) )
printf( "Fisierul 'test1.c' nu a fost inchisn" );
   /* Toate celelalte fisiere se inchid: */
numclosed = _fcloseall( );
printf( "Numarul fisierelor inchise cu _fcloseall: %un",
numclosed );}
     În urma execuţiei programului se obţine:
      Fisierul 'test1.c' a fost deschis
      Fisierul 'test2.c' a fost deschis
      Numarul fisierelor inchise cu _fcloseall: 1
      Press any key to continue

10.4.3. Funcţiile rename() şi remove()
      Funcţia rename() schimbă numele vechi al fişierului,
"oldname", cu numele nou, "newname". Întoarce o valoare diferită de
zero dacă incercarea nu reuseste.
     int rename (char *oldname, char *newname);
     Funcţia remove()
     int remove(char *filename);
      Funcţia remove() elimină fişierul cu numele specificat, astfel
încât o incercare ulterioară de deschidere a fişierului va eşua. Întoarce
o valoare diferită de zero dacă încercarea reuşeşte.
10.4.4. Funcţii de tratare a erorilor
      a) Funcţia ferror()
      Aceasta funcţie determină dacă în urma unei operaţii cu fişiere s-
a produs sau nu o eroare.
      Forma generală de declarare este:
     int ferror(FILE *fp)

                                  225
unde "fp" este un pointer la fişier.
      Funcţia ferror() întoarce o valoare diferită de zero dacă s-a
detectat o eroare şi o valoare 0 dacă nu s-a detectat nici o eroare.

      b) Funcţia feof()
      int   feof(FILE *fp)
       Funcţia feof() întoarce o valoare diferită de zero dacă indicatorul
de sfârşit de fişier este valid şi o valoare zero dacă indicatorul de
sfârşit de fişier nu este valid.

      c) Funcţia perror()
       void perror(const char              *s)
     Funcţia perror() scrie s şi un mesaj de eroare care depinde de
implementare, corespunzator cu intregul din errno.h, astfel:
      fprintf(stderr,"%s %sn, s, "error message")

10.4.5. Funcţii cu acces direct
      a) Funcţia fread()
      Permite citirea unui bloc de date. Forma generală de declarare:
int fread(void *buffer,int num_bytes,int count,FILE *fp)
        Funcţia fread() citeşte din fişierul specificat prin "fp" cel mult
"count" obiecte, fiecare obiect având lungimea egală cu "num_bytes"
şi îi trimite în zona de memorie indirectată prin "buffer" .
        *fp este un pointer fişier la fişierul deschis anterior cu fopen().
        Funcţia întoarce numărul de obiecte citite, acesta putând fi mai
mic decât cele cerute. Pentru a determina starea funcţiei se pot utiliza
funcţiile feof(), ferror().
        b) Funcţia fwrite()
        Permite scrierea unui bloc de date. Forma generală de declarare:
int fwrite(void *buffer,int num_bytes,int count, FILE *fp)
      Funcţia fwrite() scrie din zona (tabloul) "buffer" în fişierul
indirectat prin "fp", "count" obiect de lungime "nr_bytes". Funcţia
întoarce numărul de obiecte scrise, care, în caz de eroare este mai mic
decât "count".
Exemplu: Programul următor scrise un număr real pe disc
      # include "stdio.h"
      void main() {
           FILE *fp;
           float f = 12.23;
           if ((fp = fopen ("test", "wb")) == NULL){
                printf (" Cannot open filen ");

                                    226
return; }
            fwrite (&f, sizeof (float), 1, fp);
            fclose (fp); }
      Aşa cum se vede din acest program, "buffer" poate fi o simplă
variabilă.
Exemplu: Programul următor copiază un tablou de numere reale
"balance", în fişierul "balance":
     # include "stdio.h"
     void main()
{ FILE *fp;
     float balance[100];     /* tabloul balance */
     if ((fp = fopen("balance", "w+")) == NULL) {
               printf ("Cannot open filen");
               return;}
          . . . . . . . . . . . . . . . . .
     fwrite (balance, sizeof (balance), 1, fp);
     . . . . . . . . . . . . . . . . .
     fclose (fp); }
Exemplu: Programul următor deschide fişierul FREAD.OUT şi scrie
în el 25 de caractere şi apoi îl redeschide şi citeşte din nou caracterele
din fişier după care afişează numărul caracterelor citite şi conţinutul.
#include <stdio.h>
void main( void )
{ FILE *stream;
   char list[30];
   int i, numread, numwritten;
   /* Deschide fisierul in mod text: */
if( (stream = fopen( "fread.out", "w+t" )) != NULL )
{     for ( i = 0; i < 25; i++ )
      list[i] = (char)('z' - i);
      /* Scrie 25 caractere in fisier */
numwritten = fwrite(list,sizeof(char),25,stream );
printf( "S-au scris %d caracteren", numwritten );
fclose( stream );}
else
printf( "Probleme cu deschiderea fisieruluin" );
if( (stream = fopen( "fread.out", "r+t" )) != NULL )
{      /* Incearca sa citeasca 25 caractere */
numread = fread( list, sizeof( char ), 25, stream );
printf("Nr. caracterelor citite = %dn", numread);
printf( "Continutul bufferului = %.25sn", list );
fclose( stream );}
else
printf( "Fisierul nu a putut fi deschisn" );}
      În urma execuţie programului se obţine:


                                  227
S-au scris 25 caractere
Nr. caracterelor citite = 25
Continutul bufferului = zyxwvutsrqponmlkjihgfedcb
Press any key to continue

10.4.6. Funcţii pentru poziţionare
     a) Funcţia fseek()
     Determină poziţionarea fişierului la citire sau scriere, începând
cu poziţia selectată. Forma funcţiei:
      int fseek(FILE *fp, long offset, int origin)
unde "fp" este un pointer-fişier returnat prin apelul funcţiei fopen(),
"offset" este deplasamentul (număr octeţi) noii poziţii faţă de "origin",
iar "origin" este una din următoarele macrodefiniţii:
             SEEK_SET           - început de fişier;
             SEEK_CUR           - poziţie curentă;
             SEEK_END           - sfârşit de fişier.
Funcţia returnează 0 dacă se execută cu succes şi o valoare nenulă în
caz de eroare.
       Dacă nu s-a efectuat nici o operaţie de I/O de la deschiderea
fişierului în mod APPEND (adăugare), atunci pointerul indică
începutul fişierului.
       Nu se recomanda utilizarea funcţiei fseek() pentru fişiere text; se
sugerează utilizarea acesteia numai pentru fişiere binare. Translaţiile
CR-LF efectuate în mod text pot cauza funcţionarea defectoasă a
funcţiei fseek. Funcţia fopen şi toate celelalte funcţii vor căuta să
înlăture caracterul CTRL/Z terminator de fişier (EOF).
       Singurele operaţii garantate să funcţioneze corect când se
utilizează fseek asupra fişierelor deschise în mod text este poziţionarea
cu offset 0 relativă la orice poziţie din fişier şi poziţionarea faţă de
începutul fişierului cu un offset returnat de funcţia ftell().
       Funcţia ftell() este definită astfel:
      long ftell( FILE *stream );
       Funcţia returnează valoarea curentă a pointerului fişier. Poziţia
este exprimată prin offsetul faţă de începutul fiierului.
       În cazul fişierelor deschise în mod text, acest offset nu reflectă
întotdeauna exact numărul de octeţi datorită translaţiei CR-LF. Este
preferată folosirea simultană a funcţiilor fseek şi ftell pentru a opera
asupra fişierelor text, dar se recomandă folosirea lor în special asupra
fişierelor binare.


                                  228
Exemplu: Pentru a citi cel de-al 235 byte din fişierul numit "test" se
poate folosi următorul program:
     func1() /* se declara funcţia func1() */
    { FILE *fp;
     if ((fp = fopen("test", "rb")) == NULL) {
     printf ("Cannot open filen"); exit (1);  }
fseek(fp, 235L, 0);
return getc(fp);} /* se citeste un caracter de la pozitia
235 */
Observaţie: L modifică constanta 235 la tipul long int.
Exemplu:
/* Acest program deschide fisierul FSEEK.OUT si muta
pointerul in diverse locuri din fisier */
#include <stdio.h>
void main( void )
{ FILE *stream;
   char line[81];
   int result;
   stream = fopen( "fseek.out", "w+" );
   if( stream == NULL )
   printf( "”Fisierul fseek.out nu s-a deschisn" );
   else
   {fprintf( stream, "Fseek incepe aici: "
   "Acesta este fisierul 'fseek.out'.n" );
      result = fseek( stream, 19L, SEEK_SET);
      if( result ) perror( "Fseek esec" );
      else
{ printf( "Pointerul fisier este plasat la mijlocul
primei linii.n" );
         fgets( line, 80, stream );
         printf( "%s", line );}
fclose( stream );}}
În urma execuţie programului se obţine:
Pointerul fisier este plasat la mijlocul primei linii.
     Acesta este fisierul 'fseek.out'.
     Press any key to continue

10.4.7. Ieşiri cu format
      Funcţiile de tip printf() asigură conversiile de ieşire cu format.
      a) Funcţia fprintf()
      Forma acestei funcţii este:
int fprintf(FILE *fp, "format", lista_argumente)
       Funcţia fprintf() realizează conversia şi scrierea la ieşire în
fişierul indirectat cu "fp" sub controlul formatului, "format". Valoarea


                                    229
întoarsă de funcţie este numărul de caractere scrise, sau orice valoare
negativă, dacă apare o eroare.
      Şirul "format" conţine două tipuri de obiecte: caractere obişnuite
care sunt copiate în fişierul de ieşire şi descriptori de conversie,
fiecare determinând conversia şi tipărirea argumentelor din lista de
argumente. Fiecare descriptor începe cu caracterul % şi se încheie cu
un caracter de conversie. Între % şi caracterul de conversie pot exista:
  1) Indicatori (în orice ordine):
      "-" - determină alinierea la stânga a argumentului convertit în
câmpul de reprezentare;
      "+" - precizează că numărul va fi reprezentat cu semn;
      " " - dacă primul caracter nu este un semn se va scrie un blanc la
început;
      "0" - se utilizează în conversiile numerice şi indică umplerea cu
zerouri la începutul câmpului;
      "#" - indică o formă de ieşire alternativă : pentru "0", prima cifra
va fi zero; pentru "x" sau "X", la începutul fiecărui număr nenul se va
scrie "0x" sau "0X"; pentru "e", "E", "g", "G", "f" ieşirea va avea
întotdeauna un punct zecimal; pentru "g" şi "G" nu se vor elimina
zerourile de la sfârşit.
     2) Un număr care indică lungimea minimă a câmpului de
reprezentare.
      Argumentul convertit va fi tipărit într-un câmp cu o lungime cel
puţin egală cu cea specificată, dacă va fi nevoie şi mai mare. Dacă
argumentul specificat are mai puţine caractere, atunci câmpul va fi
umplut la stânga sau la dreapta, funcţie de aliniere. Caracterul de
umplere este de obicei spatiul, dar este 0 dacă s-a ales această optiune
(exemplu: %05d).
  3) Un punct ce separă lungimea câmpului de precizie.
  4) Un număr, precizia, care indică numărul maxim de caracetre care
se vor tipări după virgulă pentru "e", "E", sau "f", sau numărul de cifre
semnificative pentru conversiile "g" sau "G", sau numărul maxim de
caractere ce se vor tipări dintr-un şir. Lungimea, sau precizia, sau
amândoua se pot specifica şi prin "*". De exemplu:
      %10.4f - va afişa un număr de cel puţin 10 caractere cu 4
caractere după virgulă;
      %5.7s - va afişa un şir de cel puţin 5 caractere dar nu mai lung
de 7 caractere;

                                  230
%-10.2f - va alinia la stânga un număr real cu 2 zecimale într-
un câmp de reprezentare de cel puţin 10 caractere.
      Descriptorii de conversie utilizaţi de C sunt:
      %c          - un singur caracter.
      %d, %i      - notaţie zecimala cu semn.
      %x, %X - notaţie hexazecimală fără semn (fără 0x sau 0X).
      %u - notaţie zecimală fără semn.
       %s         - caracterele din şir sunt tipărite până se întâlneşte '
0' sau cât timp numărul de caractere tipărit precizia.
       %f         - notaţie zecimală de forma [-]mmm.ddd, unde
numărul d-urilor este indicat de precizie; precizia implicită este 6, iar
o precizie egală cu zero elimina punctul zecimal.
      %e, %E - notaţie zecimală de forma:
                        [-]m.dddddde+/-xx              sau
                        [-]m.ddddddE+/-XX
                    unde numărul de d-uri este indicat de precizie
                    (precizia implicită este 6, iar o precizie egală cu 0
                    va elimina punctul zecimal).
       %g, %G - se utilizează %e sau %E dacă exponentul este mai
                  mic decât -4, sau precizie, în caz contrar se
                  utilizează %f.
      %p - afiseaza un pointer.
      %o - notaţie octalăa fără semn (fără 0 la început).
      %% - nu se face conversie, se tipăreşte "%".
       %n - nu se realizează conversie de argument; numărul de
caractere scrise până în acel moment este scris în argument.
      Există doi modificatori de format care permit funcţiei fprintf() să
afişeze întregii long şi short. Aceşti modificatori se pot aplică
caracterelor de conversie d, i, o, u şi x, precedându-i pe aceştia
(exemplu: %ld, %li, %lu). Modificatorul l poate prefixa şi caracterele
de conversie e, f şi g şi indică faptul că numerele tiparite sunt de tip
double. Modificatorul h comandă funcţiei fprintf() să afişeze short int.
Atunci %hu va preciza că data este de tip short unsigned int.]

      b) Funcţia printf()
      Forma funcţiei :
int printf("format", lista-argumente)
Funcţia printf() este echivalentă cu :
      fprintf(stdout, "format", lista_argumente)

                                   231
Exemplu:
            printf()                                     ieşire
            ("%-5.2f", 123.456)                     123.45
            ("%5.2f", 3.4565)                            3.45
            ("%10s", "hello")                       hello
            ("%-10s", "hello")                           hello
            (%5.7s", "123456789")                        1234567
Exemplu de utilizare a functiei fprintf.

/* Acest program foloseste fprintf pentru scrierea datelor cu diferite
formate intr-un fisier si apoi tipareste fisierul folosind functia sistem
system ce apeleaza comanda TYPE a sistemului de operare */
#include <stdio.h>
#include <process.h>
FILE *stream;
void main( void )
{ int     i = 10;
   double fp = 1.5;
   char   s[] = "this is a string";
   char   c = 'n';
   stream = fopen( "fprintf.out", "w" );
   fprintf( stream, "%s%c", s, c );
   fprintf( stream, "%dn", i );
   fprintf( stream, "%fn", fp );
   fclose( stream );
   system( "type fprintf.out" );}

10.4.8. Intrări cu format
Funcţiile de tip scanf() realizează conversiile de intrare cu format
     a) Funcţia fscanf()
     Forma acestei funcţii este:
int   fscanf(FILE *fp, "format", lista_argumente)
      Funcţia fscanf() citeşte din fişierul indirectat prin "fp" sub
controlul formatului "format" şi atribuie valorile citite argumentelor
următoare, fiecare argument trebuind să fie un pointer.
      Funcţia întoarce EOF dacă se detectează sfârşitul de fişier sau
apare o altă eroare înainte de orice conversie. În caz contrar, funcţia
întoarce numărul de elemente care au primit valori.
      Şirul "format" poate conţine:
      - specificatori de conversie, constând dintr-un caracter %, un
caracter opţional de suprimare a atribuirii; un număr opţional care
indică lungimea câmpului, un caracter opţional h, l sau L, care indică
lungimea argumentului şi un caracter de conversie;

                                   232
- spaţii sau caractere HT sau VT care se ignoră;
      - caractere obişnuite (diferite de %) care indică următorul
caracter diferit de caracterele albe cu care începe fişierul de intrare.
      Un câmp de intrare se defineşte ca un şir de caractere diferite de
cele albe şi se întinde până la următorul caracter alb (spaţiu, tab-uri,
CR, LF, VT, FF).
      Rezultatul conversiei unui câmp de intrare este plasat în
variabilă indicată de argumentul corespunzător din lista de argumente.
      Dacă se indică suprimarea atributului prin "*" ca în %*s, câmpul
de intrare este ignorat, fără a se face nici o atribuire.
      Descriptorii de conversie utilizaţi în C pentru citire sunt:
       %c - citeşte un singur caracter; caracterele următoare sunt
plasate în tablourile indicate, respectându-se numărul de caractere
indicat de lungimea câmpului; implicit este 1. Nu se adaugă '0'.
      %d - citeşte un număr întreg zecimal.
      %u - citeşte un număr întreg zecimal fără semn.
      %i - citeşte un număr întreg (intregul poate fi octal, cu 0 la
început, sau hexazecimal, cu 0x sau 0X la început).
      %o - întreg octal (cu sau fără zero la început).
      %x - întreg hexazecimal (cu sau fără 0x sau 0X la început).
      %s - şir de caractere diferite de caracterele albe, indicând
spre un tablou de caractere destul de mare pentru a memora şirul şi
caracterele terminator '0' care se va adauga.
      %e, %f, %g         - numere zecimale în virgulă mobilă.
      %p - citeşte valoarea pointerului.
      %n - se scrie în argument numerele de caractere citite până în
acel moment. Nu se citeşte nimic din intrare.
      %h - citeşte un întreg scurt.
      Un caracter obişnuit în şirul "format" determină ca funcţia
fscanf() să citească un caracter ce coincide cu cele din "format". De
exemplu, "%d, %d" face că fscanf() să citească un întreg, apoi
caracterul "," şi apoi un alt întreg. Dacă calculatorul nu găseşte
caracterul specificat, fscanf() va fi încheiată.
      Toate variabilele menite să primească valori prin fscanf() trebuie
să fie transmise prin adresele lor. Aceasta înseamnă că toate
argumentele trebuie să fie pointeri la variabilele utilizate ca
argumente.

     b) Funcţia scanf()
                                 233
Forma funcţiei:
      int   scanf("format", lista-argumente)
      Funcţia scanf() este echivalenta cu:
      fscanf(stdin, "format", lista-argumente)
Exemple:
scanf ("%d", &count); /* se citeşte un întreg în
variabilă count */
scanf ("%s", address); /* se citeşte un şir de caractere
în vectorul address */
scanf ("%d %d", &r, &c);    /* se citesc doua numere
separate prin spaţiu, tab sau linie noua */

      Un * plasat între % şi caracterul de conversie, va suspenda
atribuirea datei citite. Astfel, instrucţiunea :
      scanf("%d%*c%d", &x, &y);
face ca, dacă de la tastatură se introduce 10/20, 10 să fie atribuit lui x,
%*c este ignorat (nu este atribuit), iar 20 se atribuie lui y.
      Instrucţiunea :
      scanf("%20s", sir);
citeşte nu mai mult de 20 caractere în variabilă şir. Dacă se introduce
un şir de mai mult de 20 caractere, vor fi reţinute numai primele 20,
iar restul se pierd. Pentru caracterele rămase se poate apela din nou
funcţia scanf() sub forma :
      scanf("%s", sir);
care va plasa restul caracterelor tot în "şir".
      Dacă de la tastatura se introduce 10#20, instrucţiunea :
      scanf("%s#%s", &x, &y);
va plasa 10 în x şi 20 în y.
      Instrucţiunea :
      scanf("%s ", name);
nu se încheie decât dacă după ultimul caracter se introduce un spaţiu.
Exemplu de utilizare a funcţiilor fscanf şi fprintf.
/* Acest program scrie date cu format cu printf intr-un fisier. Apoi
foloseste fscanf pentru a citi datele din fisier */
#include <stdio.h>
FILE *stream;
void main( void )
{ long l;
   float fp;
   char s[81];
   char c;
   stream = fopen( "fscanf.out", "w+" );
   if( stream == NULL )

                                   234
printf( "Fisierul fscanf.out nu a fost deschisn" );
   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( "%sn", s );
      printf( "%ldn", l );
      printf( "%fn", fp );
      printf( "%cn", c );
      fclose( stream ); }}

10.4.9. Funcţii de citire şi scriere a caracterelor
      a) Funcţia fgetc()
      int fgetc(FILE *fp)
      Funcţia fgetc() întoarce următorul caracter al fişierului indirectat
cu "fp", caracter de tip unsigned char (convertit la int) sau EOF dacă
s-a detectat sfârşitul de fişier sau a apărut o eroare.
Exemplu de utilizare a funcţiei fgetc().
/* Acest program foloseste getc pentru a citi 80 de caractere dintr-un
fisier si apoi le plaseaza dintr-un buffer la intrarea standard */
#include <stdio.h>
#include <stdlib.h>
void main( void )
{ FILE *stream;
   char buffer[81];
   int i, ch;
/* Deschide fisierul pentru a citi o inregistrare */
   if( (stream = fopen( "fgetc.c", "r" )) == NULL )
      exit( 0 );
/* Citeste primele 80 de caractere si le plaseaza in
"buffer": */
   ch = fgetc(stream);
   for(i=0;(i<80) && (feof(stream)==0); i++ )
   { buffer[i] = (char)ch;
      ch = fgetc( stream );   }
   /* Adauga null la sfarsitul fisierului */
   buffer[i] = '0';
   printf( "%sn", buffer );
   fclose( stream );}

                                  235
b) Funcţia getc()
      int getc (FILE *fp)
       Această funcţie este identică cu fgetc() cu deosebirea că este o
macrodefiniţie, putând să evalueze fişierul mai mult decât o dată.
Observaţie: "fp" este un pointer-fişier returnat de funcţia fopen().
Exemplu: Pentru a citi un fişier text până la întâlnirea indicatorului de
sfârşit de fişier se scrie:
            ch = getch (fp);
            while (ch != EOF) {
                 ch = getc (fp); }

      c) Funcţia getchar()
      int getchar(void)
      Funcţia getchar() este echivalentă cu getc (stdin) .
      Dezavantajul funcţiei getchar() este că această poate păstra în
bufferul de intrare nu unul, ci mai multe caractere, primul caracter
fiind preluat după apasarea tastei CR.
      d) Funcţiile     getche() şi getch()
      int   getche(void)
      int   getch(void)
      Funcţiile introduc un caracter de la tastatură. Funcţiile asteaptă
până se apasă o tastă şi apoi întorc valoarea acesteia. Funcţia getche()
preia un caracter cu "ecou" iar getch() preia caracterul fără ecou.
      e) Funcţia gets()
        char *gets(char *s)
        Funcţia gets() citeşte un şir de caractere introduse de la tastatură
şi îl plasează în vectorul indirectat prin s. §irul se termină cu 'n' ce va
fi înlocuit cu '0'. Funcţia întoarce vectorul s sau EOF, în caz de
eroare.
       f) Funcţia fgets()
 char *fgets(char *s, int n, FILE *fp)
      Funcţia fgets() citeşte în tabloul s cel mult n-1 caractere,
oprindu-se dacă a detectat NL (New Line) care este inclus în tablou,
înlocuit prin'0'. Funcţia întoarce tabloul s, sau NULL, dacă apare o
eroare sau sfârşit de fişier.
Exemplu de folosire a funcţiei fgets.
/* Acest program utilizeaza fgets pentru afisarea unei linii dintr-un fisier
la display */
#include <stdio.h>
void main( void )
{ FILE *stream;
   char line[100];

                                    236
if( (stream = fopen( "fgets.c", "r" )) != NULL )
    {     if( fgets( line, 100, stream ) == NULL)
          printf( "fgets errorn" );
       else
          printf( "%s", line);
       fclose( stream ); }}
      a') Funcţia        fputc()
      int fputc(int ch, FILE *fp)
       Funcţia fputc() scrie caracterul "ch" convertit la unsigned char,
în fişierul indirectat prin "fp". Întoarce valoarea caracterului scris, sau
EOF, dacă apare vreo eroare.
Exemplu de utilizare a funcţie fputc.
/* Acest program foloseste fputc si _fputchar pentru a trimite un sir de
caractere la stdout. */
#include <stdio.h>
void main( void )
{ char strptr1[] = "Test pentru fputc !!n";
char strptr2[] = "Test pentru _fputchar!!n";
   char *p;
   /* Tipareste linia folosind fputc. */
   p = strptr1;
while((*p != '0') && fputc(*(p++), stdout)!=EOF);
    /* Identic cu _fputchar. (Aceasta functie nu este
compatibila ANSI */
   p = strptr2;
while((*p != '0') && _fputchar(*(p++))!=EOF);}
        În general, funcţiile care încep cu _ (subliniere) dar şi cu f (de la
file) sunt destinate lucrului cu interfeţele standard stdin şi stdout.
       b') Funcţia       putc()
      int putc(int ch, FILE *fp)
     Funcţia putc() este echivalenta cu fputc() cu excepţia că este o
macrodefinitie, putând evalua fişierul mai mult decât o dată.
     c') Funcţia putchar()
      int putchar(int ch)
      Funcţia putchar(ch) este echivalenta cu putc (ch, stdout).
      d') Funcţia de la punctul d) nu are un corespondent pentru ieşire.
      e') Funcţia puts()
      int puts(const char *s)
      Funcţia puts() scrie şirul "s" şi NL în "stdout". Întoarce EOF,
dacă apare o eroare, sau o valoare nenegativă în caz contrar.
      f') Funcţia fputs()
      int fputs(const char *s,FILE *fp)



                                    237
Funcţia fputs() scrie şirul "s" (care nu este neapărat necesar să
conţină 'n') în fişierul "fp". Întoarce EOF, în caz de eroare, o valoare
nenegativă, în caz contrar. Spre exemplu,
   /* Acest program foloseste fputs pentru a scrie o linie la terminalul
   standard */
#include <stdio.h>
void main( void )
{ fputs( "Hello world from fputs.n", stdout );}

      Funcţia ungetc()
      int ungetc(int ch,FILE *fp)
       Funcţia ungetc() pune argumentul "ch" inpoi în fişier, de unde
va fi preluat la următoarea citire. Se poate pune inapoi în fişier doar un
singur caracter. Marcajul EOF nu poate fi pus înapoi. Funcţia întoarce
caracterul ce trebuie pus, sau EOF, în caz de eroare.

      Funcţiile getw() şi putw()
      Aceste funcţii se folosesc pentru a citi, respectiv a scrie întregi
dintr-un sau într-un fişier disc. Aceste funcţii lucrează exact că
funcţiile getc() şi putc().
Exemplu de utilizare a functiilor fprintf() şi fscanf() :
       Programul permite actualizarea unei agende telefonice.
# include "stdio.h"
void ad_num(void);       /*prototipul functiilor */
void cauta(void);
char menu(void);
void main() {
     char choice;
     do { choice = menu();
      switch (choice) {
      case 'a' :    ad_num(); break;
      case 'c' :    cauta(); break; }
               } while (choice != 'q');     }
char menu() {/* Afiseaza meniul si preia optiunea */
     char ch;
     do { printf ("A(dauga), C(auta), Q(uit) : ");
       ch = tolower (getche());
       printf ("n");
     } while (ch != 'q' && ch != 'a' && ch != 'c');
     return ch;     }
void ad_num() /* Adauga un nou numar */
{FILE *fp;
     char name[80];
     int a_code, schimb, numar;

                                  238
if ((fp = fopen ("telefon", "a")) == NULL) {
     printf ("Cannot open file n"); exit (1);}
   printf("Introduceti numele si numarul: ");
fscanf(stdin,"%s%d%d%d",nume,&a_code,&schimb, &numar);
fscanf(stdin,"%*c");    /* inlatura CR din sirul de
intrare */
     /* Se scrie in fisier */
printf("%d",fprintf(fp,"%s %d %d %dn", nume, a_cod,
schimb, numar));
fclose (fp); }
void cauta() /* Cauta un numar dandu-se un nume */
     { FILE *fp;
     char nume[80], nume2[80];
     int a_code, schimb, numar;
     /* Se deschide fisierul pentru citire */
     if ((fp = fopen ("telefon", "r")) == NULL) {
     printf("Cannot open filen "); exit (1);   }
     printf ("Nume ?"); gets (nume);
     /* Se cauta numarul */
     while (!feof (fp)) {
fscanf(fp,"%s%d%d%d", nume2, &a_cod, &schimb, &numar);
      if (!strcmp(nume, nume2)) { printf("%s : (%d) %d -
%dn", nume, a_code, schimb, numar);
          break;} }
      fclose (fp);}
                         Capitolul XI

             UTILIZAREA ECRANULUI
                  ÎN MOD TEXT

        Biblioteca standard a limbajului C şi C++ conţine funcţii pentru
gestiunea ecranului. Acesta poate fi gestionat în două moduri: în mod
text sau în mod grafic.
        Prezentăm funcţiile standard mai importante utilizate la
gestiunea ecranului în mod text. Toate funcţiile standard de gestiune a
ecranului în mod text au prototipurile în fişierul antet <conio.h>.
        Modul text presupune că ecranul este compus dintr-un număr de
linii şi un număr de coloane. În mod curent se utilizează 25 de linii a
80 de coloane fiecare, deci ecranul are o capacitate de 25*80=2000 de
caractere.


                                 239
Poziţia pe ecran a unui caracter se defineşte printr-un sistem de
două coordonate întregi (x,y) unde:
       x este numărul coloanei în care este situat caracterul
       y este numărul liniei în care este situat caracterul
       Colţul din stânga sus al ecranului are coordonatele (1,1), iar
colţul din dreapta jos are coordonatele (80,25).
       În mod implicit, funcţiile de gestiune a ecranului în mod text au
acces la tot ecranul. Accesul poate fi însă limitat la o parte din ecran
utilizând aşa numitele ferestre. Fereastra este o parte a ecranului în
formă de dreptunghi şi care poate fi gestionată separat de restul
ecranului.
       Atributele unui caracter de pe ecran sunt următoarele:
                    - coordonatele x şi y;
                    - culoarea caracterului;
                    - culoarea fondului pe care este afişat caracterul;
                    - clipirea caracterului.

11.1. Setarea ecranului în mod text
       Se realizează cu ajutorul funcţiei textmode care are următorul
prototip:
      void textmode (int modtext)
     unde modtext poate fi exprimat simbolic sau numeric în modul
următor:
           Modul text            Constantă simbolică        Valoare
 Caractere albe pe fond negru;          BW40                   0
 40 de coloane
 Color 40 de coloane                     C40                    1
 Caractere albe pe fond negru;          BW80                    2
 80 de coloane
 Color 80 de coloane                     C80                    3
 Monocrome 80 de coloane                MONO                    7
 Color cu 50 de linii pentru            C4350                  64
 placa VGA
 Modul precedent                     LASTMODE                  -1

       Modul MONO se poate seta pe un adaptor monocolor, în timp
ce celelalte moduri se pot seta pe adaptoare color.


                                 240
11.2. Definirea unei ferestre
       După setarea ecranului în mod text, este activ tot ecranul şi
acesta are caracteristicile indicate în paragraful precedent. De multe
ori însă se doreşte partajarea ecranului în zone care să poată fi
gestionate în mod independent. Acest lucru poate fi realizat cu ajutorul
ferestrelor. O fereastră se defineşte cu ajutorul funcţiei window care
are următorul prototip:
      void window (int x1, int y1, int x2, int y2);
       unde: (x1,y1) – reprezintă coordonatele colţului stânga sus ;
             (x2,y2) – reprezintă coordonatele colţului dreapta jos.
       La un moment dat este activă o singură fereastră şi anume acea
definită la ultimul apel al funcţiei window. Dacă parametri de la
apelul funcţiei window sunt eronaţi, aceasta nu are nici un efect.

11.3. Ştergerea unei ferestre
       O fereastră activă se şterge utilizând funcţia clrscr care are
următorul prototip:
       void clrscr(void)
       După apelul acestei funcţii, fereastra activă (sau tot ecranul dacă
nu s-a definit în prealabil o fereastră cu funcţia window) devine vidă.
Fondul ei are culoarea definită prin culoarea de fond curentă.
       Funcţia clrscr poziţionează cursorul pe caracterul din stânga sus
al ferestrei active, adică în poziţia de coordonate (1,1).

11.4. Deplasarea cursorului
       Cursorul poate fi plasat pe un caracter al ferestrei active
folosind funcţia gotoxy ce are următorul prototip:
      void gotoxy (int x, int y);
       unde (x, y) reprezintă coordonatele caracterului pe care se
plasează cursorul. Dacă la apelul funcţiei coordonatele sunt definite în
afara ferestrei active, funcţia este ignorată.
       Poziţia cursorului din fereastra activă poate fi determinată cu
ajutorul funcţiilor wherex şi wherey care returnează numărul
coloanei, respectiv liniei, din fereastra activă pe care se află cursorul,
şi care au următoarele prototipuri:
      int wherex(void);

                                  241
int wherey(void);
       În cazul în care se doreşte ascunderea cursorului (facerea
invizibilă a cursorului) se utilizează o secvenţă ce utilizează funcţia
geninterrupt:
{
_AH=1;
_CH=0x20;
geninterrupt(0x10); }
Cursorul poate fi rafiaşat utilizând următoarea secvenţă:
{ _AH=1;
_CH=6;
_CL=7;
geninterrupt(0x10);}


11.5. Setarea culorilor
      Culoarea fondului se setează              cu      ajutorul       funcţiei
textbackground ce are următorul prototip:
      void textbackground(int culoare);
       unde culoare este un întreg în intervalul [0, 7] şi are
semnificaţia din tabelul de mai sus.
       Pot fi setate ambele culori precum şi clipirea caracterului
folosind funcţia textattr ce are următorul prototip:
      void textattr (int atribut)
      unde atribut se defineşte cu ajutorul relaţiei:
      atribut=16*culoare_fond+culoare_caracter+clipire;
       Culoarea caracterelor se setează cu ajutorul funcţiei textcolor
ce are următorul prototip:
      void textcolor(int culoare);
      unde culoare este un întreg în intervalul [0,15] a cărui
semnificaţie este explicată de tabelul următor:
      Culoare             Constantă simbolică           Valoare
           Negru                  BLACK                            0
          Albastru                 BLUE                            1
           Verde                  GREEN                            2
          Turcoaz                 CYAN                             3
            Roşu                    RED                            4
          Purpuriu              MANGETA                            5
            Maro                 BROWN                             6
         Gri deschis           LIGHTGRAY                           7
          Gri închis           DARKGRAY                            8
       Albastru deschis        LIGHTBLUE                           9
                                  242
Verde deschis          LIGHTGREEN                      10
      Turcoaz deschis         LIGHTCYAN                       11
       Roşu deschis            LIGHTRED                       12
      Purpuriu deschis      LIGHTMANGETA                      13
          Galben                YELLOW                        14
            Alb                  WHITE                        15
          Clipire                BLINK                       128

11.6. Funcţii pentru gestiunea textelor
      Pentru afişarea caracterelor se pot folosi funcţiile:
 -   int putch (int c); – afişează un singur caracter;
 -   int cputs (const char *str); – afişează un şir de caractere în
     mod similar funcţiei puts;
 -   int cprintf (const char *format); – afişează date sub controlul
     formatelor în mod similar funcţiei printf.
 -   void clreol (void); - şterge sfârşitul liniei începând cu poziţia
     cursorului;
 -   void delline (void); - şterge toată linia pe care este poziţionat
     cursorul;
 -   int gettext (int left, int top, int right, int bottom, void
     *destination); - copiază textul cuprins în dreptunghiul definit de
     coordonatele (left, top) – stânga sus şi (right, bottom) – dreapta
     jos la adresa de memorie indicată de pointerul destination;
 -   int puttext( int left, int top, int right, int bottom, void *source
     ); - citeşte textul cuprins în dreptunghiul definit de coordonatele
     (left, top) – stânga sus şi (right, bottom) – dreapta jos de la
     adresa de memorie indicată de pointerul source;
 -   int movetext( int left, int top, int right, int bottom, int
     destleft, int desttop ); - mută textul cuprins în dreptunghiul
     definit de coordonatele (left, top) – stânga sus şi (right, bottom)
     – dreapta jos în dreptunghiul cu coordonatele colţului din stânga
     sus (destleft, desttop);
 -   void insline (void); - inserează o linie vidă în fereastra activă;
 -   int getch (void); - citeşte un caracter fără ecou de la tastatură,
     adică după ce este citit caracterul nu mai este afişat pe ecran;
     funcţia returnează codul ASCII al caracterului citit de la
     tastatură.
                                 243
-    int getche (void); - citeşte un caracter cu ecou de la tastatură,
      adică după ce este citit caracterul este afişat automat pe ecran;
      funcţia returnează codul ASCII al caracterului citit de la
      tastatură.
  - int kbhit (void); - controlează dacă s-a tastat ceva la tastatură.
      Dacă a fost apăsată o tastă se returnează o valoare diferită de
      zero, altfel se returnează valoarea 0.
Exemplu: Următorul program desenează o fereastră şi scrie un număr
în aceasta.
#include   <stdio.h>
#include   <stdlib.h>
#include   <conio.h>
#include   <alloc.h>
#include   <dos.h>

#define MAX 100
#define SIMPLU 1
#define DUBLU 2
typedef struct{
int x,y,u,v;
void *zonfer;
}ELEM;
ELEM *stiva[MAX];
int istiva;
void orizontal(int,int);
void vertical(int,int,int,int);
void fereastra(int st,int sus,int dr,int jos,int fond,int
culoare,
int chenar,int n)
//Afiseaza o fereastra limitata de un chenar
{ extern ELEM *stiva[];
extern int istiva;
//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 insuficientan");
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!");

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

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




                       Capitolul XII

             UTILIZAREA ECRANULUI
                 ÎN MOD GRAFIC

     Pentru aplicaţiile grafice limbajul C pune la dispoziţie peste 60
de funcţii standard ce au prototipul în fişierul graphics.h. În

                                246
continuare sunt prezentate cele mai importante funcţii ce permit
gestiunea ecranului în mod grafic.

12.1. Iniţializarea modului grafic
         Pentru a se putea lucra în mod grafic trebuie realizată o
iniţializare utilizând funcţia initgraph. Aceasta poate fi folosită
singură sau împreună cu o altă funcţie numită detectgraph care
determină parametrii adaptorului grafic. Prototipul ei este:

        void far detectgraph(int far *gd, int far *gm);
unde:
       Pointerul gd păstrează adresa uneia din valorile din tabelul
următor (în funcţie de adaptorul grafic utilizat):

           Constantă simbolică               Valoare
                        CGA                             1
                       MCGA                             2
                        EGA                             3
                       EGA64                            4
                    EGAMONO                             5
                     IBM8514                            6
                   HERCMONO                             7
                      ATT400                            8
                        VGA                             9
                      PC3270                           10
       Valorile spre care pointează gd definesc nişte funcţii standard
corespunzătoare adaptorului grafic. Aceste funcţii se numesc drivere.
Ele se află în subdirectorului BGI. Funcţia detectgraph detectează
adaptorul grafic prezent la calculator şi păstrează valoarea
corespunzătoare acestuia în zona spre care pointează gd.
       Zona spre care pointează gm memorează una din valorile:

    Adaptor          Constantă simbolică -         Rezoluţie
                     Valoare




                                 247
CGA                 CGAC0 – 0                   320*200
                              CGAC1 – 1                   320*200
                              CGAC2 – 2                   320*200
                              CGAC3 – 3                   320*200
                              CGAHI – 4                   640*200
          EGA                 EGALO – 0                   640*200
                              EGAHI – 1                   640*350
          VGA                 VGALO – 0                   640*200
                             VGAMED – 1                   640*350
                              VGAHI – 2                   640*480
        Modul grafic se defineşte în aşa fel încât el să fie cel mai
performant pentru adaptorul grafic curent. Cele mai utilizate adaptoare
sunt cele de tip EGA şi VGA.
        Apelul funcţiei detectgraph trebuie să fie urmat de apelul
funcţiei initgraph. Aceasta setează modul grafic în conformitate cu
parametri stabiliţi de apelul prealabil al funcţiei detectgraph şi are
următorul prototip:

void far     initgraph(int       far   *gd,int    far    *gm,   int    far
*cale);
unde:
         gd şi gm sunt pointeri ce au aceeaşi semnificaţie ca şi în cazul
funcţiei detectgraph;
         cale este pointer spre şirul de caractere care defineşte calea
subdirectorului BGI care conţine driverele.
         De exemplu dacă BGI este subdirector al directorului
BORLANDC, atunci se utilizează şirul de caractere:
                         ”C:BORLANDCBGI”
Exemplu: Pentru setarea în mod implicit a modului grafic se poate
utiliza următoarea secvenţă de instrucţiuni:
        ……………………………
        int gd,gm;
        detectgraph(&gd,&gm);
        initgraph(&gd,&gm, ”C:BORLANDCBGI”);
        ……………………………
         Doar după apelul funcţiei initgraph pot fi utilizate şi alte
funcţii de gestionare a ecranului în mod grafic.
         Utilizatorul poate defini el însuşi parametri pentru iniţializarea
modului grafic. De exemplu, secvenţa următoare:
        ……………………………

                                   248
int gd=1,gm=0;
      initgraph(&gd,&gm, ”C:BORLANDCBGI”);
      ……………………………
setează modul grafic corespunzător unui adaptor grafic CGA cu
rezoluţia 320*200 de puncte.
       În afara acestor funcţii mai pot fi utilizate şi următoarele funcţii:
       void far setgraphmode (int mode) – utilizată pentru setarea
modului grafic unde mode are valorile 0 – 4 pentru VGA, 0-1 pentru
EGA, 0 – 2 pentru VGA;
       void far retorecrtmode(void) – ce permite revenirea la modul
precedent;
       void far graphdefaults(void) – repune parametri grafici la
valorile implicite;
       int far getgraphmode(void) – returnează codul modului grafic;
       char *far getmodename(int mod) – returnează pointerul spre
numele modului grafic definit de codul numeric mod;
       char *far getdrivername(void) – returnează pointerul spre
numele drieverului corespunzător adaptorului grafic curent;
       void far getmoderange(int grafdriv,int far *min, int far *max)
– defineşte valorile minimale şi maximale ale modului grafic utilizat.
       void far closegraph(void) – se utilizează pentru a ieşi din
modul grafic.

12.2. Gestiunea culorilor
       Adaptoarele grafice sunt prevăzute cu o zonă de memorie în
care se păstrează date specifice gestiunii ecranului. Această zonă de
memorie poartă denumirea de memorie video.
       În mod grafic, ecranul se consideră format din puncte luminoase
numite pixeli. Poziţia pe ecran a unui pixel se defineşte prin două
valori întregi: (x,y)
       unde: x – defineşte coloana în care este afişat pixelul;
              y – defineşte linia în care este afişat pixelul.
       Fiecărui pixel îi corespunde o culoare ce este pătrată în
memoria video. Numărul maxim de culori care pot fi afişate cu
ajutorul unui adaptor EGA este 64.. Culorile se codifică prin numere
întregi din intervalul [0, 63] şi prin constante simbolice. Cele 64 de
culori nu pot fi afişate simultan. În cazul adaptorului EGA pe ecran se


                                   249
pot afişa cel mult 16 culori ce formează o paletă. Paleta implicită este
dată de tabelul următor:
              Denumire simbolică               Valoare
                   BLACK                          0
                    BLUE                          1
                   GREEN                          2
                    CYAN                          3
                     RED                          4
                 MANGETA                          5
                   BROWN                          6
                LLIGHTGRAY                        7
                 DARKGRAY                         8
                 LIGHTBLUE                        9
                LIGHTGREEN                       10
                LIGHTCYAN                        11
                 LIGHTRED                        12
              LIGHTMANGETA                       13
                  YELLOW                         14
                   WHITE                         15

       În mod implicit, culoarea fondului este întotdeauna cea
corespunzătoare indicelui zero, iar culoarea pentru desenare este cea
corespunzătoare indicelui 15.
       Pentru controlul culorilor pot fi utilizate următoarele funcţii:
       void far setbkcolor(int culoare) – modifică culoarea
fundalului;
       int far getbkcolor(void) – returnează indexul din tabloul care
defineşte paleta pentru culoarea fundalului;
       void far setcolor(int culoare) – setează culoarea utilizată
pentru desenare;
       int far getcolor(void) – returnează indexul din tabloul care
defineşte paleta pentru culoarea de desenare;
       void far setpalette(int index,int cod) – setează o nouă culoare
în paleta ce este utilizată la colorare (index ia valori între [0, 15] iar
cod între [0, 63]);
       void far setallpalette(struct palettetype far* paleta) –
modifică mai multe culori din paletă. Palettetype este o structură
definită ca mai jos:
      struct palettetype {
      unsigned char size;

                                  250
signed char colors[MAXCOLORS+1];
      };
          unde size – este dimensiunea paletei;
                colors – este un tablou ale cărui elemente au ca valori
codurile culorilor componente ale paletei care se defineşte.
          Modificarea paletei curente cu ajutorul funcţiei setpalette sau
setallpalette conduce la schimbarea corespunzătoare a culorilor afişate
pe ecran în momentul apelului funcţiilor respective.
       void far getpalette(struct palettetype far* paleta) – determină
codurile culorilor componente ale paletei curente;
       int far getmaxcolor(void) – returnează numărul maxim de
culori diminuat cu 1;
       int far getpalettesize(void) – returnează numărul culorilor
componente ale paletei.

12.3. Setarea ecranului
       În mod grafic, ecranul se compune din n*m puncte luminoase
(pixeli), adică pe ecran se pot afişa m linii a n pixeli fiecare. Poziţia
unui pixel este dată de două numere întragi (x,y) numite coordonatele
pixelului. Pixelul aflat în stânga sus are coordonatele (0,0). Coloanele
se numerotează de la stânga la dreapta, iar liniile de sus în jos.
       Informaţii referitoare la ecran pot fi obţinute cu ajutorul
următoarelor funcţii:
       int far getmaxx(void) – returnează coordonta maximă pe
orizontală;
       int far getmaxy(void) – returnează coordonta maximă pe
verticală;
       int far getx(void) – returnează poziţia pe orizontală a pixelului
curent;
       int far gety(void) – returnează poziţia pe verticală a pixelului
curent.

12.4. Utilizarea textelor în mod grafic
      Afişarea textelor în modul grafic presupune definirea unor
parametri care pot fi controlaţi prin intermediul funcţiilor descrise în
continuare:
      a) void far settextstyle(int font,int direcţie,int charsize)
                                  251
unde:
     font – defineşte setul de caractere şi poate lua următoarele valori:
            Constantă simbolică             Valoare
             DEFAULT_FONT                      0
             TRIPLEX_FONT                      1
              SMALL_FONT                       2
            SANS_SERIF_FONT                    3
              GOTHIC_FONT                      4

     direcţie – defineşte direcţia de scris a textului, astfel:
                - de la stânga la dreapta: HORIZ_DIR;
                - de jos în sus: VERT_DIR.
      charsize – defineşte dimensiunea caracterului în pixeli, astfel:
               Valoarea            Matricea utilizată pentru
             parametrului      afişarea caracterului (în pixeli)
                   1                         8*8
                   2                        16*16
                   3                        24*24
                 ….                         ……..
                  10                        80*80

        b) void far settextjustify(int oriz, int vert) – defineşte
             cadrajul textului;
         oriz – defineşte încadrarea pe orizontală, astfel:
                  - în stânga: LEFT_TEXT;
                  - în centru: CENTER_TEXT;
                  - în dreapta: RIGHT_TEXT.
         vert – defineşte încadrarea pe verticală, astfel:
                  - marginea inferioară: BOTTOM_TEXT;
                  - în centru: CENTER_TEXT;
                  - marginea superioară: TOP_TEXT.
       După setarea acestor parametri pot fi afişate texte folosind
funcţiile outtext şi outtextxy care au următoarele prototipuri:
       void far outtext(char far* şir) , unde şir este un pointer spre
zona de memorie în care se păstrează caracterele de afişat, afişează
caracterele începând cu poziţia curentă de pe ecran;
       void far outtextxy(int x,int y,char far* şir) , unde şir este un
pointer spre zona de memorie în care se păstrează caracterele de afişat,
x,y defineşte poziţia de pe ecran unde se face afişarea.

                                  252
Dimensiunile în pixeli ale unui şir de caractere se pot determina
utilizând funcţiile textheight şi textwidth:
       void far textheight(char far* şir) – returnează înălţimea în
pixeli a şirului păstrat în zona spre care pointează şir,
       void far textwidth(char far* şir) – returnează lălţimea în
pixeli a şirului păstrat în zona spre care pointează şir.

12.5. Gestiunea imaginilor
       În modul grafic, ecranul poate fi partajat în mai multe părţi ce
pot fi gestionate independent. Aceste părţi se numesc ferestre grafice.
Următoarele funcţii sunt utilizate pentru prelucrarea ferestrelor
grafice:
       void far setviewport(int st, int sus, int dr, int jos, int d) –
defineşte o fereastră grafică, unde:
 - (st,sus) – coordonatele colţului stânga sus al ferestrei;
 - (dr,jos) – coordonatele colţului dreapta jos al ferestrei;
 - d – indicator cu privire la decuparea desenului. Dacă d are
      valoarea 1, atunci funcţiile de afişare a textelor şi de desenare nu
      pot scrie sau desena în afara limitelor ferestrei.
         void far clearviewport(void) – şterge fereastra activă; după
apelul acestei funcţii, toţi pixelii ferestrei au aceeaşi culoare, şi anume
culoarea de fond, iar poziţia curentă a cursorului este punctul de
coordonate relative (0,0);
         void far cleardevice(void) – şterge tot ecranul iar poziţia
curentă a cursorului este colţul din stânga sus al ecranului;
         void far getviewsettings(struct viewporttype far* fereastra)
– returnează parametri ferestrei active.
         Imaginea ecranului se păstrează în memoria video a
adaptorului grafic şi formează o pagină. Funcţiile următoare sunt
utilizate pentru gestionarea paginilor
         void far setactivepage(int nrpag) – activează o pagină al
cărei număr este specificat de parametrul nrpag;
         void far setvisualpage(int nrpag) – cu toate că în mod
normal este vizualizată pe ecran pagina activă, utilizatorul are
posibilitatea de a vizualiza altă pagină decât cea activă utilizând
această funcţie (această funcţie poate fi utilă pentru animaţie);


                                   253
void far getimage(int st, int sus, int dr, int jos,void far* zt) –
salvează o zonă dreptunghiulară de pe ecran, unde:
 - (st,sus) – coordonatele colţului stânga sus a zonei de pe ecran ce
     se salvează;
 - (dr,jos) – coordonatele colţului dreapta jos a zonei de pe ecran ce
     se salvează;
 - zt – pointer spre zona de memorie în care se salvează imaginea de
     pe ecran.
       unsigned far imagesize(int st, int sus, int dr, int jos) –
determină dimensiunea unei zone dreptunghiulare de pe ecran, unde:
 - (st,sus) – coordonatele colţului stânga sus a zonei de pe ecran;
 - (dr,jos) – coordonatele colţului dreapta jos a zonei de pe ecran.
       void far putimage(int st, int sus, int jos,void far* zt, int op) –
afişează oriunde pe ecran o zonă dreptunghiulară salvată cu funcţia
getimage, unde:
 - (st,sus) – coordonatele colţului stânga sus a zonei de pe ecran ce
     se salvează;
 - zt – pointer spre zona de memorie în care se păstrează imaginea
     ce se va afişa pe ecran;
 - op – defineşte operaţia între datele aflate în zona spre care
     pointează zt şi cele existente pe ecran în zona dreptunghiulară
     definită de parametri st, sus. Parametrul op se defineşte astfel:
 Constantă      Valoare                        Acţiune
 simbolică
 COPY_PUT          0           copiază imaginea din memorie pe ecran
 XOR_PUT           1      „sau exclusiv” între datele de pe ecran şi cele din
                                                memorie
   OR_PUT          2      „sau” între datele de pe ecran şi cele din memorie
  AND_PUT          3       „şi” între datele de pe ecran şi cele din memorie
  NOT_PUT          4           copiază imaginea din memorie pe ecran
                                 completând datele aflate în memorie

12.6. Desenarea şi colorarea figurilor geometrice
       Biblioteca standard pune la dispoziţia utilizatorului o serie de
funcţii care permit desenarea şi colorarea unor figuri geometrice:



                                    254
void far putpixel(int x, int y, int culoare) – afişează un pixel
pe ecran în punctul de coordonate (x,y) (relativ la fereastra activă) şi
având culoarea culoare;
       unsigned far getpixel(int x, int y) – determină culoarea unui
pixel aflat pe ecran în poziţia (x,y);
       void far moveto(int x, int y) – mută cursorul în dreptul
pixelului de coordonate (x,y);
       void far moverel(int dx, int dy) – mută cursorul în dreptul
pixelului de coordonate (x+dx,y+dy), unde (x,y) reprezintă
coordonatele pixelului curent;
       void far line(int xi, int yi, int xf, int yf) – trasează un segment de
dreaptă între punctele de coordonate (xi,yi) şi (xf,yf);
       void far lineto(int x, int y) – trasează un segment de dreaptă
între punctul curent şi punctul de coordonate (x,y);
       void far linerel(int dx, int dy) – trasează un segment de dreaptă
între punctul curent şi punctul de coordonate (x+dx,y+dy), unde (x,y)
sunt coordonatele punctului curent;
       void far arc(int xcentru, int ycentru, int unghistart, int
unghifin,int raza) – trasează un arc de cerc, unghiurile fiind exprimate
în grade sexagesimale;
       void far circle(int xcentru, int ycentru, int raza) – trasează un
cerc, cu (xcentru,ycentru) coordonatele centrului şi raza raza acestuia;
       void far ellipse(int xcentru, int ycentru, int unghistart, int
unghifin,int semiaxamare, int semiaxamică) – trasează un arc de
elipsă cu centrul în punctul de coordonate (xcentru,ycentru), semiaxa
mare definită de parametrul semiaxamare iar semiaxa mică definită de
parametrul semiaxamică;
       void far rectangle(int st, int sus, int dr, int jos) – trasează un
dreptunghi definit de colţurile diagonal opuse;
       void far drawpoly(int nr, int far* tabpct) – trasează o linie
polignală, parametrul nr specificând numărul de laturi iar tabpct este
un pointer spre un tablou de întregi ce definesc vârfurile liniei
poligonale păstrate sub forma: abscisa_i,ordonata_i unde i are valorile
1,2,…., nr+1;
       void far setlinestyle(int stil, unsigned şablon, int grosime) –
defineşte stilul utilizat pentru trasarea liniilor, unde:
       stil – este un întreg din intervalul [0,4] care defineşte stilul liniei
conform următorului tabel:

                                    255
Constantă simbolică     Valoare                       Stil
    SOLID_LINE              0                     Linie continuă
  DOTTED_LINE               1                     Linie punctată
   CENTER_LINE              2        Linie întreruptă formată din liniuţe de
                                                două dimensiuni
   DASHED_LINE               3       Linie întreruptă formată din liniuţe de
                                               aceeaşi dimensiune
   USERBIT_LINE              4        Stil definit de utilizator prin şablon
        şablon – defineşte stilul liniei şi are sens doar când parametrul
stil are valoarea 4;
        grosime – defineşte lăţimea liniei în pixeli, astfel:
NORM_WIDTH – valoarea 1 pixel şi THICK_WIDTH – valoarea 3
pixeli.
        void far getlinesettingstype(struct linesettingstype far*
linieinfo) – este utilizată pentru a determina stilul curent;
        void far bar(int st, int sus, int dr, int jos) – are aceeaşi
semnificaţie cu funcţia rectangle însă dreptunghiul este colorat;
        void far bar3d(int st, int sus, int dr, int jos, int profunzime, int
ind) – funcţia desenează o prismă colorată pentru ind diferit de zero;
pentru ind=0, nu se trasează partea de sus a prismei;
        void far pieslice(int xcentru, int ycentru, int unghistart, int
unghifin,int raza) – desenează un sector de cerc colorat;
        void far fillpoly(int nr, int far* tabpct) – desenează un
poligon colorat;
        void far fillellipse(int xcentru, int ycentru, int semiaxamare,
int semiaxamică) – desenează o elipsă colorată;
        void far setfillstyle(int haşura, int culoare) – defineşte modul
de colorare al figurilor, astfel:
        culoare – defineşte culoarea utilizată pentru haşurare;
        haşura – defineşte haşura utilizată pentru colorare conform
tabelului:
                Constantă simbolică                    Valoare
                  EMPTY_FILL                              0
                   SOLID_FILL                             1
                    LINE_FILL                             2
                 LTSLASH_FILL                             3
                  SLASH_FILL                              4
                 BKSLASH_FILL                             5
                LTBKSLASH_FILL                            6
                  HATCH_FILL                              7
                                   256
XHATCH_FILL                            8
               INTERLEAVE_FILL                          9
                WIDE_DOT_FILL                          10
                CLOSE_DOT_FILL                         11
                  USER_FILL                            12

       void far setfillpattern(char far *h_utilizator,int culoare) –
este utilizată pentru a defini o haşură a utilizatorului, astfel:
       culoare – defineşte culoarea de haşurare;
       h_utilizator – este un pointer spre o zonă de memorie care
defineşte haşura utilizatorului;
       void far getfillsettings(struct fillsettingstype far* stilculoare)
– este utilizată pentru determinarea stilului curent de colorare;
       void far floodfill(int x, int y, int culoare) – este o funcţie
utilizată pentru colorarea unui domeniu închis, astfel:
       (x,y) – reprezintă coordonatele unui punct din interiorul
domeniului închis;
       culoare – defineşte culoarea utilizată la trasarea conturului
figurii (interiorul este colorat în conformitate cu setările efectuate de
funcţia setfillstyle).
Exemplu: Prezentăm în acest exemplu un model de utilizare a
modului grafic pentru trasarea graficelor unor funcţii matematice
elementare.
#include <stdio.h>
#include <math.h>
#include <graphics.h>
#include <conio.h>
int x,y;
float a,b;
void desen(void) //functia care deseneaza axele si
                 //coloreaza ecranul
{ cleardevice();
setbkcolor(14);
setcolor(12);
line(0,y,2*x,y);
line(x,0,x,2*y);
line(2*x-4,y-4,2*x,y);
line(2*x-4,y+4,2*x,y);
line(x,0,x-4,4);
line(x,0,x+4,4); }

void interval(int l1, int l2) //functia care verifica
// daca intervalele pe care sunt definite functiile

                                  257
// trigonometrice, sunt respectate
{ while ((a<l1)||(b>l2))
 { clrscr();
cleardevice();
setbkcolor(0);
printf("reintroduce-ti intervalul    astfel   incat   sa   fie
cuprins intre -1 si 1:n ");
printf("a=");
scanf("%f",&a);
printf("n");
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)

                           258
{ return atan(1/x);}

void main()
{ int p,l,t,dr=DETECT, modgr;
initgraph(&dr,&modgr,"c:borlandcbgi");
setbkcolor(1);
x=getmaxx()/2,y=getmaxy()/2;
p=0;
while(p==0)
{ setbkcolor(1);
p=1;
printf("1 : sin(x)n");
printf("2 : cos(x)n");
printf("3 : tg(x)n");
printf("4 : ctg(x)n");
printf("5 : arcsin(x)n");
printf("6 : arccos(x)n");
printf("7 : arctg(x)n");
printf("8 : arcctg(x)n");
printf("Alegeti nr. corespunzator functiei dorite: ");
scanf("%d",&t);
while(t<1 || t>8 )
{ printf("Reintroducet t-ul cuprins intre 1 si 8n ");
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;

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




                       Capitolul XIII

               FUNCŢII MATEMATICE


       Limbajul C conţine mai multe funcţii matematice care utilizează
argumente de tip double şi întorc valori de tip double.
       Aceste funcţii se împart în următoarele categorii:
            - funcţii trigonometrice;
            - funcţii hiperbolice;
            - funcţii exponenţiale şi logaritmice;
            - alte tipuri.
       Toate funcţiile matematice sunt incluse în fişierul antet
"math.h". Acesta mai conţine o serie de macrodefiniţii cum ar fi
EDOM, ERANGE şi HUGE_VAL. Macrodefiniţiile EDOM şi
ERANGE se găsesc în fişierul "errno.h" şi sunt constante întregi
diferite de zero, utilizate pentru a semnala erorile de domeniu şi de
plajă ale funcţiei. HUGE_VAL (aflată tot în "errno.h") este o valoare
pozitivă de tip double.
       Dacă un argument al unei funcţii matematice nu este în
domeniul pentru care a fost definită funcţia, atunci funcţia întoarce 0
şi în domeniul de eroare, "errno" este modificat la EDOM.
       Dacă o funcţie produce un rezultat prea mare pentru a fi
reprezentat printr-un double, apare o depăşire, funcţia returnând
HUGE_VAL cu semnul adecvat iar "errno" este modificat la
ERANGE. Dacă se produce subdepăşire, funcţia întoarce zero, iar
"errno" este modificat la ERANGE în funcţie de implementare.
                                 260
13.1 Funcţii trigonometrice

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

Exemplu: Programul următor afişează valorile sinusului, cosinusului
şi tangentei unghiului a[-1,+1] radiani, din 0.1 în 0.1.

     # include <math.h>
     void main() { double val = -1.0;
      do {
  printf("sinusul lui %f este %fn", val, sin(val));
  printf("cosinusul lui %f este %fn",val, cos(val));
  printf("tangenta lui %f este %fn", val, tan(val));
  val += 0.1;}
     while (val <= 1.0);     }



13.2 Funcţii trigonometrice inverse

     - asin(x) , cu x [-1,1]       - arcsinusul lui x;
     - acos(x) , cu x [-1,1]       - arccosinusul lui x;
     - atan(x) , x R               - arctangenta lui x;
     - atan(y,x) ,                 - returneaza arctg (y/x).
Exemplu: Programul următor afişează valorile arcsinusului,
arccosinusului şi arctangentei unghiului a[-1,+1], din 0.1 în 0.1.
     # include <math.h>
     void main() {
double val = -1.0;
     do {
printf("arcsin lui %f este %fn", val, asin(val));
printf("arccos lui %f este %fn", val, asin(val));
printf("arctg lui %f este %fn", val, asin(val));
     val += 0.1;
     }
 while (val <= 1.0); }


13.3 Funcţii hiperbolice

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

13.4 Funcţii exponenţiale şi logaritmice

      - exp(x) , x R         - exponentiala lui x.
      - log(x) , x > 0       - logaritmul natural al lui x;
      - log10(x) ,      x>0         - logaritmul zecimal al lui x.
       Exemplu: printf ("Valoarea lui e este: %f", exp(1.0));
      Exemplu: Programul următor afişează valorile logaritmului
natural şi logaritmului zecimal din 1 în 1 al numerelor de la 1 la 10.
    # include <math.h>
     void main() {
     double val =1.0;
     do{printf("%f %f %fn",val,log(val),log10(val));
     val ++; }
     while (val < 11.0);}
     - pow(x,y); funcţia calculeaza xy. O eroare de domeniu apare
dacă x = 0 şi y = 0 sau dacă x < 0 şi y nu este întreg.
Exemplu: Programul următor afişeaza primele 11 puteri ale lui 10.
  # include <math.h>
      void main() { double x =10.0, y = 0.0;
    do {
printf ("%fn", pow(x,y));
      y ++;} while (val < 11.0);
}

13.5 Generarea de numere aleatoare
      În multe aplicaţii este necesară generarea de numere aleatoare.
Pentru asemenea cazuri limbajul C dispune de două funcţii, rand şi
random, care returnează numere întregi aleatore.
      Funcţia rand are următorul prototip:
                           int rand(void)
şi returnează un număr întreg, aleator, cuprins în intervalul de la 0 la
RAND_MAX (valoare definită în fişierul antet stdlib.h).
       Funcţia random are prototipul:
                   int random(int val_maxima)

                                 262
şi returnează un număr întreg, aleator, cuprins în intervalul [0,
    val_maxima]. Pentru generarea de numere aleatoare în virgulă mobilă
    se împarte rezultatul funcţiei random la o valoare întreagă. Următorul
    program exemplifică utilizarea acestor funcţii:
    Exemplu:

    #include <stdio.h>
    #include <stdlib.h>
     void main(void)
    { int k;
     printf(”Valorile furnizate de functia randn”);
         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 1n”);
         for(k=0;k<10;k++) printf(”%f ”,random(10)/10.0);
    printf(”Valori intregi intre -10 si 10n”);
         for(k=0;k<10;k++) printf(”%d ”,10-random(20));}



    13.6 Alte tipuri de funcţii matematice

        Nume funcţie                     Caracterizarea funcţiei
    sqrt(x)            -   - radicalul lui x, sqrt(x)=   x
-    ceil(x)           -   - cel mai mic întreg, mai mare că x, convertit la
                           double (ex. ceil(1.05) va returna valoarea 2).
-    floor(x)          -   - cel mai mare întreg, mai mic sau egal cu x,
                           convertit la double (exemplu: floor(1.02) va returna
                           valoarea 1.0, floor(-1.02) va returna valoarea -2.0).
-    fabs(x)           -   - modulul numărului x;
-    ldexp(x, n)       -   - calculeaza x*2n , unde n este de tip int.
-    fmod(x, y)        -   - restul în virgulă mobila a lui x/y, cu acelaşi semn
                           ca x.
-    modf(x, double *ip)
                      -    - împarte pe x în parte întreagă şi parte fracţionară,
                           fiecare cu acelaşi semn că x; memorează partea
                           întreagă în "*ip" şi întoarce partea fracţionară.
-    modf(x, double *ip)
                      -    - întoarce x într-o funcţie normalizată în intervalul
                           [1/2, 1] şi o putere a lui 2, care se memoreaza în
                           "*exp"; dacă x este 0, ambele părţi ale rezultatului
                                        263
sunt 0.
       Modul de utilizare al acestor funcţii este similar cu al celorlalte
funcţii matematice descrise în acest capitol.




                        Capitolul XIV

          ELEMENTE DE PROGRAMARE
                 AVANSATĂ

14.1 Gestionarea memoriei

      Un calculator poate avea trei tipuri de memorie: convenţională,
extinsă şi expandată. În programare memoria constituie un factor
important ce influenţează viteza de lucru a programelor. Fiecare tip de
memorie are diferite viteze de acces, ceea ce afectează performanţa
programelor. Volumul şi tipul de memorie instalată poate fi
determinat utilizând comanda DOS: C:>MEM /CLASSIFY (pentru
versiuni ale sistemului de operare DOS mai mari de varianta 5).
Sistemul de operare DOS dispune de capacităţi de gestionare a
memoriei ce pot maximiza performanţele calculatorului.
14.1.1 Memoria convenţională


                                  264
Primul PC compatibil IBM utiliza de obicei între 64Kb şi
256Kb memorie RAM (Read Only Memory). Pe atunci această
memorie era mai mult decât suficientă. Astăzi, memoria convenţională
a unui PC este formată din primul 1Mb de RAM. Programele DOS
rulează, în mod obişnuit, cu primii 640Kb de memorie convenţională.
PC-ul utilizează restul de 384Kb de memorie (numită memorie
rezervată sau memorie superioară) pentru memoria video a
calculatorului, driverele de dispozitive, alte dispozitive HARD mapate
în memorie şi BIOS (Basic Input-Output Services – servicii intrare-
ieşire de bază).
       Sistemul de operare Windows utilizează modelul de memorie
virtuală pentru a gestiona memoria, ceea ce înseamnă că eliberarea
memoriei convenţionale nu are semnificaţie sub acest sistem de
operare. Însă, memoria convenţională este importantă când se rulează
programe în cadrul unei ferestre DOS sub Windows.
       Structura memoriei convenţionale a unui calculator personal
este următoarea:


                           BIOS ROM
                         Memorie rezervată
                          Memorie video
                         COMMAND.COM

                      Memorie pentru programe

                        Intrări CONFIG.SYS
                            Nucleul DOS
                     Zona de comunicaţii BIOS
                     Vectori de întrerupere BIOS

       PC-ul împarte memoria în blocuri de 64Kb numite segmente. În
mod obişnuit, programul utilizează un segment de cod (ce conţine
instrucţiunile programului) şi un al doilea segment de memorie pentru
date. Dacă un program este foarte mare compilatorul va trebui să
dispună de mai multe segmente de cod sau de date, sau de ambele.
Modelul de memorie defineşte numărul de segmente pe care le poate
folosi pentru fiecare. Modele sunt foarte importante deoarece, dacă se
utilizează un model de memorie necorespunzător, programul poate să

                                 265
nu deţină suficientă memorie pentru execuţie. Compilatorul va alege
un model de memorie suficient de mare pentru a rula programul, însă
cu cât memoria utilizată este mai mare cu atât viteza de execuţie a
programului scade. Din această cauză trebuie ales modelul cel mai mic
pentru necesităţile programului. Majoritatea compilatoarelor acceptă
următoarele modele de memorie:
      a)        tiny – combină datele şi codul programului într-un
                singur segment de 64Kb (este cel mai mic şi mai rapid
                model de memorie);
      b)        small – utilizează un segment de memorie pentru cod
                şi un segment pentru date (este cel mai obişnuit model
                de memorie);
      c)        medium – utilizează un segment de 64Kb pentru date
                şi două sau mai multe segmente pentru codul
                programului. În acest caz datele sunt accesate rapid
                prin utilizarea de adrese near, în schimb însă,
                apelurile de funcţii se fac utilizând adrese far;
      d)        compact – alocă un segment de 64Kb pentru codul
                programului şi două sau mai multe segmente pentru
                date (este un model utilizat pentru programe mici ce
                manipulează un număr mare de date);
      e)        large – alocă mai multe segmente atât pentru date cât
                şi pentru cod şi este cel mai lent model de memorie
                din cele prezentate până acum. El trebuie utilizat doar
                ca ultimă resursă;
      f)        huge – este un model utilizat doar în cazul utilizării
                unor matrici mai mari de 64Kb. Pentru a stoca o astfel
                de matrice programul trebuie să utilizeze cuvântul
                cheie huge pentru a crea un pointer, astfel:
                    int huge *matrice_uriaşă
după care programul trebuie să utilizeze funcţia halloc pentru alocarea
memoriei şi funcţia hfree pentru eliberarea acesteia. Exemplul următor
alocă o matrice de 400000 octeţi:
Exemplu:
#include <stdio.h>
#include <malloc.h>
void main(void)
{ long int k;
 int huge *matrice_uriasa;


                                 266
if ((matrice_uriasa=(int huge*) halloc(100000L,sizeof
(long int)))==NULL)
 printf(”Eroare la alocarea matricii”);
else{
 printf(”Completeaza matricean”);
 for(k=0;k<100000L;k++)
  matrice_uriasa[k]=k%32768;
for(k=0;k<100000L;k++)
  printf(”%d ”,matrice_uriasa[k]);
hfree(matrice_uriasa); } }
      Pentru selectarea unui anumit model de memorie se include, de
regulă, o opţiune în cadrul liniei de comandă a compilatorului.
Majoritatea compilatoarelor predefinesc o constantă specifică pentru a
determina modelul curent de memorie. În tabelul următor sunt
prezentate aceste constante definite de compilatoarele Microsoft C şi
Borland C:
      Model de memorie       Microsoft C         Borland C
           Small              M_I86SM            _SMALL_
          Medium             M_I86MM            _MEDIUM_
         Compact             M_I86CM           _COMPACT_
           Large             M_I86LM             _LARGE_
     Programul poate verifica modelul de memorie utilizat folosind
următoarea secvenţă de instrucţiuni:
#ifndef _MEDIUM_
printf(”Programul cere modelul de memorie mediumn”);
exit(1);
#endif

      Atunci când un program trebuie să aloce memorie în mod
dinamic se utilizează fie funcţia malloc pentru a aloca memorie din
zona near (din segmentul curent), fie funcţia fmalloc pentru a aloca
memorie far.
14.1.2 Memoria expandată
       În cazul programelor mari o memorie de numai 1Mb este
insuficientă. Pentru a permite accesul la mai mult de 1Mb de memorie,
companiile Lotus, Intel şi Microsoft au creat o specificaţie pentru
memoria expandată, care combină software şi o platformă specială de
memorie expandată pentru a „păcăli” PC-ul în scopul accesării unor
volume mari de memorie. Mai întâi în zona de memorie superioară se
alocă un bloc de 64Kb după care acest bloc de memorie este împărţit

                                267
în patru secţiuni de 16Kb, numite pagini în care se încarcă paginile
logice ale programului. De exemplu un program de 128Kb este
împărţit în opt pagini de 16Kb fiecare care sunt încărcate în funcţie de
necesităţile programului în zona rezervată de 64Kb.
14.1.3 Memoria extinsă
      Calculatoarele cu procesoare peste 386 utilizează adresarea pe
32 de biţi ceea ce le dă posibilitatea de accesare directă de până la 4Gb
de memorie. Programatorii au numit memoria de peste 1Mb memorie
extinsă. Pentru a accesa memoria extinsă, trebuie încărcat un driver de
dispozitiv pentru memoria extinsă, care în DOS este de obicei
himem.sys. Pentru a utiliza însă memoria extinsă este necesară trecerea
la modul protejat de lucru al procesorului, mod de lucru în care datele
unui program nu pot fi scrise peste datele altui program ce rulează
simultan cu acesta.
14.1.4 Stiva
       Stiva este o regiune de memorie în cadrul căreia programele
păstrează temporar datele pe durata execuţiei. De exemplu, atunci
când programele transmit parametri către o funcţie, C plasează aceşti
parametri în stivă. Când funcţia îşi încheie execuţia aceştia sunt scoşi
din stivă. Stiva este numită astfel deoarece ultimele valori depuse sunt
primele extrase. În funcţie de modelul de memorie utilizat, spaţiul de
memorie ocupat de stivă diferă. Valoarea minimă a stivei este 4Kb. În
cazul modelelor compact sau large, C alocă pentru stivă un întreg
segment de 64Kb. Dacă un program plasează în stivă mai multe
informaţii decât poate reţine aceasta, va apărea o eroare de depăşire a
stivei (stack-overflow). Dacă programul a dezactivat testarea stivei,
datele depuse în stivă pot fi suprapuse peste datele programului.
Exemplul următor prezintă modul de determinare a dimensiunii stivei
utilizând funcţia _stklen.
Exemplu:
#include <stdio.h>
#include <dos.h>
void main(void)
{
printf(”Dimensiunea stivei este de %d octeti”,_stklen);
}




                                  268
14.2 Servicii DOS şi BIOS

       Aşa cum am menţionat în paragraful anterior, BIOS-ul
reprezintă serviciile de intrare-ieşire de bază. Pe scurt, BIOS este un
cip din cadrul calculatorului ce conţine instrucţiunile pe care
calculatorul le utilizează pentru a scrie pe ecran sau la imprimantă,
pentru a citi caractere de la tastatură sau pentru a citi sau scrie pe disc.
Programatorii au au proiectat rutinele BIOS pentru a fi utilizate de
programe în limbaj de asamblare, totuşi, majoritatea compilatoarelor
de C dispun de funcţiide bibliotecă ce permit utilizarea acestor
servicii fără a avea nevoie de limbaje de asamblare.
       DOS este un sistem de operare pentru calculatoarele
compatibile IBM PC. Sistemul DOS permite rularea programelor şi
păstrează informaţia pe disc. În plus, sistemul DOS pune la dispoziţie
servicii ce permit programelor să aloce memorie, să acceseze
dispozitive, cum ar fi imprimanta, şi să gestioneze alte resurse ale
sistemului. Biblioteca limbajului C oferă o interfaţă la multe servicii
DOS, prin intermediul funcţiilor.
       Mulţi programatori confundă serviciile DOS cu serviciile BIOS.
Tabelul următor prezintă relaţia dintre componenta HARD a
calculatorului, serviciile BIOS, DOS şi componenta SOFT.

                  Programe                    Nivel înalt
                    DOS                             |
                    BIOS                            |
                 HARDWARE                  Nivelul cel mai jos
       Aşa cum se observă, BIOS este situat imediat deasupra
componentei hardware, serviciiile DOS deasupra serviciilor BIOS, iar
programele deasupra sistemului DOS.
       Uneori însă, programele pot evita serviciile DOS şi BIOS şi pot
accesa direct o componentă hardware (cum este cazul memoriei
video).
       Se recomandă ca ori de câte ori poate fi utilizată o funcţie de
bibliotecă C în locul unui serviciu DOS sau BIOS, aceasta să fie
utilizată pentru a mări portabilitatea programelor şi la calculatoarele ce
utilizează alte sisteme de operare (WINDOWS, UNIX, etc.). În acest
caz, programul nu va mai trebui modificat pentru a putea fi rulat sub
WINDOWS sau UNIX.

                                   269
Toate versiunile de WINDOWS vor apela propriile lor servicii
de sistem. Însă, serviciile de sistem WINDOWS apelează până la
urmă serviciile BIOS penttru a accesa componentele hardware ale
cal;culatorului.
14.2.1 Serviciile BIOS
      Prezentăm în continuare o serie de servicii BIOS ce pot fi
accesate utilizând funcţii de bibliotecă ale limbajului C.
1) accesul la imprimantă
      Înainte ca un program să scrie ieşirea la imprimantă utilizând
indicatorul de fişier stdprn se poate face o verificare dacă imprimanta
este conectată şi dacă are hârtie utilizând funcţia biosprint din fişierul
antet bios.h:
      int biosprint(int comanda,int octet,int nr_port)
unde comanda specifică una din următoarele operaţii:
       0 – tipăreşte octetul specificat;
       1 – iniţializează portul imprimantei;
       2 – citeşte starea imprimantei.
       Parametrul octet specifică valoarea ASCII a caracterului ce se
doreşte a fi scris la imprimantă iar nr_port specifică portul
imprimantei care poate fi 0 pentru LPT1, 1 pentru LPT2, ş.a.m.d.
       Funcţia biosprint returnează o valoarea înteagă pe un octet ai
cărui biţi au următoarea semnificaţie:
       0 – dispozitiv în pauză;
       3 – eroare I/O;
       4 – imprimantă selectată;
       5 – lipsă hârtie;
       6 – confirmare dispozitiv;
       7 – dispozitivul nu este ocupat.
2) operaţii intrare/ieşire
       Operaţiile intrare/ieşire de nivel jos pot fi realizate utilizând
funcţia biodisk ce are următoarea sintaxă:
   int biodisk(int operatie, int unitate, int head, int
     track, int sector, int nr_sector, void *buffer)
        unde parametrul unitate precizează numărul unităţii, care este 0
pentru A, 1 pentru B, şi aşa mai departe. Parametrii head, track, sector
şi nr_sector precizează sectoarele fizice ale disculuice trebie scris sau
citit. Parametru buffer este un pointer la bufferul din care sunt citite
sau în care sunt scrise datele. Parametru operatie specifică funcţia
dorită astfel:
                                  270
0     Iniţializează sistemul de disc
  1     Returnează starea ultimei operaţii pe disc
  2     Citeşte numărul precizat de sectoare
  3     Scrie numărul precizat de sectoare
  4     Verifică numărul precizat de sectoare
  5     Formatează pista specificată
  6     Formatează pista specificată şi marchează sectoarele defecte
  7     Formatează unitatea începând cu pista specificată
  8     Returnează parametrii unităţii de disc
  9     Iniţializează unitatea de disc
  10    Execută o citire lungă – 512 octeţi de sector plus patru suplimentari
  11    Execută o scriere lungă – 512 octeţi de sector plus patru
        suplimentari
  12    Execută o poziţionare pe disc
  13    Iniţializarea alternativă a discului
  14    Citeşte bufferul sectorului
  15    Scrie bufferul sectorului
  16    Testează dacă unitatea este pregătită
  17    Recalibrează unitatea
  18    Execută diagnosticarea unităţii de RAM
  19    Execută diagnosticarea unităţii
  20    Execută diagnosticarea internă a controlerului
       Dacă se execută cu succes, funcţia returnează valoarea 0. Dacă
apare o eroare, valoarea returnată precizează eroarea.
3) servicii de tastatură din BIOS
       Pentru accesul la serviciile de tastatură din BIOS, C-ul pune la
dispoziţie funcţia _bios_keybrd ce are următoarea sintaxă:
            unsigned _bios_keybrd(unsigned comanda)
unde parametrul comanda specifică operaţia dorită şi poate avea una
din următoarele valori:
       _KEYBRD_READ              Indică funcţiei să citească un caracter de la
                                 tastatură
       _KEYBRD_READY             Determină dacă este prezent un caracter la
                                 bufferul tastaturii. Dacă funcţia returnează
                                 0, înseamnă că nici o intrare de la tastatură
                                 nu este prezentă. Dacă valoarea returnată
                                 este 0xFFFF, utilizatorul a apăsat CTRL C
  _KEYBRD_SHIFTSTATUS            Returnează starea tastelor de control:
                                 Bit 7 – INS este activat
                                 Bit 6 – CAPSLOCK este activat
                                 Bit 5 – NUMLOCK este activat

                                    271
Bit 4 – SCRLLOCK este activat
                                   Bit 3 – ALT este apăsată
                                   Bit 2 – CTRL este apăsată
                                   Bit 1 – SHIFT stânga este apăsată
                                   Bit 0 – SHIFT dreapta este apăsată
      _NKEYBRD_READ                Indică funcţiei să citească un caracter de la
                                   tastatură, inclusiv tastele speciale, cum ar
                                   fi tastele cu săgeţi
      _NKEYBRD_READY               Determină dacă este prezent un caracter la
                                   bufferul tastaturii. Dacă funcţia returnează
                                   0, înseamnă că nici o intrare de la tastatură
                                   nu este prezentă. Dacă valoarea returnată
                                   este 0xFFFF, utilizatorul a apăsat CTRL C
                                   Funcţia acceptă inclusiv tastele speciale,
                                   cum ar fi tastele cu săgeţi
 _NKEYBRD_SHIFTSTATUS              Returnează starea tastelor de control,
                                   inclusiv a tastelor speciale:
                                   Bit 15 – SYSREQ este activat
                                   Bit 14 – CAPSLOCK este activat
                                   Bit 13 – NUMLOCK este activat
                                   Bit 12 – SCRLLOCK este activat
                                   Bit 11 – ALT dreapta este apăsată
                                   Bit 10 – CTRL dreapta este apăsată
                                   Bit 9 – ALT stânga este apăsată
                                   Bit 8 – CTRL stânga este apăsată
4) obţinerea listei cu echipamente din BIOS
       Unele programe necesită determinarea caracteristicilor
hardware ale calculatorului. Pentru aceasta se utilizează funcţia
_bios_equiplist care are următoarea sintaxă:
                 unsigned _bios_equiplist(void);
     Funcţia returnează o valoare pe 16 biţi a căror valoare are
următoarea semnificaţie:
 15   14   13   12   11   10   9     8    7    6   5   4   3   2    1   0
15:14 – numărul de imprimante paralele instalate (de la 0 la 3);
13 – imprimanta serială;
12 – adaptorul de jocuri;
11:10:9 – numărul de porturi seriale COM (de la 0 la 7);
8 – prezenţa DMA (Direct Memory Acces); bitul are valoarea 0 dacă
există DMA şi 1 dacă nu există;
7:6 – numărul drieverelor de disc;


                                         272
5:4 – modul video: 00-neutilizat, 01-mod video 40x25 mono, 10-mod
video 80x25 color, 11-mod video 80x25 mono;
3:2 – dimensiunea memorie RAM: 00-16Kb, 01-32Kb, 10-48Kb,
11-64Kb;
1 – prezenţa coprocesorului matematic;
0 – prezenţa unităţii de disc flexibile.
5) controlul intrărilor şi ieşirilor pentru portul serial
       Pentru a executa operaţii intrare/ieşire utilizând portul serial se
utilizează funcţia bioscom ce are următoarea sintaxă:
     unsigned bioscom(int comanda,int port,char octet);
      Parametrul comanda specifică operaţia dorită şi poate avea una
din următoarele valori:
  _COM_INIT            Stabileşte valorile pentru comunicare ale portului
  _COM_RECEIVE         Primeşte un octet de la port
  _COM_SEND            Trimite un octet la port
  _COM_STATUS          Returnează valorile portului
       Parametrul port specifică portul serial ce se doreşte a fi utilizat,
unde 0 corespunde lui COM1, 1 lui COM2 şi aşa mai departe.
       Parametrul octet specifică fie octetul pentru ieşire, fie valorile
de comunicare dorite.
6) determinarea volumului de memorie convenţională BIOS
       Pentru a determina memoria convenţională ce poate fi utilizată
de către un proggram se utilizează funcţia biosmemory ce are
următoarea sintaxă:
                       int biosmemory(void);
        Valoarea returnată de această funcţie nu cuprinde memoria
extinsă, expandată sau superioară.
7) citirea cronometrului BIOS
        BIOS are incorporat un ceas intern ce bate de 18.2 ori pe
secundă. Acest cronometru este util pentru a genera punctul iniţial al
unui generator de numere aleatoare. Multe compilatoare de C pun la
dispoziţie două funcţii pentru accesul la cronometrul BIOS: biostime
şi _bios_timeofday. Sintaxa acestor funcţii este următoarea:
          long biostime(int operatie,long timp_nou);
     Parametrul operaţie poate lua două valori:
     0 – dacă se doreşte ca funcţia să citească valoarea curentă a
cronometrului;
     1 – pentru a fixa valoarea cronometrului la valoarea timp_nou.
      long _bios_timeofday(int operatie,long *batai);


                                   273
Această funcţie poate fi, de asemenea, utilizată pentru a citi sau
a fixa cronometrul BIOS.
14.2.2 Serviciile DOS
       În acest paragraf prezentăm o serie de servicii DOS ce pot fi
accesate utilizând funcţii de bibliotecă ale limbajului C.
1) suspendarea temporară a unui program
       Execuţia unui program poate fi suspendată temporar utilizând
funcţia sleep.h din fişierul antet dos.h:
                  void sleep(unsigned secunde);
parametrul secunde specificând numărul de secunde pe care este
suspendat programul.
2) utilizarea sunetelor
        Generarea de sunete ce utilizează difuzorul calculatorului se
realizează utilizând funcţiile sound şi nosound:
                 void sound(unsigned frecventa)
generează un sunet cu frecvenţa frecventa;
                 void sound(unsigned frecventa)
deconectează difuzorul. Programul următor generează un sunet de
sirenă dezactivat la apăsarea unei taste:
Exemplu:
#include <dos.h>
#include <conio.h>
void main()
{ unsigned frecventa;
do{ for (frecventa=500;frecventa<=1000;frecventa+=50)
   {      sound(frecventa);
     delay(50);    }
 for (frecventa=1000;frecventa>=500;frecventa-=50)
   {      sound(frecventa);
     delay(50);    } }
 while(!kbhit());
 nosound(); }
3) obţinerea de informaţii despre erori în DOS
       În cazul în care un serviciu al sistemului DOS eşuează,
programele pot cere informaţii suplimentare despre acea eroare
folosind funcţia dosexterr:
        int dosexterr(struct DOSERROR *info_eroare);
unde structura DOSERROR are următoarele câmpuri:
      struct DOSERROR{
       int de_exterror; //eroare
       int de_class; //clasa erorii
       int de_action;//actiune recomandata

                                  274
int de_locus;//sursa erorii };
        Dacă funcţia returnează valoarea 0, apelul serviciului DOS nu a
avut nici o eroare.
        Clasa erorii descrie categotia erorii, astfel:
         01H         Resurse depăşite
         02H         Eroare temporară
         03H         Eroare de autorizare
         04H         Eroare de sistem
         05H         Eroare hardware
         06H         Eroare de sistem nedatorată programului curent
         07H         Eroare de aplicaţie
         08H         Articol neîntâlnit
         09H         Format nevalid
         0AH         Articol blocat
         0BH         Eroare de suport
         0CH         Articolul există
         0DH         Eroare necunoscută
        Parametrul de_action indică programului cum să răspundă
erorii, astfel:
         01H Mai întâi încearcă din nou, apoi cere intervenţia utilizatorului
         02H Încearcă din nou, cu o întârziere, apoi cere intervenţia
                 utilizatorului
         03H     Cere intervenţia utilizatorului pentru soluţie
         04H     Renunţă şi elimină
         05H     Renunţă, dar nu elimina
         06H     Ignoră eroarea
         07H     Încearcă din nou după intervenţia utilizatorului
Parametrul de_locus specifică sursa erorii, astfel:
              01H Sursă necunoscută
              02H Eroare de dispozitiv bloc
              03H Eroare de reţea
              04H Eroare de dispozitiv serial
              05H Eroare de memorie
4) citirea valorilor registrului segment
        Codul programului, datele şi stiva sunt controlate de compilator
utilizând patru registre de segment: CS, DS, ES, SS. În unele cazuri
este necesar să se cunoască valoarea acestor registre. Pentru astfel de
cazuri se utillizează funcţia segread:

                                       275
void segread(struct SREGS *segs);
      Structura SREGS are următoarele câmpuri:
struct SREGS
 { unsigned int es;
  unsigned int cs;
  unsigned int ss;
  unsigned int ds; }
5) accesul la valorile de port
      Pentrul controlul hardware de nivel inferior, compilatoarele de C
pun la dispoziţie următoarele funcţii:
    - int inport (int adresa_port); - citeşte un cuvânt de la
         portul specificat de parametrul adresa_port;
    - int inportb (int adresa_port); - citeşte un octet de la
         portul specificat de parametrul adresa_port;
    - int outport (int adresa_port); - scrie un cuvânt de la
         portul specificat de parametrul adresa_port;
    - int outportb (int adresa_port); - scrie un octet de la
         portul specificat de parametrul adresa_port;
6) suspendarea unui program
      Pentru suspendarea unui program pe un anumit interval de timp
se poate utiliza funcţia delay, similară funcţiei sleep. Funcţia delay are
însă ca parametru o constantă exprimată în milisecunde:
               void delay(unsigned milisecunde);
7) apelarea unei comenzi interne DOS
      Pentru apelarea unei comenzi DOS sau a unui fişier pentru
comenzi se utilizează funcţia system:
                int system(const char *comanda);
      Parametrul comanda este un şir de caracter care conţine numele
comenzii DOS sau a fişierului de comenzi. Dacă funcţia reuşeşte să
execute comanda, se returnează valoarea 0, altfel returnează -1.
      Programul următor prezintă utilizarea funcţiei system.
Exemplu:
#include <stdlib.h>
#include <stdio.h>
void main(void)
{ if(system("DIR"))
   printf("EROARE!n"); }
8) lucrul cu vectori de întrerupere
      Un vector de întrerupere este o adresă de segment şi de
deplasament a codului care tratează o anumită întrerupere.


                                  276
Determinarea vectorului de întrerupere se realizează utilizând funcţia
_dos_getvect în modul următor:
    void interrupt(* _dos_getvect(unsigned nr_intr))();
       Parametrul nr_intr specifică numărul întreruperii dorite ce poate
avea valori de la 0 la 255. Programul următor va afişa vectorii pentru
toate întreruperile calculatorului:
Exemplu:
#include <stdio.h>
#include <dos.h>
void main(void)
{ int k;
  for(k=0;k<=255;k++)
     printf(”Intrerupere: %x Vector %lxn”,k,
_dos_getvect(k)); }
       Dacă se doreşte crearea unui program de tratare a unei
întreruperi, vectorul de întrerupere trebuie atribuit acestui program.
Această atribuire se realizează cu ajutorul funcţiei _dos_setvect:
     void _dos_setvect(unsigned nr_intr,
     void interrupt(* handler)());
       Parametrul nr_intr specifică întreruperea al cărui vector trebuie
modificat.
       Pentru activarea şi dezactivarea întreruperilor se utilizează
funcţiile:
      void _disable(void);
      void _enable(void);
       Dacă se doreşte reactivarea întreruperii originare se utilizează
funcţia _chain_interrupt:
  void chain_interrupt(void(interrupt far *handler)());
      Generarea unei întreruperi se realizează folosind funcţia
geninterrupt:
              void geninterrupt(int intrerupere);
unde parametrul intrerupere specifică întreruperea generată.

14.3 Bibliotecile C

       Dacă se examinează fişierele ce însoţesc un compilator C, se
remarcă multe fişiere cu extensia LIB. Aceste fişiere conţin biblioteci
obiect. Atunci când este compilat şi link-editat un program, editorul de
legături examinează fişierele LIB pentru a rezolva referinţele la
funcţii. Când sunt create funcţii utile ce sunt necesare şi în alte


                                 277
programe, se pot construi biblioteci în care aceste funcţii să fie
păstrate.
14.3.1 Reutilizarea unui cod obiect
        În cazul creării unei funcţii utile care se doreşte reutilizată, se
poate compila fişierul ce conţine funcţia respectivă pentru a crea codul
obiect (de exemplu din fişierul funcţie.c prin compilare se obţine
fişierul obiect funcţie.obj). Funcţia definită în acest fişier obiect poate
fi reutilizată în alt program utilizând următoarea instrucţiune:
        C:>bc fisier_nou.c funcţie.obj
        Totuşi, acest mod de a reutiliza codul unor funcţii este destul de
dificil de utilizat în cazul în care se doreşte reutilizarea unui număr
mare de funcţii aflate în fişiere obiect separate.
14.3.2 Lucrul cu fişiere bibliotecă
        Operaţiile acceptate de fişierele bibliotecă sunt următoarele:
    -     crearea unei biblioteci;
    -     adăugarea unuia sau mai multor fişiere obiect la bibliotecă;
    -     înlocuirea unui fişier obiect cu altul;
    -     ştergerea unuia sau mai multor fişiere obiect din bibliotecă;
    -     listarea rutinelor pe care le conţine biblioteca.
        În funcţie de compilator, numele programului de bibliotecă şi
opţiunile liniei de comandă pe care programul le acceptă vor diferi. În
continuare prezentăm operaţiile ce pot fi realizate cu funcţiile
bibliotecă utilizând programul TLIB al compilatorului Borland C.
Presupunem că în urma compilării am creat fişierul obiect funcţie.obj
ce conţine o serie de funcţii pe care dorim să le păstrăm într-o
bibliotecă. Crearea unei bilioteci biblioteca.lib care să conţină acest
fişier obiect se realizează cu următoarea linie de comandă:
             C:>tlib biblioteca.lib + functie.obj
      După ce fişierul bibliotecă a fost creat, funcţiile acestuia sunt
disponibile pentru compilarea şi legarea noilor programe.
      Funcţia de biliotecă TLIB a compilatorului Borland C are
următoarea sintaxă:
                     tlib cale comandă, fişier
unde:
   -    cale – este un şir de caractere care specifică calea până la
        bilioteca asupra căreia se efectuează operaţia;
    -   comandă – este formată dintr-un simbol şi numele unui fişier
        obiect. Simbol poate fi unul din caracterele: + (adaugă un
                                   278
modul la bibliotecă), - (elimină un modul din bibliotecă), *
        (extrage un modul din bibliotecă într-un fişier cu acelaşi
        nume, fără al elimina), -+ (înlocuieşte un modul din
        bibliotecă), -* (extrage şi elimină un modul din bibliotecă);
    -   fisier – reprezintă numele fişierul în care se scrie ieşirea
        operaţiei efectuate asupra bibliotecii.

14.3 Fişierele antet

       Fiecare program foloseşte una sau mai multe instrucţiuni
#include pentru a cere compilatorului de C să folosească instrucţiunile
incluse într-un fişier antet. Când compilatorul întâlneşte o instrucţiune
#include în program, el compilează codul din fişierul antet ca şi cum
ar fi scris în fişierul sursă. Fişierele antet conţin definiţii frecvent
utilizate şi furnizează compilatorului informaţii referitoare la funcţiile
sale. Dacă la compilarea programului se afişează un mesaj de eroare,
avertizând că nu se poate deschide un anumit fişier antet, trebuie
verificat subdirectorul care conţine fişierele antet, pentru a vedea dacă
acel fişier există sau nu. Dacă se găseşte fişierul respectiv, în linia de
comandă din sistemul de operare DOS trebuie scrisă următoarea
instrucţiune:
              C:>SET INCLUDE=C:BORLANDCINCLUDE




                         BIBLIOGRAFIE

1. Plum T., Learning to program in C, Prentice Hall, 1983

2. Auslander D.,Tham C., Real-time software for control: program
    examples in C, Prentice Hall, 1990.

                                  279
3. Schild H., Using Turbo C, Borland, Osborne / McGraw Hill,
   1988.

4. Holzner S., Borland C++ Programming, Brady Books, New
   York, 1992.

5. Somnea D., Turturea D., Introducere în C++, Programarea
   orientatã pe obiecte, Ed. Tehnicã, Bucureşti, 1993.

6. Marian Gh., Bãdicã C., Pãdeanu L., Limbajul PASCAL, Indrumar
   de laborator, Reprografia Universitãţii din Craiova, 1993.

7. Negrescu L., Introducere în limbajul C, Editura MicroInformatica,
   Cluj Napoca, 1993.

8. Petrovici V., Goicea F., Programarea în limbajul C, Editura
   Tehnicã, Bucureşti, 1993.

9. Marian Gh., Muşatescu C., Laşcu M., Iordache Şt., Limbajul C,
   Editura ROM TPT, Craiova, 1999.

10. Mocanu M., Ghid de programare în limbajele C/C++, Editura
   SITECH, Craiova, 2001.

11. Zaharia, M.D., Structuri de date şi algoritmi. Exemple în
   limbajele C şi C++, Ed. Albastră, Cluj Napoca, 2002.

12. Kernighan, B.W., Ritchie, D.M., The C programming languages,
   Englewood. Cliffs, N.J. Prentice-Hall, 1978.

13. Bulac, C., Iniţiere în Turbo C++ şi Borland C, Editura Teora,
   Bucureşti, 1995.




                                280

More Related Content

PDF
2011 curtea-de-conturi-standarde-de-audit
PDF
Drept civil
DOC
Drept+fiscal
PDF
56489014 dr-prop-intelectuale an-iii
PDF
Dreptul proprietatii intelectuale_curs_i
PDF
Introducere in filosofia obiectuala
DOC
54 actul administrativ de autoritate copy
DOC
81591504 curs-drept-institutional-european
2011 curtea-de-conturi-standarde-de-audit
Drept civil
Drept+fiscal
56489014 dr-prop-intelectuale an-iii
Dreptul proprietatii intelectuale_curs_i
Introducere in filosofia obiectuala
54 actul administrativ de autoritate copy
81591504 curs-drept-institutional-european

What's hot (19)

PDF
47363283 curs-comunicare-profesionala
PDF
Initiere operare pc
ZIP
Opera omiletica a_sfantului_vasile_cel_mare_si_actualitatea_ei_pentru_misiune...
DOC
Comunicare..
PDF
curs utilizarea-calculatorului
PDF
246665018 deontologie
DOC
86576063 dreptul-proprietatii-intelectuale-curs-id-iunie-2011
PDF
77997067 metodica predari_lb_si_lit_romane
PDF
17711612 dreptul-securitatii-sociale
PDF
Suport de curs instruire utilizatori IMI PQ ianuarie 2013
PDF
Metodica predarii matematicii_si_aritmetice
PDF
Romana
DOC
Fisa2verb
PDF
Drept internaţional privat
DOC
58 administrarea salarizarii. analiza diagnostic a cheltuielilor salariale (s...
PDF
Plan de Dezvoltare Locala Alutus
PDF
22 teza varga csaba f v_- 27_sept_ 2018 final-converted-semnat
PDF
Manual instructiuni-blackberry-9300-3g-black
PDF
Dreptul muncii-2011
47363283 curs-comunicare-profesionala
Initiere operare pc
Opera omiletica a_sfantului_vasile_cel_mare_si_actualitatea_ei_pentru_misiune...
Comunicare..
curs utilizarea-calculatorului
246665018 deontologie
86576063 dreptul-proprietatii-intelectuale-curs-id-iunie-2011
77997067 metodica predari_lb_si_lit_romane
17711612 dreptul-securitatii-sociale
Suport de curs instruire utilizatori IMI PQ ianuarie 2013
Metodica predarii matematicii_si_aritmetice
Romana
Fisa2verb
Drept internaţional privat
58 administrarea salarizarii. analiza diagnostic a cheltuielilor salariale (s...
Plan de Dezvoltare Locala Alutus
22 teza varga csaba f v_- 27_sept_ 2018 final-converted-semnat
Manual instructiuni-blackberry-9300-3g-black
Dreptul muncii-2011
Ad

Viewers also liked (20)

DOC
Manual de programare c
DOC
Instructiuni in c si c++
DOC
Functii, tablouri si pointeri in c si c++
PDF
Limbajul c
PPTX
Programarea calculatoarelor - Limbajul C
DOC
Manual limbaj c
DOC
'Documents.tips curs baze-de-date-5642553eddb8c.doc'
PPTX
Pagina de Facebook - Ghid practic
PPT
Introducere baza de-date
PPT
Microsoft+access+este+aplicatia+de+management+a+bazelor[1]
PPT
Instruire Asistata De Calculator Modul1a
PPT
Microsoft office word 2010
PPTX
Baze de date Access
PPT
Baze+de+date 1
PDF
Programarea independenta de platforma in C++. Qt
PPTX
Tutorial C++
PDF
Manual microsoft excel
PDF
baze-de-date-access-laborator-de-ioan-mocian
PPTX
PDF
68 De Pagini De Probleme Rezolvate Si Teorie In Pascal
Manual de programare c
Instructiuni in c si c++
Functii, tablouri si pointeri in c si c++
Limbajul c
Programarea calculatoarelor - Limbajul C
Manual limbaj c
'Documents.tips curs baze-de-date-5642553eddb8c.doc'
Pagina de Facebook - Ghid practic
Introducere baza de-date
Microsoft+access+este+aplicatia+de+management+a+bazelor[1]
Instruire Asistata De Calculator Modul1a
Microsoft office word 2010
Baze de date Access
Baze+de+date 1
Programarea independenta de platforma in C++. Qt
Tutorial C++
Manual microsoft excel
baze-de-date-access-laborator-de-ioan-mocian
68 De Pagini De Probleme Rezolvate Si Teorie In Pascal
Ad

Similar to Carte C 2003 (20)

DOC
PDF
carte-informatica-limbajul-c
PDF
Limbajul c c pentru incepatori
PDF
manual-de-programare-c
PDF
Cristian frasinaru curs-practic_de_java
PDF
510402 Cristian Frasinaru Curs Practic De Java
PDF
blah1blah1blah1blah1blah1blah1blah1blah1blah1blah1
PPT
Limbaje de programare ingineresti - C++ notiuni
PDF
Algoritmi elementari.pdf
PDF
Curs C++
PDF
Curs Visual c++
PPT
Studierea limbajului pascal
PDF
Informatica111 rom
PDF
baze c++sructura unui program declarare variabilepdf.
PDF
Cristian frasinaru curs practic de java
PDF
Cristian frasinaru curs-practic_de_java
PDF
Curs practic de_java
PDF
Lecture2 - PC
carte-informatica-limbajul-c
Limbajul c c pentru incepatori
manual-de-programare-c
Cristian frasinaru curs-practic_de_java
510402 Cristian Frasinaru Curs Practic De Java
blah1blah1blah1blah1blah1blah1blah1blah1blah1blah1
Limbaje de programare ingineresti - C++ notiuni
Algoritmi elementari.pdf
Curs C++
Curs Visual c++
Studierea limbajului pascal
Informatica111 rom
baze c++sructura unui program declarare variabilepdf.
Cristian frasinaru curs practic de java
Cristian frasinaru curs-practic_de_java
Curs practic de_java
Lecture2 - PC

More from MegaVjohnson (15)

PDF
Digital Photo Repair Using Photoshop
DOC
Notiuni De Optoelectronica
PPT
Lunar Eclipse
PPT
Space Travel
PPT
Sirtel Workshop
DOC
Securitatea Retelelor. Viermele Internetului
DOC
Solutia Linux De Conectare La Internet
PPT
Atomic Structure
PDF
Curs Sctr2009
PDF
On Social E Learning
PDF
Capturing Knowledge Of User Preferences With Recommender Systems
PDF
Evaluating Collaborative Filtering Recommender Systems
PDF
Hybrid Recommender Systems
PDF
Collaborative Projects And Self Evaluation Within A Social Reputation Based E...
PDF
An Usability Study Of A Recommender System For Mash Ups For Learning
Digital Photo Repair Using Photoshop
Notiuni De Optoelectronica
Lunar Eclipse
Space Travel
Sirtel Workshop
Securitatea Retelelor. Viermele Internetului
Solutia Linux De Conectare La Internet
Atomic Structure
Curs Sctr2009
On Social E Learning
Capturing Knowledge Of User Preferences With Recommender Systems
Evaluating Collaborative Filtering Recommender Systems
Hybrid Recommender Systems
Collaborative Projects And Self Evaluation Within A Social Reputation Based E...
An Usability Study Of A Recommender System For Mash Ups For Learning

Carte C 2003

  • 1. CUPRINS CUPRINS............................................................................................I Capitolul I...........................................................................................1 INTRODUCERE ÎN..........................................................................1 ARHITECURA SISTEMELOR DE CALCUL...............................1 1.1. Importanţa limbajului C....................................................1 1.2 Arhitectura de bază a unui calculator.................................4 1.2.1 Microprocesorul.........................................................7 1.2.2 Memoria.....................................................................8 1.2.3 Echipamentele periferice..........................................10 1.3. Programarea calculatorului.............................................13 1.3.1. Sistemul de operare.................................................14 1.3.2. Tipuri de fişiere.......................................................18 1.3.3. Construirea fişierului executabil.............................19 Capitolul II.......................................................................................24 REPREZENTAREA DATELOR ÎN CALCULATOR.................25 2.1. Reprezentarea internă/externă a numerelor....................26 2.2. Reprezentarea externă a numerelor.................................27 2.2.1. Reprezentarea externă a numerelor întregi..............28 2.2.2. Reprezentarea externă a numerelor reale................31 2.3 Reprezentarea internă a numerelor..................................33 2.3.1. Reprezentarea internă a numerelor întregi..............33 2.3.2 Adunarea, scăderea şi înmulţirea numerelor întregi.35 2.3.3 Reprezentarea internă a numerelor reale..................37 Game de reprezentare pentru numerele reale....................49 2.3.5. Codificare BCD.......................................................50 Capitolul III......................................................................................52 ELEMENTELE DE BAZĂ ALE LIMABJULUI C.....................52 3.1. Crearea şi lansarea în execuţie a unui program C...........52 3.2. Structura unui program C................................................54 3.3. Mulţimea caracterelor ....................................................56 3.3.1. Litere şi numere.......................................................57 I
  • 2. 3.3.2. Caractere whitespace...............................................57 3.3.3. Caractere speciale şi de punctuaţie..........................57 3.3.4. Secvenţe escape.......................................................58 3.4. Identificatori....................................................................59 3.5. Cuvintele cheie ale limbajului C.....................................60 3.6. Constante.........................................................................60 3.6.1. Constante caracter...................................................61 3.6.2. Constante întregi.....................................................61 3.6.3. Constante în virgulă mobilă....................................61 3.6.4. Constante şir............................................................62 3.6.5. Constanta zero.........................................................63 3.6.6. Obiecte constante....................................................63 3.6.7. Enumerări ...............................................................64 Capitolul IV......................................................................................64 ..........................................................................................................64 Operanzi şi operatori în C...............................................................64 4.1. Operanzi .........................................................................65 4.2. Operatori ........................................................................65 4.2.1. Operatori aritmetici.................................................66 4.2.2. Operatori de incrementare şi decrementare.............67 4.2.3. Operatori relaţionali................................................67 4.2.4. Operatori logici.......................................................68 4.2.5. Operatori logici la nivel de bit.................................69 4.2.6. Operatorul de atribuire............................................74 4.2.7. Operatorul sizeof.....................................................74 4.2.8. Operatorul ternar ?.................................................75 4.2.9. Operatorul virgulă...................................................76 4.2.10. Operatorul de forţare a tipului sau de conversie explicită (expresie cast)........................................77 4.2.11. Operatorii paranteză..............................................77 4.2.12. Operatorul adresă..................................................78 4.2.13. Alţi operatori ai limbajului C................................78 4.2.14. Regula conversiilor implicite şi precedenţa operatorilor...........................................................78 Capitolul V.......................................................................................80 ..........................................................................................................80 II
  • 3. Instrucţiuni.......................................................................................80 5.1. Instrucţiuni etichetate (instrucţiunea goto).....................80 5.2. Instrucţiuni expresie........................................................81 5.3. Instrucţiuni compuse.......................................................82 5.4. Instrucţiuni de selecţie....................................................82 5.4.1. Instrucţiunea if.........................................................82 5.4.2. Instrucţiuni de selecţie multiplă: if - else if.............84 5.4.3. Instrucţiunea switch.................................................85 5.5. Instrucţiuni repetitive......................................................87 5.5.1. Instrucţiunea for......................................................87 5.5.2. Instrucţiunea while..................................................90 5.5.3. Instrucţiunea do-while.............................................92 5.5.4. Bucle încuibate........................................................94 5.5.5. Instrucţiunea break..................................................96 5.5.6. Instrucţiunea continue.............................................97 Capitolul VI......................................................................................97 ..........................................................................................................97 TIPURI DE DATE STRUCTURATE ...........................................97 6.1. Tablouri unidimensionale...............................................98 6.1.1. Constante şir............................................................99 6.1.2. Iniţializarea vectorilor de caractere.......................100 6.1.3. Funcţii pentru prelucrarea şirurilor (fişierul antet string.h)...............................................................102 6.2. Tablouri cu două dimensiuni (matrice).........................104 6.2.1. Iniţializarea matricelor..........................................105 6.2.2. Tablouri bidimensionale de şiruri.........................106 6.3. Tablouri multidimensionale..........................................106 6.4. Structuri.........................................................................107 6.4.1. Tablouri de structuri..............................................109 6.4.2. Introducerea structurilor în funcţii........................115 6.4.3. Tablouri şi structuri în structuri.............................119 6.5. Uniuni...........................................................................119 6.6. Enumerări......................................................................121 Capitolul VII..................................................................................123 ........................................................................................................123 POINTERI.....................................................................................123 III
  • 4. 7.1. Operatori pointer...........................................................123 7.1.1. Importanţa tipului de bază.....................................125 7.1.2. Expresii în care intervin pointeri...........................125 7.2. Pointeri şi tablouri.........................................................130 7.2.1. Indexarea pointerilor.............................................131 7.2.2. Pointeri şi şiruri ....................................................133 7.2.3. Preluarea adresei unui element al unui tablou.......134 7.2.4. Tablouri de pointeri...............................................134 7.2.5. Pointeri la pointeri.................................................135 7.2.6. Iniţializarea pointerilor..........................................136 7.2.7. Alocarea dinamică a memoriei..............................137 7.2.8. Pointeri la structuri................................................139 7.2.9. Structuri dinamice liniare de tip listă...................142 Capitolul VIII.................................................................................155 FUNCŢII.......................................................................................155 8.1. Forma generală a unei funcţii.......................................155 8.2. Reîntoarcerea dintr-o funcţie........................................158 8.3. Valori returnate.............................................................159 8.4. Domeniul unei funcţii...................................................160 8.4.1. Variabile locale.....................................................160 8.4.2. Parametri formali...................................................162 8.4.3. Variabile globale...................................................163 8.5. Apelul funcţiilor............................................................167 8.6. Apelul funcţiilor având ca argumente tablouri.............168 8.7. Argumentele argc şi argv ale funcţiei main()...............172 8.8. Funcţii care returnează valori neîntregi........................173 8.9. Returnarea pointerilor...................................................174 8.10. Funcţii de tip void.......................................................177 8.11. Funcţii prototip............................................................178 8.12. Funcţii recursive..........................................................180 8.13. Clase de memorare (specificatori sau atribute)...........181 8.14. Pointeri la funcţii.........................................................187 Capitolul IX....................................................................................188 PREPROCESAREA.....................................................................189 9.1. Directive uzuale............................................................189 9.2. Directive pentru compilare condiţionată.......................191 IV
  • 5. 9.3. Modularizarea programelor.........................................195 Capitolul X.....................................................................................200 ........................................................................................................200 INTRĂRI/IEŞIRI...........................................................................200 10.1. Funcţii de intrare şi ieşire - stdio.h..............................200 10.2. Operaţii cu fişiere........................................................203 10.3. Nivelul inferior de prelucrare a fişierelor...................206 10.3.1. Deschiderea unui fişier........................................206 10.3.2. Scrierea într-un fişier...........................................210 10.3.3. Citirea dintr-un fişier...........................................212 10.3.4. Închiderea unui fişier...........................................214 10.3.5. Poziţionarea într-un fişier....................................214 10.3.6 Ştergerea unui fişier.............................................216 10.3.7. Exemple de utilizare a funcţiilor de intrare/ieşire de nivel inferior.......................................................217 10.4. Nivelul superior de prelucrare a fişierelor..................222 10.4.1. Funcţia fopen()....................................................223 10.4.2. Funcţia fclose()....................................................224 10.4.3. Funcţiile rename() şi remove()...........................225 10.4.4. Funcţii de tratare a erorilor..................................225 10.4.5. Funcţii cu acces direct.........................................226 10.4.6. Funcţii pentru poziţionare...................................228 10.4.7. Ieşiri cu format....................................................229 10.4.8. Intrări cu format..................................................232 10.4.9. Funcţii de citire şi scriere a caracterelor..............235 Capitolul XI....................................................................................239 ........................................................................................................239 Utilizarea ecranului.......................................................................239 în mod text.....................................................................................239 11.1. Setarea ecranului în mod text......................................240 11.2. Definirea unei ferestre.................................................241 11.3. Ştergerea unei ferestre.................................................241 11.4. Deplasarea cursorului..................................................241 11.5. Setarea culorilor..........................................................242 11.6. Funcţii pentru gestiunea textelor.................................243 Capitolul XII..................................................................................246 V
  • 6. ........................................................................................................246 Utilizarea ecranului.......................................................................246 în mod GRAFIC............................................................................246 12.1. Iniţializarea modului grafic.........................................247 12.2. Gestiunea culorilor......................................................249 12.3. Setarea ecranului.........................................................251 12.4. Utilizarea textelor în mod grafic.................................251 12.5. Gestiunea imaginilor...................................................253 12.6. Desenarea şi colorarea figurilor geometrice...............254 Capitolul XIII.................................................................................260 Funcţii matematice .......................................................................260 13.1 Funcţii trigonometrice..................................................261 13.2 Funcţii trigonometrice inverse.....................................261 13.3 Funcţii hiperbolice.......................................................261 13.4 Funcţii exponenţiale şi logaritmice..............................262 13.5 Generarea de numere aleatoare....................................262 13.6 Alte tipuri de funcţii matematice.................................263 Capitolul XIV.................................................................................264 ELEMENTE DE PROGRAMARE AVANSATĂ.......................264 14.1 Gestionarea memoriei..................................................264 14.1.1 Memoria convenţională........................................264 14.1.2 Memoria expandată..............................................267 14.1.3 Memoria extinsă...................................................268 14.1.4 Stiva......................................................................268 14.2 Servicii DOS şi BIOS..................................................269 14.2.1 Serviciile BIOS....................................................270 14.2.2 Serviciile DOS......................................................274 14.3 Bibliotecile C...............................................................277 14.3.1 Reutilizarea unui cod obiect.................................278 14.3.2 Lucrul cu fişiere bibliotecă...................................278 14.3 Fişierele antet...............................................................279 BIBLIOGRAFIE............................................................................279 VI
  • 7. Capitolul I INTRODUCERE ÎN ARHITECURA SISTEMELOR DE CALCUL 1.1. Importanţa limbajului C Creat în anul 1972 de programatorii de sistem Dennis M. Ritchie şi Brian W. Kernighan de la Bell Laboratories cu scopul de a asigura implementarea portabilă a sistemului de operare UNIX, C-ul este astăzi unul din cele mai cunoscute şi puternice limbaje de programare. Eficient, economic şi portabil, C-ul este o alegere bună pentru realizarea oricărui tip de programe, de la editoare de texte, jocuri cu facilităţi grafice, programe de gestiune şi pentru calcule ştiinţifice, până la programe de sistem, constituind unul dintre cele mai puternice instrumente de programare. Adesea referit ca limbaj portabil, C-ul permite transferul programelor între calculatoare cu diferite procesoare şi în acelaşi timp facilitează utilizarea caracteristicilor specifice ale maşinilor particulare, programele scrise în C fiind considerate cele mai portabile. Dacă evoluţia limbajelor de programare a adus în prim plan nume ca FORTRAN, LISP, COBOL, ALGOL-60 sau PASCAL, unele cu răspândire mai mult ”academică” – fiind folosite pentru a prezenta conceptele de bază sau conceptele avansate de programare – ca de pildă ALGOL-60 sau PASCAL, altele cu răspândire industrială masivă – ca de pildă FORTRAN şi COBOL – limbajul C a pătruns mai lent, dar foarte sigur. Realitatea arată clar că, în momentul de faţă, piaţa producătorilor de programe este dominată net de C şi de variantele evoluate ale acestuia. Elementele principale care au contribuit la succesul C-ului sunt următoarele: - modularizarea programelor – ce dă posibilitatea unui singur programator să stăpânească relativ uşor programe de zeci de mii de linii de sursă; 1
  • 8. - capacitatea de programare atât la nivel înalt cât şi la nivel scăzut – ceea ce dă posibilitatea utilizatorului de a programa fie fără a ”simţi” sistemul de operare şi maşina de calcul, fie la un nivel apropiat de sistemul de operare ceea ce permite un control foarte bun al eficienţei programului din punct de vedere viteză/memorie; - portabilitatea programelor – ce permite utilizarea programelor scrise în C pe o mare varietate de calculatoare şi sisteme de operare; - facilităţile de reprezentare şi prelucrare a datelor – materializate printr-un număr mare de operatori şi funcţii de bibliotecă ce fac programarea mult mai uşoară. Prin anii ’80 interesul pentru programarea orientată pe obiecte a crescut, ceea ce a condus la apariţia de limbaje care să permită utilizarea ei în scrierea programelor. Limbajul C a fost dezvoltat şi el în această direcţie şi în anul 1980 a fost dat publicităţii limbajul C++, elaborat de Bjarne Stroustrup de la AT&T. La ora actuală, majoritatea limbajelor de programare moderne au fost dezvoltate în direcţia programării orientate pe obiecte. Limbajul C++, ca şi limbajul C, se bucură de o portabilitate mare şi este implementat pe o gamă largă de calculatoare începând cu microcalculatoare şi până la cele mai mari supercalculatoare. Limbajul C++ a fost implementat pe microcalculatoarele compatibile IBM PC în mai multe variante. Cele mai importante implementări ale limbajelor C++ pe aceste calculatoare sunt cele realizate de firmele Microsoft şi Borland. Conceptele programării orientate pe obiecte au influenţat în mare măsură dezvoltarea limbajelor de programare în ultimul deceniu. De obicei, multe limbaje au fost extinse astfel încât ele să admită conceptele mai importante ale programării orientate pe obiecte. Uneori s-au făcut mai multe extensii ale aceluiaşi limbaj. De exemplu, limbajul C++ are ca extensii limbajul E ce permite crearea şi gestiunea obiectelor persistente, lucru deosebit de important pentru sistemele de gestiune a bazelor de date, limbajul O ce încearcă să îmbine facilităţile de nivel înalt cu cele ale programării de sistem, limbajul Avalon/C++ destinat calculului distribuit, şi nu în ultimul rând binecunoscutul de acum limbaj Java, specializat în aplicaţii Internet. Interfeţele utilizator au atins o mare dezvoltare datorită facilităţilor oferite de componentele hardware ale diferitelor 2
  • 9. 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 utilizând diferite standarde care conţin ferestre, bare de meniuri, cutii de dialoguri, butoane, etc. Cu ajutorul mouse-ului toate acestea pot fi accesate extrem de rapid şi uşor fără a mai fi nevoie să cunoşti şi să memorezi o serie întreagă comenzi ale sistemului de operare sau ale limbajului de programare. Toate acestea conduc la interfeţe simple şi vizuale, accesibile unui segment foarte larg de utilizatori. Implementarea interfeţelor este mult simplificată prin utilizarea limbajelor orientate spre obiecte, aceasta mai ales datorită posibilităţii de a utiliza componente standardizate aflate în biblioteci specifice. Importanţa aplicării conceptului de reutilizare a codului rezultă din faptul că interfeţele utilizator adesea ocupă 40% din codul total al aplicaţiei. Firma Borland comercializează o bibliotecă de componente standardizate care pot fi utilizate folosind limbajul C++, bibliotecă cunoscută sub numele Turbo Vision. De obicei, interfeţele utilizator gestionează ecranul în mod grafic. O astfel de interfaţă utilizator se numeşte interfaţă utilizator grafică. Una din cele mai populare interfeţe utilizator grafice pentru calculatoarele IBM PC este produsul Windows oferit de firma Microsoft. Windows este un mediu de programare ce amplifică facilităţile oferite de sistemul de operare MS-DOS. Aplicaţiile Windows se pot dezvolta folosind diferite medii de dezvoltare ca: Turbo C++ pentru Windows, Pascal pentru Windows, Microsoft C++, Microsoft Visual Basic, Visual C şi Visual C++. Componentele Visual permit specificarea în mod grafic a interfeţei utilizator, a unei aplicaţii, folosind mouse-ul, iar aplicaţia propriu-zisă se programează într-un limbaj de tip Basic, C sau C++. Dacă în ani ’70 se considera că o persoană este rezonabil să se poată ocupa de o aplicaţie de 4-5 mii de instrucţiuni, în prezent, în condiţiile folosirii limbajelor de programare orientate pe obiecte, această medie a ajuns la peste 25 de mii de instrucţiuni. Un limbaj de programare trebuie privit nu doar la suprafaţa sa – sintaxă şi mod de butonare a calculatorului pentru o implementare particulară – ci mai ales în profunzime, prin conceptele pe care se bazează, prin stilul de programare, prin modul de structurare a 3
  • 10. 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 C- ul este, şi mai mult, o necesitate vitală, acesta fiind piatra de temelie pentru înţelegerea şi utilizarea eficientă a limbajelor de nivel înalt orientate pe obiecte şi Visual. 1.2 Arhitectura de bază a unui calculator Calculatoarele de tip PC (calculatoare personale) reprezintă cele mai răspândite şi mai utilizate dintre calculatoare, datorită gradului de accesibilitate şi preţului relativ scăzut. Indiferent de tipul calculatorului, modul general de concepţie, de alcătuire şi funcţionare este acelaşi. Calculatorul este o maşină programabilă. Două dintre principalele caracteristici ale unui calculator sunt: 1. Răspunde la un set specific de instrucţiuni într-o manieră bine definită. 2. Calculatorul poate executa o listă preînregistrată de instrucţiuni, numită program. Calculatoarele moderne sunt electronice şi numerice.  Partea de circuite electrice şi electronice precum şi conexiunile fizice dintre ele se numeşte hardware.  Totalitatea programelor precum şi datele aferente acestor programe poartă denumirea de software. 4
  • 11. Echipamente de ieşire Echipamente de stocare date (HDD, FDD, CD-ROM, etc.) UPC Unitatea de procesare şi control Echipamente de intrare Fig.1.1 Configuraţia standard pentru utilizator Partea hardware a unui calculator este formată din totalitatea componentelor sale fizice. Toate calculatoarele de uz general necesită următoarele componente hardware:  memorie: Permite calculatorului să stocheze, cel puţin temporar, date şi programe.  dispozitive de stocare externe: Permit calculatoarelor să stocheze permanent programe şi mari cantităţi de date. Cele mai uzuale dispozitive de stocare externă sunt HDD (hard disk drives), FDD (floppy disk drive) şi CD-ROM (Compact Disk-Read Only Memory) sau CD-R/W (Compact Disk- Read/Write).  dispozitive de intrare : În mod uzual sunt reprezentate de tastatură (keyboard) şi de mouse. Aceste dispozitive reprezintă calea uzuală de introducere a datelor şi instrucţiunilor care gestionează funcţionarea unui calculator.  dispozitive de ieşire: Reprezintă modalitatea prin care calculatorul transmite utilizatorului uman rezultatele execuţiei programelor. Ecranul monitorului sau imprimanta sunt astfel de dispozitive uzuale.  unitatea de procesare şi control (UPC) : Este partea principală a unui calculator deoarece este componenta care execută instrucţiunile. În mod uzual această unitate de procesare şi control este reprezentată de un microprocesor care se 5
  • 12. plasează pe placa de bază (mainboard) a calculatorului împreună cu memoria internă RAM. În plus faţă de aceste componente orice calculator este prevăzut cu o magistrală (bus) prin care se gestionează modalitatea de transmitere a datelor între componentele de bază ale calculatorului. Magistrala reprezintă o colecţie de trasee electrice care leagă microprocesorul de dispozitivele de intrare/ieşire şi de dispozitivele interne/externe de stocare a datelor. Putem distinge magistrala de date, magistrala de adrese şi magistrala de comandă şi control. În figura 1.2 este prezentată interacţiunea dintre componentele HARD principale ale unui calculator. Calculatoarele pot fi în general clasificate după dimensiuni sau putere de calcul. Nu se poate face însă la ora actuală o distincţie netă între următoarele categorii de calculatoare: PC (Personal Computer): Un calculator de dimensiuni mici, monoutilizator (single-user), bazat pe un microprocesor. În plus acesta este dotat standard cu tastatură, mouse, monitor şi dispozitive periferice de stocare a datelor. Memoria secundară Echipament UNITATEA Echipament de intrare CENTRALĂ de ieşire Memoria principală Fig. 1.2 Arhitectura minimală a unui sistem de calcul  staţii de lucru (workstation): Un calculator monoutilizator de mare putere. Aceasta este asemănător unui PC dar are un microprocesor mai puternic şi un monitor de înaltă calitate (rezoluţie mai mare). 6
  • 13. minicalculator (minicomputer): Un calculator multiutilizator (multi-user) capabil să lucreze simultan cu zeci sau chiar sute de utilizatori.  mainframe: Un calculator multiutilizator capabil să lucreze simultan cu sute sau chiar mii de utilizatori.  supercomputer: Un computer extrem de rapid care poate executa sute de milioane de operaţii într-o secundă. 1.2.1 Microprocesorul Microprocesorul este cea mai importantă şi cea mai scumpă componentă a unui calculator de performanţele acesteia depinzând în mare măsură rezultatele întregului sistem. Din punct de vedere fizic, microprocesorul este un cip ce conţine un circuit integrat complex ce îi permite să prelucreze informaţii prin executarea unor operaţii logice şi matematice diverse (adunări, scăderi, înmulţiri, împărţiri, comparări de numere). El este compus din două părţi importante: unitatea de execuţie (EU – Execution Unit) şi unitatea de interfaţă a magistralei de date (BIU – Bus Interface Unit). Prima componentă realizează efectiv operaţiile, iar cea de-a doua are funcţia de transfer a datelor de la şi înspre microprocesor. Microprocesorul reprezintă de fapt unitatea centrală a unui calculator şi îndeplineşte o serie de activităţi specifice cum ar fi: execută operaţii aritmetice şi logice, decodifică instrucţiuni speciale, transmite altor cipuri din sistem semnale de control. Toate aceste operaţii sunt executate cu ajutorul unor zone de memorie ale microprocesorului, numite registre. Orice microprocesor are un set finit de instrucţiuni pe care le recunoaşte şi pe care le poate executa. Calculatoarele IBM PC folosesc procesoare INTEL sau compatibile, realizate de alte companii cum ar fi: AMD, NexGen, CYRIX. Numele microprocesorului este folosit la identificarea calculatorului. Se folosesc frecvent expresii de tipul calculator 386, calculator 486, calculator Pentium II, etc. În 1971 firma Intel a fost abordata de o companie Japoneza, acum dispărută, pentru a construi un circuit dedicat pentru un nou calculator. Designerul Ted Hoff a propus o soluţie programabilă, de uz general, şi astfel s-a născut circuitul Intel 4004. Au urmat la scurt timp chipurile 4040 si 8008 dar lor le lipseau multe din caracteristicile microprocesoarelor aşa cum le ştim noi azi. În 1974 Intel a prezentat 7
  • 14. pentru prima oară circuitul Intel 8080 care a fost folosit in sistemele Altair şi IMSAI. Curând după aceea au apărut procesoarele Motorola 6800 şi 6502 de la MOS Technology. Doi dintre proiectanţii de la Intel au părăsit firma, creând corporaţia ZILOG care a produs chipul Z80 (compatibil cu 8080 dar cu set de instrucţiuni mai puternic şi de două ori mai rapid. Cipul Intel 4004 a fost primul procesor comercial, lansat la sfârşitul anului 1971. La un preţ de circa 200$ şi înglobând 2300 de tranzistori, cipul 4004 dezvolta mai multă putere de calcul decât ENIAC, primul calculator electronic, cu 25 de ani în urma. Faţă de cele 18.000 de tuburi cu vacuum ce ocupau 900 metri cubi, procesorul 4004 putea dezvolta 60.000 de operaţii pe secundă. Această invenţie a contribuit la revoluţionarea domeniilor de aplicaţii ale computerelor, dând startul unui adevărat galop de inovaţii tehnologice. Următorul pas a fost în 1980, când IBM a inclus un procesor Intel în arhitectura primului PC. Astăzi PC-urile sunt pretutindeni în jurul nostru. Un copil care lucrează la o maşina ce incorporează un procesor Pentium Pro beneficiază de mult mai multă putere de calcul decât dispunea guvernul SUA în perioada lansării primelor echipaje umane către Lună. Într-un număr aniversar al publicaţiei Communications of the ACM, Gordon Moore, co-fondator al companiei Intel, era optimist în privinţa evoluţiei PC-urilor şi a microprocesoarelor: "complexitatea microprocesoarelor, care se măsoară prin numărul de tranzistori pe cip, s-a dublat aproape constant la fiecare 18 luni, de la apariţia primului prototip 4004. Aceasta evoluţie exponenţială a determinat o continuă creştere a performanţelor PC-urilor şi o scădere a costului procesului de calcul. Pe când în 1991 un PC bazat pe procesorul Intel 486 costa aproape 225$ pentru o performanţă de un milion de instrucţiuni pe secundă (MIPS), astăzi, un sistem desktop ce utilizează un cip Pentium Pro este evaluat la circa 7$ pe MIPS. Nu se întrevede nici o dificultate care să frâneze această rată de dezvoltare". 1.2.2 Memoria Microprocesorul are capacitatea de a memora date care urmează a fi prelucrate, cât şi rezultatele intermediare. Se observă că rolul său principal este de a prelucra şi transmite informaţiile şi rezultatele şi deci capacitatea sa de memorare este mică neputând stoca programe. 8
  • 15. De aceea, un calculator necesită şi o memorie care să găzduiască date şi programe. Memoria este formată din punct de vedere fizic din cipuri ce stochează informaţia sub forma a două niveluri de tensiune ce corespund valorilor 0 şi 1 din sistemul de numeraţie. Celulele de bază ale memoriei (ce pot avea valoarea 0 sau 1) se numesc biţi şi ele reprezintă particulele cele mai mici de informaţie din calculator. Pentru citirea informaţiilor nu se folosesc biţi în mod individual ci aceştia sunt grupaţi într-o succesiune. Astfel o succesiune de 8 biţi formează un octet (sau un byte) aceasta reprezentând unitatea de măsură a capacităţii de memorie. Deoarece reprezentarea numerelor în calculator se face în baza 2 şi nu în baza 10, aşa cum suntem obişnuiţi în mod normal să lucrăm, şi multiplii unui byte vor fi puteri ale lui 2, astfel: 1 KB=210B=1024 B 1 MB=210KB=1 048 576 B 1 GB=210MB=230 B Abrevierile K (kilo), M (mega), G (giga) se scriu de obicei cu litere mari şi reprezintă mii, milioane şi, respectiv, miliarde. Memoria unui calculator are două componente: memoria principală (internă) şi memoria secundară (externă). Memoria internă este memoria ce poate fi accesată în mod direct de către microprocesor şi în care sunt încărcate programele înainte de a fi executate de către microprocesor. Dacă primele procesoare puteau accesa doar 1 MB de memorie astăzi un procesor Pentium poate accesa peste 256 MB. Memoria principală este formată din două tipuri de circuite: cip-uri ROM şi cip-uri RAM. Circuitele de tip ROM (Read Only Memory) au memorate programele care controlează iniţial calculatorul (sistemul de operare). Aceste memorii pot fi doar citite (conţinutul lor nu poate fi modificat). Înscrierea conţinutului acestor memorii se face de către fabricant, iar operaţiunea de înscriere cu programe se mai numeşte „arderea memoriilor”. Circuitele de tip RAM (Random Acces Memory ) sunt memorii la care utilizatorul are acces şi al căror conţinut se şterge la deconectarea calculatorului. În memoria RAM informaţia este stocată temporar. De exemplu, programele de aplicaţii curente şi datele asociate acestor aplicaţii sunt încărcate în memoria RAM înainte de a fi prelucrate de către microprocesor. 9
  • 16. Deoarece capacitatea de memorie a unui calculator nu poate fi atât de mare încât să poată păstra toate programele pe vrem să le executăm, a apărut necesitatea existenţei unor memorii externe, care să fie solicitate la nevoie. Rolul acestora îl joacă discurile şi ele pot fi asemănate cu cărţile dintr-o bibliotecă pe care le putem consulta ori de câte ori avem nevoie de anumite informaţii. Primele discuri apărute pentru PC-uri, numite şi dischete, floppy disk-uri sau discuri flexibile, permiteau stocarea a maximum 160 KB de informaţie. Astăzi mai există doar dischete cu diametrul de 3.5 inch cu capacitatea de 1,44 MB. Existenţa acestora pare a fi pusă însă în pericol de apariţia CD-urilor reinscriptibile a căror capacitate de memorare depăşeşte 700 MB iar evoluţia tehnologică, din ce în ce mai rapidă, dă semne că lucrurile nu se vor opri aici. Dischetele folosesc metode magnetice de memorare a informaţiei motiv pentru care ele se mai numesc şi suporturi magnetice de informaţie. Principala componentă a unui calculator utilizată pentru memorarea programelor o reprezintă hard discul. Acesta poate fi asemănat cu o dischetă de mare capacitate, integrată într-o unitate încapsulată. Iniţial, puţine PC-uri prezentau hard discuri, dar cum preţurile acestora au scăzut considerabil, iar performanţele şi capacităţile au crescut, în prezent toate calculatoarele prezintă acest dispozitiv. În clipa de faţă, capacitatea de memorare a unui hard disc a depăşit valoarea de 40 de GB. 1.2.3 Echipamentele periferice Comunicarea om-maşină se realizează cu ajutorul echipamentelor periferice prin intermediul cărora utilizatorul poate programa sau da anumite comenzi calculatorului sau poate vizualiza rezultatele obţinute de către anumite programe. Principalele echipamente periferice ale unui calculator sunt următoarele: tastatura, mouse-ul, scanner-ul, monitorul şi imprimanta. Ele pot fi grupate în echipamente de intrare – cele prin care calculatorul primeşte informaţii sau comenzi (tastatură, mouse, scanner) - şi echipamente de ieşire – cele prin care calculatorul transmite informaţii în exterior (monitor, imprimantă). În continuare sunt prezentate câteva caracteristici ale fiecărui echipament. Tastatura – este principalul dispozitiv de intrare al calculatorului prin intermediul căruia se transmit comenzi către 10
  • 17. unitatea centrală. Cuplarea la calculator a tastaturii se face prin intermediul unui cablu de conectare. Din punct de vedere al dispunerii tastelor, tastatura se aseamănă destul de mult cu cea a unei maşini de scris dar are şi părţi care o individualizează. Primele tastaturi au avut 83/84 de taste, pentru ca, ulterior, ele să fie îmbogăţite prin dublarea tastelor existente sau adăugarea altora noi, ajungându-se la 101/102 taste. Din punct de vedere al funcţionalităţii lor ele pot fi împărţite în patru categorii: - taste alfanumerice; - taste cu scopuri speciale; - taste direcţionale şi numerice; - taste funcţionale. Tastele alfanumerice conţin literele, cifrele şi semnele de punctuaţie şi ocupă partea centrală a tastaturii. Acţionarea unei astfel de taste determină apariţia caracterului corespunzător pe ecranul calculatorului. Tastele cu scopuri speciale sunt aşezate în acelaşi bloc cu tastele alfanumerice şi determină efectuarea anumitor acţiuni fără înscrierea de caractere pe ecran. Tastele de mişcare se află situate în partea dreaptă a tastaturii şi ele funcţionează în două moduri, care pot fi comutate prin acţionarea altei taste, aflate deasupra lor, pe care scrie NumLock. Dacă ledul corespunzător acestei taste este aprins, modul de lucru este numeric, în caz contrar fiind comutat pe semnificaţia direcţională. Tastele funcţionale sunt un grup de 12 taste situate în partea de sus a tastaturii având pe ele litera F urmată de un număr între 1 şi 12. Acţionarea acestor taste determină efectuarea unor operaţii specifice de la program la program. Mouse-ul – este tot un echipament de intrare mai uşor de manevrat decât tastatura dar care poate efectua mai puţine operaţii. Totuşi, foarte multe aplicaţii (în special aplicaţiile grafice) nu mai pot fi concepute fără mouse. Un mouse are aspectul unei bucăţi de săpun, uşor manevrabil, având dedesubt o bilă poziţionabilă, cu sensibilitate şi viteză reglabile. Mişcarea maouse-ului pe o suprafaţă plană este corelată cu deplasarea pe ecran a unui cursor cu o formă deosebită: cruciuliţă, săgeată, etc. Declanşarea unei acţiuni se face prin poziţionarea cursorului în zona corespunzătoare şi apăsarea unuia dintre butoanele aflate pe partea posterioară. Iniţial un mouse avea două sau trei 11
  • 18. butoane. Acum există mouse-uri cu 5 butoane şi 2 rotiţe ce îndeplinesc o serie de funcţii corespunzătoare unor taste speciale. Folosirea mouse-ului uşurează mult munca utilizatorilor, nemaifiind necesar ca aceştia să memoreze numărul relativ mare de comenzi corespunzător fiecărui produs, ca în situaţia în care se foloseşte numai tastatura. Utilitatea mouse-ului este şi mai evidentă în cazul aplicaţiilor grafice. De altfel, WINDOWS este un sistem de operare creat special pentru lucrul cu mouse-ul. Scanner-ul – reprezintă dispozitive care se cuplează la un PC şi cu care, prin intermediul unui software adecvat, se pot capta imagini, fotografii, texte etc., în vederea unei prelucrări ulterioare. Astfel se pot manevra imagini foto, se pot crea efecte grafice speciale, care nu se pot obţine prin metode tradiţionale. După captare, imaginea poate fi prelucrată, mutată, mărită, micşorată, rotită, colorată, umbrită, suprapusă cu altă imagine etc. Cu un software de recunoaştere optică a caracterelor datele sau documentele tipărite pe coli de hârtie pot fi transformate în fişiere, putându-se realiza chiar o stocare a lor sub formă de arhivă. Monitorul - Sistemul video este format din două părţi: un adaptor (placă) video şi un monitor sau display. Adaptorul video reprezintă dispozitivul care realizează legătura (interfaţa) cu calculatorul şi se află în interiorul acestuia. El va fi corespunzător tipului de monitor video care îi este ataşat. Adaptorul video realizează o rezoluţie orizontală şi una verticală. Rezoluţia reprezintă numărul de elemente, în cazul de faţă puncte – pixeli – care pot fi afişate pe ecran. De exemplu, un monitor VGA, în mod video, are o rezoluţie de 640 x 480 pixeli. Standardul VGA (Video Graphics Array) a fost introdus de către IBM o dată cu calculatoarele PS/2, iar modurile video VGA reprezintă un superset al standardelor video anterioare, CGA (Color Graphics Adapter) şi EGA (Enhanced Graphics Adapter). Cea mai importantă îmbunătăţire adusă de standardul VGA a fost rezoluţia superioară a caracterelor în modul text, precum şi posibilitatea de a afişa 256 de culori la un moment dat. Monitorul, denumit uneori şi display, permite vizualizarea datelor introduse de la tastatură sau rezultate în urma execuţiei unor comenzi sau programe, fiind încadrat în categoria echipamentelor periferice de ieşire. Ca piesă principală, monitorul conţine un tub de 12
  • 19. vacuum, similar cu cel de la televizor şi trei tunuri de electroni (corespunzătoare celor trei culori fundamentale). Standardele video MDA, CGA şi EGA folosesc monitoare digitale. Datele care descriu culorile pixelilor sunt trimise de adaptorul video la monitor sub forma unor serii de semnale digitale care sunt echivalente unor serii de biţi. Standardul VGA a introdus un nou tip de monitor care utilizează semnale analogice pentru transferul informaţiilor privind culoarea de la adaptorul video la monitor. Dacă semnalele digitale prezintă niveluri care indică prezenţa sau absenţa unui bit, semnalele analogice pot prezenta orice valoare între una minimă şi una maximă. Imprimanta - Reprezintă un dispozitiv care poate fi ataşat unui calculator, cu scopul tipăririi de texte şi grafică, putând fi considerată un fel de maşină de scris automată. Până în prezent au fost realizate un număr destul de mare de tipuri de imprimante pentru PC-uri, ele diferind atât prin performanţe, cât şi prin modalităţile tehnice de ralizare. Fiecare dintre ele prezintă avantaje şi dezavantaje, ideal fiind a o folosi pe cea care corespunde cel mai bine tipului de lucrări executate. În funcţie de modul în care este realizată imprimarea se disting următoarele tipuri de imprimante: - imprimante matriceale cu 9, 18 sau 24 de ace – realizează imprimarea prin impactul acelor peste o bandă de hârtie; - imprimante cu jet de cerneală – funcţionează prin pulverizarea fină a unor picături de cerneală pe hârtia de imprimat; - imprimante laser – ce utilizează o rază laser sau mici diode luminiscente care încarcă electrostatic un tambur de imprimare, corespunzător caracterului care urmează a fi imprimat; - imprimante rapide de linii – ce imprimă mai multe linii odată, fiind folosite mai mult la sisteme de calcul de dimensiuni mari. Calitatea imprimării creşte de la primul la ultimul tip prezentat, dar în mod corespunzător şi preţul echipamentului. 1.3. Programarea calculatorului 13
  • 20. Programele de calculator, cunoscute sub numele de software, sunt constituite dintr-o serie de instrucţiuni pe care le execută calculatorul. Când se creează un program, trebuie specificate instrucţiunile pe care calculatorul trebuie să le execute pentru a realiza operaţiile dorite. Procesul de definire a instrucţiunilor pe care le execută calculatorul se numeşte programare. Programele executate pe un calculator pot fi împărţite în trei categorii: • programe de aplicaţie – sunt acele programe care interacţionează direct cu utilizatorul, specializate în realizarea unei categorii de prelucrări. Editoarele de texte, programele pentru gestiunea bazelor de date, programele de tehnoredactare asistată de calculator, de grafică etc. sunt programe de aplicaţie. • utilitare – programe, care la fel ca programele de aplicaţie, interacţionează direct cu utilizatorul, dar, spre deosebire de acestea, realizează prelucrări de uz general. Utilitarele realizează o serie de operaţii de „gospodărie” cum ar fi: copierea fişierelor, pregătirea discurilor magnetice pentru utilizare, crearea de copii de salvare, testarea echipamentului, etc. • programe de sistem – realizează legătura între componentele electronice ale calculatorului şi programele de aplicaţie şi utilitare. Rolul programului de sistem este acela de a uşura sarcina programatorului, simplificând îndeplinirea acelor sarcini care sunt comune marii majorităţi a programelor de aplicaţie: alocarea memoriei, afişarea caracterelor pe ecran şi la imprimantă, citirea caracterelor de la tastatură, accesul la informaţiile stocate pe disc magnetic, etc. 1.3.1. Sistemul de operare Sistemul de operare este o parte componentă a software-ului unui calculator, care mai cuprinde un număr variabil de programe utilitare selectate conform cu necesităţile programatorilor. Sistemul de operare este un program cu funcţii de coordonare şi control asupra resurselor fizice ale calculatorului şi care intermediază dialogul om-calculator. Sistemul de operare permite rularea programelor şi păstrarea informaţiilor pe disc. În plus, fiecare sistem 14
  • 21. de operare pune la dispoziţia aplicaţiilor o serie de servicii care permit programelor să aloce memorie, să acceseze diferite echipamente periferice, cum ar fi imprimanta, şi să gestioneze alte resurse ale calculatorului. Un sistem de operare trebuie să aibă capacitatea de a se adapta rapid la modificările tehnologice, rămânând în acelaşi timp compatibil cu hardware-ul anterior. Lanţul de comunicare utilizator – calculator este prezentat în Figura 1.3: Sistemul de operare este cel mai important program care rulează pe un calculator. Orice calculator de uz general este dotat cu un sistem de operare care permite execuţia altor programe. Sistemele de operare execută operaţiuni de bază precum: recunoaşterea unei intrări de la tastatură (preluare caracter), trimiterea unui caracter pentru afişare pe ecranul monitorului, gestionarea fişierelor şi a directoarelor pe disc (floppy-disk sau hard-disk), controlul fluxului de date cu echipamentele periferice ca drivere de disc sau imprimante. CALCULATOR SISTEM DE OPERARE APLICAŢII UTILIZATOR Fig. 1.3. Comunicarea utilizator - calculator 15
  • 22. Aplicaţie Disk-drive Sistem de operare Mouse Monitor Tastaturã Imprimantã Fig. 1.4 Rolul sistemului de operare Sistemul de operare al unui calculator este partea de software necesară şi suficientă pentru execuţia oricăror alte aplicaţii dorite de utilizator. Un calculator nu poate funcţiona decât sub gestiunea unui sistem de operare. Orice aplicaţie lansată în execuţie de către un utilizator apelează la resursele puse la dispoziţie de către sistemul de operare. Sistemul de operare interfaţează calculatorul cu operatorul uman de o manieră cât mai transparentă cu putinţă astfel încât utilizatorul nu trebuie să facă eforturi mari de adaptare dacă lucrează cu arhitecturi hardware diferite. Pentru sisteme mai mari, sistemele de operare au responsabilităţi şi capabilităţi şi mai mari. Ele acţionează ca un gestionar al traficului de date şi al execuţiei programelor. În principal sistemul de operare asigură ca diferite programe şi diferiţi utilizatori să nu interfereze unele cu altele. Sistemul de operare este de asemenea responsabil cu securitatea, asigurând inaccesibilitatea persoanelor neautorizate la resursele sistemului. Sistemele de operare se pot clasifica după cum urmează:  multi-user: Permit ca doi sau mai mulţi utilizatori să ruleze în acelaşi timp programe (utilizatori concurenţi). Anumite sisteme de operare permit sute sau chiar mii de utilizatori concurenţi.  multiprocesor: Permit execuţia unui program pe mai mult de un microprocesor. 16
  • 23. multitasking: Permit mai multor programe să ruleze în acelaşi timp (execuţie concurentă).  multithreading: Permit diferitelor părţi ale unui program să fie executate concurent.  timp real (real time): Răspund instantaneu la diferite intrări. Sistemele de operare de uz general, ca DOS sau UNIX nu sunt sisteme de operare de timp real. Sistemele de operare furnizează o platformă software pe baza căreia alte programe, numite programe de aplicaţie, pot rula (pot fi executate). Programele de aplicaţie trebuie să fie scrise pentru a rula pe baza unui anumit sistem de operare. Alegerea unui anumit sistem de operare determină în consecinţă mulţimea aplicaţiilor care pot fi rulate pe calculatorul respectiv. Pentru PC-uri, cele mai populare sisteme de operare sunt DOS, OS/2 sau Windows, dar mai sunt disponibile şi altele precum Linux. Ca utilizator se interacţionează cu sistemul de operare prin intermediul unor comenzi. Spre exemplu, sistemul de operare DOS acceptă comenzi precum COPY sau RENAME pentru a copia fişiere sau pentru a le redenumi. Aceste comenzi sunt acceptate şi executate de o parte a sistemului de operare numită procesor de comenzi sau interpretor de linie de comandă. Interfaţele grafice cu utilizatorul (GUI, Graphical user interfaces) permit introducerea unor comenzi prin selectarea şi acţionarea cu mouse-ul a unor obiecte grafice care apar pe ecran. Spre exemplu, sistemul de operare Windows are un desktop ca intefaţă garfică cu utilizatorul. Pe acest desktop (birou) se află diferite simboluri grafice (icoane, icons) ataşate diferitelor aplicaţii disponibile pe calculatorul respectiv. Utilizatorul are multiple posibilităţi de configurare a acestei intefeţe grafice. Primul sistem de operare creat pentru calculatoare a fost CP/M (Control Program for Microcomputers), realizat pentru calculatoarele pe 8 biţi. O dată cu perfecţionarea componentelor HARD s-a impus şi necesitatea dezvoltării unui SOFT adecvat. Astfel, în 1981, a apărut prima versiune a sistemului de operare MS-DOS. Sistemul de operare MS–DOS (MicroSoft Disk Operating System) este destinat gestionării resurselor software si hardware ale microcalculatoarelor cu o arhitectura de tip IBM – PC sau compatibilă cu aceasta şi echipate cu procesoare 8086 sau 80x86, Pentium. Odată cu creşterea 17
  • 24. capabilităţilor hardware ale calculatoarelor, acesta s-a transformat, prin dezvoltări succesive, în Windows. Indiferent de sistemul de operare utilizat, din punctul de vedere al utilizatorului, informaţiile sunt scrise pe disc sub forma unor fişiere. Un fişier este o colecţie de informaţii grupate sub acelaşi nume. Un fişier poate fi un program executabil, un text, o imagine, un grup de comenzi sau orice altceva. Un fişier este identificat prin numele său. Numele unui fişier este format dintr-un şir de caractere (care în funcţie de sistemul de operare este limitat la un anumit număr maxim de caractere), urmate eventual de semnul punct (.) şi de încă maximum 4 caractere, numite extensie, ca de exemplu: nume.ext. Pentru a putea avea acces rapid la fişiere, sistemul de operare creează nişte fişiere speciale, numite directoare, care pot fi asemănate cu cuprinsul unei cărţi, deoarece ele conţin numele fişierelor şi adresa de început a acestora. De asemenea, un director poate conţine la rândul său alte directoare creându-se astfel o structură arborescentă de directoare în care poate fi găsit foarte repede un anumit fişier. 1.3.2. Tipuri de fişiere Fişierele se pot împărţi în două categorii – executabile şi neexecutabile. În prima categorie intră acele fişiere al căror nume scris în dreptul prompterului (în cazul sistemului de operare DOS) determină executarea unor activităţi de către sistemul de operare. O parte dintre fişierele executabile sunt programe şi sunt recunoscute prin extensia lor care poate fi EXE sau COM, altele fiind constituite în fişiere de comenzi proprii sistemului de operare, a căror extensie este BAT. Fişierele COM, numite adesea şi comenzi, conţin informaţii în formatul imagine de memorie. Ele sunt mai compacte şi mai rapide decât fişierele EXE, dar lungimea lor nu poate să depăşească 64 K. Fişierele EXE pot să ajungă la dimensiuni mai mari prin segmentarea programului în fragmente a căror dimensiune să fie de maximum 64K. Dintre fişierele neexecutabile vom aminti câteva mai importante: • fişiere text ; • fişiere cu extensia SYS sau DRV, cunoscute sub numele de driver-e şi care conţin instrucţiuni despre modul în care 18
  • 25. sistemul de operare trebuie să controleze diferite componente hardware; • surse de programe scrise în diferite limbaje (cu extensiile PAS – limbajul Pascal, C – limbajul C, CPP – limbajul C++, etc.); • fişiere care conţin informaţii intermediare între cele în limbaj sursă şi cele executabile (extensiile OBJ, OVL); • fişiere ce conţin imagini (extensiile JPEG, GIF, BMP); • fişiere ce conţin sunete (extensiile WAV, MIDI, MP3) etc. 1.3.3. Construirea fişierului executabil Instrucţiunile pe care le execută un calculator sunt de fapt grupuri de 1 ş 0 (cifre binare) care reprezintă semnale electronice produse în interiorul calculatorului. Pentru a programa primele calculatoare (în anii 1940-1950), programatorii trebuiau să înţeleagă modul în care calculatorul interpreta diferitele combinaţii de 0 şi 1, deoarece programatorii scriau toate programele folosind cifre binare. Cum programele deveneau din ce în ce mai mari, acest mod de lucru a devenit foarte incomod pentru programatori. De aceea au fost create limbaje de programare care permit exprimarea instrucţiunilor calculatorului într-o formă mai accesibilă programatorului. După ce programatorul scrie instrucţiunile într-un fişier - numit fişier sursă, un al doilea program – numit compilator, converteşte instrucţiunile limbajului de programare în şirurile 1 şi 0 – cunoscute sub numele de cod maşină. Pentru a obţine un program executabil, orice program sursă trebuie eventual translatat (tradus) în limbaj cod maşină sau cod obiect pe care îl poate înţelege microprocesorul. În urma acestui proces, alături de fişierul sursă apare şi fişierul cod obiect (object file.) Această translatare sau traducere este efectuată de către compilatoare, interpretoare sau asambloare. Compilatorul este folosit pentru transformarea codului sursă, adică a programului scris într-un limbaj de programare de nivel înalt, în cod obiect (object code). Acest cod obiect va fi transformat în faza de editare de legături în cod maşină executabil de microprocesorul sistemului de calcul. Programatorii scriu programe într-o formă numită cod sursă. Acest cod sursă parcurge apoi câţiva paşi înainte de a deveni program executabil. 19
  • 26. Pe scurt, un compilator este un program special care procesează instrucţiuni scrise într-un limbaj de programare particular şi le transformă în limbaj maşină sau cod maşină pe care îl poate executa microprocesorul. La ora actuală un limbaj de programare este inclus într-un mediu de programare mai complex care include un editor de texte pentru introducerea instrucţiunilor în limbajul de programare de nivel înalt, un compilator şi un editor de legături folosite pentru translatarea codului sursă în cod maşină. În mod tipic, un programator scrie declaraţii într-un limbaj precum Pascal, C sau MATLAB folosind un editor. Se creează astfel un fişier numit fişier cod sursă ce conţine o colecţie de instrucţiuni şi declaraţii scrise în limbajul respectiv. Primul pas este prelucrarea codului sursă de către compilator, care translatează instrucţiunile de nivel înalt într-o serie de instrucţiuni cod obiect. Când este lansat în execuţie compilatorul acesta, într-o primă etapă, lansează un analizor sintactic, gramatical, numit parser. Acesta parcurge şi analizează sintactic, secvenţial, în ordinea în care au fost introduse, toate instrucţiunile scrise în limbajul de nivel înalt. O instrucţiune de nivel înalt se translatează într-una sau mai multe instrucţiuni specifice microprocesorului pentru care a fost conceput compilatorul. Aceste instrucţiuni ale microprocesorului sunt înlocuite cu codurile lor binare, fiecare instrucţiune a microprocesorului fiind codificată de către constructor. Codurile binare ale instrucţiunilor microprocesorului împreună cu reprezentările interne ale datelor manipulate formează codul obiect. Deci în unul sau mai multe faze (parserul este una dintre faze) din codul sursă de intrare se produce un cod de ieşire, numit în mod tradiţional cod obiect. Este foarte important ca referiri la alte module de cod să fie corect reprezentate în acest cod obiect. Pasul final în producerea programului executabil, după ce compilatorul a produs codul obiect, este prelucrarea codului obiect de către un editor de legături (link-editor sau linker). Acest linker combină diferitele module (le leagă) şi dă valori reale, efective, tuturor adreselor simbolice existente în codul obiect. În urma acestei prelucrări se obţine codul maşină, salvat într-un fişier cu extensia .exe. Acest cod maşină poate fi executat secvenţial, instrucţiune cu instrucţiune, de către microprocesor. 20
  • 27. Cu alte cuvinte, un program executabil (executable program - aflat pe disc cu extensia .exe) se obţine prin salvarea pe disc a codului maşină obţinut prin prelucrarea succesivă a fişierului cod sursă de către compilator (compiler) şi apoi de către link-editor (linker). Fig. 1.5 Procesul de elaborare a unui program executabil Procesul de obţinere a unui executabil este prezentat în figura de mai jos. Blocurile tridimensionale reprezintă entităţile principale ale mediului de programare: editorul de texte, compilatorul (compiler) şi editorul de legături (linker). Blocurile dreptunghiulare reprezintă fişierele rezultate în urma aplicării celor trei utilitare de sistem:  în urma utilizării editorului de texte obţinem fişierul text sursă cod cu numele generic “nume”. Dacă folosim limbajul de programare C spre exemplu, se obţine fişierul nume.c care se va salva pe disc.  în urma lansării în execuţie a compilatorului, acesta preia fişierul sursă şi îl prelucrează corespunzător, semnalizându-se toate erorile fatale pentru program sau avertismente utile programatorului în procesul de depanare. În cazul în care compilarea se efectuează cu succes, se obţine un fişier cod obiect, salvat pe disc sub numele nume.obj  în urma lansării în execuţie a editorului de legături, se preia fişierul cod obiect nume.obj şi se leagă cu toate modulele necesare (inclusiv funcţii de bibliotecă sau alte module externe), obţinându-se un program executabil (cod maşină) cu 21
  • 28. numele nume.exe la care adresele nu mai sunt simbolice ci absolute relativ la adresa de început a programului. La lansarea în execuţie a programului fluxul de informaţie este complet controlat de către microprocesor, toate salturile de adresă fiind făcute corespunzător. Interpretorul (interpreter) este un program care execută instrucţiuni scrise într-un limbaj de nivel înalt. Numai anumite limbaje de nivel înalt, spre exemplu BASIC, LISP sau MATLAB, sunt prevăzute cu un interpretor. Există două modalităţi de a executa un program scris în limbaj de nivel înalt. Cel mai comun mod este acela de a compila programul. Cealaltă modalitate este “pasarea” programului unui interpretor. Un interpretor translatează instrucţiunile de nivel înalt într-o formă intermediară care este apoi executată. Prin contrast, un compilator translatează instrucţiunile de nivel înalt direct în limbaj maşină (cod maşină). Programele compilate rulează în general mai rapid decât cele interpretate. Un alt avantaj al programelor compilate este acela al desprinderii din context în sensul că programele executabile generate în urma procesului de compilare pot fi executate direct sub sistemul de operare al calculatorului. Un program interpretat se execută sub mediul în care a fost creat. Spre exemplu, pentru a rula un program scris în limbajul BASIC se lansează în execuţie mediul BASIC, apoi se deschide fişierul sursă-BASIC corespunzător şi se lansează interpretorul de BASIC pentru execuţia sa. Avantajul unui interpretor este acela al evitării procesului de compilare consumator de timp în cazul în care avem programe de mari dimensiuni. Interpretorul poate executa imediat programele sursă. Pentru acest motiv interpretoarele se folosesc mai ales în procesul de dezvoltare al programelor, când programatorul doreşte adăugarea unor mici porţiuni de program pe care să le testeze rapid. De asemenea, interpretoarele permit o programare interactivă fiind des folosite în procesul de instrucţie. În mediul de programare MATLAB, mediu interpretor, orice comandă utilizator se execută imediat. Se pot edita şi fişiere script, care conţin secvenţe de comenzi care se execută secvenţial. 22
  • 29. Programele de descriere a paginii (Page Description Languages) ca PostScript spre exemplu folosesc un interpretor. Fiecare imprimantă PostScript are incorporat un interpretor care execută instrucţiuni PostScript. Asamblorul (assembler) este un program care face translaţia unui program scris în limbaj de asamblare (limbaj de nivel scăzut, corespunzător microprocesorului sistemului de calcul) în limbaj cod maşină. Putem spune că asamblorul reprezintă pentru limbajul de asamblare ceea ce reprezintă compilatorul pentru limbajele de nivel înalt. Cum limbajul de asamblare conţine instrucţiuni mai puţin complexe decât cele de nivel înalt, asamblorul face practic o convertire biunivocă între mnemonicele limbajului de asamblare şi codurile binare corespunzătoare acestor mnemonice (instrucţiuni). 23
  • 30. Instrucţiunile în limbajul de nivel înalt se introduc de la tastaturã. Tot ce se introduce de la tastaturã este vizibil pe monitor Editor de texte (eventual incorporat în mediu) Fişier text (fişier sursã) cu extensia adecvatã: nume.pas (limbaj Pascal) nume.c (limbaj C) nume.cpp (limbaj C++) nume.bas (limbaj BASIC), etc. Fişierul sursã cu numele “nume” şi extensia corespunzãtoare se salveazã din memoria RAM pe harddisk Compilator Se compileazã fişierul sursã Se obţine fişierul cod obiect: nume.obj Se salveazã pe harddisk fişierul nume.obj Link-editare (legarea tuturor modulelor necesare) Se obţine fişierul cod maşinã (executabil): nume.exe Se salveazã pe harddisk fişierul nume.exe Lansarea în execuţie de cãtre sistemul de operare a executabilului nume.exe Fig. 1.6 Detalierea procesului de generare a unui executabil Capitolul II 24
  • 31. REPREZENTAREA DATELOR ÎN CALCULATOR Se ştie că un calculator numeric prelucrează numere binare. Acest lucru ţine de suportul fizic de manipulare, transport şi stocare a datelor interne, mai bine zis este legat de faptul că semnalul fizic purtător de informaţie este o tensiune continuă cu două valori: una înaltă (High) şi una joasă (Low). Acestor două valori li se asociază natural două valori logice: T (true, adevărat) şi F (false, fals) sau cele două cifre binare1 şi 0. Tensiune High=’1’ Low=’0’ timp Ca urmare a acestei asocieri spunem, prin abuz de limbaj, că un calculator numeric prelucrează numere binare. Ca şi un număr zecimal, un număr binar are mai multe cifre binare. Sistemul de numeraţie binar folosit pentru reprezentarea informaţiei în calculatoare este un sistem de numeraţie ponderal, întocmai ca sistemul de numeraţie zecimal. Reprezentarea naturală a numerelor la nivelul percepţiei umane este cea zecimală, pe când reprezentarea proprie maşinilor de calcul este cea binară. De aici rezultă necesitatea compatibilizării sau interfaţării între aceste două moduri de reprezentare a numerelor. Cum cele două sisteme de numeraţie sunt ponderale, o primă diferenţă este aceea că sistemul zecimal foloseşte ca ponderi puterile întregi (pozitive sau negative) ale lui 10 (zece) iar sistemul binar va folosi puterile întregi (pozitive sau negative) ale lui 2. Î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 25
  • 32. internă sunt necesare convenţii de reprezentare: indiferent de tipul datelor, acestea vor fi colecţii sau şiruri de cifre binare cărora, prin convenţie, li se atribuie semnificaţii. Într-o primă instanţă, este foarte important să facem o distincţie între tipurile de date recunoscute de un calculator (sau mai bine zis de microprocesorul cu care este dotat calculatorul personal) şi formatele de reprezentare ale acestor date ce reprezintă convenţii pentru reprezentarea tipurilor de date, atât la nivel intern (în memoria calculatorului) cât şi la nivel extern, al percepţiei umane. Din punctul de vedere al tipurilor de date care sunt implementate în limbajul C putem spune că distingem două mari categorii, date de tip întreg (integer) şi date de tip real (float). Formatele de reprezentare internă/externă vor fi prezentate în cele ce urmează. Cel mai simplu de reprezentat sunt numerele naturale. Se face apoi trecerea la numerele întregi negative şi apoi la numerele reale care au o parte întreagă şi una fracţionară. 2.1. Reprezentarea internă/externă a numerelor Reprezentarea internă a numerelor se referă la modul în care se stochează datele în memoria RAM a calculatorului sau în regiştrii microprocesorului. În acest format se prelucrează numerele pentru implementarea diverselor operaţii aritmetice. La nivelul calculatorului informaţia nu poate fi decât binară. În această reprezentare putem scrie numere întregi pozitive sau negative sau numere reale. Există un standard IEEE care reglementează modul de reprezentare internă a datelor. Reprezentarea externă este reprezentarea numerelor la nivelul utilizatorului uman, deci în principiu se poate folosi orice bază de numeraţie pentru reprezentarea numerelor. La nivel de reprezentare externă se foloseşte semnul “-” în faţa unui număr în cazul în care acesta este negativ sau punctul care separă partea întreagă de cea fracţionară. De asemenea, numerele întregi interpretate fără semn se pot afişa şi în format binar, octal sau hexazecimal, deci în bazele 2, 8 sau 16. În cele ce urmează ne vom pune următoarele probleme: - cum se reprezintă extern un număr natural - cum se reprezintă intern un număr natural - cum se reprezintă extern un număr întreg negativ 26
  • 33. - cum se reprezintă intern un număr întreg negativ - cum se face conversia de la reprezentarea externă la cea internă - cum se face conversia de la reprezentarea internă la cea externă 2.2. Reprezentarea externă a numerelor În ceea ce priveşte reprezentarea externă, nu sunt nici un fel de dificultăţi deoarece fiecare este familiarizat cu reprezentarea zecimală a numerelor naturale sau reale. Trebuie menţionat de la început că orice tip de reprezentare pe care o vom folosi este ponderală în sensul că poziţia cifrelor în număr nu este întâmplătoare ci conformă cu o pondere corespunzătoare unei puteri a bazei de numeraţie. O caracteristică a reprezentărilor externe este folosirea unor convenţii de format unanim acceptate şi de altfel foarte naturale pentru un utilizator uman. Spre exemplu, pentru a exprima numere negative se foloseşte semnul “-” iar pentru reprezentarea numerelor reale se foloseşte punctul “.” pentru delimitarea părţii întregi de cea fracţionară. De asemenea, suntem familiarizaţi şi cu notaţia ştiinţifică în care intervine mantisa şi exponentul (în virgulă mobilă). Reprezentarea zecimală este cea mai naturală pentru utilizatorul uman. Vom oferi în continuare câteva exemple de reprezentări zecimale externe: Număr Reprezentare Reprezentare normală ştiinţifică 37 37 0.37x102 -37 -37 -0.37x102 0.375 0.375 0.375x100 -0.375 -0.375 -0.375x100 0.00375 0.00375 0.375x10-2 -0.00375 -0.00375 -0.375x10-2 12.375 12.375 0.12375x102 -12.375 -12.375 -0.12375x102 În general dorim să obţinem rezultatele numerice ale programelor pe care le concepem într-o formă de reprezentare accesibilă. Totuşi, calculatorul trebuie informat asupra formatului de reprezentare în care dorim să se afişeze datele necesare. Aceasta înseamnă că va trebui să specificăm câte cifre se vor folosi la partea 27
  • 34. întreagă şi câte la partea fracţionară sau dacă dorim reprezentare ştiinţifică sau nu. De altfel şi operatorul uman face aceleaşi convenţii 1 de reprezentare. Spre exemplu ştim că numărul nu poate fi exact 3 reprezentat ca un număr zecimal, deci fixăm un format de reprezentare. Dacă formatul ale se limitează la 4 cifre zecimale, atunci 1 vom scrie ≅ 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. 2.2.1. Reprezentarea externă a numerelor întregi Numerele naturale se pot reprezenta fie în baza de numeraţie 10, fie în orice altă bază. În general, un număr întreg în baza b se poate reprezenta cu un număr predeterminat de cifre ci ∈ B = { 0,1,2,....., b − 2, b − 1} . Mulţimea B reprezintă mulţimea cifrelor sau simbolurilor de reprezentare. Spre exemplu: b = 2 ⇒ B = { 0,1} b = 7 ⇒ B = { 0,1,2,3,4,5,6} b = 10 ⇒ B = { 0,1,2,3,4,5,6,7,8,9} Noi suntem obişnuiţi să folosim mulţimea cifrelor zecimale. Dacă totuşi se foloseşte o bază de reprezentare mai mare decât 10, atunci mulţimea cifrelor zecimale nu mai este suficientă pentru reprezentarea numerelor în acea bază. Spre exemplu să considerăm baza b = 16 care va folosi 16 cifre hexazecimale (sau mai simplu hexa). Prin convenţie, cele 16 cifre hexazecimale vor fi: Cifra Simbol Cifra Simbol 0 0 8 8 1 1 9 9 2 2 10 A 3 3 11 B 4 4 12 C 5 5 13 D 6 6 14 E 7 7 15 F 28
  • 35. Forma generală de reprezentare externă a numerelor întregi este de forma:  N b = ±c n −1c n − 2 ......c 2 c1c 0  c k ∈ B = { 0,1,2,....., b − 2, b − 1} Valoarea numerică zecimală a numărului N b va fi: ( ) ∑ ck ⋅ b k n −1 N b = ± c n−1 ⋅ b n −1 + c n − 2 ⋅ b n− 2 + ... + c1 ⋅ b1 + c 0 ⋅ b 0 = ± k =0 În continuare vom studia următoarele probleme: - cum se face conversia unui număr 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 număr natural din baza 10 în baza 2, se împarte succesiv numărul la 2 şi se utilizează resturile la aceste împărţiri în ordinea inversă de cum au fost obţinute. a) Conversia din baza 10 în baza 2 şi invers Fie de exemplu numărul zecimal 37. Reprezentarea sa binară va fi obţinută astfel: 3710 = 1001012 37 2 36 18 2 1 18 9 2 0 8 4 2 1 4 2 2 0 2 1 0 Conversia inversă, din baza 2 în baza 10 este simplă şi utilizează ponderea 2: 25 24 23 22 21 20 1001012 = 1 0 0 1 0 1 = 1x25 + 1x22 + 1x20=37 Cu aceste numere naturale putem face o serie de operaţii aritmetice. Adunarea numerelor naturale binare se face întocmai ca la cele în reprezentare în baza 10, după regula: 0+0=0 29
  • 36. 0+1=1 1+0=1 1+1=0, transport 1 spre rangul următor Astfel, să facem adunarea 37+25 în binar: 37 1 0 0 1 0 1+ 25 11001 62 111110 Se observă cum se obţine rezultatul corect. Înmulţirea se face în mod asemănător, ca o adunare repetată. Spre exemplu, să calculăm 37x25 37 1 0 0 1 0 1x 25 11001 100101 100101 100101 925 1110 011101 11100111012 = 1x20 + 1x22 + 1x23 +1x24 +1x27 +1x28+1x29 = 1+4+8+16+128+256+512 = 92510 b) Conversia dintr-o bază oarecare b1 într-o altă bază b2 . Fie spre exemplu numărul 4911 care se doreşte scris în baza 13. Pentru a realiza această conversie, vom folosi baza intermediară 10. Vom converti mai întâi 4A11 în baza 10 şi apoi numărul zecimal obţinut îl vom trece în baza 13. Se observă cum un număr în baza 11 poate conţine şi cifra A=10 iar un număr în baza 13 poate conţine cifrele A=10, B=11, C=12. 4 A11 = 10 ⋅110 + 4 ⋅111 = 44 + 10 = 5410 54 13 52 4 13 2 0 0 4 5310 = 4213 4 A11 = 4213 30
  • 37. 2.2.2. Reprezentarea externă a numerelor reale Semnificativă pentru utilizatorul uman este reprezentarea zecimală (în baza b=10) a numerelor reale, cu care suntem obişnuiţi. Faţă de reprezentarea numerelor întregi, la numerele reale intervine simbolul punct “.” care delimitează partea întreagă de partea fracţionară. Cu alte cuvinte, cu ajutorul numerelor reale putem reprezenta şi numere care nu sunt întregi. Forma generală a unui număr real reprezentat într-o bază oarecare b este:  N b = ±c n −1c n − 2 ...c1c 0 • c −1c − 2 ...c − m +1c − m  c k ∈ B = { 0,1,2,..., b − 2, b − 1} Valoarea zecimală a numărului de mai sus va fi: ( ) ∑ ck ⋅ b k n− 1 N10 = ± cn − 1b n − 1 + cn − 2b n − 2 + c1b1 + c0b 0 + c− 1b − 1 + c− 2 ⋅ b − 2 + c− m + 1b − m + 1 + c− m b − m = ± k= −m Se observă cum punctul delimitează partea întreagă (exprimată printr-o combinaţie de puteri pozitive ale bazei b) şi partea fracţionară (exprimată printr-o combinaţie de puteri negative ale bazei b). Semnificaţie pentru programator şi pentru producătorii de software sau microprocesoare au bazele de reprezentare 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 număr real cu n cifre pentru partea întreagă şi m cifre pentru partea fracţionară. Aşa cum în sistemul zecimal reprezentăm cu un număr finit de cifre zecimale numerele reale, acelaşi lucru se va întâmpla şi în sistemul binar. Punctul binar va avea o semnificaţie asemănătoare cu punctul zecimal, care face separarea între partea întreagă şi cea fracţionară. Cifrele binare situate după punctul binar vor corespunde puterilor negative ale lui 2. Astfel, în general, un număr real va avea reprezentarea binară: ( N 2 = ± bm bm− 1...b1b0 .b− 1b− 2 ...b− n = bm 2 m + bm− 1 2 m− 1 + b1 21 + b0 20 + b− 1 2 − 1 + b− 2 2 − 2 + ... + b− n 2 − n ) Spre exemplu, numărul 12.25 va avea reprezentarea binară: 12.2510 = 1100.01 = 2 3 + 2 2 + 2 −2 31
  • 38. Partea întreagă a unui număr real se reprezintă binar precum numerele întregi (cu sau fără semn). Pentru a determina partea fracţionară, se procedează în mod invers ca la partea întreagă. Astfel, dacă partea fracţionară zecimală se reprezintă binar, atunci aceasta se înmulţeşte succesiv cu 2. Dacă rezultatul depăşeşte valoarea 1, atunci se înscrie un bit 1. Se continuă mai departe cu dublarea valorii care depăşeşte 1. Dacă rezultatul nu depăşeşte valoarea 1, atunci se înscrie un bit 0 şi se continuă multiplicarea cu 2. Spre exemplificare, vom vedea cum se obţine reprezentarea binară a lui 12.25. Partea întreagă este 12. Ea se reprezintă binar prin împărţiri succesive la 2 şi considerarea resturilor. Partea fracţionară este 0.25 Partea P.F. x 2 Noua Bitul fracţionară P.F. înscris P.F. 0.25 0.5 0 0.5 1 0 1 0 Obţinem exact rezultatul căutat: 12.25 = 1100.01 Să mai considerăm un alt exemplu. Să reprezentăm numărul 5.37 Partea întreagă are reprezentarea 510 =1012 Partea P.F. x 2 Noua Bitul fracţionară P.F. P.F. înscris 0.37 0.74 0.74 0 0.74 1.48 0.48 1 0.48 0.96 0.96 0 0.96 1.92 0.92 1 0.92 1.84 0.84 1 0.84 1.68 0.68 1 0.68 1.36 0.36 1 0.36 0.72 0.72 0 0.72 1.44 0.44 1 Etc.. Etc.. Obţinem: 5.3710 = 101.010111guatda.com/cmx.p101...2 Cu cât mai multe cifre binare vom reţine după punctul binar, cu atât vom fi mai aproape de valoarea exactă 5.37. Obţinem un rezultat foarte important: Deşi un număr zecimal poate avea un număr finit de cifre zecimale după punctul zecimal, reprezentarea sa binară internă poate avea un număr infinit de cifre binare. Este valabilă şi reciproca: un număr real zecimal cu un 32
  • 39. număr infinit de cifre se poate reprezenta într-o altă bază pe un 1 număr finit de cifre ( ex: = 0.3guatda.com/cmx.p333...3...10 = 0.13 ). Cum orice 3 reprezentare binară internă este pe un număr finit de biţi, numărul poate să nu fie reprezentat exact în calculator, ci cu o anumită aproximaţie. Acest lucru este decisiv pentru a înţelege importanţa lungimii reprezentării numerelor în calculator. Cu cât un număr binar se reprezintă pe un număr mai mare de biţi, cu atât precizia de reprezentare creşte. 2.3 Reprezentarea internă a numerelor Deoarece semnalul intern purtător de informaţie într-un calculator este de tip binar, un număr zecimal (întreg sau real) se va reprezenta intern în baza 2 cu ajutorul unui număr binar. O cifră binară se numeşte bit (Binary Digit) şi poate fi fie 0 fie 1. În reprezentarea externă a numerelor am văzut că se poate folosi orice bază de numeraţie (cu cifrele corespunzătoare). De asemenea, numerele pot fi prefixate cu un simbol de semn ± şi pot include în reprezentare şi punctul de separaţie între partea întreagă şi cea fracţionară. În reprezentarea internă acest lucru nu mai este posibil deoarece semnele plus (+), minus (-) sau punct (.) nu au nici o semnificaţie pentru calculator. Orice număr (orice tip de dată) este reprezentat la nivel intern de un număr prestabilit de biţi. Specialiştii din industria software au ajuns la un consens de reprezentare concretizat prin standardul IEEE 754 de reprezentare a internă a numerelor reale în computere. Reprezentarea internă a numerelor a impus în limbajul C definirea aşa-numitelor tipuri de date. Tipul unei date reprezintă modul în care microprocesorul stochează în memorie şi prelucrează cu ajutorul regiştrilor interni o dată. Tipul unei date se referă la lungimea sa de reprezentare (pe câţi biţi se reprezintă data) precum şi ce semnificaţie au anumite câmpuri de biţi din cadrul reprezentării. 2.3.1. Reprezentarea internă a numerelor întregi 33
  • 40. Un număr binar este o colecţie de cifre binare ponderate fiecare cu o putere a lui 2. Bitul corespunzător ponderii celei mai mari, situat cel mai în stânga, se numeşte MSB (Most Significand Bit) iar cel corespunzător ponderii celei mai mici, situat cel mai în dreapta, se numeşte LSB (Less Significand Bit). În cazul reprezentării binare a numerelor naturale, reprezentarea externă (cea percepută de operatorul uman) şi cea internă (cea prelucrată de procesorul calculatorului) sunt asemănătoare. Cum pentru operatorul uman operatorii ‘+’ sau ‘-‘ semnifică faptul că un număr este pozitiv sau negativ, este necesară o convenţie pentru reprezentarea internă a numerelor întregi negative. Această convenţie prevede folosirea MSB pentru reprezentarea semnului numerelor întregi. Dacă numărul este pozitiv, se adaugă în poziţia MSB bitul de semn ‘0’, iar dacă numărul este negativ se utilizează în poziţia MSB bitul de semn ‘1’. Mai mult, numerele negative se reprezintă în aşa numitul complement faţă de 2. Reprezentarea numerelor întregi negative în complement faţă de 2 Această formă de reprezentare a numerelor negative necesită parcurgerea următorilor paşi: pas1. Se reprezintă modulul numărului negativ, folosind bit de semn (egal cu 0, evident) pas2. Se complementează toţi biţii numărului astfel obţinut. Complementarea înseamnă transformarea bitului 0 în bitul 1 şi a bitului 1 în bitul 0. pas3. Numărul astfel obţinut se adună cu 1. De exemplu, să reprezentăm numărul -37. 3710 = 1001012 = [ 0] 100101 pas1. |-37| = 37 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 numărului negativ. Pentru a vedea ce număr negativ este reprezentat, putem repeta procedeul de mai sus şi obţinem reprezentarea numărului pozitiv dat de modulul său. O modalitate mai simplă este alocarea ponderii corespunzătoare bitului de semn dar pe care o considerăm că reprezintă un număr negativ. Astfel: 34
  • 41. 10110112 = -1x26 + 1x24 + 1x23 + 1x21 + 1x20 = -64 + 27 = -37 2.3.2 Adunarea, scăderea şi înmulţirea numerelor întregi Aceste operaţii se execută folosind reprezentarea în complement faţă de 2 a numerelor întregi, sau, mai bine zis, se execută folosind în algoritmi bitul de semn ca pe un bit obişnuit. De exemplu, dorim să calculăm: 37-25 25-37 (-25)x37 (-25)x(-37) Pentru efectuarea acestor calcule, vom scrie reprezentările cu bit de semn ale numerelor implicate: 2510 = 110012 = 011001 − 25 = 100111  10 2  3710 = 1001012 = 0100101 − 3710 = 1011011  Se observă că 25 şi (-25) se reprezintă pe 6 biţi iar 37 şi (-37) pe 7 biţi. Deoarece am observat că biţii unui întreg cu semn nu au toţi aceeaşi semnificaţie, este nevoie să reprezentăm numerele cu care lucrăm pe un acelaşi număr de biţi. La adunări sau scăderi, biţii de semn se vor afla în aceeaşi poziţie (vor avea aceeaşi pondere) şi vom obţine astfel rezultate corecte. Pentru a avea o scriere pe un acelaşi număr de biţi, se adaugă (completează) la stânga bitul de semn de un număr corespunzător de ori. Astfel: 37 − 25 = 37 + (−25) = 0100101 + 1100111 0100101 + − 2510 = 1001112 = 1100111  1100111 25 = 0110012 = 0011001 −−−−−− 0001100 = 1210 35
  • 42. 25 − 37 = 25 + (−37) = 0011001 + 1011011 0011001 + − 37 = 1011011  1011011 25 = 0110012 = 0011001 −−−−−− 1110100 = −64 + 52 = −12 În continuare vom pune în evidenţă importanţa gamei de reprezentare, adică a domeniului de valori ale datelor. Să considerăm, spre exemplu, adunarea a două numere cu semn reprezentate pe un octet (8 biţi). Aceste numere sunt cuprinse în gama [− 2 , 2 − 1] 7 7 = [ − 128, 127] . Dacă vom dori să adunăm două numere din acest domeniu şi să reprezentăm rezultatul tot pe un octet, putem avea surprize. De exemplu, să considerăm operaţiile (117-12) şi (117+12). Se observă că operanzii sunt în gama de reprezentare a numerelor cu semn pe 8 biţi. Prin prima scădere, ne aşteptăm să obţinem un rezultat, 105, în aceeaşi gamă de reprezentare. 117-12=117+(-12) = 01110101+11110100 = 01101001 = 10510, rezultat corect. 117+12 = 01110101+00001100 = 10000001 = -12710, rezultat evident incorect. Incorectitudinea provine de la faptul că rezultatul a depăşit gama de reprezentare. Dacă rezultatul este interpretat pe 9 biţi de exemplu, gama de reprezentare devine [ − 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ă precizăm dacă se lucrează sau nu cu bit de semn şi pe câţi biţi se face reprezentarea, pentru că numai în acest context interpretarea rezultatelor este corectă. În ceea ce priveşte înmulţirea numerelor întregi cu semn (cu bit de semn), aici problema nu mai are o rezolvare asemănătoare, în sensul că nu putem trata biţii de semn la fel cu cei de reprezentare ai valorii. Astfel, procesorul studiază biţii de semn şi ia o decizie în privinţa semnului rezultatului. De fapt, se realizează funcţia logică XOR a biţilor de semn. Numerele negative se vor lua în modul, iar operaţiile de înmulţire se vor face numai cu numere pozitive. La final, 36
  • 43. funcţie de semnul rezultatului, se ia decizia reprezentării corecte a rezultatului. Spre exemplu, să calculăm (-25)x37. Pentru aceasta, procesorul va primi pentru procesare următoarele două numere: 37 x(−25) = [ 0]100101 × [1]100111 Se analizează separat biţii de semn şi se ia decizia că rezultatul va fi negativ, deci, la final, se va reprezenta în complement faţă de 2. Mai departe se va lucra cu 25, modulul numărului (-25), care se obţine prin complementarea faţă de 2 a numărului binar 1100111: 11001110011000+1=0011001 Se va reţine pentru procesare numai numărul (fără semn) 11001, care se va înmulţi cu numărul (fără semn) 100101, obţinând, aşa cum am arătat mai sus, valoarea 1110011101. Mai departe, se adaugă bitul de semn, 0 pentru numere pozitive, obţinându-se 01110011101. Acest ultim număr se va complementa faţă de 2, obţinându-se 10001100010+1=[1]0001100011, adică valoarea -1024+99 = -925, valoarea corectă. Ca o concluzie, pentru a furniza rezultate corecte, procesorul va trebui informat în permanenţă despre ce fel de numere prelucrează (cu sau fără semn) şi care este lungimea lor de reprezentare (toate trebuie să aibă aceeaşi lungime). Reprezentarea în complement faţă de 2 se poate folosi şi pentru numerele reale negative, bitul de semn fiind MSB de la partea întreagă. Astfel, -12.25 poate avea reprezentarea: 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 rămân valabile considerentele de la numere întregi. În cazul de mai sus, problema reprezentării numărului negativ a fost rezolvată cu ajutorul bitului de semn dar problema reprezentării punctului binar va avea altă rezolvare. 2.3.3 Reprezentarea internă a numerelor reale Din considerentele de la reprezentarea externă a datelor putem trage alte concluzii importante din punct de vedere al reprezentării 37
  • 44. interne. Numerele binare întregi fără semn au aceeaşi reprezentare atât externă cât şi internă. Numerele întregi cu semn (care în reprezentare externă sunt prefixate cu ± ) au ca reprezentare internă un bit de semn, dar care se tratează deosebit de ceilalţi biţi ai reprezentării. Toţi întregii cu semn, care au MSB=1, sunt reprezentaţi intern în complement faţă de 2. Numerele reale se pot reprezenta identic cu cele întregi cu semn, cu o precizare: nu se face o deosebire netă între biţii reprezentării părţii întregi şi cei ai reprezentării părţii fracţionare. Acest tratament nediferenţiat provine de la reprezentarea ştiinţifică uzuală cu mantisă şi exponent. Fie, spre exemplu, reprezentarea binară a numărului 12.25: 12.2510 = 1100.01 = 0.110001 x 2 4 Calculatorul poate reprezenta şirul de biţi 110001 şi reţine faptul că punctul se pune după primii 4 biţi ai reprezentării. Acest lucru se întâmplă şi în realitate. Deci, singura deosebire între reprezentarea numerelor reale şi a celor întregi constă în faptul că numerele reale necesită o informaţie suplimentară despre aşa numitul exponent, în cazul nostru numărul pozitiv 4. În cele ce urmează, vom prezenta tipurile de bază pe care le pot avea datele în reprezentarea internă. Tipul unei date determină modul în care procesorul stochează şi prelucrează data respectivă. Cum primele procesoare care au condus la apariţia pe piaţă a primelor calculatoare pentru neprofesionişti (aşa numitele Home Computers) au fost procesoare capabile să prelucreze şi să transmită în paralel 8 biţi, a fost naturală gruparea a 8 biţi într-o entitate numită byte. 1B = 8b (adică un byte reprezintă 8 biţi) Procesoarele au evoluat, ajungându-se în prezent la procesoare pe 64 de biţi. Cum evoluţia lor s-a făcut trecându-se succesiv prin multipli de 8 biţi, s-au impus şi alte entităţi de reprezentare a informaţiei, pe care le vom prezenta sintetic în tabelul de mai jos. Denumire Dimensiune Denumire Notaţie echivalentă Nr. Nr. byte biti Byte 1B 8b octet B Word 2B 16 b cuvânt W 38
  • 45. Denumire Dimensiune Denumire Notaţie echivalentă Nr. Nr. byte biti Double_Words 4B 32 b Cuvânt dublu DW Quad_Words 8B 64 b Cuvânt cvadruplu QW Ten_Words 10B 80 b TW A determina reprezentarea internă înseamnă să determinăm lungimea reprezentării (de obicei în multipli de octeţi), modul de interpretare al biţilor ce compun reprezentarea şi gama de reprezentare, adică să determinăm magnitudinea (valorile minime şi maxime pozitive şi negative) ce pot fi reprezentate în formatul respectiv. În limbajul C, există două tipuri de reprezentare pe care le putem numi principale: tipul întreg şi tipul real, fiecare având şi anumite particularizări. Astfel, tipul întreg (int) include şi tipul caracter (char) iar tipul real (float) include şi tipul real extins (double). Tipurile de date le vom reprezenta de la simplu la complex, în ordinea char, int, float, double. Tipurile de bază sunt char, int, float, double şi cu ajutorul modificatorilor de tip putem obţine diverse particularizări. Modificatorii pot fi signed, unsigned, short, long. Ca o generalitate, numerele sunt reprezentate intern luându-se în considerare bitul de semn, deci implicit numerele întregi sau reale au MSB bit de semn. Dacă se specifică explicit, prin modificatorul unsigned, nu se mai consideră (interpretează) bitul de semn. 2.3.3.1 Tipul char Codul ASCII (American Standard Code for Information Interchange) este un cod de reprezentare a caracterelor. Prin caracter înţelegem unităţile de bază care se pot tasta (intrări de la tastatură), tipări la imprimantă sau afişa pe ecran. Tastatura reprezintă, de exemplu, dispozitivul de intrare care conţine de fapt o întreagă colecţie de caractere ce pot fi emise prin apăsarea unei taste. Pentru a fi receptat, emis sau prelucrat de către calculator, fiecare caracter are asociat un cod binar (o combinaţie de biţi) care îl identifică în mod unic. Cum cu un octet putem codifica 2 8 = 256 caractere, octetul s-a 39
  • 46. dovedit o entitate suficientă pentru codificarea caracterelor utilizate în informatică. În 256 de coduri distincte se pot include literele mari şi mici ale alfabetului anglo-saxon (inclusiv litere specifice diverselor alfabete precum cel chirilic sau particularităţi ale diferitelor ţări: ş, ţ, â, î, Ş... în română, de exemplu). Se mai pot include caractere ce reprezintă numere, semne de punctuaţie sau alte caractere de control. Codul ASCII a standardizat această codificare, astfel încât el este folosit în cvasitotalitatea calculatoarelor (doar mainframe-urile IBM mai folosesc un alt cod, mai vechi, numit EBCIDIC). Dacă se declară o dată de tip char, ea este considerată explicit de tipul signed char (cu MSB bit de semn), deci reprezentarea internă este de forma: S b6 b5 b4 b3 b2 b1 b0 Bit de semn 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ă aceeaşi valoare reprezentată în format hexazecimal (baza 16) iar în coloana Sym se reprezintă simbolul afişat pe monitoarele PC. Întregul alfabet al limbajului C se regăseşte în mulţimea primelor 128 de caractere ASCII. Restul de 128 de caractere se mai numeşte şi set de caractere extins ASCII şi poate fi vizualizat printr-un program simplu. Trebuie menţionat faptul că reprezentarea datelor în format hexazecimal este foarte răspândită în tehnica programării calculatoarelor. Avantajul reprezentării interne a datelor în format 40
  • 47. hexazecimal constă în folosirea unui număr mai mic de cifre (de 4 ori mai mic decât numărul de cifre binare). Reprezentarea unui număr natural în format hexazecimal se realizează cu metoda împărţirii succesive la 16 sau, mai simplu, pornind de la reprezentarea binară a numărului. Cum mulţimea cifrelor hexa conţine 16 simboluri (0…9 şi A… F), pentru codificarea celor 16 cifre avem nevoie de 4 cifre binare ( 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 câte 4 biţi - se înlocuieşte fiecare grup de 4 biţi cu cifra hexazecimală pe care o codifică. De exemplu, să presupunem că avem numărul 217. 21710 = 110110012 = 1101.10012 = D916 = 13 ⋅161 + 9 ⋅16 0 = 208 + 9 = 217 În acest mod, dacă un număr are o reprezentare internă pe un număr de k octeţi, se poate reprezenta simplu cu ajutorul a 2k cifre hexazecimale. În tabelele de mai jos se prezintă codificarea ASCII a caracterelor. Codurile corespunzătoare simbolurilor alfanumerice din tabel sunt exact semnalele binare care se transmit în reprezentarea internă. Cu alte cuvinte, dacă la tastatură se tastează simbolul “a”, atunci circuitele corespunzătoare transmit spre calculator semnale binare corespunzătoare codului 1010 0001, adică 61H sau 97 în zecimal. La fel se întâmplă când se lucrează cu procesoare de text sau când se tipăreşte un document la imprimantă. Sistemul de calcul manevrează codurile ASCII corespunzătoare literelor şi cifrelor pe care utilizatorul le poate interpreta. D H Sym D H Sym D H Sym D H Sym 0 0 Null 1 1 ► 3 2 4 3 0 6 0 2 0 8 0 1 1 ☺ 1 1 ◄ 3 2 ! 4 3 1 7 1 3 1 9 1 2 2 ☻ 1 1 ↕ 3 2 " 5 3 2 8 2 4 2 0 2 3 3 ♥ 1 1 ‼ 3 2 # 5 3 3 9 3 5 3 1 3 41
  • 48. 4 4 ♦ 2 1 ¶ 3 2 $ 5 3 4 0 4 6 4 2 4 5 5 ♣ 2 1 § 3 2 % 5 3 5 1 5 7 5 3 5 6 6 ♠ 2 1 ▬ 3 2 & 5 3 6 2 6 8 6 4 6 7 7 2 1 ↨ 3 2 ' 5 3 7 3 7 9 7 5 7 8 8 2 1 ↑ 4 2 ( 5 3 8 4 8 0 8 6 8 9 9 2 1 ↓ 4 2 ) 5 3 9 5 9 1 9 7 9 1 a LF 2 1a → 4 2a * 5 3a : 0 6 2 8 1 b ♂ 2 1 ← 4 2 + 5 3 ; 1 7 b 3 b 9 b 1 c ♀ 2 1c ∟ 4 2c , 6 3c < 2 8 4 0 1 d CR 2 1 ↔ 4 2 - 6 3 = 3 9 d 5 d 1 d 1 e ♫ 3 1e ▲ 4 2e . 6 3e > 4 0 6 2 1 f ☼ 3 1f ▼ 4 2f / 6 3f ? 5 1 7 3 D H Sy D H Sy D H Sy D H Sym m m m 6 4 @ 8 5 P 96 60 ` 11 7 p 4 0 0 0 2 0 6 4 A 8 5 Q 97 61 a 11 7 q 5 1 1 1 3 1 6 4 B 8 5 R 98 62 b 11 7 r 6 2 2 2 4 2 6 4 C 8 5 S 99 63 c 11 7 s 7 3 3 3 5 3 6 4 D 8 5 T 10 64 d 11 7 t 8 4 4 4 0 6 4 42
  • 49. 6 4 E 8 5 U 10 65 e 11 7 u 9 5 5 5 1 7 5 7 4 F 8 5 V 10 66 f 11 7 v 0 6 6 6 2 8 6 7 4 G 8 5 W 10 67 g 11 7 w 1 7 7 7 3 9 7 7 4 H 8 5 X 10 68 h 12 7 x 2 8 8 8 4 0 8 7 4 I 8 5 Y 10 69 i 12 7 y 3 9 9 9 5 1 9 7 4a J 9 5a Z 10 6a j 12 7a z 4 0 6 2 7 4 K 9 5 [ 10 6b k 12 7 { 5 b 1 b 7 3 b 7 4c L 9 5c 10 6c L 12 7c | 6 2 8 4 7 4 M 9 5 ] 10 6d M 12 7 } 7 d 3 d 9 5 d 7 4e N 9 5e ^ 11 6e n 12 7e ~ 8 4 0 6 7 4f O 9 5f _ 11 6f o 12 7f ⌂ 9 5 1 7 2.3.3.2 Tipul int Acest tip se foloseşte pentru reprezentarea numerelor întregi cu sau fără semn. Odată cu standardizarea ANSI C din 1989, s-a trecut la modul de reprezentare a întregilor impus de noul procesor Intel 80386 dotat şi cu coprocesorul matematic Intel 80387. MSB S b30 Octetul 1 Octetul 2 Octetul 3 b0 Octetul 4 LSB 43
  • 50. Tipul int este identic cu signed int şi utilizează o reprezentare pe 4B a numerelor întregi cu semn. Reprezentarea pe 4 octeţi duce la posibilitatea măririi gamei de reprezentare astfel: max = 231 −1   ( ) 3 3 ( ) ; 231 = 2 ⋅ 230 = 2 ⋅ 210 ≅ 2 ⋅ 103 ≅ 2 ⋅109 min = −231  Rezultă că putem reprezenta numere întregi în gama: [± 2.1475 ⋅10 ] ≅ [− 2 ⋅10 , 2 ⋅10 ] 9 9 9 unsigned int nu va mai lua în considerare bitul de semn, astfel încât reprezentarea internă este de forma din figura de mai jos. Evident,  max = 232 −1 32  min = 0 3 ( ) 3 ; 2 = 4 ⋅ 230 = 4 ⋅ 210 ≅ 4 ⋅ 103 ≅ 4 ⋅109( )  Gama de reprezentare se poate schimba cu ajutorul modificatorilor short sau long. MSB S b14 b0 LSB short int se va reprezenta pe 2B, sub forma max = 215 − 1   ; 215 = 2 5 ⋅ 210 = 32 ⋅ 210 ⇒ [ − 32768, 32767] . min = −215  unsigned short int va schimba gama de reprezentare în [ 0, 65535] long int se va reprezenta pe 8B şi va conduce la o gamă imensă de reprezentare a numerelor întregi, lucru dovedit de ( ) 6 ± 2 63 = ±2 3 ⋅ 210 ≅ ±8 ⋅1018 = ±9.2234 ⋅1018 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 număr se 44
  • 51. poate exprima ca un număr zecimal (deci, cu punct zecimal) multiplicat cu o putere a lui zece sau ca un număr real binar (cu punct binar) multiplicat cu o putere a lui 2. 5.2510 = 101.01 = 1.0101 x 2 2 = 0.10101 x 2 3 Se observă cum stocarea în calculator a unei date floating-point necesită trei părţi: - bitul de semn (sign) - mantisa, fracţia (significand) - exponent (exponent) Folosind formatul specific I80386, în limbajul C se disting trei tipuri de date reale: - float , cu reprezentare pe 4 octeţi (32 biţi, double word) - double, cu reprezentare pe 8 octeţi (64 biţi, quad word) - long double, cu reprezentare pe 10 octeţi (80 biţi, ten word) MSB b31 b30 b0 LSB 45
  • 52. Exponent Significand S biased 31 30 23 22 0 Exponent = 8b Significand = 23b float S Bias = 7FH=127 63 62 52 51 0 Exponent = 11b Significand = 52b double S Bias = 3FFH=1023 79 78 64 63 0 Exponent = 15b Significand = 52b long S Bias = 3FFFH=16383 double Tipurile float şi double sunt formate pentru numere reale ce există numai în memorie. Când un astfel de număr este încărcat de procesor în stiva pentru numere reale (flotante) pentru prelucrare sau ca rezultat al prelucrării, el este automat convertit la formatul long double (sau extended). În cazul în care acest număr se stochează în memorie, el se converteşte la tipul float sau double. Toate cele trei subtipuri reale au un format comun, care va fi prezentat în continuare. Ceea ce le deosebeşte este numărul de biţi alocaţi pentru exponent şi pentru mantisă, precum şi interpretarea biţilor mantisei (significand). Semnul are alocat în toate formatele un singur bit: 0 pentru numere pozitive şi 1 pentru numere negative. Mărimea câmpului exponent variază cu formatul şi valoarea sa determină câţi biţi se mută la dreapta sau la stânga punctului binar. Câmpul significand este analogul mantisei în notaţia ştiinţifică. El conţine toţii biţii semnificativi ai reprezentării, deci biţii semnificativi atât ai părţii întregi cât şi ai părţii fracţionare cu singura restricţie ca aceşti biţi să fie consecutivi. Deoarece punctul binar este mobil, cu cât sunt mai mulţi biţi alocaţi părţii întregi, cu atât vor fi mai puţini pentru partea fracţionară şi invers. Cu cât formatul este mai larg, cu atât se vor reprezenta mai precis numerele. Pentru a salva un spaţiu preţios de stocare, nici unul dintre cele trei formate float nu stochează zerouri nesemnificative. De exemplu, pentru numărul 0.0000101 = 0.101x 2 −4 câmpul significand va stoca 46
  • 53. numărul 101, nu şi cele 4 zerouri nesemnificative ale părţii fracţionare. Pentru a salva şi mai mult spaţiu, pentru formatele float şi double câmpul significand nu va conţine primul bit semnificativ care obligatoriu este 1. Câştigând acest bit (numit bit phantom), se dublează gama de reprezentare. Formatul long double va conţine totuşi bitul de semn 1 cel mai semnificativ. Punctul binar se pune exact înaintea primului bit din câmpul significand, adică după bitul 1 implicit (phantom). În cazul long double, se aplică după primul bit 1. Pentru a uşura operarea cu aceste numere, câmpul exponent nu este stocat ca un număr întreg cu semn, ci este decalat (normalizat, cu bias) pentru a reprezenta numai numere pozitive (deci exponentul este interpretat ca număr natural fără semn). Biasul adăugat se scade pentru a afla exponentul exact. Avantajul exponentului decalat constă, pe lângă faptul că nu mai are nevoie de bit de semn, în faptul că pentru a compara două numere reale putem începe prin compararea biţilor pornind de la MSB către LSB, cel mai mare fiind cel care are 1 la primul bit diferit. Se decide astfel foarte rapid care număr este cel mai mare. Ca exemplu, să considerăm un format float în care se stochează: Sign = 0 Exponent = 10000010 = 13010 Significand = 1001000…00 Valoarea reală a exponentului va fi 130 - 127 = 3 Biţii câmpului significand se obţin adăugând MSB phantom, deci aceştia vor fi 11001guatda.com/cmx.p000...00 Numărul real care s-a stocat este: 0.110guatda.com/cmx.p010...00 x 24 = 1100.1 =12.5 Reprezentarea internă a numărului 12.5, pe 4 octeţi (float), este următoarea: Semn 0 1 0 0 0 0 0 1 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 LSB Cu alte cuvinte, putem spune că reprezentarea internă a numărului real 12.5 este (în format hexazecimal): 47
  • 54. 12.510 = 4148000016 În cazul în care dorim să reprezentăm numărul negativ –12.5, singurul bit care se va modifica va fi bitul de semn, care devine 1. Astfel, reprezentarea internă în format float a numărului negativ real –12.5 este: Semn 1 1 0 0 0 0 0 1 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 LSB − 12.510 = C148000016 Dacă numărul 12.5 se reprezintă în formatul double, deci pe 8 octeţi, atunci reprezentarea sa internă se va realiza astfel: - bitul de semn va fi 0 - 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: 3 + 1023 = 1026 = 1024 + 2 = 10000000010 exponent −1 bias - significand va fi acelaşi ca la tipul float, dar reprezentat pe 52 de biţi Semn 0 1 0 0 0 0 0 0 0 0 1 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 LSB 12.510 = 402900000000000016 48
  • 55. Reţinem că la numere reale numai bitul de semn indică dacă numărul este pozitiv sau negativ, mantisa şi exponentul se reprezintă ca numere naturale fără bit de semn. Formatele prezentate mai sus respectă standardul IEEE 754 de reprezentare a internă a numerelor reale în computere. Se poate pune o întrebare legitimă: de ce bias-ul în cazul float spre exemplu este 127? Pentru a răspunde la această întrebare, putem face următorul raţionament: - exponentul cu semn este reprezentat pe 8 biţi, deci este în gama de reprezentare [ − 128, + 127] . - pentru a obţine un exponent pozitiv, adăugăm numărul 128. - deoarece bitul phantom nu este reprezentat, exponentul trebuie micşorat cu o unitate pentru a indica unde anume se poziţionează exact punctul binar. - Exponent pozitiv = exponent +128 – 1 = exponent + bias de unde rezultă evident faptul că bias = 127 în cazul tipului float. În final să analizăm un exemplu de procesare a produsului a două numere reale. Vrem să calculăm valoarea 5.25 x 1.5. Pentru aceasta, vom scrie cei doi factori ai produsului în forma: .10101 × .11 5.2510 = 101.012 = .10101 × 23  −−−−−  1 1.510 = 1.12 = .11 × 2 ; 10101  5.25 × 1.5 = [ ( .10101) × ( .11) ] × 2 3+1 10101  −−−−− .0111111 ⇒ 5.25 × 1.5 = .0111111 × 24 = 111.111 = 7.875 Se observă cum câmpurile exponent şi significand sunt procesate separat, în final corelându-se forma de reprezentare internă. Game de reprezentare pentru numerele reale Gama de reprezentare pentru fiecare din tipurile reale prezentate mai sus se calculează luând în considerare cel mai mare număr şi cel mai mic număr posibil a fi scris în respectiva reprezentare. Astfel, exponentul este decisiv pentru gama de reprezentare. 49
  • 56. La tipul float, avem exponent _ biasmax = 255 ⇒ exponent _ realmax = 255 − 127 = 128 ( )12 nmax = 2128 = 28 ⋅ 210 = 256 ⋅ (1024 )12 ≅ 256 ⋅1036 = 2.56 ⋅1038 Valoarea maximă exactă, calculată fără a aproxima ca mai sus: 38 210 = 1024 ≅ 1000 = 103 este n max = 3.4028 ⋅10 exponent _ biasmin = 0 ⇒ exponent _ realmin = 0 − 127 = −127 ( ) nmin = 2−127 = 2− 7 ⋅ 210 −12 ( ) = 23 ⋅ 2−10 ⋅ 210 −12 ( ) = 8 ⋅ 210 −13 ≅ 8 ⋅ 10− 39 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 ( ) nmax = 21024 = 24 ⋅ 210 102 = 16 ⋅ (1024 ) 306 ≅ 16 ⋅ 10306 = 1.6 ⋅ 10307 Valoarea maximă exactă este n max = 1.7 ⋅10 308 exponent _ biasmin = 0 ⇒ exponent _ realmin = 0 − 1023 = −1023 nmin = 2−1023 = 2−3 ⋅ 210( ) −102 ≅ .125 ⋅ 10−306 Valoarea pozitivă minimă exactă este n min = 1.1125 ⋅ 10 −308 Efectuând aceleaşi consideraţii şi calcule pentru tipul long double, n  max = 1.1 ⋅ 10 4932 vom obţine  n min = 3.4 ⋅ 10 −4932  2.3.5. Codificare BCD Procesorul I80386 este considerat primul procesor care are capacitatea de a procesa operaţii aritmetice asupra unor numere reprezentate în zecimal codificat binar (BCD, binary-coded decimal) în locul formatelor binare standard. Reprezentarea numerelor în cod BCD este folosită pentru a face numerele binare mai accesibile operatorului uman. Neajunsul acestei reprezentări este faptul că numerele BCD ocupă spaţiu de stocare mai mare decât numerele binare. Ele sunt mai uşor de interpretat de către programatorul uman, pentru computer neavând nici un fel de relevanţă. Procesorul 80386 50
  • 57. poate manevra două tipuri de formate BCD: împachetat şi neîmpachetat (packed BCD şi unpacked BCD). În formatul unpacked BCD, o cifră zecimală se stochează pe un octet. Spre exemplu, cifra zecimală 5 va fi reprezentată intern sub forma 00001001. Formatul packed BCD stochează două cifre zecimale pe un octet, crescând capacitatea de stocare internă precum şi gama de reprezentare pe un acelaşi număr de octeţi. Ambele codificări folosesc reprezentarea pe 4 biţi a cifrelor zecimale. Spre exemplu, numărul 9817 se stochează pe 4 octeţi în format unpacked BCD şi pe 2 octeţi în format packed BCD: unpacked BCD: 9817 = 0000 1001 0000 1000 0000 0001 0000 0111 packed BCD: 9817 = 1001 1000 0001 0111 Se observă cum valoarea maximă care se poate stoca pe un octet este 9 pentru unpacked BCD, 99 pentru packed BCD şi 255 pentru codificarea binară fără semn standard. Toate formatele reale prezentate se conformează standardului IEEE 754 pentru reprezentarea numerelor în virgulă mobilă în format binar. Ca o concluzie la acest capitol, decisiv pentru înţelegerea dezvoltărilor ulterioare, putem sintetiza următoarele:  Reprezentarea externă a numerelor se referă la modul în care operatorul uman acceptă schimbul de date cu calculatorul. Acest schimb de date are dublu sens: de la operatorul uman către calculator şi invers. Reprezentarea externă este de obicei zecimală şi are un format aproape identic cu formatul matematic uzual: simbol de semn prefixat, punct zecimal, mantisă sau exponent. Numerele naturale se mai pot reprezenta şi în format octal sau hexazecimal. În format extern se introduc datele de la tastatură pentru prelucrare şi se obţin pe monitor sau la imprimantă rezultatele oferite de calculator.  Reprezentarea internă a numerelor se referă la modul în care se stochează datele în memoria RAM a calculatorului şi respectiv în regiştrii interni ai microprocesorului. Această reprezentare internă este legată de noţiunea de tip de dată.  Tipul de dată întreg (integer) se reprezintă intern pe 2, 4 sau 8 octeţi în complement faţă de 2, cu cel mai semnificativ bit (MSB) bit de semn: 1 pentru numere întregi negative şi 0 pentru numere întregi pozitive. Un caz particular de dată de tip întreg este tipul character, interpretat ca întreg pe un octet. 51
  • 58. Tipul de dată real (float) se reprezintă intern pe 4, 8 sau 10 octeţi şi conţine 3 câmpuri de biţi distincte: bit de semn, câmp mantisă şi câmp exponent, de lungimi corespunzătoare. Dacă se specifică explicit, toate numerele se pot defini fără semn (unsigned), caz în care calculatorul nu mai interpretează bitul de semn (MSB) diferit ci îl include în câmpul de reprezentare al mărimii, crescând gama de reprezentare. Capitolul III ELEMENTELE DE BAZĂ ALE LIMABJULUI C 3.1. Crearea şi lansarea în execuţie a unui program C Prezentăm câteva comenzi simple pentru a lansa în execuţie un program C folosind compilatorul BORLANDC v3.1 (versiunea pentru sistemul de operare DOS): • după setarea pe directorul corespunzător se tastează - bc – pentru a intra în mediul BorlandC; - <Alt>-F – pentru a selecta meniul File; - N – pentru a deschide un fişier nou. • se editează programul sursă folosind editorul mediului BorlandC; Exemplu: 52
  • 59. #include <stdio.h> void main (void) { printf("Primul program in C!"); } • se acţionează tasta F2 şi se indică numele fişierului (cu extensia .c sau .cpp) pentru salvarea programului sursă pe disc – de exemplu mesaj.c - (se recomandă salvarea pe disc după efectuarea oricărei modificări în programul sursă pentru evitarea pierderii accidentale a acesteia); • se realizează compilarea, link-editarea (realizarea legăturilor) şi lansarea în execuţie a programului executabil mesaj.exe tastând <CTRL>-F9; • pentru a vizualiza rezultatele execuţiei programului se tastează <Alt>-F5; • se revine în fereastra de editare a mediului acţionând o tastă oarecare; • pentru a închide un fişier sursă se tastează <Alt>-F3 iar pentru a ieşi din program în mediul de operare se tastează <Alt>-X. La fel de simplu poate fi utilizată şi varianta pentru sistemul de operare WINDOWS a compilatorului BORLANDC v3.1: • se lansează în execuţie programul bcw.exe; • se selectează din meniul File opţiunea New, creându-se fişierul noname00.cpp; • în fereastra noname00.cpp se introduce codul programului; • din meniul File se selectează opţiunea Save As… sau Save iar în căsuţa de dialog care apare se va salva fişierul program cu extensia .cpp; • din meniul Compile se selectează opţiunea Build All ce va afişa caseta de dialog Compiling (compilare); • dacă operaţia de compilare se încheie cu succes (nu există erori de sintaxă în program) compilatorul va afişa mesajul Press any key, caz în care compilatorul va crea fişierul executabil; • lansarea în execuţie a fişierului executabil se poate realiza folosind opţiunea Run din meniul Run sau combinaţia de taste <CTRL>-F9. 53
  • 60. Fiecare limbaj de programare are un set de reguli, denumite reguli sintactice, reguli ce trebuie respectate la editarea unui cod sursă. Dacă este încălcată o regulă sintactică, programul nu va fi compilat cu succes. În acest caz, pe ecran va fi afişat un mesaj de eroare ce specifică linia ce conţine eroarea, precum şi o scurtă descriere a erorii. În exemplul următor programului îi lipseşte caracterul punct şi virgulă după utilizarea funcţie printf: #include <stdio.h> void main (void) { printf("Primul program in C!") } La compilare pe ecran vor apare următoarele mesaje de eroare: Compiling NONAME00.CPP: Error NONAME00.CPP 5: Statement missing ; Error NONAME00.CPP 5: Compound statement missing } Cu toate că în codul sursă există doar o eroare, compilatorul de C va afişa două mesaje de eroare. Lipsa caracterului punct şi virgulă provoacă o serie de erori în cascadă. Pentru a corecta erorile sintactice se merge cu ajutorul cursorului în linia indicată de către mesajul de eroare şi se corectează instrucţiunea respectivă. 3.2. Structura unui program C Conceptul de bază folosit în structurarea programelor scrise în limbajul C este funcţia. Astfel, un program în C este compus din cel puţin o funcţie şi anume funcţia main() sau funcţia principală. La rândul ei, funcţia main() poate apela alte funcţii definite de utilizator sau existente în bibliotecile ce însoţesc orice mediu de dezvoltare de programare în C. Structura generală a unei funcţii C este de forma: tip nume_funcţie (param_1, param_2, ...,param_n) − − − − − − − −  − − − − − − − − Instrucţiuni declarare tip parametri − − − − − − − −  { − − − − − − −  − − − − − − − Corp funcţie=secvenţă de instrucţiuni sau apel de funcţii − − − − − − −  54
  • 61. } unde: - tip reprezintă tipul de dată returnat de funcţie (în mod implicit o funcţie returnează tipul int); - nume_funcţie reprezintă numele sub care funcţia este cunoscută în program; - param_1,...,param_n - parametrii cu care funcţia este apelată şi al căror tip poate fi declarat direct în această listă, sau prin instrucţiuni separate plasate imediat după lista parametrilor. Corpul funcţiei este definit ca o secvenţă de instrucţiuni şi/sau apeluri de funcţii şi este delimitat de restul funcţiei prin paranteze acolade. În limbajul C există două categorii de funcţii. O primă categorie este formată de funcţiile ce returnează o valoare la revenirea din ele în punctul de apel, tipul acestei valori fiind definit de de tipul funcţiei. Cealaltă categorie conţine funcţiile ce nu returnează nici o valoare la revenirea din ele , pentru aceste funcţii fiind utilizat cuvântul cheie void în calitate de tip. El semnifică lipsa unei valori returnate la revenirea din funcţie. Exemple: 1) void f(void) { ………… } Funcţia f nu are parametri şi nu returnează nici o valoare. 2) double g(int x) { ………… } Funcţia g are un parametru x de tipul int şi returnează la revenirea în programul principal o valoare flotantă în dublă precizie. Funcţiile C sunt în general unităţi independente, compilabile separat. Instrucţiunile, la rândul lor, pot defini tipul unor date folosite în program, sau operaţii ce trebuie executate prin program. Din punct de vedere sintactic, orice instrucţiune trebuie terminată cu caracterul ";", iar grupurile de instrucţiuni pot fi delimitate prin caracterele { şi } pentru a forma unităţi sintactice noi de tip bloc. Funcţiile apelate vor primi valori pentru argumentele (parametrii) lor şi pot returna către funcţia apelantă valori de un anumit tip. 55
  • 62. Cu aceste precizări generale, dacă avem un program compus din două funcţii, şi anume funcţia principală şi o funcţie apelată f(), atunci structura acestuia va fi de forma: tip main( )  {   --------    Functia principala f( ); / * apelul functiei f() * /  --------   }   ___________ f( )  {    − − − − − − Functia f( ) − − − − − −  }   Programul începe cu execuţia funcţiei main(). Aceasta funcţie este folosită în general fără parametri. La rândul lor, funcţiile apelate pot fi scrise în limbaj C, sau realizate în alte limbaje de programare: asamblare, Fortran, Pascal etc. 3.3. Mulţimea caracterelor În programele C pot fi utilizate două mulţimi de caractere: mulţimea caracterelor C şi mulţimea caracterelor C reprezentabile. Mulţimea caracterelor C se compune din litere, cifre, semne de punctuaţie care au o semnificaţie specifică pentru compilatorul C. Programele C sunt formate din combinaţii ale caracterelor din mulţimea de caractere C constituite în instrucţiuni semnificative. Mulţimea caracterelor C este o submulţime a mulţimii caracterelor C reprezentabile. Mulţimea caracterelor reprezentabile este formată din totalitatea literelor, cifrelor şi simbolurilor grafice. Dimensiunea mulţimii de caractere reprezentabile depinde de tipul de terminal, consolă etc. Fiecare caracter din mulţimea caracter din mulţimea caracterelor C are un înţeles explicit pentru compilatorul C. Compilatorul dă mesaje de eroare când întâlneşte caractere întrebuinţate greşit sau caractere care nu aparţin mulţimii caracterelor 56
  • 63. C. În continuare sunt descrise caracterele şi simbolurile din mulţimea caracterelor C şi utilizarea acestora. 3.3.1. Litere şi numere Mulţimea caracterelor C include literele mari şi mici ale alfabetului englez şi cifrele zecimale din sistemul de numere arabe. Literele mari şi mici ale alfabetului englez sunt următoarele: ABCDEFGHIJKLMNOPRSTUVWXYZ abcdefghijklmnoprstuvwxyz iar cifrele zecimale: 0 1 2 3 4 5 6 7 8 9. Aceste litere şi cifre pot fi folosite pentru a forma constante, identificatori şi cuvinte cheie. Compilatorul C prelucrează litere mari şi mici în mod distinct. 3.3.2. Caractere whitespace Spaţiul, tab-ul, linefeed (linie nouă), carriage return (revenire la capătul rândului), form feed, tab-ul vertical şi newline sunt numite caractere whitespace deoarece servesc pentru spaţiere între cuvinte, aliniere la o nouă coloană, salt la linie nouă. Aceste caractere separă instrucţiuni definite de utilizator, constante şi identificatori, de celelalte instrucţiuni dintr-un program. Compilatorul C ignoră caracterele whitespace dacă nu sunt folosite ca separatori sau drept componente de constante, sau ca şiruri de caractere. Caracterele whitespace sunt utilizate pentru a face programele mai lizibile. Comentariile sunt de asemenea tratate ca whitespace. 3.3.3. Caractere speciale şi de punctuaţie Caracterele speciale şi de punctuaţie din mulţimea caracterelor C sunt folosite pentru mai multe scopuri. Tabelul următor prezintă aceste caractere. Aceste caractere au o semnificaţie specială pentru compilatorul de C. Caracterele de punctuaţie din setul de caractere reprezentabile C care nu apar în acest tabel pot fi utilizate numai în şiruri, constante caracter şi comentarii. Caracte Nume Caracte Nume 57
  • 64. r r , Virgulă ! Semnul exclamării . Punct | Bară verticală ; Punct şi virgulă / Slash : Două puncte Backslash ? Semnul ~ Tilda întrebării ’ Apostrof _ Underscore ” Ghilimele # Diez ( Paranteză % Procent stânga ) Paranteză & Ampersand dreapta [ Paranteză ^ Săgeată sus dreaptă stânga ] Paranteză * Asterisc dreaptă dreapta { Acoladă stânga - Minus } Acoladă dreapta = Egal > Mai mare + Plus < Mai mic 3.3.4. Secvenţe escape Secvenţele escape sunt combinaţii speciale de caractere formate din whitespace şi caractere negrafice constituite în şiruri şi constante caracter. Ele sunt în mod tipic utilizate pentru a specifica acţiuni precum carriage return şi tab pe terminale şi imprimante şi pentru a furniza reprezentarea caracterelor care normal au înţeles special, cum ar fi ghilimelele (”). O secvenţă escape constă dintr-un backslash urmat de o literă sau combinaţii de cifre. Setul complet de secvenţe escape cuprinde: a caracterul BEL - activare sunet b caracterul BS (backspace) - revenire cu un spaţiu f caracterul FF (form feed) - salt de pagină la imprimantă n caracterul LF (line feed) - rând nou r caracterul CR (carriage return) - revenire la coloana 1 t caracterul HT (horizontal tab) - tab orizontal v caracterul VT (vertical tab) - tab vertical 58
  • 65. caracterul (backslash) " caracterul " (double qoute) - ghilimele ' caracterul ' (single qoute) - apostrof 0 caracterul NULL ooo - constantă octală xhh - constantă hexazecimală Backslash-ul care precede un caracter neinclus în lista de mai sus este ignorat şi acest caracter este reprezentat ca un literal. De exemplu, forma „c” reprezintă caracterul c într-un literal sau într-o constantă caracter. Secvenţele ooo şi xdd permit scrierea oricărui caracter din setul ASCII ca un număr octal format din trei cifre sau ca un număr hexagesimal format din două cifre. Exemplu: '6' 'x6' 6 ASCII '60' 'x30' 48 ASCII '137' 'x5f' 95 ASCII Numai cifrele octale (de la 0 la 7) pot apare într-o secvenţă escape octală şi trebuie să apară cel puţin o cifră. De exemplu, caracterul backspace poate fi scris ca „10” în loc de „010”. Similar, o secvenţă hexagesimală poate să conţină cel puţin o cifră, iar a doua cifră poate fi omisă. Totuşi, când se utilizează secvenţe escape în şiruri, este indicat să se scrie toate cele trei cifre ale secvenţei. Altfel, caracterul care urmează după secvenţa escape ar putea fi interpretat ca o parte a secvenţei, dacă se întâmplă să fie o cifră octală sau hexagesială. De exemplu, secvenţa 0331 este interpretată drept ESC şi 1. Dacă am scrie 331, omiţând primul zero, atunci am avea o interpretare greşită. Secvenţele escape permit caractere de control negrafice pentru a fi transmise către display. Caracterele negrafice trebuie totdeauna reprezentate ca secvenţe escape. Plasând necorespunzător un caracter negrafic în programe C, el are rezultat imprevizibil. 3.4. Identificatori Identificatorii sunt nume ce sunt date variabilelor, funcţiilor şi etichetelor utilizate în program. Un nume este o succesiune de litere şi eventual cifre, primul caracter fiind literă. În calitate de litere se pot utiliza literele mici şi mari ale alfabetului englez, precum şi caracterul 59
  • 66. subliniere (_). Numărul de caractere care intră în componenţa unui nume nu este limitat. Numele sunt utilizate pentru a defini diferite variabile sau funcţii într-un program C. În mod implicit, numai primele 32 de caractere dintr-un nume sunt luate în considerare, adică două nume sunt diferite dacă ele diferă în primele 32 de caractere ale lor. Exemple de nume: a, b1, a1b2c3, Fs, _hG, Nume, nUME, … Se recomandă ca numele să fie sugestive, adică ele să sugereze pe cât posibil scopul alegerii lor sau a datei pe care o reprezintă. 3.5. Cuvintele cheie ale limbajului C În limbajul C există un număr de cuvinte care au o utilizare predefinită, numite cuvinte cheie. Utilizatorul nu poate să utilizeze aceste cuvinte pentru a denumi variabile sau funcţii într-un program. Tabelul următor prezintă cuvintele cheie ale limbajului C: Cuvintele cheie ale limbajului C auto default float register struct volatile break do for return switch while case double goto short typedef char else if signed union const enum int sizeof unsigned continue extern long static void 3.6. Constante În C, constantele se referă la valori fixe pe care programul nu le poate modifica. Constantele pot fi: întregi, în virgulă mobilă sau reale, constante-caracter, constante-şir sau enumerări. Zero poate fi folosit ca o constantă pentru tipurile pointer, iar şirurile de caractere sunt de fapt constante de tip char[]. Este posibil, de asemenea, să se specifice constante simbolice. O constantă simbolică este un nume a cărui valoare nu poate fi modificată în domeniul său. În C există trei feluri de constante simbolice: 1. orice valoare de orice tip poate fi folosită ca şi constantă prin adaugarea cuvântului cheie const la definirea sa; 2. un set de constante întregi definite ca o enumerare; 3. orice nume de vector sau funcţie. 60
  • 67. 3.6.1. Constante caracter O constantă caracter este un caracter inclus între apostrofuri. De exemplu, 'a', 'A' şi '%' sunt constante caracter. Valoarea unei constante caracter este chiar valoarea numerică corespunzătoare caracterului dat în setul de caractere al maşinii. De pildă, dacă pe un calculator caracterele se reprezintă în cod ASCII, atunci constanta '1' are valoarea 0618, 4910 sau 3116. Constantele caracter pot fi folosite în operaţii de calcul exact ca şi întregii. Caracterele pot fi reprezentate şi prin secvenţe escape (de exemplu, prin constanta 'n' se introduce caracterul newline). 3.6.2. Constante întregi Constantele întregi se reprezintă în 4 forme: zecimale, octale, hexazecimale şi constante caracter. Constantele zecimale sunt cel mai frecvent folosite şi se reprezintă ca şiruri de cifre zecimale. O constantă care începe cu zero urmat de x (0x) este un număr hexazecimal, iar o constantă care începe cu zero este un număr octal. Pentru a reprezenta cifrele hexazecimale 10,...,15 se folosesc literele a,...,f sau literele mari corespunzătoare. Notaţiile octale şi hexazecimale sunt utile în exprimarea succesiunilor de biţi. Exemplu: int hex = 0xFF; /* numărul 255 în zecimal */ int oct = 011 ; /* numărul 9 în zecimal */ 3.6.3. Constante în virgulă mobilă O constantă în virgulă mobilă are tipul float, double sau long double. Compilatorul, ca şi în cazul constantelor întregi, trebuie să semnaleze eroare în cazul în care constantele sunt prea mari pentru a putea fi reprezentate. Exemple de constante în virgulă mobilă: 123.23 .23 0.23 1.0 1. 1.2e10 1.256-15 Observaţie. În interiorul constantelor întregi sau reale nu pot apare spaţii albe. De exemplu, 56.62 e - 17 nu este o constantă în virgulă mobilă, ci sunt de fapt 4 atomi lexicali: 56.62, e, -, 17 şi se va genera o eroare de sintaxă. Dacă se doreşte o constantă de tip float, aceasta se poate defini astfel: const float pi8 = 3.14159265; 61
  • 68. 3.6.4. Constante şir Constantele şir constau din caractere cuprinse între ghilimele, ca în faimosul “Hello, worldn“. Constantele şir, spre deosebire de altele, au o locaţie în memoria calculatorului. Caracterele dintr-un şir sunt stocate în memorie, iar valoarea numerică a constantei este adresa acestei memorii. În plus, compilatorul stochează caracterul null ‘0’ la sfârşitul şirului, marcând astfel sfârşitul său. În cazul setului de caractere ASCII, constanta şir “0“ arată astfel în memorie: 3000 48 (30H) 0 ‘0’ ‘0’ iar valoarea constantei este adresa sa din memorie (în exemplul de mai sus valoarea 3000), pe când valoarea caracterului 0 este 48 sau 30H. Cea mai scurtă constantă şir este şirul null scris drept “ “ şi este stocat în memorie ca un singur caracter null ‘0’. De exemplu, dacă avem constanta şir “ABC“ atunci, la o anumită adresă de memorie vom avea: Adresa 61 ‘A’ Adresa+1 62 ‘B’ Adresa+2 63 ‘C’ Adresa+3 0 ‘/0’ Valoarea constantei şir “ABC“ va fi Adresa, adică valoarea adresei locaţiei în care se stochează primul caracter din şir. Ca o ultimă remarcă, vom face precizarea că din punctul de vedere al reprezentării, constanta caracter ‘A’, spre exemplu, este diferită de consta şir “A“, care se stochează în memorie la o anumită adresă şi se termină cu caracterul null, deci are alocaţi doi octeţi. Fiecare constantă şir conţine cu un caracter mai mult decât numărul de caractere din şir deoarece aceasta se termină totdeauna cu caracterul 0 care are valoarea 0. De exemplu, sizeof("asaf") va fi 5. 62
  • 69. Tipul unui şir este vector de un număr de caractere a.i. "asaf" are tipul char[5]. Şirul vid se notează prin " " şi are tipul char[1]. De notat că, pentru fiecare şir s, strlen(s) == sizeof(s) - 1, deoarece funcţia strlen() nu numără şi terminatorul 0. În interiorul unui şir se poate folosi convenţia de notaţie cu . Aceasta face posibilă reprezentarea caracterului ghilimele (") şi în interiorul unui şir. Cel mai frecvent caracter folosit este caracterul 'n'=newline (NL). De exemplu, instrucţiunea: printf ("beep at end of message007n"); determină scrierea unui mesaj, a caracterului BEL şi a caracterului NL. O secvenţă de forma n într-un şir nu determină introducerea unui caracter NL în şir, ci este o simplă notaţie (n este caracter neafişabil). Nu este permisă continuarea şirurilor de caractere de pe o linie pe alta. Atunci când se include o constantă numerică într-un şir de caractere utilizând notaţia octală sau hexazecimală este recomandat să se folosească 3 cifre pentru număr. Exemplu: char v1[] = "ax0fah0129";//'a' 'x0f' 'a' 'h' '012' '9' char v2[] = "axfah 129"; /* 'a' 'xfa' 'h' '12' '9' */ char v3[] = "axfad127"; /* 'a' 'xfa' 'd' '127' */ 3.6.5. Constanta zero Zero poate fi utilizat ca o constantă pentru tipurile întregi, în virgulă mobilă sau pointer. Nu se recomandă alocarea unui obiect la adresa zero. Tipul lui zero va fi determinat de context. 3.6.6. Obiecte constante Cuvântul cheie const poate fi inclus într-o declaraţie a unui obiect pentru a determina ca tipul acestui obiect să fie constant şi nu variabil. Exemplu : const int model = 145; const int v[ ] = {1, 2, 3, 4}; Deoarece nu i se poate atribui o valoare, o constantă poate fi doar iniţializată. Declarând ceva ca fiind constant, ne asigurăm că valoarea sa nu se modifică în domeniul său. Astfel instrucţiunile: model = 165; /* eroare */ model++; /* eroare */ vor determina apariţia unor mesaje de eroare corespunzătoare. 63
  • 70. De notat că const modifică un tip ceea ce înseamnă că restricţionează felul în care se poate utiliza un obiect, şi nu modul de alocare. Pentru o constantă, compilatorul nu rezervă memorie deoarece i se cunoaşte valoarea (precizată la iniţializare). Mai mult, iniţializatorul pentru o expresie constantă este, de obicei (dar nu întotdeauna), o expresie constantă. Dacă este aşa, aceasta poate fi evaluată în timpul compilării. 3.6.7. Enumerări Folosirea cuvântului cheie enum este o metodă alternativă pentru definirea constantelor întregi, ceea ce este uneori mult mai util decât utilizarea lui const. De exemplu, enum {ASM , AUTO , BREAK }; defineşte 3 constante întregi denumite enumeratori şi le atribuie valori. Deoarece valorile enumeratorilor sunt atribuite implicit, începând cu 0, aceasta este echivalentă cu: const ASM = 0; const AUTO = 1; const BREAK = 2; O enumerare poate avea nume. De exemplu, enum Keyword {ASM , AUTO , BREAK }; defineşte o enumerare cu numele Keyword. Numele enumerării devine sinonim cu int şi nu cu un nou tip. Declararea unei variabile Keyword în loc de int poate oferi atât utilizatorului, cât şi compilatorului, o sugestie asupra modului de utilizare. De exemplu, enum Keyword Key; //declara var. Key de tip enum Keyword switch (Key) { case ASM: ........... break; case BREAK: ........... break; } determină compilatorul să iniţieze un avertisment deoarece sunt folosite numai două din cele trei valori ale lui Key. Capitolul IV OPERANZI ŞI OPERATORI ÎN C 64
  • 71. 4.1. Operanzi O expresie, în limbajul C, este formată dintr-un operand sau mai mulţi legaţi prin operatori. Un operand poate fi: - o constantă; - o constantă simbolică; - numele unei variabile; - numele unui tablou; - numele unei structuri; - numele unui tip; - numele unei funcţii; - elementele unui tablou; - elementele unei structuri; - o expresie inclusă între paranteze rotunde. Unui operand îi corespunde un tip şi o valoare. Dacă tipul operandului este bine precizat la compilare, valoarea operandului se determină fie la compilare, fie la execuţie. Exemple: 1. 6353 – este o constantă întreagă zecimală de tip int şi reprezintă un operand constant de tip int. 2. float x2 – reprezintă declaraţia variabilei x2, iar numele x2 reprezintă un operand de tipul float. 3. 0xa13d – este o constantă întreagă hexazecimală de tip unsigned şi reprezintă un operand de tipul unsigned. 4. produs(a,b) – este un apel al funcţiei produs. Această funcţie reprezintă un operand al cărui tip coincide cu tipul valori returnate de funcţia produs. 4.2. Operatori Operatorii pot fi unari sau binari în funcţie de numărul de operanzi cărora li se aplică. Un operator unar se aplică unui singur operand, iar un operator binar se aplică la doi operanzi. Operatorul 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: 65
  • 72. aritmetici, relaţionali şi logici, operatori pentru prelucrare biţi, precum şi câţiva operatori speciali pentru sarcini particulare. La scrierea unei expresii se pot utiliza operatori din toate clasele. La evaluarea unei astfel de expresii este necesar să se ţină seama de priorităţile operatorilor care aparţin diferitelor clase de operatori, de asociativitatea operatorilor de aceeaşi prioritate şi de regula conversiilor implicite. 4.2.1. Operatori aritmetici Lista operatorilor aritmetici este următoarea: + reprezintă operatorul plus unar sau binar, în funcţie de context - reprezintă operatorul minus unar sau binar, în funcţie de context * reprezintă operatorul de înmulţire (binar) / reprezintă operatorul de împărţire (binar) % reprezintă operatorul modulo (binar) Operandul operatorului unar plus trebuie să fie de tip aritmetic sau pointer, iar rezultatul este valoarea operandului. Un operand întreg presupune o promovare a întregilor. Operandul operatorului unar minus trebuie să fie de tip aritmetic, iar rezultatul este numărul negativ corespunzător. Un operand întreg presupune promovarea întregilor. Operanzii operatorilor * şi / trebuie să fie de tip aritmetic, iar ai lui % trebuie să fie de tip întreg. Operatorul binar / reprezintă câtul, iar % oferă restul împărţirii primului operand la al doilea. Dacă al doilea operand al operatorului / sau % este zero, rezultatul este nedefinit. Pentru operanzi de tip întreg este adevărată egalitatea: (a / b) * b + a % b = a În expresii operatorii binari + şi - au aceeaşi precedenţă, care însă este mai mică decât a grupului *, / şi %. Precedenţa ultimului grup este mai mică decât cea a operatorilor unari + şi -. Folosirea parantezelor în expresii poate schimba precedenţa între operatori în timpul evaluării acestora. Exemplu: Dacă a, b, c, d sunt variabile de tip int, atunci: - expresia d * b % a este echivalentă cu (d * b) % a; - expresia -a / d este echivalentă cu (-a) / d; - expresia a=b=c=d-15 este echivalentă cu a=(b=(c=(d -15))); - expresia a%-b*c este echivalentă cu (a%(-b))*c; 66
  • 73. 4.2.2. Operatori de incrementare şi decrementare În C, operaţiile de forma i = i+1 şi j = j-1 pot fi programate folosind doi operatori unari specifici şi anume ++ pentru incrementare cu 1 şi -- pentru decrementare cu 1. Aceşti operatori pot fi folosiţi atât ca prefix pentru variabile (de exemplu, ++i, --j) sau ca sufix (i++, j--). Între aceste moduri de utilizare există diferenţe. Astfel, în expresia + +i, variabila i este incrementată înainte de a-i folosi valoarea, în timp ce în expresia i++, variabila i este incrementată după întrebuinţarea valorii acesteia. Exemplu: Considerăm secvenţa: x = 10; y = ++x; Dacă se afişează y, atunci vom găsi y = 11 deoarece mai întâi se incrementează x şi apoi se atribuie valoarea lui y. Dacă scriem: x = 10; y = x++; vom găsi y=10 (mai întâi se face atribuirea lui x la y şi apoi incrementarea lui x). Precedenţa tuturor operatorilor aritmetici este: Înaltă ++ -- + - (unari) * / % Scăzută + - (binari) Operatorii de aceeaşi precedenţă sunt evaluaţi de al stânga la dreapta. 4.2.3. Operatori relaţionali Operatorii relaţionali permit compararea a două valori şi luarea unei decizii după cum rezultatul comparării este adevărat sau fals. Dacă rezultatul operaţiei este fals, atunci valoarea returnată este zero, iar dacă este adevărat, valoarea returnată este 1. Operatorii relaţionali folosiţi în C sunt: 67
  • 74. == egal != diferit < mai mic strict <= mai mic sau egal > mai mare strict >= mai mare sau egal Operatorii relaţionali au o precedenţă mai mică decât operatorii aritmetici, astfel o expresie de forma a < b + c este interpretată ca a<(b+c). 4.2.4. Operatori logici Operatorii logici binari && (ŞI, AND) şi || (SAU, OR) precum şi operatorul logic unar de negare “!“ (NOT), atunci când sunt aplicaţi unor expresii, conduc la valori întregi 0 şi 1, cu semnificaţia fals şi adevărat. Semantica acestor operatori se deduce din tabelul următor, unde e1 şi e2 sunt două expresii: e1 e2 e1&&e2 e1||e2 ! e1 zero zero 0 0 1 zero diferit de zero 0 1 1 diferit de zero zero 0 1 0 diferit de zero diferit de zero 1 1 0 Expresiile legate prin operatori logici binari sunt evaluate de la stânga la dreapta. Precedenţa operatorilor logici şi relaţionali este următoarea: Înaltă ! > >= < <= == != && Scăzută || Astfel, expresia: 10>5 && !(10<9) || 3<4 este adevarată; expresia: 1 && !0 || 1 este adevarată; expresia; 1 && ! (0 ||1) este falsă. Programul următor tipăreşte numerele pare cuprinse între 0 şi 100. # include <stdio.h> main () 68
  • 75. { int i; for (i = 0; i <= 100; i++) if (! (i%2)) printf ("%d" , i); } Operatorii logici şi relaţionali sunt utilizaţi în formarea instrucţiunilor repetitive precum şi a instrucţiunii if. 4.2.5. Operatori logici la nivel de bit Ne reîntoarcem la cei trei operatori de tip booleean & (AND, §I), | (OR, SAU) şi ~ (NOT) precum şi la un al patrulea operator, denumit SAU-EXCLUSIV ^ (EXCLUSIVE-OR). Aceşti operatori se aplică la nivel de bit sau grupuri de biţi, după tabelele: AND x OR x NOT EXCLUSIVE-OR x & 0 1 | 0 1 ~ ^ 0 1 y 0 0 0 y 0 0 1 y 0 1 y 0 0 1 1 0 1 1 1 1 1 0 1 1 0 În C, aceşti operatori se aplică în paralel biţilor corespunzători aflaţi în orice poziţie. Din această cauză ei se mai numesc şi operatori logici pe bit. Trebuie făcută distincţia faţă de operatorii logici, care folosesc notaţii dublate: &&, ||, sau !. Operatorii logici au aceleaşi denumiri, dar ei tratează întregul operator ca pe o singură valoare, adevărată sau falsă. În scriere, se mai foloseşte şi denumirea bit-and, bit-or, bit-negate sau exclusive-or. Ca exemplu, considerăm operaţia bit-not. Fie numărul binar: N2 = 0000000000000111 = 0x0007 = 710 Negarea sa pe bit se realizează cu instrucţiunea ~0x7 sau ~07 sau ~7 şi valoarea sa va fi ~N2 = 1111111111111000 = 0xFFF8 sau 0177770 pe un computer cu întreg pe 16 biţi sau 0xFFFFFFF8 pe un computer cu întreg pe 32 de biţi. Exemplul următor realizează un SAU şi un ŞI pentru două caractere: ‘a’ | ’c’ = 0110 0001 | 0110 0011 = 0110 0011 = ‘c’ ‘a’ & ’c’ = 0110 0001 & 0110 0011 = 0110 0010 = ‘a’ 69
  • 76. Deoarece limbajul C a fost gândit să înlocuiască limbajul de asamblare în majoritatea operaţiilor de programare, acesta trebuie să aibă capacitatea să suporte toţi (sau cel puţin mulţi) operatorii utilizaţi în asamblare. Operatorii pentru prelucrarea biţilor se aplică biţilor dintr-un byte sau cuvânt, ambele variabile de tip char şi short int. Aceşti operatori nu se aplică tipurilor float, double, long double, void sau altor tipuri mai complexe. Operatorii pentru prelucrarea biţilor utilizaţi în C sunt: & AND | OR ^ exclusive OR (XOR) ~ complement faţă de unu (NOT) >> deplasare dreapta << deplasare stânga Operaţiile pe biţi sunt utilizate de obicei în drivere, pentru testarea şi mascarea anumitor biţi. De exemplu, operaţia AND poate fi folosită pentru ştergerea unor biţi dintr-un byte sau dintr-un cuvânt, OR poate fi folosită pentru setarea unor biţi, iar XOR pentru complementarea unor biţi şi testarea egalităţii a 2 bytes. Observaţie: Operatorii relaţionali şi logici (&&, ||, !,...) produc totdeauna un rezultat care este fie 0, fie 1, pe când operatorii similari destinaţi prelucrării biţilor pot produce orice valoare arbitrară, în concordanţă cu operaţia specifică. Operatorii >> şi << deplasează toţi biţii dintr-o variabilă la dreapta, respectiv la stânga. Forma generală a operaţiilor de deplasare este: variabilă >> număr_de_poziţii_bit - pentru deplasare dreapta. variabilă << număr_de_poziţii_bit - pentru deplasare stânga. În poziţiile rămase libere, după deplasare, se introduc zerouri. Operaţiile de deplasare pot fi utile când se decodifică perifericele de intrare cum ar fi convertoarele D/A (digital/analogice) şi când se citesc informaţii de stare. Operatorii de deplasare se pot utiliza şi pentru realizarea cu rapiditate a operaţiilor de înmulţire şi împărţire. Se ştie că o deplasare stânga cu 1 bit realizează înmulţirea cu 2, iar o deplasare dreapta cu 1 bit realizează o împărţire cu 2. Exemplu: 70
  • 77. x = 7; 0 0 0 0 0 1 1 1 7 x << 1; 0 0 0 0 1 1 1 0 14 x << 3; 0 1 1 1 0 0 0 0 112 x << 2; 1 1 0 0 0 0 0 0 192 x >> 1; 0 1 1 0 0 0 0 0 96 x >> 2; 0 0 0 1 1 0 0 0 24 Următorul exemplu evidenţiază efectul operatorilor de deplasare: # include <stdio.h> void disp_binary(); /* prototipul functiei disp_binary() */ void main() { int i = 1, t; for (t=0;t<8;t++) { disp_binary(i); i=i<<1;} printf (" n"); for (t=0;t<8;t++) { i=i>>1; disp_binary(i);}} void disp_binary(int i) /* se defineste functia disp_binary() */ /* care afiseaza bitii dintr-un byte */ {register int t; for (t=128;t>0;t=t/2) if (i&t) printf("1"); else printf("0"); printf("n");} Programul produce următoarea ieşire: 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 . . . . . . . . . . . . 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 . . . . . . . . . . . . 0 0 0 0 0 0 0 1 Deşi limbajul C nu conţine un operator de rotire, se poate realiza o funcţie care să efectueze această operaţie. De exemplu rotirea la stânga cu o poziţie a numărului 10101010 ne conduce la numărul 01010101 şi se realizează după schema: 71
  • 78. 1 0 1 0 1 0 1 0 0 1 0 1 0 1 0 1 O posibilitate de realizare a operaţiei de rotire necesită utilizarea unei uniuni cu două tipuri de date diferite. De exemplu utilizând uniunea: union rotate { char ch[1]; unsigned int i; } rot; Următoarea funcţie realizează o rotire cu 1 bit. void rotate_bit(union rotate *rot) {rot->ch[1]=0; rot->i=rot->i<<1; if (rot->ch[1]) rot->i=rot->i|1;} Atât întregul i cât şi cele două caractere ch[0] şi ch[1] partajează primii doi octeţi din cei 4 rezervaţi de uniune. Numărul de rotit se introduce (pe 8 biţi) în ch[0]. Se roteşte apoi întregul i (deci se rotesc toţi cei 4 octeţi care îi corespund). Se testează MSB al lui ch[0] care se găseşte în urma rotirii în poziţia LSB din ch[1]. Dacă este 1, atunci se setează la 1 LSB din ch[0], realizându-se astfel operaţia de rotaţie. Un exemplu de program care să utilizeaze această funcţie: # include <stdio.h> union rotate { char ch[1]; unsigned int i; } rot; void disp_binary(); void rotate_bit(); void main() { register int t; rot.ch[0]=147; for (t=0;t<7;t++) { disp_binary(rot.i); rotate_bit(&rot);}} /* se defineste functia rotate_bit() */ void rotate_bit(union rotate *rot) {rot->ch[1]=0; rot->i=rot->i<<1; if (rot->ch[1]) rot->i=rot->i|1;} 72
  • 79. /* se defineste functia disp_binary() */ void disp_binary(int i) {register int t; for (t=128;t>0;t=t/2) if (i&t) printf("1"); else printf("0"); printf("n");} Acest program realizează rotirea numărului 14710=100100112 cu 6 poziţii 10010011 00100111 01001110 10011100 00111001 01110010 11100100 Programul de mai sus funcţionează pentru numere reprezentabile pe un octet (mai mici de 255). Dacă dorim să facem o rotire pe doi octeţi, atunci se poate modifica programul de mai sus după cum urmează: # include <stdio.h> union rotate { char ch[3]; unsigned int i; } rot; void disp_binary(); void rotate_bit(); void main() { register int t; rot.i=17843; for (t=0;t<7;t++) { disp_binary(rot.i); rotate_bit(&rot);}} /* se defineste functia rotate_bit() */ void rotate_bit(union rotate *rot) {rot->ch[2]=0; rot->i=rot->i<<1; if (rot->ch[2]) rot->i=rot->i|1;} /* se defineste functia disp_binary() */ void disp_binary(int i) {register int t; for (t=32768;t>0;t=t/2) if (i&t) printf("1"); else printf("0"); printf("n");} 73
  • 80. 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 %cn", ch, ~ch);} while (ch != 'q');} 4.2.6. Operatorul de atribuire În C, operatorul de atribuire (asignare) este semnul egal (=). Valoarea expresiei din dreapta se atribuie variabilei din stânga operatorului "=". În C, forma: suma = a + b + c; trebuie privită ca o nouă expresie, numită expresie de asignare. Valoarea ei este chiar valoarea expresiei din dreapta operatorului de atribuire. Dacă într-o expresie se fac mai multe atribuiri, atunci evaluarea se face de la dreapta la stânga: x = y = z = 0 este echivalentă cu (x=(y=(z=0))); O expresie de atribuire de forma x = x + 5 în care variabila din stânga apare imediat după operatorul = se poate scrie într-o formă compactă de tipul x += 5, unde operatorul += este tot un operator de atribuire. Majorităţii operatorilor binari le corespund operatori de atribuire de forma "op = " unde op poate fi : +, -, *, %, <<, >>, &, ^ , Operatorii de atribuire (asignare) sunt: = , += , -= , *= , /= , %= , <<= , >>= , &= , ^= , |= Deci, o expresie de asignare de forma : var = (var) op (expr) unde var este o variabilă şi expr este o expresie, admite o reprezentare compactă de forma: var op= expresie Într-o formă compactă, ca mai sus, var este evaluată o singură dată. 4.2.7. Operatorul sizeof Operatorul sizeof returnează numărul de octeţi necesar memorării variabilei sau tipului datei care este operandul său. Dacă 74
  • 81. sizeof operează asupra unui tip de date, atunci tipul trebuie să apară între paranteze. De exemplu, sizeof(char) va fi 1, sizeof(int) va fi 4 etc., deci rezultatul este un număr întreg, fără semn. Fişierul standard stddef.h defineşte tipul size_t al rezultatului oferit de operatorul sizeof. Dacă sizeof se aplică unui tablou, rezultatul este numărul total de octeţi din tablou. Exemplul din programul următor prezintă dimensiunea principalelor tipuri de date: # include <stdio.h> # include <stddef.h> void main(){ printf("nTip caracter pe %d octet",sizeof(char)); printf("nTip short int pe %d octeti",sizeof(short int)); printf("nTip int pe %d octeti",sizeof(int)); printf("nTip long int pe %d octeti",sizeof(long int)); printf("nTip float pe %d octeti",sizeof(float)); printf("nTip double pe %d octeti",sizeof(double)); printf("nTip long double pe %d octetin", sizeof(long double));} În urma execuţiei acestui program, se va afişa (rezultatele depind de tipul de procesor sau de compilator): Tip caracter pe 1 octet Tip short int pe 2 octeti Tip int pe 4 octeti Tip long int pe 4 octeti Tip float pe 4 octeti Tip double pe 8 octeti Tip long double pe 8 octeti 4.2.8. Operatorul ternar ? Operatorul " ? " poate fi utilizat pentru a înlocui instrucţiunea if / else având forma: if (conditie) expresie1 else expresie2 Operatorul ternar " ? " necesită trei operanzi şi are forma generală: Expr1 ? Expr2 : Expr3 unde Expr1, Expr2 şi Expr3 sunt expresii. Se evaluează expresia Expr1. Dacă este adevărată, se evaluează Expr2, care devine valoarea întregii expresii. Dacă Expr1 este falsă, se 75
  • 82. evaluează Expr3, iar valoarea acesteia devine valoarea întregii expresii: Exemplu: x = 10; y = x > 9 ? 100 : 200; Cum 10 > 9, valoarea lui y va fi 100. Dacă x ar fi mai mic decât 9, y va primi valoarea 200. Acelaşi program scris cu if /else va fi: x = 10; if (x > 9) y = 100; else y = 200; În alcătuirea expresiilor din declaraţia operatorului ternar " ? " pot fi folosite şi funcţii: Exemplu: # include <stdio.h> f1(); f2(); // prototipurile functiilor f1() si f2() void main() { int t; printf (": "); scanf("%d",&t); // se introduce numarul intreg t t?f1()+f2(t): printf(" S-a introdus zeron");} f1() {printf ("S-a introdus "); } f2(int n) {printf ("%dn", n);} Dacă se introduce zero, atunci va fi apelată printf() şi va afişa " S-a introdus zero". Dacă se introduce alt număr, atunci programul va executa atât funcţia f1(), cât şi funcţia f2(). 4.2.9. Operatorul virgulă Operatorul virgulă se utilizează într-un şir în care se introduc mai multe expresii. Astfel, instrucţiunea: x = (y = 3, y+1), are că efect atribuirea valorii 4 variabilei x. Deci expresiile separate prin virgulă sunt evaluate de la stânga la dreapta, prima expresie evaluată căpătând valoarea void. Dacă se utilizează un operator de atribuire, valoarea atribuită variabilei din stânga operatorului de atribuire este valoarea ultimei expresii din dreapta, după evaluare. Exemplu: y = 10; x = (y = y - 5, 30 / y); Variabila x va căpăta valoarea 6. 76
  • 83. Observaţie Deoarece operatorul virgulă are o precedenţă mai mică decât operatorul de atribuire, pentru ca atribuirile să se facă corect, trebuie utilizate paranteze. 4.2.10. Operatorul de forţare a tipului sau de conversie explicită (expresie cast) Adesea se doreşte specificarea conversiei valorii unui operand spre un tip dat. Acest lucru este posibil folosind o construcţie de forma: (tip) operand Printr-o astfel de construcţie valoarea operandului se converteşte spre tipul indicat în paranteze. În construcţia de mai sus (tip) se consideră că este un operator unar. Acest operator este cunoscut sub numele de operator de forţare a tipului sau de conversie explicită. De cele mai multe ori însă este utilizată denumirea engleză a operatorului şi anume expresie cast. Exemplu: Presupunem că o funcţie oarecare f are un parametru de tip double. Pentru ca această funcţie să poată fi apelată cu un parametru int n (n este un parametru de tip întreg) acesta trebuie mai întâi convertit la tipul double. Acest lucru se poate realiza printr-o atribuire: double x f(x=n) Un alt mod mai simplu de conversie a parametrului întreg spre tipul double este utilizarea unei expresii cast: f((double)n) Operatorul de forţare a tipului fiind unar, are aceeaşi prioritate ca şi ceilalţi operatori unari ai limbajului C. 4.2.11. Operatorii paranteză Parantezele rotunde se utilizează fie pentru a include o expresie, fie la apelul funcţiilor. O expresie inclusă în paranteze rotunde formează un operand. În acest mod se poate impune o altă ordine în efectuarea operaţiilor, decât cea care rezultă din prioritatea şi asociativitatea operatorilor. Operanzii obţinuţi prin includerea unei expresii între paranteze impun anumite limite asupra operatorilor. De exemplu, la un astfel de operand nu se pot aplica operanzii de incrementare şi decrementare sau operatorul adresă. Astfel construcţiile: (a-5+b)++ --(a+b) &(a*b) sunt eronate. 77
  • 84. La apelul unei funcţii, lista parametrilor efectivi se include între paranteze rotunde. În acest caz se obişnuieşte să se spună că parantezele rotunde sunt operatori de apel de funcţie. Parantezele pătrate include expresii care reprezintă indici. Ele se numesc operatori de indexare. Parantezele sunt operatori de prioritate maximă. Operatorii unari au prioritatea imediat mai mică decât parantezele. 4.2.12. Operatorul adresă Operatorul adresă este unar şi se notează prin caracterul &. El se aplică pentru a determina adresa de început a zonei de memorie alocată unei date. În forma cea mai simplă, acest operator se utilizează în construcţii de forma: &nume unde nume este numele unei variabile simple sau al unei structuri. În cazul în care nume este numele unui tablou, atunci acesta are ca valoare chiar adresa de început a zonei de memorie alocată tabloului respectiv şi, în acest caz, nu se mai utilizează operatorul adresă &. 4.2.13. Alţi operatori ai limbajului C În limbajul C se mai utilizeză şi operatorii: „ * ” , „ . ” şi „->” Operatorul „ * ” unar (a nu se confunda cu operatorul aritmetic binar de înmulţire) se utilizează pentru a face acces la conţinutul unei zone de memorie definită prin adresa ei de început. Se obişnuieşte să se spună că operatorul de adresă & este operator de referenţiere, iar operatorul „ * ” este operator de dereferenţiere. Operatorii „ . ” şi „ -> ” se utilizează pentru a se accesa componentele unei structuri. Ei au prioritate maximă, având aceeaşi prioritate cu parantezele. 4.2.14. Regula conversiilor implicite şi precedenţa operatorilor Regula conversiilor implicite se aplică la evaluarea expresiilor. Ea acţionează atunci când un operator binar se aplică la doi operanzi de tipuri diferite. În acest caz, operandul de tip inferior se converteşte spre tipul superior al celuilalt operand şi rezultatul este de tip superior. Înainte de toate se convertesc operanzii de tip char şi enum în tipul int. Dacă operatorul curent se aplică la operanzi de acelaşi tip, atunci se execută operatorul respectiv, iar tipul rezultatului coincide cu tipul comun al operanzilor. Dacă rezultatul aplicării operatorului 78
  • 85. reprezintă o valoare în afara limitelor tipului respectiv, atunci rezultatul este eronat (are loc o „depăşire”). Exemplu: Rezultatul împărţiirii 7/3 este 2 şi nu 2.5 deoarece cei doi operanzi sunt de tip întreg şi prin urmare rezultatul (care este de tip real) este şi el convertit la tipul întreg. Dacă operatorul binar se aplică la operanzi de tipuri diferite, atunci se face o conversie înainte de execuţia operatorului, conform algoritmului umător: 1. Dacă unul din operanzi este de tip long double, atunci celălalt operand se converteşte spre tipul long double iar tipul rezultatului aplicării operatorului este de asemenea de tip long double. 2. Altfel, dacă unul din operanzi este de tip double atunci celălalt operand se converteşte spre tipul double iar tipul rezultatului aplicării operatorului este de asemenea de tip double. 3. Altfel, dacă unul din operanzi este de tip float atunci celălalt operand se converteşte spre tipul float iar tipul rezultatului aplicării operatorului este de asemenea de tip float. 4. Altfel, dacă unul din operanzi este de tip unsigned long atunci celălalt operand se converteşte spre tipul unsigned long iar tipul rezultatului aplicării operatorului este de asemenea de tip unsigned long. 5. Altfel, dacă unul din operanzi este de tip long atunci celălalt operand se converteşte spre tipul long iar tipul rezultatului aplicării operatorului este de asemenea de tip long. 6. Altfel, unul din operanzi trebuie sa fie de tip unsigned, celălalt de tip int şi acesta se converteşte spre tipul unsigned, iar tipul rezultatului aplicării operatorului este de tip unsigned. Precedenţele operatorilor C sunt prezentate în tabelul următor. Operatorii aflaţi pe aceeaşi linie au aceeaşi prioritate. Ei se asociază de la stânga la dreapta, exceptând operatorii unari, condiţionali şi de atribuire, care se asociază de la dreapta la stânga. Precedenţa Operatorul 79
  • 86. Înaltă () [ ] -> . ! ~ ++ -- - (type) * & sizeof * / % + - << >> < <= > >= == != & ^ | && || ?: Scăzută = += -= *= /= , Capitolul V INSTRUCŢIUNI Limbajul C posedă un set variat de instrucţiuni, set care îi permite să realizeze principalele compuneri de operaţii: secvenţierea, repetiţia cu test final, repetiţia cu test iniţial, repetiţia cu număr cunoscut de paşi, decizia şi selecţia, saltul necondiţionat, ieşirea prematură dintr-un ciclu. Instrucţiunile pot fi clasificate în: instrucţiuni etichetate, instrucţiuni expresie, instrucţiuni compuse, instrucţiuni de selecţie, instrucţiuni repetitive, instrucţiuni de salt. 5.1. Instrucţiuni etichetate (instrucţiunea goto) Instrucţiunile etichetate posedă etichete ca prefixe şi au forma: etichetă: instrucţiune 80
  • 87. Eticheta formată dintr-un identificator defineşte identificatorul ca destinaţie pentru o instrucţiune de salt, singura utilizare a sa fiind ca destinaţie a unei instrucţiuni goto. Etichetele nu pot fi redeclarate. Etichetele sunt locale în corpul funcţiei în care sunt definite. Instrucţiunea goto are următorul format: goto etichetă La întâlnirea instrucţiunii goto, se realizează un salt la instrucţiunea prefixată de eticheta aflată după instrucţiunea goto. Deoarece o etichetă este locală în corpul unei funcţii rezultă că ea este nedefinită în afara corpului funcţiei respective, deci, o instrucţiune goto nu poate face salt la o instrucţiune din afara corpului funcţiei în care este definită. Nu este recomandată utilizarea abuzivă a acestei instrucţiuni deoarece programul devine mai puţin lizibil şi pot apare erori logice în program foarte greu de detectat. Instrucţiunea goto se utilizează în special pentru ieşirea din mai multe cicluri imbricate. Exemplu: Următorul program utilizează instrucţiunea goto pentru a afişa numerele de la 1 la 100: #include <stdio.h> void main(void) { int nr=1; eticheta: printf(”%d”, nr++); if (nr<=100) goto eticheta; } 5.2. Instrucţiuni expresie Cele mai multe instrucţiuni sunt instrucţiunile expresie, care au forma: [ expresie ]; unde expresie este opţională. Majoritatea instrucţiunilor expresie sunt atribuiri sau apeluri de funcţii. Deci, o instrucţiune expresie constă dintr-o expresie urmată de caracterul ";". Exemplu: a = b * c + 3; printf ("FAC. DE AUTOMATICA"); 81
  • 88. Dacă expresia expresie de mai sus lipseşte, construcţia “;“ se numeşte instrucţiune vidă. Aceasta nu are nici un efect, dar este utilizată pentru a înlocui un corp vid al unei iteraţii sau pentru a plasa o etichetă. 5.3. Instrucţiuni compuse O instrucţiune compusă este o posibilă listă de declaraţii şi/sau instrucţiuni închise între acolade. Exemplu: { a = b + 2; b++; } O instrucţiune compusă se numeşte bloc. Un bloc ne permite să tratăm mai multe instrucţiuni ca pe una singură. Corpul unei funcţii este o instrucţiune compusă. Domeniul de vizibilitate al unui identificator declarat într-un bloc se întinde din punctul declaraţiei până la sfârşitul blocului. Identificatorii utilizaţi într-un bloc pot fi ascunşi prin declaraţii de acelaşi nume în blocurile interioare blocului iniţial. Exemplu: # include <stdio.h> int x = 34; /* x este global */ void main(void) { int *p = &x; /*p preia adresa variabilei globale*/ int x1, x2; printf("x = %dn", x); {int x; /*x este local si il ascunde pe cel global */ x = 1; /* atribuirea se face la cel local */ x1 = x; printf("x = %dn", x1);} { int x; /* se ascunde prima variabila locala */ x = 2; /* se atribuie valoarea 2 acestui x */ x2 = x; printf("x = %dn",x2); } printf("x = %d %d %d n",x,x1,x2); } 5.4. Instrucţiuni de selecţie 5.4.1. Instrucţiunea if O instrucţiune if cu care în C se implementează o structură de control de selecţie sau o structură alternativă, are următorul format general: if (conditie) instructiune1; else instructiune2; 82
  • 89. unde conditie este orice expresie care prin evaluare conduce la o valoare întreagă. Dacă valoarea expresiei este diferită de zero (condiţie adevărată), atunci se execută instructiune1; altfel, dacă valoarea expresiei este zero (condiţie falsă), se execută instructiune2. În ambele cazuri, după executarea lui instructiune1 sau instructiune2, controlul este transferat la instrucţiunea ce urmează după if. Aici, prin instructiune1 sau instructiune2 se înţelege o instrucţiune simplă, o instrucţiune compusă (un bloc) sau o instrucţiune vidă. Porţiunea else instructiune2; este opţională, în acest fel putându- se obţine o structură de selecţie cu o ramură vidă de forma: if (conditie) instructiune; Exemplu: Următorul program citeşte două numere şi afişează pe cel mai mare dintre ele. # include <stdio.h> void main (void) { int x, y; printf("Introduceti doua numere intregi: n"); scanf ("%d %d", &x, &y); if (x > y) printf ("Cel mai mare este : %dn",x); else printf("Cel mai mare este : %dn", y); } Deoarece partea else dintr-o instrucţiune if este opţională, apare o ambiguitate atunci când else este omis dintr-un if inclus (încuibat). În C acest lucru se rezolvă prin asocierea lui else cu cel mai apropiat if. De exemplu, în secvenţa: if (x) if (y) printf ("1"); else printf ("2"); else este asociat cu instrucţiunea if(y). Dacă dorim ca else să fie asociat cu if(x) trebuie să utilizăm acolade, astfel: if (x) { if (y) printf ("1"); } else printf ("2"); Secvenţa anterioară este echivalentă cu: if (x) { if (y) printf ("1"); else ;} else printf ("2"); 83
  • 90. 5.4.2. Instrucţiuni de selecţie multiplă: if - else if Într-o instrucţiune if se poate include, pe o ramură, o altă instrucţiune if. În acest fel se creează posibilitatea de a codifica structuri de selecţie multiplă, folosindu-se perechi else if. O asemenea construcţie este de forma: if (conditie1) instructiune1; else if (conditie2) instructiune2; else if (conditie3) instructiune3; . . . . . . . . . . . . . . . . else if (conditieN) instructiuneN; else instructiuneN+1; În acest caz, condiţiile sunt testate în ordine. Dacă una din ele este adevărată, atunci este executată instrucţiunea corespunzătoare, după care controlul este transferat la instrucţiunea următoare din program. Codul pentru fiecare alternativă poate fi format dintr-o instrucţiune simplă (inclusiv instrucţiunea vidă) sau dintr-un bloc delimitat prin { şi }. Dacă nici una dintre expresii nu este adevărată, atunci se execută secvenţa corespunzătoare ultimei alternative introdusă prin else. Această ultimă alternativă nu este obligatorie, structura putându-se încheia după secvenţa notată cu instructiuneN. Exemplu: Considerăm un program care realizează conversiile inch-cm şi cm-inch. Presupunem că indicăm unitatea intrării cu i pentru inch şi c pentru centimetru: # include <stdio.h> # include <conio.h> void main(void) { const float fact = 2.54; float x,in,cm; char ch = 0; printf ("nIntroduceti numarul: n"); scanf("%f",&x); printf("nIntroduceti unitatea: n"); ch = getche(); /* se introduce un caracter de la tastatura care se afiseaza pe ecran */ if (ch == 'i') { in = x; cm = x * fact;} else if(ch == 'c') { 84
  • 91. in = x/fact; cm = x; } else in = cm = 0; printf("n%5.2f in = %5.2f cm n",in,cm); } 5.4.3. Instrucţiunea switch Într-o instrucţiune de selecţie switch, se compară, pe rând, o valoare cu constantele dintr-o mulţime şi în momentul găsirii unei coincidenţe se execută instrucţiunea sau blocul de instrucţiuni asociate acelei constante. Forma generală a instrucţiunii switch este: switch (variabila) { case constanta1 : secventa_instructiuni_1 break; case constanta2 : secventa_instructiuni_2 break; case constanta3 : secventa_instructiuni_3 break; . . . . . . . . . . . . . . . . . . . case constantaN : secventa_instructiuni_N break; default : secventa_instructiuni_N+1 } Instrucţiunea switch realizează transferul controlului la una din secvenţele de instrucţiuni dacă valoarea variabila ce trebuie să aibă tipul întreg coincide cu una din constantele de dupa case. Secvenţa de instrucţiuni se execută pâna se întâlneşte break, după care se trece la instrucţiunea imediat următoare după switch. Dacă nu se găseşte nici o coincidenţă, se execută secvenţa de instrucţiuni de după default, iar dacă default lipseşte, deoarece prezenţa acesteia este opţională, se trece la instrucţiunea următoare. Exemplu: Decizia din exemplul anterior poate fi realizată şi astfel: # include <stdio.h> # include <conio.h> void main(void) { const float fact = 2.54; float x, in, cm; char ch = 0; printf ("nIntroduceti numarul: n"); scanf("%f", &x); 85
  • 92. printf("nIntroduceti unitatea: n"); ch = getche(); switch(ch) { case 'i': in = x; cm = x * fact; break; case 'c': in = x/fact; cm = x; break; default: in = cm = 0; break; } printf("n%5.2f in = %5.2f cm n",in,cm); } Observaţie: Constantele case trebuie sa fie distincte. Pentru a ieşi din instrucţiunea switch se foloseşte instrucţiunea break. Exemplu: # include <stdio.h> void main (void) { int t; for (t = 0; t < 10; t++) switch (t) { case 1 : printf ("Now"); break; case 2 : printf (" is "); break; case 3 : case 4 : printf (" the "); printf (" time for all good men n"); break; case 5 : case 6 : printf (" to "); break; case 7 : case 8 : case 9 : printf (" . "); break; } } Rulând acest program, vom obţine: Now is the time for all good men the time for all good men to to . . . Instrucţiunea switch este foarte eficientă în scrierea programelor care afişează pe ecran o listă de opţiuni (un meniu) din 86
  • 93. care utilizatorul alege câte una şi o execută. Instrucţiunile switch pot fi şi incluse (încuibate) una în alta. 5.5. Instrucţiuni repetitive 5.5.1. Instrucţiunea for Forma generală a instrucţiunii for este: for (initializare; conditie; incrementare) instructiune; unde: - initializare este o instrucţiune de atribuire utilizată pentru iniţializarea variabilei de control a ciclului. Nu există nici o restricţie privitoare la tipul său; - condiţie este o expresie relaţională care se testează înaintea fiecărei iteraţii: dacă condiţia este adevărată (diferită de 0), ciclul se continuă; dacă condiţia este falsă (egală cu 0), instrucţiunea for se încheie; - incrementare se evaluează după fiecare iteraţie specificând astfel reiniţializarea ciclului. Exemplu: Următorul program afişează pe ecran numerele de la 1 la 100. # include <stdio.h> void main (void) { int x; for (x = 1; x <= 100; x++) printf("%d ", x); } Nu întotdeauna ciclul for trebuie să se desfăşoare în sensul creşterii variabilei de control. Putem crea cicluri for în care variabila de control se decrementează. Exemplu: Programul următor afişează numerele de la 100 la 1. # include <stdio.h> void main (void) { int x; for (x =100; x > 0; x--) printf("%d", x); } Nu există restricţii în incrementarea sau decrementarea variabilei de control a ciclului. Exemplu: Următorul program afişează pe ecran numerele de la 0 la 100 din 5 în 5: # include <stdio.h> void main (void) { int x; for (x = 0; x <= 100; x = x + 5) printf ("%d", x); } 87
  • 94. Instructiunea instrucţiune din declaraţia ciclului for poate fi o instrucţiune simplă sau un bloc (un grup de instrucţiuni delimitate de acolade) care va fi executat repetitiv. Exemplu: Programul următor afişează pe ecran numerele de la 0 la 99, precum şi pătratul acestora: # include <stdio.h> void main (void) { int i; for (i = 0; i < 100; i++) { printf (" Acesta este i : %3d", i); printf (" si i patrat : %5d n", i*i); } } Exemplu: Calculul factorialului unui număr: n! = 123...n # include <stdio.h> void main (void) { int n, i; long int factorial; printf ("Introduceti n : "); scanf ("%d", &n); factorial = 1; for (i = 1; i <= n; i++) factorial *= i; printf (" %d ! = %ld n", n, factorial); } Instrucţiunile for pot fi incluse una în alta. Exemplu: Programul următor parcurge un şir de caractere de la stânga la dreapta, afişând subşirurile ce au ca bază primul caracter. # include <stdio.h> # include <string.h> void main (void) { int l, n, i; char sir[81]; puts ("Tastati un sir terminat cu <CR> : "); gets (sir); l = strlen (sir); for (n = 0; n <= l; ++n) { for (i = 0; i < n; ++i) putchar (sir[i]); putchar ('n'); } } Variante ale ciclului for: Limbajul C permite mai multe variante ale ciclului for care determină creşterea flexibilităţii acestuia în diferite situaţii. Una din cele mai utilizate variante constă în folosirea a mai multe variabile de control a ciclului. În exemplul următor, atât x cât şi y sunt variabile de control a ciclului: 88
  • 95. # include <stdio.h> void main (void) { int x, y; for (x = 0, y = 0; x + y < l00; x++, y++) printf ("%d ", x + y); } Acest program tipăreşte numerele de la 0 la 98 din 2 în 2. Se observă că iniţializările şi incrementările celor două variabile sunt separate prin virgulă. Terminarea ciclului presupune testarea nu numai a variabilei de control cu anumite valori prestabilite, ci condiţia de terminare a ciclului poate fi orice expresie C corectă. Exemplu: Considerăm un program pentru antrenarea unui copil în exerciţiile de adunare. Dacă copilul vrea să se oprească se apesa tasta T, atunci când calculatorul îl întreabă dacă să continue. # include <stdio.h> # include <conio.h> void main (void) { int i, j, raspuns; char terminare = ' '; for (i=1; i<100; i++) { for (j=1; j<100 && terminare !='t';j++) { printf ("nCit este %d + %d ? ",i,j); scanf("%d",&raspuns); if (raspuns != i+j) printf("nGresit !"); else printf("nCorect !"); printf(" Continuam ? "); terminare = getchar(); } /* Pentru terminare se apasa tasta t */ } } O altă caracteristică interesantă a ciclului for este aceea că nu se impune definirea tuturor celor trei parametri ai ciclului for, oricare dintre ei putând fi opţionali. De exemplu, ciclul următor se va executa până când de la tastatura se introduce numărul 123: for (x = 0; x != 123;) scanf("%d", &x); Deoarece instrucţiunea de incrementare a lui x lipseşte, de fiecare dată când ciclul se repetă, programul testează ca x să fie egal cu 123, dar nu modifică pe x în nici un fel. Dacă de la tastatură se introduce 123, condiţia buclei devine falsă şi ciclul se termină. Exemplu: O variantă de calcul a lui n! ar fi următoarea: # include <stdio.h> void main (void) { int n, i; long int factorial; 89
  • 96. printf ("Introduceti n : "); scanf ("%d", &n); factorial = 1; for (i = 1; i <= n;) { factorial *= i ++; printf (" %d ! = %ld n", n, factorial); } } Bucle infinite: Una din cele mai interesante utilizări ale ciclului for constă în crearea de bucle infinite. Dacă nici una din cele trei expresii care formează ciclul for nu sunt precizate, se obţine o buclă fără sfârşit, ca în exemplul următor în care se consideră că elementul condiţie are valoarea adevărat: for (;;) printf ("Aceasta bucla va rula la nesfirsit. n "); Ieşirea dintr-o bucla for: Pentru terminarea unei bucle for, chiar şi a buclei for(; ;) se foloseşte instrucţiunea break care se plasează oriunde în corpul ciclului şi determină încheierea imediată a ciclului (la întâlnirea acesteia), programul continuându-se cu instrucţiunea ce urmează după instrucţiunea for. Exemplu: Acest program va rula până când de la tastatura se apasă tasta A: # include <stdio.h> # include <ctype.h> void main (void) { for (; ;) { ch = getche (); if (ch == 'a') break; } printf (" Ai apasat tasta A "); } Utilizarea ciclurilor for fără corp (instrucţiune) : Pentru crearea unor întârzieri de timp se pot folosi cicluri for cu corp vid de forma: for (t = 0; t < O_ANUMITA_VALOARE; t++); Observaţie: De obicei, instrucţiunea for este legată de parcurgerea unor structuri de date de tip tablou. 5.5.2. Instrucţiunea while Forma generală a instrucţiunii repetitive while este: while (conditie) instructiune; unde instructiune poate fi o instrucţiune vidă, o instrucţiune simplă sau un bloc de instrucţiuni ce vor fi executate repetitiv. În timpul 90
  • 97. execuţiei se evaluează mai întâi condiţia buclei a cărei valoare trebuie să fie întreagă. Dacă valoarea calculată este diferită de 0 (condiţie adevărată), atunci instructiune se execută. Dacă, după o evaluare (inclusiv prima) rezultă o valoare 0 (condiţie falsă), atunci controlul este transferat la instrucţiunea ce urmează după while. Astfel, instrucţiunea asociată cu while se execută repetat, cât timp valoarea asociată condiţiei este diferită de 0 sau condiţia este adevărată. Exemplu: Programul următor calculează c.m.m.d.c. pentru o pereche x, y de numere întregi pozitive. # include <stdio.h> void main (void) { int xi, yi, x, y; printf (" Introduceti doua numere pozitive: n"); scanf ("%d %d", &xi, &yi); x = xi; y = yi; while (x != y) if (x > y) x -= y; else y -= x; printf (" C.m.m.d.c. (%d, %d) = %d", xi, yi, x); } Metoda de calcul se bazează pe faptul că: ♦ daca x > y, atunci cmmdc (x, y) = cmmdc (x-y, x); ♦ daca x < y, atunci cmmdc (x, y) = cmmdc (x, y-x); ♦ daca x = y, atunci cmmdc (x, y) = x =y . De exemplu, cmmdc (14, 21) = 7. Deoarece instrucţiunea while realizează testarea condiţiei la începutul instrucţiunii, aceasta instrucţiune este bună de utilizat în situaţiile în care nu se doreşte execuţia buclei, evident dacă condiţia nu este adevărată. Exemplu: Programul următor realizează centrarea unui text pe ecran: # include <stdio.h> # include <ctype.h> void main (void) { char sir[255]; printf(" Introduceti un sir de caractere: n"); gets (sir); centreaza (strlen (sir)); printf (sir); } /* Se calculează numărul de spaţii pentru centrarea unui şir de caractere cu lungimea lung */ centreaza (lung) int lung; { lung = (80 - lung)/2; 91
  • 98. while (lung > 0) { printf (" "); lung--; } } Dacă dorim să programăm un ciclu infinit, atunci se poate găsi o expresie care ramâne tot timpul adevărată. Un exemplu uzual este următorul: while (1) { Corpul ciclului } Ieşirea din ciclu, în acest caz, se asigură prin mecanisme de tip break, goto sau return. Corpul ciclului while poate conţine şi numai instrucţiunea vidă. De exemplu, while ((ch = getche ()) != 'A'); este o buclă simplă care se execută până când de la tastatură se va introduce caracterul "A". Observaţie: Instrucţiunea while reprezintă mecanismul sintactic de bază pentru a programa cicluri în C. Reamintim că instrucţiunea for se foloseşte după următorul format general: for (initializare; conditie; incrementare) instructiune; care este echivalentă semantic cu secvenţa: initializare; while (conditie) { instructiune; incrementare; } 5.5.3. Instrucţiunea do-while Spre deosebire de ciclurile programate cu while sau for, unde condiţia de ciclare este verificată la început, în cazul folosisii mecanismului do-while, condiţia se evaluează după execuţia secvenţei de instrucţiuni ce reprezintă corpul ciclului. Forma generală a buclei do-while este: do { instructiune; } while (conditie); Semantic, do-while este echivalentă cu secvenţa: instructiune; while (conditie) instructiune; Deşi acoladele nu sunt necesare când instructiune este o instrucţiune simplă, de obicei se utilizează pentru a evita confuzia cu while. Se remarcă faptul că instructiune ce reprezintă corpul ciclului (adică, o instrucţiune simplă, o instrucţiune compusă sau o instrucţiune vidă) este executată cel puţin odată. Celelalte execuţii sunt condiţionate de valoarea întreagă rezultată din evaluarea 92
  • 99. condiţiei. Dacă această valoare este 0 (condiţie falsă), atunci controlul se transferă la următoarea instrucţiune din program; în caz contrar se execută corpul ciclului şi se reevaluează condiţia. Exemplu: Următoarea secvenţă asigură preluarea corectă a unei valori întregi între 1 şi 10: # include <stdio.h> void main (void) { int num; do { printf("nnIntrod. un intreg între 1 si 10: "); scanf ("%d", &num); printf (" Numarul introdus este : %d ", num); } while (num < 1 || num > 10); } Un caz tipic de utilizare a instrucţiunii do-while este oferit de programele interactive în care selecţia unei opţiuni se face pe baza unui meniu afişat pe ecranul terminalului. Exemplu: Următorul program implementează o versiune a unui meniu de verificare a corectitudinii ortografice într-un text: # include <stdio.h> # include <ctype.h> void main (void) { char ch; printf ("1. Verificarea ortografiei n "); printf ("2. Corectarea erorilor de ortografie n"); printf ("3. Afisarea erorilor de ortografie n "); do { printf ("n Introduceti optiunea dumneavoastra: "); ch=getche(); // Se citeste optiunea de la tastatura switch (ch) { case '1': verifica_ortografia(); break; case '2': corecteaza_erorile(); break; case '3': afiseaza_erorile(); break; } } while (ch != '1' && ch != '2' && ch != '3'); } După afişarea opţiunilor, programul va bucla până când se va selecta o opţiune validă. Exemplu: Adunarea elementelor a doi vectori: int a[10], b[10], c[10]; . . . . . . . . . . . . . . i = 0; 93
  • 100. do { c[i] = a[i] + b[i]; i = i + 1; } while (i < 10); sau i = 0; do { c[i] = a[i] + b[i]; i++; } while (i < 10); 5.5.4. Bucle încuibate Când o buclă este introdusă în altă buclă, bucla interioară se spune a fi inclusă (nested, încuibată) în bucla exterioară. Exemplu: Programul următor afişează primele 4 puteri ale numerelor cuprinse între 1 şi 9: # include <stdio.h> void main (void) { int i, j, k, p; printf (" i i^2 i^3 i^4 n "); for (i = 1; i < 10; i++) { for (j = 1; i < 5; j++) { p = 1; for (k = 1; i < j; k++) p = p * i; printf (" %9d ", p); } printf (" n "); } } Când se execută acest program se obţin următoarele rezultate: i i^2 i^3 i^4 1 1 1 1 2 4 8 16 3 9 27 81 . . . . . . . . . . . 9 81 729 6561 Alinierea rezultatelor se datoreşte utilizării în printf() a unui format de afişare corespunzător (%9d) care precizează dimensiunea minimă a câmpului specificat. Un alt exemplu, puţin mai complex, este un program de înmulţire a două matrice. Evident, în acest caz vom avea 3 bucle for incluse una în cealaltă. // Program de inmultire a doua matrici # include <stdio.h> float a[100][100],b[100][100],c[100][100]; float elem, s; int la, ca, lb, cb, lc, cc, i, j, k; void main(void) { la=101; ca=101; lb=ca+1; cb=ca; 94
  • 101. printf("Program de inmultire a doua matricinSe declara dimensiunile fiecarei matricinn"); /* 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!nn"); while ((la>=100)||(ca>=100)) { printf("Intoduceti dimensiunile primei matrice"); printf("nNr. linii matrice A = n"); scanf("%d",&la); printf("Nr. coloane matrice A = n"); scanf("%d",&ca); } while ((lb>=101)||(cb>=101)) { printf("Intoduceti dimens. celei de-a doua matrice"); printf("nNr. linii matrice B = n"); scanf("%d",&lb); printf("Nr. coloane matrice B = n"); scanf("%d",&cb); } if(ca!=lb) { la=101;ca=101; lb=ca+1;cb=ca;} } /* Se introduc matricile */ for(i=0; i<=la-1; i++) for(j=0; j<=ca-1; j++) { printf("a(%d,%d) = ", i, j); scanf("%f",&elem); a[i][j] = elem; } for(i=0;i<=lb-1;i++) for(j=0;j<=cb-1;j++) { printf("b(%d,%d) = ",i,j); scanf("%f",&elem); b[i][j]=elem; } // Se calculeaza fiecare element al matricei produs for(i=0;i<=la-1;i++) 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("nnA = n"); for(i=0;i<=la-1;i++) { printf("n"); for(j=0;j<=ca-1;j++) printf("%6.3f ",a[i][j]); } printf("nnB = n"); 95
  • 102. for(i=0;i<=lb-1;i++) { printf("n"); for(j=0;j<=cb-1;j++) printf("%6.3f ",b[i][j]); } printf("nnC = A*Bn"); for(i=0;i<=la-1;i++) { printf("n"); for(j=0;j<=cb-1;j++) printf("%6.3f ",c[i][j]); }} 5.5.5. Instrucţiunea break Instrucţiunea break are două utilizări. Prima utilizare constă în terminarea unui case în cadrul instrucţiunii switch. A doua utilizare constă în terminarea imediată a unui ciclu scurtcircuitând testul condiţional normal al buclei. Dacă într-o buclă se întâlneşte o instrucţiune break, calculatorul termină (părăseşte) imediat bucla şi controlul programului se transferă la instrucţiunea ce urmează instrucţiunii de buclare. De exemplu, programul: # include <stdio.h> void main (void) { int t; for (t = 0; t < 100; t++) { printf (" %3d ", t); if (t == 10) break; }} tipăreşte numerele până la 10 şi atunci se opreşte deoarece break determină ieşirea imediată din ciclu. În cazul buclelor incluse, este important de notat ca break determină ieşirea imediată numai din bucla interioară (din bucla în care este introdus). De exemplu, programul: # include <stdio.h> void main (void) { int t; for (t = 0; t < 100; ++t) { count = 1; for (;;) { printf (" %d ", count); count++; if (count == 10) break; } } va afişa pe ecran numerele de la 1 la 10 de 100 de ori. Instrucţiunea break se poate utiliza şi în cadrul ciclurilor programate cu while sau do-while, schema generală de utilizare fiind următoarea: while (expresie) { ................. 96
  • 103. if (conditie) break; ................. } Dacă la una din iteraţii, condiţia din if este îndeplinită, atunci ciclul se termină automat, altfel el poate continua până când expresia din while are valoarea fals. Dacă instrucţiunea break se execută în cadrul unei instrucţiuni switch, care la rândul ei este inclusă într-un ciclu programat cu while, for sau do-while, atunci ea determină terminarea numai a instrucţiunii switch, nu şi ieşirea din ciclu. 5.5.6. Instrucţiunea continue Instrucţiunea continue, executată într-un ciclu, determină oprirea iteraţiei curente şi asigură trecerea imediată la iteraţia următoare. De exemplu, programul următor va afişa pe ecran numai numerele pare. # include <stdio.h> void main (void) { int x; for (x = 0; t < 100; x++) { if (x % 2) continue; printf (" %d ", x); } } Se observă că atunci când se generează un număr impar se execută instrucţiunea continue ce va determina trecerea la iteraţia următoare by-pasând instrucţiunea printf(). În cazul instrucţiunilor while şi do-while, o instrucţiune continue determină trecerea direct la testul condiţional şi prin urmare, continuarea procesului de buclare. În cazul unui for, se realizează mai întâi operaţia de incrementare a variabilei de control a ciclului, apoi testarea condiţiei de continuare a buclei. Capitolul VI TIPURI DE DATE STRUCTURATE În C există două categorii de tipuri de date structurate: tablourile şi structurile. Un tablou este o colecţie omogenă de valori de acelaşi tip identificate printr-un indice, iar o structură este o colecţie neomogenă de valori identificate prin nume simbolice, denumite selectori. 97
  • 104. 6.1. Tablouri unidimensionale Un tablou este o colecţie de variabile de acelaşi tip care sunt referite printr-un nume comun. În C, un tablou constă din locaţii de memorie contigue. Adresa cea mai mică corespunde primului element, iar adresa cea mai mare corespunde ultimului element. Un tablou poate avea de la una la mai multe dimensiuni. Accesul la un element specific al tabloului se face utilizând un index. Cel mai utilizat tablou este tabloul de caractere. Şirurile de caractere pot fi definite prin conceptele: vector de caractere şi pointer-caracter. Declararea unui tablou cu o singură dimensiune are următoarea formă generală: tip var_nume[size]; Aici, tip, declară tipul de bază al tabloului. Tipul de bază determină tipul de dată al fiecărui element al tabloului. var_nume este numele tabloului, iar size este numărul elementelor pe care le va conţine tabloul. Exemple: int a[10]; // vectorul a contine 10 intregi float v[3]; // vectorul v contine 3 reali În C toate tablourile folosesc pe zero ca index al primului lor element. Elementele tabloului a[10] sunt a[0],...,a[9]. Exemplu: Programul următor încarcă un tablou de întregi cu numerele de la 0 la 9: void main (void) { int x[10]; // se rezerva 10 elemente intregi int t; for (t = 0; t < 10; t++) x[t] = t; } Pentru un tablou unidimensional, dimensiunea totală, în bytes, a acestuia va fi: Total bytes = sizeof (tip) * lungimea_tabloului Observaţie: Limbajul C nu realizează verificarea dimensiunilor unui tablou: astfel, nu există nimic care să ne oprească să nu trecem peste sfârşitul tabloului. Dacă se trece peste sfârşitul unui tablou într-o operaţie de atribuire, atunci se vor atribui valori unor alte variabile sau chiar se vor distruge părţi din program. Exemplu: Deşi următorul program este incorect, compilatorul C nu semnalează nici o eroare: void main (void) { 98
  • 105. int crash[10], i; for (i = 0; i < 100; i++) crash[i] = i; } Se observă că bucla se iterează de 100 de ori, deşi vectorul crash conţine numai 10 elemente. Aceste verificări rămân în sarcina exclusivă a programatorului. Tablourile unidimensionale sunt, de fapt, liste de informaţii de acelaşi tip. De exemplu, prin rularea programului: char ch[7]; void main (void) { int i; for (i = 0; i < 7; i++) ch[i] = 'A' + i; } vectorul “ch“ arată astfel: ch(0) ch(1) ch(2) ch(3) ch(4) ch(5) ch(6) A B C D E F G 6.1.1. Constante şir În C o constantă şir este o secvenţă de caractere închisă între ghilimele. Exemplu: "acesta este un sir". Fiecare constantă şir conţine cu un caracter mai mult decât numărul de caractere din şir, deoarece aceasta se termină totdeauna cu caracterul NULL '0' care are valoarea 0. De exemplu, sizeof ("asaf") = 5. Tipul unui şir este "vector de un număr de caractere"; astfel "asaf" are tipul char[5]. §irul vid este descris prin " " şi are tipul char[1]. De notat că, pentru fiecare şir s, funcţia strlen(s) din fişierul antet "string.h" întoarce numărul caracterelor din şir fără terminatorul 0, adică: strlen(s) = sizeof(s) - 1. În interiorul unui şir se poate folosi convenţia de notaţie cu . Aceasta face posibilă reprezentarea caracterelor " şi în interiorul unui ş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 99
  • 106. caracterul null într-un şir, dar majoritatea programelor nu testează dacă mai sunt caractere după el. 6.1.2. Iniţializarea vectorilor de caractere • Citirea unui şir de la tastatură utilizând funcţiile scanf() şi gets().  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 ("%sn%sn", nume, adresa); } S-au definit variabilele nume şi adresa ca tip şir de caractere de maximum 20 şi 40 de caractere. Şirul "%s" care apare în apelul funcţiei scanf() precizează că se vor citi în variabilele nume, respectiv adresa, valori de tip şir de caractere. În printf() descriptorul "%s" are rolul de a preciza cum trebuie convertite valorile datelor de afişat (în cazul de faţă, valorile variabilelor nume şi adresa). Funcţia scanf() citeşte un şir de caractere din bufferul de intrare până când întâlneşte un spaţiu, un caracter TAB, sau ajunge la sfârşitul acestuia. Astfel, dacă se tastează, "ENE ALEXANDRU", atunci în variabila nume se va memora doar valoarea "ENE". Pentru a obţine şirul în întregime este recomandat să se transmită numele sub forma: "ENE_ALEXANDRU".  Cea mai bună cale de a introduce un şir de la tastatură constă în utilizarea funcţiei gets() din fişierul antet "stdio.h". Forma generală a funcţiei gets() este: gets (nume_vector) Pentru a citi un şir se apelează gets() având ca argument numele vectorului, fără nici un index. Funcţia gets() returnează vectorul ce va păstra şirul de caractere introdus de la tastatură. gets() va continua să citească caractere până la introducerea caracterului CR. Exemplu: Programul următor afişează şirul de caractere introdus de la tastatură. # include <stdio.h> void main (void) { char sir[80]; gets (sir); /* citeste un sir de la tastatura */ printf ("%s", sir); } 100
  • 107. Se observă că funcţia printf() poate primi ca argument un şir de caractere. Dacă se introduce un şir mai lung decât dimensiunea tabloului, vectorul va fi suprascris. • Iniţializarea vectorilor de caractere utilizând constantele şir Folosind constantele şir, vectorii de caractere se iniţializează sub forma: char nume_vector[size] = "sir_de_caractere" unde size = numărul caracterelor din şir plus 1. Exemplu: # include <stdio.h> void main (void) { char nume[14] = "ENE ALEXANDRU"; char adresa[24] = "Str. A. I. Cuza, nr.13"; puts (nume); puts (adresa); } Vectorul nume va ocupa începând de la adresa nume, 13 + 1 = 14 octeţi, iar cel de-al doilea vector va ocupa începând de la adresa adresa, 23 + 1 = 24 locaţii (bytes). Funcţia puts() scrie pe stdout şirul memorat în vectorul al cărui nume apare ca parametru al funcţiei puts(), precum şi caracterul "n". De multe ori, în C se realizează iniţializarea unor vectori de caractere a căror dimensiune nu este precizată. Dacă dimensiunea vectorului nu este precizată, compilatorul C va crea un vector suficient de lung încât să permită iniţializarea dorită. Exemplu: În loc să scriem : char e1[12] = "read errorn"; char e2[13] = "write errorn"; char e3[18] = "cannot open filen"; putem scrie: char e1[ ] = "read errorn"; char e2[ ] = "write errorn"; char e3[ ] = "cannot open filen"; Cu această ultimă iniţializare, instrucţiunea printf ("%s are lungimea %dn", e2, sizeof (e2)); va tipari: write error are lungimea 13  Iniţializarea unui vector (tablou unidimensional) se poate face şi cu o listă de iniţializatori scrişi între acolade. Dacă vectorul are o lungime necunoscută, numărul de iniţializatori determină mărimea tabloului, iar tipul devine complet. Dacă tabloul are lungime fixă, numărul de iniţializatori nu poate depăşi numărul de membri din 101
  • 108. tablou. În cazul în care sunt mai puţini iniţializatori, membrii în plus sunt iniţializaţi cu zero. Exemple: - Instrucţiunea următoare iniţializează un vector de 10 întregi cu numerele de la 1 la 10: int i[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; Rezultă că: i[0] = 1, ... , i[9] = 10. - Instrucţiunea următoare declară şi iniţializează vectorul x ca un tablou unidimensional cu 3 membri: int x[] = {1, 2, 3}; - Instrucţiunea următoare: char sir[6] = { 'h', 'e', 'l', 'l', 'o', '0' }; este echivalentă cu: char sir[6] = "hello"; 6.1.3. Funcţii pentru prelucrarea şirurilor (fişierul antet string.h) • Funcţia strcpy() Apelul funcţiei strcpy() are următoarea formă generală: strcpy (nume_sir, constanta_sir); Funcţia strcpy() copiază conţinutul constantei_sir (inclusiv caracterul terminator 'n') în nume_sir. Exemplu: # include <string.h> void main(void) { char sir[80]; strcpy (sir, "hello"); printf("%s", sir); } Acest program va copia "hello" în şirul sir. • Funcţia strcat() Apelul funcţiei strcat() are forma: strcat (s1, s2); Funcţia strcat() concatenează şirul s2 la sfârşitul şirului s1 şi întoarce şirul s1. Şirul s2 nu se modifică. Ambele şiruri trebuie să aibă caracterul terminator NULL, iar rezultatul va avea de asemenea caracterul terminator NULL. Exemplu: # include <stdio.h> # include <string.h> void main(void) { char first[20], second[10]; strcpy (first, "hello"); 102
  • 109. strcpy (second, "there"); strcat (first, second); printf ("%s", first); } Acest program va afişa "hellothere" pe ecran. • Funcţia strcmp() Se apelează sub forma: strcmp (s1, s2); Această funcţie compară şirurile s1 şi s2 şi returnează valori negative, dacă s1 < s2, 0, dacă s1 = s2 şi un număr pozitiv, dacă s1 > s2. Exemplu: Această funcţie poate fi folosită ca o subrutină de verificare a parolei: # include <stdio.h> # include <string.h> void main (void) { char s[80]; printf ("Introduceti parola: "); gets (s); if (strcmp (s, "pasword")) { printf (" Invalid pasword n "); return 0;} return 1; } • Funcţia strlen() Funcţia strlen() se apelează sub forma: strlen (s) unde s este un şir. Funcţia strlen() returnează lungimea şirului s. Exemplu: Programul următor returnează lungimea unui şir introdus de la tastatură. # incude <stdio.h> # incude <string.h> void main (void) { char sir[80]; printf ("Introduceti un sir: "); gets (sir); printf ("Sirul %s contine %d caractere ", sir, strlen(sir)); } Observaţie: Funcţia strlen() nu numără şi caracterul NULL. Exemplu: Programul următor afişează inversul unui şir de caractere introduse de la tastatură. # include <stdio.h> # include <string.h> void main (void) { char sir[80]; 103
  • 110. int i; gets(sir); for(i=strlen(sir)-1;i>=0;i--) printf("%c",sir[i]); } Exemplu: Programul următor realizează introducerea unor şiruri, compararea lor, concatenarea lor şi afişarea rezultatului. # include <stdio.h> # include <string.h> void main (void) { char s1[80], s2[80]; gets(s1); gets(s2); printf("Lungimi: %d %d n",strlen(s1),strlen(s2)); if (!strcmp (s1, s2)) printf ("Sirurile sunt egalen"); strcat (s1, s2); printf ("%sn", s1); } Dacă se rulează acest program şi se introduc şirurile s1 = "AUTOMATICA" şi s2 = "AUTOMATICA", se va afişa: Lungimi 10 10 Sirurile sunt egale AUTOMATICAAUTOMATICA Dacă şirurile sunt egale, funcţia strcmp() returnează fals (0) şi din această cauză în instrucţiunea if s-a folosit !strcmp(). Observaţie: Caracterul NULL de terminare a vectorului de caractere poate fi utilizat în buclele for ca în exemplul următor, unde se converteşte un şir de caractere scris cu litere mici la litere mari. # include <stdio.h> # include <string.h> void main (void) { char sir[80]; int i; strcpy (sir, "acesta este un test"); for(i = 0; sir[i]; i++) sir[i] = toupper (sir[i]); printf("%s", sir); } Conversia caracterelor se face cu funcţia toupper() care returnează litera mare corespunzătoare argumentului (literei mici). Ciclul funcţionează până când sir[i] devine caracterul null. 6.2. Tablouri cu două dimensiuni (matrice) Tablourile bidimensionale (matricele) sunt reprezentate ca vectori de vectori. De exemplu, declaraţia: int v[2][5]; 104
  • 111. declară un vector cu 2 elemente, fiecare element fiind un vector de tip int[5]. Se observă că fiecare dimensiune a tabloului este separată (închisă) între paranteze, iar dimensiunile nu sunt separate prin virgulă. Astfel, declaraţia: int v[2, 5]; conduce la eroare. 6.2.1. Iniţializarea matricelor Declaraţia : char v[2][5] = { 'a', 'b', 'c', 'd', 'e', '0', '1', '2', '3', '4' }; conduce la iniţializarea primului vector cu primele 5 litere, iar a celui de-al doilea cu primele 5 cifre. Exemplu: Programul: # include <stdio.h> void main (void) { char v[2][5] = { 'a', 'b', 'c', 'd', 'e', '0', '1', '2', '3', '4' }; int i, j; for (i = 0; i < 2; i++){ for(j = 0; j < 5; j++) printf ("v[%d][%d] = %c", i, j, v[i][j]); printf ("n"); } } va produce : v[0][0]=a v[0][1]=b v[0][2]=c v[0][3]=d v[0][4]=e v[1][0]=0 v[1][1]=1 v[1][2]=2 v[1][3]=3 v[1][4]=4. Exemplu: Secvenţa de instrucţiuni: # include <stdio.h> void main (void) { int l, c, num[3][4]; for (l = 0; l < 3; ++l) for (c = 0; c < 4; ++c) num[l][c] = (l * 4) + c + 1; } conduce la încărcarea tabloului num[3][4]cu numerele de la 1 la 12. Astfel, num[0][0] = 1, ..., num[2][3] = 12. Se observă că limbajul C memoreză tablourile bidimensionale într-o matrice linii-coloane, unde primul indice se referă la linie şi al doilea indice se referă la coloană. Cantitatea de memorie alocată permanent pentru un tablou, exprimată în bytes, este: nr_linii * nr_coloane * sizeof(tipul_datei) Declaraţia: float y[4][3] = { {1,3,5}, {2,4,6}, {3,5,7},}; este o iniţializare cu paranteze complete şi are următorul efect: 105
  • 112. - numerele 1, 3, 5 iniţializează prima linie a tabloului: y[0][0], y[0][1], y[0][2] sau y[0]; - numerele 2, 4, 6 iniţializează pe y[1]; - numerele 3, 5, 7 iniţializează pe y[2]. Întrucât iniţializatorul se termină cu virgulă, elementele lui y[3] vor fi iniţializate cu 0. Acelaşi efect ar fi putut fi realizat de: float y[4][3]={1, 3, 5, 2, 4, 6, 3, 5, 7, }; Secvenţa: float y[4][3] = { {1}, {2}, {3}, {0}, }; iniţializează prima coloană a lui y, privit ca un tablou bidimensional, cu 1, 2, 3 şi 0, restul tabloului fiind iniţializat cu 0. 6.2.2. Tablouri bidimensionale de şiruri Pentru crearea unui tablou de şiruri se foloseşte un tablou de caractere, bidimensional, în care mărimea indicelui din stânga determină numărul de şiruri, iar indicele din drepta specifică lungimea maximă a fiecărui şir. De exemplu, declaraţia : char sir_tablou[30][80]; defineşte un tablou de 30 de şiruri, fiecare şir având maximum 80 de caractere. Accesul la un singur şir este foarte uşor: se specifică numai primul indice. De exemplu: gets (sir_tablou[2]) întoarce al treilea şir din tabloul sir_tablou. Funcţional, instrucţiunea anterioară este echivalentă cu: gets (&sir_tablou[2][0]); 6.3. Tablouri multidimensionale Forma generală a declaraţiei unui tablou multidimensional este: tip nume[size1][size2]...[sizeN]; De exemplu, declaraţia: int trei[4][10][3]; creează un tablou de 4*10*3 întregi. Forma generală de iniţializare a tablourilor este următoarea: specificator_tip nume_tablou[size1][size2]...[sizeN]={lista_valori}; unde lista_valori este o listă de constante separate prin virgulă, compatibile cu tipul de bază al tabloului. Observaţie: Limbajul C permite şi iniţializarea tablourilor multidimensionale fără dimensiune. Trebuie menţionat că pentru 106
  • 113. aceasta este necesară precizarea indicelui celui mai din dreapta. Astfel, declaraţia: int sqrs[5][2] = {1, 1, 2, 4, 3, 9, 4, 16, 5, 25}; este echivalentă cu declaraţia: int sqrs[ ][2] = {1, 1, 2, 4, 3, 9, 4, 16, 5, 25}; 6.4. Structuri O structură este o colecţie de variabile (de tipuri diferite) referite sub un singur nume. Definiţia unei structuri formează un aşa numit şablon (template, tag) ce poate fi folosit la crearea variabilelor tip structură. Variabilele care formează structuri se numesc elementele structurii. De exemplu, fragmentul următor defineşte un şablon pentru o structură numită addr care defineşte la rândul său numele şi adresa unei persoane necesare în cazul transmiterii unei scrisori: struct addr { struct { char name[30]; char *name; char street[40]; char *street; char city[20]; char *city; char state[3]; char *state; unsigned int zip; unsigned int zip; }; } addr; Pentru definirea unui şablon al unei structuri se foloseşte cuvântul cheie struct. Terminarea definiţiei se face cu ”;” (este unul din puţinele cazuri de utilizare a caracterului punct şi virgulă ”;” după acoladă). Precizăm că numele addr identifică structura particulară definită anterior (şablonul) şi este specificatorul său de tip. Programul anterior defineşte numai forma (tipul) datelor structurii, dar nu defineşte variabilele structură, deci trebuie făcută distincţie dintre structura- şablon şi variabila-structură. O variabilă de tip structură se declară cu ajutorul şablonului structurii. Pentru a declara o variabilă actuală cu această structură vom scrie struct addr addr_info; Această linie declară variabila addr_info ca variabilă structură de tip addr. Limbajul C alocă suficientă memorie pentru a păstra toate variabilele ce alcătuiesc o structură. De exemplu, memoria alocată pentru structura addr_info va fi : 107
  • 114. Name 30 bytes Street 40 bytes City 20 bytes State 3 bytes Zip 4 bytes Când se defineşte o structură şablon, se pot declara una sau mai multe variabile-structuri, astfel : struct addr { char name[30]; char street[40]; char city[20]; char state[3]; unsigned int zip; } addr_info, binfo, cinfo; Secvenţa anterioară defineşte o structură şablon numită addr şi declară variabilele addr_info, binfo, cinfo de acelaşi tip. Pentru declararea unei singure structuri numite addr_info, nu mai este necesară includerea numelui addr al structurii, astfel: struct { char name[30]; char street[40]; char city[20]; char state[3]; unsigned int zip; } addr_info; În cazul de mai sus se defineşte variabila-structură addr_info cu şablonul definit, dar fără nume Forma generală de definire a unei structuri este : struc nume_tip_structura { tip nume_variabile; tip nume_variabile; tip nume_variabile; . . . . . . . . . . . . . . } variabile_structura; unde pot fi omise fie numele tipului structurii nume_tip_struct, fie variabile_structura, dar nu ambele. După cum se observă, nume_tip_structura după cuvântul cheie struct se referă la şablonul structurii (tipul său) iar variabile_structura se referă la lista de variabile de acest tip (cu această structură) 108
  • 115. Referirea individuală a elementelor unei structuri se face cu operatorul punct ".". De exemplu, instrucţiunea următoare va atribui elementului zip al variabilei structură addr_info valoarea 12345: addr_info.zip = 12345; Pentru accesarea elementelor unei structuri se foloseşte forma generală : nume_structura.nume_element De exemplu, pentru afişarea variabilei zip se va scrie: printf ("%d", addr_info.zip); În aceeaşi formă, se pot referi celelalte elemente ale structurii addr_info. De exemplu apelul: gets (addr_info.name); are ca efect trecerea la un pointer-caracter la primul caracter al elementului nume. Pentru a avea acces la fiecare caracter al elementului addr_info.name, se poate indexa name. De exemplu, pentru afişarea conţinutului lui addr_info.name, caracter cu caracter, se foloseşte programul: register int t; for (t = 0; addr_info.name[t]; ++t) putchar (addr_info.name[t]); 6.4.1. Tablouri de structuri Cel mai uzual mod de folosire a structurilor este în tablouri de structuri. Pentru declararea unui tablou de structuri, mai întâi se defineşte o structură şi apoi se declară un tablou de variabile de acel tip. De exemplu, pentru declararea unui tablou cu 100 de structuri addr definite anterior, se va scrie: struct addr addr_info[100]; Pentru a avea acces la o structură oarecare din cele 100 se va indexa numele structurii (în cazul acesta addr_info). De exemplu: printf ("%d", addr_info[2].zip); are ca efect afişarea codului zip din a treia structură. Se observă că, la fel orice variabilă tablou, tablourile de structuri încep cu indexul 0. Exemplu de program pentru actualizarea unei liste de corespondenţa - maillist Pentru a ilustra modul de utilizare a structurilor şi tablourilor de structuri prezentăm un exemplu de program pentru actualizarea unei liste de corespondenţă. 109
  • 116. Informaţiile ce vor fi memorate se referă la name, street, city, state, zip. Pentru definirea structurii de bază addr care va conţine aceste informaţii vom scrie: struct addr { char name[20]; char street[30]; char city[15]; char state[10]; unsigned int zip; } addr_info[SIZE]; Tabloul addr_info contine SIZE structuri de tip addr, unde SIZE se defineşte după necesităţi. Prima funcţie necesară în program este main(), a cărei structură este următoarea: void main() { char choice; init_list(); for (;;) { choice = menu(); switch (choice) { case 'e' : enter(); break; case 'd' : display(); break; case 's' : save(); break; case 'l' : load(); break; case 'q' : exit(); }}} Funcţia init_list() pregăteşte tabloul structură pentru utilizare prin punerea unui caracter null în primul byte al câmpului "nume". Programul impune ca o variabilă structură să nu fie utilizată dacă câmpul nume este vid. Această iniţializare are loc în memoria internă a calculatorului (nu în fişierul maillist de pe disc). Structura funcţiei de initializare init_list() ar putea fi următoarea: /* Functia init_list() */ void init_list() { register int t; for (t = 0; t < SIZE; t++) *addr_info[t].name = '0'; } Funcţia de selectare a meniului menu() va afişa mesajele opţiunilor şi va returna varianta aleasă. Prin tastarea literei din paranteze, se va lansa în execuţie o anumită procedură. /* Functia menu() */ char menu() { char s[5],ch; do { 110
  • 117. printf ("(E)ntern"); printf ("(D)isplayn"); printf ("(L)oadn"); printf ("(S)aven"); printf ("(Q)uitn"); printf (" Alegeti optiunea: "); gets(s); ch=s[0]; } while (!strrchr("edlsq",ch)); return tolower(ch); } Observaţie: Funcţia strrchr(cs,c) din <string.h> întoarce un pointer la ultima apariţie a lui c în cs sau NULL dacă nu apare. Funcţia enter() are ca efect introducerea unor noi informaţii în următoarea structură liberă a listei addr_info[SIZE]. Această introducere se efectuează prin determinarea primei structuri libere din memorie (cu addr_info.name setată la 0) şi prin completarea sa cu informaţii culese de la tastatură. /* Functia enter() */ void enter() { register int i; for (i=0; i < SIZE; i++) if (!*addr_info[i].name) break; if (i == SIZE) { printf ("addr_info full n"); /* Lista plina */ return;} printf ("Name: "); gets (addr_info[i].name); printf ("Street: "); gets (addr_info[i].street); printf ("City: "); gets (addr_info[i].city); printf ("State: "); gets (addr_info[i].state); printf ("Zip: "); scanf ("%d",&addr_info[i].zip);} Rutinele save() şi load() se utilizează pentru actualizarea bazei de date. Aceste rutine lucrează cu fişierul disc maillist. Când se apelează load(), atunci se copiază în tabloul structură din memorie datele stocate în fişierul maillist. Funcţia load() realizează operaţiunea inversă, de supraînscriere a fişierului disc cu datele din memorie. Spre exemplu, dacă dorim să adăugăm date la fişierul maillist care conţine deja date introduse anterior, ordinea de lansare a comenzilor va fi: init_list(), load(), enter(), save(), exit(). Structura acestor rutine este următoarea: 111
  • 118. /* Functia save() */ void save() { register int i; if ((fp = fopen("maillist", "wb")) == NULL) { printf (" Cannot open filen "); 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 filen "); 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 errorn"); } Atât save() cât şi load() confirmă un succes a unei operaţii cu fişiere prin verificarea valorilor întoarse de funcţiile fread() sau fwrite(). În plus, load() trebuie să caute şi indicatorul de sfârşit de fişier utilizând funcţia feof() deoarece fread() întoarce aceeaşi valoare dacă se întâlneşte indicatorul de sfârşit de fişier sau dacă apare o eroare. Funcţia display() afişează pe ecran întregul tablou structură din memorie care conţine date valide: /* Functia display() */ void display() { register int t; for (t=0;t<SIZE;t++) { if (*addr_info[t].name!='0') { printf("%sn",addr_info[t].name); printf("%sn",addr_info[t].street); printf("%sn",addr_info[t].city); printf("%sn",addr_info[t].state); printf("%dnn",addr_info[t].zip); getchar();}}} Listingul complet al programului va fi: # include <stdio.h> # include <ctype.h> # include <string.h> 112
  • 119. # define SIZE 100 struct addr { char name[20]; char street[30]; char city[15]; char state[10]; unsigned int zip; } addr_info[SIZE]; FILE *fp; void init_list(),enter(),save(),load(); void display(),exit(); char menu(); void main() { char choice; init_list(); for (;;) { choice = menu(); switch (choice) { case 'e' : enter(); break; case 'd' : display(); break; case 's' : save(); break; case 'l' : load(); break; case 'q' : exit(); }}} /* Functia init_list() */ void init_list() { register int t; for (t = 0; t < SIZE; t++) *addr_info[t].name = '0'; } /* Functia menu() */ char menu() { char s[5],ch; do { printf ("(E)ntern"); printf ("(D)isplayn"); printf ("(L)oadn"); printf ("(S)aven"); printf ("(Q)uitn"); printf (" Alegeti optiunea: "); gets(s); ch=s[0]; } while (!strrchr("edlsq",ch)); return tolower(ch); } /* Functia enter() */ void enter() { 113
  • 120. 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 filen "); 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 filen "); 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 errorn"); } /* Functia display() */ void display(){ register int t; printf("n%20s","Name"); printf("%30s","Street"); printf("%15s","City"); printf("%10s","State"); 114
  • 121. printf("%5sn","Zip"); for (t=0;t<SIZE;t++) { if (*addr_info[t].name!='0') { printf("%20s",addr_info[t].name); printf("%30s",addr_info[t].street); printf("%15s",addr_info[t].city); printf("%10s",addr_info[t].state); printf("%5d",addr_info[t].zip); getchar();}}} 6.4.2. Introducerea structurilor în funcţii a) Introducerea elementelor unei structuri în funcţii Când un element al unei variabile tip structură este transmis (pasat) unei funcţii, de fapt este transmisă valoarea acelui element. Exemplu: struct struct1{ char x; int y; float z; char s[10]; } struct2; Modalitatea de a introduce fiecare element într-o funcţie este următoarea: func (struct2.x); /* se paseaza valoarea caracterului x */ func2 (struct2.y); /* se paseaza valoarea intregului y */ func3 (struct2.z); /* se paseaza valoarea reala a lui z */ func4 (struct2.s); /* se utilizeaza adresa sirului s */ func (struct2.s[2]); //se utilizeaza valoarea caracterului lui s[2] unde func(), func2(), func3(), func4() sunt numele unor funcţii. Pentru a transmite funcţiei func() adresele elementelor din structura struct2, se scrie astfel: func (&struct2.x); /* se paseaza adresa caracterului x */ func (&struct2.s[2]); /* se paseaza adresa caracterului s[2] */ b) Transmiterea unei întregi structuri unei funcţii Când o structură este utilizată ca argument al unei funcţii, limbajul C transmite funcţiei întreaga structură utilizând metoda 115
  • 122. standard a apelului prin valoare. Aceasta înseamnă că orice modificare asupra conţinutului structurii în interiorul funcţiei în care este apelată structura, nu afectează structura folosită ca argument. Ceea ce trebuie avut neapărat în vedere atunci când, ca parametru, se utilizează o structură este ca tipul argumentului să fie identic cu tipul parametrului. Exemplu: # include <stdio.h> void f1(); void main() { struct { int a,b; char ch; } arg; arg.a = 1000; f1(arg); /* se apeleaza functia f1() */ } void f1(param) /* se declara functia f1 */ struct { int x, y; char ch; } param; {printf ("%dn", param.x); } Acest program declară atât argumentul arg al lui f1, cât şi parametrul param ca având acelaşi tip de structură. Programul va afişa pe ecran valoarea 1000. Pentru a face programele mai compacte se procedează la definirea structurii ca variabilă globală şi apoi la utilizarea numelui acesteia pentru a declara diversele variabile structură. Exemplu: # include <stdio.h> void f1(); /* Se defineste un tip structura */ struct struct_tip { int a, b; char ch;}; void main() { struct struct_tip arg; /* se declara structura arg */ arg.a = 1000; f1(arg);} void f1(struct struct_tip param) /* se declara functia f1() */ {printf ("%dn",param.a); } 116
  • 123. Pentru exemplificare, propunem următorul program: - se preia de la tastatura un prim şir de numere întregi - se preia de la tastatura un al doilea şir de numere întregi - se concatenează cele două şiruri - şirul rezultat se sortează în ordine crescătoare, având în vedere ca primele poziţii să fie ocupate de numerele pare din şir sortate crescător după care să urmeze numerele impare din şir sortate tot crescător. Pentru a realiza acest program, vom utiliza nu variabile de tip şir ci o structură care să conţină ca prim element şirul respectiv iar cel de-al doilea element să fie lungimea acestuia. Se vor construi funcţii care să realizeze citirea şirurilor de la tastatură, scrierea lor pe display, respectiv să le ordoneze şi să le sorteze după paritate. Toate aceste funcţii comunică prin intermediul unei variabile globale de tip structură şi a mai multor variabile locale de tip structură. Programul în C este prezentat în continuare: # include <stdio.h> // definim prototipurile functiilor utilizate struct sir_lung cit_sir(); struct sir_lung concat_sir(); struct sir_lung ord_sir(); struct sir_lung par_sir(); struct sir_lung impar_sir(); void tip_sir(); /* se defineste structura sir+lungime si variabila globala sir */ struct sir_lung { int sir[80]; int lung;} sir; // programul principal void main(){ struct sir_lung sir_init,sir_ord,sir_par,sir_impar; sir_init=cit_sir(); getchar(); sir_ord=cit_sir(); sir_init=concat_sir(sir_init,sir_ord); sir_par=par_sir(sir_init); sir_par=ord_sir(sir_par); sir_impar=impar_sir(sir_init); sir_impar=ord_sir(sir_impar); sir_ord=concat_sir(sir_par,sir_impar); tip_sir(sir_init); tip_sir(sir_ord);} // se definesc functiile 117
  • 124. 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;} Din funcţia cit_sir() se poate ieşi prin tastarea oricarui caracter nenumeric. Se observă cum aceasta funcţie lucrează direct cu variabila 118
  • 125. globală şir iar celelalte cu variabile locale proprii care apoi sunt asignate variabilei globale şir la returnare. Rulând programul vom obţine rezultatele: 1 2 17 -3 6 -4; -9 -2 31 2 -7; 1 2 17 -3 6 -4 -9 -2 31 2 -7 -4 -2 2 2 6 -9 -7 -3 1 17 31 6.4.3. Tablouri şi structuri în structuri Elementele unei structuri pot fi simple (int, char etc.) sau complexe (tablouri de elemente de diferite tipuri, structuri). Exemplu: Considerăm structura : struct x { int a[10][10]; /* tablou de 10x10 intregi */ float b;} y; De exemplu, referirea întregului a[3][7] se face prin: y.a[3][7] Când o structură este un element al altei structuri, structurile se numesc încuibate (nested, incluse). Exemplu: struct sal { struct addr adresa; float salariu; } muncitor; Se observă că elementele variabilei-structură "adresa" sunt incluse în structura "sal". Aici "addr" este structura definită anterior. Exemplul anterior defineşte structura "sal" cu 2 elemente: primul este o structură de tip "addr" care conţine adresa salariatului; al doilea element este salariul săptămânal al acestuia. Instrucţiunea următoare va atribui codul 90178 variabilei "zip" din adresa muncitorului muncitor.adresa.zip = 90178; Se observă că elementele fiecărei structuri sunt referite de la exterior către interior, respectiv de la stânga la dreapta. 6.5. Uniuni În C o uniune este o zonă de memorie utilizată de mai multe variabile ce pot fi diferite ca tip. Definitia uniunilor este similară celei a structurilor. Exemplu: 119
  • 126. union tip_u { int i; char ch;}; O variabilă de acest tip poate fi declarată fie prin plasarea numelui său la sfîrşitul acestei definiţii, fie utilizând o instrucţiune de declarare separată. De exemplu, instrucţiunea : union tip_u exuniune; declară variabila-uniune "exuniune" în care atât întregul i, cât şi caracterul ch ocupă aceeaşi zonă de memorie (cu toate ca int ocupa 4 octeţi, iar char numai un octet). Când se declară o variabilă union compilatorul C rezervă o zonă de memorie suficient de lungă capabilă să preia variabila cu lungimea cea mai mare. Pentru a accesa elementele unei uniuni se utilizează aceeaşi sintaxă folosită la structuri (operatorii punct şi " -> "). Când un element al unei uniuni se adresează direct, se utilizează operatorul ".", iar când elementul se adresează printr-un pointer, se foloseşte operatorul "-> ". De exemplu, pentru a atribui elementul întreg i al uniunii anterioare "exuniune" valoarea 10, se va scrie: exuniune.i = 10; În exemplul următor, programul transferă un pointer la "exuniune" unei funcţii : func1(union tip_u *un) { un -> i = 10; } /* se atribuie valoarea 10 intregului i al uniunii exuniune */ In C, uniunile sunt des utilizate când sunt necesare conversii de tip. De exemplu, funcţia standard putw() ce realizează scrierea binară a unui întreg într-un fişier de pe disc, poate fi scrisă folosind o uniune. Pentru aceasta, mai întâi se crează o uniune ce cuprinde un întreg şi un vector de două caractere, astfel: union pw { int i; char ch[2];}; Atunci, structura lui putw(), utilizând aceasta uniune este : putw(word, fp) union pw word; /* se declara uniunea word */ FILE *fp { putc(word ->ch[0]); // se scrie primul caracter putc(word->ch[1]); // se scrie al doilea caracter } 120
  • 127. 6.6. Enumerări O enumerare este o mulţime de constante întregi ce pot lua toate valorile unei variabile de un anumit tip. Enumerările se definesc în acelaşi mod ca şi structurile, utilizând cuvântul cheie enum ce semnalează începutul unui tip enumerare. Forma generală de definire a unei enumerări este: enum nume_tip_enum { lista_enumeratori} lista_variabile; unde atât nume_tip_enum, cât şi lista_variabile sunt opţionale. Exemplu: Următorul fragment defineşte o enumerare numită "bancnota" cu care apoi se declară o enumerare numită "bani" având acest tip: enum bancnota {suta,douasute,cincisute,mie,cincimii,zecemii}; enum bancnota bani; Dându-se această definiţie şi declaraţie, sunt valabile urmatoarele instructiuni: bani = mie; if (5*bani == cincimii) printf("Sunt 5000 lei.n"); Trebuie precizat că fiecare enumerator este caracterizat printr-o valoare întreaga. Fără nici o altă iniţializare, valoarea primului enumerator este 0, a celui de-al doilea este 1, s.a.m.d. De aceea, instrucţiunea: printf ("%d %d, suta, mie); va afisa pe ecran: 0 3 Se pot specifica valorile unuia sau mai multor simboluri folosind iniţializatori. De exemplu: enum bancnota {suta, douasute, cincisute, mie=1000, cincimii, zecemii}; face ca simbolurile din enumerarea bancnota să aibă valorile: suta = 0 douasute = 1 cincisute = 2 mie = 1000 cincimii = 1001 zecemii = 1002 Urmatorul fragment de program nu funcţionează, deoarece "bani" este un întreg şi nu un şir : bani = cincimii; printf ("%s", bani); Nici acest program nu functionează: gets (s); strcpy (bani, s); 121
  • 128. Pentru a afişa tipurile bancnotelor conţinute în enumerarea "bani", se va scrie: switch (bani) { case suta: printf (" suta "); break; case douasute: printf (" douasute "); break; . . . . . . . . . . . . . . . . . . . . . . . . . . case zecemii: printf (" zecemii "); } Uneori pentru a translata valoarea unui enumerator în şirul corespunzător, se poate declara un tablou de şiruri şi utiliza valoarea enumeratorului ca index. De exemplu, următorul fragment va afişa şirul corespunzător: char name[ ][20] = { "suta", "douasute", "cincisute", "mie", "cincimii" "zecemii" }; . . . . . . printf ("%s", name[bani]); Fragmentul anterior va funcţiona numai dacă nu se realizează iniţializarea simbolurilor, deoarece indexarea şirurilor începe cu zero. Următorul program afişează numele bancnotelor: # include <stdio.h> enum bancnota { suta, douasute, cincisute,mie, cincimii,zecemii,cincizecimii,sutamii,cincisutemii}; char name[][20]= {"suta","douasute","cincisute","mie","douamii","cincimii" ,"zecemii","cincizecimii" "sutamii","cincisutemii"}; void main() { enum bancnota bani; for (bani = suta; bani <= cincisutemii; bani ++) printf ("%sn", name[bani]);} Dacă variabilei uniune y din exemplul următor i se aplică operatorul sizeof() vom găsi sizeof(y) = 8. # include <stdio.h> union { char ch; int i; double f; } y; void main() {printf("%dn",sizeof(y));} Deci compilatorul va reţine valoarea celei mai largi tipuri de date din uniune. 122
  • 129. Capitolul VII POINTERI Un pointer este o variabilă care păstrează adresa unui obiect de tip corespunzător. Forma generală pentru declararea unei variabile pointer este: tip * nume_variabila; unde tip poate fi oricare din tipurile de bază admise în C, iar nume_variabila este numele variabilei pointer. Tipul de bază al pointerului defineşte tipul variabilelor spre care indică pointerul. Variabila pointer este o variabilă de un tip special, aparte de tipurile char, int, float. Cuvântul cheie tip din declaraţia unui pointer se referă la tipul de dată spre care indică pointerul, nu la formatul în care se stochează efectiv o variabilă pointer în memorie. Formatul în care se stochează o variabilă pointer în memorie depinde de tipul de compilator care se foloseşte, deci depinde în mare măsură de tipul procesorului pentru care a fost proiectat compilatorul. O indicaţie despre formatul în care se stochează o variabilă pointer în memorie poate fi obţinută prin tipărirea conţinutului unei variabile pointer (o adresă) utilizând printf() cu formatul %p. Exemplu: char *p; /* pointer la caracter */ int *temps, *start; /* pointeri la intregi */ char *const q; /* pointer constant la caracter */ 7.1. Operatori pointer Există doi operatori pointer speciali * şi &: • Operatorul & este un operator unar care oferă (returnează) adresa unei variabile (adresa operandului său). • Operatorul * este complementarul lui &. Este un operator unar care returnează valoarea variabilei plasată la adresa care urmează după acest operator. Exemplu: # include <stdio.h> void main (void) { int *count_addr, count, val; count = 100; /* int count are valoarea 100 */ 123
  • 130. count_addr = &count; /*count_addr indica spre count. Aceasta instructiune plaseaza in count_addr adresa din memorie a variabilei count, adresa care nu are nici o legatura cu valoarea lui count */ val = count_addr; /* val preia valoarea de la adresa count_addr. Aceasta instructiune plaseaza valoarea lui count aflata la adresa count_addr în variabila val */ printf ("%d", val); /*Se va tipari numarul 100 */ } Spre exemplu, să considerăm porţiunea de program: short i, j; // i si j sunt ambele intregi scurti short *p // p este pointer la tip intreg scurt i = 123; p = &i; j = *p; Să presupunem că zona de stocare a celor trei variabile arată astfel: Ad s re a Me om ria i 12 0 0 j 12 2 0 p 12 4 0 După primele două atribuiri i = 123; p = &i; zona de stocare va arăta astfel: Ad s re a M m ria e o i 12 0 0 123 j 12 2 0 p 12 4 0 12 0 0 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ă: Ad s re a M m ria e o i 12 0 0 12 3 j 12 2 0 123 p 12 4 0 12 0 0 124
  • 131. 7.1.1. Importanţa tipului de bază Considerăm declaraţia: val = *count_addr; Se pune întrebarea: care va fi numărul de bytes ce va fi transferat variabilei val de la adresa indicată prin *count_addr. Sau, mai general, de unde ştie compilatorul câţi bytes să transfere în cazul oricărei asignări care utilizează pointeri. Răspunsul la aceste întrebări este acela că, tipul de bază al pointerului determină tipul datei spre care indică pointerul. Exemplu: /* Acest program nu lucreaza corect */ # include <stdio.h> void main (void) { float x = 10.12, y; short int *p; /* pointer la intreg */ p = &x; /* p preia adresa lui x */ y = *p; /* y preia valoarea de la adresa p */ printf ("x = %f y = %f",x,y); } Acest program nu va atribui valoarea lui x lui y, deoarece în program se declară p ca fiind pointer la întreg scurt şi compilatorul va transfera în y numai 2 bytes (corespunzători reprezentării unui întreg scurt) şi nu 4 bytes, corespunzători unui număr real în virgulă mobilă. 7.1.2. Expresii în care intervin pointeri În general, expresiile în care intervin pointeri respectă aceleaşi reguli ca orice alte expresii din limbajul C. • Atribuirea pointerilor Ca orice variabilă, un pointer poate fi folosit în membrul drept al unei instrucţiuni de asignare (atribuire), pentru atribuirea valorii unui pointer unui alt pointer. Exemplu: # include <stdio.h> void main (void) { int x; int *p1,*p2; /* pointeri la intregi */ p1 = &x; /* p1 indica spre x */ p2 = p1 /* p2 indica tot spre x */ printf ("p1 = %p p2 = %p", p1, p2); } /* Se afiseaza valoarea hexa a adresei lui x, nu valoarea lui x */ Se observă că în funcţia printf() tipărirea se face cu formatul %p care specifică faptul că variabilele din listă vor fi afişate ca adrese pointer. 125
  • 132. Din cele de mai sus se observă că operaţia fundamentală care se execută asupra unui pointer este indirectarea, ceea ce presupune referirea la un obiect indicat de acel pointer. Exemplu: char c1 = 'a'; char *p = &c1; /* p contine adresa lui c1 */ char c2 = *p; /*c2 preia valoarea de la adresa p*/ printf ("c1 = %c c2 = %c", c1, c2); Deci variabila indicată de p este c1, iar valoarea memorată în c1 este 'a', aşa încât valoarea lui *p atribuită lui c2 este 'a'. • Operaţii aritmetice efectuate asupra pointerilor  Utilizarea operatorilor de incrementare şi decrementare Fie secvenţa: int *p1; /* pointer la intreg */ p1++; De fiecare dată când se incrementează p1, acesta va indica spre următorul întreg. Astfel, dacă p1 = 2000, după efectuarea instrucţiunii p1++, acesta va fi p1 = 2004 (va indica spre următorul întreg).  După fiecare incrementare a unui pointer, acesta va indica spre următorul element al tipului său de bază.  După fiecare decrementare a unui pointer, acesta va indica spre elementul anterior. Valoarea pointerilor va fi crescută sau micşorată în concordanţă cu lungimea tipului datelor spre care aceştia indică, aşa cum se poate vedea în exemplul următor: char *ch = 3000; short int *i = 3000; ch 3000 i ch + 1 3001 ch + 2 3002 i+1 ch + 3 3003 ch + 4 3004 i+2 ch + 5 3005 ch + 6 3006 i+3 Memoria Cum valoarea indirectată de un pointer este o l-valoare, ea poate fi asignată şi incrementată ca orice altă variabilă. O l-valoare (left value) este un operand care poate fi plasat în stânga unei operaţii de atribuire. Verificaţi utilizarea pointerilor din programul următor: # include <stdio.h> 126
  • 133. void main(void) { short *pi, *pj, t; long *pl; double *pd; short i, j; i=1; j=2; t=3; printf("i= %d, j= %dn", i, j); pi=&i; pj=&j; printf("pi= %p, pj= %pn", pi, pj); *pj /= *pi+1; printf("*pi= %d *pj= %dn", *pi, *pj); *pj /= *pi+2; printf("*pi= %d *pj= %dn", *pi, *pj); printf("++pj= %p, ++*pj= %dn",++pj,++*pj); } În urma rulării sale, pe calculatoarele mai moderne obţinem rezultatul i= 1,j= 2 pi= 0065FDE0,pj= 0065FDDC *pi= 1 *pj= 1 *pi= 1 *pj= 0 ++pj= 0065FDDE,++*pj= 1  Utilizarea operatorilor de adunare şi de scădere La sau dintr-un pointer, se pot aduna sau scădea valori de tip întreg. Rezultatul este un pointer de acelaşi tip cu cel iniţial, indicând spre un alt element din tablou. De exemplu, p1 = p1 + 9; face ca p1 să indice spre al 9-lea element având tipul lui p1, considerând că elementul curent este indicat de p1. Evident că valoarea pointerului se va modifica corespunzător lungimii tipului datei indicată prin pointer. Exemplu: int *p1; /* Pointer la intreg */ p1 = p1 + 9; Dacă valoarea p1 = 3000, atunci p1 + 9 va avea valoarea: (valoarea lui p1)+9*sizeof(int)=3000+9*4=3036 Aceleaşi considerente sunt valabile în cazul în care un întreg este scăzut dintr-un pointer. Dacă doi pointeri de acelaşi tip sunt scăzuţi, rezultatul este un număr întreg cu semn care reprezintă deplasamentul dintre cei doi pointeri (pointerii la obiecte vecine diferă cu 1). În cazul tablourilor, dacă pointerul rezultat indică în afara tabloului, rezultatul este nedefinit. 127
  • 134. Dacă p indică spre ultimul membru dintr-un tablou, atunci (p+1) are valoare nedeterminată. Observaţii :  Nu se pot aduna sau scădea valori de tip float sau double la/sau dintr-un pointer.  Nu se pot efectua operaţii de înmulţire şi împărţire cu pointeri. Exemplu: Scăderea a doi pointeri este exemplificată în programul: # include <stdio.h> void main(){ int i=4, j; float x[] = {1,2,3,4,5,6,7,8,9}, *px; j = &x[i]-&x[i-2]; px = &x[4]+i; printf("%d %f %p %pn",j,*px,&x[4],px); }  Compararea pointerilor Doi pointeri de acelaşi tip se pot compara printr-o expresie relaţională, astfel: dacă p şi q sunt doi pointeri, atunci instrucţiunile: if (p < q) printf (“ p indica spre o adresa mai mica decit q n “); sunt corecte. Compararea pointerilor se utilizează când doi sau mai mulţi pointeri indică spre acelaşi obiect comun. Exemplu: Un exemplu interesant de utilizare a pointerilor constă în examinarea conţinutului locaţiilor de memorie ale calculatorului. /* Programul afiseaza continutul locatiilor de memorie de la o adresa specificata */ # include <stdio.h> # include <stdlib.h> dump (start); void main (void) { /* start = adresa de inceput */ unsigned long int start; printf (“Introduceti adresa de start: “); scanf (“ %lu “, &start); dump (start); /* Se apeleaza functia dump () */ } dump (start) /* Se defineste functia dump() */ unsigned long int start; {char far *p; int t; p = (char far *) start; /*Conversie la un pointer*/ for (t = 0; ; t++, p++) { if (!(t%16)) printf ("/n"); printf ("%2X ", *p); 128
  • 135. /*Afiseaza in hexazecimal continutul locatiei de memorie adresata cu *p*/ if (kbhit()) return;} // Stop cand se apasa orice tasta } Programul introduce cuvântul cheie far care permite pointerului p să indice locaţii de memorie care nu sunt în acelaşi segment de memorie în care este plasat programul. Formatul %lu din scanf() înseamnă: "citeşte un unsigned long int". Formatul %X din printf() ordonă calculatorului să afişeze argumentul în hexazecimal cu litere mari (Formatul %x afişează în hexazecimal cu litere mici). Programul foloseşte explicit un şablon de tip pentru transferul valorii întregului fără semn, start, pe care îl introducem de la tastatură, într-un pointer. Funcţia kbhit() returnează ADEVARAT dacă se apasă o tastă, altfel returnează FALS.  Utilizarea pointerilor ca parametri formali ai funcţiilor În exemplele de până acum, s-au folosit funcţii C care atunci când erau apelate, parametrii acestor funcţii erau (întotdeauna) actualizaţi prin pasarea valorii fiecărui argument. Acest fapt ne îndreptăţeşte să numim C-ul ca un limbaj de apel prin valoare. Există totuşi o excepţie de la această regulă atunci când argumentul este un tablou. Această excepţie este explicată, pe scurt, prin faptul că valoarea unui nume al unui tablou (vector, matrice etc.) neindexate este adresa primului său element. Deci, în cazul unui argument de tip tablou, ceea ce se pasează unei funcţii este o anumită adresă. Folosind variabile pointer se pot pasa adrese pentru orice tip de date. Spre exemplu, funcţia scanf() acceptă un parametru de tip pointer (adresă): scanf(“%1f“,&x); Ceea ce este important de evidenţiat este cum anume se poate scrie o funcţie care să accepte ca parametri formali sau ca argumente pointeri ?. Funcţia care recepţionează o adresă ca argument va trebui să declare acest parametru ca o variabilă pointer. De exemplu, funcţia swap() care va interschimba valorile a doi întregi poate fi declarată astfel: # include <stdio.h> void swap();/*Prototipul functiei swap()*/ void main(void) { int i,j; i=1; j=2; printf("i= %d j= %dn", i, j); swap(&i,&j); /* Apelul functiei */ 129
  • 136. printf("i= %d j= %dn", i, j); } void swap(int *pi, int *pj) { short t; t = *pi; *pi = *pj; *pj = t; } 7.2. Pointeri şi tablouri Între pointeri şi tablouri există o strânsă legătură în limbajul C. Există însă o mare deosebire între tablouri şi pointeri pe care trebuie să o avem mereu în vedere. Un tablou constă întotdeauna dintr-o mare cantitate de memorie, îndeajuns de mare pentru a reţine toţi octeţii corepunzători tuturor elementelor tabloului. Astfel, tabloul q declarat ca short q[100]; rezervă 2x100 octeţi de memorie iar int q[100] rezervă 4x100 octeţi de memorie. Pe de altă parte, pentru un pointer se alocă o zonă de memorie redusă la numai câţiva octeţi necesari pentru a reţine o adresă de memorie. De fapt, zona de memorie alocată unui pointer depinde de tipul pointerului (tipul datelor spre care punctează acesata) şi va fi de numai câţiva octeţi ( în exemplele anterioare - 4 octeţi). Considerăm secvenţa: char sir[80]; char *p1; p1 = sir; Acest fragment face ca p1 să adreseze primul element al tabloului sir. În C numele unui tablou fără indici este adresa de start a tabloului. De fapt, numele tabloului este un pointer la tablou. Ca o concluzie, un pointer declarat sau numele unui tablou fără indici reprezintă adrese, pe când numele unui tablou cu indici se referă la valorile stocate în acea poziţie în tablou. Deoarece indicele inferior al oricărui vector este zero, pentru a referi al 5-lea element al şirului sir, putem scrie sir[4] sau *(p1+4) sau p1[4]. Deci pointerul p1 indică spre primul element al lui sir. Pentru a avea acces la elementul unui tablou, în limbajul C, se folosesc 2 metode : 1. utilizarea indicilor tabloului; 2. utilizarea pointerilor . Deoarece a doua metodă este mai rapidă, în programarea în C, de regulă, se utilizează această metodă. 130
  • 137. Pentru a vedea modul de utilizare a celor două metode, considerăm un program care tipăreşte cu litere mici un şir de caractere introdus de la tastatură cu litere mari: Exemplu: Versiunea cu indici void main (void) { char sir[80]; int i; printf ("Introduceti un sir de caractere scrise cu litere mari: n"); gets (sir); printf ("Acesta este sirul in litere mici: n"); for(i=0;sir[i];i++) printf("%c", tolower(str[i])); } Exemplu: Versiunea cu pointeri void main (void) { char sir[80] , *p; printf ("Introduceti un sir de caractere scrise cu litere mari: n"); gets (sir); printf (" Acesta este sirul in litere mici: n"); p = sir; /* p preia adresa de inceput a sirului */ while (*p) printf (" %c ", tolower(*p++)); } Pointerii sunt rapizi şi uşor de utilizat când se doreşte referirea elementelor unui tablou în ordine strict crescătoare sau strict descrescătoare. Dacă însă se doreşte referirea aleatoare a elementelor unui tablou, atunci indexarea tabloului este cel mai simplu şi sigur de utilizat. 7.2.1. Indexarea pointerilor În C, dacă p este un pointer, iar i este întreg, p[i] este identic cu *(p+i). Dacă avem declaraţiile: short q[100]; short *pq atunci sunt permise şi posibile următoarele declaraţii: Varianta cu Varianta cu Descriere tablou pointeri pq=&q[0] pq=&q[0] Pointerul pq indică adresa pq=q pq=q primului element al tabloului q q[n] pq[n] pq[n] înseamnă acelaşi lucru cu *(pq+n) *(pq+n) În C, dacă se pune un index unui pointer, ca în pq[n], atunci se consideră această utilizare echivalentă cu *(pq+n). Cu alte cuvinte, 131
  • 138. orice referire la pq[n] înseamnă acelaşi lucru cu valoarea de la adresa (pq+n). Exemplu: Programul următor realizează tipărirea pe ecran a numerelor cuprinse între 1 şi 10. void main (void) { int v[10]={1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; int *p, i; p = v; /* p indica spre v */ for (i=0;i<10;i++) printf ("%d", p[i]); } Utilizarea constantelor şir în locul poinetrilor la caractere este posibilă dar nu este uzuală. Exemplu: # include <stdio.h> void main(){ char *sir = "To be or not to be", *altsir; printf("%sn", "That don't impress me much"+5); printf("%cn",*("12345"+3)); printf("%cn","12345"[1]); puts("stringn"); altsir = "American pie"; printf("sir = %snaltsir = %sn",sir,altsir); } Exemplu de utilizare a stivei. Stiva este o listă cu acces tip LIFO (last in - first out). Pentru a avea acces la elementele stivei, se utilizează doua rutine: push() şi pop(). Calculatorul păstrează stiva într-un tablou, iar rutinele push() şi pop() realizează accesul la elementele stivei utilizând pointeri. Pentru memorarea vârfului stivei, utilizăm variabila tos (top of stack). Considerăm că folosim numai numere de tip întreg. În programul main(), rutinele push() şi pop() sunt utilizate astfel: push() citeşte numerele introduse de la tastatură şi le memorează în stivă dacă acestea sunt nenule. Când se introduce un zero, pop() extrage valoarea din stivă. # include <stdlib.h> # include <stdio.h> void push(); /* Prototipul functiei push() */ int pop(); /* Prototipul functiei pop() */ // Se rezerva pentru stiva 50x4 = 200 locatii int stack[50]; int *p1, *tos; void main(void) { int value; p1 = stack; /* p1 preia adresa bazei stivei */ tos = p1; /* tos va contine varful stivei */ do { scanf ("%d", &value); 132
  • 139. if (value != 0) push (value); else printf ("Nr. extras din stiva este %d n", pop()); } while (value != -1); } void push(int i) /* Functia push() */ { p1++; if (p1==(tos+50)) { printf("n Stiva este plina."); exit (0);} *p1 = i; printf("introdus in stivan"); } int pop() { if (p1 == tos) { printf ("n Stiva este complet goala."); exit (0); } p1 --; return *(p1+1); } 7.2.2. Pointeri şi şiruri Deoarece numele unui tablou fără indici este un pointer la primul element al tabloului, pentru implementarea unor funcţii care manipulează şiruri, se pot utiliza pointeri. §tim că funcţia strcmp(s1, s2) realizează compararea şirurilor s1 şi s2 şi întoarce 0 dacă s1 = s2, o valoare negativă, dacă s1 < s2 şi o valoare pozitivă, dacă s1 > s2. Exemplu: Prezentăm o variantă de scriere a funcţiei strcmp(s1,s2) char *s1, *s2; { while (*s1) if (*s1 - *s2) /* Daca valoarea punctata de s1 este diferita de valoarea punctata de s2, */ return *s1-*s2; /* Returneaza diferenta */ else {s1++; s2++;} return '0'; //Se returneaza 0 in caz de egalitate } Reamintim că un şir în C se termină cu caracterul NULL. De aceea, instructiunea while(*s1) rămâne adevărată până când se întâlneşte caracterul NULL, care este o valoare falsă. Dacă într-o expresie se utilizează un şir constant, calculatorul tratează constanta ca pointer la primul caracter al şirului. Exemplu: Programul următor afişează pe ecran mesajul " Acest program funcţionează ": # include <stdio.h> void main (void) { char *s; s = " Acest program functioneaza "; printf (s); } 133
  • 140. 7.2.3. Preluarea adresei unui element al unui tablou Până acum s-a văzut că un pointer poate să adreseze primul element al unui tablou. Este posibil să se adreseze orice element al unui tablou aplicând operatorul & unui tablou indexat. De exemplu, p = &x[2]; plasează adresa celui de-al 3-lea element al vectorului x în pointerul p. Un domeniu în care această practică este esenţiala constă în găsirea unui subşir într-un şir dat. Exemplu: Programul următor afişează ultima parte a unui şir introdus de la tastatură, din punctul în care se întâlneşte primul spaţiu: # include <stdio.h> void main (void) { char s[80]; char *p; int i; printf (" Introduceti un sir : n "); gets (s); /* Gaseste primul spatiu sau sfarsitul sirului */ for (i = 0; s[i] && s[i] != ' '; i++) p = & s[i+1]; printf (p); } Dacă p indică spre un spaţiu, programul va afişa spaţiul şi apoi subşirul rămas. Dacă în şirul introdus nu este nici un spaţiu, p indică spre sfârşitul şirului şi atunci nu se va afişa nimic. De exemplu, dacă se introduce “my friend“, atunci printf() afişează mai întâi un spaţiu şi apoi “friend“. 7.2.4. Tablouri de pointeri Putem construi tablouri de pointeri în aceeaşi manieră în care se definesc alte tipuri de date. Exemplu: int *x[10]; // Vector de 10 pointeri la intregi char *p[20]; // Vector de 20 pointeri la caracter Pentru atribuirea unei variabile întregi, var, celui de al treilea element al tabloului de pointeri *x[10], se va scrie: x[2] = &var; Pentru găsirea valorii lui var, se va scrie: y = *x[2]; //Valoarea lui var este atribuita lui y Exemplu: Tablourile de pointeri pot fi utilizate în construirea mesajelor de eroare, astfel: 134
  • 141. char *err[ ] = { " Cannot open file n ", " Read error n ", " Write error n " }; selmes (int num) /* Selecteaza un mesaj */ { scanf("%d", &num); /* Se introduce un numar de la tastatura */ printf("%s", err[num]); } Funcţia printf() este apelată din funcţia selmes(). Aceasta va afişa mesajul de eroare indexat prin numărul de eroare num, care este pasat ca argument funcţiei selmes(). De exemplu, dacă se introduce 2, atunci se va afişa mesajul: Write error Atentie !. Trebuie facută distincţia între: int *v[10]; // Tablou de 10 pointeri la intregi int (*v)[10]; // Pointer la un tablou de 10 intregi Pentru aceasta trebuie ţinut cont de faptul că * este un operator prefixat, iar [] şi () sunt operatori postfixaţi. Deoarece prioritatea operatorilor postfixaţi este mai mare decât cea a operatorilor prefixaţi, atunci când se doreşte schimbarea priorităţii, trebuie folosite paranteze. 7.2.5. Pointeri la pointeri Un tablou de pointeri este ceea ce numim pointeri la pointeri. Conceptul de tablou de pointeri este simplu, deoarece indexarea tabloului conduce la clarificarea semnificaţiei lui. Un pointer la un pointer este o formă de indirectare multiplă sau un lanţ de pointeri. În cazul unei indirectari simple, valoarea pointerului este adresa variabilei care conţine valoarea dorită: Pointer Variabilă Adresă ---------> Valoare În cazul unui pointer la pointer, primul pointer conţine adresa celui de-al doilea pointer, care indică spre variabila ce conţine valoarea dorită: Pointer Pointer Variabilă Adresă --------- Adresă --------- Valoare > > Declararea indirectărilor multiple se face sub forma: 135
  • 142. /* cpp este un pointer la pointer la caracter */ char **cpp; /* newbalance este un pointer la pointer la float */ float **newbalance; Pentru a avea acces la o valoare indirectată printr-un pointer la pointer este necesară, de asemenea, utilizarea operatorului * de două ori, aşa cum se vede în exemplul următor: # include <stdio.h> void main (void) { int x, *p, **q; x = 10; p = &x; /* p preia adresa lui x */ q = &p; /* q preia adresa lui p */ printf(" %d ", **q);/*Se afiseaza valoarea lui x*/ } 7.2.6. Iniţializarea pointerilor Secvenţa: char *p; char s[] = "Hello world n "; p = s; /* p indica spre s */ este echivalentă cu: char *p = "Hello world n"; Într-un program, p din ultima declaraţie poate fi utilizat ca orice alt şir. Astfel, programul următor este corect: # include <stdio.h> char *p = " Hello world n "; void main (void) { register int t; /*Se tipareste sirul in ordine directa si inversa*/ printf (p); for(t = strlen(p)-1; t > -1; t--) printf("%c",p[t]); } Observaţie: Neiniţializarea pointerilor sau iniţializarea greşită a acestora, poate duce la erori care, în cazul programelor de dimensiuni mari, sunt foarte greu de depistat şi pot avea urmări catastrofale. Exemplu: Considerăm programul: # include <stdio.h> void main(void) { int x, *p; x = 10; *p = x; printf ("%dn", *p); } 136
  • 143. Acest program atribuie valoarea 10 anumitor locaţii de memorie necunoscute. Programul nu va oferi niciodată o valoare pointerului p dar va tipări valoarea lui x. Exemplu: Considerăm acum următorul program: # include <stdio.h> void main (void) { int x, *p; x = 10; p = x; printf("%d",*p); } Funcţia printf() nu va afişa niciodată valoarea lui x (care este 10), dar va tipări o valoare necunoscută. Aceasta datorită atribuirii greşite: p = x; Instrucţiunea atribuie valoarea 10 pointerului p, care se presupune că reprezintă o adresă şi nu o valoare. Pentru a corecta programul, trebuie scris: p = &x; 7.2.7. Alocarea dinamică a memoriei Există două metode principale prin care un program C poate memora informaţii în memoria principală a calculatorului. • Prima metodă foloseşte variabilele globale şi locale. În cazul variabilelor globale, memoria ce li se alocă este fixă pe tot timpul execuţiei programului. Pentru variabilele locale, programul alocă memorie în spaţiul stivei, în timpul execuţiei programului. Deşi variabilele locale sunt eficient de implementat, în C, de multe ori, utilizarea acestora, necesită cunoaşterea în avans a cantităţii de memorie necesare în fiecare situaţie. H h ig Stiva Memorie liberá pentru alocare (heap) Variabile globale (statice) Low Program M m ria e o 137
  • 144. • 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 memoriei libere numită heap, plasată între programul util şi memoria sa permanentă şi stivă. Se observă că stiva creşte în jos, iar dimensiunea acesteia depinde de program. Un program cu multe funcţii recursive va folosi mult mai intens stiva în comparaţie cu un program ce nu utilizeaza funcţii recursive, aceasta deoarece adresele de retur şi variabilele locale corespunzătoare acestor funcţii sunt salvate în stivă. Funcţiile malloc() şi free() Aceste funcţii formează sistemul de alocare dinamică a memoriei în C şi fac parte din fisierul antet <stdlib.h>. Acestea lucrează împreună şi utilizează zona de memorie liberă plasată între codul program şi memoria sa permanentă (fixă) şi vârful stivei, în scopul stabilirii şi menţinerii unei liste a variabilelor memorate. De fiecare dată când se face o cerere de memorie, funcţia malloc() alocă o parte din memoria rămasă liberă. De fiecare dată când se face un apel de eliberare a memoriei, funcţia free() eliberează memorie sistemului. Declararea funcţiei malloc() se face sub forma: void *malloc (int numar_de_bytes); Aceasta întoarce un pointer de tip void, ceea ce înseamnă că trebuie utilizat un şablon explicit de tip atunci când pointerul returnat de malloc() se atribuie unui pointer de un alt tip. Dacă apelul lui malloc() se execută cu succes, malloc() va returna un pointer la primul byte al zonei de memorie din heap ce a fost alocată. Dacă nu este suficientă memorie pentru a satisfce cererea malloc(), apare un eşec şi malloc() returnează NULL. Pentru determinarea exactă a numărului de bytes necesari fiecărui tip de date, se poate folosi operatorul sizeof(). Prin aceasta, programele pot deveni portabile pe o varietate de sisteme. Funcţia free() returnează sistemului memoria alocată anterior cu malloc(). După eliberarea memoriei cu free(), aceasta se poate reutiliza folosind un apel malloc(). Declararea funcţiei free() se realizează sub forma: free(void *p); 138
  • 145. Funcţia free() eliberează spaţiul indicat de p şi nu face nimic dacă p este NULL. Parametrul actual p trebuie să fie un pointer la un spaţiu alocat anterior cu malloc(), calloc() sau realloc(). Exemplu: Următorul program va aloca memorie pentru 40 de întregi, va afişa valoarea acestora, după care eliberează zona, utilizând free(): # include <stdio.h> # include <stdlib.h> void main(void) { int t, *p; p = (int *) malloc(40*sizeof(int)); if (!p) printf("Out of memory n"); //Verificati neaparat daca p este un pointer corect else { for (t=0; t<40; ++t) *(p + t) = t; for (t=0; t < 40; ++t) printf("%d", *(p + t)); free(p); } } Funcţiile calloc() şi realloc() Funcţia calloc() alocă un bloc de n zone de memorie, fiecare de dim octeţi şi setează la 0 zonele de memorie; funcţia returnează un pointer la acel bloc (adresa primului octet din bloc). Declararea funcţiei se face cu: void *calloc(unsigned int n, unsigned int dim); Funcţia realloc() primeşte un pointer la un bloc de memorie alocat în prealabil (declarat pointer de tip void) şi redimensionează zona alocată la dim octeţi (dacă este nevoie, se copiază vechiul conţinut într-o altă zonă de memorie). Declararea funcţiei se face cu: void *realloc(void *ptr, unsigned int dim); 7.2.8. Pointeri la structuri Limbajul C recunoaşte pointerii la structuri în acelaşi mod în care se recunoaşte pointerii la orice alt tip de variabilă. Declararea unui pointer la structură se face plasând operatorul * în faţa numelui unei variabile structură. De exemplu, pentru structura addr definită mai înainte, următoarea instrucţiune declară pe addr_pointer ca pointer la o dată de acest tip : struct addr *addr_pointer; O utilizare importantă a pointerilor la structură constă în realizarea apelului prin adresă într-o funcţie. 139
  • 146. Când unei funcţii i se transmite ca argument un pointer la o structură, calculatorul va salva şi va reface din stivă numai adresa structurii, conducând astfel la cresterea vitezei de executare a programului. Pentru a găsi adresa unei variabile structură, se plasează operatorul & înaintea numelui variabilei structură. De exemplu, dându-se următorul fragment : struct balanta{ float balance; char name[80]; } person; struct balanta *p; /* se declara un pointer la structura */ atunci: p = &person; plasează adresa lui person în pointerul p. Pentru a referi elementul balance, se va scrie: (*p).balance Deoarece operatorul punct are prioritate mai mare decât operatorul *, pentru o referire corectă a elementelor unei structuri utilizând pointerii sunt necesare paranteze. Actualmente, pentru referirea unui element al unei variabile structură dându-se un pointer la acea variabilă, există două metode: Prima metodă utilizează referirea explicită a pointerului nume- structură şi este considerată o metoda învechită (obsolete), iar a doua metodă, modernă, utilizează operatorul săgeată -> (minus urmat de mai mare). Exemplu: Pentru a vedea cum se utilizează un pointer-struct, examinăm următorul program care afişează ora, minutul şi secunda utilizând un timer software. # include <stdio.h> void actualizeaza(); void afiseaza(), delay(); struct tm { /* se defineste structura tm */ int ore; int minute; int secunde;}; void main() {struct tm time; // Structura time de tip tm time.ore = 0; time.minute = 0; time.secunde = 0; for (;;) { 140
  • 147. actualizeaza (&time); afiseaza (&time); }} void actualizeaza(t) /*Versiunea 1- referirea explicita prin pointeri */ struct tm *t; { (*t).secunde ++; if ((*t).secunde == 60) { (*t).secunde = 0; (*t).minute ++; } if ((*t).minute == 60) { (*t).minute = 0; (*t).ore ++;} if ((*t).ore == 24) (*t).ore = 0; delay();} void afiseaza(t) // Se defineste functia afiseaza() struct tm *t; { printf ("%d : ", (*t).ore); printf ("%d : ", (*t).minute); printf ("%d ", (*t).secunde); printf ("n");} void delay() /* Se defineste functia delay() */ { long int t; for (t = 1;t<140000000;++t);} Pentru ajustarea duratei întârzierii se poate modifica valoarea contorului t al buclei. Se vede ca programul defineşte o structură globală numită tm, dar nu declară variabilele. In interiorul funcţiei main() se declară structura "time" şi se iniţializează cu 00:00:00. Programul transmite adresa lui time funcţiilor actualizeaza() şi afiseaza(). În ambele funcţii, argumentul acestora este declarat a fi un pointer-structură de tip "tm". Referirea fiecărui element se face prin intermediul acestui pointer. Elementele unei structuri pot fi referite utilizând în locul operatorului ".", operatorul "->". De exemplu : (*t).ore este echivalent cu t -> ore Atunci programul de mai sus se poate rescrie sub forma: # include <stdio.h> void actualizeaza(); void afiseaza(), delay(); struct tm { /* se defineste structura tm */ int ore; int minute; int secunde;}; 141
  • 148. void main() {struct tm time; // Declara structura time de tip tm time.ore = 0; time.minute = 0; time.secunde = 0; for (;;) { actualizeaza (&time); afiseaza (&time); }} void actualizeaza(t) /*Versiunea 1- referirea explicita prin pointeri */ struct tm *t; { t->secunde ++; if (t->secunde == 60) { t->secunde = 0; t->minute ++; } if (t->minute == 60) { t->minute = 0; t->ore ++;} if (t->ore == 24) t->ore = 0; delay();} void afiseaza(t) // Se defineste functia afiseaza() struct tm *t; { printf ("%d : ", t->ore); printf ("%d : ", t->minute); printf ("%d ", t->secunde); printf ("n");} void delay() /* Se defineşte funcţia delay() */ { long int t; for (t = 1;t<140000000;++t);} 7.2.9. Structuri dinamice liniare de tip listă Structura a fost introdusă ca fiind o grupă (colecţie) ordonată de date care pot fi de tipuri diferite şi care au anumite legături logice între ele. Adesea, această grupă de date se repetă de mai multe ori. Se ajunge astfel la noţiunea de tablou, ale cărui elemente sunt fiecare câte o structură. Tabloul de date definit în acest fel este şi el de acest tip nou, pe care îl mai numim şi tip structurat. După cum s-a remarcat, în exemplele de până acum am folosit structuri de tip static. Static se referă la faptul că tablourile de structuri au dimensiuni predefinite. Termenul structuri de date statice exprimă ideea că putem modifica cu uşurinţă valorile componentelor dar este foarte dificil să mărim numărul lor peste limita maximă declarată 142
  • 149. înainte de compilare. Mai mult, prin ştergerea unor elemente structură dintr-un tablou de structuri obţinem goluri în tablou, pe care le putem umple numai printr-o gestiune foarte precisă a poziţiilor din tablou. Folosind pointeri la tabloul de structuri, este foarte posibil să indicăm spre un element care a fost şters. Dacă dorim o reprezentare contiguă în memorie, va trebui să compactăm (sau să defragmentăm) tabloul la fiecare ştergere a unui element de tip structură. Mai mult, dacă dorim să schimbăm ordinea în care s-au stocat elementele din tablou sau să inserăm într-o poziţie intermediară un element nou, aceaste operaţii devin foarte anevoioase. Într-un exemplu anterior am folosit secvenţa # define SIZE 100 struct addr { char name[20]; char street[30]; char city[15]; char state[10]; unsigned int zip; } addr_info[SIZE]; Rezultă că am definit un tablou static cu 100 de elemente, cu numele addr_info, la care fiecare element este o structură cu şablonul addr. Dacă în această aplicaţie, chiar în timpul execuţiei programului, constatăm că avem nevoie de mai mult de 100 de rezervări de memorie, nu există nici o posibilitate de a mări tabloul fără a modifică şi apoi recompila sursa programului. Tabloul trebuie redeclarat cu o dimansiune mai mare (în cazul nostru prin #define SIZE 200, de exemplu), apoi se recompilează programul şi abia apoi se execută cu succes. Acest lucru prezentă două inconveniente (vezi [Mocanu, 2001]): 1- Execuţia şi obţinerea rezultatelor sunt amânate şi produc întârzieri pentru modificarea programului sursă, recompilare şi reintroducerea datelor care fuseseră deja introduse până în momentul în care s-a constatat necesitatea măririi tabloului. 2- Este posibil ca programul sursă să nu fie disponibil. Eliminarea neajunsurilor prezentate mai sus se face prin implementarea listelor cu ajutorul unor structuri de date dinamice. Când apare necesitatea introducerii unui element în listă, se va aloca dinamic spaţiu pentru respectivul element, se va crea elementul prin înscrierea informaţiilor corespunzătoare şi se va lega în listă. 143
  • 150. Când un element este scos din listă spaţiul de memorie ocupat de acesta va fi eliberat şi se vor reface legăturile. Structurile dinamice se construiesc prin legarea componentelor structurii, numite variabile dinamice. O variabilă dinamică ce intră în componenţa unor structuri de date dinamice (nod) prezintă în structura sa două părţi: 1. Partea de informaţie (info) ce poate aparţine unui tip simplu (int, char, float, double, etc.) conform cu necesităţile problemei. 2. Partea de legătură (link) ce conţine cel puţin un pointer de legătură (next) la o altă variabilă dinamică, de obicei de acelaşi tip, ce realizează înlănţuirea variabilelor dinamice în structuri de date dinamice. Dintre structurile de date dinamice, cele mai simple şi mai utilizate sunt listele. Lista este o structură liniară, de tipul unui tablou unidimensional (vector), care are un prim element şi un ultim element. Celelalte elemente au un predecesor şi un succesor. Elementele unei liste se numesc noduri. La rândul lor, listele pot fi grupate în mai multe categorii, cele mai importante fiind listele simplu înlănţuite, listele circulare şi listele dublu legate. Principalele operaţii care se pot efectua asupra unei liste sunt: crearea listei, adăugare/ştergere/modificare au unui element (nod), accesul la un element şi ştergerea în totalitate a listei. Lista simplu înlănţuită este cel mai simplu tip de listă din punctul de vedere al legării elementelor: legătura între elemente este într-un singur sens, de la primul către ultimul. Există un nod pentru care pointerul spre nodul următor este NULL. Acesta este ultimul nod al listei simplu înlănţuite (sau simplu legate). De asemenea, există un nod spre care nu pointează nici un alt nod, acesta fiin primul nod al listei. O listă simplu înlănţuită poate fi identificată în mod unic prin primul element al listei. Determinarea ultimului element se poate face prin parcurgerea secvenţială a listei până la întâlnirea nodului cu pointerul spre nodul următor cu valoarea NULL. 144
  • 151. info info info next next NULL Listă simplă înlănţuită Listele circulare sunt un alt tip de liste pentru care relaţia de precedenţă nu mai este liniară ci ultimul element pointează către primul. Procedurile necesare pentru crearea şi utilizarea unei liste circulare sunt extrem de asemănătoare cu cele de la liste liniare, cu singura deosebire că ultimul element nu are adresa de pointer vid (NULL) ci adresa primului element. info info info next next next Listă circulară Listele dublu legate sunt utile în rezolvarea unor probleme care necesită parcurgeri frecvente ale structurilor dinamice în ambele sensuri. Ele pot fi privite ca structuri dinamice ce combină două liste liniare simplu înlănţuite ce partajează acelaşi câmp comun info, una fiind imaginea în oglindă a celeilalte. info info info next next NULL NULL previous previous Listă dublu legată Pointerul next indică spre următorul nod, iar câmpul previous indică spre câmpul anterior. Vom prezenta în continuare modul în care putem proiecta funcţiile principale care acţionează asupra unei structuri dinamice. Pentru aceasta vom utiliza două variabile globale de tip pointer, una care pointează spre primul nod al listei iar cealaltă spre ultimul nod al listei. Vom denumi aceste variabile first respectiv last. Particularizările se vor face pe exemplul bazei de date construite anterior. Tipul unui nod se declară în felul următor: 145
  • 152. General Particular struct tip_nod { struct addr { declaratii char name[20]; struct tip_nod *next; char street[30]; }; char city[15]; char state[10]; unsigned int zip; struct addr *next; }; Atât la crearea unei liste cât şi la inserarea unui nod se va apela funcţia malloc() pentru a rezerva spaţiu de memorie pentru un nod. Zona alocată prin intermediul funcţiei malloc() se poate elibera folosind funcţia free(). Propunem conceperea unui program asemănător cu programul de exploatare a unei baze de date conceput anterior dar care să folosească în locul unui tablou static o listă simplu înlănţuită. Programul poate fi extins ulterior pentru structuri dinamice mai complexe, care să folosească liste dublu înlănţuite sau circulare. Programul va avea următoarele facilităţi: 1. Crearea unei liste simplu înlănţuite în memorie (pentru prima oară). 2. Exploatarea unei liste simplu înlănţuite în memorie: 2.1. Inserarea unor înregistrări (noduri): a) Inserarea unui nod înaintea primului nod al listei b) Inserarea unui nod înainte/după un nod intern al listei c) Inserarea unui nod după ultimul nod al listei (adăugare la coada listei) 2.2. Ştergerea unor înregistrări a) Ştergerea primului nod al listei b) Ştergerea unui nod intern al listei c) Ştergerea ultimului nod al listei 3. Salvarea din memorie pe disc a unei liste simplu înlănţuite 4. Citirea de pe disc în memorie a bazei de date salvate anterior 5. Afişarea pe ecran, înregistrare cu înregistrare, a bazei de date conţinute în lista dinamică. 146
  • 153. Programul este prevăzut cu o intefaţă simplă prin care utilizatorul poate alege dintre opţiunile pe care le are la dispoziţie. Interfaţa este sub forma unui meniu din care, prin tastarea iniţialelor comenzilor, acestea se lansează în execuţie. Vom descrie pe rând funcţiile care îndeplinesc sarcinile enumerate mai sus. Pentru o mai bună proiectare a programului, vom folosi atât modularizarea internă prin construirea mai multor funcţii cât şi o modularizare externă prin izolarea programului principal de restul funcţiilor care se folosesc. Bază de date cu listă simplu înlănţuită Afişare pe ecran a înregistrărilor Interfaţa cu din baza de date utilizatorul Comenzi pentru citire/scriere Comenzi pentru procesarea pe disc listei dinamice Citire de Salvarea pe pe disc în disc a listei Inserare Ştergere lista dinamice din dinamică memorie - prima înregistrare - ultima înregistrare - înregistrare intermediară Exemplu: Programul principal bd_main.c # include "local.h" void main() { char choice; for (; ;) { choice = menu(); switch (choice) { case 'c' : create_list(); break; case 'l' : loadf_list(); break; case 's' : savef_list(); break; 147
  • 154. case 'd' : display_list(); break; case 'i' : insert(); break; case 'e' : erase(); break; case 'q' : exit(0); break;}}} Fişierul header local.h este: # include <stdio.h> # include <ctype.h> # include <string.h> # include <stdlib.h> typedef struct addr { char name[20]; char street[30]; char city[15]; char state[10]; unsigned int zip; struct addr *next;}TNOD; TNOD *first, *last; FILE *fp; extern int create_list(); extern char menu(); extern void display_list(), insert(), erase(); extern int loadf_list(), savef_list(); extern TNOD *add_nod(); Cu ajutorul acestui fişier header se realizează definirea şablonului structurii addr şi apoi, prin comanda typedef, se defineşte un nou tip de date numit TNOD. First şi last sunt pointeri la structuri de acest tip iar fp este pointer la fişier (file pointer). Cu extern se definesc prototipurile unor funcţii care nu se găsesc în fişierul bd_main.c ci în fişierul bd_bib.c unde sunt colectate toate funcţiile pe care le folosim. Acest fişier are la rândul său un fişier header numit local1.h care conţine: # include <stdio.h> # include <ctype.h> # include <string.h> # include <stdlib.h> extern FILE *fp; typedef struct addr { char name[20]; char street[30]; char city[15]; char state[10]; unsigned int zip; struct addr *next;}TNOD; extern TNOD *first, *last; 148
  • 155. Prin cele două fişiere header cele două fişiere sursă bd_main.c şi bd_bib.c se pot compila împreună, rezultând un singur fişier executabil. Interfaţa cu utilizatorul Aceasta constă într-un meniu principal, care permite accesarea funcţiilor principale, şi din două submeniuri pentru operaţiile de ştergere şi inserare de înregistrări. /* Functia menu() principala */ char menu() { char s[5],ch; do { printf ("n(C)reate new listn"); printf ("(L)oad list from filen"); printf ("(S)ave to filen"); printf ("(D)isplay listn"); printf ("(I)nsert recordn"); printf ("(E)rasen"); printf ("(Q)uitn"); 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 deleten"); printf ("(F)irst record deleten"); printf ("(L)ast record deleten"); printf ("(I)ntermediate deleten"); printf ("(Q)uitn"); 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 insertn"); printf ("(L)ast record insertn"); printf ("(I)ntermediate insertn"); printf ("(Q)uitn"); printf (" Option ? "); 149
  • 156. gets(s);ch=s[0]; } while (!strrchr("fliq",ch)); return tolower(ch); } Încărcarea bazei de date de pe disc Baza de date este înregistrată pe disc în fişierul maillist.dat care se crează la prima salvare a listei dinamice din memorie. Funcţia loadf_nod() citeşte o înregistrare (record) din fişier, returnând 1 în caz de reuşită, -1 dacă se ajunge la sfârşitul fişierului şi 0 în cazul în care există o eroare de citire de pe disc. /* Functia loadf_nod() from file*/ int loadf_nod(TNOD *p) {if (fread(p,sizeof(TNOD),1,fp)==1) return 1; else if (feof(fp)) {fclose (fp); return -1;} else {printf ("File read errorn"); return 0;}} /* Functia loadf_list() from file */ int loadf_list() { int n=sizeof(TNOD),i; TNOD *p; first=last=NULL; if ((fp = fopen("maillist","rb")) == NULL) { printf("Cannot open filen ");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 listan"); return 0;} free(p);return i;} Funcţia loadf_list() alocă fiecare înregistrare citită de pe disc unui nod al listei dinamice construite în memorie. În pointerii first şi last se stochează în permanenţă adresele primei şi ultimei înregistrări (nod sau structură). Salvarea bazei de date pe disc Reprezintă operaţiunea inversă celei de citire. Lista simplu înlănţuită din memorie se salvează în întregime pe disc în fişierul maillist.dat. Spre deosebire de funcţia loadf_list() care apela la funcţia 150
  • 157. 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 filen ");return 0;} p=first; do { if(fwrite(p, sizeof(TNOD), 1,fp) !=1) {printf (" File write error n "); fclose (fp);return 0;} p=p->next;} while (p!=NULL); fclose (fp);return 1;} Crearea unei liste simple înlănţuite La început variabilele first şi last au valoarea NULL, lista fiind vidă. Crearea unei liste se face prin funcţia create_list. Ea returnează 0 pentru eroare şi 1 dacă operaţia reuşeşte. Prin această funcţie se iniţializează o listă dinamică în memorie, fără a face o citire a unei baze de date create anterior. Aceasta este prima funcţie care se apelează când construim o bază de date nouă. /* Functia create_list() new */ void create_list() { int n=sizeof(TNOD); char ch='c'; TNOD *p; first=last=NULL; while ((p=(TNOD *)malloc(n))!=NULL) {p=add_nod(p); if (first==NULL){ /* prima creare */ first=last=p; first->next=NULL;} else { last->next=p; last=p; last->next=NULL;} printf ("Exit? (x): "); if ((ch=getchar())=='x') break;} if (p==NULL){ printf("Memorie insuficienta pentru listan"); free(p);}} Afişarea listei dinamice din memorie 151
  • 158. Prin această operaţie, realizată de funcţia display_list(), se afişează secvenţial toate înregistrările conţinute în nodurile listei pornind de la prima şi terminând cu ultima. // functie de afisare antet void disp_antet() { printf("n%20s","Name"); printf("%30s","Street"); printf("%15s","City"); printf("%10s","State"); printf("%5sn","Zip");} // afisare o singura inregistrare (nod) void disp_nod(TNOD *p) { printf("%20s",p->name); printf("%30s",p->street); printf("%15s",p->city); printf("%10s",p->state); printf("%5d",p->zip);} /* Functia display_list() */ void display_list() { TNOD *p; disp_antet(); p=first; if (p!=NULL) do { disp_nod(p); p=p->next; getchar();} while (p!=NULL); else printf("Lista vida !n");} Funcţia display_list() apelează la funcţia disp_nod() care afişeză o singură înregistrare. Dacă este nevoie de afişarea unui cap de tabel (antet) se apelează la funcţia disp_antet(). Inserarea unor noduri în lista dinamică Această operaţie presupune introducerea unor noduri (înregistrări) fie la începutul listei înaintea primului nod, fie la sfârşitul său (adăugare) după ultimul nod, fie între două noduri vecine nesituate la extremităţi. Funcţiile listate mai jos realizează aceste sarcini. // functia de inserare void insert() { char choice; for (; ;) { choice = menu_insert(); switch (choice) { case 'f' : ins_first(); break; case 'l' : ins_last(); break; 152
  • 159. case 'i' : ins_int(); break; case 'q' : break;} break;}} /* Functia insert_last() */ void ins_last() { int n=sizeof(TNOD); char ch='c',s[2]; TNOD *p; /* ne pozitionam pe ultimul nod */ p=first; while (p->next!=NULL) p=p->next; // se creaza lista in memorie while (ch!='x') { p=(TNOD *)malloc(n); last->next=p; p=add_nod(p); p->next=NULL; last=p; printf("Exit? (x): "); gets(s);ch=s[0];}} /* Functia insert_first() */ void ins_first() { int n=sizeof(TNOD); char ch='c',s[2]; TNOD *p; while (ch!='x') { p=(TNOD *)malloc(n); p->next=first; p=add_nod(p); first=p; printf("Exit? (x): "); gets(s);ch=s[0];}} // inserare dupa inregistrarea afisata void ins_int() { TNOD *p, *pi; char ch='n', s[2]; disp_antet(); p=first; while ((p!=NULL)&&(ch!='y')) { disp_nod(p); printf("Here ? [y]: "); gets(s);ch=s[0]; if (ch!='y') p=p->next;} pi=(TNOD *)malloc(sizeof(TNOD)); pi=add_nod(pi); pi->next=p->next; p->next=pi;} La inserarea unui nod, este nevoie ca acesta să fie legat de cele între care se introduce cu ajutorul pointerilor next. Dacă se doreşte ca 153
  • 160. nodul inserat să fie primul sau ultimul din listă, este nevoie să modificăm pointerii globali first şi last. Ştergerea unor noduri din lista dinamică Nodurile pe care dorim să le ştergem pot fi interioare sau se pot afla la extremităţi. În acest caz, este nevoie să modificăm pointerii first şi last . În toate cazurile este nevoie să refacem legăturile distruse după dispariţia prin ştergere a unor noduri. §tergerea nu este efectivă, ci numai se refac legăturile şi se eliberează cu funcţia free() zona de memorie alocată în prealabil (prin inserare sau creare) cu funcţia malloc(). Mai mult, avem opţiunea de a şterge întreaga listă din memorie în scopul înlocuirii în totalitate a datelor conţinute în înregistrări. // funcţia de ştergere void erase() { char choice; for (; ;) { choice = menu_del(); switch (choice) { case 'e' : del_list(); break; 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); 154
  • 161. char ch='c',s[2]; TNOD *p; /* ne pozitionam pe penultimul nod */ while (ch!='x') { p=first; while (p->next!=last) p=p->next; free(p->next); p->next=NULL; last=p; printf("Deleted. Exit? (x): "); gets(s);ch=s[0];}} // stergere inregistrare intermediara void del_int() { TNOD *p, *pa, *pu; char ch='n', s[2]; disp_antet(); pa=first;p=pa->next;pu=p->next; while ((p!=last)&&(ch!='y')) { disp_nod(p); printf("Delete ? [y]: "); gets(s);ch=s[0]; if (ch='y') {pa->next=pu; free(p);} else {pa=p;p=pu;pu=pu->next;}}} Capitolul VIII FUNCŢII 8.1. Forma generală a unei funcţii Principalul mijloc prin care se pot modulariza programele C este oferit de conceptul de funcţie (unele funcţii standard au fost deja folosite pentru diverse operaţii). În C orice funcţie "întoarce" (returnează), după apel, o valoare al cărui tip trebuie cunoscut. În practică, însă, de multe ori valorile returnate de funcţii sunt ignorate. Standardul limbajului C permite chiar declararea explicită a funcţiilor care nu returnează valori ca fiind de tip void. În C o funcţie poate fi definită, declarată şi apelată. Definirea unei funcţii C se realizează după următorul format general: tip nume_funcţie (lista_parametri) declaraţii_parametri { 155
  • 162. declaraţii _locale instrucţiuni } sau, o formă mai nouă adoptată de ANSI-C în 1989: tip nume_funcţie (declaraţii _parametri) { declaraţii _locale instrucţiuni } Tipul unei funcţii corespunde tipului valorii pe care funcţia o va returna utilizând instrucţiunea return. Acesta poate fi un tip de bază (char, int, float, double etc.) sau un tip derivat (pointer, structură etc.). Dacă pentru o funcţie nu se specifică nici un tip, atunci, implicit se consideră că funcţia întoarce o valoare întreagă. Lista parametrilor, lista_parametri, este o listă de nume de variabile separate prin virgulă care vor primi valorile argumentelor în momentul apelării acesteia. Tipul acestor parametri este descris fie în paragraful declaraţii_parametri, fie direct în lista parametrilor. Lista parametrilor este închisă între paranteze. Chiar dacă o funcţie nu are parametri, parantezele nu trebuie să lipsească. De exemplu, funcţia max(a, b), care returnează cel mai mare dintre numerele întregi a şi b, se poate defini sub forma: int max(a, b) sau int max(int a, int b) int a, b; { { if (a > b) if (a > b) return (a); return (a); else else return (b); return (b); } } În cazul în care tipul parametrilor formali ai unei funcţii nu se declară, atunci ei sunt consideraţi implicit de tip int. În cazul compilatoarelor moderne, programul următor va genera două avertismente. Exemplu: # include <stdio.h> float max(); // Prototipul functiei max() float x; void main() { x = max(3, 4); printf("max= %d",x); 156
  • 163. } float max(a, b) float a, b; { if (a > b) return (a); else return (b); } În urma compilării va rezulta: Compiling... test.c C:cpp_examplestest.c(11): warning C4244: 'return': conversion from 'int' to 'float', possible loss of data C:cpp_examplestest.c(13): warning C4244: 'return': conversion from 'int' to 'float', possible loss of data test.obj - 0 error(s), 2 warning(s) Aceste avertismente sunt generate deoarece, la primul pas al compilării, la parcurgerea liniei de declarare a funcţiei: float max(a,b) parametrii formali a şi b sunt consideraţi de tip întreg. Programul, modificat ca mai jos, va duce la o compilare fără probleme: Exemplu: # include <stdio.h> float max(); float x; void main() { x = max(3.2, 4.1); printf("max= %dn",x); } float max(float a, float b) { if (a > b) return (a); else return (b); } Tot corect va rula şi programul: # include <stdio.h> int max(); int x; void main() { x = max(-3,4); printf("max= %dn",x); } int max(a,b) { if (a > b) return (a); 157
  • 164. else return (b); } Se observă că nu mai este nevoie de declararea explicită a parametrilor formali de tip întreg a şi b. 8.2. Reîntoarcerea dintr-o funcţie Mai intâi precizăm că instrucţiunea return are două utilizări importante: ♦ return determină ieşirea imediată din funcţia în care se află instrucţiunea şi reîntoarcerea în programul apelant; ♦ return poate fi folosită pentru a întoarce o valoare. Reîntoarcerea dintr-o funcţie în programul apelant (funcţia apelantă) se poate face în două moduri: a) După parcurgerea codului corespunzător funcţiei se revine în programul apelant la instrucţiunea imediat următoare. Exemplu: Aceasta funcţie tipăreşte un şir în ordine inversă: # include <string.h> void afis_invers(char s[]); void main() { char s[10]; printf("Introduceti un sir de caracrere de la tastatura (max 10)n"); scanf("%s",s); afis_invers(s); } void afis_invers(char s[]) { register int t; for (t = strlen(s)-1; t >= 0; t--) printf("%c", s[t]); printf("n"); } b) Al doilea mod de întoarcere dintr-o funcţie se realizează utilizând funcţia return. Funcţia return poate fi folosită fără nici o valoare asociată. Exemplu: Funcţia următoare afişează rezultatele ridicării unui număr întreg la o putere întreagă pozitivă: power (baza, exp){ int baza, exp, i; scanf("%d %d", &baza, &exp); if (exp < 0) return; /* Functia se termina daca exp e negativ */ i = 1; for (; exp; exp--) i = baza * i; 158
  • 165. printf (" Rezultatul este %d n", i); } Dacă exponentul exp este negativ, instrucţiunea return determină terminarea funcţiei înainte ca sistemul să întâlnească }, dar nu returnează nici o valoare. O funcţie poate conţine mai multe instrucţiuni return, care pot simplifica anumite algoritme. 8.3. Valori returnate Toate funcţiile, cu excepţia celor daclarate a fi de tip void, returnează o valoare. Această valoare este fie explicit specificată prin return, fie este zero dacă nu se utilizează instrucţiunea return. Dacă o funcţie este declară ta ca fiind de tip void, aceasta poate fi folosită în orice expresie C. O funcţie nu poate fi membrul stâng într-o expresie de atribuire. De exemplu, instrucţiunea: swap(x,y) = 100; este greşită. Funcţiile care nu sunt de tip void se pot împărţi în trei categorii: 1) Funcţii "pure" sunt funcţiile care efectuează operaţii asupra argumentelor şi returnează o valoare de bază pe acea operaţie. Exemplu: sqrt() şi sin() returnează respectiv radăcina pătrată şi sinusul argumentului. 2) A doua categorie de funcţii sunt cele care manipulează informaţii şi întorc o valoare care arată reuşita sau eşecul acestei manipulări. Un exemplu este fwrite() folosită pentru a scrie informaţii pe disk. Dacă scrierea se face cu succes, fwrite() întoarce numărul de octeţi înscrişi (ceruţi să se înscrie); orice altă valoare indică apariţia unei erori. 3) A treia categorie de funcţii sunt cele care nu trebuie să întoarcă o valoare explicită. De exemplu, funcţia printf() întoarce numărul de caractere tipărite, număr care, de obicei, nu are o utilizare ulterioară. Dacă pentru o funcţie care returnează o valoare nu se specifică o operaţie de atribuire, calculatorul va ignora valoarea returnată. Exemplu: Considerăm următorul program care utilizează funcţia mul(): # include <stdio.h> mul(); void main (void){ int x, y, z; x = 10; y = 20; z = mul(x, y); //- primul apel al lui mul() 159
  • 166. printf("%dn", mul(x,y)); /- al doilea apel al lui mul() mul(x,y); //- al treilea apel al lui mul() } mul(a,b) // Se defineste functia mul() { return a*b; } Linia a atribuie valoarea returnată de mul() lui z. În linia b, valoarea returnată nu este atribuită, dar aceasta este utilizată de printf(). In linia c valoarea returnată este pierdută, deoarece nu se atribuie nici unei variabile ce va fi utilizată în altă parte a programului. 8.4. Domeniul unei funcţii În C, fiecare funcţie este un bloc de instrucţiuni. Codul unei funcţii este propriu acelei funcţii şi nu poate fi accesat (utilizat) prin nici o instrucţiune din orice altă funcţie, cu excepţia instrucţiunii de apel al acelei funcţii. (De exemplu, nu putem utiliza goto pentru a realiza saltul dintr-o funcţie în mijlocul unei alte funcţii). Blocul de instrucţiuni care descrie corpul unei funcţii este separat de restul programului şi dacă acesta nu utilizează variabile globale sau date, el nici nu poate afecta, nici nu va fi afectat de alte părţi ale programului. Codul şi datele care sunt definite în interiorul unei funcţii nu pot interacţiona cu codul şi datele definite în altă funcţie, deoarece cele două funcţii au scopuri diferite. În cadrul unei funcţii se deosebesc trei tipuri de variabile, astfel: variabile locale, parametri formali şi variabile globale. Domeniul unei funcţii determină modul în care alte părţi ale programului pot avea acces la aceste trei tipuri de variabile stabilind şi durata de viaţă a acestora. 8.4.1. Variabile locale Variabilele declarate în interiorul unei funcţii se numesc variabile locale. Variabilele locale pot fi referite numai prin instrucţiuni interioare blocului în care au fost daclarate aceste variabile. Variabilele locale nu sunt cunoscute în afara blocului în care au fost daclarate, domeniul lor limitându-se numai la acest bloc. Mai exact, variabilele locale există numai pe durata execuţiei blocului de cod în care acestea au fost daclarate; deci o variabilă locală este creată la intrarea în blocul său şi distrusă la ieşire. De obicei, blocurile de program în care se declară variabilele locale sunt funcţiile. Implicit, o variabilă locală este auto, deci se stochează în memoria stivă. Ea 160
  • 167. poate fi declarată şi register, caz în care se stochează în regiştrii interni ai microprocesorului sau poate fi declarată static, caz în care se stochează în memoria de date sau statică, valoarea sa păstrându-se şi la ieşirea din funcţie. Exemplu: func1() { int x; x = 10; } func2() { int x; x = -199; } Aici variabila întreagă x este declarată de două ori, o dată în func1() şi o dată în func2(). x din func1() nu are nici o legatură cu x din func2(), deoarece fiecare x este cunoscut numai în blocul în interiorul căruia a fost declarat. Limbajul C conţine cuvântul cheie auto, care poate fi folosit pentru declararea de variabile locale. Cu toate acestea, întrucât C presupune că toate variabilele neglobale sunt prin definiţie (implicit) variabile locale, deci au atributul auto, acest cuvânt cheie nu se utilizează. De obicei, variabilele locale utilizate în interiorul unei funcţii se declară la începutul blocului de cod al acestei funcţii. Acest lucru nu este neapărat necesar, deoarece o variabilă locală poate fi declarată oriunde în interiorul blocului în care se utilizează, dar înainte de a fi folosită. Exemplu: Considerăm următoarea funcţie: func (){ char ch; printf (" Continuam (y / n) ? : "); ch = getche(); //Se preia optiunea de la tastatura /* Daca raspunsul este yes */ if (ch == 'y') { char s[80]; /* s se creeaza numai dupa intrarea in acest bloc */ printf (" Introduceti numerele: n "); gets (s); prelucreaza_nr (s); /* Se prelucreaza numerele */ } } Aici, func() creează variabila locală s la intrarea în blocul de cod a lui if şi o distruge la ieşirea din acesta. Mai mult, s este cunoscută numai în interiorul blocului if şi nu poate fi referită din altă parte, chiar din altă parte a funcţiei func() care o conţine. 161
  • 168. Deoarece calculatorul creează şi distruge variabilele locale la fiecare intrare şi ieşire din blocul în care acestea sunt daclarate, conţinutul lor este pierdut o dată ce calculatorul părăseste blocul. Astfel, variabilele locale nu pot reţine valorile lor după încheierea apelului funcţiei. 8.4.2. Parametri formali Dacă o funcţie va folosi argumente, atunci aceasta trebuie să declare variabilele care vor accepta (primi) valorile argumentelor. Aceste variabile se numesc parametri formali ai funcţiei. Parametrii formali ai funcţiei se comportă ca orice altă variabilă locală din interiorul funcţiei. Declararea parametrilor formali se face după numele funcţiei şi înaintea corpului propriu-zis al funcţiei. Exemplu: /* Funcţia următoare întoarce 1 dacă caracterul c aparţine şirului s altfel întoarce 0 */ # include <stdio.h> int func (char s[10],char c) { while (*s) if (*s == c) return 1; else s++; return 0; } void main() { char s[10], c; scanf("%c %s", &c, &s); if (func(s, c)) printf("Caracterul se afla in sirn"); else printf("Caracterul NU se afla in sirn"); } Funcţia func() are doi parametri: s şi c. Aceasta funcţie întoarce 1 dacă caracterul c aparţine şirului şi 0 dacă c nu aparţine şirului. Precizăm că argumentele cu care se va apela funcţia trebuie să aibă acelaşi tip cu parametrii formali declaraţi în funcţie. Aceşti parametri formali pot fi utilizaţi ca orice altă variabilă locală. Un al doilea mod (recomandat de ANSI-C în 1989) de a declara parametrii unei funcţii constă în declararea completă a fiecărui parametru în interiorul parantezelor asociate funcţiei. De exemplu, declararea parametrilor funcţiei func() de mai sus se poate face şi sub forma: func (char *s, char c) { . . . . . . . . . . . } 162
  • 169. 8.4.3. Variabile globale Spre deosebire de variabilele locale, variabilele globale sunt cunoscute întregului modul program şi pot fi utilizate de orice parte a programului. De asemenea, variabilele globale vor păstra valorile lor pe durata execuţiei complete a programului, deci se stochează în memoria statică. Variabilele globale se creează prin declararea lor în afara oricărei funcţii (inclusiv main()). Variabilele globale pot fi plasate în orice parte a programului, evident în afara oricărei funcţii, şi înainte de prima lor utilizare. De obicei, variabilele globale se plasează la începutul unui program, mai exact înaintea funcţiei main(). Exemplu: int count; /* count este global */ void main (void) { count = 100; func1(); } func1() /* Se defineste functia func1() */ { int temp; /* temp preia variabila globala count */ temp = count; func2(); printf ("count is %d",count); // Se va afisa 100 } func2() /* se defineste functia func2() */ { int count; /* count este local */ for (count = 1; count < 10; count ++) printf ("%2dn" , count); } Se observă că, deşi nici funcţia main() şi nici funcţia func1() nu au declarat variabila count, ambele o folosesc. Funcţia func2() a declarat o variabilă locală count. Când se referă la count, func2() se va referi numai la variabila locală count şi nu la variabila globală count declarată la începutul programului. Reamintim că, dacă o variabilă globală şi o variabilă locală au acelaşi nume, toate referirile la numele variabilei în interiorul funcţiei în care este declarată variabila locală se vor efectua numai asupra variabilei locale şi nu vor avea nici un efect asupra variabilei globale. Deci, o variabilă locală ascunde o variabilă globală. Variabilele globale sunt memorate într-o zonă fixă de memorie destinată special acestui scop (memoria statică), rămânând în această zonă pe parcursul întregii execuţii a programului. 163
  • 170. Variabilele declarate explicit extern sunt tot variabile globale, dar accesibile nu numai modulului program în care au fost declarate, ci şi tuturor modulelor în care au fost declarate de tip extern. Un alt exemplu util şi pentru înţelegerea lucrului cu pointeri este următorul: se declară o variabilă globală x, şi apoi două variabile locale cu acelaşi nume, x, care se vor “ascunde“ una pe cealaltă. Pentru a vedea şi modul în care se stochează în memorie aceste variabile, vom afişa şi locaţiile de memorie pentru fiecare tip de variabilă x, precum şi pentru pointerul p corespunzător. Reţinem că, în general, dacă: p = &x => p = &x = &(*p) = (&*)p = p *p = *(&x) = (*&)x = x Faptul că * respectiv & sunt operaţiuni complementare (inverse una celeilalte) se observă din relaţiile de mai sus, din care deducem că: &* = *& = identitate &(*z)= z; // z este pointer *(&z) = z; // z este o variabila de un anume tip (z este de alt tip în fiecare din egalităţile de mai sus, pointer sau variabilă). Adresa Memoria .. .. .. .. .. .. .. .. . &x=a re ax d s x .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. &p d s p =a re a p =&x .. .. .. .. .. .. .. .. &q d s q =a re a q=&p # include <stdio.h> int x = 34; /* x este global */ void main(void) { int *p = &x, *r; /* p este o variabila pointer catre un intreg */ void **q; printf("x=%d &x=%p *p=%d p=%p &p=%pn",x,&x, *p, p, &p); { int x; x = 1; /* Acest prim x este o variabila locala ce o ascunde pe cea globala */ 164
  • 171. p = &x; printf("x=%d &x=%p *p=%d p=%p &p=%pn",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=%pn",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=%pn", q, *q, *r, &q); } } În urma execuţiei programului, obţinem următorul rezultat: x=34 &x=00426A54 *p=34 p=00426A54 &p=0065FDF4 x=1 &x=0065FDE8 *p=1 p=0065FDE8 &p=0065FDF4 x=2 &x=0065FDE4 *p=2 p=0065FDE4 &p=0065FDF4 q=0065FDF4 *q=0065FDE4 **q=2 &q=0065FDEC Prin declaraţia int *p = &x; variabila p este declarată variabilă pointer către o dată de tip întreg şi este iniţializată cu adresa variabilei spre punctează, anume x. Pointerul q este declarat ca un pointer la un alt pointer. Denum. Tipul Caracteristici ale Caracteristici ale variabilei Variab. variabilei variabilei pointer ataşate Adresa &x Valoare Adresa &p Valoarea p *p x int x globală 00426A54 34 0065FDF4 00426A54 34 int x locală 0065FDF0 1 0065FDF4 0065FDF0 1 int x locală 0065FDEC 2 0065FDF4 0065FDEC 2 p pointer p = &x *p = x local q pointer q = &p *q = p &q= q= **q local 0065FDEC 0065FDF4 =2 Acelaşi lucru se face pentru celelalte două variabile locale. Din interpretarea rezultatelor de mai sus putem trage următoarele concluzii. Spre exemplu, printf(“%d”,x); este echivalentă cu printf(“%d”,*p); scanf(“%d”,&x); este echivalentă cu scanf(“%d”,p); dacă în prealabil s-a făcut atribuirea p = &x; Se mai observă cum pointerul p, care iniţial indica spre variabila globală x, este încărcat cu adresa de memorie 4336176 165
  • 172. (00426A54 H), pe când în cazurile când indica variabilele locale x se alocau adresele de memorie 6684140 (0065FDF0 H) şi 6684144 (0065FDEC H), adrese adiacente, la un interval de 4 octeţi, atât cât sunt alocaţi pentru variabila de tip întreg. Se observă că variabila globală se află într-o altă zonă de memorie decât variabilele locale. Modul de lucru cu pointerii este scos în evidenţă prin instrucţiunile: q=&p; // q retine adresa pointerului p r=*q; // r retine valoarea de la adresa q // q=&p => r = *(&p) = p => *r = *p = x printf("q=%p *q=%p **q=%d &q=%pn",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: General : Specific : mul (x, y) int x, y; int x, y mul () { return (x * y);} { return (x * y);} Când se doreşte realizarea produsului a oricăror doi întregi x şi y se utilizează varianta generală a funcţiei, iar când se doreşte produsul numai al variabilelor globale x şi y se utilizează varianta specifică. Exemplu: # include <stdio.h> # include <string.h> int count; // count este global intregului program play(); // Prototipul pentru functia play() void main(void) { char sir[80]; // sir este variabila locala a functiei main() printf("Introduceti un sir : n"); gets(sir); play(sir); } play(char *p) // Se declara functia play() { // p este local functiei play() if (!strcmp(p, "add")) { 166
  • 173. int a,b; /* a si b sunt locale blocului if din interiorul functiei play()*/ scanf ("%d %d", &a, &b); printf ("%d n", a+b); } // int a, b nu sunt cunoscute sau evidente aici else if(!strcmp(p,"beep")) printf("%c",7); } 8.5. Apelul funcţiilor Apelul unei funcţii înseamnă referirea funcţiei, împreună cu valorile actuale ale parametrilor formali, precum şi preluarea valorii returnate, dacă este necesar. La apelul funcţiei, tipul argumentelor trebuie să fie acelaşi cu cel al tipului parametrilor formali ai funcţiei. Dacă apar nepotriviri de tip (de exemplu, parametrul formal al funcţiei este de tip int, iar apelul funcţiei foloseşte un argument de tip float) de obicei, compilatorul C nu semnalizează eroare, dar rezultatul poate fi incorect. În C transmiterea argumentelor de la funcţia apelantă spre funcţia apelată se face prin valori sau prin adrese. a) În cazul transmiterii argumentului prin valoare, se realizează copierea (atribuirea) valorilor fiecărui argument în (la) câte un parametru formal al funcţiei apelate. Exemplu: Se apelează o funcţie ce calculeaza pătratul unui număr întreg. # include <stdio.h> square(); // Prototipul functiei sqrt() void main(void) { int t = 10; printf("%d %dn", t, square(t)); } square(x) // Se declara functia sqrt() int x; { x = x*x; return(x); } Se observă că prin această metodă, schimbările survenite asupra parametrului formal x nu afectează variabila utilizată pentru apelul funcţiei (schimbările lui x nu modifică în nici un fel pe t). b) Dacă transmiterea argumentului se realizează prin adrese, atunci la apelul funcţiei în loc de valori se folosesc adrese, iar în definiţie, parametrii formali se declară ca pointeri. Exemplu: O funcţie swap() care schimbă valorile a două variabile reale se poate defini astfel: void swap(float *x, float *y){ 167
  • 174. float temp; temp = x; /* temp preia valoarea de la adresa x */ *x = *y; /* valoarea de la adresa y este copiata la adresa x */ y = temp; /* la adresa y se copiaza valoarea lui temp */ } Se observă că parametrii formali ai funcţiei swap() sunt pointeri la float. Programul următor arată modul de apel al acestei funcţii. # include <stdio.h> void swap(float *x,float *y); void main(void) { float x, y; // x si y sunt de tip float scanf("%f,%f",&x,&y);/*Se introduc de la tastatura doua numere reale separate prin virgula*/ printf ("x = %f, y = %f n ",x,y); swap(&x,&y); /*Se apeleaza functia swap() avand ca argumente adresele lui x si y */ printf("x = %f, y = %f n ",x,y); } Prin &x şi &y, programul transferă adresele lui x şi y funcţiei swap() şi nu valorile lui x şi y. Un apel combinat, valoare-referinţă este prezentat în exemplul următor: # include <stdio.h> void f(); void main (void) { int x = 1, y = 1; printf("x = %d, y = %d n", x, y); f(x,&y);} void f(int val, int *ref) { val++; (*ref)++; printf("x = %d, y = %d n",val,*ref); } 8.6. Apelul funcţiilor având ca argumente tablouri Când se apelează o funcţie având ca argument un tablou, acesteia i se va transmite un pointer la primul element al tabloului. Reamintim că în C numele unui tablou fără nici un indice este un pointer la primul element al tabloului. Deci, un argument de tipul T[ ] (vector de tipul T) va fi convertit la T * (pointer de tipul T). Rezultă că vectorii, ca şi tablourile multidimensionale, nu pot fi transmise prin valoare. Aceasta înseamnă că declararea parametrului formal trebuie 168
  • 175. să fie compatibilă tipului pointer. Există trei moduri de a declara un parametru care va primi un pointer la un tablou (vector). a) Parametrul formal poate fi declarat ca un tablou, astfel: # include <stdio.h> display(); // Prototipul functiei display() void main(void) { int v[10], i; for (i = 0; i < 10; ++i) v[i] = i; display(v);} display(num) // Se defineste functia display() int num[10]; { int i; for (i = 0; i < 10; i++) printf ("%d", num[i]); } Chiar dacă acest program declară parametrul num ca pe un vector de 10 întregi, compilatorul C va converti automat pe num la un pointer la întreg, deoarece parametrul nu poate primi întregul tablou (vector). b) O a doua cale de a declara un parametru vector (tablou), constă în a specifica parametrul ca pe un vector fără dimensiune: display(int num[]) { int i; for(i = 0; i < 10; i++) printf ("%d",num[i]); } Aceasta funcţie declară pe num ca fiind un vector de întregi cu dimensiune necunoscută. Deoarece limbajul C nu verifică dimensiunea vectorilor, dimensiunea actuală a vectorului este irelevantă ca parametru al funcţiei. §i de aceasta dată, num va fi convertit la un pointer la întreg. c) Ultima metodă prin care se poate declara un parametru tablou este ca pointer, astfel: display(int *num) { int i; for (i = 0; i < 10; i++) printf ("%d", num[i]); } Limbajul C permite acest tip de declaraţie deoarece putem indexa orice pointer utilizând []. Toate cele trei metode de declarare a unui tablou ca parametru produc acelaşi rezultat: un pointer. Cu toate acestea, un element al unui tablou folosit ca argument al unei funcţii va fi tratat ca orice altă variabilă. Astfel, programul de mai sus poate fi rescris sub forma: # include <stdio.h> void main (void) { int v[10], i; for (i = 0; i < 10; i++) v[i] = i; for (i = 0; i < 10; i++) display (v[i]); } 169
  • 176. display(int num) { printf ("%d" , num); } De data aceasta, parametrul din display() este de tip int, deoarece programul utilizează numai valoarea elementului tabloului. Exemplu: Vom prezenta un program pentru afişarea tuturor numerelor prime cuprinse între două limite întregi. Programul principal apelează două funcţii: nr_prim() returnează 1 dacă argumentul său întreg este prim şi 0 dacă nu este prim; numerele prime sunt grupate într-un vector, care se afişează ulterior cu funcţia display(). # include <stdio.h> int nr_prim(); // Se declara prototipul void display(); void main (void) { int a,b,i,j,v[80]; printf("Introduceti limitele: "); scanf("%d %d", &a, &b); j = 0; for (i=a; i<=b; ++i) if (nr_prim(i)) {v[j]=i; ++j;} display(v,j);} int nr_prim(int i) // Decide daca i este prim { int j; for (j=2; j<=i/2; j++) if (i%j==0) return 0; return 1; } void display(int *p, int j) /* Tipareste un vector de intregi */ { int i; for (i=0; i<j; ++i) printf("%d ", p[i]); } Din cele de mai sus, trebuie reţinut că atunci când un tablou se utilizează ca argument al unei funcţii, calculatorul transmite funcţiei adresa de început a tabloului. Acest lucru constituie o excepţie a limbajului C în convenţia de transmitere a parametrilor prin valoare. Astfel, codul funcţiei poate acţiona asupra conţinutului tabloului şi îl poate altera. Exemplu: Programul următor va modifica conţinutul vectorului sir din funcţia main() după apelul funcţiei afis_litmari(). # include <stdio.h> # include <ctype.h> afis_litmari(); void main (void) { char sir[80]; gets(sir); afis_litmari(sir); printf("n%sn",sir);} 170
  • 177. // Se defineste functia afis_litmari() afis_litmari(char *s) { register int t; for (t = 0; s[t]; ++t) { // Se modifica continutul sirului sir s[t] = toupper(s[t]); printf("%c",s[t]);}} Rezultatul rulării programului va fi: abcdefghijklmnoprstuvxyzw ABCDEFGHIJKLMNOPRSTUVXYZW ABCDEFGHIJKLMNOPRSTUVXYZW Exemplu: Dacă nu dorim să se întâmple acest lucru, programul de mai sus se poate rescrie sub forma: # include <stdio.h> # include <ctype.h> afis_litmari(); void main (void) { char sir[80]; gets(sir); afis_litmari(sir); printf("n%sn",sir);} afis_litmari(char *s) /* Se defineste functia afis_litmari() */ { register int t; for (t = 0; s[t]; ++t) printf("%c",toupper(s[t])); } // Nu se modifica continutul sirului sir Rezultatul rulării va fi de această dată: abcbdefghijklmnoprstuvxyzw ABCBDEFGHIJKLMNOPRSTUVXYZW abcbdefghijklmnoprstuvxyzw În aceasta variantă conţinutul tabloului ramâne nemodificat, deoarece programul nu-i schimbă valoarea. Un exemplu clasic de transmitere a tablourilor într-o funcţie îl constituie funcţia gets() din biblioteca C standard. Prezentăm o variantă simplificată a acestei funcţii numită xgets(). xgets(s) char *s; { char ch; int t; for (t = 0; t < 80; ++t) { ch = getchar(); switch (ch) { case 'n' : s[t] = '0'; /* terminare sir */ return; 171
  • 178. case 'b': if (t > 0) t--; break; default: s[t] = ch; } } s[80] ='0'; } Funcţia xgets() trebuie apelată având ca argument un tablou de caractere, care, prin definiţie, este un pointer la caracter. Numărul caracterelor introduse de la tastatură, prin funcţia for este de 80. Dacă se introduc mai mult de 80 de caractere, funcţia se încheie cu return. Dacă se introduce un spaţiu, contorul t este redus cu 1. Când se apasă CR, xgets() introduce terminatorul de şir. 8.7. Argumentele argc şi argv ale funcţiei main() Singurele argumente pe care le poate avea funcţia main() sunt argv şi argc. Parametrul argc conţine numărul argumentelor din linia de comandă şi este un întreg. Întotdeauna acesta va fi cel puţin 1, deoarece numele programului este codificat ca primul argument. Parametrul argv este un pointer la un tablou de pointeri la caractere. Fiecare element din acest tablou indică spre un argument linie_comanda. Toate argumentele linie_comanda sunt şiruri. Exemplu: Următorul program arată modul de utilizare al argumentelor linie_comanda şi va afişa Hello urmat de numele dumneavoastră, dacă vă introduceţi numele, imediat după numele programului: # include <stdio.h> void main (argc, argv) // Numele programului int argc; char *argv[]; {if (argc != 2) { printf (" Ati uitat sa va introduceti numele n"); return; } printf ("Hello %s !", argv[1]); } Dacă acest program se numeşte ARG_LC.C şi numele dumneavoastră este DAN, atunci, pentru a executa programul, în linia de comandă, veţi tipări ARG_LC DAN. Ieşirea programului va fi Hello DAN !. Argumentele linie_comanda trebuie separate prin spaţiu sau TAB şi nu prin virgulă, sau;. 172
  • 179. Parametrul argv[] se declară, de obicei, sub forma char *argv[]; şi reprezintă un tablou de lungime nedeterminată, mai precis reprezintă un tablou de pointeri. Accesul la elementele lui argv[] se realizează prin indexarea acestuia, astfel: argv[0] va indica spre primul şir, care este întotdeauna numele programului; argv[1] va indica spre primul argument etc. Evitaţi folosirea sa fără paranteze, adică char *argv. Următorul program numit "nrinvers" numără invers de la o valoare specificată prin linia de comandă şi transmite un beep când ajunge la zero. Precizăm că programul converteşte primul argument, care conţine numărul la un întreg folosind funcţia standard atoi(). Dacă şirul "display" apare ca al doilea argument_comanda, programul va afişa, de asemenea, numărul introdus pe ecran. # include <stdio.h> # include <string.h> # include <stdlib.h> void main(int argc, char *argv[]) /* nrinvers */ { int disp, count; if (argc < 2) { printf ("Trebuie introdusa lungimea numarului in linia de comandan"); return; } if (argc==3 && !strcmp(argv[2], "display")) disp = 1; else disp = 0; for (count = atoi(argv[1]); count; --count) if (disp) printf("%d ",count); printf("%c",7); /* Se emite un beep */ } Observaţie: Dacă în linia de comandă nu se specifică nici un argument, programul va afişa un mesaj de eroare. 8.8. Funcţii care returnează valori neîntregi Dacă nu se declară explicit tipul funcţiei, compilatorul C o va declara implicit de tip int. Pentru ca funcţia să întoarcă un tip diferit de int trebuie, pe de o parte, să se precizeze un specificator de tip al funcţiei şi apoi să se identifice tipul funcţiei înaintea apelului acesteia. O funcţie C poate returna orice tip de dată din C. Declararea tipului este similară celei de la declararea tipului variabilei: specificatorul de tip ce precede funcţia indică tipul datei întoarse de funcţie. Pentru a nu se genera incertitudini datorate dimensiunii de reprezentare, înainte de utilizarea unei funcţii ce întoarce tipuri 173
  • 180. neîntregi, tipul acestei funcţii trebuie făcut cunoscut programului. Acest lucru este necesar deoarece compilatorul nu cunoaşte tipul datei întoarse de funcţie şi acesta va genera un cod greşit pentru apelul funcţiei. Pentru a preveni această greşeală, la începutul programului se plasează o formă specială de declaraţie care să precizeze compilatorului ce tip de valoare va returna acea funcţie. Această declaraţie se numeşte prototipul funcţiei. Exemplu: # include <stdio.h> float sum();//Prototipul functiei (fara parametri) void main(void) { float first = 123.23, second = 99.09; printf("%fn", sum(first, second)); } float sum(float a, float b) // Definitie sum() //Se returnează o valoare de tip float { return a+b; } Instructiunea de declarare a tipului funcţiei are forma generală: specificator_de_tip nume_funcţie(); Chiar dacă funcţia are argumente, în declaraţia de tip acestea nu se precizează (cu excepţia compilatoarelor mai vechi de 1989, care nu sunt adaptate la cerinţele ANSI-C). Dacă o funcţie ce a fost declarată int întoarce un caracter, calculatorul converteşte valoarea caracter într-un întreg. Deoarece conversiile caracter --> întreg-caracter sunt fără probleme, o funcţie ce întoarce un caracter poate fi definită ca o funcţie care întoarce un întreg. 8.9. Returnarea pointerilor Deşi funcţiile care întorc pointeri se manipulează în acelaşi mod ca şi celelalte tipuri de funcţii, trebuie discutate câteva concepte importante. Pointerii la variabile nu sunt nici întregi, nici întregi fără semn. Pointerii sunt adrese de memorie a anumitor tipuri de date: int, char, float, double, struct etc. Motivul acestei distincţii este legat de faptul că atunci când se prelucrează un pointer aritmetic, această prelucrare este dependentă de tipul datei indirectate: de exemplu, dacă este increment un pointer la 174
  • 181. int, noua valoare (a adresei) va fi cu 4 mai mare faţă de valoarea anterioară. În general, când un pointer este incrementat sau decrementat, acesta va indica către elementul următor, respectiv anterior, din tabloul pe care îl indirectează. De exemplu, dacă funcţia int f() returnează un întreg, atunci funcţia int *f(), returnează un pointer la o dată de tip int. Deoarece fiecare tip de date poate avea lungimi diferite, compilatorul trebuie "să ştie" ce tip de dată este indirectată de pointer, pentru a-l face să indice corect spre următorul element. Exemplu: Programul următor conţine o funcţie care întoarce un pointer într-un şir în locul în care calculatorul găseşte o coincidenţă de caractere. char *match (char c, char *s) {int count; count = 0; while (c!=s[count] && s[count] != '0') count ++; return (&s[count]); } Funcţia match() va încerca să întoarcă un pointer la locul (elementul) din şir unde calculatorul găseşte prima coincidenţă cu caracterul c. Dacă nu se găseste nici o coincidenţă, funcţia va întoarce un pointer la terminatorul de şir (NULL). Un scurt program ce ar utiliza funcţia match() este următorul : # include <stdio.h> # 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 %sn",p);} else printf("Nu exista nici o coincidenta"); } Acest program citeşte mai întâi un şir şi apoi un caracter. În cazul în care caracterul este în şir, atunci se tipăreste şirul din punctul unde se află caracterul, altfel se tipăreşte "Nu există nici o coincidenţă". 175
  • 182. Un caz interesant este oferit de funcţiile care returnează pointeri către şiruri de caractere. O astfel de funcţie se declară sub forma: char *f() Exemplu: Programul următor arată modul în care se defineşte, se declară şi se apelează o astfel de funcţie. # include <stdio.h> void main(void) { int i; char *NumeLuna(); scanf("%d", &i); printf("%s n ", NumeLuna(i)); } char *NumeLuna(nr) int nr; { char *luna[]= {"Eroare", "Ianuarie", "Februarie", "Martie", "Aprilie", "Mai", "Iunie","Iulie", "August", "Septembrie", "Octombrie", "Noiembrie", "Decembrie"}; return ((nr>=1) && (nr <= 12)?luna[nr]:luna[0]); } Un alt exemplu va fi reprezentat de o variantă a funcţiei strcpy() din string.h , deci o funcţie care copiază caracterele din şirul s2 în şirul s1. Rezultatul se găseşte în s1. /* Vom incepe cu definirea functiei strcpy2() si apoi vom declara programul principal main(). In acest fel nu mai este necesara declararea prototipului functiei strcpy2() */ # include <stdio.h> char *strcpy2(register char s1[],register char s2[]) { char *s0 = s1; // Echivalent: char *s0;s0 = s1; while ((*s1++ = *s2++) != '0'); return s0; } void main() { char *sir1,*sir2; puts(“Introduceti un sir de la tatstatura n”); gets(sir2); puts(strcpy2(sir1,sir2));} Se observă cum se iniţializează s0 cu s1. Bucla while atribuie valorile (caracterele) (*s2) în locaţiile indicate de pointerul s1, incrementând ambii pointeri simultan. Bucla se termină la întâlnirea caracterului null, care se copiază şi el. Valoarea returnată, s0, reţine adresa de început a şirului s1. Un ultim exemplu îl constituie un program de manipulare a unor matrici. Acest program realizează citirea unei matrici, 176
  • 183. transpunerea sa şi respectiv afişarea rezultatului apelând la funcţiile cit_mat(), trans_mat() şi tip_mat(). # include <stdio.h> # define DIM_MAX 10 void cit_mat(); void tip_mat(); int *trans_mat(); void main() { int a[DIM_MAX][DIM_MAX], dim_lin, dim_col, *p; printf("Introduceti dimensiunea matricei [dim_lin dim_col]: "); scanf("%d %d", &dim_lin, &dim_col); cit_mat(a, dim_lin, dim_col); tip_mat(a, dim_lin, dim_col); p = trans_mat(a, dim_lin, dim_col); tip_mat(a, dim_col, dim_lin); } void cit_mat(int p[][DIM_MAX], int lin, int col) { int i, j; for (i=0; i<lin; i++) for (j=0; j<col; j++) { printf("x[%d][%d] = ", i, j); scanf("%d", &p[i][j]); } } void tip_mat(int p[][DIM_MAX], int lin, int col) { int i, j; for (i=0; i<lin; i++) { for (j=0; j<col; j++) printf("%d ",p[i][j]); printf("n"); } printf("n"); } int *trans_mat(int p[][DIM_MAX], int lin, int col) { int t, i, j; for (i=0; i<lin; i++) for (j=i; j<col; j++) {t = p[i][j], p[i][j] = p[j][i], p[j][i] = t;} return p; } 8.10. Funcţii de tip void Din punct de vedere sintactic, tipul void se comportă ca un tip fundamental (de bază). Nu există obiecte de tip void. 177
  • 184. Tipul void este utilizat pentru declararea implicită a acelor funcţii care nu întorc o valoare. void se utilizează şi ca tip de bază pentru pointeri la un obiect de tip necunoscut. Exemplu: void f(void) /* functia f nu intoarce o valoare */ void *pv /* pointer la un obiect necunoscut */ Utilizând void se impiedică folosirea funcţiilor ce nu întorc o valoare în orice expresie, prevenind astfel o întrebuinţare greşită a acestora. De exemplu, funcţia afis_vertical() afişează pe ecran argumentul său şir, vertical, şi întrucât nu întoarce nici o valoare, este declarată de tip void. void afis_vertical (sir) char *sir; { while (*sir) printf ("%c n", *sir ++); } Înaintea utilizării acestei funcţii sau oricărei alte funcţii de tip void, aceasta trebuie declarată. Dacă nu se declară, compilatorul C consideră că aceasta întoarce o valoare întreagă. Astfel, modul de utilizare al funcţiei afis_vertical() este următorul: # include <stdio.h> void afis_vertical(); // Se declara prototipul void main (void) { afis_vertical ("Hello "); } void afis_vertical (sir) char *sir; { while (*sir) printf ("%c n", *sir ++); } 8.11. Funcţii prototip După cum se ştie, înaintea folosirii unei funcţii care întoarce o altă valoare decât int, aceasta trebuie definită. Funcţiile prototip au fost adăugate de comitetul ANSI-C standard. Declararea unei funcţii prototip se face conform următorului format: tip nume_funcţie (tip_arg1, tip_arg2,...) unde: tip = tipul valorii întoarse de funcţie; tip_arg1, tip_arg2,... = tipurile argumentelor funcţiei. Exemplu: Programul următor va determina compilatorul să emită un mesaj de eroare sau de avertisment deoarece acesta încearcă să 178
  • 185. apeleze funcţia func() având al doilea argument de tip int, în loc de float, cum a fost declarat în funcţia func(): #include <stdio.h> void func(int, float);//Prototipul functiei func() void main (void) { int x, y; x = 10; y = 10; func (x, y); } /* Se afiseaza o nepotrivire */ void func (x, y) /* Parametrii functiei sunt: */ int x; /* x - intreg */ float y; /* y - real */ { printf ("%f", y/(float) x); } Funcţiile prototip se folosesc pentru a ajuta compilatorul în prima fază în care funcţiile utilizate sunt definite după programul principal. Acesta trebuie înştiinţat asupra tipul datei returnat de o funcţie pentru a aloca corect memoria. Dacă funcţiile sunt declarate înaintea liniei de program main(), funcţiile prototip nu mai sunt necesare, deoarece compilatorul extrage informaţia despre funcţii în momentul în care parcurge corpul definiţiei lor. Spre exemplu, programul de mai sus se poate scrie şi sub forma următoare, în care nu vom mai avea o declaraţie de funcţie prototip: #include <stdio.h> void func (x, y) /* Parametrii functiei sunt: */ int x; /* x - intreg */ float y; /* y - real */ { printf ("%f", y/(float) x); } void main (void) { int x, y; x = 10; y = 10; func (x, y); } /* Nu se afiseaza nepotrivire */ Utilizând recomandările ANSI-C din 1989, programul de mai sus se poate scrie mai compact: #include <stdio.h> void func (int x, float y) /* Parametrii formali includ tipul */ { printf ("%f", y/(float) x); } void main (void) { int x, y; x = 10; y = 10; func (x, y); }//afisare avertisment de conversie sau, folosind funcţia prototip: #include <stdio.h> void func(); /* Declarare prototip fara parametri formali ! */ 179
  • 186. void main (void) { int x, y; x = 10; y = 10; func (x, y); } void func (int x, float y) /* Parametrii formali includ tipul */ { printf ("%f", y/(float) x); } În ultimul program am evidenţiat o recomandare care simplifică efortul de programare în sensul că în linia de declarare a prototipurilor funcţiilor folosite este necesar să definim tipul funcţiei nu şi tipul parametrilor formali. Compilatorul se informează despre tipul parametrilor formali la parcurgerea corpului definiţiei funcţiei. Din cele de mai sus se observă ca folosirea funcţiilor prototip ne ajută la verificarea corectitudinii programelor, deoarece nu este permisă apelarea unei funcţii cu alte tipuri de argumente, decât tipul celor declarate. 8.12. Funcţii recursive Funcţiile C pot fi recursive, adică se pot autoapela direct sau indirect. O funcţie este recursivă dacă o instrucţiune din corpul funcţiei este o instrucţiune de apel al aceleiaşi funcţii. Uneori o funcţie recursivă se numeşte şi funcţie circulară. Un exemplu de o astfel de funcţie este funcţia factorial() care determină factorialul unui număr. Această funcţie se poate organiza recursiv, ştiind că: n! = n(n-1)!. Având în vedere 0!=1, această funcţie se poate organiza astfel: long factorial (int n) { if (n == 0) return (1); else return (n * factorial(n-1)); } Programul de apel al acestei funcţii se scrie sub forma: # include <stdio.h> void main (void) { int n; printf("Introduceti un numar intreg : n"); scanf ("%d, &n); printf ("(%d) ! = %ld",n,factorial(n)); } long factorial (int n) { if (n == 0) return (1); else return (n * factorial(n-1)); } 180
  • 187. Observaţie: Atunci când o funcţie se autoapelează recursiv, la fiecare apel al funcţiei se memorează pe stivă atât valorile parametrilor actuali, cât şi întregul set de variabile dinamice definite în cadrul funcţiei. Din aceasta cauză stiva trebuie dimensionată corespunzător. O variantă echivalentă a funcţiei factorial() definită mai sus ar fi următoarea: long factorial(int n) { if (!n) return (1); else return (n * factorial (n-1)); } Un alt exemplu interesant este dat de şirul lui Fibonacci, în care termenul general an este dat de relaţia de recurenţă: an = an-1+ an-2 , unde a0 = 0 şi a1=1. Codul funcţiei poate fi scris sub forma: long fib(int n) { if (n == 0) return (0); else if (n == 1) return (1); else return (fib(n-1)+fib(n-2)); } Utilizarea recursivităţii poate să nu conducă la o reducere a memoriei necesare, atât timp cât stiva este folosită intens pentru fiecare apel recursiv. De asemenea şi execuţia programului poate să nu fie mai rapidă. Dar codul recursiv este mai compact şi de multe ori mai uşor de scris şi înţeles decât echivalentul său recursiv. Recursivitatea este convenabilă în mod deosebit pentru operaţii pe structuri de date definite recursiv, cum sunt listele, arborii etc. 8.13. Clase de memorare (specificatori sau atribute) Din punct de vedere al execuţiei programelor C, memoria computerului este organizată în trei zone, cunoscute în mod tradiţional ca segment de memorie text, segment de memorie statică (sau de date) şi segment de memorie dinamică (sau stivă). Segment de memorie text Conţine instrucţiunile programului, deci (memorie program) programul executabil Segment de memorie Conţine variabilele a caror locaţie rămâne fixă statică 181
  • 188. Segment de memorie Conţine variabilele de tip automatic, dinamică (de tip stivă) parametrii funcţiilor şi apelurile şi retururile de/din funcţii În tabelul următor se prezintă caracteristicile claselor de memorie. Specificator Domeniul de Durata de viaţă Plasament de memorie vizibilitate al variabilei a variabilei Auto Local fiecărei funcţii Temporară, numai În memoria (automatic) sau fiecărui bloc în care când se execută dinamică (de a fost declarată funcţia în care este tip stivă) declarată Register Local fiecărei funcţii Temporară, numai În regiştrii (registru) când se execută microproceso funcţia în care este rului declarată Extern Global, de către toate Permanentă, pe În memoria funcţiile dintr-un fişier parcursul rulării statică sursă sau din mai multe programului fişiere sursă executabil Static Local sau global Permanentă, cât In memoria timp este in statică 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 declară este activată) sau permanentă (există pe toată durata de execuţie a programului). Dacă tipul se declară explicit în declaratorul variabilei, clasa de memorie se determină prin specificatorul de clasă de memorie şi prin locul unde se face declaraţia (în interiorul unei funcţii sau înaintea oricărei funcţii). Variabilele cele mai folosite sunt cele care sunt declarate în blocurile aparţinând unei funcţii. Aceste variabile sunt de două feluri: - auto, aşa cum sunt marea majoritate a variabilelor declarate numai prin tip. Acesta este un specificator implicit, deci nu 182
  • 189. este nevoie să îl invocăm la declararea variabilelor. Variabilele auto sunt plasate în memoria stivă, iar domeniul de vizibilitate este local, numai pentru funcţia în care variabila a fost declarată, iar din punctul de vedere al duratei de viaţă sunt volatile, adică dispar din memoria stivă după reîntoarcerea din funcţie. - static, declarate explicit. Variabilele static sunt plasate în memoria statică, iar domeniul de vizibilitate este local, numai pentru funcţia în care variabila a fost declarată, iar din punctul de vedere al duratei de viaţă sunt permanente, adică nu dispar din memoria statică după reîntoarcerea din funcţie. - register, declarate explicit. Variabilele register sunt identice cu cele auto cu excepţia faptului că stocarea nu are loc în memoria stivă ci în regiştrii interni ai microprocesorului în scopul sporirii vitezei de execuţie a programelor. - extern, declarate explicit. Din punct de vedere al modulării unor programe, este preferabil să divizăm un program complex în mai multe module program care se leagă în faza de link-editare. O variabilă declarată extern într-un modul program semnalează compilatorului faptul că această variabilă a fost declarată într-un alt modul. Aceste variabile sunt globale, adică sunt văzute de orice modul de program şi de orice funcţie componentă a unui modul program. Stocarea are loc în memoria statică iar durata de viaţă este permanentă, pe toată perioada execuţiei programului. Iniţializarea unei variabile static diferă de cea a unei variabile auto prin aceea că iniţializarea este făcută o singură dată, la încărcarea programului în memorie şi lansarea sa în execuţie. După prima iniţializare, o variabilă static nu mai poate fi reiniţializată (de exemplu, la un nou apel al funcţiei în care este iniţializată). Iată ilustrat acest lucru prin două exemple simple. Se tipăreşte, cu ajutorul funcţiei receip(), un număr care este mai întâi iniţializat cu valoarea 1 şi returnat incrementat cu o unitate. În cazul folosirii variabilelor implicite locale auto se rulează programul: # include <stdio.h> short receip(); void main(){ printf("First = %dn",receip()); printf("Second = %dn",receip());} 183
  • 190. 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 = %dn",receip()); printf("Second = %dn",receip());} short receip() { static short number = 1; return number++;} şi obţinem rezultatul First = 1 Second = 2 Limbajul C suportă patru specificatori ai claselor de memorare: auto, extern, static, register. Aceştia precizează modul de memorare al variabilelor care îi urmează. Specificatorii de memorare preced restul declaraţiei unei variabile care capătă forma generală: specificator_de_memorare specificator_de_tip lista_de_variabile; Specificatorul auto Se foloseşte pentru a declară varibilele locale (obiectele dintr-un bloc). Totuşi, utilizarea acestuia este foarte rară, deoarece, implicit, variabilele locale au clasa de memorare automată (auto). Specificatorul extern Se utilizează pentru a face cunoscute anumite variabile globale declarare într-un modul de program (fişier) altor module de programe (fişiere) cu care se va lega primul pentru a alcătui programul complet. Exemplu: Modulul 1 Modulul 2 int x, y; extern int x, y; char ch; extern char ch; main() func22() { { . . . . . . x = y / 10; . . . . . . } } func23() func1() { { x = 123; } y = 10; } 184
  • 191. Dacă o variabilă globală este utilizată într-una sau mai multe funcţii din modulul în care acestea au fost declarate nu este necesară utilizarea opţiunii extern. Dacă compilatorul găseşte o variabilă ce n-a fost declarată, atunci acesta o va căuta automat printre variabilele globale. Exemplu: int first, last; /* variabile globale */ main( ) { extern int first;}//folosire optionala declaratie extern Variabile statice Obiectele statice pot fi locale unui bloc sau externe tuturor blocurilor, dar în ambele situaţii ele îşi păstrează valoarea la ieşirea şi intrarea, din sau în funcţii. Variabile locale statice Când cuvântul cheie static se aplică unei variabile locale, compilatorul C crează pentru aceasta o memorie permanentă în acelaşi mod ca şi pentru o variabilă globală. Diferenţa dintre o variabilă locală statică şi o variabilă globală este că variabila locală statică este cunoscută numai în interiorul blocului în care a fost declarată. Un exemplu de funcţie care necesită o astfel de variabilă este un generator de numere care produce un nou număr pe baza celui anterior. serie() {static int numar_serie; numar_serie = numar_serie + 23; return (numar_serie); } Se observă că variabila numar_serie continuă să existe între două apeluri ale funcţiei serie() fără ca aceasta să fi fost declarată ca variabilă globală. Se observă de asemenea că funcţia nu atribuie nici o valoare iniţială variabilei numar_serie, ceea ce înseamnă că valoarea inţială a acesteia este 0. Variabile globale statice O variabilă globală cu atributul static este o variabilă globală cunoscută numai în modulul în care a fost declarată. Deci o variabilă globală statică nu poate fi cunoscută şi nici modificată din alte module de program (alte fişiere). Exemplu: static int numar_serie; //var. globala este cunoscuta numai in acest fisier 185
  • 192. serie() { numar_serie = numar_serie + 23; return (numar_serie); } /* initializarea variabilei numar_serie */ serie_start(val_init) int val_init;{ numar_serie = val_init; } Apelul funcţiei serie_start() cu o valoare intreagă iniţializează seria generatoare de numere, după care apelul funcţiei serie() va genera următorul număr din serie. Specificatorul register Acest modificator se aplică numai variabilei de tip int şi char. Acest specificator precizează faptul ca variabilele declarate cu acest modificator sunt des utilizate şi se pastrează de obicei în registrele CPU. Specificatorul register nu se aplica variabilelor globale. Exemplu: Aceasta funcţie calculeaza me pentru întregi : int_putere (m, e) int m; register int e; { register int temp; temp = 1; for (; e; e--) temp * = m; return temp; } În acest exemplu au fost declarate ca variabile registru atât e cât şi temp. De obicei utilizarea unei variabile registru conduce la micşorarea timpului de execuţie al unui program. Exemplu : unsigned int i; unsigned int delay; main() { register unsigned int j; long t; t = time ('0'); for (delay = 0; delay < 10; delay++) for (i = 0; i < 64000; i++); printf("Timpul pentru bucla non-registru: %ldn" ,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);} 186
  • 193. Dacă se execută acest program se va găsi că timpul de execuţie al buclei registru este aproximativ jumătate din timpul de execuţie al variabilei non-registru. 8.14. Pointeri la funcţii Într-un fel, un pointer funcţie este un nou tip de dată. Chiar dacă o funcţie nu este o variabilă, aceasta are o locaţie fizică în memorie care poate fi atribuită unui pointer. Adresa atribuită pointerului este punctul de intrare al funcţiei. Acest pointer poate fi utilizat în locul numelui funcţiei. Pointerul permite de asemenea funcţiilor să fie pasate (trecute) ca argumente în alte funcţii. Adresa unei funcţii se obţine utilizând numele funcţiei fără nici o paranteză sau argumente (ca în cazul tablourilor). Exemplu: # include <stdio.h> # include <ctype.h> void check(); int strcmp(); /* prototip functie */ void main() { char s1[80], s2[80]; void *p; /* p preia adresa de intrare a functiei */ p = strcmp; gets(s1); gets(s2); check(s1,s2,p); } void check (char *a, char *b, int (*cmp) ()) /* cu int (*cmp) () se declara un pointer functie */ { printf (" Test de egalitate n "); if (!(*cmp) (a,b)) printf ("Egaln"); else printf ("Neegaln"); } 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. Apelul funcţiei check() se face având ca parametri doi pointeri la caracter şi un pointer funcţie. Instrucţiunea : (*cmp)(a, b) 187
  • 194. 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 ("Egaln"); else printf("Neegaln"); } 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 ("Egaln"); else printf ("Neegaln"); } Capitolul IX 188
  • 195. PREPROCESAREA Un preprocesor C realizează substituirea macrodefiniţiilor, o serie de calcule adiţionale şi incluziunea fişierelor. Liniile programului sursă care încep cu "#", precedat eventual de spaţiu comunică cu preprocesorul. Sintaxa acestor linii este independentă de restul limbajului; pot apare oriunde în program şi pot avea efect care se menţine (indiferent de domeniul în care apare) până la sfârşitul unitatii de translatare. Preprocesorul C conţine următoarele directive: #if #include #ifdef #define #ifndef #undef #else #line #elif #error #pragma 9.1. Directive uzuale Directiva #define se utilizează pentru a defini un identificator şi un şir (o secvenţă) pe care compilatorul îl va atribui identificatorului de fiecare dată când îl întâlneşte în textul sursă. Forma generală a directivei #define este : #define identificator şir Se observă că directiva #define nu conţine "; ". În secvenţa de atomi lexicali "şir" nu trebuie să apară spaţiu. Linia se termina cu CR. Exemplu: # define TRUE 1 # define FALSE 0 Când în program se întâlnesc numele TRUE şi FALSE, acestea se vor înlocui cu 1, respectiv 0. Instrucţiunea: printf ("%d %d %d", FALSE, TRUE, TRUE + 5); va afişa pe ecran 0 1 6. După definirea unui macro_name, acesta poate fi folosit pentru definirea altui macro_name. 189
  • 196. Exemplu: # define ONE 1 /* Se defineşte macro_name ONE */ # define TWO ONE + ONE /* Se utilizează macro_name ONE */ # define THREE ONE + TWO Deci această macrodefiniţie realizează simpla înlocuire a unui identificator cu şirul asociat. Dacă, de exemplu, se doreşte definirea unui mesaj standard de eroare, se poate scrie: # define E_MS "standard error on input n" . . . . . . . . . . printf (E_MS); Ultima linie este echivalentă cu : printf ("standard error on inputn"); atunci când în program se întâlneşte identificatorul E_MS. Exemplu: Programul următor nu va afişa "this is a test", deoarece argumentul lui printf() nu este închis între ghilimele. # define XYZ this is a test . . . . . . . . . . . . . . . . . printf ("XYZ"); Se va afişa XYZ şi nu "this is a test". Dacă şirul este prea lung şi nu încape pe o linie, acesta se scrie sub forma: # define LONG_STRING " this is a very long string that is used as an example " Observaţie: De obicei macro_names sunt definite cu litere mari. Directiva #define poate fi folosită şi pentru precizarea dimensiunii unui tablou, astfel: # define MAX_SIZE 100 float balance [ MAX_SIZE ]; Macro_nameul dintr-o directiva #define poate avea şi argumente. Exemplu : # define MIN (a ,b) a < b ? a : b void main() { int x, y; x = 10; y = 20; printf("Numarul mai mic este: %d ", MIN (x,y)); } După substituirea lui MIN(a, b) în care a = x şi b = y, instrucţiunea printf() va arata astfel : printf("Numarul mai mic este: %d",(x<y)?x:y); Directiva #error 190
  • 197. Directiva #error forţează compilatorul să stopeze operaţia de compilare când această este intilnita în program. Este utilizata în primul rind pentru depanarea programelor. Forma generală a directivei este: #error mesaj_de_eroare Aceasta linie determină procesorul să scrie mesajul de eroare şi să termine compilarea. Directiva # include Directiva # include comandă compilatorului să includă în fişierul ce conţine directiva #include un alt fişier sursă al cărui nume este specificat în directivă. Formele directivei sunt : # include <nume_fisier> # include "nume_fisier" Prima formă se referă la fişiere header (cu extensia .h) care se găsesc în subdirectorul include din fiecare mediu de programare C, iar cea de-a doua la fişiere header create în directorul de lucru al utilizatorului (directorul curent). Directivele # include pot fi folosite şi una în interiorul celeilalte. 9.2. Directive pentru compilare condiţionată Limbajul C conţine câteva directive care ne permit să compilăm selectiv anumite porţiuni de program. Directivele #if, #else, #elif şi #endif Forma generală a lui #if este: #if expresie_constanta secventa de instructiuni #endif Dacă expresie_constanta este adevărată, compilatorul va compila fragmentul de cod cuprins între #if şi #endif, iar dacă expresie_constanta este falsă, compilatorul va sări peste acest bloc. Exemplu: #define MAX 100 void main() { #if MAX > 99 printf("Se compileaza pentru tablouri > 99n"); #endif } Observaţie: Expresie_constanta se evaluează în timpul compilării. De aceea, aceasta trebuie să conţină numai variabile constante definite 191
  • 198. anterior utilizării lor. Expresie_constanta nu trebuie să conţină operatorul sizeof. Directiva #else lucrează similar cu instrucţiunea else determinând o alternativă de compilare. Exemplu : # define MAX 10 void main() { #if MAX > 99 printf("Se compileaza pentru tablouri > 99n"); #else printf("Se compileaza pentru tablouri < 99n"); #endif } Deoarece MAX = 10, compilatorul va compila numai codul cuprins între #else şi #endif, deci va tipări mesajul : Se compilează pentru tablouri < 99 Directiva #elif inlocuieşte "else if" şi este utilizată pentru realizarea opţiunilor multiple de tip if / else / if utilizate la compilare. Forma generală a directivelor #if , #elif, #endif este: #if expresie Secventa_de_instructiuni #elif expresie_1 Secventa_de_instructiuni_1 #elif expresie_2 Secventa_de_instructiuni_2 . . . . . . . . . . . . . . #elif expresie_N Secventa_de_instructiuni_N #endif Dacă "expresie" este adevărată se compilează "Secventa_de_instructiuni" şi nu se mai tastează nici o altă expresie #elif. Dacă "expresie" este falsă, compilatorul verifică următoarele expresii în serie, compilându-se "Secventa_de_instructiuni_i", corespunzatoare primei "expresie_i" adevărată, i = 1, 2, . . . , N. Directivele #if şi #elif se pot include unele pe altele. Exemplu: #if MAX > 100 #if VERSIUNE_SERIALA int port = 198; #elif int port = 200; #endif #else char out_buffer[100]; #endif Directivele #ifdef şi #ifndef 192
  • 199. O altă metodă de compilare condiţionată utilizează directivele #ifdef şi #ifndef, care înseamnă "if defined" şi "if not defined". Forma generală a lui #ifdef este : #ifdef macro_name Secventa_de_instructiuni #endif Dacă anterior apariţiei secvenţei de mai sus s-a definit un macro_name printr-o directivă #define, compilatorul va compila "Secventa_de_instructiuni" dintre #ifdef şi #endif. Forma generală a lui #ifndef este: #ifndef macro_name Secventa_de_instructiuni #endif Dacă macro_name nu este definit prîntr-o directivă #define, atunci se va compila blocul dintre #ifndef şi #endif. Atât #ifdef, cât şi #ifndef pot utiliza un #else, dar nu #elif. Exemplu: # define TOM 10 void main() { #ifdef TOM printf("Hello TOM !n"); #else printf("Hello anyone !n"); #endif #ifndef JERY printf ("Jery not defined n"); #endif } Programul va afişa: Hello TOM ! şi JERY not defined. Dacă nu s-a definit TOM, atunci programul va afişa : Hello anyone !. Directiva #undef Se utilizează pentru a anula definiţia unui macro_name definit printr-o directivă #define. Exemplu: #define LENGTH 100 #define WIDTH 100 char array[LENGTH][WIDTH]; #undef LENGTH #undef WIDTH Acest program defineşte atât LENGTH, cât şi WIDTH până se întâlneşte directiva #undef. Principala utilizare a lui #undef este de a permite localizarea unui macro_name numai în anumite secţiuni ale programului. 193
  • 200. Directiva #line O linie cu una din formele: #line numar "nume_fiaier" #line numar determină compilatorul să considere, din motive de diagnosticare a erorilor, că numărul de linie al urmatoarei linii din programul sursă este dat de "număr", iar numele fişierului în care se află programul sursă este dat de "nume_fişier". Dacă lipseste "nume_fişier", programul sursă se află în fişierul curent. Exemplu: Următoarea secvenţă face ca numărul de linie să înceapă cu 100. # line 100 void main() /* linia 100 */ { /* linia 101 */ printf ("%dn" , __LINE__); /* linia 102 */ } Instructiunea printf() va afişa valoarea 102 deoarece această reprezintă a treia linie în program, după instrucţiunea #line 100. Directiva #pragma O linie de control de forma: #pragma nume determină compilatorul să realizeze o acţiune care depinde de modul de implementare al directivei #pragma. "nume" este numele acţiunii #pragma dorite. Limbajul C defineşte două instrucţiuni #pragma: warn şi inline. Directiva warn determină compilatorul să emită un mesaj de avertisment. Forma generală a lui warn este : #pragma warn mesaj unde "mesaj" este unul din mesajele de avertisment definite în C. Forma generală a directivei inline este : #pragma inline şi avertizează compilatorul că programul sursă conţine şi cod în limbajul de asamblare. Directiva vidă O linie de forma: # nu are nici un efect. Macro_names (macrosimboluri) predefinite 194
  • 201. Limbajul C conţine câţiva identificatori predefiniţi, care la compilare se expandează pentru a produce informaţii speciale. Aceştia sunt: __LINE__ o constanta zecimală care conţine numele liniei sursă curente. __FILE__ un şir care conţine numele fişierului care se compilează. __DATA__ un şir care conţine data compilării sub forma luna/zi/an. __TIME__ un şir care conţine ora compilării sub form: hh:mm:ss __STDC__ constanta 1. Acest identificator este 1 numai în implementarile standard; dacă constanta este orice alt număr, atunci implementarea este diferită de cea standard. Aceste macrosimboluri, împreună cu simbolurile definite cu #define nu pot fi redefinite. 9.3. Modularizarea programelor De obicei (vezi [Mocanu, 2001] programele C constau din fişiere sursă unice, cu excepţia fişierelor header. Un singur fişier sursă este în general suficient în cazul programelor mici. Modularizarea internă este un principiu de bază al programării în C şi constă în utilizarea pe scară largă a funcţiilor definite de utilizator. Scrierea programului principal (main) se concentrează mai ales pe apelul acestor funcţii. În cazul în care corpul de definiţie al funcţiilor utilizator se află după corpul de definiţie main, este necesar ca să declarăm prototipul funcţiilor utilizate de main() pentru a informa corect compilatorul despre tipul variabilelor returnate de funcţii. O altă modalitate este aceea de a defini funcţiile utilizator înaintea funcţiei principale main(), caz în care nu mai sunt necesare prototipurile. Programul este modularizat cu ajutorul funcţiilor prin divizarea sa în nuclee funcţionale. Acestea pot fi comparate cu nişte mici piese de lego cu ajutorul cărora se pot construi ulterior structuri (programe) foarte complexe. Pe scurt, modularizarea internă constă în descompunerea sarcinii globale a unui program în funcţii de prelucrare distincte. O funcţie de uz general este o funcţie care poate fi folosită într- o varietate de situaţii şi, probabil, de către mai mulţi utilizatori. Este 195
  • 202. de preferat ca aceste funcţii de uz general să nu primească informaţii prin intermediul unor variabile globale ci prin intermediul parametrilor. Sporeşte astfel foarte mult flexibilitatea în folosirea acestor funcţii. Modularizarea externă constă în divizarea unui program foarte complex în mai multe subprograme. Astfel, un fişier sursă mai mare se poate diviza în două sau mai multe fişiere sursă mai mici. Evident, aceste fişiere sunt strâns legate între ele pentru a forma în final un tot unitar echivalent cu programul complex iniţial (dinainte de divizare). În figura de mai sus se prezintă un ecran al Microsoft Visual C+ + din MSDN 6.0 Noţiunea cea mai cuprinzătoare este aceea de Workspace (spaţiu de lucru) care cuprinde în esenţă o colecţie de proiecte corelate şi prelucrabile împreună. Un workspace cuprinde unul sau mai multe Projects (proiecte) dintre care numai unul este principal şi restul sunt subordonate (subprojects). Fiecare proiect este compus la rândul său din mai multe fişiere, de acelaşi tip sau de tipuri diferite. Prezentarea exhaustivă a organizării acestui mediu de dezvoltare a aplicaţiilor C/C++ este un demers în afara prezentei lucrări. Ceea ce 196
  • 203. merită să subliniem este faptul că, în cadrul cel mai întâlnit, anume un workspace care include un singur project, acest proiect conţine mai ales fişiere sursă şi fişiere de tip header. Aceste fişiere se numesc module. Modulul principal este fişierul care conţine funcţia principală main(). Celelalte fişiere sursă, dacă există, se numesc module secundare. De obicei, cu fiecare modul secundar se asociază un fişier header propriu separat. Acest fişier header trebuie să conţină toate directivele şi declaraţiile de variabile necesare pentru o corectă compilare separată a modulului cu care se asociază. Pentru a exemplifica cele de mai sus, vom modulariza un exemplu anterior, anume al unei baze de date simple. Workspace-ul va conţine un singur project, care va conţine următoarele 4 fişiere: bd_main.c local.h bd_bib.c local1.h bd_main.c (bd - bază de date) este modulul principal, cel care conţine funcţia main(). El are asociat fişierul header local.h. În mod asemănător, local1.h este fişierul header asociat cu modulul secundar bd_bib.c (bib - bibliotecă) care conţine toate definiţiile funcţiilor utilizator. Conţinutul lor este prezentat în continuare. Modulul bd_main.c este: # include "local.h" void main() { char choice; init_list(); for (; ;) { choice = menu(); switch (choice) { case 'e' : enter(); break; case 'd' : display(); break; case 's' : save(); break; case 'l' : load(); break; case 'q' : exit(); }}} Fişierul local.h conţine: # include <stdio.h> # include <ctype.h> # include <string.h> # define SIZE 100 struct addr { char name[20]; char street[30]; char city[15]; char state[10]; unsigned int zip; 197
  • 204. } addr_info[SIZE]; FILE *fp; extern void init_list(); extern char menu(); extern void enter(),save(),load(); extern void display(),exit(); Modulul bd_bib.c este: # include "local1.h" /* Functia init_list() */ void init_list() { register int t; for (t = 0; t < SIZE; t++) *addr_info[t].name = '0'; } /* Functia menu() */ char menu() { char s[5],ch; do { printf ("(E)ntern"); printf ("(D)isplayn"); printf ("(L)oadn"); printf ("(S)aven"); printf ("(Q)uitn"); 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);} 198
  • 205. /* Functia save() */ void save() { register int i; if ((fp = fopen("maillist", "wb")) == NULL) { printf (" Cannot open filen "); 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 filen "); 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 errorn"); } /* Functia display() */ void display(){ register int t; printf("n%20s","Name"); printf("%30s","Street"); printf("%15s","City"); printf("%10s","State"); printf("%5sn","Zip"); for (t=0;t<SIZE;t++) { if (*addr_info[t].name!='0') { printf("%20s",addr_info[t].name); printf("%30s",addr_info[t].street); printf("%15s",addr_info[t].city); printf("%10s",addr_info[t].state); printf("%5d",addr_info[t].zip); getchar();}}} Fişierul local1.h conţine: # include <stdio.h> # include <ctype.h> # include <string.h> # define SIZE 100 extern struct addr { char name[20]; char street[30]; char city[15]; 199
  • 206. char state[10]; unsigned int zip; } addr_info[SIZE]; extern FILE *fp; Se poate verifica cum fiecare modul în parte este compilabil fără erori, iar la link-editare nu se semnalează, de asemenea, erori. Capitolul X INTRĂRI/IEŞIRI 10.1. Funcţii de intrare şi ieşire - stdio.h Limbajul C nu dispune de instrucţiuni de intrare/ieşire. Aceste operaţii se realizează prin intermediul unor funcţii din biblioteca standard a limbajului C. Aceste funcţii pot fi aplicate în mod eficient la o gamă largă de aplicaţii datorită multiplelor facilităţi pe care le oferă. De asemenea, ele asigură o bună portabilitate a programelor, fiind implementate într-o formă compatibilă pe toate sistemele de operare. O altă caracteristică a limbajului C constă în faptul că nu există un sistem de gestionare a fişierelor care să permită organizări de date, aşa cum în alte limbaje există fişiere cu organizare relativă sau indexată. În limbajul C toate fişierele sunt tratate ca o înşiruire de octeţi, neexistând structuri de date specifice care să se aplice acestor fişiere. Programatorul poate să interpreteze datele după cum doreşte Prin urmare, prin scrierea/citirea datelor se scriu/citesc un număr de octeţi fără o interpretare specifică. Funcţiile de intrare/ieşire, tipurile şi macrodefiniţiile din "stdio.h" reprezintă aproape o treime din bibliotecă. În C, intrarea standard respectiv ieşirea standard sunt în mod implicit reprezentate de terminalul de la care s-a lansat programul. Prin fişier înţelegem o mulţime ordonată de elemente păstrate pe diferite suporturi. Aceste elemente se numesc înregistrări. Suporturile cele mai des utilizate sunt cele magnetice (floppy sau harddiscuri). Ele se mai numesc suporturi reutilizabile deoarece zona utilizată pentru păstrarea înregistrărilor unui fişier poate fi ulterior reutilizată ulterior pentru păstrarea înregistrărilor unui alt fişier.În C 200
  • 207. un fişier reprezintă o sursă sau o destinaţie de date, care poate fi asociată cu un disc sau cu alte periferice. Biblioteca acceptă fişiere de tip text şi binar, deşi în anumite sisteme, de exemplu UNIX, acestea sunt identice. Un fişier de tip text este o succesiune de linii, fiecare linie având zero sau mai multe caractere terminate cu ' n '. Într-o altă reprezentare, anumite caractere pot fi convertite într-o succesiune de caractere, adică să nu existe o relaţie unu la unu între caracterele scrise (citite) şi acţiunea perifericului. De exemplu, caracterul NL (new line), ' n ', corespunde grupului CR (carriage return) şi LF (line feed). În aceeaşi idee, se consideră că datele introduse de la un terminal formează un fişier de intrare. Înregistrarea se consideră că este formată de datele unui rând tastate de la terminal (tastatură, keyboard), deci caracterul de rând nou NL se consideră ca fiind terminator de înregistrare. În mod analog, datele care se afişează pe terminal (monitor, display) formează un fişier de ieşire. Şi în acest caz înregistrarea este formată din caracterele unui rând. Ceea ce este important de subliniat este că fişierele text pot fi accesate la nivel de octet sau de caracter, ele putând fi interpretate drept o colecţie de caractere, motiv pentru care se şi numesc fişiere text. Toate funcţiile de intrare/ ieşire folosite până acum se pot utiliza şi pentru fişierele text. Un fişier de tip binar este o succesiune de octeţi neprelucraţi care conţin date interne, cu proprietatea că dacă sunt scrise şi citite pe acelaşi sistem, datele sunt egale. Aceste fişiere sunt organizate ca date binare, adică octeţii nu sunt consideraţi ca fiind coduri de caractere. La fişierele binare înregistrarea se consideră că este o colecţie de date structurate numite articole. Structurile de date sunt pretabile pentru stocarea în astfel de fişiere Tratarea fişierelor se poate face la două nivele, inferior şi superior. Nivelul inferior de prelucrare a fişierelor oferă o tratare a fişierelor fără zone tampon (buffere), făcând apel direct la sistemul de operare. Rezervarea de zone tampon este lăsată pe seama utilizatorului. Fişierele de tip text se pretează la o astfel de tratare. Nivelul superior de prelucrare a fişierelor se bazează pe utilizarea unor proceduri specializate în prelucrarea fişierelor care printre altele pot rezerva şi gestiona automat zonele tampon necesare. 201
  • 208. Fişierele binare se pot manipula cu facilitate la acest nivel. Funcţiile specializate de nivel superior au denumiri asemănătoare cu cele de nivel inferior, doar prima literă a numelui este f. În practică operaţiile de intrare/ieşire (I/O) cu memoria externă (hard-disk sau floppy-disk) sunt mult mai lente decât cele cu memoria internă. Din această cauză, pentru a spori viteza de lucru, se încearcă să se reducă numărul de operaţii de acces la disc. În acest scop se folosesc bufferele. Un buffer este o zonă de memorie în care sistemul memorează o cantitate de informaţie (număr de octeţi), în general mai mare decât cantitatea solicitată de o operaţie de I/O. Dacă un program efectuează o operaţie de citire a 2 octeţi dintr-un fişier, atunci sistemul citeşte într-un buffer întreg sectorul de pe disc (512 octeţi) în care se găsesc şi cei 2 octeţi solicitaţi, eventual chiar mai mult, în funcţie de dimensiunea bufferului (zonei tampon). Dacă în continuare se vor solicita încâ 2 octeţi, aceştia vor fi preluaţi din bufferul din memorie, fără a mai fi nevoie să mai accesăm discul pe care se află fişierul din care se face citirea. Operaţiile de citire continuă în acest mod până la citirea tuturor octeţilor din buffer, moment în care se va face o nouă umplere a bufferului cu noi date prin citirea următorului sector de pe disc. Invers, dacă un program efectuează o operaţie de scriere a unui număr de octeţi pe disc, aceştia se vor înscrie de fapt secvenţial în buffer şi nu direct pe disc. Scrierea va continua astfel până la umplerea bufferului, moment în care sistemul de operare efectuează o operaţie de scriere a unui secto de pe disc cu cei 512 octeţi din buffer (se goleşte bufferul prin scriere). În acest fel, reducând numărul de operaţii de acces la disc (pentru citire sau scriere) creşte viteza de execuţie a programelor şi fiabilitatea dispozitivelor de I/O. Bufferele au o mărime implicită, dar ea poate fi modificată prin program. Dimensiunea trebuie aleasă în funcţie de aplicaţie ţinând cont de faptul că prin mărirea bufferului creşte viteza de execuţie dar scade dimensiunea memoriei disponibile codului programului şi invers, prin micşorarea sa creşte memoria cod disponibilă dar scade viteza de lucru. Bufferul de tastatură are, spre exemplu, dimensiunea de 256 octeţi, din care 254 sunt puşi la dispoziţie. Orice fişier are o înregistrare care marchează sfârşitul de fişier. În cazul fişierelor de intrare ale căror date se introduc de la terminal, sfârşitul de fişier se generează în funcţie de sistemul de operare 202
  • 209. considerat. Pentru sistemele de operare MS-DOS sau MIX şi RSX11 se tastează CTRL/Z iar pentru UNIX se tastează CTRL/U. Un fişier stocat pe suport magnetic se mai numeşte şi fişier extern. Când se prelucrează un astfel de fişier se crează o imagine a acestuia în memoria internă (RAM) a calculatorului. Această imagine se mai numeşte şi fişier intern. Un fişier intern este conectat la un fişier extern sau dispozitiv prin deschidere; conexiunea este întreruptă prin închidere. Deschiderea unui fişier întoarce un pointer la un obiect de tip FILE, care conţine toate datele necesare pentru controlul fişierului. Operaţiile de deschidere şi închidere a fişierelor se poate realiza în C prin funcţii specializate din biblioteca standard I/O a limbajului. Alte operaţii care sunt executate frecvent în prelucrarea fişierelor sunt: • Crearea unui fişier (acest fişier nu există în format extern) • Actualizarea unui fişier (deja existent) • Adăugarea de înregistrări unui fişier deja existent • Consultarea unui fişier • Poziţionarea într-un fişier • Redenumirea unui fişier • Ştergerea unui fişier Ca şi operaţiile de deschidere şi închidere de fişiere, operaţiile indicate mai sus pot fi realizate printr-un set de funcţii aflate în biblioteca standard I/O a limbajului. Aceste funcţii realizează acţiuni similare sub diferite sisteme de operare, dar multe dintre ele pot depinde de implementare. În cele ce urmează se prezintă funcţiile care au o utilizare comună pe diferite medii de programare şi sunt cele mai frecvent utilizate. 10.2. Operaţii cu fişiere În acest subcapitol vom detalia principalele operaţii efectuate asupra unor fişiere. În timpul lucrului cu fişierele, sistemul de operare păstrează un indicator de fişier care indică poziţia curentă în fişier, poziţie la care se va face următoarea operaţie de scriere sau citire. De exemplu, la deschiderea unui fişier pentru citire indicatorul de fişier va indica la începutul fişierului. Dacă se va face o operaţie de citire a 2 octeţi se 203
  • 210. vor citi octeţii cu numărul de ordine 0 şi 1 iar indicatorul va indica spre următorul octet, adică cel cu numărul de ordine 3. Pentru o mai corectă înţelegere a acestor funcţii le vom structura după nivelul la care se utilizează: inferior sau superior. În momentul începerii execuţiei unui program, interfeţele standard (cu ecranul, tastatura şi porturile seriale şi paralele) sunt deschise în mod text. Principalele funcţii sunt grupate în tabelul de mai jos: Descriere Nume funcţie de Nume funcţie de nivel inferior nivel superior Deschidere _open fopen Creare _creat fcreate Citire _read fread Scriere _write fwrite Închidere _close fclose Poziţionare _lseek fseek Ştergere _unlink remove Redenumire _rename rename În afara acestor funcţii principale mai există anumite funcţii specializate, cum ar fi: - Funcţii pentru prelucrarea pe caractere a unui fişier: putc (scriere caracter) şi getc (citire caracter). - Funcţii pentru Intrări/Ieşiri cu format: fscanf şi fprintf. - Funcţii pentru Intrări/Ieşiri de şiruri de caractere: fgets şi fputs. Pentru ca sistemul de operare să poată opera asupra fişierelor ca fluxuri (stream) de intrare/ieşire trebuie să cunoască anumite informaţii despre ele. Acest lucru se realizează prin operaţia de deschidere a fluxurilor (stream-urilor). Pointerul fişier În urma operaţiei de deschidere se crează în memorie o variabilă de tip structură FILE care este o structură predefinită. În această variabilă, care se numeşte bloc de control al fişierului, FCB (File Control Block) sistemul păstrează informaţii despre fişierul deschis, precum: • Nume 204
  • 211. Dimensiune • Atribute fişier • Descriptorul fişierului Un pointer-fişier este un pointer la informaţiile care definesc diferitele aspecte ale unui fişier: nume, stare, poziţie curentă. Un pointer-fişier este o variabilă pointer de tip FILE, definită în "stdio.h". Tipul FILE este un tip structurat care depinde de sistemul de operare. Dacă facem abstracţie de cazurile speciale de calculatoare tip VAX sau U3B, pe majoritatea implementărilor tipul FILE se defineşte prin următoarea structură: typedef struct { unsigned char *_ptr; int _cnt; unsigned char *_base; char _flag; char _file; } FILE; Variabila de tip FILE este creată şi gestionată de către suportul pentru exploatarea fişierelor în limbajul C. În urma deschiderii unui fişier, programul primeşte un pointer la variabila creată, deci un pointer la o structură de tip FILE. Se spune că s-a deschis un stream (flux de date). Toate operaţiile care se fac pe acest stream se referă la fişierul asociat stream-ului. În limbajul C există 5 stream-uri standard, definite în <stdio.h>: FILE *stdin; care se referă la dispozitivul standard de intrare (tastatura). Orice operaţie de citire de la stream-ul stdin înseamnă citire de la tastatură. Bufferul folosit are o dimensiune de 254 de caractere şi bufferul se goleşte la tastarea NL (‘n’). Se mai spune că stdin este cu buffer la nivel de linie. FILE *stdout; care se referă la dispozitivul standard de ieşire (ecranul). Orice operaţie de scriere la stream-ul stdout înseamnă scriere pe ecran. Spre deosebire de stdin, stdout este ne-bufferizat deoarece orice scriere pe ecran se face direct la scrierea unui caracter în fişierul stdout. FILE *stderr; care se referă la dispozitivul standard pentru afişarea mesajelor de eroare (ecranul). Este ne-bufferizat. FILE *stdprn; 205
  • 212. care se referă la primul port paralel PRN la care se conectează de obicei imprimanta (LPT). Este bufferizat la nivel linie. FILE *stdaux; care se referă la primul port serial COM1. Este ne-bufferizat. 10.3. Nivelul inferior de prelucrare a fişierelor La acest nivel operaţiile de prelucrare a fişierelor se execută fără o gestiune automată a zonelor tampon, făcându-se apel direct la sistemul de operare. Programatorul are în gestiune o zonă declarată drept buffer şi trebuie să ţină cont de faptul că această bufferizare este la nivel linie. Numele funcţiilor de nivel inferior, orientate pe text (transfer de octeţi) încep de obicei cu _ (underline). Dacă un fişier se deschide în modul text, atunci, în cazul citirii dintr-un fişier, secvenţa de octeţi CR-LF (0DH, 0AH) este translatată (înlocuită) cu un singur caracter LF, iar în cazul scrierii în fişier caracterul LF este expandat la secvenţa CR-LF. De asemenea, în cazul MS-DOS sau Windows CTRL/Z este interpretat în cazul citirii drept caracter de sfârşit de fişier (EOF). 10.3.1. Deschiderea unui fişier Orice fişier înainte de a fi prelucrat trebuie deschis, motiv pentru care operaţia de deschidere a unui fişier este de mare importanţă. Deschiderea unui fişier existent se realizează prin intermediul funcţiei _open. La revenirea din ea se returnează un aşa numit descriptor de fişier. Acesta este un număr întreg. El identifică în continuare fişierul respectiv în toate operaţiile realizate asupra lui. În forma cea mai simplă funcţia _open se apelează printr-o expresie de atribuire de forma: df = _open(spf,mod) unde: df – este un număr întreg care reprezintă descriptorul de fişier spf – este specificatorul fişierului care se deschide mod – defineşte modul de prelucrare a fişierului Specificatorul de fişier este fie un şir de caractere, fie un pointer spre un astfel de şir de caractere. Conţinutul şirului de caractere depinde de sistemul de operare folosit. În cea mai simplă formă el este un nume sau mai general o cale care indică plasamentul pe disc al fişierului care se operează. Fişierele deschise la acest nivel pot fi 206
  • 213. prelucrate în citire (consultare), scriere (adăugare de înregistrări) sau citire/scriere (actualizare sau punere la zi). Calea spre fişier trebuie să respecte convenţiile sistemului de operare MS-DOS în general. În cea mai simplă formă ea este un şir de caractere care defineşte numele fişierului, urmat de extensia fişierului. Aceasta presupune că fişierul se găseşte în directorul curent. Dacă fişierul nu se află în fişierul curent, atunci numele este precedat de o construcţie de forma: litera:nume_1nume_2…nume_k unde: litera – defineşte discul (în general A, B pentru floppy-disk şi C, D,.. pentru hard-disk) nume_i – este un nume de subdirector. Deoarece calea se include între ghilimele, caracterul ‘’ se dublează. Spre exemplu, putem folosi o comandă de deschidere de forma: int d; d=_open(“A:JOCBIO.C“,O_RDWR); caz în care fişierul BIO.C din directorul JOC de pe dscheta A se deschide în citire/scriere. În funcţie de operaţia dorită, mod poate avea valorile: 0 - pentru citire 1 - pentru scriere 2 - pentru citire/scriere Deschiderea unui fişier nu reuşeşte dacă unul dintre parametri este eronat. În acest caz funcţia _open returnează valoarea (-1). int _open( const char *filename, int oflag [, int pmode] ); este definiţia generală a funcţiei _open. Modul de acces mod se poate furniza în mod explicit printr-o variabilă de tip întreg (oflag) care poate avea valorile: Variabila mod Modul de deschidere a fişierului _O_RDONLY Fişierul se deschide numai în citire (read-only) Nu se poate specifica împreună cu _O_RDWR sau _O_WRONLY _O_WRONLY Fişierul se deschide numai în scriere (write-only) Nu se poate specifica împreună cu _O_RDWR sau _O_RDONLY _O_RDWR Fişierul se deschide în citire/scriere (read/write) 207
  • 214. _O_APPEND Fişierul se deschide pentru adăugarea de înregistrări la sfârşitul său. _O_CREAT Crează şi deschide un nou fişier pentru scriere. Nu are nici un efect dacă fişierul este deja existent. _O_BINARY Fişierul se prelucrează în mod binar _O_TEXT Fişierul este de tip text, adică se prelucrează pe caractere sau octeţi (implicit) Menţionăm că în MSDN aceste variabile se mai numesc şi oflag (open-flag) şi sunt definite în fişierul header FCNTL.H. În cazul în care oflag este _O_CREAT, atunci este necesară specificarea constantelor opţionale pmode, care se găsesc definite în SYSSTAT.H. Acestea sunt: _S_IREAD - este permisă numai citirea fişierului _S_IWRITE - este permisă şi citirea (permite efectiv citirea/scrierea fişierului) _S_IREAD | _S_IWRITE - este permisă şi scrierea şi citirea fişierului. Argumentul pmode este cerut numai când se specifică _O_CREAT. Dacă fişierul există deja, pmode este ignorat. Altcumva, pmode specifică setările de permisiune asupra fişerului care sunt activate când fişierul este închis pentru prima oară. _open aplică masca curentă de permisiune la fişier înainte de setarea accesului la fişier. Pentru a crea un fişier nou se va utiliza funcţia _creat pentru a-l deschide. De fapt se deschide prin creare un fişier inexistent. Funcţia este definită astfel: int _creat( const char *filename, int pmode ); în care parametrii au fost descrişi mai sus. Protecţia unui fişier este dependentă de sistemul de operare. Spre exemplu, în UNIX protecţia se defineşte prin 9 biţi ataşaţi oricărui fişier, grupaţi în 3 grupe de câte 3 biţi. Fiecare bit controlează o operaţie de citire, scriere, execuţie. Protecţia operaţiilor se exprimă faţă de proprietar, grup sau oricine altcineva. Numărul octal 0751 permite proprietarului toate cele 3 operaţii indicate mai sus (7 = 1112), grupul la care aparţine proprietarul poate citi şi executa fişierul (5 = 1012) iar alţi utilizatori pot numai executa fişierul (1 = 0012). Funcţia _creat poate fi apelată şi în cazul în care se deschide un fişier deja 208
  • 215. existent, caz în care se pierde conţinutul vechi al fişierului respectiv şi se crează în locul lui unul nou cu acelaşi nume. Fiecare din funcţiile _open sau _creat returnează un specificator de fişier (handle) pentru fişierul deschis. Acest specificator este o valoare întreagă pozitivă. Implicit, stdin are specificatorul 0, stdout şi stderr au specificatorii 1 respectiv 2 iar fişierele disc care sunt deschise primesc pe rând valorile 3, 4,..etc. până la numărul maxim admis de fişiere deschise. Valoarea returnată -1 indică o eroare de deschidere, în care caz variabila errno este setată la una din valorile: EACCES – (valoare 13) s-a încercat deschiderea pentru scriere a unui fişier read-only sau modul de partajare a fişierului nu permite operaţia specificată sau calea nu specifică un nume de fişier ci de director. EEXIST – (valoare 17) flagurile _O_CREAT şi _O_EXCL sunt specificate, dar numele de fişier este al unui fişier deja existent. EINVAL – (valoare 22) unul dintre argumentele oflag sau pmode sunt invalide. EMFILE – (valoare 24) nu mai sunt disponibile specificatoare de fişier (prea multe fişiere deschise). ENOENT – (valoare 2) fişierul sau calea nu au fost găsite. Variabila globală errno păstrează codurile de eroare folosite de funcţiile perror (print error) sau strerror (string error) pentru tratarea erorilor. Constantele manifest pentru aceste variabile sunt declarate în STDLIB.H după cum urmează: extern int _doserrno; extern int errno; errno este setată de o eroare într-un apel de funcţie la nivel de sistem (la nivelul inferior). Deoarece errno păstrează valoarea setată de ultimul apel, această valoare se poate modifica la fiecare apel de funcţie sistem. Din această cauză errno trebuie verificată imediat înainte şi după un apel care poate s-o modifice. Toate valorile errno, definite drept constante manifest în ERRNO.H, sunt compatibile UNIX. Valorile valide pentru aplicaţiile Windows pe 32 de biţi sunt un subset al acestor valori UNIX. Valorile specificate mai sus sunt valabile pentru aplicaţii Windows pe 32 de biţi. La o eroare, errno nu este setată în mod necesar la aceeaşi valoare cu codul erorii de sistem. Numai pentru operaţii de I/O se 209
  • 216. foloseşte _doserrno pentru a accesa codul erorilor sistemului de operare echivalent cu codurile semnalate de errno. Exemplu: Acest program foloseste _open pentru a deschide un fisier numit OPEN.C pentru citire si un fisier numit OPEN.OUT scriere. Apoi fisierele sunt inchise #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <io.h> #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 filen" ); _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 filen" ); _close( fh2 );}} Prin execuţia acestui program se vor obţine următoarele mesaje pe display: open failed on input file: No such file or directory Open succeeded on output file Press any key to continue 10.3.2. Scrierea într-un fişier Scrierea într-un fişier se realizează folosind funcţia _write. Se presupune că fişierul respectiv a fost în prealabil deschis prin funcţiile _open sau _creat. Ea este asemănătoare cu funcţia _read, doar că se realizează transferul de date în sens invers şi anume din memorie pe suportul fiierului. Funcţia _write, ca şi _read, se apelează printr-o atribuire de forma: nr = _read(df,zt,n) unde: nr – este o variabilă de tip întreg căreia i se atribuie numărul de octeţi scrişi în fişier. 210
  • 217. df – este descriptorul de fişier returnat de funcţia _open la deschiderea sau _creat la crearea fişierului. zt - este un pointer spre zona tampon definită de utilizator, zonă din care se face scrierea. n – este dimensiunea zonei tampon sau numărul de octeţi care se doreşte să se scrie. Definiţia funcţiei este: int _write( int handle, const void *buffer, unsigned int count ); Funcţia _write scrie count octeţi din buffer în fişierul asociat cu descriptorul handle. Operaţia de scriere începe la poziţia curentă a pointerului de fişier asociat cu fişierul dat. Dacă fişierul este deschis cu indicatorul _O_APPEND, operaţia de scriere începe la sfârşitul fişierului. După o operaţie de scriere pointerul de fişier este incrementat cu numărul de biţi scrişi efectiv. Dacă fişierul a fost deschis în mod text (implicit), atunci _write tratează CTRL/Z drept un caracter ce indică sfârşitul logic al fişierului. Când se scrie într-un dispozitiv, _write tratează CTRL/Z din buffer drept terminator al operaţiei de ieşire. În general trebuie ca la revenirea din funcţia _write să avem nr=n, ceea ce semnifică faptul că s-au scris pe disc exact numărul de biţi din buffer. În caz contrar scrierea este eronată: aceasta semnifică faptul că pe disc a rămas mai puţin spaţiu (în octeţi) decât numărul de octeţi ai bufferului. Dacă valoarea returnată este -1, se semnalizează eşecul operaţiei de scriere. În acest caz variabila globală errno poate avea una din valorile EBADF (care semnifică un descriptor de fişier invalid sau că fişierul nu a fost deschis pentru scriere) sau ENOSPC (care semnifică lipsa spaţiului pe disc pentru operaţia de scriere). Funcţia _write poate fi utilizată pentru a scrie pe ieşirile standard (display). Astfel, pentru a scrie pe ieşirea standard identificată prin stdout se foloseşte descriptorul 1, iar pentru a scrie pe ieşirea standard pentru erori, stderr, se foloseşte descriptorul de fişier 2. De asemenea, în acest caz nu este nevoie să apelăm funcţia _open sau _creat deoarece fişierele respective se deschid automat la lansarea programului. Exemplu: /*Acest program deschide un fisier pentru scriere si foloseste _write pentru a scrie octeti in fisier*/ #include <io.h> #include <stdio.h> #include <stdlib.h> #include <fcntl.h> 211
  • 218. #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) perror( "Write failed" ); else printf( "Wrote %u bytes to filen", byteswritten ); _close( fh );}} În urma execuţiei programului, se va afişa mesajul: Wrote 36 bytes to file Press any key to continue 10.3.3. Citirea dintr-un fişier Citirea dintr-un fişier deschis în prealabil cu funcţia _open se realizează cu ajutorul funcţiei _read. Ea returnează numărul efectiv al octeţilor citiţi din fişier. Funcţia _read se poate apela folosind o expresie de atribuire de forma: nr = _read(df,zt,n) cu definiţia generală: int _read( int handle, void *buffer, unsigned int count ); unde: nr – este o variabilă de tip întreg căreia i se atribuie numărul de octeţi citiţi din fişier. df – este descriptorul de fişier returnat de funcţia open la deschiderea sau creat la crearea fişierului. zt - este un pointer spre zona tampon definită de utilizator, zonă în care se face citirea. n – reprezintă numărul de biţi care se citesc Funcţia _read citeşte maximum count octeţi în buffer din fişierul asociat cu descriptorul handle. Operaţia de citire începe de pe poziţia curentă îndicată de pointerul de fişier asociat cu fişierul dat. După operaţia de citire, pointerul de fişier indică spre următorul octet necitit din fişier. Dacă fişierul a fost deschis în mod text, citirea se termină la întâlnirea caracterului CTRL/Z, care este interpretat drept indicator de sfârşit de fişier. 212
  • 219. _read returnează numărul de biţi citiţi din fişier, care poate fi mai mic decât count dacă sunt mai puţini decât count octeţi rămaşi în fişier sau dacă fişierul este deschis în mod text. În acest caz fiecare pereche CR-LF (carriage return–linefeed) (CR-LF) este înlocuită cu un singur caracter LF. Numai acest caracter LF se consideră în valoarea returnată. Înlocuirea nu afectează pointerul de fişier. Dacă funcţia încearcă să citească după sfârşitul fişierului, se returnează valoarea 0. Dacă descriptorul de fişier (handle) este invalid sau dacă fişierul nu este deschis pentru citire sau dacă este blocat, funcţia returnează valoarea negativă -1 şi setează variabila errno la EBADF. Tipul erorii şi depistarea ei este dependentă de sistemul de operare utilizat. Dacă n = 1, se citeşte un singur octet. De obicei, nu este eficient să se citească câte un octet dintr-un fişier, deoarece apelurile multiple ale funcţiei _read pot conduce la un consum de timp apreciabil. Dimensiunea maximă a lui n este dependentă de sistemul de operare. O valoare utilizată frecvent este 512, valoare optimă pentru MS-DOS sau pentru UNIX. Funcţia _read citeşte maximum count biţi în zona buffer din fişierul cu descriptorul handle. Operaţia de citire începe de la poziţia curentă a pointerului de fişier asociat cu fişierul respectiv. După o operaţie de citire, pointerul fişier indică spre următorul caracter (octet) necitit din fişier. Dacă fişierul a fost deschis în mod text, _read se termină când se întâlnete indicatorul de fişier CTRL/Z. Funcţia _read poate fi utilizată pentru a citi de la intrarea standard (tastatură). În acest caz descriptorul de fişier are valoarea 0. De asemenea, în acest caz nu este nevoie să apelăm funcţia _open deoarece fişierul se deschide automat la lansarea programului. Exemplu: /* Acest program deschide fisierul WRITE.O creat anterior si incearca sa citeasca 60000 octeti din fisier folosind _read. Apoi va afisa numarul de octeti cititi */ #include <fcntl.h> /* Necesara numai pentru definirea _O_RDWR */ #include <io.h> #include <stdlib.h> #include <stdio.h> char buffer[60000]; void main( void ) { int fh; 213
  • 220. 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 printf( "Read %u bytes from filen", bytesread ); _close( fh );} La execuţia programului se va afişa următorul mesaj: Read 36 bytes from file Press any key to continue 10.3.4. Închiderea unui fişier După terminarea prelucrării unui fişier el trebuie închis. Acest lucru se realizează automat dacă programul se termină prin apelul funcţiei exit. Programatorul poate închide un fişier folosind funcţia _close. Se recomandă ca programatorul să închidă orice fişier de îndată ce s-a terminat prelucrarea lui, deoarece numărul fişierelor ce pot fi deschise simultan este limitat între 15 şi 25, în funcţie de sistemul de operare. Menţionăm că fişierele corespunzătoare intrărilor şi ieşirilor standard nu trebuie închise de programator. Definiţia funcţiei este: int _close( int handle ); Funcţia _close închide fişierul asociat cu descriptorul handle. Funcţia _close returnează valoarea 0 la o închidere reuşită şi -1 în caz de incident. Apelul ei se realizează printr-o expresie de atribuire de forma: v =_ close(df) unde: v – este variabila de tip întreg ce preia valoarea returnată de funcţie df – este descriptorul de fişier (handle) al fişierului pe care dorim să-l închidem. 10.3.5. Poziţionarea într-un fişier Operaţiile de citire/scriere într-un fişier se execută secvenţial, astfel încât: - fiecare apel al funcţiei _read citeşte înregistrarea din poziţia următoare din fişier 214
  • 221. - fiecare apel al funcţiei _write scrie înregistrarea în poziţia următoare din fişier. Acest mod de acces la fişier se numeşte secvenţial şi el este util când dorim să prelucrăm fiecare înregistrare a fişierului. În practică apar însă şi situaţii în care noi dorim să scriem şi să citim înregistrări într-o ordine oarecare. În acest caz se spune că accesul la fişier este aleator. Pentru a realiza un acces aleator este nevoie să ne putem poziţiona oriunde în fişierul respectiv O astfel de poziţionare este posibilă pe hard-uri şi floppy-uri prin funcţia _lseek. Definiţia funcţiei este: long _lseek( int handle, long offset, int origin ); Funcţia _lseek mută pointerul de fişier asociat cu descriptorul handle (df) pe o nouă locaţie care este situată la offset octeţi de origin. Următoarea operaţie de citire/scriere se va efectua de la noua locaţie. Argumentul origin trebuie să fie una dintre următoarele constante, definite în STDIO.H: SEEK_SET – începutul fişierului (valoare 0) SEEK_CUR – poziţia curentă a pointerului de fişier (valoare 1) SEEK_END – sfârşitul fişierului (valoare implicită 2) Funcţia _lseek returnează valoarea 0 la poziţionare corectă şi -1 la incident. Ea poate fi apelată prin: v = _lseek(df, deplasament, origine) unde: v – este o variabilă de tip întreg căreia i se atribuie valoarea returnată de către funcţie (0 sau -1) df – este descriptorul de fişier (handle) a cărui valoare a fost definită la deschiderea sau crearea fişierului. deplasament – este o variabilă de tip long şi conţine numărul de octeţi peste care se va deplasa capul de citire/scriere al discului. origine – are una din valorile 0 - deplasamentul se socoteşte de la începutul fişierului; 1 - deplasamentul se socoteşte din poziţia curentă a capului de citire/ scriere; 2 - deplasamentul se socoteşte de la sfârşitul fişierului. Menţionăm că prin apelul lui _lseek nu se realizează nici un fel de transfer de informaţie ci numai poziţionarea în fişier. Operaţia următoare realizată prin apelul funcţiei _read sau _write se va realiza din această poziţie. Spre exemplu, apelul: v = _lseek(df, 0l, 2) 215
  • 222. permite să se facă poziţionarea la sfârşitul fişierului. În continuare se pot adăuga articole folosind funcţia _write. Poziţionarea la începutul fişierului se face prin apelul: v = _lseek(df, 0l, 0) Exemplu: #include <io.h> #include <fcntl.h> #include <stdlib.h> #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 = %ldn", 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 = %ldn", 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 = %ldn", pos ); _close( fh );} În urma execuţiei programului se va afişa: Pozitia pentru inceputul fisierului = 0 Pozitia curenta = 10 Pozitia ultima este = 36 Press any key to continue 10.3.6 Ştergerea unui fişier Un fişier poate fi şters apelând funcţia _unlink astfel: 216
  • 223. v = _unlink(spf) unde v este o variabilă de tip întreg căreia i se atribuie valoarea 0 pentru ştergere reuşită şi (-1) pentru ştergere nereuşită. spf este specificatorul de fişier folosit la deschidere a fişierului. Definiţia funcţiei este: int _unlink( const char *filename ); Funcţia _unlink şterge de pe disc fişierul specificat prin filename. Exemplu: /* Acest program sterge fisierul WRITE.O creat si prelucrat anterior. */ #include <stdio.h> void main( void ) { if( _unlink( "write.o" ) == -1 ) perror( "Nu se poate sterge 'WRITE.O'" ); else printf( "S-a sters 'WRITE.O'n" );} În urma execuţiei programului se afişează: S-a sters 'WRITE.O' Press any key to continue 10.3.7. Exemple de utilizare a funcţiilor de intrare/ieşire de nivel inferior 1. Să se scrie un program care copiază intrarea standard la ieşirea standard. Această problemă se poate rezolva uşor prin folosirea funcţiilor getchar şi putchar. Acum o vom rezolva folosind funcţiile _read şi _write. # include <stdio.h> # include <io.h> void main() /* copiaza intrarea standard la iesirea standard */ { char c[1]; while (_read(0,c,1)>0) _write(1,c,1);} Menţionăm că cel de-al doilea parametru al funcţiei _read sau _write trebuie să fie pointer spre caractere. Lucrul la nivelul inferior nu este chiar atât de simplu pe cât pare. Vom ilustra în continuare responsabilitatea pe care o are programatorul în gestionarea zonelor tampon. Să considerăm exemplul anterior în care zona tampon o mărim la 3 caractere, deci programul arată astfel: # include <stdio.h> 217
  • 224. # include <io.h> void main() { char c[3]; while (_read(0,c,3)>0) _write(1,c,3);} Citirea nu se va opri după 3 caractere, ci funcţia _read va continua să funcţioneze până la tastarea ENTER (CR+LF). Imediat funcţia _read va tipări grupele de 3 caractere introduse, inclusiv grupul final CR+LF. Zona tampon definită este supraînscrisă de fiecare dată când se introduc noi caractere. Dacă de la tastatură vom introduce 123456<CR><LF> atunci se va tipări primul grup (prima înscriere a zonei tampon) 123, apoi a doua grupă 456 şi grupul <CR> şi <LF> va supraînscrie primele două caractere ale bufferului, anume codurile ASCII ale lui 4 şi 5 şi se va tipări <CR><LF>6. 123456 123456 6 Primul grup 123456 este scris prin ecou de la tastatură, iar următoru se înscrie de către program. Dacă în continuare vom introduce 1<ENTER> atunci se va tipări 1 urmat de două rânduri noi deoarece fiecare CR sau LF sunt expandate de stdout în perechi <CR><LF>. Dacă mărim la 5 dimensiunea bufferului şi de la tastatură introducem 12<ENTER>, atunci se va tipări 12 12 ¦ deoarece cel de-al 5-lea octet al bufferului nu a fost alocat prin citire, având o valoare nedefinită. Problemele de mai sus legate de gestiunea bufferului în/din care se face citirea/scrierea pot fi depăşite cu o modificare simplă, prezentată mai jos. Prin scriere nu se vor trimite spre stdout decât numărul de caractere citit de la stdin. # include <stdio.h> # include <io.h> # define LZT 10 // lungime zona tampon void main() /* copiaza intrarea standard la iesirea standard */ 218
  • 225. { char zt[LZT]; int n; while ((n=_read(0,zt,LZT))>0) _write(1,zt,n);} Programatorul trebuie să ţină cont însă şi de alte amănunte cum ar fi dimensiunea implicită a bufferului stdin, care este de 254 de caractere. 2. Să se scrie un program care citeşte un şir de numere flotante de la intrarea standard şi crează 2 fişiere fis1.dat şi fis2.dat, primul conţinând numerele de ordin impar citite de la intrarea standard (primul, al 3-lea, al 5-lea, etc.) iar cel de-al doilea pe cele de ordin par citite de la aceeaşi intrare. Apoi să se listeze, la ieşirea standard, cele două fişiere în ordinea fis1.dat, fis2.dat câte un număr pe un rând în formatul număr de ordine: număr Vom scrie programul folosindu-ne de funcţii definite de utilizator care să facă apel la funcţiile de nivel inferior. Programul arată astfel: # include <stdio.h> # include <io.h> # include <sys/types.h> # include <sys/stat.h> # include <fcntl.h> #include <stdlib.h> char nume1[]="fis1.dat"; char nume2[]="fis2.dat"; union unr { float nr; char tnr[sizeof(float)];}; union unr nrcit; int creare_fis(const char *nume) { int df; if ((df=_creat(nume,_S_IWRITE|_S_IREAD))==-1) { printf("%s: ",nume); printf("Nu se poate deschide fisierul in crearen"); 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 fisieruluin");exit(1);}} 219
  • 226. 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 fisieruluin"); 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 citiren"); 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: %gn",j,nrcit.nr);j+=2;} if (i<0) { printf("%s: ",nume); printf("Eroare la citire din fisieruln"); exit(1);} _close(df);} void main() { int df1,df2; df1=creare_fis(nume1); df2=creare_fis(nume2); date_fis(df1,nume1,df2,nume2); inchid_fis(df1,nume1);inchid_fis(df2,nume2); df1=deschid_fis_cit(nume1); df2=deschid_fis_cit(nume2); list_fis(df1,nume1,1);list_fis(df2,nume2,2);} 3. Să se realizeze programul de mai sus folosind un singur fişier fis.dat. Programul va diferi faţă de cel anterior prin faptul că înregistrările se stochează într-un singur fişier, deci funcţia de listare se va modifica pentru citirea din 2 în 2 a înregistrărilor. După fiecare citire din fişier, se va face un salt cu o înregistrare pentru a poziţiona capul de citire/scriere peste înregistrarea următoare. # include <stdio.h> 220
  • 227. # 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 crearen"); 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 fisieruluin");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 fisieruluin"); 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 citiren"); 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: %gn",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 fisieruln"); 221
  • 228. 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: %gn",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 fisieruln"); exit(1);} _close(df);} void main() { int df; df=creare_fis(nume); date_fis(df,nume); inchid_fis(df,nume); df=deschid_fis_cit(nume); list_fis(df,nume);} Atragem atenţia asupra modului în care lucrează funcţiile de intrare/ieşire pentru stdin şi stdout faţă de cele pentru disc. Dacă intrările şi ieşirile pentru perifericele standard le putem executa în formatul dorit cu ajutorul funcţiilor specializate scanf şi printf, pentru lucrul cu discul variabila float este tratată sub forma unui grup de 4 octeţi care se scriu sau se citesc pe disc aşa cum este reprezentarea lor internă. Există funcţii specializate pentru scrierea/citirea pe disc cu format, dar care sunt de nivel superior. Ceea ce merită să subliniem este faptul că echivalentele de nivel superior pentru fişiere ale funcţiilor printf() şi scanf() sunt fprintf() şi fscanf(). Echipamentele periferice pot fi considerate fişiere externe şi deci funcţiile specializate pentru I/O cu fişiere pot fi folosite şi pentru operaţii de I/O cu echipamentele periferice. Funcţiile printf şi scanf sunt proiectate pentru a lucra implicit cu fişierele stdout respectiv stdin, deci cu monitorul şi tastatura. 10.4. Nivelul superior de prelucrare a fişierelor Nivelul superior de prelucrare a fişierelor se referă la aşa numitul format binar de reprezentare a informaţiei în fişiere care la rândul său face apel la informaţia structurată. Bufferul este alocat automat şi gestionat de funcţii C specializate. 222
  • 229. 10.4.1. Funcţia fopen() Funcţia fopen se apelează printr-o expresie de atribuire de forma: pf = fopen(spf,mod) unde: pf - este un pointer spre tipul FILE spf – este specificatorul fişierului care se deschide mod – este un şir de caractere care defineşte modul în care se deschide fişierul. Forma generală de declarare a funcţiei fopen() este: FILE *fopen(char *filename, char *mode); Funcţia deschide fişierul al cărui nume este specificat prin "filename" (de obicei un fişier disc) şi întoarce un pointer la FILE pentru operaţie reuşită şi NULL pentru operaţie nereuşită. Varibilele permise pentru modul "mode" sunt: a _O_WRONLY | _O_APPEND (usual _O_WRONLY | _O_CREAT | _O_APPEND) a+ _O_RDWR | _O_APPEND (usual _O_RDWR | _O_APPEND | _O_CREAT ) r _O_RDONLY r+ _O_RDWR w _O_WRONLY(usual _O_WRONLY | _O_CREAT | _O_TRUNC) w+ _O_RDWR (usual _O_RDWR | _O_CREAT | _O_TRUNC) b _O_BINARY t _O_TEXT c Nimic n Nimic Modul "a" nu şterge markerul de sfârşit d fişier EOF înainte de a adăuga la sfârşitul fişierului. După ce s-a făcut o adăugare, comanda MS-DOS TYPE tipăreşte datele până la markerul original EOF şi nu până la ultima dată adăugată. Modul "a+" şterge identificatorul de sfârşit de fişier EOF înainte de adăugarea de înregistrări la sfârşitul fişierului. După adăugare comanda MS-DOS TYPE va tipări toate 223
  • 230. datele conţinute în fiier. Modul "a+" este cerut pentru adăugarea la sfârşitul unui fişier care are marker terminator CTRL/Z = EOF. Dacă modul "mode" include "b" după litera iniţială, ca în "rb" sau "w+b" se indică un fişier binar. Numele fişierului conţine cel mult FILENAME_MAX caractere. La un moment dat pot fi deschise cel mult FOPEN_MAX fişiere. Menţionăm că stdin, stdout şi stderr sunt pointeri spre tipul FILE şi permit ca funcţiile de nivel superior de prelucrare a fişierelor să poată trata intrarea standard, ieşirea standard şi ieşirea standard pentru erori, la fel ca şi restul fişierelor. Singura deosebire constă în faptul că în acest caz programatorul nu trebuie să deschidă sau să închidă fişierele respective. Exemplu: FILE *fp, *fopen(); /* se declara pointerii de tip file *fp si *fopen() */ fp = fopen("test","w"); /* se deschide fisierul " test " pentru screiere */ Pentru detectarea unei erori la deschiderea unui fişier se utilizează secvenţa: if ((fp = fopen("test", "w")) == NULL) { puts ("Cannot open filen"); exit(1); } Dacă pentru operaţia de citire se încearcă deschiderea unui fişier inexistent, fopen() va returna o eroare. 10.4.2. Funcţia fclose() Forma generală de declarare a funcţiei fclose() este: int fclose(FILE *fp); unde "fp" este pointerul la fişier returnat după apelul funcţiei fopen(). Funcţia fclose() se utilizează pentru a închide un fişier deschis cu fopen(). Funcţia fclose() scrie mai întâi în fişier datele rămase în fişierul buffer apoi închide fişierul. fclose() întoarce zero (0) pentru închidere reuşită şi EOF (-1) dacă apare o eroare. La execuţia funcţiei exit se închid automat toate fişierele deschise. Cu toate acestea, se recomandă ca programatorul să închidă un fişier de îndată ce s-a terminat prelucrarea lui, altfel se poate ajunge în situaţia de a se depăşi numărul limită admis pentru fişierele care pot fi simultan deschise într-un program. Exemplu: /* Acest program deschide fisierele numite "test1.c" si "test2.c".Apoi foloseste fclose pentru a inchide "test1.c" si _fcloseall pentru a inchide restul fisierelor deschise */ #include <stdio.h> 224
  • 231. 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 deschisn" ); else printf( "Fisierul 'test1.c' a fost deschisn" ); /* Deschidere pentru scriere */ if( (stream2 = fopen( "test2.c", "w+" )) == NULL ) printf( "Fisierul 'test2.c' nu a fost deschisn" ); else printf( "Fisierul 'test2.c' a fost deschisn" ); /* Inchide fisierul cu pointerul stream1 */ if( fclose( stream1 ) ) printf( "Fisierul 'test1.c' nu a fost inchisn" ); /* Toate celelalte fisiere se inchid: */ numclosed = _fcloseall( ); printf( "Numarul fisierelor inchise cu _fcloseall: %un", numclosed );} În urma execuţiei programului se obţine: Fisierul 'test1.c' a fost deschis Fisierul 'test2.c' a fost deschis Numarul fisierelor inchise cu _fcloseall: 1 Press any key to continue 10.4.3. Funcţiile rename() şi remove() Funcţia rename() schimbă numele vechi al fişierului, "oldname", cu numele nou, "newname". Întoarce o valoare diferită de zero dacă incercarea nu reuseste. int rename (char *oldname, char *newname); Funcţia remove() int remove(char *filename); Funcţia remove() elimină fişierul cu numele specificat, astfel încât o incercare ulterioară de deschidere a fişierului va eşua. Întoarce o valoare diferită de zero dacă încercarea reuşeşte. 10.4.4. Funcţii de tratare a erorilor a) Funcţia ferror() Aceasta funcţie determină dacă în urma unei operaţii cu fişiere s- a produs sau nu o eroare. Forma generală de declarare este: int ferror(FILE *fp) 225
  • 232. unde "fp" este un pointer la fişier. Funcţia ferror() întoarce o valoare diferită de zero dacă s-a detectat o eroare şi o valoare 0 dacă nu s-a detectat nici o eroare. b) Funcţia feof() int feof(FILE *fp) Funcţia feof() întoarce o valoare diferită de zero dacă indicatorul de sfârşit de fişier este valid şi o valoare zero dacă indicatorul de sfârşit de fişier nu este valid. c) Funcţia perror() void perror(const char *s) Funcţia perror() scrie s şi un mesaj de eroare care depinde de implementare, corespunzator cu intregul din errno.h, astfel: fprintf(stderr,"%s %sn, s, "error message") 10.4.5. Funcţii cu acces direct a) Funcţia fread() Permite citirea unui bloc de date. Forma generală de declarare: int fread(void *buffer,int num_bytes,int count,FILE *fp) Funcţia fread() citeşte din fişierul specificat prin "fp" cel mult "count" obiecte, fiecare obiect având lungimea egală cu "num_bytes" şi îi trimite în zona de memorie indirectată prin "buffer" . *fp este un pointer fişier la fişierul deschis anterior cu fopen(). Funcţia întoarce numărul de obiecte citite, acesta putând fi mai mic decât cele cerute. Pentru a determina starea funcţiei se pot utiliza funcţiile feof(), ferror(). b) Funcţia fwrite() Permite scrierea unui bloc de date. Forma generală de declarare: int fwrite(void *buffer,int num_bytes,int count, FILE *fp) Funcţia fwrite() scrie din zona (tabloul) "buffer" în fişierul indirectat prin "fp", "count" obiect de lungime "nr_bytes". Funcţia întoarce numărul de obiecte scrise, care, în caz de eroare este mai mic decât "count". Exemplu: Programul următor scrise un număr real pe disc # include "stdio.h" void main() { FILE *fp; float f = 12.23; if ((fp = fopen ("test", "wb")) == NULL){ printf (" Cannot open filen "); 226
  • 233. return; } fwrite (&f, sizeof (float), 1, fp); fclose (fp); } Aşa cum se vede din acest program, "buffer" poate fi o simplă variabilă. Exemplu: Programul următor copiază un tablou de numere reale "balance", în fişierul "balance": # include "stdio.h" void main() { FILE *fp; float balance[100]; /* tabloul balance */ if ((fp = fopen("balance", "w+")) == NULL) { printf ("Cannot open filen"); return;} . . . . . . . . . . . . . . . . . fwrite (balance, sizeof (balance), 1, fp); . . . . . . . . . . . . . . . . . fclose (fp); } Exemplu: Programul următor deschide fişierul FREAD.OUT şi scrie în el 25 de caractere şi apoi îl redeschide şi citeşte din nou caracterele din fişier după care afişează numărul caracterelor citite şi conţinutul. #include <stdio.h> void main( void ) { FILE *stream; char list[30]; int i, numread, numwritten; /* Deschide fisierul in mod text: */ if( (stream = fopen( "fread.out", "w+t" )) != NULL ) { for ( i = 0; i < 25; i++ ) list[i] = (char)('z' - i); /* Scrie 25 caractere in fisier */ numwritten = fwrite(list,sizeof(char),25,stream ); printf( "S-au scris %d caracteren", numwritten ); fclose( stream );} else printf( "Probleme cu deschiderea fisieruluin" ); if( (stream = fopen( "fread.out", "r+t" )) != NULL ) { /* Incearca sa citeasca 25 caractere */ numread = fread( list, sizeof( char ), 25, stream ); printf("Nr. caracterelor citite = %dn", numread); printf( "Continutul bufferului = %.25sn", list ); fclose( stream );} else printf( "Fisierul nu a putut fi deschisn" );} În urma execuţie programului se obţine: 227
  • 234. S-au scris 25 caractere Nr. caracterelor citite = 25 Continutul bufferului = zyxwvutsrqponmlkjihgfedcb Press any key to continue 10.4.6. Funcţii pentru poziţionare a) Funcţia fseek() Determină poziţionarea fişierului la citire sau scriere, începând cu poziţia selectată. Forma funcţiei: int fseek(FILE *fp, long offset, int origin) unde "fp" este un pointer-fişier returnat prin apelul funcţiei fopen(), "offset" este deplasamentul (număr octeţi) noii poziţii faţă de "origin", iar "origin" este una din următoarele macrodefiniţii: SEEK_SET - început de fişier; SEEK_CUR - poziţie curentă; SEEK_END - sfârşit de fişier. Funcţia returnează 0 dacă se execută cu succes şi o valoare nenulă în caz de eroare. Dacă nu s-a efectuat nici o operaţie de I/O de la deschiderea fişierului în mod APPEND (adăugare), atunci pointerul indică începutul fişierului. Nu se recomanda utilizarea funcţiei fseek() pentru fişiere text; se sugerează utilizarea acesteia numai pentru fişiere binare. Translaţiile CR-LF efectuate în mod text pot cauza funcţionarea defectoasă a funcţiei fseek. Funcţia fopen şi toate celelalte funcţii vor căuta să înlăture caracterul CTRL/Z terminator de fişier (EOF). Singurele operaţii garantate să funcţioneze corect când se utilizează fseek asupra fişierelor deschise în mod text este poziţionarea cu offset 0 relativă la orice poziţie din fişier şi poziţionarea faţă de începutul fişierului cu un offset returnat de funcţia ftell(). Funcţia ftell() este definită astfel: long ftell( FILE *stream ); Funcţia returnează valoarea curentă a pointerului fişier. Poziţia este exprimată prin offsetul faţă de începutul fiierului. În cazul fişierelor deschise în mod text, acest offset nu reflectă întotdeauna exact numărul de octeţi datorită translaţiei CR-LF. Este preferată folosirea simultană a funcţiilor fseek şi ftell pentru a opera asupra fişierelor text, dar se recomandă folosirea lor în special asupra fişierelor binare. 228
  • 235. Exemplu: Pentru a citi cel de-al 235 byte din fişierul numit "test" se poate folosi următorul program: func1() /* se declara funcţia func1() */ { FILE *fp; if ((fp = fopen("test", "rb")) == NULL) { printf ("Cannot open filen"); exit (1); } fseek(fp, 235L, 0); return getc(fp);} /* se citeste un caracter de la pozitia 235 */ Observaţie: L modifică constanta 235 la tipul long int. Exemplu: /* Acest program deschide fisierul FSEEK.OUT si muta pointerul in diverse locuri din fisier */ #include <stdio.h> void main( void ) { FILE *stream; char line[81]; int result; stream = fopen( "fseek.out", "w+" ); if( stream == NULL ) printf( "”Fisierul fseek.out nu s-a deschisn" ); else {fprintf( stream, "Fseek incepe aici: " "Acesta este fisierul 'fseek.out'.n" ); result = fseek( stream, 19L, SEEK_SET); if( result ) perror( "Fseek esec" ); else { printf( "Pointerul fisier este plasat la mijlocul primei linii.n" ); fgets( line, 80, stream ); printf( "%s", line );} fclose( stream );}} În urma execuţie programului se obţine: Pointerul fisier este plasat la mijlocul primei linii. Acesta este fisierul 'fseek.out'. Press any key to continue 10.4.7. Ieşiri cu format Funcţiile de tip printf() asigură conversiile de ieşire cu format. a) Funcţia fprintf() Forma acestei funcţii este: int fprintf(FILE *fp, "format", lista_argumente) Funcţia fprintf() realizează conversia şi scrierea la ieşire în fişierul indirectat cu "fp" sub controlul formatului, "format". Valoarea 229
  • 236. întoarsă de funcţie este numărul de caractere scrise, sau orice valoare negativă, dacă apare o eroare. Şirul "format" conţine două tipuri de obiecte: caractere obişnuite care sunt copiate în fişierul de ieşire şi descriptori de conversie, fiecare determinând conversia şi tipărirea argumentelor din lista de argumente. Fiecare descriptor începe cu caracterul % şi se încheie cu un caracter de conversie. Între % şi caracterul de conversie pot exista: 1) Indicatori (în orice ordine): "-" - determină alinierea la stânga a argumentului convertit în câmpul de reprezentare; "+" - precizează că numărul va fi reprezentat cu semn; " " - dacă primul caracter nu este un semn se va scrie un blanc la început; "0" - se utilizează în conversiile numerice şi indică umplerea cu zerouri la începutul câmpului; "#" - indică o formă de ieşire alternativă : pentru "0", prima cifra va fi zero; pentru "x" sau "X", la începutul fiecărui număr nenul se va scrie "0x" sau "0X"; pentru "e", "E", "g", "G", "f" ieşirea va avea întotdeauna un punct zecimal; pentru "g" şi "G" nu se vor elimina zerourile de la sfârşit. 2) Un număr care indică lungimea minimă a câmpului de reprezentare. Argumentul convertit va fi tipărit într-un câmp cu o lungime cel puţin egală cu cea specificată, dacă va fi nevoie şi mai mare. Dacă argumentul specificat are mai puţine caractere, atunci câmpul va fi umplut la stânga sau la dreapta, funcţie de aliniere. Caracterul de umplere este de obicei spatiul, dar este 0 dacă s-a ales această optiune (exemplu: %05d). 3) Un punct ce separă lungimea câmpului de precizie. 4) Un număr, precizia, care indică numărul maxim de caracetre care se vor tipări după virgulă pentru "e", "E", sau "f", sau numărul de cifre semnificative pentru conversiile "g" sau "G", sau numărul maxim de caractere ce se vor tipări dintr-un şir. Lungimea, sau precizia, sau amândoua se pot specifica şi prin "*". De exemplu: %10.4f - va afişa un număr de cel puţin 10 caractere cu 4 caractere după virgulă; %5.7s - va afişa un şir de cel puţin 5 caractere dar nu mai lung de 7 caractere; 230
  • 237. %-10.2f - va alinia la stânga un număr real cu 2 zecimale într- un câmp de reprezentare de cel puţin 10 caractere. Descriptorii de conversie utilizaţi de C sunt: %c - un singur caracter. %d, %i - notaţie zecimala cu semn. %x, %X - notaţie hexazecimală fără semn (fără 0x sau 0X). %u - notaţie zecimală fără semn. %s - caracterele din şir sunt tipărite până se întâlneşte ' 0' sau cât timp numărul de caractere tipărit precizia. %f - notaţie zecimală de forma [-]mmm.ddd, unde numărul d-urilor este indicat de precizie; precizia implicită este 6, iar o precizie egală cu zero elimina punctul zecimal. %e, %E - notaţie zecimală de forma: [-]m.dddddde+/-xx sau [-]m.ddddddE+/-XX unde numărul de d-uri este indicat de precizie (precizia implicită este 6, iar o precizie egală cu 0 va elimina punctul zecimal). %g, %G - se utilizează %e sau %E dacă exponentul este mai mic decât -4, sau precizie, în caz contrar se utilizează %f. %p - afiseaza un pointer. %o - notaţie octalăa fără semn (fără 0 la început). %% - nu se face conversie, se tipăreşte "%". %n - nu se realizează conversie de argument; numărul de caractere scrise până în acel moment este scris în argument. Există doi modificatori de format care permit funcţiei fprintf() să afişeze întregii long şi short. Aceşti modificatori se pot aplică caracterelor de conversie d, i, o, u şi x, precedându-i pe aceştia (exemplu: %ld, %li, %lu). Modificatorul l poate prefixa şi caracterele de conversie e, f şi g şi indică faptul că numerele tiparite sunt de tip double. Modificatorul h comandă funcţiei fprintf() să afişeze short int. Atunci %hu va preciza că data este de tip short unsigned int.] b) Funcţia printf() Forma funcţiei : int printf("format", lista-argumente) Funcţia printf() este echivalentă cu : fprintf(stdout, "format", lista_argumente) 231
  • 238. Exemplu: printf() ieşire ("%-5.2f", 123.456) 123.45 ("%5.2f", 3.4565) 3.45 ("%10s", "hello") hello ("%-10s", "hello") hello (%5.7s", "123456789") 1234567 Exemplu de utilizare a functiei fprintf. /* Acest program foloseste fprintf pentru scrierea datelor cu diferite formate intr-un fisier si apoi tipareste fisierul folosind functia sistem system ce apeleaza comanda TYPE a sistemului de operare */ #include <stdio.h> #include <process.h> FILE *stream; void main( void ) { int i = 10; double fp = 1.5; char s[] = "this is a string"; char c = 'n'; stream = fopen( "fprintf.out", "w" ); fprintf( stream, "%s%c", s, c ); fprintf( stream, "%dn", i ); fprintf( stream, "%fn", fp ); fclose( stream ); system( "type fprintf.out" );} 10.4.8. Intrări cu format Funcţiile de tip scanf() realizează conversiile de intrare cu format a) Funcţia fscanf() Forma acestei funcţii este: int fscanf(FILE *fp, "format", lista_argumente) Funcţia fscanf() citeşte din fişierul indirectat prin "fp" sub controlul formatului "format" şi atribuie valorile citite argumentelor următoare, fiecare argument trebuind să fie un pointer. Funcţia întoarce EOF dacă se detectează sfârşitul de fişier sau apare o altă eroare înainte de orice conversie. În caz contrar, funcţia întoarce numărul de elemente care au primit valori. Şirul "format" poate conţine: - specificatori de conversie, constând dintr-un caracter %, un caracter opţional de suprimare a atribuirii; un număr opţional care indică lungimea câmpului, un caracter opţional h, l sau L, care indică lungimea argumentului şi un caracter de conversie; 232
  • 239. - spaţii sau caractere HT sau VT care se ignoră; - caractere obişnuite (diferite de %) care indică următorul caracter diferit de caracterele albe cu care începe fişierul de intrare. Un câmp de intrare se defineşte ca un şir de caractere diferite de cele albe şi se întinde până la următorul caracter alb (spaţiu, tab-uri, CR, LF, VT, FF). Rezultatul conversiei unui câmp de intrare este plasat în variabilă indicată de argumentul corespunzător din lista de argumente. Dacă se indică suprimarea atributului prin "*" ca în %*s, câmpul de intrare este ignorat, fără a se face nici o atribuire. Descriptorii de conversie utilizaţi în C pentru citire sunt: %c - citeşte un singur caracter; caracterele următoare sunt plasate în tablourile indicate, respectându-se numărul de caractere indicat de lungimea câmpului; implicit este 1. Nu se adaugă '0'. %d - citeşte un număr întreg zecimal. %u - citeşte un număr întreg zecimal fără semn. %i - citeşte un număr întreg (intregul poate fi octal, cu 0 la început, sau hexazecimal, cu 0x sau 0X la început). %o - întreg octal (cu sau fără zero la început). %x - întreg hexazecimal (cu sau fără 0x sau 0X la început). %s - şir de caractere diferite de caracterele albe, indicând spre un tablou de caractere destul de mare pentru a memora şirul şi caracterele terminator '0' care se va adauga. %e, %f, %g - numere zecimale în virgulă mobilă. %p - citeşte valoarea pointerului. %n - se scrie în argument numerele de caractere citite până în acel moment. Nu se citeşte nimic din intrare. %h - citeşte un întreg scurt. Un caracter obişnuit în şirul "format" determină ca funcţia fscanf() să citească un caracter ce coincide cu cele din "format". De exemplu, "%d, %d" face că fscanf() să citească un întreg, apoi caracterul "," şi apoi un alt întreg. Dacă calculatorul nu găseşte caracterul specificat, fscanf() va fi încheiată. Toate variabilele menite să primească valori prin fscanf() trebuie să fie transmise prin adresele lor. Aceasta înseamnă că toate argumentele trebuie să fie pointeri la variabilele utilizate ca argumente. b) Funcţia scanf() 233
  • 240. Forma funcţiei: int scanf("format", lista-argumente) Funcţia scanf() este echivalenta cu: fscanf(stdin, "format", lista-argumente) Exemple: scanf ("%d", &count); /* se citeşte un întreg în variabilă count */ scanf ("%s", address); /* se citeşte un şir de caractere în vectorul address */ scanf ("%d %d", &r, &c); /* se citesc doua numere separate prin spaţiu, tab sau linie noua */ Un * plasat între % şi caracterul de conversie, va suspenda atribuirea datei citite. Astfel, instrucţiunea : scanf("%d%*c%d", &x, &y); face ca, dacă de la tastatură se introduce 10/20, 10 să fie atribuit lui x, %*c este ignorat (nu este atribuit), iar 20 se atribuie lui y. Instrucţiunea : scanf("%20s", sir); citeşte nu mai mult de 20 caractere în variabilă şir. Dacă se introduce un şir de mai mult de 20 caractere, vor fi reţinute numai primele 20, iar restul se pierd. Pentru caracterele rămase se poate apela din nou funcţia scanf() sub forma : scanf("%s", sir); care va plasa restul caracterelor tot în "şir". Dacă de la tastatura se introduce 10#20, instrucţiunea : scanf("%s#%s", &x, &y); va plasa 10 în x şi 20 în y. Instrucţiunea : scanf("%s ", name); nu se încheie decât dacă după ultimul caracter se introduce un spaţiu. Exemplu de utilizare a funcţiilor fscanf şi fprintf. /* Acest program scrie date cu format cu printf intr-un fisier. Apoi foloseste fscanf pentru a citi datele din fisier */ #include <stdio.h> FILE *stream; void main( void ) { long l; float fp; char s[81]; char c; stream = fopen( "fscanf.out", "w+" ); if( stream == NULL ) 234
  • 241. printf( "Fisierul fscanf.out nu a fost deschisn" ); 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( "%sn", s ); printf( "%ldn", l ); printf( "%fn", fp ); printf( "%cn", c ); fclose( stream ); }} 10.4.9. Funcţii de citire şi scriere a caracterelor a) Funcţia fgetc() int fgetc(FILE *fp) Funcţia fgetc() întoarce următorul caracter al fişierului indirectat cu "fp", caracter de tip unsigned char (convertit la int) sau EOF dacă s-a detectat sfârşitul de fişier sau a apărut o eroare. Exemplu de utilizare a funcţiei fgetc(). /* Acest program foloseste getc pentru a citi 80 de caractere dintr-un fisier si apoi le plaseaza dintr-un buffer la intrarea standard */ #include <stdio.h> #include <stdlib.h> void main( void ) { FILE *stream; char buffer[81]; int i, ch; /* Deschide fisierul pentru a citi o inregistrare */ if( (stream = fopen( "fgetc.c", "r" )) == NULL ) exit( 0 ); /* Citeste primele 80 de caractere si le plaseaza in "buffer": */ ch = fgetc(stream); for(i=0;(i<80) && (feof(stream)==0); i++ ) { buffer[i] = (char)ch; ch = fgetc( stream ); } /* Adauga null la sfarsitul fisierului */ buffer[i] = '0'; printf( "%sn", buffer ); fclose( stream );} 235
  • 242. b) Funcţia getc() int getc (FILE *fp) Această funcţie este identică cu fgetc() cu deosebirea că este o macrodefiniţie, putând să evalueze fişierul mai mult decât o dată. Observaţie: "fp" este un pointer-fişier returnat de funcţia fopen(). Exemplu: Pentru a citi un fişier text până la întâlnirea indicatorului de sfârşit de fişier se scrie: ch = getch (fp); while (ch != EOF) { ch = getc (fp); } c) Funcţia getchar() int getchar(void) Funcţia getchar() este echivalentă cu getc (stdin) . Dezavantajul funcţiei getchar() este că această poate păstra în bufferul de intrare nu unul, ci mai multe caractere, primul caracter fiind preluat după apasarea tastei CR. d) Funcţiile getche() şi getch() int getche(void) int getch(void) Funcţiile introduc un caracter de la tastatură. Funcţiile asteaptă până se apasă o tastă şi apoi întorc valoarea acesteia. Funcţia getche() preia un caracter cu "ecou" iar getch() preia caracterul fără ecou. e) Funcţia gets() char *gets(char *s) Funcţia gets() citeşte un şir de caractere introduse de la tastatură şi îl plasează în vectorul indirectat prin s. §irul se termină cu 'n' ce va fi înlocuit cu '0'. Funcţia întoarce vectorul s sau EOF, în caz de eroare. f) Funcţia fgets() char *fgets(char *s, int n, FILE *fp) Funcţia fgets() citeşte în tabloul s cel mult n-1 caractere, oprindu-se dacă a detectat NL (New Line) care este inclus în tablou, înlocuit prin'0'. Funcţia întoarce tabloul s, sau NULL, dacă apare o eroare sau sfârşit de fişier. Exemplu de folosire a funcţiei fgets. /* Acest program utilizeaza fgets pentru afisarea unei linii dintr-un fisier la display */ #include <stdio.h> void main( void ) { FILE *stream; char line[100]; 236
  • 243. if( (stream = fopen( "fgets.c", "r" )) != NULL ) { if( fgets( line, 100, stream ) == NULL) printf( "fgets errorn" ); else printf( "%s", line); fclose( stream ); }} a') Funcţia fputc() int fputc(int ch, FILE *fp) Funcţia fputc() scrie caracterul "ch" convertit la unsigned char, în fişierul indirectat prin "fp". Întoarce valoarea caracterului scris, sau EOF, dacă apare vreo eroare. Exemplu de utilizare a funcţie fputc. /* Acest program foloseste fputc si _fputchar pentru a trimite un sir de caractere la stdout. */ #include <stdio.h> void main( void ) { char strptr1[] = "Test pentru fputc !!n"; char strptr2[] = "Test pentru _fputchar!!n"; char *p; /* Tipareste linia folosind fputc. */ p = strptr1; while((*p != '0') && fputc(*(p++), stdout)!=EOF); /* Identic cu _fputchar. (Aceasta functie nu este compatibila ANSI */ p = strptr2; while((*p != '0') && _fputchar(*(p++))!=EOF);} În general, funcţiile care încep cu _ (subliniere) dar şi cu f (de la file) sunt destinate lucrului cu interfeţele standard stdin şi stdout. b') Funcţia putc() int putc(int ch, FILE *fp) Funcţia putc() este echivalenta cu fputc() cu excepţia că este o macrodefinitie, putând evalua fişierul mai mult decât o dată. c') Funcţia putchar() int putchar(int ch) Funcţia putchar(ch) este echivalenta cu putc (ch, stdout). d') Funcţia de la punctul d) nu are un corespondent pentru ieşire. e') Funcţia puts() int puts(const char *s) Funcţia puts() scrie şirul "s" şi NL în "stdout". Întoarce EOF, dacă apare o eroare, sau o valoare nenegativă în caz contrar. f') Funcţia fputs() int fputs(const char *s,FILE *fp) 237
  • 244. Funcţia fputs() scrie şirul "s" (care nu este neapărat necesar să conţină 'n') în fişierul "fp". Întoarce EOF, în caz de eroare, o valoare nenegativă, în caz contrar. Spre exemplu, /* Acest program foloseste fputs pentru a scrie o linie la terminalul standard */ #include <stdio.h> void main( void ) { fputs( "Hello world from fputs.n", stdout );} Funcţia ungetc() int ungetc(int ch,FILE *fp) Funcţia ungetc() pune argumentul "ch" inpoi în fişier, de unde va fi preluat la următoarea citire. Se poate pune inapoi în fişier doar un singur caracter. Marcajul EOF nu poate fi pus înapoi. Funcţia întoarce caracterul ce trebuie pus, sau EOF, în caz de eroare. Funcţiile getw() şi putw() Aceste funcţii se folosesc pentru a citi, respectiv a scrie întregi dintr-un sau într-un fişier disc. Aceste funcţii lucrează exact că funcţiile getc() şi putc(). Exemplu de utilizare a functiilor fprintf() şi fscanf() : Programul permite actualizarea unei agende telefonice. # include "stdio.h" void ad_num(void); /*prototipul functiilor */ void cauta(void); char menu(void); void main() { char choice; do { choice = menu(); switch (choice) { case 'a' : ad_num(); break; case 'c' : cauta(); break; } } while (choice != 'q'); } char menu() {/* Afiseaza meniul si preia optiunea */ char ch; do { printf ("A(dauga), C(auta), Q(uit) : "); ch = tolower (getche()); printf ("n"); } while (ch != 'q' && ch != 'a' && ch != 'c'); return ch; } void ad_num() /* Adauga un nou numar */ {FILE *fp; char name[80]; int a_code, schimb, numar; 238
  • 245. if ((fp = fopen ("telefon", "a")) == NULL) { printf ("Cannot open file n"); exit (1);} printf("Introduceti numele si numarul: "); fscanf(stdin,"%s%d%d%d",nume,&a_code,&schimb, &numar); fscanf(stdin,"%*c"); /* inlatura CR din sirul de intrare */ /* Se scrie in fisier */ printf("%d",fprintf(fp,"%s %d %d %dn", nume, a_cod, schimb, numar)); fclose (fp); } void cauta() /* Cauta un numar dandu-se un nume */ { FILE *fp; char nume[80], nume2[80]; int a_code, schimb, numar; /* Se deschide fisierul pentru citire */ if ((fp = fopen ("telefon", "r")) == NULL) { printf("Cannot open filen "); exit (1); } printf ("Nume ?"); gets (nume); /* Se cauta numarul */ while (!feof (fp)) { fscanf(fp,"%s%d%d%d", nume2, &a_cod, &schimb, &numar); if (!strcmp(nume, nume2)) { printf("%s : (%d) %d - %dn", nume, a_code, schimb, numar); break;} } fclose (fp);} Capitolul XI UTILIZAREA ECRANULUI ÎN MOD TEXT Biblioteca standard a limbajului C şi C++ conţine funcţii pentru gestiunea ecranului. Acesta poate fi gestionat în două moduri: în mod text sau în mod grafic. Prezentăm funcţiile standard mai importante utilizate la gestiunea ecranului în mod text. Toate funcţiile standard de gestiune a ecranului în mod text au prototipurile în fişierul antet <conio.h>. Modul text presupune că ecranul este compus dintr-un număr de linii şi un număr de coloane. În mod curent se utilizează 25 de linii a 80 de coloane fiecare, deci ecranul are o capacitate de 25*80=2000 de caractere. 239
  • 246. Poziţia pe ecran a unui caracter se defineşte printr-un sistem de două coordonate întregi (x,y) unde: x este numărul coloanei în care este situat caracterul y este numărul liniei în care este situat caracterul Colţul din stânga sus al ecranului are coordonatele (1,1), iar colţul din dreapta jos are coordonatele (80,25). În mod implicit, funcţiile de gestiune a ecranului în mod text au acces la tot ecranul. Accesul poate fi însă limitat la o parte din ecran utilizând aşa numitele ferestre. Fereastra este o parte a ecranului în formă de dreptunghi şi care poate fi gestionată separat de restul ecranului. Atributele unui caracter de pe ecran sunt următoarele: - coordonatele x şi y; - culoarea caracterului; - culoarea fondului pe care este afişat caracterul; - clipirea caracterului. 11.1. Setarea ecranului în mod text Se realizează cu ajutorul funcţiei textmode care are următorul prototip: void textmode (int modtext) unde modtext poate fi exprimat simbolic sau numeric în modul următor: Modul text Constantă simbolică Valoare Caractere albe pe fond negru; BW40 0 40 de coloane Color 40 de coloane C40 1 Caractere albe pe fond negru; BW80 2 80 de coloane Color 80 de coloane C80 3 Monocrome 80 de coloane MONO 7 Color cu 50 de linii pentru C4350 64 placa VGA Modul precedent LASTMODE -1 Modul MONO se poate seta pe un adaptor monocolor, în timp ce celelalte moduri se pot seta pe adaptoare color. 240
  • 247. 11.2. Definirea unei ferestre După setarea ecranului în mod text, este activ tot ecranul şi acesta are caracteristicile indicate în paragraful precedent. De multe ori însă se doreşte partajarea ecranului în zone care să poată fi gestionate în mod independent. Acest lucru poate fi realizat cu ajutorul ferestrelor. O fereastră se defineşte cu ajutorul funcţiei window care are următorul prototip: void window (int x1, int y1, int x2, int y2); unde: (x1,y1) – reprezintă coordonatele colţului stânga sus ; (x2,y2) – reprezintă coordonatele colţului dreapta jos. La un moment dat este activă o singură fereastră şi anume acea definită la ultimul apel al funcţiei window. Dacă parametri de la apelul funcţiei window sunt eronaţi, aceasta nu are nici un efect. 11.3. Ştergerea unei ferestre O fereastră activă se şterge utilizând funcţia clrscr care are următorul prototip: void clrscr(void) După apelul acestei funcţii, fereastra activă (sau tot ecranul dacă nu s-a definit în prealabil o fereastră cu funcţia window) devine vidă. Fondul ei are culoarea definită prin culoarea de fond curentă. Funcţia clrscr poziţionează cursorul pe caracterul din stânga sus al ferestrei active, adică în poziţia de coordonate (1,1). 11.4. Deplasarea cursorului Cursorul poate fi plasat pe un caracter al ferestrei active folosind funcţia gotoxy ce are următorul prototip: void gotoxy (int x, int y); unde (x, y) reprezintă coordonatele caracterului pe care se plasează cursorul. Dacă la apelul funcţiei coordonatele sunt definite în afara ferestrei active, funcţia este ignorată. Poziţia cursorului din fereastra activă poate fi determinată cu ajutorul funcţiilor wherex şi wherey care returnează numărul coloanei, respectiv liniei, din fereastra activă pe care se află cursorul, şi care au următoarele prototipuri: int wherex(void); 241
  • 248. int wherey(void); În cazul în care se doreşte ascunderea cursorului (facerea invizibilă a cursorului) se utilizează o secvenţă ce utilizează funcţia geninterrupt: { _AH=1; _CH=0x20; geninterrupt(0x10); } Cursorul poate fi rafiaşat utilizând următoarea secvenţă: { _AH=1; _CH=6; _CL=7; geninterrupt(0x10);} 11.5. Setarea culorilor Culoarea fondului se setează cu ajutorul funcţiei textbackground ce are următorul prototip: void textbackground(int culoare); unde culoare este un întreg în intervalul [0, 7] şi are semnificaţia din tabelul de mai sus. Pot fi setate ambele culori precum şi clipirea caracterului folosind funcţia textattr ce are următorul prototip: void textattr (int atribut) unde atribut se defineşte cu ajutorul relaţiei: atribut=16*culoare_fond+culoare_caracter+clipire; Culoarea caracterelor se setează cu ajutorul funcţiei textcolor ce are următorul prototip: void textcolor(int culoare); unde culoare este un întreg în intervalul [0,15] a cărui semnificaţie este explicată de tabelul următor: Culoare Constantă simbolică Valoare Negru BLACK 0 Albastru BLUE 1 Verde GREEN 2 Turcoaz CYAN 3 Roşu RED 4 Purpuriu MANGETA 5 Maro BROWN 6 Gri deschis LIGHTGRAY 7 Gri închis DARKGRAY 8 Albastru deschis LIGHTBLUE 9 242
  • 249. Verde deschis LIGHTGREEN 10 Turcoaz deschis LIGHTCYAN 11 Roşu deschis LIGHTRED 12 Purpuriu deschis LIGHTMANGETA 13 Galben YELLOW 14 Alb WHITE 15 Clipire BLINK 128 11.6. Funcţii pentru gestiunea textelor Pentru afişarea caracterelor se pot folosi funcţiile: - int putch (int c); – afişează un singur caracter; - int cputs (const char *str); – afişează un şir de caractere în mod similar funcţiei puts; - int cprintf (const char *format); – afişează date sub controlul formatelor în mod similar funcţiei printf. - void clreol (void); - şterge sfârşitul liniei începând cu poziţia cursorului; - void delline (void); - şterge toată linia pe care este poziţionat cursorul; - int gettext (int left, int top, int right, int bottom, void *destination); - copiază textul cuprins în dreptunghiul definit de coordonatele (left, top) – stânga sus şi (right, bottom) – dreapta jos la adresa de memorie indicată de pointerul destination; - int puttext( int left, int top, int right, int bottom, void *source ); - citeşte textul cuprins în dreptunghiul definit de coordonatele (left, top) – stânga sus şi (right, bottom) – dreapta jos de la adresa de memorie indicată de pointerul source; - int movetext( int left, int top, int right, int bottom, int destleft, int desttop ); - mută textul cuprins în dreptunghiul definit de coordonatele (left, top) – stânga sus şi (right, bottom) – dreapta jos în dreptunghiul cu coordonatele colţului din stânga sus (destleft, desttop); - void insline (void); - inserează o linie vidă în fereastra activă; - int getch (void); - citeşte un caracter fără ecou de la tastatură, adică după ce este citit caracterul nu mai este afişat pe ecran; funcţia returnează codul ASCII al caracterului citit de la tastatură. 243
  • 250. - int getche (void); - citeşte un caracter cu ecou de la tastatură, adică după ce este citit caracterul este afişat automat pe ecran; funcţia returnează codul ASCII al caracterului citit de la tastatură. - int kbhit (void); - controlează dacă s-a tastat ceva la tastatură. Dacă a fost apăsată o tastă se returnează o valoare diferită de zero, altfel se returnează valoarea 0. Exemplu: Următorul program desenează o fereastră şi scrie un număr în aceasta. #include <stdio.h> #include <stdlib.h> #include <conio.h> #include <alloc.h> #include <dos.h> #define MAX 100 #define SIMPLU 1 #define DUBLU 2 typedef struct{ int x,y,u,v; void *zonfer; }ELEM; ELEM *stiva[MAX]; int istiva; void orizontal(int,int); void vertical(int,int,int,int); void fereastra(int st,int sus,int dr,int jos,int fond,int culoare, int chenar,int n) //Afiseaza o fereastra limitata de un chenar { extern ELEM *stiva[]; extern int istiva; //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 insuficientan"); 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!"); 244
  • 251. 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); 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(); 245
  • 252. 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(); } Capitolul XII UTILIZAREA ECRANULUI ÎN MOD GRAFIC Pentru aplicaţiile grafice limbajul C pune la dispoziţie peste 60 de funcţii standard ce au prototipul în fişierul graphics.h. În 246
  • 253. continuare sunt prezentate cele mai importante funcţii ce permit gestiunea ecranului în mod grafic. 12.1. Iniţializarea modului grafic Pentru a se putea lucra în mod grafic trebuie realizată o iniţializare utilizând funcţia initgraph. Aceasta poate fi folosită singură sau împreună cu o altă funcţie numită detectgraph care determină parametrii adaptorului grafic. Prototipul ei este: void far detectgraph(int far *gd, int far *gm); unde: Pointerul gd păstrează adresa uneia din valorile din tabelul următor (în funcţie de adaptorul grafic utilizat): Constantă simbolică Valoare CGA 1 MCGA 2 EGA 3 EGA64 4 EGAMONO 5 IBM8514 6 HERCMONO 7 ATT400 8 VGA 9 PC3270 10 Valorile spre care pointează gd definesc nişte funcţii standard corespunzătoare adaptorului grafic. Aceste funcţii se numesc drivere. Ele se află în subdirectorului BGI. Funcţia detectgraph detectează adaptorul grafic prezent la calculator şi păstrează valoarea corespunzătoare acestuia în zona spre care pointează gd. Zona spre care pointează gm memorează una din valorile: Adaptor Constantă simbolică - Rezoluţie Valoare 247
  • 254. CGA CGAC0 – 0 320*200 CGAC1 – 1 320*200 CGAC2 – 2 320*200 CGAC3 – 3 320*200 CGAHI – 4 640*200 EGA EGALO – 0 640*200 EGAHI – 1 640*350 VGA VGALO – 0 640*200 VGAMED – 1 640*350 VGAHI – 2 640*480 Modul grafic se defineşte în aşa fel încât el să fie cel mai performant pentru adaptorul grafic curent. Cele mai utilizate adaptoare sunt cele de tip EGA şi VGA. Apelul funcţiei detectgraph trebuie să fie urmat de apelul funcţiei initgraph. Aceasta setează modul grafic în conformitate cu parametri stabiliţi de apelul prealabil al funcţiei detectgraph şi are următorul prototip: void far initgraph(int far *gd,int far *gm, int far *cale); unde: gd şi gm sunt pointeri ce au aceeaşi semnificaţie ca şi în cazul funcţiei detectgraph; cale este pointer spre şirul de caractere care defineşte calea subdirectorului BGI care conţine driverele. De exemplu dacă BGI este subdirector al directorului BORLANDC, atunci se utilizează şirul de caractere: ”C:BORLANDCBGI” Exemplu: Pentru setarea în mod implicit a modului grafic se poate utiliza următoarea secvenţă de instrucţiuni: …………………………… int gd,gm; detectgraph(&gd,&gm); initgraph(&gd,&gm, ”C:BORLANDCBGI”); …………………………… Doar după apelul funcţiei initgraph pot fi utilizate şi alte funcţii de gestionare a ecranului în mod grafic. Utilizatorul poate defini el însuşi parametri pentru iniţializarea modului grafic. De exemplu, secvenţa următoare: …………………………… 248
  • 255. int gd=1,gm=0; initgraph(&gd,&gm, ”C:BORLANDCBGI”); …………………………… setează modul grafic corespunzător unui adaptor grafic CGA cu rezoluţia 320*200 de puncte. În afara acestor funcţii mai pot fi utilizate şi următoarele funcţii: void far setgraphmode (int mode) – utilizată pentru setarea modului grafic unde mode are valorile 0 – 4 pentru VGA, 0-1 pentru EGA, 0 – 2 pentru VGA; void far retorecrtmode(void) – ce permite revenirea la modul precedent; void far graphdefaults(void) – repune parametri grafici la valorile implicite; int far getgraphmode(void) – returnează codul modului grafic; char *far getmodename(int mod) – returnează pointerul spre numele modului grafic definit de codul numeric mod; char *far getdrivername(void) – returnează pointerul spre numele drieverului corespunzător adaptorului grafic curent; void far getmoderange(int grafdriv,int far *min, int far *max) – defineşte valorile minimale şi maximale ale modului grafic utilizat. void far closegraph(void) – se utilizează pentru a ieşi din modul grafic. 12.2. Gestiunea culorilor Adaptoarele grafice sunt prevăzute cu o zonă de memorie în care se păstrează date specifice gestiunii ecranului. Această zonă de memorie poartă denumirea de memorie video. În mod grafic, ecranul se consideră format din puncte luminoase numite pixeli. Poziţia pe ecran a unui pixel se defineşte prin două valori întregi: (x,y) unde: x – defineşte coloana în care este afişat pixelul; y – defineşte linia în care este afişat pixelul. Fiecărui pixel îi corespunde o culoare ce este pătrată în memoria video. Numărul maxim de culori care pot fi afişate cu ajutorul unui adaptor EGA este 64.. Culorile se codifică prin numere întregi din intervalul [0, 63] şi prin constante simbolice. Cele 64 de culori nu pot fi afişate simultan. În cazul adaptorului EGA pe ecran se 249
  • 256. pot afişa cel mult 16 culori ce formează o paletă. Paleta implicită este dată de tabelul următor: Denumire simbolică Valoare BLACK 0 BLUE 1 GREEN 2 CYAN 3 RED 4 MANGETA 5 BROWN 6 LLIGHTGRAY 7 DARKGRAY 8 LIGHTBLUE 9 LIGHTGREEN 10 LIGHTCYAN 11 LIGHTRED 12 LIGHTMANGETA 13 YELLOW 14 WHITE 15 În mod implicit, culoarea fondului este întotdeauna cea corespunzătoare indicelui zero, iar culoarea pentru desenare este cea corespunzătoare indicelui 15. Pentru controlul culorilor pot fi utilizate următoarele funcţii: void far setbkcolor(int culoare) – modifică culoarea fundalului; int far getbkcolor(void) – returnează indexul din tabloul care defineşte paleta pentru culoarea fundalului; void far setcolor(int culoare) – setează culoarea utilizată pentru desenare; int far getcolor(void) – returnează indexul din tabloul care defineşte paleta pentru culoarea de desenare; void far setpalette(int index,int cod) – setează o nouă culoare în paleta ce este utilizată la colorare (index ia valori între [0, 15] iar cod între [0, 63]); void far setallpalette(struct palettetype far* paleta) – modifică mai multe culori din paletă. Palettetype este o structură definită ca mai jos: struct palettetype { unsigned char size; 250
  • 257. signed char colors[MAXCOLORS+1]; }; unde size – este dimensiunea paletei; colors – este un tablou ale cărui elemente au ca valori codurile culorilor componente ale paletei care se defineşte. Modificarea paletei curente cu ajutorul funcţiei setpalette sau setallpalette conduce la schimbarea corespunzătoare a culorilor afişate pe ecran în momentul apelului funcţiilor respective. void far getpalette(struct palettetype far* paleta) – determină codurile culorilor componente ale paletei curente; int far getmaxcolor(void) – returnează numărul maxim de culori diminuat cu 1; int far getpalettesize(void) – returnează numărul culorilor componente ale paletei. 12.3. Setarea ecranului În mod grafic, ecranul se compune din n*m puncte luminoase (pixeli), adică pe ecran se pot afişa m linii a n pixeli fiecare. Poziţia unui pixel este dată de două numere întragi (x,y) numite coordonatele pixelului. Pixelul aflat în stânga sus are coordonatele (0,0). Coloanele se numerotează de la stânga la dreapta, iar liniile de sus în jos. Informaţii referitoare la ecran pot fi obţinute cu ajutorul următoarelor funcţii: int far getmaxx(void) – returnează coordonta maximă pe orizontală; int far getmaxy(void) – returnează coordonta maximă pe verticală; int far getx(void) – returnează poziţia pe orizontală a pixelului curent; int far gety(void) – returnează poziţia pe verticală a pixelului curent. 12.4. Utilizarea textelor în mod grafic Afişarea textelor în modul grafic presupune definirea unor parametri care pot fi controlaţi prin intermediul funcţiilor descrise în continuare: a) void far settextstyle(int font,int direcţie,int charsize) 251
  • 258. unde: font – defineşte setul de caractere şi poate lua următoarele valori: Constantă simbolică Valoare DEFAULT_FONT 0 TRIPLEX_FONT 1 SMALL_FONT 2 SANS_SERIF_FONT 3 GOTHIC_FONT 4 direcţie – defineşte direcţia de scris a textului, astfel: - de la stânga la dreapta: HORIZ_DIR; - de jos în sus: VERT_DIR. charsize – defineşte dimensiunea caracterului în pixeli, astfel: Valoarea Matricea utilizată pentru parametrului afişarea caracterului (în pixeli) 1 8*8 2 16*16 3 24*24 …. …….. 10 80*80 b) void far settextjustify(int oriz, int vert) – defineşte cadrajul textului; oriz – defineşte încadrarea pe orizontală, astfel: - în stânga: LEFT_TEXT; - în centru: CENTER_TEXT; - în dreapta: RIGHT_TEXT. vert – defineşte încadrarea pe verticală, astfel: - marginea inferioară: BOTTOM_TEXT; - în centru: CENTER_TEXT; - marginea superioară: TOP_TEXT. După setarea acestor parametri pot fi afişate texte folosind funcţiile outtext şi outtextxy care au următoarele prototipuri: void far outtext(char far* şir) , unde şir este un pointer spre zona de memorie în care se păstrează caracterele de afişat, afişează caracterele începând cu poziţia curentă de pe ecran; void far outtextxy(int x,int y,char far* şir) , unde şir este un pointer spre zona de memorie în care se păstrează caracterele de afişat, x,y defineşte poziţia de pe ecran unde se face afişarea. 252
  • 259. Dimensiunile în pixeli ale unui şir de caractere se pot determina utilizând funcţiile textheight şi textwidth: void far textheight(char far* şir) – returnează înălţimea în pixeli a şirului păstrat în zona spre care pointează şir, void far textwidth(char far* şir) – returnează lălţimea în pixeli a şirului păstrat în zona spre care pointează şir. 12.5. Gestiunea imaginilor În modul grafic, ecranul poate fi partajat în mai multe părţi ce pot fi gestionate independent. Aceste părţi se numesc ferestre grafice. Următoarele funcţii sunt utilizate pentru prelucrarea ferestrelor grafice: void far setviewport(int st, int sus, int dr, int jos, int d) – defineşte o fereastră grafică, unde: - (st,sus) – coordonatele colţului stânga sus al ferestrei; - (dr,jos) – coordonatele colţului dreapta jos al ferestrei; - d – indicator cu privire la decuparea desenului. Dacă d are valoarea 1, atunci funcţiile de afişare a textelor şi de desenare nu pot scrie sau desena în afara limitelor ferestrei. void far clearviewport(void) – şterge fereastra activă; după apelul acestei funcţii, toţi pixelii ferestrei au aceeaşi culoare, şi anume culoarea de fond, iar poziţia curentă a cursorului este punctul de coordonate relative (0,0); void far cleardevice(void) – şterge tot ecranul iar poziţia curentă a cursorului este colţul din stânga sus al ecranului; void far getviewsettings(struct viewporttype far* fereastra) – returnează parametri ferestrei active. Imaginea ecranului se păstrează în memoria video a adaptorului grafic şi formează o pagină. Funcţiile următoare sunt utilizate pentru gestionarea paginilor void far setactivepage(int nrpag) – activează o pagină al cărei număr este specificat de parametrul nrpag; void far setvisualpage(int nrpag) – cu toate că în mod normal este vizualizată pe ecran pagina activă, utilizatorul are posibilitatea de a vizualiza altă pagină decât cea activă utilizând această funcţie (această funcţie poate fi utilă pentru animaţie); 253
  • 260. void far getimage(int st, int sus, int dr, int jos,void far* zt) – salvează o zonă dreptunghiulară de pe ecran, unde: - (st,sus) – coordonatele colţului stânga sus a zonei de pe ecran ce se salvează; - (dr,jos) – coordonatele colţului dreapta jos a zonei de pe ecran ce se salvează; - zt – pointer spre zona de memorie în care se salvează imaginea de pe ecran. unsigned far imagesize(int st, int sus, int dr, int jos) – determină dimensiunea unei zone dreptunghiulare de pe ecran, unde: - (st,sus) – coordonatele colţului stânga sus a zonei de pe ecran; - (dr,jos) – coordonatele colţului dreapta jos a zonei de pe ecran. void far putimage(int st, int sus, int jos,void far* zt, int op) – afişează oriunde pe ecran o zonă dreptunghiulară salvată cu funcţia getimage, unde: - (st,sus) – coordonatele colţului stânga sus a zonei de pe ecran ce se salvează; - zt – pointer spre zona de memorie în care se păstrează imaginea ce se va afişa pe ecran; - op – defineşte operaţia între datele aflate în zona spre care pointează zt şi cele existente pe ecran în zona dreptunghiulară definită de parametri st, sus. Parametrul op se defineşte astfel: Constantă Valoare Acţiune simbolică COPY_PUT 0 copiază imaginea din memorie pe ecran XOR_PUT 1 „sau exclusiv” între datele de pe ecran şi cele din memorie OR_PUT 2 „sau” între datele de pe ecran şi cele din memorie AND_PUT 3 „şi” între datele de pe ecran şi cele din memorie NOT_PUT 4 copiază imaginea din memorie pe ecran completând datele aflate în memorie 12.6. Desenarea şi colorarea figurilor geometrice Biblioteca standard pune la dispoziţia utilizatorului o serie de funcţii care permit desenarea şi colorarea unor figuri geometrice: 254
  • 261. void far putpixel(int x, int y, int culoare) – afişează un pixel pe ecran în punctul de coordonate (x,y) (relativ la fereastra activă) şi având culoarea culoare; unsigned far getpixel(int x, int y) – determină culoarea unui pixel aflat pe ecran în poziţia (x,y); void far moveto(int x, int y) – mută cursorul în dreptul pixelului de coordonate (x,y); void far moverel(int dx, int dy) – mută cursorul în dreptul pixelului de coordonate (x+dx,y+dy), unde (x,y) reprezintă coordonatele pixelului curent; void far line(int xi, int yi, int xf, int yf) – trasează un segment de dreaptă între punctele de coordonate (xi,yi) şi (xf,yf); void far lineto(int x, int y) – trasează un segment de dreaptă între punctul curent şi punctul de coordonate (x,y); void far linerel(int dx, int dy) – trasează un segment de dreaptă între punctul curent şi punctul de coordonate (x+dx,y+dy), unde (x,y) sunt coordonatele punctului curent; void far arc(int xcentru, int ycentru, int unghistart, int unghifin,int raza) – trasează un arc de cerc, unghiurile fiind exprimate în grade sexagesimale; void far circle(int xcentru, int ycentru, int raza) – trasează un cerc, cu (xcentru,ycentru) coordonatele centrului şi raza raza acestuia; void far ellipse(int xcentru, int ycentru, int unghistart, int unghifin,int semiaxamare, int semiaxamică) – trasează un arc de elipsă cu centrul în punctul de coordonate (xcentru,ycentru), semiaxa mare definită de parametrul semiaxamare iar semiaxa mică definită de parametrul semiaxamică; void far rectangle(int st, int sus, int dr, int jos) – trasează un dreptunghi definit de colţurile diagonal opuse; void far drawpoly(int nr, int far* tabpct) – trasează o linie polignală, parametrul nr specificând numărul de laturi iar tabpct este un pointer spre un tablou de întregi ce definesc vârfurile liniei poligonale păstrate sub forma: abscisa_i,ordonata_i unde i are valorile 1,2,…., nr+1; void far setlinestyle(int stil, unsigned şablon, int grosime) – defineşte stilul utilizat pentru trasarea liniilor, unde: stil – este un întreg din intervalul [0,4] care defineşte stilul liniei conform următorului tabel: 255
  • 262. Constantă simbolică Valoare Stil SOLID_LINE 0 Linie continuă DOTTED_LINE 1 Linie punctată CENTER_LINE 2 Linie întreruptă formată din liniuţe de două dimensiuni DASHED_LINE 3 Linie întreruptă formată din liniuţe de aceeaşi dimensiune USERBIT_LINE 4 Stil definit de utilizator prin şablon şablon – defineşte stilul liniei şi are sens doar când parametrul stil are valoarea 4; grosime – defineşte lăţimea liniei în pixeli, astfel: NORM_WIDTH – valoarea 1 pixel şi THICK_WIDTH – valoarea 3 pixeli. void far getlinesettingstype(struct linesettingstype far* linieinfo) – este utilizată pentru a determina stilul curent; void far bar(int st, int sus, int dr, int jos) – are aceeaşi semnificaţie cu funcţia rectangle însă dreptunghiul este colorat; void far bar3d(int st, int sus, int dr, int jos, int profunzime, int ind) – funcţia desenează o prismă colorată pentru ind diferit de zero; pentru ind=0, nu se trasează partea de sus a prismei; void far pieslice(int xcentru, int ycentru, int unghistart, int unghifin,int raza) – desenează un sector de cerc colorat; void far fillpoly(int nr, int far* tabpct) – desenează un poligon colorat; void far fillellipse(int xcentru, int ycentru, int semiaxamare, int semiaxamică) – desenează o elipsă colorată; void far setfillstyle(int haşura, int culoare) – defineşte modul de colorare al figurilor, astfel: culoare – defineşte culoarea utilizată pentru haşurare; haşura – defineşte haşura utilizată pentru colorare conform tabelului: Constantă simbolică Valoare EMPTY_FILL 0 SOLID_FILL 1 LINE_FILL 2 LTSLASH_FILL 3 SLASH_FILL 4 BKSLASH_FILL 5 LTBKSLASH_FILL 6 HATCH_FILL 7 256
  • 263. XHATCH_FILL 8 INTERLEAVE_FILL 9 WIDE_DOT_FILL 10 CLOSE_DOT_FILL 11 USER_FILL 12 void far setfillpattern(char far *h_utilizator,int culoare) – este utilizată pentru a defini o haşură a utilizatorului, astfel: culoare – defineşte culoarea de haşurare; h_utilizator – este un pointer spre o zonă de memorie care defineşte haşura utilizatorului; void far getfillsettings(struct fillsettingstype far* stilculoare) – este utilizată pentru determinarea stilului curent de colorare; void far floodfill(int x, int y, int culoare) – este o funcţie utilizată pentru colorarea unui domeniu închis, astfel: (x,y) – reprezintă coordonatele unui punct din interiorul domeniului închis; culoare – defineşte culoarea utilizată la trasarea conturului figurii (interiorul este colorat în conformitate cu setările efectuate de funcţia setfillstyle). Exemplu: Prezentăm în acest exemplu un model de utilizare a modului grafic pentru trasarea graficelor unor funcţii matematice elementare. #include <stdio.h> #include <math.h> #include <graphics.h> #include <conio.h> int x,y; float a,b; void desen(void) //functia care deseneaza axele si //coloreaza ecranul { cleardevice(); setbkcolor(14); setcolor(12); line(0,y,2*x,y); line(x,0,x,2*y); line(2*x-4,y-4,2*x,y); line(2*x-4,y+4,2*x,y); line(x,0,x-4,4); line(x,0,x+4,4); } void interval(int l1, int l2) //functia care verifica // daca intervalele pe care sunt definite functiile 257
  • 264. // trigonometrice, sunt respectate { while ((a<l1)||(b>l2)) { clrscr(); cleardevice(); setbkcolor(0); printf("reintroduce-ti intervalul astfel incat sa fie cuprins intre -1 si 1:n "); printf("a="); scanf("%f",&a); printf("n"); 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) 258
  • 265. { return atan(1/x);} void main() { int p,l,t,dr=DETECT, modgr; initgraph(&dr,&modgr,"c:borlandcbgi"); setbkcolor(1); x=getmaxx()/2,y=getmaxy()/2; p=0; while(p==0) { setbkcolor(1); p=1; printf("1 : sin(x)n"); printf("2 : cos(x)n"); printf("3 : tg(x)n"); printf("4 : ctg(x)n"); printf("5 : arcsin(x)n"); printf("6 : arccos(x)n"); printf("7 : arctg(x)n"); printf("8 : arcctg(x)n"); printf("Alegeti nr. corespunzator functiei dorite: "); scanf("%d",&t); while(t<1 || t>8 ) { printf("Reintroducet t-ul cuprins intre 1 si 8n "); 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; 259
  • 266. 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(); } Capitolul XIII FUNCŢII MATEMATICE Limbajul C conţine mai multe funcţii matematice care utilizează argumente de tip double şi întorc valori de tip double. Aceste funcţii se împart în următoarele categorii: - funcţii trigonometrice; - funcţii hiperbolice; - funcţii exponenţiale şi logaritmice; - alte tipuri. Toate funcţiile matematice sunt incluse în fişierul antet "math.h". Acesta mai conţine o serie de macrodefiniţii cum ar fi EDOM, ERANGE şi HUGE_VAL. Macrodefiniţiile EDOM şi ERANGE se găsesc în fişierul "errno.h" şi sunt constante întregi diferite de zero, utilizate pentru a semnala erorile de domeniu şi de plajă ale funcţiei. HUGE_VAL (aflată tot în "errno.h") este o valoare pozitivă de tip double. Dacă un argument al unei funcţii matematice nu este în domeniul pentru care a fost definită funcţia, atunci funcţia întoarce 0 şi în domeniul de eroare, "errno" este modificat la EDOM. Dacă o funcţie produce un rezultat prea mare pentru a fi reprezentat printr-un double, apare o depăşire, funcţia returnând HUGE_VAL cu semnul adecvat iar "errno" este modificat la ERANGE. Dacă se produce subdepăşire, funcţia întoarce zero, iar "errno" este modificat la ERANGE în funcţie de implementare. 260
  • 267. 13.1 Funcţii trigonometrice - sin(x) , x în radiani - sinusul lui x; - cos(x) , x în radiani - cosinusul lui x. - tan(x) , x în radiani - tangenta lui x; Exemplu: Programul următor afişează valorile sinusului, cosinusului şi tangentei unghiului a[-1,+1] radiani, din 0.1 în 0.1. # include <math.h> void main() { double val = -1.0; do { printf("sinusul lui %f este %fn", val, sin(val)); printf("cosinusul lui %f este %fn",val, cos(val)); printf("tangenta lui %f este %fn", val, tan(val)); val += 0.1;} while (val <= 1.0); } 13.2 Funcţii trigonometrice inverse - asin(x) , cu x [-1,1] - arcsinusul lui x; - acos(x) , cu x [-1,1] - arccosinusul lui x; - atan(x) , x R - arctangenta lui x; - atan(y,x) , - returneaza arctg (y/x). Exemplu: Programul următor afişează valorile arcsinusului, arccosinusului şi arctangentei unghiului a[-1,+1], din 0.1 în 0.1. # include <math.h> void main() { double val = -1.0; do { printf("arcsin lui %f este %fn", val, asin(val)); printf("arccos lui %f este %fn", val, asin(val)); printf("arctg lui %f este %fn", val, asin(val)); val += 0.1; } while (val <= 1.0); } 13.3 Funcţii hiperbolice 261
  • 268. - sinh(x) , x R - sinus hipebolic de x; - cosh(x) , x R - cosinus hipebolic de x; - tanh(x) , x R - tangenta hipebolica de x. 13.4 Funcţii exponenţiale şi logaritmice - exp(x) , x R - exponentiala lui x. - log(x) , x > 0 - logaritmul natural al lui x; - log10(x) , x>0 - logaritmul zecimal al lui x. Exemplu: printf ("Valoarea lui e este: %f", exp(1.0)); Exemplu: Programul următor afişează valorile logaritmului natural şi logaritmului zecimal din 1 în 1 al numerelor de la 1 la 10. # include <math.h> void main() { double val =1.0; do{printf("%f %f %fn",val,log(val),log10(val)); val ++; } while (val < 11.0);} - pow(x,y); funcţia calculeaza xy. O eroare de domeniu apare dacă x = 0 şi y = 0 sau dacă x < 0 şi y nu este întreg. Exemplu: Programul următor afişeaza primele 11 puteri ale lui 10. # include <math.h> void main() { double x =10.0, y = 0.0; do { printf ("%fn", pow(x,y)); y ++;} while (val < 11.0); } 13.5 Generarea de numere aleatoare În multe aplicaţii este necesară generarea de numere aleatoare. Pentru asemenea cazuri limbajul C dispune de două funcţii, rand şi random, care returnează numere întregi aleatore. Funcţia rand are următorul prototip: int rand(void) şi returnează un număr întreg, aleator, cuprins în intervalul de la 0 la RAND_MAX (valoare definită în fişierul antet stdlib.h). Funcţia random are prototipul: int random(int val_maxima) 262
  • 269. şi returnează un număr întreg, aleator, cuprins în intervalul [0, val_maxima]. Pentru generarea de numere aleatoare în virgulă mobilă se împarte rezultatul funcţiei random la o valoare întreagă. Următorul program exemplifică utilizarea acestor funcţii: Exemplu: #include <stdio.h> #include <stdlib.h> void main(void) { int k; printf(”Valorile furnizate de functia randn”); 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 1n”); for(k=0;k<10;k++) printf(”%f ”,random(10)/10.0); printf(”Valori intregi intre -10 si 10n”); for(k=0;k<10;k++) printf(”%d ”,10-random(20));} 13.6 Alte tipuri de funcţii matematice Nume funcţie Caracterizarea funcţiei sqrt(x) - - radicalul lui x, sqrt(x)= x - ceil(x) - - cel mai mic întreg, mai mare că x, convertit la double (ex. ceil(1.05) va returna valoarea 2). - floor(x) - - cel mai mare întreg, mai mic sau egal cu x, convertit la double (exemplu: floor(1.02) va returna valoarea 1.0, floor(-1.02) va returna valoarea -2.0). - fabs(x) - - modulul numărului x; - ldexp(x, n) - - calculeaza x*2n , unde n este de tip int. - fmod(x, y) - - restul în virgulă mobila a lui x/y, cu acelaşi semn ca x. - modf(x, double *ip) - - împarte pe x în parte întreagă şi parte fracţionară, fiecare cu acelaşi semn că x; memorează partea întreagă în "*ip" şi întoarce partea fracţionară. - modf(x, double *ip) - - întoarce x într-o funcţie normalizată în intervalul [1/2, 1] şi o putere a lui 2, care se memoreaza în "*exp"; dacă x este 0, ambele părţi ale rezultatului 263
  • 270. sunt 0. Modul de utilizare al acestor funcţii este similar cu al celorlalte funcţii matematice descrise în acest capitol. Capitolul XIV ELEMENTE DE PROGRAMARE AVANSATĂ 14.1 Gestionarea memoriei Un calculator poate avea trei tipuri de memorie: convenţională, extinsă şi expandată. În programare memoria constituie un factor important ce influenţează viteza de lucru a programelor. Fiecare tip de memorie are diferite viteze de acces, ceea ce afectează performanţa programelor. Volumul şi tipul de memorie instalată poate fi determinat utilizând comanda DOS: C:>MEM /CLASSIFY (pentru versiuni ale sistemului de operare DOS mai mari de varianta 5). Sistemul de operare DOS dispune de capacităţi de gestionare a memoriei ce pot maximiza performanţele calculatorului. 14.1.1 Memoria convenţională 264
  • 271. Primul PC compatibil IBM utiliza de obicei între 64Kb şi 256Kb memorie RAM (Read Only Memory). Pe atunci această memorie era mai mult decât suficientă. Astăzi, memoria convenţională a unui PC este formată din primul 1Mb de RAM. Programele DOS rulează, în mod obişnuit, cu primii 640Kb de memorie convenţională. PC-ul utilizează restul de 384Kb de memorie (numită memorie rezervată sau memorie superioară) pentru memoria video a calculatorului, driverele de dispozitive, alte dispozitive HARD mapate în memorie şi BIOS (Basic Input-Output Services – servicii intrare- ieşire de bază). Sistemul de operare Windows utilizează modelul de memorie virtuală pentru a gestiona memoria, ceea ce înseamnă că eliberarea memoriei convenţionale nu are semnificaţie sub acest sistem de operare. Însă, memoria convenţională este importantă când se rulează programe în cadrul unei ferestre DOS sub Windows. Structura memoriei convenţionale a unui calculator personal este următoarea: BIOS ROM Memorie rezervată Memorie video COMMAND.COM Memorie pentru programe Intrări CONFIG.SYS Nucleul DOS Zona de comunicaţii BIOS Vectori de întrerupere BIOS PC-ul împarte memoria în blocuri de 64Kb numite segmente. În mod obişnuit, programul utilizează un segment de cod (ce conţine instrucţiunile programului) şi un al doilea segment de memorie pentru date. Dacă un program este foarte mare compilatorul va trebui să dispună de mai multe segmente de cod sau de date, sau de ambele. Modelul de memorie defineşte numărul de segmente pe care le poate folosi pentru fiecare. Modele sunt foarte importante deoarece, dacă se utilizează un model de memorie necorespunzător, programul poate să 265
  • 272. nu deţină suficientă memorie pentru execuţie. Compilatorul va alege un model de memorie suficient de mare pentru a rula programul, însă cu cât memoria utilizată este mai mare cu atât viteza de execuţie a programului scade. Din această cauză trebuie ales modelul cel mai mic pentru necesităţile programului. Majoritatea compilatoarelor acceptă următoarele modele de memorie: a) tiny – combină datele şi codul programului într-un singur segment de 64Kb (este cel mai mic şi mai rapid model de memorie); b) small – utilizează un segment de memorie pentru cod şi un segment pentru date (este cel mai obişnuit model de memorie); c) medium – utilizează un segment de 64Kb pentru date şi două sau mai multe segmente pentru codul programului. În acest caz datele sunt accesate rapid prin utilizarea de adrese near, în schimb însă, apelurile de funcţii se fac utilizând adrese far; d) compact – alocă un segment de 64Kb pentru codul programului şi două sau mai multe segmente pentru date (este un model utilizat pentru programe mici ce manipulează un număr mare de date); e) large – alocă mai multe segmente atât pentru date cât şi pentru cod şi este cel mai lent model de memorie din cele prezentate până acum. El trebuie utilizat doar ca ultimă resursă; f) huge – este un model utilizat doar în cazul utilizării unor matrici mai mari de 64Kb. Pentru a stoca o astfel de matrice programul trebuie să utilizeze cuvântul cheie huge pentru a crea un pointer, astfel: int huge *matrice_uriaşă după care programul trebuie să utilizeze funcţia halloc pentru alocarea memoriei şi funcţia hfree pentru eliberarea acesteia. Exemplul următor alocă o matrice de 400000 octeţi: Exemplu: #include <stdio.h> #include <malloc.h> void main(void) { long int k; int huge *matrice_uriasa; 266
  • 273. if ((matrice_uriasa=(int huge*) halloc(100000L,sizeof (long int)))==NULL) printf(”Eroare la alocarea matricii”); else{ printf(”Completeaza matricean”); for(k=0;k<100000L;k++) matrice_uriasa[k]=k%32768; for(k=0;k<100000L;k++) printf(”%d ”,matrice_uriasa[k]); hfree(matrice_uriasa); } } Pentru selectarea unui anumit model de memorie se include, de regulă, o opţiune în cadrul liniei de comandă a compilatorului. Majoritatea compilatoarelor predefinesc o constantă specifică pentru a determina modelul curent de memorie. În tabelul următor sunt prezentate aceste constante definite de compilatoarele Microsoft C şi Borland C: Model de memorie Microsoft C Borland C Small M_I86SM _SMALL_ Medium M_I86MM _MEDIUM_ Compact M_I86CM _COMPACT_ Large M_I86LM _LARGE_ Programul poate verifica modelul de memorie utilizat folosind următoarea secvenţă de instrucţiuni: #ifndef _MEDIUM_ printf(”Programul cere modelul de memorie mediumn”); exit(1); #endif Atunci când un program trebuie să aloce memorie în mod dinamic se utilizează fie funcţia malloc pentru a aloca memorie din zona near (din segmentul curent), fie funcţia fmalloc pentru a aloca memorie far. 14.1.2 Memoria expandată În cazul programelor mari o memorie de numai 1Mb este insuficientă. Pentru a permite accesul la mai mult de 1Mb de memorie, companiile Lotus, Intel şi Microsoft au creat o specificaţie pentru memoria expandată, care combină software şi o platformă specială de memorie expandată pentru a „păcăli” PC-ul în scopul accesării unor volume mari de memorie. Mai întâi în zona de memorie superioară se alocă un bloc de 64Kb după care acest bloc de memorie este împărţit 267
  • 274. în patru secţiuni de 16Kb, numite pagini în care se încarcă paginile logice ale programului. De exemplu un program de 128Kb este împărţit în opt pagini de 16Kb fiecare care sunt încărcate în funcţie de necesităţile programului în zona rezervată de 64Kb. 14.1.3 Memoria extinsă Calculatoarele cu procesoare peste 386 utilizează adresarea pe 32 de biţi ceea ce le dă posibilitatea de accesare directă de până la 4Gb de memorie. Programatorii au numit memoria de peste 1Mb memorie extinsă. Pentru a accesa memoria extinsă, trebuie încărcat un driver de dispozitiv pentru memoria extinsă, care în DOS este de obicei himem.sys. Pentru a utiliza însă memoria extinsă este necesară trecerea la modul protejat de lucru al procesorului, mod de lucru în care datele unui program nu pot fi scrise peste datele altui program ce rulează simultan cu acesta. 14.1.4 Stiva Stiva este o regiune de memorie în cadrul căreia programele păstrează temporar datele pe durata execuţiei. De exemplu, atunci când programele transmit parametri către o funcţie, C plasează aceşti parametri în stivă. Când funcţia îşi încheie execuţia aceştia sunt scoşi din stivă. Stiva este numită astfel deoarece ultimele valori depuse sunt primele extrase. În funcţie de modelul de memorie utilizat, spaţiul de memorie ocupat de stivă diferă. Valoarea minimă a stivei este 4Kb. În cazul modelelor compact sau large, C alocă pentru stivă un întreg segment de 64Kb. Dacă un program plasează în stivă mai multe informaţii decât poate reţine aceasta, va apărea o eroare de depăşire a stivei (stack-overflow). Dacă programul a dezactivat testarea stivei, datele depuse în stivă pot fi suprapuse peste datele programului. Exemplul următor prezintă modul de determinare a dimensiunii stivei utilizând funcţia _stklen. Exemplu: #include <stdio.h> #include <dos.h> void main(void) { printf(”Dimensiunea stivei este de %d octeti”,_stklen); } 268
  • 275. 14.2 Servicii DOS şi BIOS Aşa cum am menţionat în paragraful anterior, BIOS-ul reprezintă serviciile de intrare-ieşire de bază. Pe scurt, BIOS este un cip din cadrul calculatorului ce conţine instrucţiunile pe care calculatorul le utilizează pentru a scrie pe ecran sau la imprimantă, pentru a citi caractere de la tastatură sau pentru a citi sau scrie pe disc. Programatorii au au proiectat rutinele BIOS pentru a fi utilizate de programe în limbaj de asamblare, totuşi, majoritatea compilatoarelor de C dispun de funcţiide bibliotecă ce permit utilizarea acestor servicii fără a avea nevoie de limbaje de asamblare. DOS este un sistem de operare pentru calculatoarele compatibile IBM PC. Sistemul DOS permite rularea programelor şi păstrează informaţia pe disc. În plus, sistemul DOS pune la dispoziţie servicii ce permit programelor să aloce memorie, să acceseze dispozitive, cum ar fi imprimanta, şi să gestioneze alte resurse ale sistemului. Biblioteca limbajului C oferă o interfaţă la multe servicii DOS, prin intermediul funcţiilor. Mulţi programatori confundă serviciile DOS cu serviciile BIOS. Tabelul următor prezintă relaţia dintre componenta HARD a calculatorului, serviciile BIOS, DOS şi componenta SOFT. Programe Nivel înalt DOS | BIOS | HARDWARE Nivelul cel mai jos Aşa cum se observă, BIOS este situat imediat deasupra componentei hardware, serviciiile DOS deasupra serviciilor BIOS, iar programele deasupra sistemului DOS. Uneori însă, programele pot evita serviciile DOS şi BIOS şi pot accesa direct o componentă hardware (cum este cazul memoriei video). Se recomandă ca ori de câte ori poate fi utilizată o funcţie de bibliotecă C în locul unui serviciu DOS sau BIOS, aceasta să fie utilizată pentru a mări portabilitatea programelor şi la calculatoarele ce utilizează alte sisteme de operare (WINDOWS, UNIX, etc.). În acest caz, programul nu va mai trebui modificat pentru a putea fi rulat sub WINDOWS sau UNIX. 269
  • 276. Toate versiunile de WINDOWS vor apela propriile lor servicii de sistem. Însă, serviciile de sistem WINDOWS apelează până la urmă serviciile BIOS penttru a accesa componentele hardware ale cal;culatorului. 14.2.1 Serviciile BIOS Prezentăm în continuare o serie de servicii BIOS ce pot fi accesate utilizând funcţii de bibliotecă ale limbajului C. 1) accesul la imprimantă Înainte ca un program să scrie ieşirea la imprimantă utilizând indicatorul de fişier stdprn se poate face o verificare dacă imprimanta este conectată şi dacă are hârtie utilizând funcţia biosprint din fişierul antet bios.h: int biosprint(int comanda,int octet,int nr_port) unde comanda specifică una din următoarele operaţii: 0 – tipăreşte octetul specificat; 1 – iniţializează portul imprimantei; 2 – citeşte starea imprimantei. Parametrul octet specifică valoarea ASCII a caracterului ce se doreşte a fi scris la imprimantă iar nr_port specifică portul imprimantei care poate fi 0 pentru LPT1, 1 pentru LPT2, ş.a.m.d. Funcţia biosprint returnează o valoarea înteagă pe un octet ai cărui biţi au următoarea semnificaţie: 0 – dispozitiv în pauză; 3 – eroare I/O; 4 – imprimantă selectată; 5 – lipsă hârtie; 6 – confirmare dispozitiv; 7 – dispozitivul nu este ocupat. 2) operaţii intrare/ieşire Operaţiile intrare/ieşire de nivel jos pot fi realizate utilizând funcţia biodisk ce are următoarea sintaxă: int biodisk(int operatie, int unitate, int head, int track, int sector, int nr_sector, void *buffer) unde parametrul unitate precizează numărul unităţii, care este 0 pentru A, 1 pentru B, şi aşa mai departe. Parametrii head, track, sector şi nr_sector precizează sectoarele fizice ale disculuice trebie scris sau citit. Parametru buffer este un pointer la bufferul din care sunt citite sau în care sunt scrise datele. Parametru operatie specifică funcţia dorită astfel: 270
  • 277. 0 Iniţializează sistemul de disc 1 Returnează starea ultimei operaţii pe disc 2 Citeşte numărul precizat de sectoare 3 Scrie numărul precizat de sectoare 4 Verifică numărul precizat de sectoare 5 Formatează pista specificată 6 Formatează pista specificată şi marchează sectoarele defecte 7 Formatează unitatea începând cu pista specificată 8 Returnează parametrii unităţii de disc 9 Iniţializează unitatea de disc 10 Execută o citire lungă – 512 octeţi de sector plus patru suplimentari 11 Execută o scriere lungă – 512 octeţi de sector plus patru suplimentari 12 Execută o poziţionare pe disc 13 Iniţializarea alternativă a discului 14 Citeşte bufferul sectorului 15 Scrie bufferul sectorului 16 Testează dacă unitatea este pregătită 17 Recalibrează unitatea 18 Execută diagnosticarea unităţii de RAM 19 Execută diagnosticarea unităţii 20 Execută diagnosticarea internă a controlerului Dacă se execută cu succes, funcţia returnează valoarea 0. Dacă apare o eroare, valoarea returnată precizează eroarea. 3) servicii de tastatură din BIOS Pentru accesul la serviciile de tastatură din BIOS, C-ul pune la dispoziţie funcţia _bios_keybrd ce are următoarea sintaxă: unsigned _bios_keybrd(unsigned comanda) unde parametrul comanda specifică operaţia dorită şi poate avea una din următoarele valori: _KEYBRD_READ Indică funcţiei să citească un caracter de la tastatură _KEYBRD_READY Determină dacă este prezent un caracter la bufferul tastaturii. Dacă funcţia returnează 0, înseamnă că nici o intrare de la tastatură nu este prezentă. Dacă valoarea returnată este 0xFFFF, utilizatorul a apăsat CTRL C _KEYBRD_SHIFTSTATUS Returnează starea tastelor de control: Bit 7 – INS este activat Bit 6 – CAPSLOCK este activat Bit 5 – NUMLOCK este activat 271
  • 278. Bit 4 – SCRLLOCK este activat Bit 3 – ALT este apăsată Bit 2 – CTRL este apăsată Bit 1 – SHIFT stânga este apăsată Bit 0 – SHIFT dreapta este apăsată _NKEYBRD_READ Indică funcţiei să citească un caracter de la tastatură, inclusiv tastele speciale, cum ar fi tastele cu săgeţi _NKEYBRD_READY Determină dacă este prezent un caracter la bufferul tastaturii. Dacă funcţia returnează 0, înseamnă că nici o intrare de la tastatură nu este prezentă. Dacă valoarea returnată este 0xFFFF, utilizatorul a apăsat CTRL C Funcţia acceptă inclusiv tastele speciale, cum ar fi tastele cu săgeţi _NKEYBRD_SHIFTSTATUS Returnează starea tastelor de control, inclusiv a tastelor speciale: Bit 15 – SYSREQ este activat Bit 14 – CAPSLOCK este activat Bit 13 – NUMLOCK este activat Bit 12 – SCRLLOCK este activat Bit 11 – ALT dreapta este apăsată Bit 10 – CTRL dreapta este apăsată Bit 9 – ALT stânga este apăsată Bit 8 – CTRL stânga este apăsată 4) obţinerea listei cu echipamente din BIOS Unele programe necesită determinarea caracteristicilor hardware ale calculatorului. Pentru aceasta se utilizează funcţia _bios_equiplist care are următoarea sintaxă: unsigned _bios_equiplist(void); Funcţia returnează o valoare pe 16 biţi a căror valoare are următoarea semnificaţie: 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 15:14 – numărul de imprimante paralele instalate (de la 0 la 3); 13 – imprimanta serială; 12 – adaptorul de jocuri; 11:10:9 – numărul de porturi seriale COM (de la 0 la 7); 8 – prezenţa DMA (Direct Memory Acces); bitul are valoarea 0 dacă există DMA şi 1 dacă nu există; 7:6 – numărul drieverelor de disc; 272
  • 279. 5:4 – modul video: 00-neutilizat, 01-mod video 40x25 mono, 10-mod video 80x25 color, 11-mod video 80x25 mono; 3:2 – dimensiunea memorie RAM: 00-16Kb, 01-32Kb, 10-48Kb, 11-64Kb; 1 – prezenţa coprocesorului matematic; 0 – prezenţa unităţii de disc flexibile. 5) controlul intrărilor şi ieşirilor pentru portul serial Pentru a executa operaţii intrare/ieşire utilizând portul serial se utilizează funcţia bioscom ce are următoarea sintaxă: unsigned bioscom(int comanda,int port,char octet); Parametrul comanda specifică operaţia dorită şi poate avea una din următoarele valori: _COM_INIT Stabileşte valorile pentru comunicare ale portului _COM_RECEIVE Primeşte un octet de la port _COM_SEND Trimite un octet la port _COM_STATUS Returnează valorile portului Parametrul port specifică portul serial ce se doreşte a fi utilizat, unde 0 corespunde lui COM1, 1 lui COM2 şi aşa mai departe. Parametrul octet specifică fie octetul pentru ieşire, fie valorile de comunicare dorite. 6) determinarea volumului de memorie convenţională BIOS Pentru a determina memoria convenţională ce poate fi utilizată de către un proggram se utilizează funcţia biosmemory ce are următoarea sintaxă: int biosmemory(void); Valoarea returnată de această funcţie nu cuprinde memoria extinsă, expandată sau superioară. 7) citirea cronometrului BIOS BIOS are incorporat un ceas intern ce bate de 18.2 ori pe secundă. Acest cronometru este util pentru a genera punctul iniţial al unui generator de numere aleatoare. Multe compilatoare de C pun la dispoziţie două funcţii pentru accesul la cronometrul BIOS: biostime şi _bios_timeofday. Sintaxa acestor funcţii este următoarea: long biostime(int operatie,long timp_nou); Parametrul operaţie poate lua două valori: 0 – dacă se doreşte ca funcţia să citească valoarea curentă a cronometrului; 1 – pentru a fixa valoarea cronometrului la valoarea timp_nou. long _bios_timeofday(int operatie,long *batai); 273
  • 280. Această funcţie poate fi, de asemenea, utilizată pentru a citi sau a fixa cronometrul BIOS. 14.2.2 Serviciile DOS În acest paragraf prezentăm o serie de servicii DOS ce pot fi accesate utilizând funcţii de bibliotecă ale limbajului C. 1) suspendarea temporară a unui program Execuţia unui program poate fi suspendată temporar utilizând funcţia sleep.h din fişierul antet dos.h: void sleep(unsigned secunde); parametrul secunde specificând numărul de secunde pe care este suspendat programul. 2) utilizarea sunetelor Generarea de sunete ce utilizează difuzorul calculatorului se realizează utilizând funcţiile sound şi nosound: void sound(unsigned frecventa) generează un sunet cu frecvenţa frecventa; void sound(unsigned frecventa) deconectează difuzorul. Programul următor generează un sunet de sirenă dezactivat la apăsarea unei taste: Exemplu: #include <dos.h> #include <conio.h> void main() { unsigned frecventa; do{ for (frecventa=500;frecventa<=1000;frecventa+=50) { sound(frecventa); delay(50); } for (frecventa=1000;frecventa>=500;frecventa-=50) { sound(frecventa); delay(50); } } while(!kbhit()); nosound(); } 3) obţinerea de informaţii despre erori în DOS În cazul în care un serviciu al sistemului DOS eşuează, programele pot cere informaţii suplimentare despre acea eroare folosind funcţia dosexterr: int dosexterr(struct DOSERROR *info_eroare); unde structura DOSERROR are următoarele câmpuri: struct DOSERROR{ int de_exterror; //eroare int de_class; //clasa erorii int de_action;//actiune recomandata 274
  • 281. int de_locus;//sursa erorii }; Dacă funcţia returnează valoarea 0, apelul serviciului DOS nu a avut nici o eroare. Clasa erorii descrie categotia erorii, astfel: 01H Resurse depăşite 02H Eroare temporară 03H Eroare de autorizare 04H Eroare de sistem 05H Eroare hardware 06H Eroare de sistem nedatorată programului curent 07H Eroare de aplicaţie 08H Articol neîntâlnit 09H Format nevalid 0AH Articol blocat 0BH Eroare de suport 0CH Articolul există 0DH Eroare necunoscută Parametrul de_action indică programului cum să răspundă erorii, astfel: 01H Mai întâi încearcă din nou, apoi cere intervenţia utilizatorului 02H Încearcă din nou, cu o întârziere, apoi cere intervenţia utilizatorului 03H Cere intervenţia utilizatorului pentru soluţie 04H Renunţă şi elimină 05H Renunţă, dar nu elimina 06H Ignoră eroarea 07H Încearcă din nou după intervenţia utilizatorului Parametrul de_locus specifică sursa erorii, astfel: 01H Sursă necunoscută 02H Eroare de dispozitiv bloc 03H Eroare de reţea 04H Eroare de dispozitiv serial 05H Eroare de memorie 4) citirea valorilor registrului segment Codul programului, datele şi stiva sunt controlate de compilator utilizând patru registre de segment: CS, DS, ES, SS. În unele cazuri este necesar să se cunoască valoarea acestor registre. Pentru astfel de cazuri se utillizează funcţia segread: 275
  • 282. void segread(struct SREGS *segs); Structura SREGS are următoarele câmpuri: struct SREGS { unsigned int es; unsigned int cs; unsigned int ss; unsigned int ds; } 5) accesul la valorile de port Pentrul controlul hardware de nivel inferior, compilatoarele de C pun la dispoziţie următoarele funcţii: - int inport (int adresa_port); - citeşte un cuvânt de la portul specificat de parametrul adresa_port; - int inportb (int adresa_port); - citeşte un octet de la portul specificat de parametrul adresa_port; - int outport (int adresa_port); - scrie un cuvânt de la portul specificat de parametrul adresa_port; - int outportb (int adresa_port); - scrie un octet de la portul specificat de parametrul adresa_port; 6) suspendarea unui program Pentru suspendarea unui program pe un anumit interval de timp se poate utiliza funcţia delay, similară funcţiei sleep. Funcţia delay are însă ca parametru o constantă exprimată în milisecunde: void delay(unsigned milisecunde); 7) apelarea unei comenzi interne DOS Pentru apelarea unei comenzi DOS sau a unui fişier pentru comenzi se utilizează funcţia system: int system(const char *comanda); Parametrul comanda este un şir de caracter care conţine numele comenzii DOS sau a fişierului de comenzi. Dacă funcţia reuşeşte să execute comanda, se returnează valoarea 0, altfel returnează -1. Programul următor prezintă utilizarea funcţiei system. Exemplu: #include <stdlib.h> #include <stdio.h> void main(void) { if(system("DIR")) printf("EROARE!n"); } 8) lucrul cu vectori de întrerupere Un vector de întrerupere este o adresă de segment şi de deplasament a codului care tratează o anumită întrerupere. 276
  • 283. Determinarea vectorului de întrerupere se realizează utilizând funcţia _dos_getvect în modul următor: void interrupt(* _dos_getvect(unsigned nr_intr))(); Parametrul nr_intr specifică numărul întreruperii dorite ce poate avea valori de la 0 la 255. Programul următor va afişa vectorii pentru toate întreruperile calculatorului: Exemplu: #include <stdio.h> #include <dos.h> void main(void) { int k; for(k=0;k<=255;k++) printf(”Intrerupere: %x Vector %lxn”,k, _dos_getvect(k)); } Dacă se doreşte crearea unui program de tratare a unei întreruperi, vectorul de întrerupere trebuie atribuit acestui program. Această atribuire se realizează cu ajutorul funcţiei _dos_setvect: void _dos_setvect(unsigned nr_intr, void interrupt(* handler)()); Parametrul nr_intr specifică întreruperea al cărui vector trebuie modificat. Pentru activarea şi dezactivarea întreruperilor se utilizează funcţiile: void _disable(void); void _enable(void); Dacă se doreşte reactivarea întreruperii originare se utilizează funcţia _chain_interrupt: void chain_interrupt(void(interrupt far *handler)()); Generarea unei întreruperi se realizează folosind funcţia geninterrupt: void geninterrupt(int intrerupere); unde parametrul intrerupere specifică întreruperea generată. 14.3 Bibliotecile C Dacă se examinează fişierele ce însoţesc un compilator C, se remarcă multe fişiere cu extensia LIB. Aceste fişiere conţin biblioteci obiect. Atunci când este compilat şi link-editat un program, editorul de legături examinează fişierele LIB pentru a rezolva referinţele la funcţii. Când sunt create funcţii utile ce sunt necesare şi în alte 277
  • 284. programe, se pot construi biblioteci în care aceste funcţii să fie păstrate. 14.3.1 Reutilizarea unui cod obiect În cazul creării unei funcţii utile care se doreşte reutilizată, se poate compila fişierul ce conţine funcţia respectivă pentru a crea codul obiect (de exemplu din fişierul funcţie.c prin compilare se obţine fişierul obiect funcţie.obj). Funcţia definită în acest fişier obiect poate fi reutilizată în alt program utilizând următoarea instrucţiune: C:>bc fisier_nou.c funcţie.obj Totuşi, acest mod de a reutiliza codul unor funcţii este destul de dificil de utilizat în cazul în care se doreşte reutilizarea unui număr mare de funcţii aflate în fişiere obiect separate. 14.3.2 Lucrul cu fişiere bibliotecă Operaţiile acceptate de fişierele bibliotecă sunt următoarele: - crearea unei biblioteci; - adăugarea unuia sau mai multor fişiere obiect la bibliotecă; - înlocuirea unui fişier obiect cu altul; - ştergerea unuia sau mai multor fişiere obiect din bibliotecă; - listarea rutinelor pe care le conţine biblioteca. În funcţie de compilator, numele programului de bibliotecă şi opţiunile liniei de comandă pe care programul le acceptă vor diferi. În continuare prezentăm operaţiile ce pot fi realizate cu funcţiile bibliotecă utilizând programul TLIB al compilatorului Borland C. Presupunem că în urma compilării am creat fişierul obiect funcţie.obj ce conţine o serie de funcţii pe care dorim să le păstrăm într-o bibliotecă. Crearea unei bilioteci biblioteca.lib care să conţină acest fişier obiect se realizează cu următoarea linie de comandă: C:>tlib biblioteca.lib + functie.obj După ce fişierul bibliotecă a fost creat, funcţiile acestuia sunt disponibile pentru compilarea şi legarea noilor programe. Funcţia de biliotecă TLIB a compilatorului Borland C are următoarea sintaxă: tlib cale comandă, fişier unde: - cale – este un şir de caractere care specifică calea până la bilioteca asupra căreia se efectuează operaţia; - comandă – este formată dintr-un simbol şi numele unui fişier obiect. Simbol poate fi unul din caracterele: + (adaugă un 278
  • 285. modul la bibliotecă), - (elimină un modul din bibliotecă), * (extrage un modul din bibliotecă într-un fişier cu acelaşi nume, fără al elimina), -+ (înlocuieşte un modul din bibliotecă), -* (extrage şi elimină un modul din bibliotecă); - fisier – reprezintă numele fişierul în care se scrie ieşirea operaţiei efectuate asupra bibliotecii. 14.3 Fişierele antet Fiecare program foloseşte una sau mai multe instrucţiuni #include pentru a cere compilatorului de C să folosească instrucţiunile incluse într-un fişier antet. Când compilatorul întâlneşte o instrucţiune #include în program, el compilează codul din fişierul antet ca şi cum ar fi scris în fişierul sursă. Fişierele antet conţin definiţii frecvent utilizate şi furnizează compilatorului informaţii referitoare la funcţiile sale. Dacă la compilarea programului se afişează un mesaj de eroare, avertizând că nu se poate deschide un anumit fişier antet, trebuie verificat subdirectorul care conţine fişierele antet, pentru a vedea dacă acel fişier există sau nu. Dacă se găseşte fişierul respectiv, în linia de comandă din sistemul de operare DOS trebuie scrisă următoarea instrucţiune: C:>SET INCLUDE=C:BORLANDCINCLUDE BIBLIOGRAFIE 1. Plum T., Learning to program in C, Prentice Hall, 1983 2. Auslander D.,Tham C., Real-time software for control: program examples in C, Prentice Hall, 1990. 279
  • 286. 3. Schild H., Using Turbo C, Borland, Osborne / McGraw Hill, 1988. 4. Holzner S., Borland C++ Programming, Brady Books, New York, 1992. 5. Somnea D., Turturea D., Introducere în C++, Programarea orientatã pe obiecte, Ed. Tehnicã, Bucureşti, 1993. 6. Marian Gh., Bãdicã C., Pãdeanu L., Limbajul PASCAL, Indrumar de laborator, Reprografia Universitãţii din Craiova, 1993. 7. Negrescu L., Introducere în limbajul C, Editura MicroInformatica, Cluj Napoca, 1993. 8. Petrovici V., Goicea F., Programarea în limbajul C, Editura Tehnicã, Bucureşti, 1993. 9. Marian Gh., Muşatescu C., Laşcu M., Iordache Şt., Limbajul C, Editura ROM TPT, Craiova, 1999. 10. Mocanu M., Ghid de programare în limbajele C/C++, Editura SITECH, Craiova, 2001. 11. Zaharia, M.D., Structuri de date şi algoritmi. Exemple în limbajele C şi C++, Ed. Albastră, Cluj Napoca, 2002. 12. Kernighan, B.W., Ritchie, D.M., The C programming languages, Englewood. Cliffs, N.J. Prentice-Hall, 1978. 13. Bulac, C., Iniţiere în Turbo C++ şi Borland C, Editura Teora, Bucureşti, 1995. 280